chore: merge dev into v2 (#35591)

Co-authored-by: Frank <frank@anoma.ly>
Co-authored-by: Aarav Sareen <96787824+arvsrn@users.noreply.github.com>
Co-authored-by: Brendan Allan <git@brendonovich.dev>
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: Jack <jack@anoma.ly>
Co-authored-by: Brendan Allan <14191578+Brendonovich@users.noreply.github.com>
Co-authored-by: Shoubhit Dash <shoubhit2005@gmail.com>
Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: James Long <longster@gmail.com>
Co-authored-by: Dustin Deus <deusdustin@gmail.com>
Co-authored-by: starptech <starptech@starptechs-MBP.fritz.box>
Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com>
Co-authored-by: 𝓛𝓲𝓽𝓽𝓵𝓮 𝓕𝓻𝓪𝓷𝓴 <little-frank@opencord.local>
Co-authored-by: Dax <mail@thdxr.com>
Co-authored-by: usrnk1 <7547651+usrnk1@users.noreply.github.com>
Co-authored-by: Jay <53023+jayair@users.noreply.github.com>
Co-authored-by: runvip <164729189+runvip@users.noreply.github.com>
Co-authored-by: opencode <opencode@sst.dev>
Co-authored-by: Julian Coy <julian@ex-machina.co>
Co-authored-by: Vladimir Glafirov <vglafirov@gitlab.com>
Co-authored-by: Adam <2363879+adamdotdevin@users.noreply.github.com>
Co-authored-by: Kit Langton <kit.langton@gmail.com>
Co-authored-by: Simon Klee <hello@simonklee.dk>
Co-authored-by: Jay <air@live.ca>
Co-authored-by: David Hill <1879069+iamdavidhill@users.noreply.github.com>
This commit is contained in:
Aiden Cline 2026-07-06 16:05:29 -05:00 committed by GitHub
commit 9e0d3976e1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
332 changed files with 24739 additions and 4586 deletions

View file

@ -127,6 +127,12 @@
height: 24px;
}
}
&[data-variant="ghost"][data-scope="file-tree-v2"] {
> [data-slot="collapsible-trigger"] {
height: 28px;
}
}
}
@keyframes slideDown {

View file

@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test"
import { scrollKey, scrollTopFromThumbPointer } from "./scroll-view"
import { canScrollKey, scrollKey, scrollTopFromThumbPointer } from "./scroll-view"
describe("scrollKey", () => {
test("maps plain navigation keys", () => {
@ -16,6 +16,27 @@ describe("scrollKey", () => {
expect(scrollKey({ key: "PageUp", altKey: false, ctrlKey: true, metaKey: false, shiftKey: false })).toBeUndefined()
expect(scrollKey({ key: "End", altKey: false, ctrlKey: false, metaKey: false, shiftKey: true })).toBeUndefined()
})
test("maps space and shift-space directions", () => {
expect(scrollKey({ key: " ", altKey: false, ctrlKey: false, metaKey: false, shiftKey: false })).toBe("page-down")
expect(scrollKey({ key: " ", altKey: false, ctrlKey: false, metaKey: false, shiftKey: true })).toBe("page-up")
})
})
describe("canScrollKey", () => {
const element = (scrollTop: number, clientHeight = 100, scrollHeight = 300) =>
({ scrollTop, clientHeight, scrollHeight }) as HTMLElement
test("owns upward keys only above the top boundary", () => {
expect(canScrollKey(element(50), "page-up")).toBe(true)
expect(canScrollKey(element(0), "page-up")).toBe(false)
})
test("owns downward keys only before the bottom boundary", () => {
expect(canScrollKey(element(50), "page-down")).toBe(true)
expect(canScrollKey(element(200), "page-down")).toBe(false)
expect(canScrollKey(element(0, 100, 100), "page-down")).toBe(false)
})
})
describe("scrollTopFromThumbPointer", () => {

View file

@ -1,15 +1,19 @@
import { onMount, splitProps, type ComponentProps, Show, mergeProps } from "solid-js"
import { onCleanup, onMount, splitProps, type ComponentProps, Show, mergeProps } from "solid-js"
import { createResizeObserver } from "@solid-primitives/resize-observer"
import { createStore } from "solid-js/store"
import { useI18n } from "../context/i18n"
export type ScrollViewThumbVisibility = "hover" | "scroll"
export interface ScrollViewProps extends ComponentProps<"div"> {
viewportRef?: (el: HTMLDivElement) => void
orientation?: "vertical" | "horizontal" // currently only vertical is fully implemented for thumb
thumbVisibility?: ScrollViewThumbVisibility
}
export const scrollKey = (event: Pick<KeyboardEvent, "key" | "altKey" | "ctrlKey" | "metaKey" | "shiftKey">) => {
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return
if (event.altKey || event.ctrlKey || event.metaKey) return
if (event.shiftKey && event.key !== " ") return
switch (event.key) {
case "PageDown":
@ -24,9 +28,36 @@ export const scrollKey = (event: Pick<KeyboardEvent, "key" | "altKey" | "ctrlKey
return "up"
case "ArrowDown":
return "down"
case " ":
return event.shiftKey ? "page-up" : "page-down"
}
}
export function canScrollKey(element: HTMLElement, key: NonNullable<ReturnType<typeof scrollKey>>) {
const up = key === "up" || key === "page-up" || key === "home"
return up ? element.scrollTop > 0 : element.scrollTop + element.clientHeight < element.scrollHeight
}
export function scrollKeyOwner(
root: HTMLElement,
target: EventTarget | null,
key: NonNullable<ReturnType<typeof scrollKey>>,
) {
const element = target instanceof Element ? target : undefined
const owner = element?.closest<HTMLElement>("[data-scrollable]")
if (!owner || owner === root) return root
if (!root.contains(owner)) return owner
return canScrollKey(owner, key) ? owner : root
}
export function isScrollKeyTarget(target: EventTarget | null, key: NonNullable<ReturnType<typeof scrollKey>>) {
const element = target instanceof HTMLElement ? target : undefined
if (!element) return true
if (["INPUT", "TEXTAREA", "SELECT"].includes(element.tagName) || element.isContentEditable) return false
if ((key === "page-up" || key === "page-down") && element.closest('button, a[href], [role="button"]')) return false
return true
}
export function scrollTopFromThumbPointer(input: {
pointer: number
viewportTop: number
@ -44,10 +75,10 @@ export function scrollTopFromThumbPointer(input: {
export function ScrollView(props: ScrollViewProps) {
const i18n = useI18n()
const merged = mergeProps({ orientation: "vertical" }, props)
const merged = mergeProps({ orientation: "vertical", thumbVisibility: "hover" }, props)
const [local, events, rest] = splitProps(
merged,
["class", "children", "viewportRef", "orientation", "style"],
["class", "children", "viewportRef", "orientation", "thumbVisibility", "style"],
[
"onScroll",
"onWheel",
@ -68,16 +99,37 @@ export function ScrollView(props: ScrollViewProps) {
const [state, setState] = createStore({
isHovered: false,
isDragging: false,
isScrolling: false,
thumbHeight: 0,
thumbTop: 0,
showThumb: false,
})
const isHovered = () => state.isHovered
const isDragging = () => state.isDragging
const isScrolling = () => state.isScrolling
const thumbHeight = () => state.thumbHeight
const thumbTop = () => state.thumbTop
const showThumb = () => state.showThumb
let scrollIdleTimer: ReturnType<typeof setTimeout> | undefined
const markScrolling = () => {
if (local.thumbVisibility !== "scroll") return
setState("isScrolling", true)
if (scrollIdleTimer !== undefined) clearTimeout(scrollIdleTimer)
scrollIdleTimer = setTimeout(() => setState("isScrolling", false), 800)
}
const thumbVisible = () => {
if (isDragging()) return true
if (local.thumbVisibility === "scroll") return isScrolling()
return isHovered()
}
onCleanup(() => {
if (scrollIdleTimer !== undefined) clearTimeout(scrollIdleTimer)
})
const updateThumb = () => {
if (!viewportRef) return
const { scrollTop, scrollHeight, clientHeight } = viewportRef
@ -160,9 +212,10 @@ export function ScrollView(props: ScrollViewProps) {
if (document.activeElement && ["INPUT", "TEXTAREA", "SELECT"].includes(document.activeElement.tagName)) {
return
}
const next = scrollKey(e)
if (!next) return
if (!isScrollKeyTarget(e.target, next)) return
if (scrollKeyOwner(viewportRef, e.target, next) !== viewportRef) return
const scrollAmount = viewportRef.clientHeight * 0.8
const lineAmount = 40
@ -208,11 +261,18 @@ export function ScrollView(props: ScrollViewProps) {
<div
ref={viewportRef}
class="scroll-view__viewport"
data-scrollable
onScroll={(e) => {
updateThumb()
markScrolling()
if (typeof events.onScroll === "function") events.onScroll(e as any)
}}
onWheel={events.onWheel as any}
onWheel={(e) => {
markScrolling()
const handler = events.onWheel
if (typeof handler === "function") handler(e as any)
if (Array.isArray(handler)) handler[0](handler[1], e as any)
}}
onTouchStart={events.onTouchStart as any}
onTouchMove={events.onTouchMove as any}
onTouchEnd={events.onTouchEnd as any}
@ -236,7 +296,7 @@ export function ScrollView(props: ScrollViewProps) {
ref={thumbRef}
onPointerDown={onThumbPointerDown}
class="scroll-view__thumb"
data-visible={isHovered() || isDragging()}
data-visible={thumbVisible()}
data-dragging={isDragging()}
style={{
height: `${thumbHeight()}px`,

View file

@ -135,7 +135,8 @@
}
}
#review-panel &[data-variant="normal"][data-orientation="horizontal"] {
#review-panel &[data-variant="normal"][data-orientation="horizontal"],
#terminal-panel &[data-variant="normal"][data-orientation="horizontal"] {
background-color: var(--background-stronger);
[data-slot="tabs-list"] {
@ -267,6 +268,51 @@
}
}
#terminal-panel &[data-variant="normal"][data-orientation="horizontal"] {
[data-slot="tabs-list"] {
height: 52px;
padding-right: 12px;
gap: 8px;
}
[data-slot="tabs-trigger-wrapper"] {
height: 28px;
padding-inline: 8px;
border: 0;
background-color: transparent;
box-shadow: none;
&::after {
display: none;
}
[data-slot="tabs-trigger"] {
padding: 0 !important;
}
&:has([data-slot="tabs-trigger-close-button"]) {
padding-right: 8px;
}
&:has([data-selected]) {
background-color: var(--v2-background-bg-layer-01);
box-shadow: inset 0 0 0 0.5px var(--v2-border-border-muted);
}
}
[data-slot="terminal-tab-title"] {
font-family: Inter, sans-serif;
font-style: normal;
font-weight: 440;
font-size: 13px;
line-height: 16px;
letter-spacing: -0.04px;
color: var(--v2-text-text-base);
font-variation-settings: "slnt" 0;
font-variant-numeric: tabular-nums;
}
}
&[data-variant="alt"] {
[data-slot="tabs-list"] {
padding-left: 24px;