Conversation
The panel is fixed at bottom-centre and cannot be moved, so it sits on top of the page content the agent is working on. This makes the header a drag handle. The offset is applied as a CSS custom property rather than by rewriting `left`/`top`. The wrapper's `transform` already carries the `translateX(-50%)` centring and the show/hide entrance animation, and a media query resizes it under 480px, so rewriting the positioning would fight all three. `.wrapper` gains `translate(var(--drag-x, 0px), var(--drag-y, 0px))`, and dragging sets only those two properties. - `.header` is the handle, on Pointer Events so mouse, touch and pen share one path. `touch-action: none`, `cursor: grab`/`grabbing`. Presses on the control buttons, on a non-primary button, or from a second contact are ignored. - A 4px threshold on true distance separates a click from a drag, so the header's expand/collapse toggle still works. The click that ends a drag is consumed by a flag cleared on the next press that could produce one. A one-shot capture listener does not work here: a gesture ending in `pointercancel` is followed by no click at all, so the listener survives and eats the user's next genuine press. - Offsets keep an 8px margin inside the layout viewport during a drag, on window resize, and on `show()`. A hidden panel measures zero, so a resize while it is hidden never re-clamps. Without the `show()` clamp, dragging right, closing, then narrowing the window leaves the panel off-screen with its only handle out of reach. That clamp waits for the entrance transition to settle, since `getBoundingClientRect()` reports the box mid-animation and a panel parked against the bottom edge would otherwise walk upwards on every hide/show. - `dispose()` tears down a gesture still in flight, whose move and end handlers live on `window` rather than on the panel. - `show()` and `hide()` write `transform` inline, which overrides the class rule, so both carry the same `var()` suffix. Without it, every show/hide resets the drag. - The clamping maths is a pure module so it can be tested without a layout engine. The default position is unchanged. Known limitation: clamping measures the wrapper, which is the collapsed header. The history list and input row are absolutely positioned siblings outside that box, so an expanded panel dragged to the top clips history off-screen. Clamping their union instead would let a tall history exceed the viewport and lock the drag through the degenerate branch, which is worse. The handle stays grabbable either way. Closes alibaba#547
10 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The panel is fixed at bottom-centre and cannot be moved, so it sits on top of the page content the agent is working on. This makes the header a drag handle.
The offset is applied as a CSS custom property rather than by rewriting
left/top. The wrapper'stransformalready carries thetranslateX(-50%)centring and the show/hide entrance animation, and a media query resizes it under 480px, so rewriting the positioning would fight all three..wrappergainstranslate(var(--drag-x, 0px), var(--drag-y, 0px))and declares both to0px, and dragging sets only those two properties. The declaration matters because custom properties inherit and the panel is injected into pages we do not control: left to thevar()fallback, a host page using either name shifts the panel across the screen..headeris the handle, on Pointer Events so mouse, touch and pen share one path.touch-action: none,cursor: grab/grabbing. Presses on the control buttons, on a non-primary button, or from a second contact are ignored.pointercancelis followed by no click at all, so the listener survives and eats the user's next genuine press.documentElement.clientWidth/Height, which excludes a classic scrollbar, capped by the window so quirks mode cannot report the whole document as the viewport — during a drag, on window resize, and onshow(). A hidden panel measures zero, so a resize while it is hidden never re-clamps. Without theshow()clamp, dragging right, closing, then narrowing the window leaves the panel off-screen with its only handle out of reach. That clamp waits for the entrance transition to settle:getBoundingClientRect()reports the box mid-animation, and a panel parked against the bottom edge would otherwise be walked upwards by every hide/show. A resize landing inside that window is deferred to the same scheduled clamp for the same reason — clamping a corrupted offset later is a no-op, so the error would stick.dispose()tears down a gesture still in flight, whose move and end handlers live onwindowrather than on the panel. Only one gesture runs at a time: a mouse and a pen are both primary at once, so a second primary press is ignored rather than allowed to overwrite the teardown handle for the gesture already running.show()andhide()writetransforminline, which overrides the class rule, so both carry the samevar()suffix. Without it, every show/hide resets the drag.The default position is unchanged.
Known limitation: clamping measures the wrapper, which is the collapsed header. The history list and input row are absolutely positioned siblings outside that box, so an expanded panel dragged to the top clips history off-screen, and one parked at the bottom clamp leaves only the top few pixels of the input row visible — 8px of 48px — so it cannot be typed into until it is dragged back up. Clamping their union instead would let a tall history exceed the viewport and lock the drag through the degenerate branch, which is worse, and reserving the input row's height would mean clamping against a box that changes with the panel's own state. The handle stays grabbable either way.
The 8px margin is the interactive one.
.backgroundoverhangs the wrapper by 8px horizontally and carries a 16px blur, so the glow is clipped at the edge; fully containing it would need a 24px dead zone, which costs more than it buys.Closes #547
Type
Testing
npm run cipassespackages/uihad no test setup. This addsvitest.config.js(happy-dom) and atestscript, matching the config style inpage-controller.65 tests. A table over the clamping maths covers all four edges, both axes at once, exact boundaries, a panel larger than the viewport, a zero rect, and margins of 0, 8 and 100. A table over the gesture in happy-dom covers the threshold at 0, 3, 2+3, 3+3 and 4px, drag versus click,
pointercancel, a second contact, the control buttons, presses that race a drag's trailing click, show/hide preserving the offset, resize and entrance re-clamp, a resize that lands mid-entrance, a scrollbar-inclusive viewport, a quirks-mode viewport larger than the window, degenerate boxes, a second pointer lifting mid-drag, and gesture teardown on dispose for both a single gesture and an overlapping second primary pointer.Every production line the tests cover was mutation-checked: reverting it makes a named test fail. That caught two tests that had been passing for the wrong reason, one measuring from a base the viewport had moved and one unable to tell
Math.hypotfrom a Manhattan distance.Verified in Chrome against a built demo bundle driven by real mouse input: the drag tracks the cursor 1:1, clamps to 8px at every edge, a crisp click and a 3px wobble both toggle while a deliberate drag does not,
grabbingand the suppressed transition apply mid-drag only, narrowing the window walks the panel back inward to the pixel the unit test predicts, dragging to the bottom edge then hiding and showing leaves the offset untouched,dispose()mid-drag detaches all three window listeners, and the default position matches main exactly at x=460, y=580.Requirements / 要求