# GTKX Widget Reference

## Common Props (All Widgets)

| Prop | Type | Description |
|------|------|-------------|
| `hexpand` / `vexpand` | boolean | Expand to fill available space |
| `halign` / `valign` | `Gtk.Align.START \| CENTER \| END \| FILL` | Alignment |
| `marginStart/End/Top/Bottom` | number | Margins in pixels |
| `sensitive` | boolean | Enabled/disabled state |
| `visible` | boolean | Visibility |
| `cssClasses` | string[] | CSS classes for styling |
| `widthRequest` / `heightRequest` | number | Minimum size |

---

## Containers

### GtkBox
Linear layout (horizontal or vertical).

```tsx
<GtkBox orientation={Gtk.Orientation.VERTICAL} spacing={12}>
    {children}
</GtkBox>
```

### GtkGrid
2D grid with explicit positioning using `GridChild`.

```tsx
<GtkGrid rowSpacing={8} columnSpacing={12}>
    <x.GridChild column={0} row={0}><GtkLabel label="Name:" /></x.GridChild>
    <x.GridChild column={1} row={0}><GtkEntry hexpand /></x.GridChild>
    <x.GridChild column={0} row={1} columnSpan={2}><GtkButton label="Submit" /></x.GridChild>
</GtkGrid>
```

**GridChild props:** `column`, `row`, `columnSpan`, `rowSpan`

### GtkStack
Page container, shows one child at a time.

```tsx
<GtkStack page="page1" transitionType={Gtk.StackTransitionType.SLIDE_LEFT_RIGHT}>
    <x.StackPage id="page1" title="First" iconName="document-new">
        <Content1 />
    </x.StackPage>
    <x.StackPage id="page2" title="Second">
        <Content2 />
    </x.StackPage>
</GtkStack>
```

**StackPage props:** `id` (optional), `title`, `iconName`, `needsAttention`, `badgeNumber` (AdwViewStack only)

### GtkNotebook
Tabbed container with visible tabs.

```tsx
<GtkNotebook>
    <x.NotebookPage label="Tab 1"><Content1 /></x.NotebookPage>
    <x.NotebookPage label="Tab 2" tabExpand tabFill><Content2 /></x.NotebookPage>
</GtkNotebook>
```

Custom tab widget:
```tsx
<x.NotebookPage>
    <x.NotebookPageTab>
        <GtkBox spacing={4}>
            <GtkImage iconName="folder-symbolic" />
            <GtkLabel label="Files" />
        </GtkBox>
    </x.NotebookPageTab>
    <Content />
</x.NotebookPage>
```

**NotebookPage props:** `label`, `tabExpand`, `tabFill`

### GtkPaned
Resizable split with draggable divider. **Requires Slot components.**

```tsx
<GtkPaned position={280} shrinkStartChild={false}>
    <x.Slot for={GtkPaned} id="startChild"><Sidebar /></x.Slot>
    <x.Slot for={GtkPaned} id="endChild"><MainContent /></x.Slot>
</GtkPaned>
```

### GtkOverlay
Stack widgets on top of each other. First child is base layer, additional children need `OverlayChild` wrapper. Multiple children supported per overlay.

```tsx
<GtkOverlay>
    <GtkButton label="Notifications" />
    <x.OverlayChild>
        <GtkLabel label="3" cssClasses={["badge"]} halign={Gtk.Align.END} valign={Gtk.Align.START} />
    </x.OverlayChild>
</GtkOverlay>
```

### GtkFixed
Absolute positioning with optional 3D transforms. Use `x.FixedChild` wrapper for children.

```tsx
<GtkFixed>
    <x.FixedChild x={20} y={30}>
        <GtkLabel label="Top Left" />
    </x.FixedChild>
    <x.FixedChild x={200} y={100} transform={someGskTransform}>
        <GtkLabel label="Transformed" />
    </x.FixedChild>
</GtkFixed>
```

**FixedChild props:** `x`, `y` (pixel coordinates), `transform` (optional `Gsk.Transform`)

### GtkScrolledWindow
Scrollable container.

```tsx
<GtkScrolledWindow vexpand hscrollbarPolicy={Gtk.PolicyType.NEVER}>
    <Content />
</GtkScrolledWindow>
```

---

## Virtual Lists

All virtual list widgets use an `items` data prop and a `renderItem` function. Items are `{ id: string, value: T }` objects.

### GtkListView
High-performance scrollable list with selection.

```tsx
<GtkListView
    estimatedItemHeight={48}
    vexpand
    selected={selectedId ? [selectedId] : []}
    selectionMode={Gtk.SelectionMode.SINGLE}
    onSelectionChanged={(ids) => setSelectedId(ids[0])}
    items={items.map(item => ({ id: item.id, value: item }))}
    renderItem={(item: Item) => <GtkLabel label={item.name} />}
/>
```

### GtkGridView
Grid-based virtual scrolling.

```tsx
<GtkGridView
    estimatedItemHeight={100}
    minColumns={2}
    maxColumns={4}
    items={items.map(item => ({ id: item.id, value: item }))}
    renderItem={(item: Item) => (
        <GtkBox orientation={Gtk.Orientation.VERTICAL}>
            <GtkImage iconName={item.icon} />
            <GtkLabel label={item.name} />
        </GtkBox>
    )}
/>
```

### GtkColumnView
Table with sortable columns.

```tsx
<GtkColumnView
    estimatedRowHeight={48}
    sortColumn="name"
    sortOrder={Gtk.SortType.ASCENDING}
    onSortChanged={handleSort}
    items={items.map(item => ({ id: item.id, value: item }))}
>
    <x.ColumnViewColumn
        title="Name"
        id="name"
        expand
        resizable
        sortable
        renderCell={(item: Item) => <GtkLabel label={item.name} />}
    />
    <x.ColumnViewColumn
        title="Size"
        id="size"
        fixedWidth={100}
        renderCell={(item: Item) => <GtkLabel label={`${item.size} KB`} />}
    />
</GtkColumnView>
```

### GtkDropDown
Selection dropdown.

```tsx
<GtkDropDown
    selectedId={selectedId}
    onSelectionChanged={setSelectedId}
    items={options.map(opt => ({ id: opt.id, value: opt.label }))}
/>
```

### GtkListView (tree mode)
Hierarchical tree with expand/collapse. Items with nested `children` arrays trigger tree behavior.

```tsx
<GtkListView
    estimatedItemHeight={48}
    vexpand
    autoexpand={false}
    selectionMode={Gtk.SelectionMode.SINGLE}
    selected={selectedId ? [selectedId] : []}
    onSelectionChanged={(ids) => setSelectedId(ids[0])}
    items={files.map(file => ({
        id: file.id,
        value: file,
        children: file.children?.map(child => ({ id: child.id, value: child })),
    }))}
    renderItem={(item: FileNode, row) => (
        <GtkBox spacing={8}>
            <GtkImage iconName={item.isDirectory ? "folder-symbolic" : "text-x-generic-symbolic"} />
            <GtkLabel label={item.name} />
        </GtkBox>
    )}
/>
```

**ListItem data props:** `id`, `value`, `children` (nested items for tree mode), `hideExpander`, `indentForDepth`, `indentForIcon`, `section` (for sectioned lists)

---

## Inputs

### GtkEntry
Single-line text input. **Requires two-way binding.**

```tsx
const [text, setText] = useState("");
<GtkEntry text={text} onChanged={(e) => setText(e.getText())} placeholderText="Enter text..." />
```

### GtkToggleButton
Toggle button. Auto-prevents signal feedback loops.

```tsx
<GtkToggleButton active={isActive} onToggled={() => setIsActive(!isActive)} label="Toggle" />
```

### GtkCheckButton
Checkbox.

```tsx
<GtkCheckButton active={checked} onToggled={() => setChecked(!checked)} label="Option" />
```

### GtkSwitch
On/off switch.

```tsx
<GtkSwitch active={enabled} onStateSet={() => { setEnabled(!enabled); return true; }} />
```

### GtkSpinButton
Numeric input with increment/decrement. Adjustment props are set directly.

```tsx
<GtkSpinButton
    value={count}
    lower={0}
    upper={100}
    stepIncrement={1}
    onValueChanged={setCount}
/>
```

### GtkScale
Slider with adjustment props and optional marks.

```tsx
<GtkScale
    drawValue
    valuePos={Gtk.PositionType.TOP}
    value={volume}
    lower={0}
    upper={100}
    stepIncrement={1}
    onValueChanged={setVolume}
    marks={[
        { value: 0, label: "Min", position: Gtk.PositionType.BOTTOM },
        { value: 50, position: Gtk.PositionType.BOTTOM },
        { value: 100, label: "Max", position: Gtk.PositionType.BOTTOM },
    ]}
/>
```

**Adjustment props:** `value`, `lower`, `upper`, `stepIncrement`, `pageIncrement`, `pageSize`, `onValueChanged`
**ScaleMark type:** `{ value: number, position?: Gtk.PositionType, label?: string }`

### GtkCalendar
Date picker with markable days.

```tsx
<GtkCalendar
    onDaySelected={(cal) => setDate(cal.getDate())}
    markedDays={[15, 20, 25]}
/>
```

### GtkLevelBar
Progress/level indicator with customizable thresholds.

```tsx
<GtkLevelBar
    value={0.6}
    offsets={[
        { id: "low", value: 0.25 },
        { id: "high", value: 0.75 },
        { id: "full", value: 1.0 },
    ]}
/>
```

**LevelBarOffset type:** `{ id: string, value: number }`

---

## Display

### GtkLabel
```tsx
<GtkLabel label="Text" halign={Gtk.Align.START} wrap useMarkup />
```

### GtkButton
```tsx
<GtkButton label="Click" onClicked={handleClick} iconName="document-new-symbolic" />
```

### GtkImage
```tsx
<GtkImage iconName="folder-symbolic" pixelSize={48} />
```

---

## Header & Action Bars

### GtkHeaderBar
Title bar with packed widgets. Use `x.ContainerSlot` for packing and `x.Slot` for titleWidget.

```tsx
<GtkHeaderBar>
    <x.ContainerSlot for={GtkHeaderBar} id="packStart"><GtkButton iconName="go-previous-symbolic" /></x.ContainerSlot>
    <x.Slot for={GtkHeaderBar} id="titleWidget">
        <GtkLabel label="Title" cssClasses={["title"]} />
    </x.Slot>
    <x.ContainerSlot for={GtkHeaderBar} id="packEnd"><GtkMenuButton iconName="open-menu-symbolic" /></x.ContainerSlot>
</GtkHeaderBar>
```

### GtkActionBar
Bottom action bar.

```tsx
<GtkActionBar>
    <x.ContainerSlot for={GtkActionBar} id="packStart"><GtkButton label="Cancel" /></x.ContainerSlot>
    <x.ContainerSlot for={GtkActionBar} id="packEnd"><GtkButton label="Save" cssClasses={["suggested-action"]} /></x.ContainerSlot>
</GtkActionBar>
```

---

## Menus

### GtkPopoverMenu with GtkMenuButton

```tsx
<GtkMenuButton iconName="open-menu-symbolic">
    <x.Slot for={GtkMenuButton} id="popover">
        <GtkPopoverMenu>
            <x.MenuSection>
                <x.MenuItem id="new" label="New" onActivate={handleNew} accels="<Control>n" />
                <x.MenuItem id="open" label="Open" onActivate={handleOpen} />
            </x.MenuSection>
            <x.MenuSection>
                <x.MenuSubmenu label="Export">
                    <x.MenuItem id="pdf" label="PDF" onActivate={exportPdf} />
                    <x.MenuItem id="csv" label="CSV" onActivate={exportCsv} />
                </x.MenuSubmenu>
            </x.MenuSection>
            <x.MenuSection>
                <x.MenuItem id="quit" label="Quit" onActivate={quit} accels="<Control>q" />
            </x.MenuSection>
        </GtkPopoverMenu>
    </x.Slot>
</GtkMenuButton>
```

**Menu.Item props:** `id` (required), `label`, `onActivate`, `accels` (e.g., `"<Control>n"`)

---

## Windows

### GtkApplicationWindow

```tsx
<GtkApplicationWindow
    title="App"
    defaultWidth={800}
    defaultHeight={600}
    onClose={quit}
>
    <Content />
</GtkApplicationWindow>
```

Custom titlebar:
```tsx
<GtkApplicationWindow ...>
    <x.Slot for={GtkWindow} id="titlebar">
        <GtkHeaderBar />
    </x.Slot>
    <Content />
</GtkApplicationWindow>
```

---

## Adwaita (Libadwaita)

Import: `import * as Adw from "@gtkx/ffi/adw";`

### AdwApplicationWindow + AdwToolbarView
Modern app structure.

```tsx
<AdwApplicationWindow title="App" defaultWidth={800} defaultHeight={600} onClose={quit}>
    <AdwToolbarView>
        <x.ContainerSlot for={AdwToolbarView} id="addTopBar">
            <AdwHeaderBar>
                <x.Slot for={AdwHeaderBar} id="titleWidget">
                    <AdwWindowTitle title="App" subtitle="Description" />
                </x.Slot>
            </AdwHeaderBar>
        </x.ContainerSlot>
        <MainContent />
        <x.ContainerSlot for={AdwToolbarView} id="addBottomBar">
            <GtkActionBar />
        </x.ContainerSlot>
    </AdwToolbarView>
</AdwApplicationWindow>
```

### AdwStatusPage
Welcome, error, or empty state.

```tsx
<AdwStatusPage iconName="applications-system-symbolic" title="Welcome" description="Get started" vexpand>
    <GtkButton label="Start" cssClasses={["suggested-action", "pill"]} halign={Gtk.Align.CENTER} />
</AdwStatusPage>
```

### AdwBanner
Dismissable notification.

```tsx
<AdwBanner title="Update available" buttonLabel="Dismiss" revealed={show} onButtonClicked={() => setShow(false)} />
```

### AdwPreferencesPage / AdwPreferencesGroup
Settings UI.

```tsx
<AdwPreferencesPage title="Settings">
    <AdwPreferencesGroup title="Appearance" description="Customize look">
        <AdwSwitchRow title="Dark Mode" active={dark} onActivated={() => setDark(!dark)} />
        <AdwActionRow title="Theme" subtitle="Select color">
            <x.ContainerSlot for={AdwActionRow} id="addPrefix">
                <GtkImage iconName="preferences-color-symbolic" />
            </x.ContainerSlot>
            <x.ContainerSlot for={AdwActionRow} id="addSuffix">
                <GtkImage iconName="go-next-symbolic" valign={Gtk.Align.CENTER} />
            </x.ContainerSlot>
        </AdwActionRow>
    </AdwPreferencesGroup>
</AdwPreferencesPage>
```

**ActionRow children:** Use `x.ContainerSlot for={AdwActionRow} id="addPrefix"` for left widgets, `x.ContainerSlot for={AdwActionRow} id="addSuffix"` for right widgets, or `x.Slot for={AdwActionRow} id="activatableWidget"` for clickable suffix.

### AdwExpanderRow
Expandable settings row with optional action widget.

```tsx
<AdwExpanderRow title="Advanced" subtitle="More options">
    <x.ContainerSlot for={AdwExpanderRow} id="addAction">
        <GtkButton iconName="emblem-system-symbolic" cssClasses={["flat"]} />
    </x.ContainerSlot>
    <x.ContainerSlot for={AdwExpanderRow} id="addRow">
        <AdwSwitchRow title="Option 1" active />
        <AdwSwitchRow title="Option 2" />
    </x.ContainerSlot>
</AdwExpanderRow>
```

**ExpanderRow slots:** `x.ContainerSlot for={AdwExpanderRow} id="addRow"` for nested rows, `x.ContainerSlot for={AdwExpanderRow} id="addAction"` for header action widget. Direct children also work for simple cases.

### AdwEntryRow / AdwPasswordEntryRow
Input in list row.

```tsx
<AdwEntryRow title="Username" text={username} onChanged={(e) => setUsername(e.getText())} />
<AdwPasswordEntryRow title="Password" />
```

### AdwToggleGroup
Segmented button group for mutually exclusive options.

```tsx
const [mode, setMode] = useState("list");

<AdwToggleGroup activeName={mode} onActiveChanged={(_index, name) => setMode(name ?? "list")}>
    <x.Toggle id="list" iconName="view-list-symbolic" tooltip="List view" />
    <x.Toggle id="grid" iconName="view-grid-symbolic" tooltip="Grid view" />
    <x.Toggle id="flow" label="Flow" />
</AdwToggleGroup>
```

**ToggleGroup props:** `activeName`, `active` (index), `onActiveChanged` (callback with index and name)

**Toggle props:** `id` (optional), `label`, `iconName`, `tooltip`, `enabled`

### AdwNavigationView
Stack-based navigation with history.

```tsx
const [history, setHistory] = useState(["home"]);

<AdwNavigationView history={history} onHistoryChanged={setHistory}>
    <x.NavigationPage for={AdwNavigationView} id="home" title="Home">
        <GtkButton label="Go to Details" onClicked={() => setHistory([...history, "details"])} />
    </x.NavigationPage>
    <x.NavigationPage for={AdwNavigationView} id="details" title="Details" canPop>
        <GtkLabel label="Details content" />
    </x.NavigationPage>
</AdwNavigationView>
```

**NavigationPage props:** `for` (required, parent widget type), `id` (required), `title`, `canPop`. Control navigation via `history` array.

### AdwNavigationSplitView
Sidebar/content split layout for master-detail interfaces.

```tsx
const [selected, setSelected] = useState(items[0]);

<AdwNavigationSplitView sidebarWidthFraction={0.33} minSidebarWidth={200} maxSidebarWidth={300}>
    <x.NavigationPage for={AdwNavigationSplitView} id="sidebar" title="Sidebar">
        <AdwToolbarView>
            <x.ContainerSlot for={AdwToolbarView} id="addTopBar"><AdwHeaderBar /></x.ContainerSlot>
            <GtkListBox cssClasses={["navigation-sidebar"]} onRowSelected={(row) => {
                    if (!row) return;
                    const item = items[row.getIndex()];
                    if (item) setSelected(item);
                }}>
                {items.map((item) => <AdwActionRow key={item.id} title={item.title} />)}
            </GtkListBox>
        </AdwToolbarView>
    </x.NavigationPage>

    <x.NavigationPage for={AdwNavigationSplitView} id="content" title={selected?.title ?? ""}>
        <AdwToolbarView>
            <x.ContainerSlot for={AdwToolbarView} id="addTopBar"><AdwHeaderBar /></x.ContainerSlot>
            <GtkLabel label={selected?.title ?? ""} />
        </AdwToolbarView>
    </x.NavigationPage>
</AdwNavigationSplitView>
```

**Props:** `sidebarWidthFraction`, `minSidebarWidth`, `maxSidebarWidth`, `collapsed`, `showContent`.
**NavigationPage:** Use `for={AdwNavigationSplitView}` with `id="sidebar"` for left pane, `id="content"` for right pane.
**Selection:** Use `GtkListBox` with `onRowSelected` (single click) not `onRowActivated` (double click).

### AdwAlertDialog
Modern modal alert dialogs with response buttons.

```tsx
const [showDialog, setShowDialog] = useState(false);

{showDialog && (
    <AdwAlertDialog
        heading="Delete File?"
        body="This action cannot be undone."
        onResponse={(id) => {
            if (id === "delete") handleDelete();
            setShowDialog(false);
        }}
    >
        <x.AlertDialogResponse id="cancel" label="Cancel" />
        <x.AlertDialogResponse id="delete" label="Delete" appearance={Adw.ResponseAppearance.DESTRUCTIVE} />
    </AdwAlertDialog>
)}
```

**AlertDialogResponse props:** `id`, `label`, `appearance` (SUGGESTED, DESTRUCTIVE), `enabled`

### GtkColorDialogButton / GtkFontDialogButton
Color and font picker dialogs.

```tsx
<GtkColorDialogButton rgba={color} onRgbaChanged={setColor} title="Select Color" modal withAlpha />
<GtkFontDialogButton fontDesc={font} onFontDescChanged={setFont} title="Select Font" modal useFont useSize />
```

### Other Adwaita Widgets

| Widget | Description |
|--------|-------------|
| `AdwClamp` | Limits content width (`maximumSize={600}`) |
| `AdwAvatar` | User avatar (`size={48} text="Name" showInitials`) |
| `AdwSpinner` | Loading indicator |
| `AdwWindowTitle` | Title + subtitle for header bars |
| `AdwButtonRow` | Button styled as list row |

---

## Animations

Wrap widgets in `x.Animation` for declarative animations with spring or timed transitions:

```tsx
<x.Animation
    initial={{ opacity: 0, scale: 0.8 }}
    animate={{ opacity: 1, scale: 1 }}
    transition={{ mode: "spring", damping: 0.8, stiffness: 200, mass: 1 }}
    animateOnMount
    onAnimationComplete={() => console.log("done")}
>
    <GtkBox>...</GtkBox>
</x.Animation>
```

**Props:** `initial`, `animate`, `exit`, `transition` (`AnimationTransition` discriminated union), `animateOnMount`, `onAnimationStart`, `onAnimationComplete`

**Spring transition:** `{ mode: "spring", damping, stiffness, mass, initialVelocity, clamp, delay }`

**Timed transition:** `{ mode: "timed", duration, easing (from Adw.Easing), delay, repeat, reverse, alternate }`

---

## Drag and Drop

All widgets support drag-and-drop through props. Use `onDragPrepare`, `onDragBegin`, and `onDragEnd` to make a widget draggable, and `dropTypes`, `onDrop`, `onDropEnter`, and `onDropLeave` to accept drops.

```tsx
import * as Gdk from "@gtkx/ffi/gdk";
import { Type, Value } from "@gtkx/ffi/gobject";

const DraggableButton = ({ label }: { label: string }) => (
    <GtkButton
        label={label}
        onDragPrepare={() => Gdk.ContentProvider.newForValue(Value.newFromString(label))}
        dragIcon={someTexture}
        dragIconHotX={16}
        dragIconHotY={16}
    />
);

const DropZone = () => {
    const [dropped, setDropped] = useState<string | null>(null);
    return (
        <GtkBox
            dropTypes={[Type.STRING]}
            onDrop={(value: Value) => { setDropped(value.getString()); return true; }}
        >
            <GtkLabel label={dropped ?? "Drop here"} />
        </GtkBox>
    );
};
```

**Drag source props:** `dragIcon`, `dragIconHotX`, `dragIconHotY`

## GValue Factories

Create typed values for drag-and-drop and signal emission:

| Factory                        | Description                   |
| ------------------------------ | ----------------------------- |
| `Value.newFromString(str)`     | String values                 |
| `Value.newFromDouble(num)`     | 64-bit floating point         |
| `Value.newFromInt(num)`        | 32-bit signed integer         |
| `Value.newFromBoolean(bool)`   | Boolean values                |
| `Value.newFromObject(obj)`     | GObject instances             |
| `Value.newFromBoxed(boxed)`    | Boxed types (Gdk.RGBA, etc.)  |
| `Value.newFromEnum(gtype, n)`  | Enum values (requires GType)  |
| `Value.newFromFlags(gtype, n)` | Flags values (requires GType) |

Type constants for `dropTypes`: `Type.STRING`, `Type.INT`, `Type.DOUBLE`, `Type.BOOLEAN`, `Type.OBJECT`.

## Custom Drawing

Render custom graphics with `GtkDrawingArea` using the `render` callback:

```tsx
import type { Context } from "@gtkx/ffi/cairo";

const Canvas = () => {
    const handleDraw = (cr: Context, width: number, height: number) => {
        cr.setSourceRgb(0.2, 0.4, 0.8);
        cr.rectangle(10, 10, width - 20, height - 20);
        cr.fill();
    };

    return <GtkDrawingArea contentWidth={400} contentHeight={300} render={handleDraw} />;
};
```

Changing the `render` callback reference automatically queues a redraw. Add a `GtkGestureDrag` child for interactive drawing.

## Event Controllers

Event controllers are added as children to any widget. They are auto-generated from GTK's introspection data.

```tsx
<GtkBox focusable>
    <GtkEventControllerMotion
        onEnter={(x, y) => console.log("Entered at", x, y)}
        onMotion={(x, y) => setPosition({ x, y })}
        onLeave={() => console.log("Left")}
    />
    <GtkEventControllerKey
        onKeyPressed={(keyval, keycode, state) => {
            console.log("Key pressed:", keyval);
            return false;
        }}
    />
    <GtkGestureClick onPressed={(nPress, x, y) => console.log("Clicked")} />
    <GtkLabel label="Hover or type here" />
</GtkBox>
```

**Input controllers:** `GtkEventControllerMotion`, `GtkEventControllerKey`, `GtkEventControllerScroll`, `GtkEventControllerFocus`

**Gesture controllers:** `GtkGestureClick`, `GtkGestureDrag`, `GtkGestureLongPress`, `GtkGestureZoom`, `GtkGestureRotate`, `GtkGestureSwipe`, `GtkGestureStylus`, `GtkGesturePan`

**Drag-and-drop:** `GtkDragSource`, `GtkDropTarget`, `GtkDropControllerMotion`

## SearchBar

```tsx
const [searchActive, setSearchActive] = useState(false);

<GtkSearchBar searchModeEnabled={searchActive} onSearchModeChanged={setSearchActive}>
    <GtkSearchEntry text={query} onSearchChanged={(entry) => setQuery(entry.getText())} />
</GtkSearchBar>
```

The `onSearchModeChanged` callback fires when search mode changes (e.g., user presses Escape).

## TextView / SourceView

Text content is provided as direct children. Use `x.TextTag` for formatting and `x.TextAnchor` for embedded widgets.

```tsx
<GtkTextView enableUndo onBufferChanged={(text) => console.log(text)}>
    Normal text, <x.TextTag id="bold" weight={Pango.Weight.BOLD}>bold</x.TextTag>, and
    <x.TextAnchor><GtkButton label="Click" /></x.TextAnchor> inline.
</GtkTextView>
```

**TextView props:** `enableUndo`, `onBufferChanged`, `onTextInserted`, `onTextDeleted`, `onCanUndoChanged`, `onCanRedoChanged`

**TextTag props:** `id` (required), `priority`, `foreground`, `background`, `weight`, `style`, `underline`, `strikethrough`, `family`, `size`, `sizePoints`, `scale`, `justification`, `leftMargin`, `rightMargin`, `indent`, `editable`, `invisible`

**TextAnchor:** Embeds widgets inline with `children`

**TextPaintable:** Embeds images inline with `paintable` prop

```tsx
<GtkSourceView
    showLineNumbers
    highlightCurrentLine
    language="typescript"
    styleScheme="Adwaita-dark"
    highlightSyntax
    highlightMatchingBrackets
    enableUndo
    onBufferChanged={setCode}
>
    {code}
</GtkSourceView>
```

**SourceView additional props:** `language`, `styleScheme`, `highlightSyntax`, `highlightMatchingBrackets`, `implicitTrailingNewline`, `onCursorMoved`, `onHighlightUpdated`

## Keyboard Shortcuts

Attach shortcuts with `<GtkShortcutController>` and `x.Shortcut`:

```tsx
<GtkBox orientation={Gtk.Orientation.VERTICAL} spacing={12} focusable>
    <GtkShortcutController scope={Gtk.ShortcutScope.LOCAL}>
        <x.Shortcut trigger="<Control>equal" onActivate={() => setCount((c) => c + 1)} />
        <x.Shortcut trigger="<Control>minus" onActivate={() => setCount((c) => c - 1)} />
    </GtkShortcutController>
    <GtkLabel label={`Count: ${count}`} />
</GtkBox>
```

**Scopes:** `LOCAL` (widget focus), `MANAGED` (parent managed), `GLOBAL` (window-wide)

**Trigger syntax:** `<Control>s`, `<Control><Shift>s`, `<Alt>F4`, `<Primary>q`, `F5`

**Multiple triggers:** `trigger={["F5", "<Control>r"]}`
