select(item)}
onAuxClick={(event) => {
if (event.isRightClick) openContextMenu(event.x, event.y)
}}
/>
```
`onMouseDown` and `onMouseUp` still see every button through `event.button`: `0` left, `1` middle, `2` right.
19. **Window geometry hooks are pull-based.** `useWindowSize()` seeded state with a hardcoded `800x600` and read the renderer once from an effect, so a first read before the platform window had a size kept `800x600` forever, and a resize was never observed at all. It samples every **100 ms** now and only rerenders when the numbers change.
New `getWindowInsets()` and `useWindowInsets()` report system and software-keyboard geometry, so a composer can stay above the iOS keyboard instead of hiding behind it:
```tsx
const { keyboardTop, keyboardVisible, ime } = useWindowInsets()
return (
{keyboardVisible ? `Keyboard starts at ${keyboardTop}px` : 'Keyboard closed'}
)
```
| Field | Meaning |
| --- | --- |
| `ime` | Edges covered by the software keyboard |
| `safeArea` | Edges covered by notches, status bars, home indicators |
| `effective` | Per-edge max of the two, the region content should avoid |
| `keyboardTop` | Y coordinate where the keyboard starts |
| `keyboardVisible` | `ime.bottom > 0` |
| `visibleHeight` | Window height minus the effective top and bottom |
Both hooks take the same option, because Safari fires `visualViewport` events in bursts while the keyboard animates and iOS reports stale values on some of them:
```tsx
useWindowInsets() // 100ms, the default
useWindowInsets({ intervalMs: 250 }) // slower
useWindowInsets({ intervalMs: false }) // read once, never poll
```
20. **One diagonal gesture scrolls both axes**, and `position: "fixed"` lays out. `overflow: "scroll"` moved only one axis per wheel event, because GPUI zeroes the smaller of the two deltas by default.
```tsx
{/* one diagonal swipe now pans on X and Y together */}
```
A flex column stretches its children to the cross axis, so rows in a two-axis container still need to state a width, or there is nothing to pan on X. `position: "fixed"` blocked hits like `absolute` but stayed in normal flow, so a box drifted when its siblings changed; it now lays out like `absolute`.
21. **Text selection starts from the empty space before the glyphs.** A press in parent padding, a code gutter, or the empty start of a line clamps to the nearest text on that row, instead of requiring the mouse-down to land inside the tight text box.
```
[padding] hello world
^
press here, drag right → "hello world"
```
A press above or below every line still does not start a selection, so a composer or titlebar cannot claim the nearest paragraph, and `userSelect: "none"` now also blocks the start.
Copying across interpolated text is fixed too. `
Hello {name}!` is three painted runs of one line, and selecting across them used to copy them joined with newlines. Runs now carry the parent host element they belong to, so the same selection yields `Hello Tommy!`, while `
`, `` and `` keep one line per line.
22. **Fixed `key` on every GPUIX element.** A list built with `.map()` failed to typecheck, so any real app broke on the first `tsc` run:
```
error TS2322: Type '{ key: string; ... }' is not assignable to type 'Props'.
Property 'key' does not exist on type 'Props'.
```
`key` lives on `Props` now, next to `ref`. It cannot live on `JSX.IntrinsicAttributes`, because TypeScript 5 ignores that member for intrinsic elements. Every element prop type extends `Props`, so `