fix(web): keep sidebar scrollbar inside inset
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* WorkspaceBrowser scroll-region style contract, asserted against the CSS text
|
||||
* on disk: the session list keeps one stable right inset for row hover fills,
|
||||
* with or without overflow, while outer clip seats add no hidden second inset.
|
||||
* WorkspaceBrowser spacing contract, asserted against the CSS text on disk:
|
||||
* row fills share the shell's trailing inset, the stable scrollbar counts
|
||||
* inside it, and flat, grouped, and search views keep their intended rhythm.
|
||||
*/
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
@@ -10,46 +10,56 @@ import { describe, expect, it } from 'vitest'
|
||||
const css = readFileSync(fileURLToPath(new URL('../src/client/WorkspaceBrowser.module.css', import.meta.url)), 'utf8')
|
||||
|
||||
/**
|
||||
* Declarations of one class rule, keyed by property with whitespace collapsed.
|
||||
* Declarations of one selector rule, keyed by property with whitespace collapsed.
|
||||
* Declaration order and trailing semicolons are normalized away.
|
||||
* @param className - local class name, without the leading dot.
|
||||
* @param selector - one exact selector, including a leading dot for local classes.
|
||||
* @returns the rule's declarations, or undefined when no such rule exists.
|
||||
*/
|
||||
function declarations(className: string): Map<string, string> | undefined {
|
||||
function declarations(selector: string): Map<string, string> | undefined {
|
||||
const withoutComments = css.replace(/\/\*[\s\S]*?\*\//g, ' ')
|
||||
const match = new RegExp(String.raw`(^|[\s,}])\.${className}\s*\{([^{}]*)\}`).exec(withoutComments)
|
||||
if (match === null) return undefined
|
||||
const found = new Map<string, string>()
|
||||
// The body group is unconditional in the pattern; the fallback only satisfies
|
||||
// noUncheckedIndexedAccess.
|
||||
for (const part of (match[2] ?? '').split(';')) {
|
||||
const colon = part.indexOf(':')
|
||||
if (colon === -1) continue
|
||||
found.set(part.slice(0, colon).trim(), part.slice(colon + 1).trim().replace(/\s+/g, ' '))
|
||||
for (const [, selectorList = '', body = ''] of withoutComments.matchAll(/([^{}]+)\{([^{}]*)\}/g)) {
|
||||
if (!selectorList.split(',').map(value => value.trim()).includes(selector)) continue
|
||||
const found = new Map<string, string>()
|
||||
for (const part of body.split(';')) {
|
||||
const colon = part.indexOf(':')
|
||||
if (colon === -1) continue
|
||||
found.set(part.slice(0, colon).trim(), part.slice(colon + 1).trim().replace(/\s+/g, ' '))
|
||||
}
|
||||
return found
|
||||
}
|
||||
return found
|
||||
return undefined
|
||||
}
|
||||
|
||||
describe('WorkspaceBrowser.module.css list', () => {
|
||||
const root = declarations('root')
|
||||
const listArea = declarations('listArea')
|
||||
const list = declarations('list')
|
||||
const treeBody = declarations('treeBody')
|
||||
const root = declarations('.root')
|
||||
const listArea = declarations('.listArea')
|
||||
const list = declarations('.list')
|
||||
|
||||
it('is the scrolling region', () => {
|
||||
expect(list).toBeDefined()
|
||||
expect(list!.get('overflow-y')).toBe('auto')
|
||||
})
|
||||
|
||||
it('keeps row backgrounds edge-flush with the scrolling region', () => {
|
||||
expect(root?.get('padding-right')).toBe('12px')
|
||||
expect(listArea?.get('margin-right')).toBe('-12px')
|
||||
expect(treeBody?.get('margin-right')).toBeUndefined()
|
||||
expect(list?.get('margin-right')).toBeUndefined()
|
||||
expect(list?.get('padding-right')).toBe('var(--dsh-session-list-edge-inset)')
|
||||
it('counts the themed scrollbar inside the shell trailing inset', () => {
|
||||
expect(root?.get('--dsh-session-list-edge-inset')).toBe('var(--dsh-sidebar-inline-padding)')
|
||||
expect(root?.get('--dsh-session-list-scrollbar-width')).toBe('8px')
|
||||
expect(root?.get('padding-right')).toBe('var(--dsh-session-list-edge-inset)')
|
||||
expect(listArea?.get('margin-right')).toBe('calc(-1 * var(--dsh-session-list-edge-inset))')
|
||||
expect(declarations('.fade')?.get('right')).toBe('var(--dsh-session-list-edge-inset)')
|
||||
expect(list?.get('padding-right')).toBe(
|
||||
'calc(var(--dsh-session-list-edge-inset) - var(--dsh-session-list-scrollbar-width))',
|
||||
)
|
||||
expect(declarations('.list::-webkit-scrollbar')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('reserves the scrollbar inside the stable visual inset', () => {
|
||||
it('reserves the scrollbar whether or not the list overflows', () => {
|
||||
expect(list!.get('scrollbar-gutter')).toBe('stable')
|
||||
})
|
||||
|
||||
it('keeps 2px between rows and 4px between workspace groups', () => {
|
||||
expect(declarations('.flatList > * + *')?.get('margin-top')).toBe('2px')
|
||||
expect(declarations(".searchTree > [role='treeitem'] + [role='treeitem']")?.get('margin-top')).toBe('2px')
|
||||
expect(declarations('.groupSection > * + *')?.get('margin-top')).toBe('2px')
|
||||
expect(declarations('.groupSection + .groupSection')?.get('margin-top')).toBe('4px')
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user