1.1.7: TAB hides the asset + properties asides for a full-width stage
Press TAB to slide the asset sidebar and the properties aside off-screen and let the stage absorb their grid columns; press again to bring them back. Skipped while typing in inputs so form-field focus traversal keeps working.
This commit is contained in:
1 parent
b5c5270256
commit
492f25f629
4 files changed
+92
-3
No files matched your search
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "ruwio",
|
"name": "ruwio",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.1.6",
|
"version": "1.1.7",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
+48
-1
@@ -85,6 +85,12 @@ function readStoredTimelineHeight(): number {
|
|||||||
|
|
||||||
const timelineHeight = ref(readStoredTimelineHeight())
|
const timelineHeight = ref(readStoredTimelineHeight())
|
||||||
|
|
||||||
|
// Hide the asset sidebar + properties aside on TAB so the stage
|
||||||
|
// gets the full width. Toggled, not held — press TAB again to bring
|
||||||
|
// them back. Session-only state; we don't persist this so reopening
|
||||||
|
// the app always starts with the panels visible.
|
||||||
|
const panelsHidden = ref(false)
|
||||||
|
|
||||||
function maxTimelineHeight(): number {
|
function maxTimelineHeight(): number {
|
||||||
return Math.max(MIN_TIMELINE_HEIGHT, window.innerHeight - MAX_TIMELINE_HEIGHT_MARGIN)
|
return Math.max(MIN_TIMELINE_HEIGHT, window.innerHeight - MAX_TIMELINE_HEIGHT_MARGIN)
|
||||||
}
|
}
|
||||||
@@ -93,6 +99,8 @@ const appStyle = computed(() => ({
|
|||||||
'--timeline-height': `${timelineHeight.value}px`,
|
'--timeline-height': `${timelineHeight.value}px`,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
const appClass = computed(() => ({ 'panels-hidden': panelsHidden.value }))
|
||||||
|
|
||||||
let resizeStartY = 0
|
let resizeStartY = 0
|
||||||
let resizeStartHeight = 0
|
let resizeStartHeight = 0
|
||||||
let resizePointerId = -1
|
let resizePointerId = -1
|
||||||
@@ -366,6 +374,22 @@ function onKeyDown(e: KeyboardEvent) {
|
|||||||
testsOpen.value = true
|
testsOpen.value = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// TAB toggles the side panels (asset sidebar + properties aside)
|
||||||
|
// so the user can give the stage the full window width on demand.
|
||||||
|
// Skipped while typing — TAB still has to walk between form fields
|
||||||
|
// inside dialogs / inputs as expected.
|
||||||
|
if (
|
||||||
|
e.key === 'Tab' &&
|
||||||
|
!e.ctrlKey &&
|
||||||
|
!e.metaKey &&
|
||||||
|
!e.altKey &&
|
||||||
|
!e.shiftKey &&
|
||||||
|
!isEditable(e.target)
|
||||||
|
) {
|
||||||
|
e.preventDefault()
|
||||||
|
panelsHidden.value = !panelsHidden.value
|
||||||
|
return
|
||||||
|
}
|
||||||
// Arrow-key nudge: 1px step, 10px with Shift. Intercepted before
|
// Arrow-key nudge: 1px step, 10px with Shift. Intercepted before
|
||||||
// any other key handler so the stage steals the event from browser
|
// any other key handler so the stage steals the event from browser
|
||||||
// scroll / selectable regions. Skipped when the user is typing in
|
// scroll / selectable regions. Skipped when the user is typing in
|
||||||
@@ -499,7 +523,7 @@ onBeforeUnmount(() => {
|
|||||||
<div class="root">
|
<div class="root">
|
||||||
<div class="shell">
|
<div class="shell">
|
||||||
<ClipartDrawer />
|
<ClipartDrawer />
|
||||||
<div class="app" :style="appStyle">
|
<div class="app" :class="appClass" :style="appStyle">
|
||||||
<DocumentDrawer />
|
<DocumentDrawer />
|
||||||
<header class="topbar">
|
<header class="topbar">
|
||||||
<div class="brand-area">
|
<div class="brand-area">
|
||||||
@@ -691,6 +715,15 @@ onBeforeUnmount(() => {
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
transition: grid-template-columns 220ms var(--ease, ease);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TAB-toggle: collapse the side columns so the stage takes the
|
||||||
|
full width. The asides themselves slide off via the .sidebar /
|
||||||
|
.properties transform rules below for a pop-out feel; the grid
|
||||||
|
transition smooths the stage's resize. */
|
||||||
|
.app.panels-hidden {
|
||||||
|
grid-template-columns: 0 1fr 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.topbar {
|
.topbar {
|
||||||
@@ -856,6 +889,13 @@ onBeforeUnmount(() => {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
transition: transform 220ms var(--ease, ease), opacity 180ms var(--ease, ease);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app.panels-hidden .sidebar {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-scroll {
|
.sidebar-scroll {
|
||||||
@@ -920,6 +960,13 @@ onBeforeUnmount(() => {
|
|||||||
background: var(--panel);
|
background: var(--panel);
|
||||||
border-left: 1px solid var(--border);
|
border-left: 1px solid var(--border);
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
transition: transform 220ms var(--ease, ease), opacity 180ms var(--ease, ease);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app.panels-hidden .properties {
|
||||||
|
transform: translateX(100%);
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.timeline {
|
.timeline {
|
||||||
|
|||||||
@@ -4621,6 +4621,48 @@ export function registerAllTests(): void {
|
|||||||
const btn = document.querySelector('button[aria-label="Create a new track group"]')
|
const btn = document.querySelector('button[aria-label="Create a new track group"]')
|
||||||
expect(btn).toBeTruthy()
|
expect(btn).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('TAB toggles .panels-hidden on the .app grid', async () => {
|
||||||
|
// Pressing TAB from the document root flips a class on .app
|
||||||
|
// that collapses the side / props grid columns. A second TAB
|
||||||
|
// restores them. Skipped while the user is typing in an input.
|
||||||
|
const app = document.querySelector('.app') as HTMLElement
|
||||||
|
expect(app).toBeTruthy()
|
||||||
|
// Reset to a known baseline — preceding tests may have left
|
||||||
|
// the panels in either state.
|
||||||
|
while (app.classList.contains('panels-hidden')) {
|
||||||
|
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab', bubbles: true }))
|
||||||
|
await nextTick()
|
||||||
|
}
|
||||||
|
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab', bubbles: true }))
|
||||||
|
await nextTick()
|
||||||
|
expect(app.classList.contains('panels-hidden')).toBe(true)
|
||||||
|
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab', bubbles: true }))
|
||||||
|
await nextTick()
|
||||||
|
expect(app.classList.contains('panels-hidden')).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('TAB inside an input does not toggle the panels', async () => {
|
||||||
|
// Typed-text inputs need TAB for focus traversal; the global
|
||||||
|
// toggle bails out via isEditable().
|
||||||
|
const app = document.querySelector('.app') as HTMLElement
|
||||||
|
while (app.classList.contains('panels-hidden')) {
|
||||||
|
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab', bubbles: true }))
|
||||||
|
await nextTick()
|
||||||
|
}
|
||||||
|
const input = document.createElement('input')
|
||||||
|
input.type = 'text'
|
||||||
|
document.body.appendChild(input)
|
||||||
|
try {
|
||||||
|
input.dispatchEvent(
|
||||||
|
new KeyboardEvent('keydown', { key: 'Tab', bubbles: true }),
|
||||||
|
)
|
||||||
|
await nextTick()
|
||||||
|
expect(app.classList.contains('panels-hidden')).toBe(false)
|
||||||
|
} finally {
|
||||||
|
document.body.removeChild(input)
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
suite('app — arrow-key nudge', () => {
|
suite('app — arrow-key nudge', () => {
|
||||||
|
|||||||
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
export const APP_VERSION = '1.1.6'
|
export const APP_VERSION = '1.1.7'
|
||||||
export const APP_NAME = 'RUWIO'
|
export const APP_NAME = 'RUWIO'
|
||||||
Reference in new issue
Block a user