# `Drafter.Widget.FilePicker`
[🔗](https://github.com/jaman/drafter/blob/main/lib/drafter/widget/file_picker.ex#L1)

A modal file picker screen for selecting files or directories.

Use `show/1` to open the picker as a modal overlay. Callbacks are delivered
as app events to the calling screen's `handle_event/3`.

This module has no `component_tag/0` and no `Drafter.App` helper: it is a
screen shown by `show/1`, not an element placed in a render tree.

## Options

These are the options `show/1` accepts. It converts them into the props
`mount/1` receives, so `:path` arrives as `:initial_path`, and `:width` and
`:height` are applied to the modal frame rather than passed to `mount/1`.

  * `:on_select` - atom event name or one-arity function called with the selected
    path. Default `nil`
  * `:on_cancel` - atom event name, or a function of arity 0 or 1, called when the
    picker is dismissed. Default `nil`
  * `:path` - initial path. Default `Path.expand("~/")`. A file path opens its
    containing directory with that file preselected
  * `:allow_dirs` - `t:boolean/0`, allow selecting directories. Default `false`.
    With it on, the Open button stays enabled and falls back to the directory
    currently shown
  * `:filter` - list of file extensions, e.g. `[".ex", ".exs"]`. Default `nil`.
    Carried on the state but not applied: every file is listed either way
  * `:title` - label for the open button. Default `"Open"`
  * `:width` - modal width in columns. Default `90`
  * `:height` - modal height in rows. Default `26`

## Key bindings

`↑`/`↓` navigate the tree, `Enter` expands a directory or selects a file, `h`
toggles hidden files, and `Esc` cancels. Pasting text sets the location to the
first pasted line, stripping a `file://` prefix.

## Usage

    FilePicker.show(on_select: :file_picked, on_cancel: :pick_cancelled)

# `state`

```elixir
@type state() :: %{
  tree_root: Path.t(),
  selected_path: Path.t() | nil,
  show_hidden: boolean(),
  sidebar: [{String.t(), Path.t()}],
  on_select: (Path.t() -&gt; any()) | nil,
  on_cancel: (any() -&gt; any()) | (-&gt; any()) | nil,
  allow_dirs: boolean(),
  filter: [String.t()] | nil,
  title: String.t()
}
```

# `handle_event`

```elixir
@spec handle_event(tuple(), state()) ::
  {:ok, state()} | {:noreply, state()} | {:pop, term()}
```

Handles raw input events.

`{:key, :escape}` calls `:on_cancel` and returns `{:pop, :cancelled}`.
`{:key, :h}` toggles hidden files. A bracketed paste takes the first line, strips
a `file://` prefix, and moves to that directory, or to the file's parent with the
file selected; a path that does not exist is ignored. Everything else returns
`{:noreply, state}`.

# `handle_event`

```elixir
@spec handle_event(atom(), term(), state()) ::
  {:ok, state()} | {:noreply, state()} | {:pop, term()}
```

Handles the app events the picker's own widgets raise.

  * `:fp_file_selected` — records the file as the selection
  * `:fp_item_selected` — records the path as the selection when it is a regular
    file, or a directory with `:allow_dirs` set; otherwise `{:noreply, state}`
  * `:fp_location_selected` — moves the tree to that directory and clears the
    selection
  * `:fp_open` — calls `:on_select` with the selection, or with the current
    directory when `:allow_dirs` is set and nothing is selected, then returns
    `{:pop, {:selected, path}}`. With nothing to open it returns
    `{:noreply, state}`
  * `:fp_cancel` — calls `:on_cancel` and returns `{:pop, :cancelled}`

# `keybindings`

```elixir
@spec keybindings() :: [{String.t(), String.t()}]
```

The key bindings shown by `Drafter.Widget.Footer` while the picker is on top.

    iex> Drafter.Widget.FilePicker.keybindings()
    [{"↑↓", "navigate"}, {"Enter", "expand/select"}, {"h", "hidden"}, {"Esc", "cancel"}]

# `mount`

```elixir
@spec mount(map()) :: state()
```

Builds the screen state from the props `show/1` produced.

`:initial_path` decides the starting location: a directory becomes the tree root
with nothing selected, and a regular file opens its parent directory with that
file preselected. The sidebar lists only the standard locations that exist on this
machine.

# `on_ready`

# `on_timer`

# `render`

```elixir
@spec render(state()) :: tuple()
```

Builds the picker's element tree: a hidden-files toolbar, a split pane with the
locations sidebar on the left and the directory tree on the right, and a selection
line with Cancel and Open buttons at the bottom.

Open is disabled while nothing is selected, unless `:allow_dirs` is set.

# `show`

```elixir
@spec show(keyword()) :: {:show_modal, module(), map(), keyword()}
```

Builds the `{:show_modal, module, props, opts}` tuple that opens the picker.

Return it from a screen's `handle_event/3` or `update/2`. An atom `:on_select` or
`:on_cancel` is wrapped into a closure that sends `{:app_event, name, data}` to
the calling process, so `show/1` must be called from the app loop's process.

See the module documentation for every option and its default.

# `unmount`

---

*Consult [api-reference.md](api-reference.md) for complete listing*
