Skip to content

Commit

Permalink
✨ Smooth tab reordering
Browse files Browse the repository at this point in the history
  • Loading branch information
trickypr committed Mar 30, 2024
1 parent 962177c commit 319d130
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 63 deletions.
13 changes: 12 additions & 1 deletion apps/content/src/browser/components/Tab.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@
} from '../windowApi/WindowTabs.js'
import { readable } from 'svelte/store'
import { dragTabIds, dragTabsTranslation } from './tabs__drag.js'
import { derived } from '@amadeus-it-group/tansu'
/** @type {WebsiteView} */
export let view
/** @type {number} */
export let index
const iconUrl = readable(view.iconUrl, (set) => {
view.events.on('changeIcon', set)
Expand All @@ -27,6 +30,8 @@
return () => view.events.off('changeTitle', set)
})
const transform = derived([dragTabIds, dragTabsTranslation], ([dragTabIds, dragTabsTranslation]) => dragTabIds.includes(view.windowBrowserId) ? `translateY(${dragTabsTranslation}px)` : '')
$: isActive = $activeTabId === view.windowBrowserId
$: isSelected = $selectedTabIds.includes(view.windowBrowserId)
Expand Down Expand Up @@ -89,12 +94,13 @@
role="tab"
id={`tab-${view.windowBrowserId}`}
aria-selected={isActive}
data-index={index}
data-not-active-selected={isSelected}
data-window-browser-id={view.windowBrowserId}
data-dragging={$dragTabIds.includes(view.windowBrowserId)}
aria-controls={`website-view-${view.windowBrowserId}`}
tabindex={isActive ? 0 : -1}
style:transform={$dragTabIds.includes(view.windowBrowserId) ? `translateY(${$dragTabsTranslation}px)` : ''}
style:transform={$transform}
on:mousedown={(event) => {
if (event.ctrlKey) {
addSelectedTabs([view.windowBrowserId])
Expand Down Expand Up @@ -185,6 +191,7 @@
width: 100%;
border-radius: 1rem;
margin-bottom: 0.25rem;
display: flex;
align-items: center;
Expand All @@ -208,6 +215,10 @@
pointer-events: none;
}
button[role='tab'][data-dragging='false'] {
transition: transform 0.2s;
}
img {
width: 1.5rem;
height: 1.5rem;
Expand Down
5 changes: 2 additions & 3 deletions apps/content/src/browser/components/Tabs.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
</script>

<ul role="tablist" on:dragstart={TabDragApi.dragStart} on:dragend={TabDragApi.dragEnd} on:dragover={TabDragApi.dragOver}>
{#each $windowTabs as tab (tab.view.windowBrowserId)}
<Tab view={tab.view} />
{#each $windowTabs as tab, index (tab.view.windowBrowserId)}
<Tab view={tab.view} {index} />
{/each}

<div class="spacer" />
Expand Down Expand Up @@ -42,7 +42,6 @@
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.spacer {
Expand Down
121 changes: 62 additions & 59 deletions apps/content/src/browser/components/tabs__drag.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// @ts-check
import { batch, writable } from '@amadeus-it-group/tansu'
import { writable } from '@amadeus-it-group/tansu'

import { browserImports } from '../browserImports.js'
import {
Expand All @@ -11,14 +11,14 @@ import {
const WINDOW_ID_TYPE = 'text/x-fushra-window-id'
const WINDOW_BROWSER_IDS_TYPE = 'text/x-fushra-window-browser-ids'

const ANIM_SCREEN_Y_TYPE = 'number/x-fushra-screen-y'

/** @type {import('@amadeus-it-group/tansu').WritableSignal<number[]>} */
export let dragTabIds = writable([])
export let dragTabsTranslation = writable(0)

let startEventRelativeY = 0
let lastScreenY = 0
let startingIndex = 0
let expectedIndex = 0
/** @type {HTMLCanvasElement | null} */
let dndCanvas = null
/** @type {XULPanel | null} */
Expand Down Expand Up @@ -48,6 +48,7 @@ export function dragStart(event) {
const rect = target.getBoundingClientRect()

startEventRelativeY = event.screenY - rect.y
startingIndex = Number(target.dataset.index)
}

if (dragTabIds().length != 1) {
Expand Down Expand Up @@ -118,11 +119,46 @@ export function dragOver(event) {
* @param {DragEvent} event
*/
export function dragEnd(event) {
windowTabs.update((tabs) => {
if (dragTabIds().length == 0) {
return tabs
}

const targetBrowserId =
tabs[Math.min(expectedIndex, tabs.length - 1)].view.windowBrowserId
if (dragTabIds()[0] == targetBrowserId) {
return tabs
}

const draggingTabs = tabs.filter((tab) =>
dragTabIds().includes(tab.view.windowBrowserId),
)
return tabs
.filter((tab) => !dragTabIds().includes(tab.view.windowBrowserId))
.flatMap((tab) => {
if (tab.view.windowBrowserId == targetBrowserId) {
if (expectedIndex < startingIndex) {
return [...draggingTabs, tab]
}

return [tab, ...draggingTabs]
}

return tab
})
})

dragTabIds.set([])
dragTabsTranslation.set(0)

startEventRelativeY = 0
lastScreenY = 0

document
.querySelectorAll("button[role='tab']")
.forEach(
(/** @type {HTMLButtonElement} */ tab) => void (tab.style.transform = ''),
)
}

function groupDraggedTabs() {
Expand Down Expand Up @@ -173,62 +209,29 @@ function animateTabMove(event) {
return
}

batch(() => {
let indexChange = 0
/** @type {HTMLButtonElement} */
const firstDraggingTab = document.getElementById(`tab-${dragTabIds()[0]}`)
/** @type {HTMLLIElement} */
const firstPresentation = firstDraggingTab?.parentElement
const presentationBox = firstPresentation.getBoundingClientRect()

/** @type {HTMLElement} */
const target = event.target
const targetBrowserIdRaw = target.dataset.windowBrowserId
if (targetBrowserIdRaw) {
const targetBrowserId = Number(targetBrowserIdRaw)

windowTabs.update((tabs) => {
if (dragTabIds().length == 0) {
return tabs
}

const draggingTabIndex = tabs.findIndex((tab) =>
dragTabIds().includes(tab.view.windowBrowserId),
)
const dragTargetIndex = tabs.findIndex(
(tab) => tab.view.windowBrowserId === targetBrowserId,
)

indexChange = dragTargetIndex - dragTargetIndex

const draggingTabs = tabs.filter((tab) =>
dragTabIds().includes(tab.view.windowBrowserId),
)
return tabs
.filter((tab) => !dragTabIds().includes(tab.view.windowBrowserId))
.flatMap((tab) => {
if (tab.view.windowBrowserId == targetBrowserId) {
if (dragTargetIndex == 0) {
return [...draggingTabs, tab]
}

return [tab, ...draggingTabs]
}

return tab
})
})
}

/** @type {HTMLButtonElement} */
const dragTrigger = document.getElementById(`tab-${dragTabIds()[0]}`)
const presentationElement = dragTrigger.parentElement

const presentationBox = presentationElement?.getBoundingClientRect()
dragTabsTranslation.set(
event.screenY -
presentationBox?.y -
startEventRelativeY -
indexChange * presentationBox?.height,
)

dragTrigger.style.transform = `translateY(${dragTabsTranslation()}px)`
dragTabsTranslation.set(
event.screenY - presentationBox.y - startEventRelativeY,
)

lastScreenY = event.screenY
})
expectedIndex = Math.floor(event.screenY / presentationBox.height)
const translateAmount = presentationBox.height * dragTabIds().length

document
.querySelectorAll("button[role='tab'][data-dragging='false']")
.forEach((/** @type {HTMLButtonElement} */ tab) => {
const index = Number(tab.dataset.index)
if (index <= expectedIndex && index >= startingIndex) {
tab.style.transform = `translateY(-${translateAmount}px)`
} else if (index >= expectedIndex && index <= startingIndex) {
tab.style.transform = `translateY(${translateAmount}px)`
} else {
tab.style.transform = ''
}
})
}

0 comments on commit 319d130

Please sign in to comment.