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
co-authored by Frank Aarav Sareen Brendan Allan opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com> Jack Brendan Allan Shoubhit Dash opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com> James Long Dustin Deus starptech Luke Parker 𝓛𝓲𝓽𝓽𝓵𝓮 𝓕𝓻𝓪𝓷𝓴 Dax usrnk1 Jay runvip opencode Julian Coy Vladimir Glafirov Adam Kit Langton Simon Klee Jay David Hill
parent f87998f37f
commit 9e0d3976e1
332 changed files with 24650 additions and 4497 deletions
@@ -127,6 +127,12 @@
height: 24px;
}
}
&[data-variant="ghost"][data-scope="file-tree-v2"] {
> [data-slot="collapsible-trigger"] {
height: 28px;
}
}
}
@keyframes slideDown {
+22 -1
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", () => {
+67 -7
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`,
+47 -1
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;