mirror of
https://github.com/anotherhadi/ilovetui.git
synced 2026-10-05 19:08:25 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18668f4aea | ||
|
|
238d764b0a | ||
|
|
b8dc56adb1 | ||
|
|
ad619d23d5 | ||
|
|
fc5e87c028 | ||
|
|
4dada889cb | ||
|
|
080ed34fe4 | ||
|
|
1c1d8ead1f | ||
|
|
3d6bcdf0f2 | ||
|
|
280507f7e0 | ||
|
|
6d858380c7 | ||
|
|
edf72ab183 | ||
|
|
a1d368088c | ||
|
|
20b4e7a0a5 | ||
|
|
21e2039433 | ||
|
|
9158a070f3 | ||
|
|
5e66c5b303 | ||
|
|
b329bdc074 | ||
|
|
038842878a | ||
|
|
12305ba1f2 | ||
|
|
000f9997fd | ||
|
|
df7bd3df0a | ||
|
|
68bf97efd6 | ||
|
|
1bc3ee2821 | ||
|
|
2fd1861339 | ||
|
|
b22ce7cbf7 | ||
|
|
0648e7a761 | ||
|
|
55cb4be01d |
@@ -1,24 +0,0 @@
|
|||||||
version: 2
|
|
||||||
|
|
||||||
before:
|
|
||||||
hooks:
|
|
||||||
- go mod tidy
|
|
||||||
|
|
||||||
builds:
|
|
||||||
- skip: true
|
|
||||||
|
|
||||||
archives:
|
|
||||||
- formats:
|
|
||||||
- tar.gz
|
|
||||||
name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}"
|
|
||||||
|
|
||||||
checksum:
|
|
||||||
name_template: checksums.txt
|
|
||||||
|
|
||||||
changelog:
|
|
||||||
sort: asc
|
|
||||||
filters:
|
|
||||||
exclude:
|
|
||||||
- "^docs:"
|
|
||||||
- "^test:"
|
|
||||||
- "^ci:"
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
# Contributing
|
|
||||||
|
|
||||||
Everybody is invited and welcome to contribute. There is a lot to do... Check the issues!
|
|
||||||
|
|
||||||
The process is straight-forward.
|
|
||||||
|
|
||||||
- Read [How to get faster PR reviews](https://github.com/kubernetes/community/blob/master/contributors/guide/pull-requests.md#best-practices-for-faster-reviews) by Kubernetes (but skip step 0 and 1)
|
|
||||||
- Fork this git repository
|
|
||||||
- Write your changes (bug fixes, new features, ...).
|
|
||||||
- Create a Pull Request against the main branch.
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ko_fi: anotherhadi
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
name: Release
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- "v*"
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
release:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version-file: go.mod
|
|
||||||
|
|
||||||
- uses: goreleaser/goreleaser-action@v6
|
|
||||||
with:
|
|
||||||
version: "~> v2"
|
|
||||||
args: release --clean --config .github/.goreleaser.yaml
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
+1
-4
@@ -1,4 +1 @@
|
|||||||
.claude/
|
node_modules/
|
||||||
CLAUDE.md
|
|
||||||
result/
|
|
||||||
.pre-commit-config.yaml
|
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2026 Hadi
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
@@ -1,36 +1,29 @@
|
|||||||
# I Love TUI
|
# I Love TUI
|
||||||
|
|
||||||
A shared [Base16](https://github.com/tinted-theming/home) theme, a themed wrapper around every official component, and a small set of custom Bubble Tea components, in one Go module, so every TUI built with it shares one config file and looks consistent.
|
Shared OpenTUI/Solid theme, components, and config helpers for my TUIs.
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
```sh
|
```bash
|
||||||
go get github.com/anotherhadi/ilovetui
|
bun add github:anotherhadi/ilovetui
|
||||||
```
|
```
|
||||||
|
|
||||||
## Packages
|
Peer dependencies, install whichever you actually use: `@opentui/core`, `@opentui/solid`, `@opentui/keymap`, `solid-js`, `opentui-spinner`.
|
||||||
|
|
||||||
- [`style`](style/README.md): the theme itself. Colors, pre-built panel styles, config loading.
|
## Usage
|
||||||
- [`bubbles`](bubbles/README.md): themed constructors for official `bubbles/v2` components (`help`, `textarea`, `textinput`, `viewport`, ...).
|
|
||||||
- [`tabs`](tabs/README.md), [`modal`](modal/README.md), [`drawer`](drawer/README.md), [`notification`](notification/README.md), [`helpbar`](helpbar/README.md): custom components not found in the official `bubbles` library, styled from the same theme.
|
|
||||||
|
|
||||||
Each package has its own README and a runnable example under `examples/<package>`.
|
```ts
|
||||||
|
import { theme, presets } from "ilovetui";
|
||||||
## Quick start
|
import "ilovetui/solid";
|
||||||
|
|
||||||
On import, `style` automatically loads the user's theme from `~/.config/ilovetui/config.yaml` (embedded default as fallback), exposed as the package-level `S`:
|
|
||||||
|
|
||||||
```go
|
|
||||||
import "github.com/anotherhadi/ilovetui/style"
|
|
||||||
|
|
||||||
s := lipgloss.NewStyle().Foreground(style.S.Primary)
|
|
||||||
box := style.RenderWithTitle(style.S.PanelFocused, "Title", content, w, h)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
No setup required. See [`style/README.md`](style/README.md) for config details.
|
Entry points: `.`, `./solid`, `./context`, `./project-config`, `./keymap`, `./components`, `./components/help`, `./components/spinner`, `./components/section-header`. See the source for what each exports.
|
||||||
|
|
||||||
## Projects using ilovetui
|
The theme is base16 and user-overridable via `~/.config/ilovetui/config.yaml`. Copy [`src/default.yaml`](https://github.com/anotherhadi/ilovetui/blob/main/src/default.yaml) there as a starting point.
|
||||||
|
|
||||||
|
An app can layer its own theme overrides on top of that file (e.g. a `theme:` section of its own config, validated with `ThemeConfigSchema`) by calling `configureTheme(overrides)` once at startup, before the first render. Invalid theme files are reported as warning toasts and skipped; unknown keys are ignored, so other TUIs can keep their own keys in the shared file.
|
||||||
|
|
||||||
|
## Focus and keymap scoping
|
||||||
|
|
||||||
|
Wrap each focus region in a `<Pane>` (`focusable` when the pane itself is the focus target). Inside it, `usePane()` gives `target` — pass it to `useBindings` so the layer is only active while focus is within the pane — and `focused()` for styling. `<Modal>` takes focus while open, restores it on close, and binds its `dismissKey` (default `escape`) plus any `commands`/`bindings` you pass, scoped to itself. Note that keymap layers are ordered by `priority` then registration order, not by tree depth: keep overlapping layers on disjoint targets, or give them explicit priorities.
|
||||||
|
|
||||||
- [anotherhadi/spilltea](https://github.com/anotherhadi/spilltea): A minimal, terminal-based HTTP(S) proxy for pentesters and CTF players. Think Burp Suite or Caido, but entirely in your terminal.
|
|
||||||
- [anotherhadi/usbguard-tui](https://github.com/anotherhadi/usbguard-tui): A terminal UI for managing USB devices via usbguard. TUI built with golang & bubbletea.
|
|
||||||
- [anotherhadi/jwt-tui](https://github.com/anotherhadi/jwt-tui): A terminal UI for inspecting, editing, and signing JSON Web Tokens (JWTs).
|
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
# bubbles
|
|
||||||
|
|
||||||
Themed constructors for every official `bubbles/v2` component: `help`, `textarea`, `textinput`,
|
|
||||||
`list`, `table`, `filepicker`, `spinner`, `progress`, `paginator`, `viewport`. Each wraps the
|
|
||||||
official `New`, then applies `style.S` colors on top.
|
|
||||||
|
|
||||||
- `spinner`, `table`, `progress` forward any `opts` to the upstream `New` before the theme is
|
|
||||||
applied, so the theme's colors always win over anything conflicting in `opts`.
|
|
||||||
- `ViewportView` renders a `viewport.Model` with a themed scrollbar thumb in the left gutter,
|
|
||||||
shown only when content overflows.
|
|
||||||
- `NewList`/`NewDefaultDelegate` theme both the list chrome and the selection/filter colors of its
|
|
||||||
default delegate.
|
|
||||||
|
|
||||||
Custom components in this repo (`tabs`, `modal`, `drawer`, `notification`, `helpbar`) build any
|
|
||||||
official component they need through here, never through `charm.land/bubbles/v2` directly.
|
|
||||||
|
|
||||||
See `examples/bubbles`.
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
package bubbles
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
package bubbles
|
|
||||||
|
|
||||||
import (
|
|
||||||
"charm.land/bubbles/v2/filepicker"
|
|
||||||
|
|
||||||
"github.com/anotherhadi/ilovetui/style"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewFilePicker() filepicker.Model {
|
|
||||||
f := filepicker.New()
|
|
||||||
s := filepicker.DefaultStyles()
|
|
||||||
s.Cursor = s.Cursor.Foreground(style.S.Primary)
|
|
||||||
s.DisabledCursor = s.DisabledCursor.Foreground(style.S.Subtle)
|
|
||||||
s.Symlink = s.Symlink.Foreground(style.S.Warning)
|
|
||||||
s.Directory = s.Directory.Foreground(style.S.Primary)
|
|
||||||
s.File = s.File.Foreground(style.S.Text)
|
|
||||||
s.DisabledFile = s.DisabledFile.Foreground(style.S.Subtle)
|
|
||||||
s.Permission = s.Permission.Foreground(style.S.Muted)
|
|
||||||
s.Selected = s.Selected.Foreground(style.S.Primary)
|
|
||||||
s.DisabledSelected = s.DisabledSelected.Foreground(style.S.Subtle)
|
|
||||||
s.FileSize = s.FileSize.Foreground(style.S.Muted)
|
|
||||||
s.EmptyDirectory = s.EmptyDirectory.Foreground(style.S.Subtle)
|
|
||||||
f.Styles = s
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
package bubbles
|
|
||||||
|
|
||||||
import (
|
|
||||||
"charm.land/bubbles/v2/help"
|
|
||||||
"charm.land/lipgloss/v2"
|
|
||||||
|
|
||||||
"github.com/anotherhadi/ilovetui/style"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewHelp() help.Model {
|
|
||||||
h := help.New()
|
|
||||||
h.Styles.ShortKey = lipgloss.NewStyle().Foreground(style.S.Primary)
|
|
||||||
h.Styles.ShortDesc = lipgloss.NewStyle().Foreground(style.S.Muted)
|
|
||||||
h.Styles.ShortSeparator = lipgloss.NewStyle().Foreground(style.S.Subtle)
|
|
||||||
h.Styles.FullKey = lipgloss.NewStyle().Foreground(style.S.Primary)
|
|
||||||
h.Styles.FullDesc = lipgloss.NewStyle().Foreground(style.S.Muted)
|
|
||||||
h.Styles.FullSeparator = lipgloss.NewStyle().Foreground(style.S.Subtle)
|
|
||||||
h.Styles.Ellipsis = lipgloss.NewStyle().Foreground(style.S.Subtle)
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
package bubbles
|
|
||||||
|
|
||||||
import (
|
|
||||||
"charm.land/bubbles/v2/list"
|
|
||||||
|
|
||||||
"github.com/anotherhadi/ilovetui/style"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewList(items []list.Item, width, height int) list.Model {
|
|
||||||
m := list.New(items, NewDefaultDelegate(), width, height)
|
|
||||||
m.Styles = themedListStyles()
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
func themedListStyles() list.Styles {
|
|
||||||
s := list.DefaultStyles(true)
|
|
||||||
s.Title = s.Title.Background(style.S.Primary).Foreground(style.S.Background)
|
|
||||||
s.Spinner = s.Spinner.Foreground(style.S.Primary)
|
|
||||||
s.Filter = themedTextInputStyles()
|
|
||||||
s.DefaultFilterCharacterMatch = s.DefaultFilterCharacterMatch.Foreground(style.S.Primary)
|
|
||||||
s.StatusBar = s.StatusBar.Foreground(style.S.Muted)
|
|
||||||
s.StatusEmpty = s.StatusEmpty.Foreground(style.S.Subtle)
|
|
||||||
s.StatusBarActiveFilter = s.StatusBarActiveFilter.Foreground(style.S.Text)
|
|
||||||
s.StatusBarFilterCount = s.StatusBarFilterCount.Foreground(style.S.Subtle)
|
|
||||||
s.NoItems = s.NoItems.Foreground(style.S.Subtle)
|
|
||||||
s.ArabicPagination = s.ArabicPagination.Foreground(style.S.Subtle)
|
|
||||||
s.ActivePaginationDot = s.ActivePaginationDot.Foreground(style.S.Primary)
|
|
||||||
s.InactivePaginationDot = s.InactivePaginationDot.Foreground(style.S.Subtle)
|
|
||||||
s.DividerDot = s.DividerDot.Foreground(style.S.Subtle)
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDefaultDelegate() list.DefaultDelegate {
|
|
||||||
d := list.NewDefaultDelegate()
|
|
||||||
d.Styles.NormalTitle = d.Styles.NormalTitle.Foreground(style.S.Text)
|
|
||||||
d.Styles.NormalDesc = d.Styles.NormalDesc.Foreground(style.S.Muted)
|
|
||||||
d.Styles.SelectedTitle = d.Styles.SelectedTitle.
|
|
||||||
BorderForeground(style.S.Primary).
|
|
||||||
Foreground(style.S.Primary)
|
|
||||||
d.Styles.SelectedDesc = d.Styles.SelectedDesc.
|
|
||||||
BorderForeground(style.S.Primary).
|
|
||||||
Foreground(style.S.Primary)
|
|
||||||
d.Styles.DimmedTitle = d.Styles.DimmedTitle.Foreground(style.S.Subtle)
|
|
||||||
d.Styles.DimmedDesc = d.Styles.DimmedDesc.Foreground(style.S.SubtleBg)
|
|
||||||
d.Styles.FilterMatch = d.Styles.FilterMatch.Foreground(style.S.Primary)
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package bubbles
|
|
||||||
|
|
||||||
import (
|
|
||||||
"charm.land/bubbles/v2/paginator"
|
|
||||||
"charm.land/lipgloss/v2"
|
|
||||||
|
|
||||||
"github.com/anotherhadi/ilovetui/style"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewPaginator() paginator.Model {
|
|
||||||
p := paginator.New()
|
|
||||||
p.Type = paginator.Dots
|
|
||||||
p.ActiveDot = lipgloss.NewStyle().Foreground(style.S.Primary).Render("•")
|
|
||||||
p.InactiveDot = lipgloss.NewStyle().Foreground(style.S.Subtle).Render("•")
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package bubbles
|
|
||||||
|
|
||||||
import (
|
|
||||||
"charm.land/bubbles/v2/progress"
|
|
||||||
|
|
||||||
"github.com/anotherhadi/ilovetui/style"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewProgress(opts ...progress.Option) progress.Model {
|
|
||||||
allOpts := append([]progress.Option{
|
|
||||||
progress.WithColors(style.S.Subtle, style.S.Primary),
|
|
||||||
}, opts...)
|
|
||||||
p := progress.New(allOpts...)
|
|
||||||
p.EmptyColor = style.S.SubtleBg
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package bubbles
|
|
||||||
|
|
||||||
import (
|
|
||||||
"charm.land/bubbles/v2/spinner"
|
|
||||||
"charm.land/lipgloss/v2"
|
|
||||||
|
|
||||||
"github.com/anotherhadi/ilovetui/style"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewSpinner(opts ...spinner.Option) spinner.Model {
|
|
||||||
s := spinner.New(opts...)
|
|
||||||
s.Style = lipgloss.NewStyle().Foreground(style.S.Primary)
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package bubbles
|
|
||||||
|
|
||||||
import (
|
|
||||||
"charm.land/bubbles/v2/table"
|
|
||||||
|
|
||||||
"github.com/anotherhadi/ilovetui/style"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewTable(opts ...table.Option) table.Model {
|
|
||||||
t := table.New(opts...)
|
|
||||||
s := table.DefaultStyles()
|
|
||||||
s.Header = s.Header.Foreground(style.S.Primary)
|
|
||||||
s.Cell = s.Cell.Foreground(style.S.Text)
|
|
||||||
s.Selected = s.Selected.Foreground(style.S.Primary)
|
|
||||||
t.SetStyles(s)
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
package bubbles
|
|
||||||
|
|
||||||
import (
|
|
||||||
"charm.land/bubbles/v2/textarea"
|
|
||||||
"charm.land/lipgloss/v2"
|
|
||||||
|
|
||||||
"github.com/anotherhadi/ilovetui/style"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewTextarea(showLineNumbers bool) textarea.Model {
|
|
||||||
ta := textarea.New()
|
|
||||||
ta.Prompt = ""
|
|
||||||
ta.ShowLineNumbers = showLineNumbers
|
|
||||||
ta.CharLimit = 0
|
|
||||||
ta.EndOfBufferCharacter = '~'
|
|
||||||
ts := ta.Styles()
|
|
||||||
ts.Focused.Base = lipgloss.NewStyle()
|
|
||||||
ts.Blurred.Base = lipgloss.NewStyle()
|
|
||||||
ts.Focused.Text = lipgloss.NewStyle().Foreground(style.S.Text)
|
|
||||||
ts.Focused.CursorLine = lipgloss.NewStyle().Background(style.S.Selection).Foreground(style.S.Text)
|
|
||||||
ts.Focused.CursorLineNumber = lipgloss.NewStyle().Background(style.S.Selection).Foreground(style.S.Primary).Bold(true)
|
|
||||||
ts.Focused.LineNumber = lipgloss.NewStyle().Foreground(style.S.Subtle)
|
|
||||||
ts.Focused.Placeholder = lipgloss.NewStyle().Foreground(style.S.Subtle)
|
|
||||||
ts.Focused.EndOfBuffer = lipgloss.NewStyle().Foreground(style.S.SubtleBg)
|
|
||||||
ts.Blurred.Text = lipgloss.NewStyle().Foreground(style.S.Muted)
|
|
||||||
ts.Blurred.LineNumber = lipgloss.NewStyle().Foreground(style.S.SubtleBg)
|
|
||||||
ts.Blurred.Placeholder = lipgloss.NewStyle().Foreground(style.S.Subtle)
|
|
||||||
ts.Blurred.EndOfBuffer = lipgloss.NewStyle().Foreground(style.S.SubtleBg)
|
|
||||||
ta.SetStyles(ts)
|
|
||||||
return ta
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
package bubbles
|
|
||||||
|
|
||||||
import (
|
|
||||||
"charm.land/bubbles/v2/textinput"
|
|
||||||
|
|
||||||
"github.com/anotherhadi/ilovetui/style"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewTextInput() textinput.Model {
|
|
||||||
t := textinput.New()
|
|
||||||
t.SetStyles(themedTextInputStyles())
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
func themedTextInputStyles() textinput.Styles {
|
|
||||||
s := textinput.DefaultStyles(true)
|
|
||||||
s.Focused.Text = s.Focused.Text.Foreground(style.S.Text)
|
|
||||||
s.Focused.Placeholder = s.Focused.Placeholder.Foreground(style.S.Subtle)
|
|
||||||
s.Focused.Suggestion = s.Focused.Suggestion.Foreground(style.S.Subtle)
|
|
||||||
s.Focused.Prompt = s.Focused.Prompt.Foreground(style.S.Primary)
|
|
||||||
s.Blurred.Text = s.Blurred.Text.Foreground(style.S.Muted)
|
|
||||||
s.Blurred.Placeholder = s.Blurred.Placeholder.Foreground(style.S.Subtle)
|
|
||||||
s.Blurred.Suggestion = s.Blurred.Suggestion.Foreground(style.S.Subtle)
|
|
||||||
s.Blurred.Prompt = s.Blurred.Prompt.Foreground(style.S.Subtle)
|
|
||||||
s.Cursor.Color = style.S.Primary
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
package bubbles
|
|
||||||
|
|
||||||
import (
|
|
||||||
"charm.land/bubbles/v2/viewport"
|
|
||||||
"charm.land/lipgloss/v2"
|
|
||||||
|
|
||||||
"github.com/anotherhadi/ilovetui/style"
|
|
||||||
)
|
|
||||||
|
|
||||||
const ViewportGutterWidth = 2
|
|
||||||
|
|
||||||
func NewViewport() viewport.Model {
|
|
||||||
vp := viewport.New()
|
|
||||||
vp.MouseWheelEnabled = false
|
|
||||||
return vp
|
|
||||||
}
|
|
||||||
|
|
||||||
func ViewportView(vp *viewport.Model) string {
|
|
||||||
height := vp.Height()
|
|
||||||
total := vp.TotalLineCount()
|
|
||||||
blank := lipgloss.NewStyle().Width(ViewportGutterWidth).Render("")
|
|
||||||
|
|
||||||
if height <= 0 || total <= height {
|
|
||||||
vp.LeftGutterFunc = func(viewport.GutterContext) string { return blank }
|
|
||||||
return vp.View()
|
|
||||||
}
|
|
||||||
|
|
||||||
yOffset := vp.YOffset()
|
|
||||||
thumbSize := max(1, height*height/total)
|
|
||||||
thumbStart := 0
|
|
||||||
if maxOffset := total - height; maxOffset > 0 {
|
|
||||||
thumbStart = yOffset * (height - thumbSize) / maxOffset
|
|
||||||
}
|
|
||||||
|
|
||||||
trackStyle := lipgloss.NewStyle().Foreground(style.S.Subtle)
|
|
||||||
thumbStyle := lipgloss.NewStyle().Foreground(style.S.Primary)
|
|
||||||
|
|
||||||
vp.LeftGutterFunc = func(ctx viewport.GutterContext) string {
|
|
||||||
pos := ctx.Index - yOffset
|
|
||||||
if pos >= thumbStart && pos < thumbStart+thumbSize {
|
|
||||||
return thumbStyle.Render("█") + " "
|
|
||||||
}
|
|
||||||
return trackStyle.Render("│") + " "
|
|
||||||
}
|
|
||||||
|
|
||||||
return vp.View()
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,273 @@
|
|||||||
|
{
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"configVersion": 1,
|
||||||
|
"workspaces": {
|
||||||
|
"": {
|
||||||
|
"name": "ilovetui-opentui",
|
||||||
|
"dependencies": {
|
||||||
|
"yaml": "^2.8.1",
|
||||||
|
"zod": "^4.5.4",
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@opentui/core": "^0.5.9",
|
||||||
|
"@opentui/keymap": "^0.5.9",
|
||||||
|
"@opentui/solid": "^0.5.9",
|
||||||
|
"@types/bun": "^1.3.14",
|
||||||
|
"opentui-spinner": "^0.0.7",
|
||||||
|
"solid-js": "1.9.12",
|
||||||
|
"typescript": "^5.9.3",
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@opentui/core": ">=0.5.0",
|
||||||
|
"@opentui/keymap": ">=0.5.0",
|
||||||
|
"@opentui/solid": ">=0.5.0",
|
||||||
|
"opentui-spinner": ">=0.0.7",
|
||||||
|
"solid-js": ">=1.9.0",
|
||||||
|
},
|
||||||
|
"optionalPeers": [
|
||||||
|
"@opentui/core",
|
||||||
|
"@opentui/keymap",
|
||||||
|
"@opentui/solid",
|
||||||
|
"opentui-spinner",
|
||||||
|
"solid-js",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"packages": {
|
||||||
|
"@ampproject/remapping": ["@ampproject/[email protected]", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],
|
||||||
|
|
||||||
|
"@babel/code-frame": ["@babel/[email protected]", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="],
|
||||||
|
|
||||||
|
"@babel/compat-data": ["@babel/[email protected]", "", {}, "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg=="],
|
||||||
|
|
||||||
|
"@babel/core": ["@babel/[email protected]", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.0", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", "@babel/helpers": "^7.27.6", "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.0", "@babel/types": "^7.28.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ=="],
|
||||||
|
|
||||||
|
"@babel/generator": ["@babel/[email protected]", "", { "dependencies": { "@babel/parser": "^7.29.8", "@babel/types": "^7.29.8", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg=="],
|
||||||
|
|
||||||
|
"@babel/helper-annotate-as-pure": ["@babel/[email protected]", "", { "dependencies": { "@babel/types": "^7.29.7" } }, "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw=="],
|
||||||
|
|
||||||
|
"@babel/helper-compilation-targets": ["@babel/[email protected]", "", { "dependencies": { "@babel/compat-data": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g=="],
|
||||||
|
|
||||||
|
"@babel/helper-create-class-features-plugin": ["@babel/[email protected]", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-member-expression-to-functions": "^7.29.7", "@babel/helper-optimise-call-expression": "^7.29.7", "@babel/helper-replace-supers": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", "@babel/traverse": "^7.29.7", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg=="],
|
||||||
|
|
||||||
|
"@babel/helper-globals": ["@babel/[email protected]", "", {}, "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA=="],
|
||||||
|
|
||||||
|
"@babel/helper-member-expression-to-functions": ["@babel/[email protected]", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg=="],
|
||||||
|
|
||||||
|
"@babel/helper-module-imports": ["@babel/[email protected]", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g=="],
|
||||||
|
|
||||||
|
"@babel/helper-module-transforms": ["@babel/[email protected]", "", { "dependencies": { "@babel/helper-module-imports": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg=="],
|
||||||
|
|
||||||
|
"@babel/helper-optimise-call-expression": ["@babel/[email protected]", "", { "dependencies": { "@babel/types": "^7.29.7" } }, "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong=="],
|
||||||
|
|
||||||
|
"@babel/helper-plugin-utils": ["@babel/[email protected]", "", {}, "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw=="],
|
||||||
|
|
||||||
|
"@babel/helper-replace-supers": ["@babel/[email protected]", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.29.7", "@babel/helper-optimise-call-expression": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ=="],
|
||||||
|
|
||||||
|
"@babel/helper-skip-transparent-expression-wrappers": ["@babel/[email protected]", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ=="],
|
||||||
|
|
||||||
|
"@babel/helper-string-parser": ["@babel/[email protected]", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="],
|
||||||
|
|
||||||
|
"@babel/helper-validator-identifier": ["@babel/[email protected]", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
|
||||||
|
|
||||||
|
"@babel/helper-validator-option": ["@babel/[email protected]", "", {}, "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw=="],
|
||||||
|
|
||||||
|
"@babel/helpers": ["@babel/[email protected]", "", { "dependencies": { "@babel/template": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg=="],
|
||||||
|
|
||||||
|
"@babel/parser": ["@babel/[email protected]", "", { "dependencies": { "@babel/types": "^7.29.8" }, "bin": "./bin/babel-parser.js" }, "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA=="],
|
||||||
|
|
||||||
|
"@babel/plugin-syntax-jsx": ["@babel/[email protected]", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A=="],
|
||||||
|
|
||||||
|
"@babel/plugin-syntax-typescript": ["@babel/[email protected]", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA=="],
|
||||||
|
|
||||||
|
"@babel/plugin-transform-modules-commonjs": ["@babel/[email protected]", "", { "dependencies": { "@babel/helper-module-transforms": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ=="],
|
||||||
|
|
||||||
|
"@babel/plugin-transform-typescript": ["@babel/[email protected]", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", "@babel/plugin-syntax-typescript": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw=="],
|
||||||
|
|
||||||
|
"@babel/preset-typescript": ["@babel/[email protected]", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", "@babel/plugin-transform-typescript": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ=="],
|
||||||
|
|
||||||
|
"@babel/template": ["@babel/[email protected]", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg=="],
|
||||||
|
|
||||||
|
"@babel/traverse": ["@babel/[email protected]", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.8", "@babel/helper-globals": "^7.29.7", "@babel/parser": "^7.29.8", "@babel/template": "^7.29.7", "@babel/types": "^7.29.8", "debug": "^4.3.1" } }, "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg=="],
|
||||||
|
|
||||||
|
"@babel/types": ["@babel/[email protected]", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg=="],
|
||||||
|
|
||||||
|
"@jridgewell/gen-mapping": ["@jridgewell/[email protected]", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
||||||
|
|
||||||
|
"@jridgewell/resolve-uri": ["@jridgewell/[email protected]", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
||||||
|
|
||||||
|
"@jridgewell/sourcemap-codec": ["@jridgewell/[email protected]", "", {}, "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw=="],
|
||||||
|
|
||||||
|
"@jridgewell/trace-mapping": ["@jridgewell/[email protected]", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||||
|
|
||||||
|
"@opentui/core": ["@opentui/[email protected]", "", { "dependencies": { "bun-ffi-structs": "0.3.1", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.5.9", "@opentui/core-darwin-x64": "0.5.9", "@opentui/core-linux-arm64": "0.5.9", "@opentui/core-linux-arm64-musl": "0.5.9", "@opentui/core-linux-x64": "0.5.9", "@opentui/core-linux-x64-musl": "0.5.9", "@opentui/core-win32-arm64": "0.5.9", "@opentui/core-win32-x64": "0.5.9" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-d0EWYyp6djitu1N1R0o75NrLl4TxY3oJEmRNrX9vSKKC5/jriGQdSV6lJmwiB77O0cxtBnPzWztAV79vA1J2fA=="],
|
||||||
|
|
||||||
|
"@opentui/core-darwin-arm64": ["@opentui/[email protected]", "", { "os": "darwin", "cpu": "arm64" }, "sha512-YcpWGD8GwoO3UYYw0kLDI4qof3ElSwWW5M3fNG+Kw87BSjCa5frZqak5xebdZ/XU5Xsk+BjgbBsxbzo9yrIjZQ=="],
|
||||||
|
|
||||||
|
"@opentui/core-darwin-x64": ["@opentui/[email protected]", "", { "os": "darwin", "cpu": "x64" }, "sha512-xgdZwgcwlDCqi0WPCS1d0GVSd+oKDFX+UVE1ZPei8u8W3pirXGSExxVHNku7QbEYCRvpCeMtoKIa6xN9cVTsoQ=="],
|
||||||
|
|
||||||
|
"@opentui/core-linux-arm64": ["@opentui/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-vHBiJp69wMaL+fRMDpnXCCIpPyQRPZEWCWBatnblUUGIWUseqeQlaPow6ljHSbgAd/GjygAf4tFCyTqNT2ezZA=="],
|
||||||
|
|
||||||
|
"@opentui/core-linux-arm64-musl": ["@opentui/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-A7GW4NCTInj2vy3X2gVO2Sm8jllgdLxwPdOklPqyl+xqivQtyG9qwS5YXoCk9fBJt10ORhYKETEjYEnIE5xvMQ=="],
|
||||||
|
|
||||||
|
"@opentui/core-linux-x64": ["@opentui/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-rdw7QVopVHwj6fMrJc7hPGVaYg2V5NBlW3MUfkrQBlUlo/87OC4aYDAh5oRIL1CIFD9LjDdh7hwmUNbn/K9Pcw=="],
|
||||||
|
|
||||||
|
"@opentui/core-linux-x64-musl": ["@opentui/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-J4wQs1OMPZ4hR93Op1C/BLFmIta2mUJm4M7djevlgcWcal4RSNO4V8QvUXfogWGci+ADZqEmX2osQQDY5NVJpw=="],
|
||||||
|
|
||||||
|
"@opentui/core-win32-arm64": ["@opentui/[email protected]", "", { "os": "win32", "cpu": "arm64" }, "sha512-OnmEsGLWwsHrm6sTsWSSesu4LGjSV1JNWqCsEItbuitWaqH6rXdSahSc5jMFsS57bZxUyaRz01yAqiZzUSXnVQ=="],
|
||||||
|
|
||||||
|
"@opentui/core-win32-x64": ["@opentui/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-/CnAIfKL7+ZeGLyZoXV5zS71Nd8Zn97RUir2DAIY05MJozmfg5s7XOAdUNYuK1cg5mbwlAuqeAsfbuWUAEe15g=="],
|
||||||
|
|
||||||
|
"@opentui/keymap": ["@opentui/[email protected]", "", { "dependencies": { "@opentui/core": "0.5.9" }, "peerDependencies": { "@opentui/react": "0.5.9", "@opentui/solid": "0.5.9", "react": ">=19.2.0", "solid-js": "1.9.12" }, "optionalPeers": ["@opentui/react", "@opentui/solid", "react", "solid-js"] }, "sha512-ZcRNeuCDv+LJ89BS5xBw90e0S1Etn6j41jflg6LZlWj4bnqjCU55dq0+zERUdkkC4rqD4SikM8q7V3ZfkwzyyQ=="],
|
||||||
|
|
||||||
|
"@opentui/solid": ["@opentui/[email protected]", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.5.9", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-zGSP/ia9ww+TTMvQMZjJW2+h7fg9YkVRIM723EgvP+v7ZBZ/0vMzPNh7FJ0jET3ysw0h5hsbvcYISs4sUcf+UA=="],
|
||||||
|
|
||||||
|
"@types/bun": ["@types/[email protected]", "", { "dependencies": { "bun-types": "1.4.0" } }, "sha512-K+lZULY23vRgK/CfTjFIV+tyifaNdSMlPh9j+6mQ/cLfpOznLyAuzgV/JQysyECpkBQLVMSyvjlr2fBUSA9wFQ=="],
|
||||||
|
|
||||||
|
"@types/node": ["@types/[email protected]", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-faiGnoIrLH/V8cibOMEAZ8pMw6oXqSukl29ra4mN8GdaB2ZewzeaLj+INpV5N+Z1eKWzY+IzaIZH2EIR6YZRNQ=="],
|
||||||
|
|
||||||
|
"ansi-regex": ["[email protected]", "", {}, "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ=="],
|
||||||
|
|
||||||
|
"babel-plugin-jsx-dom-expressions": ["[email protected]", "", { "dependencies": { "@babel/helper-module-imports": "7.18.6", "@babel/plugin-syntax-jsx": "^7.18.6", "@babel/types": "^7.20.7", "html-entities": "2.3.3", "parse5": "^7.1.2" }, "peerDependencies": { "@babel/core": "^7.20.12" } }, "sha512-lxve6Y02YiZTldB7efKpnbf1BH00XCFZNYYW235jSGsYaJNFtHrYlKV6/O+miHbjqpIr9FTe5+0no4hofAMbfA=="],
|
||||||
|
|
||||||
|
"babel-plugin-module-resolver": ["[email protected]", "", { "dependencies": { "find-babel-config": "^2.1.1", "glob": "^9.3.3", "pkg-up": "^3.1.0", "reselect": "^4.1.7", "resolve": "^1.22.8" } }, "sha512-9KtaCazHee2xc0ibfqsDeamwDps6FZNo5S0Q81dUqEuFzVwPhcT4J5jOqIVvgCA3Q/wO9hKYxN/Ds3tIsp5ygg=="],
|
||||||
|
|
||||||
|
"babel-preset-solid": ["[email protected]", "", { "dependencies": { "babel-plugin-jsx-dom-expressions": "^0.40.6" }, "peerDependencies": { "@babel/core": "^7.0.0", "solid-js": "^1.9.12" }, "optionalPeers": ["solid-js"] }, "sha512-LLqnuKVDlKpyBlMPcH6qEvs/wmS9a+NczppxJ3ryS/c0O5IiSFOIBQi9GzyiGDSbcJpx4Gr87jyFTos1MyEuWg=="],
|
||||||
|
|
||||||
|
"balanced-match": ["[email protected]", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||||
|
|
||||||
|
"baseline-browser-mapping": ["[email protected]", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-H0ulySigv6icDJ1F7SjtdCD6PrhTpdYCmP0CactWy1+ekh0AFd0o1Wn5T8b+hnTmdBx19u9yhL6wvCylXMY7zw=="],
|
||||||
|
|
||||||
|
"brace-expansion": ["[email protected]", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg=="],
|
||||||
|
|
||||||
|
"browserslist": ["[email protected]", "", { "dependencies": { "baseline-browser-mapping": "^2.11.12", "caniuse-lite": "^1.0.30001809", "electron-to-chromium": "^1.5.402", "node-releases": "^2.0.53", "update-browserslist-db": "^1.3.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA=="],
|
||||||
|
|
||||||
|
"bun-ffi-structs": ["[email protected]", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-3gM7PpVWLyrwxWjcilSiGuhWanhZivvo6l0u573NziPH6f/gwk6McbaYgn7oJWov6pKGRTDbrg94W5DcJsKTtQ=="],
|
||||||
|
|
||||||
|
"bun-types": ["[email protected]", "", { "dependencies": { "@types/node": "*" } }, "sha512-iIKw23BspnQQYd3prITOBxeUsxBHnwzX6YJfGMuNOZzeNcMmVqzIIVGRm1l69ogaPQmb4wB6BN8mA5bE9YuC5Q=="],
|
||||||
|
|
||||||
|
"caniuse-lite": ["[email protected]", "", {}, "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg=="],
|
||||||
|
|
||||||
|
"cli-spinners": ["[email protected]", "", {}, "sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw=="],
|
||||||
|
|
||||||
|
"convert-source-map": ["[email protected]", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
||||||
|
|
||||||
|
"csstype": ["[email protected]", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
||||||
|
|
||||||
|
"debug": ["[email protected]", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||||
|
|
||||||
|
"diff": ["[email protected]", "", {}, "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw=="],
|
||||||
|
|
||||||
|
"electron-to-chromium": ["[email protected]", "", {}, "sha512-4T+DTDWuMPM4aHlHwWdAVCVWwp7LDilnhzkj+c/Lbj91XSQrLuOmZSLtS9Q4iIqjlPUbPOnC624zDVVHCHaolQ=="],
|
||||||
|
|
||||||
|
"emoji-regex": ["[email protected]", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
|
||||||
|
|
||||||
|
"entities": ["[email protected]", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="],
|
||||||
|
|
||||||
|
"es-errors": ["[email protected]", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
|
||||||
|
|
||||||
|
"escalade": ["[email protected]", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||||
|
|
||||||
|
"find-babel-config": ["[email protected]", "", { "dependencies": { "json5": "^2.2.3" } }, "sha512-ZfZp1rQyp4gyuxqt1ZqjFGVeVBvmpURMqdIWXbPRfB97Bf6BzdK/xSIbylEINzQ0kB5tlDQfn9HkNXXWsqTqLg=="],
|
||||||
|
|
||||||
|
"find-up": ["[email protected]", "", { "dependencies": { "locate-path": "^3.0.0" } }, "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg=="],
|
||||||
|
|
||||||
|
"fs.realpath": ["[email protected]", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
|
||||||
|
|
||||||
|
"function-bind": ["[email protected]", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
||||||
|
|
||||||
|
"gensync": ["[email protected]", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
|
||||||
|
|
||||||
|
"get-east-asian-width": ["[email protected]", "", {}, "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA=="],
|
||||||
|
|
||||||
|
"glob": ["[email protected]", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="],
|
||||||
|
|
||||||
|
"hasown": ["[email protected]", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="],
|
||||||
|
|
||||||
|
"html-entities": ["[email protected]", "", {}, "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA=="],
|
||||||
|
|
||||||
|
"is-core-module": ["[email protected]", "", { "dependencies": { "hasown": "^2.0.3" } }, "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA=="],
|
||||||
|
|
||||||
|
"js-tokens": ["[email protected]", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
||||||
|
|
||||||
|
"jsesc": ["[email protected]", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
|
||||||
|
|
||||||
|
"json5": ["[email protected]", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
||||||
|
|
||||||
|
"locate-path": ["[email protected]", "", { "dependencies": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" } }, "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A=="],
|
||||||
|
|
||||||
|
"lru-cache": ["[email protected]", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||||
|
|
||||||
|
"marked": ["[email protected]", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg=="],
|
||||||
|
|
||||||
|
"minimatch": ["[email protected]", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg=="],
|
||||||
|
|
||||||
|
"minipass": ["[email protected]", "", {}, "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ=="],
|
||||||
|
|
||||||
|
"ms": ["[email protected]", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||||
|
|
||||||
|
"node-releases": ["[email protected]", "", {}, "sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ=="],
|
||||||
|
|
||||||
|
"opentui-spinner": ["[email protected]", "", { "dependencies": { "cli-spinners": "^3.3.0" }, "peerDependencies": { "@opentui/core": "^0.3.4", "@opentui/react": "^0.3.4", "@opentui/solid": "^0.3.4", "typescript": "^5" }, "optionalPeers": ["@opentui/react", "@opentui/solid"] }, "sha512-nPzwAvJG+y9rVEwwHLHqbsMzLnIk2zw+F9LqwA7aYJvpM5gsrKC2rrGi36A+tZpA+1RnWxXeWEgVZMchnaH18Q=="],
|
||||||
|
|
||||||
|
"p-limit": ["[email protected]", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
|
||||||
|
|
||||||
|
"p-locate": ["[email protected]", "", { "dependencies": { "p-limit": "^2.0.0" } }, "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ=="],
|
||||||
|
|
||||||
|
"p-try": ["[email protected]", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="],
|
||||||
|
|
||||||
|
"parse5": ["[email protected]", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
|
||||||
|
|
||||||
|
"path-exists": ["[email protected]", "", {}, "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="],
|
||||||
|
|
||||||
|
"path-parse": ["[email protected]", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
|
||||||
|
|
||||||
|
"path-scurry": ["[email protected]", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
|
||||||
|
|
||||||
|
"picocolors": ["[email protected]", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||||
|
|
||||||
|
"pkg-up": ["[email protected]", "", { "dependencies": { "find-up": "^3.0.0" } }, "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA=="],
|
||||||
|
|
||||||
|
"reselect": ["[email protected]", "", {}, "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ=="],
|
||||||
|
|
||||||
|
"resolve": ["[email protected]", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="],
|
||||||
|
|
||||||
|
"s-js": ["[email protected]", "", {}, "sha512-RtpOm+cM6O0sHg6IA70wH+UC3FZcND+rccBZpBAHzlUgNO2Bm5BN+FnM8+OBxzXdwpKWFwX11JGF0MFRkhSoIQ=="],
|
||||||
|
|
||||||
|
"semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||||
|
|
||||||
|
"seroval": ["[email protected]", "", {}, "sha512-rVQVWjjSvlINzaQPZH5JFqsqEsIWdTxY3iJZCnTL/5gQbXIRooVZKI60tVCkOVfzcRPejboxO2t0P89dg5mQaA=="],
|
||||||
|
|
||||||
|
"seroval-plugins": ["[email protected]", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-HXuLAX2pu/UByPpaeo/TaMfvMIi+1QqIoPJYCcAtU8QkVNwgR6MPlGuCQTErV1JwraaMbYaWVIBX7mppzGLATQ=="],
|
||||||
|
|
||||||
|
"solid-js": ["[email protected]", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.5.0", "seroval-plugins": "~1.5.0" } }, "sha512-QzKaSJq2/iDrWR1As6MHZQ8fQkdOBf8GReYb7L5iKwMGceg7HxDcaOHk0at66tNgn9U2U7dXo8ZZpLIAmGMzgw=="],
|
||||||
|
|
||||||
|
"string-width": ["[email protected]", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
|
||||||
|
|
||||||
|
"strip-ansi": ["[email protected]", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
|
||||||
|
|
||||||
|
"supports-preserve-symlinks-flag": ["[email protected]", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
|
||||||
|
|
||||||
|
"typescript": ["[email protected]", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||||
|
|
||||||
|
"undici-types": ["[email protected]", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="],
|
||||||
|
|
||||||
|
"update-browserslist-db": ["[email protected]", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw=="],
|
||||||
|
|
||||||
|
"web-tree-sitter": ["[email protected]", "", { "peerDependencies": { "@types/emscripten": "^1.40.0" }, "optionalPeers": ["@types/emscripten"] }, "sha512-Y09sF44/13XvgVKgO2cNDw5rGk6s26MgoZPXLESvMXeefBf7i6/73eFurre0IsTW6E14Y0ArIzhUMmjoc7xyzA=="],
|
||||||
|
|
||||||
|
"yallist": ["[email protected]", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
||||||
|
|
||||||
|
"yaml": ["[email protected]", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="],
|
||||||
|
|
||||||
|
"zod": ["[email protected]", "", {}, "sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA=="],
|
||||||
|
|
||||||
|
"babel-plugin-jsx-dom-expressions/@babel/helper-module-imports": ["@babel/[email protected]", "", { "dependencies": { "@babel/types": "^7.18.6" } }, "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA=="],
|
||||||
|
|
||||||
|
"parse5/entities": ["[email protected]", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
|
||||||
|
|
||||||
|
"path-scurry/lru-cache": ["[email protected]", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||||
|
|
||||||
|
"path-scurry/minipass": ["[email protected]", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="],
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+25
@@ -0,0 +1,25 @@
|
|||||||
|
// @bun
|
||||||
|
// src/context/focus.ts
|
||||||
|
import { CliRenderEvents } from "@opentui/core";
|
||||||
|
import { useRenderer } from "@opentui/solid";
|
||||||
|
import { createEffect, createSignal, onCleanup } from "solid-js";
|
||||||
|
function isInside2(node, root) {
|
||||||
|
if (!root)
|
||||||
|
return false;
|
||||||
|
for (let current = node;current; current = current.parent) {
|
||||||
|
if (current === root)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
function useFocusWithin2(target) {
|
||||||
|
const renderer = useRenderer();
|
||||||
|
const [focused, setFocused] = createSignal(false);
|
||||||
|
const sync = () => setFocused(isInside2(renderer.currentFocusedRenderable, target() ?? null));
|
||||||
|
renderer.on(CliRenderEvents.FOCUSED_RENDERABLE, sync);
|
||||||
|
onCleanup(() => renderer.off(CliRenderEvents.FOCUSED_RENDERABLE, sync));
|
||||||
|
createEffect(sync);
|
||||||
|
return focused;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { isInside2, useFocusWithin2 };
|
||||||
Vendored
+21
@@ -0,0 +1,21 @@
|
|||||||
|
// @bun
|
||||||
|
// src/context/notifications.ts
|
||||||
|
import { createSignal } from "solid-js";
|
||||||
|
var DEFAULT_DURATION = 3000;
|
||||||
|
var [notifications2, setNotifications] = createSignal([]);
|
||||||
|
var nextId = 0;
|
||||||
|
function notify2(message, options = {}) {
|
||||||
|
const id = options.id ?? `toast-${nextId++}`;
|
||||||
|
const toast = { id, title: options.title, message, kind: options.kind ?? "info" };
|
||||||
|
setNotifications((prev) => [...prev.filter((n) => n.id !== id), toast]);
|
||||||
|
const duration = options.duration ?? DEFAULT_DURATION;
|
||||||
|
if (duration > 0) {
|
||||||
|
setTimeout(() => dismiss2(id), duration);
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
function dismiss2(id) {
|
||||||
|
setNotifications((prev) => prev.filter((n) => n.id !== id));
|
||||||
|
}
|
||||||
|
|
||||||
|
export { notifications2, notify2, dismiss2 };
|
||||||
Vendored
+23
@@ -0,0 +1,23 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
theme2
|
||||||
|
} from "./chunk-p6jvscj3.js";
|
||||||
|
|
||||||
|
// src/components/Box.tsx
|
||||||
|
import { spread as _$spread } from "@opentui/solid";
|
||||||
|
import { mergeProps as _$mergeProps } from "@opentui/solid";
|
||||||
|
import { memo as _$memo } from "@opentui/solid";
|
||||||
|
import { createElement as _$createElement } from "@opentui/solid";
|
||||||
|
function Box2(props) {
|
||||||
|
return (() => {
|
||||||
|
var _el$ = _$createElement("box");
|
||||||
|
_$spread(_el$, _$mergeProps(props, {
|
||||||
|
get borderStyle() {
|
||||||
|
return props.borderStyle ?? theme2.borderStyle;
|
||||||
|
}
|
||||||
|
}), false);
|
||||||
|
return _el$;
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Box2 };
|
||||||
Vendored
+79
@@ -0,0 +1,79 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
notifications2,
|
||||||
|
dismiss2
|
||||||
|
} from "./chunk-6ggk5fg7.js";
|
||||||
|
import {
|
||||||
|
theme2
|
||||||
|
} from "./chunk-p6jvscj3.js";
|
||||||
|
|
||||||
|
// src/components/NotificationHost.tsx
|
||||||
|
import { effect as _$effect } from "@opentui/solid";
|
||||||
|
import { insertNode as _$insertNode } from "@opentui/solid";
|
||||||
|
import { memo as _$memo } from "@opentui/solid";
|
||||||
|
import { insert as _$insert } from "@opentui/solid";
|
||||||
|
import { createComponent as _$createComponent } from "@opentui/solid";
|
||||||
|
import { setProp as _$setProp } from "@opentui/solid";
|
||||||
|
import { createElement as _$createElement } from "@opentui/solid";
|
||||||
|
import { Portal } from "@opentui/solid";
|
||||||
|
import { For } from "solid-js";
|
||||||
|
var KIND_LABEL = {
|
||||||
|
info: "Info",
|
||||||
|
success: "Success",
|
||||||
|
warning: "Warning",
|
||||||
|
error: "Error"
|
||||||
|
};
|
||||||
|
function NotificationHost2(props = {}) {
|
||||||
|
const defaultColor = () => ({
|
||||||
|
info: theme2.primary,
|
||||||
|
success: theme2.success,
|
||||||
|
warning: theme2.warning,
|
||||||
|
error: theme2.error
|
||||||
|
});
|
||||||
|
const colorFor = (kind) => props.colors?.[kind] ?? defaultColor()[kind];
|
||||||
|
return _$createComponent(Portal, {
|
||||||
|
get children() {
|
||||||
|
var _el$ = _$createElement("box");
|
||||||
|
_$setProp(_el$, "position", "absolute");
|
||||||
|
_$setProp(_el$, "top", 1);
|
||||||
|
_$setProp(_el$, "right", 1);
|
||||||
|
_$setProp(_el$, "flexDirection", "column");
|
||||||
|
_$setProp(_el$, "zIndex", 1000);
|
||||||
|
_$insert(_el$, _$createComponent(For, {
|
||||||
|
get each() {
|
||||||
|
return notifications2();
|
||||||
|
},
|
||||||
|
children: (toast) => (() => {
|
||||||
|
var _el$2 = _$createElement("box"), _el$3 = _$createElement("text"), _el$4 = _$createElement("text");
|
||||||
|
_$insertNode(_el$2, _el$3);
|
||||||
|
_$insertNode(_el$2, _el$4);
|
||||||
|
_$setProp(_el$2, "border", true);
|
||||||
|
_$setProp(_el$2, "marginBottom", 1);
|
||||||
|
_$insert(_el$3, () => toast.title ?? KIND_LABEL[toast.kind]);
|
||||||
|
_$insert(_el$4, () => toast.message);
|
||||||
|
_$effect((_p$) => {
|
||||||
|
var _v$ = theme2.borderStyle, _v$2 = colorFor(toast.kind), _v$3 = props.backgroundColor ?? theme2.background, _v$4 = props.width ?? 32, _v$5 = theme2.mouse ? () => dismiss2(toast.id) : undefined, _v$6 = colorFor(toast.kind);
|
||||||
|
_v$ !== _p$.e && (_p$.e = _$setProp(_el$2, "borderStyle", _v$, _p$.e));
|
||||||
|
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$2, "borderColor", _v$2, _p$.t));
|
||||||
|
_v$3 !== _p$.a && (_p$.a = _$setProp(_el$2, "backgroundColor", _v$3, _p$.a));
|
||||||
|
_v$4 !== _p$.o && (_p$.o = _$setProp(_el$2, "width", _v$4, _p$.o));
|
||||||
|
_v$5 !== _p$.i && (_p$.i = _$setProp(_el$2, "onMouseDown", _v$5, _p$.i));
|
||||||
|
_v$6 !== _p$.n && (_p$.n = _$setProp(_el$3, "fg", _v$6, _p$.n));
|
||||||
|
return _p$;
|
||||||
|
}, {
|
||||||
|
e: undefined,
|
||||||
|
t: undefined,
|
||||||
|
a: undefined,
|
||||||
|
o: undefined,
|
||||||
|
i: undefined,
|
||||||
|
n: undefined
|
||||||
|
});
|
||||||
|
return _el$2;
|
||||||
|
})()
|
||||||
|
}));
|
||||||
|
return _el$;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export { NotificationHost2 };
|
||||||
Vendored
+121
@@ -0,0 +1,121 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
theme2
|
||||||
|
} from "./chunk-p6jvscj3.js";
|
||||||
|
|
||||||
|
// src/components/HelpBar.tsx
|
||||||
|
import { createTextNode as _$createTextNode } from "@opentui/solid";
|
||||||
|
import { insertNode as _$insertNode } from "@opentui/solid";
|
||||||
|
import { effect as _$effect } from "@opentui/solid";
|
||||||
|
import { memo as _$memo } from "@opentui/solid";
|
||||||
|
import { insert as _$insert } from "@opentui/solid";
|
||||||
|
import { createComponent as _$createComponent } from "@opentui/solid";
|
||||||
|
import { use as _$use } from "@opentui/solid";
|
||||||
|
import { setProp as _$setProp } from "@opentui/solid";
|
||||||
|
import { createElement as _$createElement } from "@opentui/solid";
|
||||||
|
import { CliRenderEvents, TextAttributes } from "@opentui/core";
|
||||||
|
import { onResize, useRenderer } from "@opentui/solid";
|
||||||
|
import { formatCommandBindings } from "@opentui/keymap/extras";
|
||||||
|
import { useKeymapSelector } from "@opentui/keymap/solid";
|
||||||
|
import { createMemo, createSignal, For } from "solid-js";
|
||||||
|
var KEY_DISPLAY = {
|
||||||
|
up: "\u2191",
|
||||||
|
down: "\u2193",
|
||||||
|
left: "\u2190",
|
||||||
|
right: "\u2192",
|
||||||
|
enter: "Enter",
|
||||||
|
escape: "Esc"
|
||||||
|
};
|
||||||
|
var SEPARATOR = " ";
|
||||||
|
var ELLIPSIS = "\u2026";
|
||||||
|
function HelpBar2(props = {}) {
|
||||||
|
const accent = () => props.accentColor ?? theme2.primary;
|
||||||
|
const muted = () => props.mutedColor ?? theme2.muted;
|
||||||
|
const renderer = useRenderer();
|
||||||
|
const [width, setWidth] = createSignal(0);
|
||||||
|
let container;
|
||||||
|
const measure = () => {
|
||||||
|
if (!container)
|
||||||
|
return;
|
||||||
|
setWidth(container.width);
|
||||||
|
};
|
||||||
|
const remeasureNextFrame = () => renderer.once(CliRenderEvents.FRAME, measure);
|
||||||
|
onResize(remeasureNextFrame);
|
||||||
|
const entries = useKeymapSelector((keymap) => keymap.getCommandEntries({
|
||||||
|
visibility: "active"
|
||||||
|
}).filter((entry) => entry.command.shortHelp === true).map((entry) => ({
|
||||||
|
command: entry.command.name,
|
||||||
|
keys: formatCommandBindings(entry.bindings, {
|
||||||
|
keyNameAliases: KEY_DISPLAY
|
||||||
|
}) ?? "",
|
||||||
|
label: typeof entry.command.desc === "string" ? entry.command.desc : entry.command.name
|
||||||
|
})));
|
||||||
|
const visible = createMemo(() => {
|
||||||
|
const available = width() - 2;
|
||||||
|
const shown = [];
|
||||||
|
let used = 0;
|
||||||
|
for (const entry of entries()) {
|
||||||
|
const sepWidth = shown.length > 0 ? SEPARATOR.length : 0;
|
||||||
|
const entryWidth = entry.keys.length + 1 + entry.label.length;
|
||||||
|
if (used + sepWidth + entryWidth <= available) {
|
||||||
|
used += sepWidth + entryWidth;
|
||||||
|
shown.push(entry);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const remaining = available - used - sepWidth;
|
||||||
|
const prefix = `${entry.keys} `;
|
||||||
|
if (remaining > prefix.length + ELLIPSIS.length) {
|
||||||
|
const labelBudget = remaining - prefix.length - ELLIPSIS.length;
|
||||||
|
shown.push({
|
||||||
|
...entry,
|
||||||
|
label: `${entry.label.slice(0, labelBudget)}${ELLIPSIS}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return shown;
|
||||||
|
});
|
||||||
|
return (() => {
|
||||||
|
var _el$ = _$createElement("box");
|
||||||
|
_$use((el) => {
|
||||||
|
container = el;
|
||||||
|
remeasureNextFrame();
|
||||||
|
}, _el$);
|
||||||
|
_$setProp(_el$, "flexDirection", "row");
|
||||||
|
_$setProp(_el$, "paddingLeft", 1);
|
||||||
|
_$setProp(_el$, "paddingRight", 1);
|
||||||
|
_$setProp(_el$, "overflow", "hidden");
|
||||||
|
_$insert(_el$, _$createComponent(For, {
|
||||||
|
get each() {
|
||||||
|
return visible();
|
||||||
|
},
|
||||||
|
children: (entry, i) => [_$memo(() => _$memo(() => i() > 0)() && (() => {
|
||||||
|
var _el$4 = _$createElement("text");
|
||||||
|
_$insertNode(_el$4, _$createTextNode(` `));
|
||||||
|
_$effect((_$p) => _$setProp(_el$4, "fg", muted(), _$p));
|
||||||
|
return _el$4;
|
||||||
|
})()), (() => {
|
||||||
|
var _el$2 = _$createElement("text");
|
||||||
|
_$insert(_el$2, () => entry.keys);
|
||||||
|
_$effect((_p$) => {
|
||||||
|
var _v$ = accent(), _v$2 = TextAttributes.BOLD;
|
||||||
|
_v$ !== _p$.e && (_p$.e = _$setProp(_el$2, "fg", _v$, _p$.e));
|
||||||
|
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$2, "attributes", _v$2, _p$.t));
|
||||||
|
return _p$;
|
||||||
|
}, {
|
||||||
|
e: undefined,
|
||||||
|
t: undefined
|
||||||
|
});
|
||||||
|
return _el$2;
|
||||||
|
})(), (() => {
|
||||||
|
var _el$3 = _$createElement("text");
|
||||||
|
_$insert(_el$3, () => ` ${entry.label}`);
|
||||||
|
_$effect((_$p) => _$setProp(_el$3, "fg", muted(), _$p));
|
||||||
|
return _el$3;
|
||||||
|
})()]
|
||||||
|
}));
|
||||||
|
return _el$;
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
export { HelpBar2 };
|
||||||
Vendored
+44
@@ -0,0 +1,44 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
theme2
|
||||||
|
} from "./chunk-p6jvscj3.js";
|
||||||
|
|
||||||
|
// src/components/KeyValue.tsx
|
||||||
|
import { memo as _$memo } from "@opentui/solid";
|
||||||
|
import { effect as _$effect } from "@opentui/solid";
|
||||||
|
import { insertNode as _$insertNode } from "@opentui/solid";
|
||||||
|
import { createComponent as _$createComponent } from "@opentui/solid";
|
||||||
|
import { insert as _$insert } from "@opentui/solid";
|
||||||
|
import { setProp as _$setProp } from "@opentui/solid";
|
||||||
|
import { createElement as _$createElement } from "@opentui/solid";
|
||||||
|
import { Show } from "solid-js";
|
||||||
|
function KeyValue2(props) {
|
||||||
|
return (() => {
|
||||||
|
var _el$ = _$createElement("box"), _el$2 = _$createElement("text");
|
||||||
|
_$insertNode(_el$, _el$2);
|
||||||
|
_$setProp(_el$, "flexShrink", 0);
|
||||||
|
_$setProp(_el$, "flexDirection", "row");
|
||||||
|
_$setProp(_el$, "justifyContent", "space-between");
|
||||||
|
_$insert(_el$2, () => props.label);
|
||||||
|
_$insert(_el$, _$createComponent(Show, {
|
||||||
|
get when() {
|
||||||
|
return props.children;
|
||||||
|
},
|
||||||
|
get fallback() {
|
||||||
|
return (() => {
|
||||||
|
var _el$3 = _$createElement("text");
|
||||||
|
_$insert(_el$3, () => props.value ?? "");
|
||||||
|
_$effect((_$p) => _$setProp(_el$3, "fg", props.valueColor, _$p));
|
||||||
|
return _el$3;
|
||||||
|
})();
|
||||||
|
},
|
||||||
|
get children() {
|
||||||
|
return props.children;
|
||||||
|
}
|
||||||
|
}), null);
|
||||||
|
_$effect((_$p) => _$setProp(_el$2, "fg", theme2.muted, _$p));
|
||||||
|
return _el$;
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
export { KeyValue2 };
|
||||||
Vendored
+9
@@ -0,0 +1,9 @@
|
|||||||
|
// @bun
|
||||||
|
// src/context/help.ts
|
||||||
|
import { createSignal } from "solid-js";
|
||||||
|
var [helpOpen2, setHelpOpen] = createSignal(false);
|
||||||
|
function toggleHelp2() {
|
||||||
|
setHelpOpen((open) => !open);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { helpOpen2, toggleHelp2 };
|
||||||
Vendored
+87
@@ -0,0 +1,87 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
helpOpen2,
|
||||||
|
toggleHelp2
|
||||||
|
} from "./chunk-ee2qaswx.js";
|
||||||
|
import {
|
||||||
|
Modal2
|
||||||
|
} from "./chunk-t2cszvff.js";
|
||||||
|
|
||||||
|
// src/components/HelpModal.tsx
|
||||||
|
import { createComponent as _$createComponent } from "@opentui/solid";
|
||||||
|
import { insert as _$insert } from "@opentui/solid";
|
||||||
|
import { setProp as _$setProp } from "@opentui/solid";
|
||||||
|
import { effect as _$effect } from "@opentui/solid";
|
||||||
|
import { createTextNode as _$createTextNode } from "@opentui/solid";
|
||||||
|
import { insertNode as _$insertNode } from "@opentui/solid";
|
||||||
|
import { createElement as _$createElement } from "@opentui/solid";
|
||||||
|
import { TextAttributes } from "@opentui/core";
|
||||||
|
import { formatCommandBindings } from "@opentui/keymap/extras";
|
||||||
|
import { useKeymapSelector } from "@opentui/keymap/solid";
|
||||||
|
import { createSignal } from "solid-js";
|
||||||
|
var KEY_DISPLAY = {
|
||||||
|
up: "\u2191",
|
||||||
|
down: "\u2193",
|
||||||
|
left: "\u2190",
|
||||||
|
right: "\u2192",
|
||||||
|
enter: "Enter",
|
||||||
|
escape: "Esc"
|
||||||
|
};
|
||||||
|
function HelpModal2(props = {}) {
|
||||||
|
const [scope, setScope] = createSignal(null);
|
||||||
|
const entries = useKeymapSelector((keymap) => keymap.getCommandEntries({
|
||||||
|
visibility: "active",
|
||||||
|
focused: scope()
|
||||||
|
}).map((entry) => ({
|
||||||
|
command: entry.command.name,
|
||||||
|
keys: formatCommandBindings(entry.bindings, {
|
||||||
|
keyNameAliases: KEY_DISPLAY
|
||||||
|
}) ?? "",
|
||||||
|
label: typeof entry.command.desc === "string" ? entry.command.desc : entry.command.name
|
||||||
|
})));
|
||||||
|
return _$createComponent(Modal2, {
|
||||||
|
get open() {
|
||||||
|
return helpOpen2();
|
||||||
|
},
|
||||||
|
onDismiss: toggleHelp2,
|
||||||
|
onOpen: (previous) => setScope(() => previous),
|
||||||
|
get dismissKey() {
|
||||||
|
return props.dismissKey;
|
||||||
|
},
|
||||||
|
get accentColor() {
|
||||||
|
return props.accentColor;
|
||||||
|
},
|
||||||
|
get backgroundColor() {
|
||||||
|
return props.backgroundColor;
|
||||||
|
},
|
||||||
|
get backdropColor() {
|
||||||
|
return props.backdropColor;
|
||||||
|
},
|
||||||
|
get width() {
|
||||||
|
return props.width;
|
||||||
|
},
|
||||||
|
get maxHeight() {
|
||||||
|
return entries().length + 6;
|
||||||
|
},
|
||||||
|
scrollable: true,
|
||||||
|
get children() {
|
||||||
|
return [(() => {
|
||||||
|
var _el$ = _$createElement("text");
|
||||||
|
_$insertNode(_el$, _$createTextNode(`Keybindings`));
|
||||||
|
_$effect((_$p) => _$setProp(_el$, "attributes", TextAttributes.BOLD, _$p));
|
||||||
|
return _el$;
|
||||||
|
})(), (() => {
|
||||||
|
var _el$3 = _$createElement("text");
|
||||||
|
_$insertNode(_el$3, _$createTextNode(` `));
|
||||||
|
return _el$3;
|
||||||
|
})(), (() => {
|
||||||
|
var _el$5 = _$createElement("text");
|
||||||
|
_$insert(_el$5, () => entries().map((entry) => `${entry.keys.padEnd(10)}${entry.label}`).join(`
|
||||||
|
`));
|
||||||
|
return _el$5;
|
||||||
|
})()];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export { HelpModal2 };
|
||||||
Vendored
+26
@@ -0,0 +1,26 @@
|
|||||||
|
// @bun
|
||||||
|
// src/context/helper.tsx
|
||||||
|
import { createComponent as _$createComponent } from "@opentui/solid";
|
||||||
|
import { createContext, useContext } from "solid-js";
|
||||||
|
function createSimpleContext2(input) {
|
||||||
|
const ctx = createContext();
|
||||||
|
return {
|
||||||
|
provider: (props) => {
|
||||||
|
const value = input.init(props);
|
||||||
|
return _$createComponent(ctx.Provider, {
|
||||||
|
value,
|
||||||
|
get children() {
|
||||||
|
return props.children;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
use() {
|
||||||
|
const value = useContext(ctx);
|
||||||
|
if (!value)
|
||||||
|
throw new Error(`${input.name} context must be used within its provider`);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export { createSimpleContext2 };
|
||||||
Vendored
+35
@@ -0,0 +1,35 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
theme2
|
||||||
|
} from "./chunk-p6jvscj3.js";
|
||||||
|
|
||||||
|
// src/components/Spinner.tsx
|
||||||
|
import { setProp as _$setProp } from "@opentui/solid";
|
||||||
|
import { effect as _$effect } from "@opentui/solid";
|
||||||
|
import { createElement as _$createElement } from "@opentui/solid";
|
||||||
|
import"opentui-spinner/solid";
|
||||||
|
function Spinner2(props) {
|
||||||
|
return (() => {
|
||||||
|
var _el$ = _$createElement("spinner");
|
||||||
|
_$effect((_p$) => {
|
||||||
|
var { name: _v$, frames: _v$2, interval: _v$3 } = props, _v$4 = props.autoplay ?? !theme2.reducedMotion, _v$5 = props.color ?? theme2.primary, _v$6 = props.backgroundColor;
|
||||||
|
_v$ !== _p$.e && (_p$.e = _$setProp(_el$, "name", _v$, _p$.e));
|
||||||
|
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$, "frames", _v$2, _p$.t));
|
||||||
|
_v$3 !== _p$.a && (_p$.a = _$setProp(_el$, "interval", _v$3, _p$.a));
|
||||||
|
_v$4 !== _p$.o && (_p$.o = _$setProp(_el$, "autoplay", _v$4, _p$.o));
|
||||||
|
_v$5 !== _p$.i && (_p$.i = _$setProp(_el$, "color", _v$5, _p$.i));
|
||||||
|
_v$6 !== _p$.n && (_p$.n = _$setProp(_el$, "backgroundColor", _v$6, _p$.n));
|
||||||
|
return _p$;
|
||||||
|
}, {
|
||||||
|
e: undefined,
|
||||||
|
t: undefined,
|
||||||
|
a: undefined,
|
||||||
|
o: undefined,
|
||||||
|
i: undefined,
|
||||||
|
n: undefined
|
||||||
|
});
|
||||||
|
return _el$;
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Spinner2 };
|
||||||
Vendored
+65
@@ -0,0 +1,65 @@
|
|||||||
|
// @bun
|
||||||
|
// src/config.ts
|
||||||
|
import { z } from "zod";
|
||||||
|
var COLOR_KEYS2 = [
|
||||||
|
"base00",
|
||||||
|
"base01",
|
||||||
|
"base02",
|
||||||
|
"base03",
|
||||||
|
"base04",
|
||||||
|
"base05",
|
||||||
|
"base06",
|
||||||
|
"base07",
|
||||||
|
"base08",
|
||||||
|
"base09",
|
||||||
|
"base0a",
|
||||||
|
"base0b",
|
||||||
|
"base0c",
|
||||||
|
"base0d",
|
||||||
|
"base0e",
|
||||||
|
"base0f"
|
||||||
|
];
|
||||||
|
var OPENTUI_BORDER_STYLES2 = ["single", "double", "rounded", "heavy"];
|
||||||
|
var hexColor = z.string().regex(/^#?[0-9a-fA-F]{6}$/, 'expected a hex color like "#1e1e2e"');
|
||||||
|
var colorsShape = {};
|
||||||
|
for (const key of COLOR_KEYS2)
|
||||||
|
colorsShape[key] = hexColor.optional();
|
||||||
|
var ThemeConfigSchema2 = z.object({
|
||||||
|
nerd_fonts: z.boolean().optional(),
|
||||||
|
emoji_fonts: z.boolean().optional(),
|
||||||
|
reduced_motion: z.boolean().optional(),
|
||||||
|
mouse: z.boolean().optional(),
|
||||||
|
border: z.enum(OPENTUI_BORDER_STYLES2).optional(),
|
||||||
|
colors: z.object(colorsShape).optional()
|
||||||
|
});
|
||||||
|
function layerConfigs2(layers) {
|
||||||
|
const out = {};
|
||||||
|
for (const layer of layers) {
|
||||||
|
const { colors, ...rest } = layer;
|
||||||
|
Object.assign(out, rest);
|
||||||
|
if (colors)
|
||||||
|
out.colors = { ...out.colors, ...colors };
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
function resolveConfig2(config) {
|
||||||
|
const colors = {};
|
||||||
|
for (const key of COLOR_KEYS2)
|
||||||
|
colors[key] = normalizeColor2(config.colors?.[key] ?? "");
|
||||||
|
return {
|
||||||
|
nerdFonts: config.nerd_fonts ?? false,
|
||||||
|
emojiFonts: config.emoji_fonts ?? false,
|
||||||
|
reducedMotion: config.reduced_motion ?? false,
|
||||||
|
mouse: config.mouse ?? true,
|
||||||
|
border: config.border ?? "rounded",
|
||||||
|
colors
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function normalizeColor2(hex) {
|
||||||
|
const trimmed = hex.trim();
|
||||||
|
if (trimmed && !trimmed.startsWith("#"))
|
||||||
|
return `#${trimmed}`;
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { COLOR_KEYS2, OPENTUI_BORDER_STYLES2, ThemeConfigSchema2, layerConfigs2, resolveConfig2, normalizeColor2 };
|
||||||
Vendored
+79
@@ -0,0 +1,79 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
ThemeConfigSchema2,
|
||||||
|
layerConfigs2,
|
||||||
|
resolveConfig2
|
||||||
|
} from "./chunk-njvj322f.js";
|
||||||
|
import {
|
||||||
|
notify2
|
||||||
|
} from "./chunk-6ggk5fg7.js";
|
||||||
|
import {
|
||||||
|
buildPresets2
|
||||||
|
} from "./chunk-yyg967q8.js";
|
||||||
|
import {
|
||||||
|
configPath2
|
||||||
|
} from "./chunk-xykm10y9.js";
|
||||||
|
|
||||||
|
// src/index.ts
|
||||||
|
import { existsSync, readFileSync } from "fs";
|
||||||
|
import { join } from "path";
|
||||||
|
import { parse as parseYAML } from "yaml";
|
||||||
|
import { z } from "zod";
|
||||||
|
function reportThemeError(path, message) {
|
||||||
|
const text = `${path}: ${message}`;
|
||||||
|
console.error(`[ilovetui theme] ${text}`);
|
||||||
|
notify2(text, { kind: "warning", duration: 0 });
|
||||||
|
}
|
||||||
|
function readThemeFile(path) {
|
||||||
|
if (!existsSync(path))
|
||||||
|
return {};
|
||||||
|
let raw;
|
||||||
|
try {
|
||||||
|
raw = parseYAML(readFileSync(path, "utf8"));
|
||||||
|
} catch (error) {
|
||||||
|
reportThemeError(path, error instanceof Error ? error.message : String(error));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const result = ThemeConfigSchema2.safeParse(raw ?? {});
|
||||||
|
if (!result.success) {
|
||||||
|
reportThemeError(path, z.prettifyError(result.error));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return result.data;
|
||||||
|
}
|
||||||
|
var fileLayers = [readThemeFile(join(import.meta.dir, "default.yaml")), readThemeFile(configPath2())];
|
||||||
|
function buildTheme(overrides) {
|
||||||
|
const resolved = resolveConfig2(layerConfigs2([...fileLayers, overrides]));
|
||||||
|
const colors = resolved.colors;
|
||||||
|
return {
|
||||||
|
...colors,
|
||||||
|
background: colors.base00,
|
||||||
|
subtleBg: colors.base01,
|
||||||
|
selection: colors.base02,
|
||||||
|
subtle: colors.base03,
|
||||||
|
muted: colors.base04,
|
||||||
|
text: colors.base05,
|
||||||
|
primary: colors.base0d,
|
||||||
|
success: colors.base0b,
|
||||||
|
warning: colors.base09,
|
||||||
|
error: colors.base08,
|
||||||
|
nerdFonts: resolved.nerdFonts,
|
||||||
|
emojiFonts: resolved.emojiFonts,
|
||||||
|
reducedMotion: resolved.reducedMotion,
|
||||||
|
mouse: resolved.mouse,
|
||||||
|
borderStyle: resolved.border
|
||||||
|
};
|
||||||
|
}
|
||||||
|
var theme2 = buildTheme({});
|
||||||
|
var presets2 = buildPresets2(theme2);
|
||||||
|
function configureTheme2(overrides = {}) {
|
||||||
|
Object.assign(theme2, buildTheme(overrides));
|
||||||
|
const next = buildPresets2(theme2);
|
||||||
|
for (const key of Object.keys(next))
|
||||||
|
Object.assign(presets2[key], next[key]);
|
||||||
|
}
|
||||||
|
function icon2(codepoint) {
|
||||||
|
return theme2.nerdFonts ? `${codepoint} ` : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
export { theme2, presets2, configureTheme2, icon2 };
|
||||||
Vendored
+54
@@ -0,0 +1,54 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
theme2
|
||||||
|
} from "./chunk-p6jvscj3.js";
|
||||||
|
|
||||||
|
// src/components/MinSizeGuard.tsx
|
||||||
|
import { createComponent as _$createComponent } from "@opentui/solid";
|
||||||
|
import { effect as _$effect } from "@opentui/solid";
|
||||||
|
import { insertNode as _$insertNode } from "@opentui/solid";
|
||||||
|
import { insert as _$insert } from "@opentui/solid";
|
||||||
|
import { setProp as _$setProp } from "@opentui/solid";
|
||||||
|
import { createElement as _$createElement } from "@opentui/solid";
|
||||||
|
import { memo as _$memo } from "@opentui/solid";
|
||||||
|
import { useTerminalDimensions } from "@opentui/solid";
|
||||||
|
import { Show } from "solid-js";
|
||||||
|
function MinSizeGuard2(props) {
|
||||||
|
const dimensions = useTerminalDimensions();
|
||||||
|
const minWidth = () => props.minWidth ?? 80;
|
||||||
|
const minHeight = () => props.minHeight ?? 24;
|
||||||
|
const tooSmall = () => dimensions().width < minWidth() || dimensions().height < minHeight();
|
||||||
|
return [_$memo(() => props.children), _$createComponent(Show, {
|
||||||
|
get when() {
|
||||||
|
return tooSmall();
|
||||||
|
},
|
||||||
|
get children() {
|
||||||
|
var _el$ = _$createElement("box"), _el$2 = _$createElement("text");
|
||||||
|
_$insertNode(_el$, _el$2);
|
||||||
|
_$setProp(_el$, "position", "absolute");
|
||||||
|
_$setProp(_el$, "top", 0);
|
||||||
|
_$setProp(_el$, "left", 0);
|
||||||
|
_$setProp(_el$, "alignItems", "center");
|
||||||
|
_$setProp(_el$, "justifyContent", "center");
|
||||||
|
_$setProp(_el$, "zIndex", 9999);
|
||||||
|
_$insert(_el$2, () => `Terminal too small
|
||||||
|
Minimum size: ${minWidth()}x${minHeight()} \u2014 current: ${dimensions().width}x${dimensions().height}`);
|
||||||
|
_$effect((_p$) => {
|
||||||
|
var _v$ = dimensions().width, _v$2 = dimensions().height, _v$3 = props.backgroundColor ?? theme2.background, _v$4 = props.textColor ?? theme2.muted;
|
||||||
|
_v$ !== _p$.e && (_p$.e = _$setProp(_el$, "width", _v$, _p$.e));
|
||||||
|
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$, "height", _v$2, _p$.t));
|
||||||
|
_v$3 !== _p$.a && (_p$.a = _$setProp(_el$, "backgroundColor", _v$3, _p$.a));
|
||||||
|
_v$4 !== _p$.o && (_p$.o = _$setProp(_el$2, "fg", _v$4, _p$.o));
|
||||||
|
return _p$;
|
||||||
|
}, {
|
||||||
|
e: undefined,
|
||||||
|
t: undefined,
|
||||||
|
a: undefined,
|
||||||
|
o: undefined
|
||||||
|
});
|
||||||
|
return _el$;
|
||||||
|
}
|
||||||
|
})];
|
||||||
|
}
|
||||||
|
|
||||||
|
export { MinSizeGuard2 };
|
||||||
Vendored
+61
@@ -0,0 +1,61 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
theme2
|
||||||
|
} from "./chunk-p6jvscj3.js";
|
||||||
|
import {
|
||||||
|
Badge2
|
||||||
|
} from "./chunk-v9cetqzr.js";
|
||||||
|
|
||||||
|
// src/components/Toggle.tsx
|
||||||
|
import { setProp as _$setProp } from "@opentui/solid";
|
||||||
|
import { effect as _$effect } from "@opentui/solid";
|
||||||
|
import { insert as _$insert } from "@opentui/solid";
|
||||||
|
import { createComponent as _$createComponent } from "@opentui/solid";
|
||||||
|
import { memo as _$memo } from "@opentui/solid";
|
||||||
|
import { createElement as _$createElement } from "@opentui/solid";
|
||||||
|
import { TextAttributes } from "@opentui/core";
|
||||||
|
import { Show } from "solid-js";
|
||||||
|
function Toggle2(props) {
|
||||||
|
const onColor = () => props.onColor ?? theme2.success;
|
||||||
|
const offColor = () => props.offColor ?? theme2.muted;
|
||||||
|
const nerdFonts = () => props.withNerdfont ?? theme2.nerdFonts;
|
||||||
|
return (() => {
|
||||||
|
var _el$ = _$createElement("box");
|
||||||
|
_$insert(_el$, _$createComponent(Show, {
|
||||||
|
get when() {
|
||||||
|
return nerdFonts();
|
||||||
|
},
|
||||||
|
get fallback() {
|
||||||
|
return (() => {
|
||||||
|
var _el$2 = _$createElement("text");
|
||||||
|
_$insert(_el$2, () => props.on ? "on" : "off");
|
||||||
|
_$effect((_p$) => {
|
||||||
|
var _v$ = props.on ? onColor() : offColor(), _v$2 = TextAttributes.BOLD;
|
||||||
|
_v$ !== _p$.e && (_p$.e = _$setProp(_el$2, "fg", _v$, _p$.e));
|
||||||
|
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$2, "attributes", _v$2, _p$.t));
|
||||||
|
return _p$;
|
||||||
|
}, {
|
||||||
|
e: undefined,
|
||||||
|
t: undefined
|
||||||
|
});
|
||||||
|
return _el$2;
|
||||||
|
})();
|
||||||
|
},
|
||||||
|
get children() {
|
||||||
|
return _$createComponent(Badge2, {
|
||||||
|
get label() {
|
||||||
|
return props.on ? " \u25CF" : "\u25CF ";
|
||||||
|
},
|
||||||
|
get color() {
|
||||||
|
return _$memo(() => !!props.on)() ? onColor() : offColor();
|
||||||
|
},
|
||||||
|
withNerdfont: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
_$effect((_$p) => _$setProp(_el$, "onMouseDown", theme2.mouse ? props.onToggle : undefined, _$p));
|
||||||
|
return _el$;
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Toggle2 };
|
||||||
Vendored
+155
@@ -0,0 +1,155 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
theme2,
|
||||||
|
presets2
|
||||||
|
} from "./chunk-p6jvscj3.js";
|
||||||
|
|
||||||
|
// src/components/Sidebar.tsx
|
||||||
|
import { use as _$use } from "@opentui/solid";
|
||||||
|
import { createComponent as _$createComponent } from "@opentui/solid";
|
||||||
|
import { createTextNode as _$createTextNode } from "@opentui/solid";
|
||||||
|
import { insertNode as _$insertNode } from "@opentui/solid";
|
||||||
|
import { effect as _$effect } from "@opentui/solid";
|
||||||
|
import { insert as _$insert } from "@opentui/solid";
|
||||||
|
import { setProp as _$setProp } from "@opentui/solid";
|
||||||
|
import { createElement as _$createElement } from "@opentui/solid";
|
||||||
|
import { TextAttributes } from "@opentui/core";
|
||||||
|
import { createEffect, Show } from "solid-js";
|
||||||
|
function truncateWithEllipsis(text, maxWidth) {
|
||||||
|
if (maxWidth <= 0)
|
||||||
|
return "";
|
||||||
|
if (text.length <= maxWidth)
|
||||||
|
return text;
|
||||||
|
return text.substring(0, Math.max(0, maxWidth - 1)) + "\u2026";
|
||||||
|
}
|
||||||
|
function itemIndexAtScreenY(el, screenY) {
|
||||||
|
const internals = el;
|
||||||
|
if (!internals.linesPerItem)
|
||||||
|
return null;
|
||||||
|
const localY = screenY - el.screenY;
|
||||||
|
if (localY < 0)
|
||||||
|
return null;
|
||||||
|
const index = internals.scrollOffset + Math.floor(localY / internals.linesPerItem);
|
||||||
|
return index >= 0 && index < el.options.length ? index : null;
|
||||||
|
}
|
||||||
|
var BORDER_WIDTH = 2;
|
||||||
|
var ITEM_LEFT_PADDING = 1;
|
||||||
|
var ITEM_INDICATOR_WIDTH = 2;
|
||||||
|
function Sidebar2(props) {
|
||||||
|
let select;
|
||||||
|
const contentWidth = () => Math.max(0, (props.width ?? 24) - BORDER_WIDTH);
|
||||||
|
const itemTextWidth = () => Math.max(0, contentWidth() - ITEM_LEFT_PADDING - ITEM_INDICATOR_WIDTH);
|
||||||
|
const displayTitle = () => props.title ? truncateWithEllipsis(props.title, contentWidth()) : props.title;
|
||||||
|
const displayItems = () => props.items.map((item) => ({
|
||||||
|
...item,
|
||||||
|
name: truncateWithEllipsis(item.name, itemTextWidth()),
|
||||||
|
description: truncateWithEllipsis(item.description, itemTextWidth())
|
||||||
|
}));
|
||||||
|
const handleMouseDown = (event) => {
|
||||||
|
if (!select)
|
||||||
|
return;
|
||||||
|
const index = itemIndexAtScreenY(select, event.y);
|
||||||
|
if (index !== null) {
|
||||||
|
select.focus();
|
||||||
|
select.setSelectedIndex(index);
|
||||||
|
select.selectCurrent();
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleMouseScroll = (event) => {
|
||||||
|
if (!select || !event.scroll)
|
||||||
|
return;
|
||||||
|
const steps = Math.max(1, event.scroll.delta);
|
||||||
|
if (event.scroll.direction === "down")
|
||||||
|
select.moveDown(steps);
|
||||||
|
else if (event.scroll.direction === "up")
|
||||||
|
select.moveUp(steps);
|
||||||
|
};
|
||||||
|
createEffect(() => {
|
||||||
|
const index = props.items.findIndex((item) => item.value === props.value);
|
||||||
|
if (select && index !== -1 && select.getSelectedIndex() !== index)
|
||||||
|
select.setSelectedIndex(index);
|
||||||
|
});
|
||||||
|
return (() => {
|
||||||
|
var _el$ = _$createElement("box"), _el$5 = _$createElement("select");
|
||||||
|
_$insertNode(_el$, _el$5);
|
||||||
|
_$setProp(_el$, "border", true);
|
||||||
|
_$setProp(_el$, "flexDirection", "column");
|
||||||
|
_$insert(_el$, _$createComponent(Show, {
|
||||||
|
get when() {
|
||||||
|
return props.title;
|
||||||
|
},
|
||||||
|
get children() {
|
||||||
|
return [(() => {
|
||||||
|
var _el$2 = _$createElement("text");
|
||||||
|
_$insert(_el$2, displayTitle);
|
||||||
|
_$effect((_$p) => _$setProp(_el$2, "attributes", TextAttributes.BOLD, _$p));
|
||||||
|
return _el$2;
|
||||||
|
})(), (() => {
|
||||||
|
var _el$3 = _$createElement("text");
|
||||||
|
_$insertNode(_el$3, _$createTextNode(` `));
|
||||||
|
return _el$3;
|
||||||
|
})()];
|
||||||
|
}
|
||||||
|
}), _el$5);
|
||||||
|
_$use((el) => {
|
||||||
|
select = el ?? undefined;
|
||||||
|
props.ref?.(el && {
|
||||||
|
moveUp: () => el.moveUp(),
|
||||||
|
moveDown: () => el.moveDown(),
|
||||||
|
selectCurrent: () => el.selectCurrent(),
|
||||||
|
focus: () => el.focus()
|
||||||
|
});
|
||||||
|
}, _el$5);
|
||||||
|
_$setProp(_el$5, "flexGrow", 1);
|
||||||
|
_$setProp(_el$5, "showScrollIndicator", true);
|
||||||
|
_$setProp(_el$5, "onChange", (_index, option) => {
|
||||||
|
if (!option)
|
||||||
|
return;
|
||||||
|
props.onSelect(option);
|
||||||
|
});
|
||||||
|
_$setProp(_el$5, "onSelect", (_index, option) => {
|
||||||
|
if (!option)
|
||||||
|
return;
|
||||||
|
props.onConfirm?.(option);
|
||||||
|
});
|
||||||
|
_$effect((_p$) => {
|
||||||
|
var _v$ = props.width ?? 24, _v$2 = theme2.borderStyle, _v$3 = props.focused ? props.accentColor ?? theme2.primary : props.mutedColor ?? theme2.muted, _v$4 = props.focused, _v$5 = displayItems(), _v$6 = theme2.mouse ? handleMouseDown : undefined, _v$7 = theme2.mouse ? handleMouseScroll : undefined, _v$8 = props.backgroundColor ?? "transparent", _v$9 = props.textColor ?? presets2.select.textColor, _v$0 = props.focusedBackgroundColor ?? "transparent", _v$1 = props.focusedTextColor ?? presets2.select.focusedTextColor, _v$10 = props.selectedBackgroundColor ?? presets2.select.selectedBackgroundColor, _v$11 = props.selectedTextColor ?? presets2.select.selectedTextColor, _v$12 = props.descriptionColor ?? presets2.select.descriptionColor, _v$13 = props.selectedDescriptionColor ?? presets2.select.selectedDescriptionColor;
|
||||||
|
_v$ !== _p$.e && (_p$.e = _$setProp(_el$, "width", _v$, _p$.e));
|
||||||
|
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$, "borderStyle", _v$2, _p$.t));
|
||||||
|
_v$3 !== _p$.a && (_p$.a = _$setProp(_el$, "borderColor", _v$3, _p$.a));
|
||||||
|
_v$4 !== _p$.o && (_p$.o = _$setProp(_el$5, "focused", _v$4, _p$.o));
|
||||||
|
_v$5 !== _p$.i && (_p$.i = _$setProp(_el$5, "options", _v$5, _p$.i));
|
||||||
|
_v$6 !== _p$.n && (_p$.n = _$setProp(_el$5, "onMouseDown", _v$6, _p$.n));
|
||||||
|
_v$7 !== _p$.s && (_p$.s = _$setProp(_el$5, "onMouseScroll", _v$7, _p$.s));
|
||||||
|
_v$8 !== _p$.h && (_p$.h = _$setProp(_el$5, "backgroundColor", _v$8, _p$.h));
|
||||||
|
_v$9 !== _p$.r && (_p$.r = _$setProp(_el$5, "textColor", _v$9, _p$.r));
|
||||||
|
_v$0 !== _p$.d && (_p$.d = _$setProp(_el$5, "focusedBackgroundColor", _v$0, _p$.d));
|
||||||
|
_v$1 !== _p$.l && (_p$.l = _$setProp(_el$5, "focusedTextColor", _v$1, _p$.l));
|
||||||
|
_v$10 !== _p$.u && (_p$.u = _$setProp(_el$5, "selectedBackgroundColor", _v$10, _p$.u));
|
||||||
|
_v$11 !== _p$.c && (_p$.c = _$setProp(_el$5, "selectedTextColor", _v$11, _p$.c));
|
||||||
|
_v$12 !== _p$.w && (_p$.w = _$setProp(_el$5, "descriptionColor", _v$12, _p$.w));
|
||||||
|
_v$13 !== _p$.m && (_p$.m = _$setProp(_el$5, "selectedDescriptionColor", _v$13, _p$.m));
|
||||||
|
return _p$;
|
||||||
|
}, {
|
||||||
|
e: undefined,
|
||||||
|
t: undefined,
|
||||||
|
a: undefined,
|
||||||
|
o: undefined,
|
||||||
|
i: undefined,
|
||||||
|
n: undefined,
|
||||||
|
s: undefined,
|
||||||
|
h: undefined,
|
||||||
|
r: undefined,
|
||||||
|
d: undefined,
|
||||||
|
l: undefined,
|
||||||
|
u: undefined,
|
||||||
|
c: undefined,
|
||||||
|
w: undefined,
|
||||||
|
m: undefined
|
||||||
|
});
|
||||||
|
return _el$;
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Sidebar2 };
|
||||||
Vendored
+71
@@ -0,0 +1,71 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
theme2,
|
||||||
|
presets2
|
||||||
|
} from "./chunk-p6jvscj3.js";
|
||||||
|
|
||||||
|
// src/components/ListRow.tsx
|
||||||
|
import { effect as _$effect } from "@opentui/solid";
|
||||||
|
import { createComponent as _$createComponent } from "@opentui/solid";
|
||||||
|
import { insertNode as _$insertNode } from "@opentui/solid";
|
||||||
|
import { insert as _$insert } from "@opentui/solid";
|
||||||
|
import { setProp as _$setProp } from "@opentui/solid";
|
||||||
|
import { createElement as _$createElement } from "@opentui/solid";
|
||||||
|
import { TextAttributes } from "@opentui/core";
|
||||||
|
import { Show } from "solid-js";
|
||||||
|
function ListRow2(props) {
|
||||||
|
const tone = (fallback) => props.selected ? presets2.select.selectedTextColor : fallback;
|
||||||
|
return (() => {
|
||||||
|
var _el$ = _$createElement("box"), _el$2 = _$createElement("box"), _el$3 = _$createElement("box"), _el$4 = _$createElement("text");
|
||||||
|
_$insertNode(_el$, _el$2);
|
||||||
|
_$setProp(_el$, "flexShrink", 0);
|
||||||
|
_$setProp(_el$, "flexDirection", "column");
|
||||||
|
_$setProp(_el$, "paddingLeft", 1);
|
||||||
|
_$setProp(_el$, "paddingRight", 1);
|
||||||
|
_$insertNode(_el$2, _el$3);
|
||||||
|
_$setProp(_el$2, "flexDirection", "row");
|
||||||
|
_$setProp(_el$2, "justifyContent", "space-between");
|
||||||
|
_$setProp(_el$2, "alignItems", "center");
|
||||||
|
_$setProp(_el$2, "height", 1);
|
||||||
|
_$insertNode(_el$3, _el$4);
|
||||||
|
_$setProp(_el$3, "flexGrow", 1);
|
||||||
|
_$setProp(_el$3, "flexShrink", 1);
|
||||||
|
_$setProp(_el$3, "minWidth", 0);
|
||||||
|
_$setProp(_el$3, "marginRight", 1);
|
||||||
|
_$setProp(_el$3, "overflow", "hidden");
|
||||||
|
_$setProp(_el$4, "wrapMode", "none");
|
||||||
|
_$insert(_el$4, () => props.label);
|
||||||
|
_$insert(_el$2, _$createComponent(Show, {
|
||||||
|
get when() {
|
||||||
|
return props.right;
|
||||||
|
},
|
||||||
|
get children() {
|
||||||
|
var _el$5 = _$createElement("box");
|
||||||
|
_$setProp(_el$5, "flexShrink", 0);
|
||||||
|
_$setProp(_el$5, "flexDirection", "row");
|
||||||
|
_$setProp(_el$5, "alignItems", "center");
|
||||||
|
_$insert(_el$5, () => props.right?.(tone));
|
||||||
|
return _el$5;
|
||||||
|
}
|
||||||
|
}), null);
|
||||||
|
_$insert(_el$, () => props.below?.(tone), null);
|
||||||
|
_$effect((_p$) => {
|
||||||
|
var _v$ = props.id, _v$2 = props.selected ? presets2.select.selectedBackgroundColor : "transparent", _v$3 = theme2.mouse && props.onClick ? () => props.onClick?.() : undefined, _v$4 = tone(), _v$5 = props.bold ? TextAttributes.BOLD : undefined;
|
||||||
|
_v$ !== _p$.e && (_p$.e = _$setProp(_el$, "id", _v$, _p$.e));
|
||||||
|
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$, "backgroundColor", _v$2, _p$.t));
|
||||||
|
_v$3 !== _p$.a && (_p$.a = _$setProp(_el$, "onMouseDown", _v$3, _p$.a));
|
||||||
|
_v$4 !== _p$.o && (_p$.o = _$setProp(_el$4, "fg", _v$4, _p$.o));
|
||||||
|
_v$5 !== _p$.i && (_p$.i = _$setProp(_el$4, "attributes", _v$5, _p$.i));
|
||||||
|
return _p$;
|
||||||
|
}, {
|
||||||
|
e: undefined,
|
||||||
|
t: undefined,
|
||||||
|
a: undefined,
|
||||||
|
o: undefined,
|
||||||
|
i: undefined
|
||||||
|
});
|
||||||
|
return _el$;
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
export { ListRow2 };
|
||||||
Vendored
+140
@@ -0,0 +1,140 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
theme2
|
||||||
|
} from "./chunk-p6jvscj3.js";
|
||||||
|
|
||||||
|
// src/components/Modal.tsx
|
||||||
|
import { insertNode as _$insertNode } from "@opentui/solid";
|
||||||
|
import { effect as _$effect } from "@opentui/solid";
|
||||||
|
import { insert as _$insert } from "@opentui/solid";
|
||||||
|
import { use as _$use } from "@opentui/solid";
|
||||||
|
import { setProp as _$setProp } from "@opentui/solid";
|
||||||
|
import { createElement as _$createElement } from "@opentui/solid";
|
||||||
|
import { createComponent as _$createComponent } from "@opentui/solid";
|
||||||
|
import { RGBA } from "@opentui/core";
|
||||||
|
import { useBindings } from "@opentui/keymap/solid";
|
||||||
|
import { Portal, useRenderer, useTerminalDimensions } from "@opentui/solid";
|
||||||
|
import { createSignal, onCleanup, onMount, Show } from "solid-js";
|
||||||
|
function Modal2(props) {
|
||||||
|
return _$createComponent(Portal, {
|
||||||
|
get children() {
|
||||||
|
return _$createComponent(Show, {
|
||||||
|
get when() {
|
||||||
|
return props.open;
|
||||||
|
},
|
||||||
|
get children() {
|
||||||
|
return _$createComponent(ModalPanel, props);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function ModalPanel(props) {
|
||||||
|
const renderer = useRenderer();
|
||||||
|
const dimensions = useTerminalDimensions();
|
||||||
|
const [panel, setPanel] = createSignal();
|
||||||
|
const screenCap = () => Math.max(5, dimensions().height - 4);
|
||||||
|
const maxPanelHeight = () => Math.min(props.maxHeight ?? screenCap(), screenCap());
|
||||||
|
const maxContentHeight = () => Math.max(1, maxPanelHeight() - 4);
|
||||||
|
const maxPanelWidth = () => Math.max(10, dimensions().width - 4);
|
||||||
|
const panelWidth = () => Math.min(props.width ?? 50, maxPanelWidth());
|
||||||
|
let previousFocus = null;
|
||||||
|
onMount(() => {
|
||||||
|
previousFocus = renderer.currentFocusedRenderable;
|
||||||
|
props.onOpen?.(previousFocus);
|
||||||
|
panel()?.focus();
|
||||||
|
});
|
||||||
|
onCleanup(() => {
|
||||||
|
if (previousFocus && !previousFocus.isDestroyed)
|
||||||
|
previousFocus.focus();
|
||||||
|
});
|
||||||
|
const dismissKey = props.dismissKey ?? "escape";
|
||||||
|
if (dismissKey !== false || props.commands || props.bindings) {
|
||||||
|
useBindings(() => ({
|
||||||
|
target: panel,
|
||||||
|
commands: [...dismissKey !== false ? [{
|
||||||
|
name: "modal_dismiss",
|
||||||
|
desc: "close",
|
||||||
|
shortHelp: true,
|
||||||
|
run: () => props.onDismiss?.()
|
||||||
|
}] : [], ...props.commands ?? []],
|
||||||
|
bindings: [...dismissKey !== false ? [{
|
||||||
|
key: dismissKey,
|
||||||
|
cmd: "modal_dismiss"
|
||||||
|
}] : [], ...props.bindings ?? []]
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return (() => {
|
||||||
|
var _el$ = _$createElement("box"), _el$2 = _$createElement("box");
|
||||||
|
_$insertNode(_el$, _el$2);
|
||||||
|
_$setProp(_el$, "position", "absolute");
|
||||||
|
_$setProp(_el$, "top", 0);
|
||||||
|
_$setProp(_el$, "left", 0);
|
||||||
|
_$setProp(_el$, "alignItems", "center");
|
||||||
|
_$setProp(_el$, "justifyContent", "center");
|
||||||
|
_$setProp(_el$, "zIndex", 2000);
|
||||||
|
_$use(setPanel, _el$2);
|
||||||
|
_$setProp(_el$2, "focusable", true);
|
||||||
|
_$setProp(_el$2, "border", true);
|
||||||
|
_$setProp(_el$2, "flexDirection", "column");
|
||||||
|
_$setProp(_el$2, "paddingLeft", 2);
|
||||||
|
_$setProp(_el$2, "paddingRight", 2);
|
||||||
|
_$setProp(_el$2, "paddingTop", 1);
|
||||||
|
_$setProp(_el$2, "paddingBottom", 1);
|
||||||
|
_$insert(_el$2, _$createComponent(Show, {
|
||||||
|
get when() {
|
||||||
|
return props.scrollable;
|
||||||
|
},
|
||||||
|
get fallback() {
|
||||||
|
return (() => {
|
||||||
|
var _el$4 = _$createElement("box");
|
||||||
|
_$setProp(_el$4, "flexDirection", "column");
|
||||||
|
_$setProp(_el$4, "overflow", "hidden");
|
||||||
|
_$insert(_el$4, () => props.children);
|
||||||
|
_$effect((_$p) => _$setProp(_el$4, "maxHeight", maxContentHeight(), _$p));
|
||||||
|
return _el$4;
|
||||||
|
})();
|
||||||
|
},
|
||||||
|
get children() {
|
||||||
|
var _el$3 = _$createElement("scrollbox");
|
||||||
|
_$setProp(_el$3, "flexShrink", 1);
|
||||||
|
_$setProp(_el$3, "scrollY", true);
|
||||||
|
_$setProp(_el$3, "scrollX", false);
|
||||||
|
_$setProp(_el$3, "focusable", false);
|
||||||
|
_$insert(_el$3, () => props.children);
|
||||||
|
_$effect((_$p) => _$setProp(_el$3, "maxHeight", maxContentHeight(), _$p));
|
||||||
|
return _el$3;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
_$effect((_p$) => {
|
||||||
|
var _v$ = dimensions().width, _v$2 = dimensions().height, _v$3 = props.backdropColor ?? RGBA.fromInts(0, 0, 0, 150), _v$4 = theme2.mouse ? props.onDismiss : undefined, _v$5 = panelWidth(), _v$6 = maxPanelHeight(), _v$7 = theme2.borderStyle, _v$8 = props.accentColor ?? theme2.primary, _v$9 = props.accentColor ?? theme2.primary, _v$0 = props.backgroundColor ?? theme2.background, _v$1 = theme2.mouse ? (event) => event.stopPropagation() : undefined;
|
||||||
|
_v$ !== _p$.e && (_p$.e = _$setProp(_el$, "width", _v$, _p$.e));
|
||||||
|
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$, "height", _v$2, _p$.t));
|
||||||
|
_v$3 !== _p$.a && (_p$.a = _$setProp(_el$, "backgroundColor", _v$3, _p$.a));
|
||||||
|
_v$4 !== _p$.o && (_p$.o = _$setProp(_el$, "onMouseDown", _v$4, _p$.o));
|
||||||
|
_v$5 !== _p$.i && (_p$.i = _$setProp(_el$2, "width", _v$5, _p$.i));
|
||||||
|
_v$6 !== _p$.n && (_p$.n = _$setProp(_el$2, "maxHeight", _v$6, _p$.n));
|
||||||
|
_v$7 !== _p$.s && (_p$.s = _$setProp(_el$2, "borderStyle", _v$7, _p$.s));
|
||||||
|
_v$8 !== _p$.h && (_p$.h = _$setProp(_el$2, "borderColor", _v$8, _p$.h));
|
||||||
|
_v$9 !== _p$.r && (_p$.r = _$setProp(_el$2, "focusedBorderColor", _v$9, _p$.r));
|
||||||
|
_v$0 !== _p$.d && (_p$.d = _$setProp(_el$2, "backgroundColor", _v$0, _p$.d));
|
||||||
|
_v$1 !== _p$.l && (_p$.l = _$setProp(_el$2, "onMouseDown", _v$1, _p$.l));
|
||||||
|
return _p$;
|
||||||
|
}, {
|
||||||
|
e: undefined,
|
||||||
|
t: undefined,
|
||||||
|
a: undefined,
|
||||||
|
o: undefined,
|
||||||
|
i: undefined,
|
||||||
|
n: undefined,
|
||||||
|
s: undefined,
|
||||||
|
h: undefined,
|
||||||
|
r: undefined,
|
||||||
|
d: undefined,
|
||||||
|
l: undefined
|
||||||
|
});
|
||||||
|
return _el$;
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Modal2 };
|
||||||
Vendored
+32
@@ -0,0 +1,32 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
theme2
|
||||||
|
} from "./chunk-p6jvscj3.js";
|
||||||
|
|
||||||
|
// src/components/SignalBars.tsx
|
||||||
|
import { effect as _$effect } from "@opentui/solid";
|
||||||
|
import { insert as _$insert } from "@opentui/solid";
|
||||||
|
import { createComponent as _$createComponent } from "@opentui/solid";
|
||||||
|
import { setProp as _$setProp } from "@opentui/solid";
|
||||||
|
import { createElement as _$createElement } from "@opentui/solid";
|
||||||
|
import { For } from "solid-js";
|
||||||
|
var BARS = ["\u2581", "\u2583", "\u2585", "\u2587"];
|
||||||
|
function SignalBars2(props) {
|
||||||
|
const level = () => Math.floor(props.signal * BARS.length / 100);
|
||||||
|
return (() => {
|
||||||
|
var _el$ = _$createElement("box");
|
||||||
|
_$setProp(_el$, "flexDirection", "row");
|
||||||
|
_$insert(_el$, _$createComponent(For, {
|
||||||
|
each: BARS,
|
||||||
|
children: (bar, i) => (() => {
|
||||||
|
var _el$2 = _$createElement("text");
|
||||||
|
_$insert(_el$2, bar);
|
||||||
|
_$effect((_$p) => _$setProp(_el$2, "fg", i() < level() ? props.filledColor : props.emptyColor ?? theme2.muted, _$p));
|
||||||
|
return _el$2;
|
||||||
|
})()
|
||||||
|
}));
|
||||||
|
return _el$;
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
export { SignalBars2 };
|
||||||
Vendored
+83
@@ -0,0 +1,83 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
theme2
|
||||||
|
} from "./chunk-p6jvscj3.js";
|
||||||
|
|
||||||
|
// src/components/Badge.tsx
|
||||||
|
import { insert as _$insert } from "@opentui/solid";
|
||||||
|
import { memo as _$memo } from "@opentui/solid";
|
||||||
|
import { createComponent as _$createComponent } from "@opentui/solid";
|
||||||
|
import { effect as _$effect } from "@opentui/solid";
|
||||||
|
import { createTextNode as _$createTextNode } from "@opentui/solid";
|
||||||
|
import { insertNode as _$insertNode } from "@opentui/solid";
|
||||||
|
import { setProp as _$setProp } from "@opentui/solid";
|
||||||
|
import { createElement as _$createElement } from "@opentui/solid";
|
||||||
|
import { TextAttributes } from "@opentui/core";
|
||||||
|
import { Show } from "solid-js";
|
||||||
|
function Badge2(props) {
|
||||||
|
const textColor = () => props.textColor ?? theme2.background;
|
||||||
|
const backgroundColor = () => props.backgroundColor ?? theme2.background;
|
||||||
|
const attributes = () => props.bold === false ? undefined : TextAttributes.BOLD;
|
||||||
|
const nerdFonts = () => props.withNerdfont ?? theme2.nerdFonts;
|
||||||
|
return (() => {
|
||||||
|
var _el$ = _$createElement("box"), _el$4 = _$createElement("text");
|
||||||
|
_$insertNode(_el$, _el$4);
|
||||||
|
_$setProp(_el$, "flexDirection", "row");
|
||||||
|
_$insert(_el$, _$createComponent(Show, {
|
||||||
|
get when() {
|
||||||
|
return nerdFonts();
|
||||||
|
},
|
||||||
|
get children() {
|
||||||
|
var _el$2 = _$createElement("text");
|
||||||
|
_$insertNode(_el$2, _$createTextNode(`\uE0B6`));
|
||||||
|
_$effect((_p$) => {
|
||||||
|
var _v$ = props.color, _v$2 = backgroundColor();
|
||||||
|
_v$ !== _p$.e && (_p$.e = _$setProp(_el$2, "fg", _v$, _p$.e));
|
||||||
|
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$2, "bg", _v$2, _p$.t));
|
||||||
|
return _p$;
|
||||||
|
}, {
|
||||||
|
e: undefined,
|
||||||
|
t: undefined
|
||||||
|
});
|
||||||
|
return _el$2;
|
||||||
|
}
|
||||||
|
}), _el$4);
|
||||||
|
_$insert(_el$4, (() => {
|
||||||
|
var _c$ = _$memo(() => !!nerdFonts());
|
||||||
|
return () => _c$() ? props.label : ` ${props.label} `;
|
||||||
|
})());
|
||||||
|
_$insert(_el$, _$createComponent(Show, {
|
||||||
|
get when() {
|
||||||
|
return nerdFonts();
|
||||||
|
},
|
||||||
|
get children() {
|
||||||
|
var _el$5 = _$createElement("text");
|
||||||
|
_$insertNode(_el$5, _$createTextNode(`\uE0B4`));
|
||||||
|
_$effect((_p$) => {
|
||||||
|
var _v$3 = props.color, _v$4 = backgroundColor();
|
||||||
|
_v$3 !== _p$.e && (_p$.e = _$setProp(_el$5, "fg", _v$3, _p$.e));
|
||||||
|
_v$4 !== _p$.t && (_p$.t = _$setProp(_el$5, "bg", _v$4, _p$.t));
|
||||||
|
return _p$;
|
||||||
|
}, {
|
||||||
|
e: undefined,
|
||||||
|
t: undefined
|
||||||
|
});
|
||||||
|
return _el$5;
|
||||||
|
}
|
||||||
|
}), null);
|
||||||
|
_$effect((_p$) => {
|
||||||
|
var _v$5 = textColor(), _v$6 = props.color, _v$7 = attributes();
|
||||||
|
_v$5 !== _p$.e && (_p$.e = _$setProp(_el$4, "fg", _v$5, _p$.e));
|
||||||
|
_v$6 !== _p$.t && (_p$.t = _$setProp(_el$4, "bg", _v$6, _p$.t));
|
||||||
|
_v$7 !== _p$.a && (_p$.a = _$setProp(_el$4, "attributes", _v$7, _p$.a));
|
||||||
|
return _p$;
|
||||||
|
}, {
|
||||||
|
e: undefined,
|
||||||
|
t: undefined,
|
||||||
|
a: undefined
|
||||||
|
});
|
||||||
|
return _el$;
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Badge2 };
|
||||||
Vendored
+42
@@ -0,0 +1,42 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
useFocusWithin2
|
||||||
|
} from "./chunk-59dzyjgd.js";
|
||||||
|
|
||||||
|
// src/components/Pane.tsx
|
||||||
|
import { createComponent as _$createComponent } from "@opentui/solid";
|
||||||
|
import { insert as _$insert } from "@opentui/solid";
|
||||||
|
import { use as _$use } from "@opentui/solid";
|
||||||
|
import { spread as _$spread } from "@opentui/solid";
|
||||||
|
import { createElement as _$createElement } from "@opentui/solid";
|
||||||
|
import { createContext, createSignal, splitProps, useContext } from "solid-js";
|
||||||
|
var PaneContext = createContext();
|
||||||
|
function Pane2(props) {
|
||||||
|
const [local, rest] = splitProps(props, ["handle", "children"]);
|
||||||
|
const [el, setEl] = createSignal();
|
||||||
|
const focused = useFocusWithin2(el);
|
||||||
|
const handle = {
|
||||||
|
target: el,
|
||||||
|
focused,
|
||||||
|
focus: () => el()?.focus()
|
||||||
|
};
|
||||||
|
local.handle?.(handle);
|
||||||
|
return _$createComponent(PaneContext.Provider, {
|
||||||
|
value: handle,
|
||||||
|
get children() {
|
||||||
|
var _el$ = _$createElement("box");
|
||||||
|
_$use(setEl, _el$);
|
||||||
|
_$spread(_el$, rest, true);
|
||||||
|
_$insert(_el$, () => local.children);
|
||||||
|
return _el$;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function usePane2() {
|
||||||
|
const pane = useContext(PaneContext);
|
||||||
|
if (!pane)
|
||||||
|
throw new Error("usePane() must be used inside a <Pane>");
|
||||||
|
return pane;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Pane2, usePane2 };
|
||||||
Vendored
+168
@@ -0,0 +1,168 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
theme2
|
||||||
|
} from "./chunk-p6jvscj3.js";
|
||||||
|
|
||||||
|
// src/components/Tabs.tsx
|
||||||
|
import { effect as _$effect } from "@opentui/solid";
|
||||||
|
import { insertNode as _$insertNode } from "@opentui/solid";
|
||||||
|
import { createComponent as _$createComponent } from "@opentui/solid";
|
||||||
|
import { insert as _$insert } from "@opentui/solid";
|
||||||
|
import { use as _$use } from "@opentui/solid";
|
||||||
|
import { setProp as _$setProp } from "@opentui/solid";
|
||||||
|
import { createElement as _$createElement } from "@opentui/solid";
|
||||||
|
import { BorderChars, CliRenderEvents, TextAttributes } from "@opentui/core";
|
||||||
|
import { onResize, useRenderer } from "@opentui/solid";
|
||||||
|
import { createMemo, createSignal, Index } from "solid-js";
|
||||||
|
function stepTabValue2(items, value, delta) {
|
||||||
|
const index = items.findIndex((t) => t.value === value);
|
||||||
|
if (index === -1)
|
||||||
|
return value;
|
||||||
|
return items[(index + delta + items.length) % items.length].value;
|
||||||
|
}
|
||||||
|
function innerWidth(label) {
|
||||||
|
return [...label].length + 2;
|
||||||
|
}
|
||||||
|
function segmentWidth(label) {
|
||||||
|
return innerWidth(label) + 2;
|
||||||
|
}
|
||||||
|
function topEdge(chars, label) {
|
||||||
|
return `${chars.topLeft}${chars.horizontal.repeat(innerWidth(label))}${chars.topRight}`;
|
||||||
|
}
|
||||||
|
function bottomEdge(chars, label, isFirst, isLast, isActive) {
|
||||||
|
const left = isFirst ? isActive ? chars.vertical : chars.leftT : isActive ? chars.bottomRight : chars.bottomT;
|
||||||
|
const right = isLast ? isActive ? chars.vertical : chars.rightT : isActive ? chars.bottomLeft : chars.bottomT;
|
||||||
|
const fill = isActive ? " " : chars.horizontal;
|
||||||
|
return left + fill.repeat(innerWidth(label)) + right;
|
||||||
|
}
|
||||||
|
function buildBottomRow(chars, segments, available) {
|
||||||
|
const tabsWidth = segments.reduce((sum, s) => sum + segmentWidth(s.label), 0);
|
||||||
|
const remainder = available - tabsWidth;
|
||||||
|
const extending = remainder > 0;
|
||||||
|
const tabs = segments.map((s, i) => bottomEdge(chars, s.label, i === 0, i === segments.length - 1 && !extending, s.isActive)).join("");
|
||||||
|
return extending ? tabs + chars.horizontal.repeat(remainder - 1) + chars.topRight : tabs;
|
||||||
|
}
|
||||||
|
function fitCount(items, start, available) {
|
||||||
|
const hiddenBefore = start;
|
||||||
|
let used = 0;
|
||||||
|
let count = 0;
|
||||||
|
for (let i = start;i < items.length; i++) {
|
||||||
|
const width = segmentWidth(items[i].label);
|
||||||
|
const hiddenAfter = items.length - start - count - 1;
|
||||||
|
const totalHidden = hiddenBefore + hiddenAfter;
|
||||||
|
const reserve = totalHidden > 0 ? segmentWidth(`+${totalHidden}`) : 0;
|
||||||
|
if (used + width + reserve > available)
|
||||||
|
break;
|
||||||
|
used += width;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
return Math.max(count, 1);
|
||||||
|
}
|
||||||
|
function Tabs2(props) {
|
||||||
|
const renderer = useRenderer();
|
||||||
|
const [width, setWidth] = createSignal(0);
|
||||||
|
let container;
|
||||||
|
const measure = () => {
|
||||||
|
if (!container)
|
||||||
|
return;
|
||||||
|
setWidth(container.width);
|
||||||
|
};
|
||||||
|
const remeasureNextFrame = () => renderer.once(CliRenderEvents.FRAME, measure);
|
||||||
|
onResize(remeasureNextFrame);
|
||||||
|
const segments = createMemo(() => {
|
||||||
|
const items = props.items;
|
||||||
|
const activeIndex = Math.max(0, items.findIndex((t) => t.value === props.value));
|
||||||
|
let start = 0;
|
||||||
|
let count = fitCount(items, start, width());
|
||||||
|
if (activeIndex >= start + count) {
|
||||||
|
start = activeIndex;
|
||||||
|
count = fitCount(items, start, width());
|
||||||
|
}
|
||||||
|
const shownItems = items.slice(start, start + count);
|
||||||
|
const shown = shownItems.map((item) => ({
|
||||||
|
value: item.value,
|
||||||
|
label: item.label,
|
||||||
|
isActive: item.value === props.value
|
||||||
|
}));
|
||||||
|
const hidden = items.length - count;
|
||||||
|
if (hidden > 0) {
|
||||||
|
const used = shownItems.reduce((sum, item) => sum + segmentWidth(item.label), 0);
|
||||||
|
const tag = `+${hidden}`;
|
||||||
|
if (shown.length === 0 || used + segmentWidth(tag) <= width()) {
|
||||||
|
shown.push({
|
||||||
|
value: undefined,
|
||||||
|
label: tag,
|
||||||
|
isActive: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return shown;
|
||||||
|
});
|
||||||
|
const accent = () => props.accentColor ?? theme2.primary;
|
||||||
|
const muted = () => props.mutedColor ?? theme2.muted;
|
||||||
|
const borderColor = () => props.focused ? accent() : muted();
|
||||||
|
const chars = BorderChars[theme2.borderStyle];
|
||||||
|
return (() => {
|
||||||
|
var _el$ = _$createElement("box"), _el$2 = _$createElement("text"), _el$3 = _$createElement("box"), _el$4 = _$createElement("text");
|
||||||
|
_$insertNode(_el$, _el$2);
|
||||||
|
_$insertNode(_el$, _el$3);
|
||||||
|
_$insertNode(_el$, _el$4);
|
||||||
|
_$use((el) => {
|
||||||
|
container = el;
|
||||||
|
remeasureNextFrame();
|
||||||
|
}, _el$);
|
||||||
|
_$setProp(_el$, "flexDirection", "column");
|
||||||
|
_$setProp(_el$, "height", 3);
|
||||||
|
_$setProp(_el$2, "selectable", false);
|
||||||
|
_$insert(_el$2, () => segments().map((s) => topEdge(chars, s.label)).join(""));
|
||||||
|
_$setProp(_el$3, "flexDirection", "row");
|
||||||
|
_$insert(_el$3, _$createComponent(Index, {
|
||||||
|
get each() {
|
||||||
|
return segments();
|
||||||
|
},
|
||||||
|
children: (s) => [(() => {
|
||||||
|
var _el$5 = _$createElement("text");
|
||||||
|
_$setProp(_el$5, "selectable", false);
|
||||||
|
_$insert(_el$5, () => chars.vertical);
|
||||||
|
_$effect((_$p) => _$setProp(_el$5, "fg", borderColor(), _$p));
|
||||||
|
return _el$5;
|
||||||
|
})(), (() => {
|
||||||
|
var _el$6 = _$createElement("text");
|
||||||
|
_$setProp(_el$6, "selectable", false);
|
||||||
|
_$insert(_el$6, () => ` ${s().label} `);
|
||||||
|
_$effect((_p$) => {
|
||||||
|
var _v$3 = s().isActive ? accent() : muted(), _v$4 = s().isActive ? TextAttributes.BOLD : undefined, _v$5 = theme2.mouse && s().value !== undefined ? () => props.onChange(s().value) : undefined;
|
||||||
|
_v$3 !== _p$.e && (_p$.e = _$setProp(_el$6, "fg", _v$3, _p$.e));
|
||||||
|
_v$4 !== _p$.t && (_p$.t = _$setProp(_el$6, "attributes", _v$4, _p$.t));
|
||||||
|
_v$5 !== _p$.a && (_p$.a = _$setProp(_el$6, "onMouseDown", _v$5, _p$.a));
|
||||||
|
return _p$;
|
||||||
|
}, {
|
||||||
|
e: undefined,
|
||||||
|
t: undefined,
|
||||||
|
a: undefined
|
||||||
|
});
|
||||||
|
return _el$6;
|
||||||
|
})(), (() => {
|
||||||
|
var _el$7 = _$createElement("text");
|
||||||
|
_$setProp(_el$7, "selectable", false);
|
||||||
|
_$insert(_el$7, () => chars.vertical);
|
||||||
|
_$effect((_$p) => _$setProp(_el$7, "fg", borderColor(), _$p));
|
||||||
|
return _el$7;
|
||||||
|
})()]
|
||||||
|
}));
|
||||||
|
_$setProp(_el$4, "selectable", false);
|
||||||
|
_$insert(_el$4, () => buildBottomRow(chars, segments(), width()));
|
||||||
|
_$effect((_p$) => {
|
||||||
|
var _v$ = borderColor(), _v$2 = borderColor();
|
||||||
|
_v$ !== _p$.e && (_p$.e = _$setProp(_el$2, "fg", _v$, _p$.e));
|
||||||
|
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$4, "fg", _v$2, _p$.t));
|
||||||
|
return _p$;
|
||||||
|
}, {
|
||||||
|
e: undefined,
|
||||||
|
t: undefined
|
||||||
|
});
|
||||||
|
return _el$;
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
export { stepTabValue2, Tabs2 };
|
||||||
Vendored
+52
@@ -0,0 +1,52 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
theme2
|
||||||
|
} from "./chunk-p6jvscj3.js";
|
||||||
|
import {
|
||||||
|
Box2
|
||||||
|
} from "./chunk-8r4rajaz.js";
|
||||||
|
import {
|
||||||
|
Tabs2
|
||||||
|
} from "./chunk-vf2ybhtx.js";
|
||||||
|
|
||||||
|
// src/components/TabbedPanel.tsx
|
||||||
|
import { insert as _$insert } from "@opentui/solid";
|
||||||
|
import { memo as _$memo } from "@opentui/solid";
|
||||||
|
import { createComponent as _$createComponent } from "@opentui/solid";
|
||||||
|
import { setProp as _$setProp } from "@opentui/solid";
|
||||||
|
import { createElement as _$createElement } from "@opentui/solid";
|
||||||
|
function TabbedPanel2(props) {
|
||||||
|
return (() => {
|
||||||
|
var _el$ = _$createElement("box");
|
||||||
|
_$setProp(_el$, "flexDirection", "column");
|
||||||
|
_$setProp(_el$, "flexGrow", 1);
|
||||||
|
_$insert(_el$, _$createComponent(Tabs2, {
|
||||||
|
get items() {
|
||||||
|
return props.items;
|
||||||
|
},
|
||||||
|
get value() {
|
||||||
|
return props.value;
|
||||||
|
},
|
||||||
|
get onChange() {
|
||||||
|
return props.onChange;
|
||||||
|
},
|
||||||
|
get focused() {
|
||||||
|
return props.focused;
|
||||||
|
}
|
||||||
|
}), null);
|
||||||
|
_$insert(_el$, _$createComponent(Box2, {
|
||||||
|
flexGrow: 1,
|
||||||
|
border: ["left", "right", "bottom"],
|
||||||
|
get borderColor() {
|
||||||
|
return _$memo(() => !!props.focused)() ? theme2.primary : theme2.muted;
|
||||||
|
},
|
||||||
|
flexDirection: "column",
|
||||||
|
get children() {
|
||||||
|
return props.children;
|
||||||
|
}
|
||||||
|
}), null);
|
||||||
|
return _el$;
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
export { TabbedPanel2 };
|
||||||
Vendored
+10
@@ -0,0 +1,10 @@
|
|||||||
|
// @bun
|
||||||
|
// src/yaml.ts
|
||||||
|
import { homedir } from "os";
|
||||||
|
import { join } from "path";
|
||||||
|
function configPath2() {
|
||||||
|
const configDir = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
||||||
|
return join(configDir, "ilovetui", "config.yaml");
|
||||||
|
}
|
||||||
|
|
||||||
|
export { configPath2 };
|
||||||
Vendored
+44
@@ -0,0 +1,44 @@
|
|||||||
|
// @bun
|
||||||
|
// src/presets.ts
|
||||||
|
function buildPresets2(theme) {
|
||||||
|
const select = {
|
||||||
|
backgroundColor: theme.background,
|
||||||
|
textColor: theme.text,
|
||||||
|
focusedBackgroundColor: theme.subtleBg,
|
||||||
|
focusedTextColor: theme.text,
|
||||||
|
selectedBackgroundColor: theme.selection,
|
||||||
|
selectedTextColor: theme.primary,
|
||||||
|
descriptionColor: theme.muted,
|
||||||
|
selectedDescriptionColor: theme.text
|
||||||
|
};
|
||||||
|
const tabSelect = {
|
||||||
|
backgroundColor: theme.background,
|
||||||
|
textColor: theme.text,
|
||||||
|
focusedBackgroundColor: theme.subtleBg,
|
||||||
|
focusedTextColor: theme.text,
|
||||||
|
selectedBackgroundColor: theme.selection,
|
||||||
|
selectedTextColor: theme.primary,
|
||||||
|
selectedDescriptionColor: theme.text
|
||||||
|
};
|
||||||
|
const input = {
|
||||||
|
backgroundColor: theme.background,
|
||||||
|
textColor: theme.text,
|
||||||
|
focusedBackgroundColor: theme.subtleBg,
|
||||||
|
focusedTextColor: theme.text,
|
||||||
|
placeholderColor: theme.muted
|
||||||
|
};
|
||||||
|
const box = {
|
||||||
|
border: true,
|
||||||
|
borderStyle: theme.borderStyle,
|
||||||
|
borderColor: theme.subtle,
|
||||||
|
focusedBorderColor: theme.primary,
|
||||||
|
backgroundColor: theme.background
|
||||||
|
};
|
||||||
|
const slider = {
|
||||||
|
backgroundColor: theme.subtleBg,
|
||||||
|
foregroundColor: theme.primary
|
||||||
|
};
|
||||||
|
return { select, tabSelect, input, textarea: input, box, slider };
|
||||||
|
}
|
||||||
|
|
||||||
|
export { buildPresets2 };
|
||||||
Vendored
+58
@@ -0,0 +1,58 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
theme2
|
||||||
|
} from "./chunk-p6jvscj3.js";
|
||||||
|
|
||||||
|
// src/components/ProgressBar.tsx
|
||||||
|
import { effect as _$effect } from "@opentui/solid";
|
||||||
|
import { memo as _$memo } from "@opentui/solid";
|
||||||
|
import { insert as _$insert } from "@opentui/solid";
|
||||||
|
import { createComponent as _$createComponent } from "@opentui/solid";
|
||||||
|
import { setProp as _$setProp } from "@opentui/solid";
|
||||||
|
import { createElement as _$createElement } from "@opentui/solid";
|
||||||
|
import { parseColor, RGBA } from "@opentui/core";
|
||||||
|
import { createMemo, For } from "solid-js";
|
||||||
|
function sampleGradient(stops, t) {
|
||||||
|
if (stops.length === 1)
|
||||||
|
return stops[0];
|
||||||
|
const clamped = Math.min(Math.max(t, 0), 1);
|
||||||
|
const segment = clamped * (stops.length - 1);
|
||||||
|
const i = Math.min(Math.floor(segment), stops.length - 2);
|
||||||
|
const localT = segment - i;
|
||||||
|
const [ar, ag, ab, aa] = parseColor(stops[i]).toInts();
|
||||||
|
const [br, bg, bb, ba] = parseColor(stops[i + 1]).toInts();
|
||||||
|
return RGBA.fromInts(Math.round(ar + (br - ar) * localT), Math.round(ag + (bg - ag) * localT), Math.round(ab + (bb - ab) * localT), Math.round(aa + (ba - aa) * localT));
|
||||||
|
}
|
||||||
|
function ProgressBar2(props) {
|
||||||
|
const width = () => props.width ?? 20;
|
||||||
|
const stops = createMemo(() => Array.isArray(props.color) ? props.color : [props.color ?? theme2.primary]);
|
||||||
|
const filled = createMemo(() => Math.round(width() * Math.min(Math.max(props.value, 0), 100) / 100));
|
||||||
|
const cells = createMemo(() => Array.from({
|
||||||
|
length: width()
|
||||||
|
}, (_, i) => i));
|
||||||
|
return (() => {
|
||||||
|
var _el$ = _$createElement("box");
|
||||||
|
_$setProp(_el$, "flexDirection", "row");
|
||||||
|
_$insert(_el$, _$createComponent(For, {
|
||||||
|
get each() {
|
||||||
|
return cells();
|
||||||
|
},
|
||||||
|
children: (i) => {
|
||||||
|
const isFilled = () => i < filled();
|
||||||
|
const t = width() > 1 ? i / (width() - 1) : 0;
|
||||||
|
return (() => {
|
||||||
|
var _el$2 = _$createElement("text");
|
||||||
|
_$insert(_el$2, (() => {
|
||||||
|
var _c$ = _$memo(() => !!isFilled());
|
||||||
|
return () => _c$() ? props.fillChar ?? "\u2588" : props.trackChar ?? "\u2591";
|
||||||
|
})());
|
||||||
|
_$effect((_$p) => _$setProp(_el$2, "fg", isFilled() ? sampleGradient(stops(), t) : props.trackColor ?? theme2.muted, _$p));
|
||||||
|
return _el$2;
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
return _el$;
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
export { ProgressBar2 };
|
||||||
Vendored
+12
@@ -0,0 +1,12 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
Badge2
|
||||||
|
} from "../chunk-v9cetqzr.js";
|
||||||
|
import"../chunk-p6jvscj3.js";
|
||||||
|
import"../chunk-yyg967q8.js";
|
||||||
|
import"../chunk-njvj322f.js";
|
||||||
|
import"../chunk-6ggk5fg7.js";
|
||||||
|
import"../chunk-xykm10y9.js";
|
||||||
|
export {
|
||||||
|
Badge2 as Badge
|
||||||
|
};
|
||||||
Vendored
+12
@@ -0,0 +1,12 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
Box2
|
||||||
|
} from "../chunk-8r4rajaz.js";
|
||||||
|
import"../chunk-p6jvscj3.js";
|
||||||
|
import"../chunk-yyg967q8.js";
|
||||||
|
import"../chunk-njvj322f.js";
|
||||||
|
import"../chunk-6ggk5fg7.js";
|
||||||
|
import"../chunk-xykm10y9.js";
|
||||||
|
export {
|
||||||
|
Box2 as Box
|
||||||
|
};
|
||||||
Vendored
+12
@@ -0,0 +1,12 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
HelpBar2
|
||||||
|
} from "../chunk-c1dx8cn3.js";
|
||||||
|
import"../chunk-p6jvscj3.js";
|
||||||
|
import"../chunk-yyg967q8.js";
|
||||||
|
import"../chunk-njvj322f.js";
|
||||||
|
import"../chunk-6ggk5fg7.js";
|
||||||
|
import"../chunk-xykm10y9.js";
|
||||||
|
export {
|
||||||
|
HelpBar2 as HelpBar
|
||||||
|
};
|
||||||
Vendored
+14
@@ -0,0 +1,14 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
HelpModal2
|
||||||
|
} from "../chunk-ersd5rrs.js";
|
||||||
|
import"../chunk-t2cszvff.js";
|
||||||
|
import"../chunk-p6jvscj3.js";
|
||||||
|
import"../chunk-yyg967q8.js";
|
||||||
|
import"../chunk-njvj322f.js";
|
||||||
|
import"../chunk-6ggk5fg7.js";
|
||||||
|
import"../chunk-xykm10y9.js";
|
||||||
|
import"../chunk-ee2qaswx.js";
|
||||||
|
export {
|
||||||
|
HelpModal2 as HelpModal
|
||||||
|
};
|
||||||
Vendored
+12
@@ -0,0 +1,12 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
KeyValue2
|
||||||
|
} from "../chunk-c4gp0x16.js";
|
||||||
|
import"../chunk-p6jvscj3.js";
|
||||||
|
import"../chunk-yyg967q8.js";
|
||||||
|
import"../chunk-njvj322f.js";
|
||||||
|
import"../chunk-6ggk5fg7.js";
|
||||||
|
import"../chunk-xykm10y9.js";
|
||||||
|
export {
|
||||||
|
KeyValue2 as KeyValue
|
||||||
|
};
|
||||||
Vendored
+12
@@ -0,0 +1,12 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
ListRow2
|
||||||
|
} from "../chunk-sh9n4zha.js";
|
||||||
|
import"../chunk-p6jvscj3.js";
|
||||||
|
import"../chunk-yyg967q8.js";
|
||||||
|
import"../chunk-njvj322f.js";
|
||||||
|
import"../chunk-6ggk5fg7.js";
|
||||||
|
import"../chunk-xykm10y9.js";
|
||||||
|
export {
|
||||||
|
ListRow2 as ListRow
|
||||||
|
};
|
||||||
Vendored
+12
@@ -0,0 +1,12 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
MinSizeGuard2
|
||||||
|
} from "../chunk-qdyd8q9r.js";
|
||||||
|
import"../chunk-p6jvscj3.js";
|
||||||
|
import"../chunk-yyg967q8.js";
|
||||||
|
import"../chunk-njvj322f.js";
|
||||||
|
import"../chunk-6ggk5fg7.js";
|
||||||
|
import"../chunk-xykm10y9.js";
|
||||||
|
export {
|
||||||
|
MinSizeGuard2 as MinSizeGuard
|
||||||
|
};
|
||||||
Vendored
+12
@@ -0,0 +1,12 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
Modal2
|
||||||
|
} from "../chunk-t2cszvff.js";
|
||||||
|
import"../chunk-p6jvscj3.js";
|
||||||
|
import"../chunk-yyg967q8.js";
|
||||||
|
import"../chunk-njvj322f.js";
|
||||||
|
import"../chunk-6ggk5fg7.js";
|
||||||
|
import"../chunk-xykm10y9.js";
|
||||||
|
export {
|
||||||
|
Modal2 as Modal
|
||||||
|
};
|
||||||
Vendored
+12
@@ -0,0 +1,12 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
NotificationHost2
|
||||||
|
} from "../chunk-ajb193xj.js";
|
||||||
|
import"../chunk-p6jvscj3.js";
|
||||||
|
import"../chunk-yyg967q8.js";
|
||||||
|
import"../chunk-njvj322f.js";
|
||||||
|
import"../chunk-6ggk5fg7.js";
|
||||||
|
import"../chunk-xykm10y9.js";
|
||||||
|
export {
|
||||||
|
NotificationHost2 as NotificationHost
|
||||||
|
};
|
||||||
Vendored
+10
@@ -0,0 +1,10 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
Pane2,
|
||||||
|
usePane2
|
||||||
|
} from "../chunk-vazj633p.js";
|
||||||
|
import"../chunk-59dzyjgd.js";
|
||||||
|
export {
|
||||||
|
Pane2 as Pane,
|
||||||
|
usePane2 as usePane
|
||||||
|
};
|
||||||
Vendored
+12
@@ -0,0 +1,12 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
ProgressBar2
|
||||||
|
} from "../chunk-z734n0wr.js";
|
||||||
|
import"../chunk-p6jvscj3.js";
|
||||||
|
import"../chunk-yyg967q8.js";
|
||||||
|
import"../chunk-njvj322f.js";
|
||||||
|
import"../chunk-6ggk5fg7.js";
|
||||||
|
import"../chunk-xykm10y9.js";
|
||||||
|
export {
|
||||||
|
ProgressBar2 as ProgressBar
|
||||||
|
};
|
||||||
Vendored
+81
@@ -0,0 +1,81 @@
|
|||||||
|
// @bun
|
||||||
|
import"../chunk-njvj322f.js";
|
||||||
|
import"../chunk-6ggk5fg7.js";
|
||||||
|
import"../chunk-yyg967q8.js";
|
||||||
|
import"../chunk-xykm10y9.js";
|
||||||
|
import {
|
||||||
|
theme2
|
||||||
|
} from "../chunk-p6jvscj3.js";
|
||||||
|
import {
|
||||||
|
Spinner2
|
||||||
|
} from "../chunk-kmqqzz9e.js";
|
||||||
|
|
||||||
|
// src/components/SectionHeader.tsx
|
||||||
|
import { createTextNode as _$createTextNode } from "@opentui/solid";
|
||||||
|
import { createComponent as _$createComponent } from "@opentui/solid";
|
||||||
|
import { effect as _$effect } from "@opentui/solid";
|
||||||
|
import { insertNode as _$insertNode } from "@opentui/solid";
|
||||||
|
import { insert as _$insert } from "@opentui/solid";
|
||||||
|
import { memo as _$memo } from "@opentui/solid";
|
||||||
|
import { setProp as _$setProp } from "@opentui/solid";
|
||||||
|
import { createElement as _$createElement } from "@opentui/solid";
|
||||||
|
import { TextAttributes } from "@opentui/core";
|
||||||
|
import { Show } from "solid-js";
|
||||||
|
function SectionHeader(props) {
|
||||||
|
return (() => {
|
||||||
|
var _el$ = _$createElement("box"), _el$2 = _$createElement("box"), _el$3 = _$createElement("text"), _el$4 = _$createElement("box");
|
||||||
|
_$insertNode(_el$, _el$2);
|
||||||
|
_$insertNode(_el$, _el$4);
|
||||||
|
_$setProp(_el$, "flexShrink", 0);
|
||||||
|
_$setProp(_el$, "flexDirection", "row");
|
||||||
|
_$setProp(_el$, "justifyContent", "space-between");
|
||||||
|
_$setProp(_el$, "alignItems", "center");
|
||||||
|
_$setProp(_el$, "height", 1);
|
||||||
|
_$insertNode(_el$2, _el$3);
|
||||||
|
_$setProp(_el$2, "flexGrow", 1);
|
||||||
|
_$setProp(_el$2, "flexShrink", 1);
|
||||||
|
_$setProp(_el$2, "minWidth", 0);
|
||||||
|
_$setProp(_el$2, "marginRight", 1);
|
||||||
|
_$setProp(_el$2, "overflow", "hidden");
|
||||||
|
_$setProp(_el$3, "wrapMode", "none");
|
||||||
|
_$insert(_el$3, () => props.title ?? "");
|
||||||
|
_$setProp(_el$4, "flexShrink", 0);
|
||||||
|
_$insert(_el$4, _$createComponent(Show, {
|
||||||
|
get when() {
|
||||||
|
return !props.loading;
|
||||||
|
},
|
||||||
|
get fallback() {
|
||||||
|
return (() => {
|
||||||
|
var _el$6 = _$createElement("box"), _el$7 = _$createElement("text"), _el$8 = _$createTextNode(` `);
|
||||||
|
_$insertNode(_el$6, _el$7);
|
||||||
|
_$setProp(_el$6, "flexDirection", "row");
|
||||||
|
_$setProp(_el$6, "alignItems", "center");
|
||||||
|
_$insert(_el$6, _$createComponent(Spinner2, {}), _el$7);
|
||||||
|
_$insertNode(_el$7, _el$8);
|
||||||
|
_$insert(_el$7, () => props.loadingLabel ?? "Loading\u2026", null);
|
||||||
|
_$effect((_$p) => _$setProp(_el$7, "fg", theme2.muted, _$p));
|
||||||
|
return _el$6;
|
||||||
|
})();
|
||||||
|
},
|
||||||
|
get children() {
|
||||||
|
var _el$5 = _$createElement("text");
|
||||||
|
_$insert(_el$5, () => props.hint ?? "");
|
||||||
|
_$effect((_$p) => _$setProp(_el$5, "fg", theme2.muted, _$p));
|
||||||
|
return _el$5;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
_$effect((_p$) => {
|
||||||
|
var _v$ = theme2.muted, _v$2 = TextAttributes.BOLD;
|
||||||
|
_v$ !== _p$.e && (_p$.e = _$setProp(_el$3, "fg", _v$, _p$.e));
|
||||||
|
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$3, "attributes", _v$2, _p$.t));
|
||||||
|
return _p$;
|
||||||
|
}, {
|
||||||
|
e: undefined,
|
||||||
|
t: undefined
|
||||||
|
});
|
||||||
|
return _el$;
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
export {
|
||||||
|
SectionHeader
|
||||||
|
};
|
||||||
Vendored
+12
@@ -0,0 +1,12 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
Sidebar2
|
||||||
|
} from "../chunk-rcywynbw.js";
|
||||||
|
import"../chunk-p6jvscj3.js";
|
||||||
|
import"../chunk-yyg967q8.js";
|
||||||
|
import"../chunk-njvj322f.js";
|
||||||
|
import"../chunk-6ggk5fg7.js";
|
||||||
|
import"../chunk-xykm10y9.js";
|
||||||
|
export {
|
||||||
|
Sidebar2 as Sidebar
|
||||||
|
};
|
||||||
Vendored
+12
@@ -0,0 +1,12 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
SignalBars2
|
||||||
|
} from "../chunk-tz3vvffs.js";
|
||||||
|
import"../chunk-p6jvscj3.js";
|
||||||
|
import"../chunk-yyg967q8.js";
|
||||||
|
import"../chunk-njvj322f.js";
|
||||||
|
import"../chunk-6ggk5fg7.js";
|
||||||
|
import"../chunk-xykm10y9.js";
|
||||||
|
export {
|
||||||
|
SignalBars2 as SignalBars
|
||||||
|
};
|
||||||
Vendored
+12
@@ -0,0 +1,12 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
Spinner2
|
||||||
|
} from "../chunk-kmqqzz9e.js";
|
||||||
|
import"../chunk-p6jvscj3.js";
|
||||||
|
import"../chunk-yyg967q8.js";
|
||||||
|
import"../chunk-njvj322f.js";
|
||||||
|
import"../chunk-6ggk5fg7.js";
|
||||||
|
import"../chunk-xykm10y9.js";
|
||||||
|
export {
|
||||||
|
Spinner2 as Spinner
|
||||||
|
};
|
||||||
Vendored
+14
@@ -0,0 +1,14 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
TabbedPanel2
|
||||||
|
} from "../chunk-wtbg0e39.js";
|
||||||
|
import"../chunk-vf2ybhtx.js";
|
||||||
|
import"../chunk-8r4rajaz.js";
|
||||||
|
import"../chunk-p6jvscj3.js";
|
||||||
|
import"../chunk-yyg967q8.js";
|
||||||
|
import"../chunk-njvj322f.js";
|
||||||
|
import"../chunk-6ggk5fg7.js";
|
||||||
|
import"../chunk-xykm10y9.js";
|
||||||
|
export {
|
||||||
|
TabbedPanel2 as TabbedPanel
|
||||||
|
};
|
||||||
Vendored
+14
@@ -0,0 +1,14 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
stepTabValue2,
|
||||||
|
Tabs2
|
||||||
|
} from "../chunk-vf2ybhtx.js";
|
||||||
|
import"../chunk-p6jvscj3.js";
|
||||||
|
import"../chunk-yyg967q8.js";
|
||||||
|
import"../chunk-njvj322f.js";
|
||||||
|
import"../chunk-6ggk5fg7.js";
|
||||||
|
import"../chunk-xykm10y9.js";
|
||||||
|
export {
|
||||||
|
Tabs2 as Tabs,
|
||||||
|
stepTabValue2 as stepTabValue
|
||||||
|
};
|
||||||
Vendored
+13
@@ -0,0 +1,13 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
Toggle2
|
||||||
|
} from "../chunk-qhp6k176.js";
|
||||||
|
import"../chunk-v9cetqzr.js";
|
||||||
|
import"../chunk-p6jvscj3.js";
|
||||||
|
import"../chunk-yyg967q8.js";
|
||||||
|
import"../chunk-njvj322f.js";
|
||||||
|
import"../chunk-6ggk5fg7.js";
|
||||||
|
import"../chunk-xykm10y9.js";
|
||||||
|
export {
|
||||||
|
Toggle2 as Toggle
|
||||||
|
};
|
||||||
Vendored
+18
@@ -0,0 +1,18 @@
|
|||||||
|
// @bun
|
||||||
|
import"../chunk-njvj322f.js";
|
||||||
|
import"../chunk-6ggk5fg7.js";
|
||||||
|
import"../chunk-yyg967q8.js";
|
||||||
|
import"../chunk-xykm10y9.js";
|
||||||
|
import"../chunk-p6jvscj3.js";
|
||||||
|
import {
|
||||||
|
HelpBar2
|
||||||
|
} from "../chunk-c1dx8cn3.js";
|
||||||
|
import"../chunk-ee2qaswx.js";
|
||||||
|
import"../chunk-t2cszvff.js";
|
||||||
|
import {
|
||||||
|
HelpModal2
|
||||||
|
} from "../chunk-ersd5rrs.js";
|
||||||
|
export {
|
||||||
|
HelpBar2 as HelpBar,
|
||||||
|
HelpModal2 as HelpModal
|
||||||
|
};
|
||||||
Vendored
+69
@@ -0,0 +1,69 @@
|
|||||||
|
// @bun
|
||||||
|
import"../chunk-njvj322f.js";
|
||||||
|
import"../chunk-6ggk5fg7.js";
|
||||||
|
import"../chunk-yyg967q8.js";
|
||||||
|
import"../chunk-xykm10y9.js";
|
||||||
|
import"../chunk-p6jvscj3.js";
|
||||||
|
import {
|
||||||
|
Badge2
|
||||||
|
} from "../chunk-v9cetqzr.js";
|
||||||
|
import {
|
||||||
|
Box2
|
||||||
|
} from "../chunk-8r4rajaz.js";
|
||||||
|
import {
|
||||||
|
KeyValue2
|
||||||
|
} from "../chunk-c4gp0x16.js";
|
||||||
|
import {
|
||||||
|
ListRow2
|
||||||
|
} from "../chunk-sh9n4zha.js";
|
||||||
|
import {
|
||||||
|
MinSizeGuard2
|
||||||
|
} from "../chunk-qdyd8q9r.js";
|
||||||
|
import {
|
||||||
|
Modal2
|
||||||
|
} from "../chunk-t2cszvff.js";
|
||||||
|
import {
|
||||||
|
NotificationHost2
|
||||||
|
} from "../chunk-ajb193xj.js";
|
||||||
|
import"../chunk-59dzyjgd.js";
|
||||||
|
import {
|
||||||
|
Pane2,
|
||||||
|
usePane2
|
||||||
|
} from "../chunk-vazj633p.js";
|
||||||
|
import {
|
||||||
|
ProgressBar2
|
||||||
|
} from "../chunk-z734n0wr.js";
|
||||||
|
import {
|
||||||
|
Sidebar2
|
||||||
|
} from "../chunk-rcywynbw.js";
|
||||||
|
import {
|
||||||
|
SignalBars2
|
||||||
|
} from "../chunk-tz3vvffs.js";
|
||||||
|
import {
|
||||||
|
stepTabValue2,
|
||||||
|
Tabs2
|
||||||
|
} from "../chunk-vf2ybhtx.js";
|
||||||
|
import {
|
||||||
|
TabbedPanel2
|
||||||
|
} from "../chunk-wtbg0e39.js";
|
||||||
|
import {
|
||||||
|
Toggle2
|
||||||
|
} from "../chunk-qhp6k176.js";
|
||||||
|
export {
|
||||||
|
Badge2 as Badge,
|
||||||
|
Box2 as Box,
|
||||||
|
KeyValue2 as KeyValue,
|
||||||
|
ListRow2 as ListRow,
|
||||||
|
MinSizeGuard2 as MinSizeGuard,
|
||||||
|
Modal2 as Modal,
|
||||||
|
NotificationHost2 as NotificationHost,
|
||||||
|
Pane2 as Pane,
|
||||||
|
ProgressBar2 as ProgressBar,
|
||||||
|
Sidebar2 as Sidebar,
|
||||||
|
SignalBars2 as SignalBars,
|
||||||
|
TabbedPanel2 as TabbedPanel,
|
||||||
|
Tabs2 as Tabs,
|
||||||
|
Toggle2 as Toggle,
|
||||||
|
stepTabValue2 as stepTabValue,
|
||||||
|
usePane2 as usePane
|
||||||
|
};
|
||||||
Vendored
+17
@@ -0,0 +1,17 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
COLOR_KEYS2,
|
||||||
|
OPENTUI_BORDER_STYLES2,
|
||||||
|
ThemeConfigSchema2,
|
||||||
|
layerConfigs2,
|
||||||
|
resolveConfig2,
|
||||||
|
normalizeColor2
|
||||||
|
} from "./chunk-njvj322f.js";
|
||||||
|
export {
|
||||||
|
COLOR_KEYS2 as COLOR_KEYS,
|
||||||
|
OPENTUI_BORDER_STYLES2 as OPENTUI_BORDER_STYLES,
|
||||||
|
ThemeConfigSchema2 as ThemeConfigSchema,
|
||||||
|
layerConfigs2 as layerConfigs,
|
||||||
|
normalizeColor2 as normalizeColor,
|
||||||
|
resolveConfig2 as resolveConfig
|
||||||
|
};
|
||||||
Vendored
+9
@@ -0,0 +1,9 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
isInside2,
|
||||||
|
useFocusWithin2
|
||||||
|
} from "../chunk-59dzyjgd.js";
|
||||||
|
export {
|
||||||
|
isInside2 as isInside,
|
||||||
|
useFocusWithin2 as useFocusWithin
|
||||||
|
};
|
||||||
Vendored
+9
@@ -0,0 +1,9 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
helpOpen2,
|
||||||
|
toggleHelp2
|
||||||
|
} from "../chunk-ee2qaswx.js";
|
||||||
|
export {
|
||||||
|
helpOpen2 as helpOpen,
|
||||||
|
toggleHelp2 as toggleHelp
|
||||||
|
};
|
||||||
Vendored
+7
@@ -0,0 +1,7 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
createSimpleContext2
|
||||||
|
} from "../chunk-jza406qg.js";
|
||||||
|
export {
|
||||||
|
createSimpleContext2 as createSimpleContext
|
||||||
|
};
|
||||||
Vendored
+27
@@ -0,0 +1,27 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
createSimpleContext2
|
||||||
|
} from "../chunk-jza406qg.js";
|
||||||
|
import {
|
||||||
|
notifications2,
|
||||||
|
notify2,
|
||||||
|
dismiss2
|
||||||
|
} from "../chunk-6ggk5fg7.js";
|
||||||
|
import {
|
||||||
|
helpOpen2,
|
||||||
|
toggleHelp2
|
||||||
|
} from "../chunk-ee2qaswx.js";
|
||||||
|
import {
|
||||||
|
isInside2,
|
||||||
|
useFocusWithin2
|
||||||
|
} from "../chunk-59dzyjgd.js";
|
||||||
|
export {
|
||||||
|
createSimpleContext2 as createSimpleContext,
|
||||||
|
dismiss2 as dismiss,
|
||||||
|
helpOpen2 as helpOpen,
|
||||||
|
isInside2 as isInside,
|
||||||
|
notifications2 as notifications,
|
||||||
|
notify2 as notify,
|
||||||
|
toggleHelp2 as toggleHelp,
|
||||||
|
useFocusWithin2 as useFocusWithin
|
||||||
|
};
|
||||||
Vendored
+11
@@ -0,0 +1,11 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
notifications2,
|
||||||
|
notify2,
|
||||||
|
dismiss2
|
||||||
|
} from "../chunk-6ggk5fg7.js";
|
||||||
|
export {
|
||||||
|
dismiss2 as dismiss,
|
||||||
|
notifications2 as notifications,
|
||||||
|
notify2 as notify
|
||||||
|
};
|
||||||
+10
-3
@@ -2,11 +2,18 @@
|
|||||||
# Copy to ~/.config/ilovetui/config.yaml and edit to customize.
|
# Copy to ~/.config/ilovetui/config.yaml and edit to customize.
|
||||||
|
|
||||||
# Whether components may use Nerd Font glyphs (requires a patched font).
|
# Whether components may use Nerd Font glyphs (requires a patched font).
|
||||||
# Leave false for maximum terminal/font compatibility.
|
nerd_fonts: true
|
||||||
nerd_fonts: false
|
|
||||||
|
# Whether the terminal font can render emoji glyphs.
|
||||||
|
emoji_fonts: true
|
||||||
|
|
||||||
|
# Disable spinner animation and other motion across all ilovetui-based TUIs.
|
||||||
|
reduced_motion: false
|
||||||
|
|
||||||
|
# Whether components may register mouse handlers (e.g. click-to-dismiss).
|
||||||
|
mouse: true
|
||||||
|
|
||||||
# Border style used by panels across all ilovetui-based TUIs.
|
# Border style used by panels across all ilovetui-based TUIs.
|
||||||
# One of: rounded, normal, thick, double, hidden, ascii.
|
|
||||||
border: rounded
|
border: rounded
|
||||||
|
|
||||||
colors:
|
colors:
|
||||||
Vendored
+23
@@ -0,0 +1,23 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
theme2,
|
||||||
|
presets2,
|
||||||
|
configureTheme2,
|
||||||
|
icon2
|
||||||
|
} from "./chunk-p6jvscj3.js";
|
||||||
|
import"./chunk-yyg967q8.js";
|
||||||
|
import {
|
||||||
|
ThemeConfigSchema2
|
||||||
|
} from "./chunk-njvj322f.js";
|
||||||
|
import"./chunk-6ggk5fg7.js";
|
||||||
|
import {
|
||||||
|
configPath2
|
||||||
|
} from "./chunk-xykm10y9.js";
|
||||||
|
export {
|
||||||
|
ThemeConfigSchema2 as ThemeConfigSchema,
|
||||||
|
configPath2 as configPath,
|
||||||
|
configureTheme2 as configureTheme,
|
||||||
|
icon2 as icon,
|
||||||
|
presets2 as presets,
|
||||||
|
theme2 as theme
|
||||||
|
};
|
||||||
Vendored
+27
@@ -0,0 +1,27 @@
|
|||||||
|
// @bun
|
||||||
|
// src/keymap/provider.tsx
|
||||||
|
import { createComponent as _$createComponent } from "@opentui/solid";
|
||||||
|
import { createDefaultOpenTuiKeymap } from "@opentui/keymap/opentui";
|
||||||
|
import { registerCommaBindings } from "@opentui/keymap/addons";
|
||||||
|
import { KeymapProvider as BaseKeymapProvider } from "@opentui/keymap/solid";
|
||||||
|
import { useRenderer } from "@opentui/solid";
|
||||||
|
import { onCleanup } from "solid-js";
|
||||||
|
import { useKeymap, useBindings, useKeymapSelector, reactiveMatcherFromSignal } from "@opentui/keymap/solid";
|
||||||
|
function KeymapProvider(props) {
|
||||||
|
const renderer = useRenderer();
|
||||||
|
const keymap = createDefaultOpenTuiKeymap(renderer);
|
||||||
|
onCleanup(registerCommaBindings(keymap));
|
||||||
|
return _$createComponent(BaseKeymapProvider, {
|
||||||
|
keymap,
|
||||||
|
get children() {
|
||||||
|
return props.children;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export {
|
||||||
|
KeymapProvider,
|
||||||
|
reactiveMatcherFromSignal,
|
||||||
|
useBindings,
|
||||||
|
useKeymap,
|
||||||
|
useKeymapSelector
|
||||||
|
};
|
||||||
Vendored
+7
@@ -0,0 +1,7 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
buildPresets2
|
||||||
|
} from "./chunk-yyg967q8.js";
|
||||||
|
export {
|
||||||
|
buildPresets2 as buildPresets
|
||||||
|
};
|
||||||
Vendored
+117
@@ -0,0 +1,117 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
notify2
|
||||||
|
} from "./chunk-6ggk5fg7.js";
|
||||||
|
|
||||||
|
// src/project-config.ts
|
||||||
|
import { existsSync, readFileSync } from "fs";
|
||||||
|
import { homedir } from "os";
|
||||||
|
import { join } from "path";
|
||||||
|
import { parse as parseYAML } from "yaml";
|
||||||
|
import { z } from "zod";
|
||||||
|
function userConfigPath(projectName) {
|
||||||
|
const configHome = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
||||||
|
return join(configHome, projectName, "config.yaml");
|
||||||
|
}
|
||||||
|
function readYaml(path) {
|
||||||
|
return parseYAML(readFileSync(path, "utf-8"));
|
||||||
|
}
|
||||||
|
function warn(projectName, path, message) {
|
||||||
|
const text = `${path}: ${message}`;
|
||||||
|
console.error(`[${projectName} config] ${text}`);
|
||||||
|
notify2(text, { kind: "warning", duration: 0 });
|
||||||
|
}
|
||||||
|
function loadProjectConfig(options) {
|
||||||
|
const { projectName, defaultConfigPath, defaultSchema, userSchema } = options;
|
||||||
|
let rawDefaults;
|
||||||
|
try {
|
||||||
|
rawDefaults = readYaml(defaultConfigPath);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to read ${defaultConfigPath}: ${error instanceof Error ? error.message : error}`);
|
||||||
|
}
|
||||||
|
const defaultsResult = defaultSchema.safeParse(rawDefaults);
|
||||||
|
if (!defaultsResult.success) {
|
||||||
|
throw new Error(`${defaultConfigPath} is invalid:
|
||||||
|
${z.prettifyError(defaultsResult.error)}`);
|
||||||
|
}
|
||||||
|
const path = userConfigPath(projectName);
|
||||||
|
const user = (() => {
|
||||||
|
if (!existsSync(path))
|
||||||
|
return {};
|
||||||
|
let raw;
|
||||||
|
try {
|
||||||
|
raw = readYaml(path);
|
||||||
|
} catch (error) {
|
||||||
|
warn(projectName, path, error instanceof Error ? error.message : String(error));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const result = userSchema.safeParse(raw ?? {});
|
||||||
|
if (!result.success) {
|
||||||
|
warn(projectName, path, z.prettifyError(result.error));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return result.data;
|
||||||
|
})();
|
||||||
|
return { defaults: defaultsResult.data, user };
|
||||||
|
}
|
||||||
|
function isPlainObject(value) {
|
||||||
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||||
|
}
|
||||||
|
function deepMerge(base, over) {
|
||||||
|
if (!isPlainObject(base) || !isPlainObject(over))
|
||||||
|
return over === undefined ? base : over;
|
||||||
|
const out = { ...base };
|
||||||
|
for (const [key, value] of Object.entries(over))
|
||||||
|
out[key] = deepMerge(base[key], value);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
function loadMergedProjectConfig(options) {
|
||||||
|
const { projectName, defaultConfigPath, schema } = options;
|
||||||
|
let rawDefaults;
|
||||||
|
try {
|
||||||
|
rawDefaults = readYaml(defaultConfigPath);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to read ${defaultConfigPath}: ${error instanceof Error ? error.message : error}`);
|
||||||
|
}
|
||||||
|
const defaults = schema.safeParse(rawDefaults);
|
||||||
|
if (!defaults.success) {
|
||||||
|
throw new Error(`${defaultConfigPath} is invalid:
|
||||||
|
${z.prettifyError(defaults.error)}`);
|
||||||
|
}
|
||||||
|
const path = userConfigPath(projectName);
|
||||||
|
if (!existsSync(path))
|
||||||
|
return defaults.data;
|
||||||
|
let rawUser;
|
||||||
|
try {
|
||||||
|
rawUser = readYaml(path);
|
||||||
|
} catch (error) {
|
||||||
|
warn(projectName, path, error instanceof Error ? error.message : String(error));
|
||||||
|
return defaults.data;
|
||||||
|
}
|
||||||
|
const merged = schema.safeParse(deepMerge(rawDefaults, rawUser ?? {}));
|
||||||
|
if (!merged.success) {
|
||||||
|
warn(projectName, path, z.prettifyError(merged.error));
|
||||||
|
return defaults.data;
|
||||||
|
}
|
||||||
|
return merged.data;
|
||||||
|
}
|
||||||
|
function keybindsSchema(names, required) {
|
||||||
|
const shape = {};
|
||||||
|
for (const name of names) {
|
||||||
|
shape[name] = required ? z.string().min(1) : z.string().min(1).optional();
|
||||||
|
}
|
||||||
|
return z.object(shape).strict();
|
||||||
|
}
|
||||||
|
function mergeKeybinds(names, defaults, user) {
|
||||||
|
const merged = {};
|
||||||
|
for (const name of names) {
|
||||||
|
merged[name] = user?.[name] ?? defaults[name];
|
||||||
|
}
|
||||||
|
return merged;
|
||||||
|
}
|
||||||
|
export {
|
||||||
|
keybindsSchema,
|
||||||
|
loadMergedProjectConfig,
|
||||||
|
loadProjectConfig,
|
||||||
|
mergeKeybinds
|
||||||
|
};
|
||||||
Vendored
+48
@@ -0,0 +1,48 @@
|
|||||||
|
// @bun
|
||||||
|
import"./chunk-njvj322f.js";
|
||||||
|
import"./chunk-6ggk5fg7.js";
|
||||||
|
import"./chunk-yyg967q8.js";
|
||||||
|
import"./chunk-xykm10y9.js";
|
||||||
|
import {
|
||||||
|
theme2,
|
||||||
|
presets2
|
||||||
|
} from "./chunk-p6jvscj3.js";
|
||||||
|
|
||||||
|
// src/solid.ts
|
||||||
|
import { extend } from "@opentui/solid";
|
||||||
|
import {
|
||||||
|
ASCIIFontRenderable,
|
||||||
|
BoxRenderable,
|
||||||
|
InputRenderable,
|
||||||
|
SelectRenderable,
|
||||||
|
SliderRenderable,
|
||||||
|
TabSelectRenderable,
|
||||||
|
TextareaRenderable,
|
||||||
|
TextRenderable
|
||||||
|
} from "@opentui/core";
|
||||||
|
function withThemeDefaults(Ctor, defaults, after) {
|
||||||
|
class Themed extends Ctor {
|
||||||
|
constructor(...args) {
|
||||||
|
super(...args);
|
||||||
|
Object.assign(this, defaults());
|
||||||
|
after?.(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Themed;
|
||||||
|
}
|
||||||
|
var boxDefaults = () => {
|
||||||
|
const { borderStyle, borderColor, focusedBorderColor } = presets2.box;
|
||||||
|
return { borderStyle, borderColor, focusedBorderColor };
|
||||||
|
};
|
||||||
|
extend({
|
||||||
|
box: withThemeDefaults(BoxRenderable, boxDefaults, (instance) => {
|
||||||
|
instance.border = false;
|
||||||
|
}),
|
||||||
|
select: withThemeDefaults(SelectRenderable, () => presets2.select),
|
||||||
|
tab_select: withThemeDefaults(TabSelectRenderable, () => presets2.tabSelect),
|
||||||
|
input: withThemeDefaults(InputRenderable, () => presets2.input),
|
||||||
|
textarea: withThemeDefaults(TextareaRenderable, () => presets2.textarea),
|
||||||
|
slider: withThemeDefaults(SliderRenderable, () => presets2.slider),
|
||||||
|
text: withThemeDefaults(TextRenderable, () => ({ fg: theme2.text })),
|
||||||
|
ascii_font: withThemeDefaults(ASCIIFontRenderable, () => ({ color: theme2.primary }))
|
||||||
|
});
|
||||||
Vendored
+7
@@ -0,0 +1,7 @@
|
|||||||
|
// @bun
|
||||||
|
import {
|
||||||
|
configPath2
|
||||||
|
} from "./chunk-xykm10y9.js";
|
||||||
|
export {
|
||||||
|
configPath2 as configPath
|
||||||
|
};
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
# drawer
|
|
||||||
|
|
||||||
A full-height panel flush against the left or right edge of an already-rendered background, on top
|
|
||||||
of it dimmed. The sidebar/drawer equivalent of [`modal`](../modal/README.md), which it otherwise
|
|
||||||
mirrors closely: same `tea.Msg`-triggered stack (`ShowMsg`/`Show`), same composite-over-a-string
|
|
||||||
`Render`, same rule that content is a `tea.Model`.
|
|
||||||
|
|
||||||
- `WithSide(Left|Right)` and `WithWidth` are per-drawer `Show` options; width otherwise shrinks to
|
|
||||||
fit content, capped by the `Model`'s `WithMaxWidth`.
|
|
||||||
- The stack is a plain LIFO. Only the topmost drawer is updated; `Close()` closes it.
|
|
||||||
- `View(width, height)` renders on a blank background of that size, for use as a standalone pane.
|
|
||||||
|
|
||||||
See `examples/drawer`.
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
package drawer
|
|
||||||
|
|
||||||
import tea "charm.land/bubbletea/v2"
|
|
||||||
|
|
||||||
type Side int
|
|
||||||
|
|
||||||
const (
|
|
||||||
Left Side = iota
|
|
||||||
Right
|
|
||||||
)
|
|
||||||
|
|
||||||
type Drawer struct {
|
|
||||||
Title string
|
|
||||||
Content tea.Model
|
|
||||||
Side Side
|
|
||||||
Width int
|
|
||||||
Style *Styles
|
|
||||||
}
|
|
||||||
|
|
||||||
type DrawerOption func(*Drawer)
|
|
||||||
|
|
||||||
func WithSide(s Side) DrawerOption {
|
|
||||||
return func(d *Drawer) { d.Side = s }
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithWidth(w int) DrawerOption {
|
|
||||||
return func(d *Drawer) { d.Width = w }
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithDrawerStyle(s Styles) DrawerOption {
|
|
||||||
return func(d *Drawer) { d.Style = &s }
|
|
||||||
}
|
|
||||||
|
|
||||||
func newDrawer(title string, content tea.Model, opts ...DrawerOption) Drawer {
|
|
||||||
d := Drawer{Title: title, Content: content}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(&d)
|
|
||||||
}
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
type ShowMsg struct{ Drawer Drawer }
|
|
||||||
|
|
||||||
func Show(title string, content tea.Model, opts ...DrawerOption) tea.Cmd {
|
|
||||||
d := newDrawer(title, content, opts...)
|
|
||||||
return func() tea.Msg { return ShowMsg{Drawer: d} }
|
|
||||||
}
|
|
||||||
|
|
||||||
type DismissMsg struct{}
|
|
||||||
|
|
||||||
func Close() tea.Cmd {
|
|
||||||
return func() tea.Msg { return DismissMsg{} }
|
|
||||||
}
|
|
||||||
|
|
||||||
type text string
|
|
||||||
|
|
||||||
func (t text) Init() tea.Cmd { return nil }
|
|
||||||
func (t text) Update(tea.Msg) (tea.Model, tea.Cmd) { return t, nil }
|
|
||||||
func (t text) View() tea.View { return tea.NewView(string(t)) }
|
|
||||||
|
|
||||||
func Text(s string) tea.Model { return text(s) }
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
package drawer
|
|
||||||
|
|
||||||
import tea "charm.land/bubbletea/v2"
|
|
||||||
|
|
||||||
type Model struct {
|
|
||||||
drawers []Drawer
|
|
||||||
nextID int
|
|
||||||
maxWidth int
|
|
||||||
styles Styles
|
|
||||||
}
|
|
||||||
|
|
||||||
type Option func(*Model)
|
|
||||||
|
|
||||||
func WithMaxWidth(w int) Option {
|
|
||||||
return func(m *Model) { m.maxWidth = w }
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithStyles(s Styles) Option {
|
|
||||||
return func(m *Model) { m.styles = s }
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(opts ...Option) Model {
|
|
||||||
m := Model{
|
|
||||||
maxWidth: 30,
|
|
||||||
styles: DefaultStyles(),
|
|
||||||
}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(&m)
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Model) Init() tea.Cmd { return nil }
|
|
||||||
|
|
||||||
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
|
|
||||||
switch msg := msg.(type) {
|
|
||||||
case ShowMsg:
|
|
||||||
return m.show(msg.Drawer)
|
|
||||||
case DismissMsg:
|
|
||||||
return m.pop(), nil
|
|
||||||
}
|
|
||||||
return m.updateTop(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Model) updateTop(msg tea.Msg) (Model, tea.Cmd) {
|
|
||||||
i := len(m.drawers) - 1
|
|
||||||
if i < 0 || m.drawers[i].Content == nil {
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
var cmd tea.Cmd
|
|
||||||
m.drawers[i].Content, cmd = m.drawers[i].Content.Update(msg)
|
|
||||||
return m, cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Model) Open() bool { return len(m.drawers) > 0 }
|
|
||||||
|
|
||||||
func (m Model) show(d Drawer) (Model, tea.Cmd) {
|
|
||||||
m.drawers = append(m.drawers, d)
|
|
||||||
return m, initContent(d)
|
|
||||||
}
|
|
||||||
|
|
||||||
func initContent(d Drawer) tea.Cmd {
|
|
||||||
if d.Content == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return d.Content.Init()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Model) pop() Model {
|
|
||||||
if len(m.drawers) == 0 {
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
m.drawers = m.drawers[:len(m.drawers)-1]
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
package drawer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"image/color"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"charm.land/lipgloss/v2"
|
|
||||||
"github.com/charmbracelet/x/ansi"
|
|
||||||
|
|
||||||
"github.com/anotherhadi/ilovetui/style"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (m Model) Render(background string) string {
|
|
||||||
if len(m.drawers) == 0 {
|
|
||||||
return background
|
|
||||||
}
|
|
||||||
|
|
||||||
result := background
|
|
||||||
for _, d := range m.drawers {
|
|
||||||
w, h := lipgloss.Width(result), lipgloss.Height(result)
|
|
||||||
if w <= 0 || h <= 0 {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
result = m.renderOne(d, result, w, h)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Model) View(width, height int) string {
|
|
||||||
return m.Render(blank(width, height))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Model) renderOne(d Drawer, background string, w, h int) string {
|
|
||||||
s := m.styles
|
|
||||||
if d.Style != nil {
|
|
||||||
s = *d.Style
|
|
||||||
}
|
|
||||||
|
|
||||||
box := m.renderBox(d, s, w, h)
|
|
||||||
bw := lipgloss.Width(box)
|
|
||||||
x := 0
|
|
||||||
if d.Side == Right {
|
|
||||||
x = max(w-bw, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
compositor := lipgloss.NewCompositor(
|
|
||||||
lipgloss.NewLayer(dim(background, s.DimColor)),
|
|
||||||
lipgloss.NewLayer(box).X(x).Y(0).Z(1),
|
|
||||||
)
|
|
||||||
return compositor.Render()
|
|
||||||
}
|
|
||||||
|
|
||||||
func dim(s string, c color.Color) string {
|
|
||||||
return lipgloss.NewStyle().Foreground(c).Render(ansi.Strip(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Model) renderBox(d Drawer, s Styles, bgW, bgH int) string {
|
|
||||||
widthCap := m.maxWidth
|
|
||||||
if d.Width > 0 {
|
|
||||||
widthCap = d.Width
|
|
||||||
}
|
|
||||||
maxW := effectiveMax(widthCap, bgW)
|
|
||||||
|
|
||||||
body := contentView(d)
|
|
||||||
inner := contentWidth(d, body, maxW)
|
|
||||||
content := s.Content.Width(inner).Render(body)
|
|
||||||
|
|
||||||
boxWidth := inner + 4
|
|
||||||
|
|
||||||
return style.RenderWithTitle(s.Border, s.Title.Render(d.Title), content, boxWidth, bgH)
|
|
||||||
}
|
|
||||||
|
|
||||||
func contentView(d Drawer) string {
|
|
||||||
if d.Content == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return d.Content.View().Content
|
|
||||||
}
|
|
||||||
|
|
||||||
func contentWidth(d Drawer, body string, maxWidth int) int {
|
|
||||||
capped := max(maxWidth-4, 1)
|
|
||||||
if d.Width > 0 {
|
|
||||||
return capped
|
|
||||||
}
|
|
||||||
natural := max(naturalWidth(body), lipgloss.Width(d.Title), 1)
|
|
||||||
return min(natural, capped)
|
|
||||||
}
|
|
||||||
|
|
||||||
func naturalWidth(content string) int {
|
|
||||||
w := 0
|
|
||||||
for _, line := range strings.Split(content, "\n") {
|
|
||||||
if lw := lipgloss.Width(line); lw > w {
|
|
||||||
w = lw
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
func effectiveMax(configured, fits int) int {
|
|
||||||
if fits < 1 {
|
|
||||||
fits = 1
|
|
||||||
}
|
|
||||||
if configured > 0 && configured < fits {
|
|
||||||
return configured
|
|
||||||
}
|
|
||||||
return fits
|
|
||||||
}
|
|
||||||
|
|
||||||
func blank(width, height int) string {
|
|
||||||
if width <= 0 || height <= 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
line := strings.Repeat(" ", width)
|
|
||||||
lines := make([]string, height)
|
|
||||||
for i := range lines {
|
|
||||||
lines[i] = line
|
|
||||||
}
|
|
||||||
return strings.Join(lines, "\n")
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
package drawer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"charm.land/lipgloss/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func background(w, h int) string {
|
|
||||||
return blank(w, h)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRenderLeftFlushToLeftEdge(t *testing.T) {
|
|
||||||
m := New(WithMaxWidth(10))
|
|
||||||
m, _ = m.Update(ShowMsg{Drawer: newDrawer("Nav", Text("hi"), WithSide(Left))})
|
|
||||||
|
|
||||||
out := m.Render(background(40, 10))
|
|
||||||
lines := strings.Split(out, "\n")
|
|
||||||
if len(lines) != 10 {
|
|
||||||
t.Fatalf("expected 10 lines, got %d", len(lines))
|
|
||||||
}
|
|
||||||
if lipgloss.Width(out) == 0 {
|
|
||||||
t.Fatalf("expected non-empty render")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len([]rune(lines[0])) == 0 {
|
|
||||||
t.Fatalf("expected a rendered top border line")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRenderRightFlushToRightEdge(t *testing.T) {
|
|
||||||
m := New(WithMaxWidth(10))
|
|
||||||
m, _ = m.Update(ShowMsg{Drawer: newDrawer("Inspector", Text("hi"), WithSide(Right))})
|
|
||||||
|
|
||||||
out := m.Render(background(40, 10))
|
|
||||||
if lipgloss.Width(out) != 40 {
|
|
||||||
t.Fatalf("expected full background width 40, got %d", lipgloss.Width(out))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSpansFullBackgroundHeight(t *testing.T) {
|
|
||||||
m := New()
|
|
||||||
m, _ = m.Update(ShowMsg{Drawer: newDrawer("Nav", Text("hi"))})
|
|
||||||
|
|
||||||
out := m.Render(background(40, 12))
|
|
||||||
if lipgloss.Height(out) != 12 {
|
|
||||||
t.Fatalf("expected full height 12, got %d", lipgloss.Height(out))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFixedWidthHonored(t *testing.T) {
|
|
||||||
m := New(WithMaxWidth(50))
|
|
||||||
m, _ = m.Update(ShowMsg{Drawer: newDrawer("Nav", Text("x"), WithWidth(20))})
|
|
||||||
|
|
||||||
box := m.renderBox(m.drawers[0], m.styles, 80, 10)
|
|
||||||
if got := lipgloss.Width(box); got != 20 {
|
|
||||||
t.Fatalf("expected fixed box width 20, got %d", got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNoDrawersReturnsBackgroundUnchanged(t *testing.T) {
|
|
||||||
m := New()
|
|
||||||
bg := background(10, 5)
|
|
||||||
if got := m.Render(bg); got != bg {
|
|
||||||
t.Fatalf("expected background unchanged when no drawer is open")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
package drawer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"image/color"
|
|
||||||
|
|
||||||
"charm.land/lipgloss/v2"
|
|
||||||
|
|
||||||
"github.com/anotherhadi/ilovetui/style"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Styles struct {
|
|
||||||
Border lipgloss.Style
|
|
||||||
Title lipgloss.Style
|
|
||||||
Content lipgloss.Style
|
|
||||||
DimColor color.Color
|
|
||||||
}
|
|
||||||
|
|
||||||
func DefaultStyles() Styles {
|
|
||||||
return Styles{
|
|
||||||
Border: style.S.PanelFocused.Padding(0, 1),
|
|
||||||
Title: lipgloss.NewStyle().Bold(true).Foreground(style.S.Primary),
|
|
||||||
Content: lipgloss.NewStyle().Foreground(style.S.Text),
|
|
||||||
DimColor: style.S.Subtle,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"charm.land/bubbles/v2/spinner"
|
|
||||||
"charm.land/bubbles/v2/textinput"
|
|
||||||
tea "charm.land/bubbletea/v2"
|
|
||||||
"charm.land/lipgloss/v2"
|
|
||||||
|
|
||||||
"github.com/anotherhadi/ilovetui/bubbles"
|
|
||||||
)
|
|
||||||
|
|
||||||
type model struct {
|
|
||||||
input textinput.Model
|
|
||||||
spinner spinner.Model
|
|
||||||
}
|
|
||||||
|
|
||||||
func newModel() model {
|
|
||||||
ti := bubbles.NewTextInput()
|
|
||||||
ti.Placeholder = "type something"
|
|
||||||
ti.Focus()
|
|
||||||
return model{input: ti, spinner: bubbles.NewSpinner()}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m model) Init() tea.Cmd { return m.spinner.Tick }
|
|
||||||
|
|
||||||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
||||||
if k, ok := msg.(tea.KeyPressMsg); ok && k.String() == "ctrl+c" {
|
|
||||||
return m, tea.Quit
|
|
||||||
}
|
|
||||||
var inputCmd, spinnerCmd tea.Cmd
|
|
||||||
m.input, inputCmd = m.input.Update(msg)
|
|
||||||
m.spinner, spinnerCmd = m.spinner.Update(msg)
|
|
||||||
return m, tea.Batch(inputCmd, spinnerCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m model) View() tea.View {
|
|
||||||
return tea.NewView(lipgloss.JoinHorizontal(lipgloss.Center, m.spinner.View(), " ", m.input.View()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if _, err := tea.NewProgram(newModel()).Run(); err != nil {
|
|
||||||
fmt.Println("Error running program:", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
tea "charm.land/bubbletea/v2"
|
|
||||||
"charm.land/lipgloss/v2"
|
|
||||||
|
|
||||||
"github.com/anotherhadi/ilovetui/drawer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type model struct {
|
|
||||||
d drawer.Model
|
|
||||||
width, height int
|
|
||||||
}
|
|
||||||
|
|
||||||
func newModel() model { return model{d: drawer.New()} }
|
|
||||||
|
|
||||||
func (m model) Init() tea.Cmd { return m.d.Init() }
|
|
||||||
|
|
||||||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
||||||
switch msg := msg.(type) {
|
|
||||||
case tea.WindowSizeMsg:
|
|
||||||
m.width, m.height = msg.Width, msg.Height
|
|
||||||
case tea.KeyPressMsg:
|
|
||||||
switch msg.String() {
|
|
||||||
case "q":
|
|
||||||
return m, tea.Quit
|
|
||||||
case "l":
|
|
||||||
return m, drawer.Show("Nav", drawer.Text("Home\nSettings"), drawer.WithSide(drawer.Left))
|
|
||||||
case "esc":
|
|
||||||
if m.d.Open() {
|
|
||||||
return m, drawer.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var cmd tea.Cmd
|
|
||||||
m.d, cmd = m.d.Update(msg)
|
|
||||||
return m, cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m model) View() tea.View {
|
|
||||||
background := lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, "l: open q: quit")
|
|
||||||
view := tea.NewView(m.d.Render(background))
|
|
||||||
view.AltScreen = true
|
|
||||||
return view
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if _, err := tea.NewProgram(newModel()).Run(); err != nil {
|
|
||||||
fmt.Println("Error running program:", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"charm.land/bubbles/v2/key"
|
|
||||||
tea "charm.land/bubbletea/v2"
|
|
||||||
"charm.land/lipgloss/v2"
|
|
||||||
|
|
||||||
"github.com/anotherhadi/ilovetui/helpbar"
|
|
||||||
)
|
|
||||||
|
|
||||||
type keyMap struct {
|
|
||||||
Inc, Help, Quit key.Binding
|
|
||||||
}
|
|
||||||
|
|
||||||
func defaultKeyMap() keyMap {
|
|
||||||
return keyMap{
|
|
||||||
Inc: key.NewBinding(key.WithKeys("+"), key.WithHelp("+", "increment")),
|
|
||||||
Help: key.NewBinding(key.WithKeys("?"), key.WithHelp("?", "help")),
|
|
||||||
Quit: key.NewBinding(key.WithKeys("q"), key.WithHelp("q", "quit")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type model struct {
|
|
||||||
help helpbar.Model
|
|
||||||
keys keyMap
|
|
||||||
n, w int
|
|
||||||
}
|
|
||||||
|
|
||||||
func newModel() model {
|
|
||||||
keys := defaultKeyMap()
|
|
||||||
return model{
|
|
||||||
keys: keys,
|
|
||||||
help: helpbar.New(helpbar.WithToggle(keys.Help), helpbar.WithGlobal(keys.Quit)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m model) Init() tea.Cmd { return nil }
|
|
||||||
|
|
||||||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
||||||
switch msg := msg.(type) {
|
|
||||||
case tea.WindowSizeMsg:
|
|
||||||
m.w = msg.Width
|
|
||||||
m.help.SetWidth(m.w)
|
|
||||||
case tea.KeyPressMsg:
|
|
||||||
switch {
|
|
||||||
case key.Matches(msg, m.keys.Quit):
|
|
||||||
return m, tea.Quit
|
|
||||||
case key.Matches(msg, m.keys.Inc):
|
|
||||||
m.n++
|
|
||||||
default:
|
|
||||||
m.help, _ = m.help.Update(msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m model) View() tea.View {
|
|
||||||
bar := m.help.View(m.keys.Inc)
|
|
||||||
body := lipgloss.Place(m.w, 1, lipgloss.Center, lipgloss.Top, fmt.Sprintf("count: %d", m.n))
|
|
||||||
view := tea.NewView(lipgloss.JoinVertical(lipgloss.Left, body, bar))
|
|
||||||
view.AltScreen = true
|
|
||||||
return view
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if _, err := tea.NewProgram(newModel()).Run(); err != nil {
|
|
||||||
fmt.Println("Error running program:", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
tea "charm.land/bubbletea/v2"
|
|
||||||
"charm.land/lipgloss/v2"
|
|
||||||
|
|
||||||
"github.com/anotherhadi/ilovetui/modal"
|
|
||||||
)
|
|
||||||
|
|
||||||
type model struct {
|
|
||||||
m modal.Model
|
|
||||||
width, height int
|
|
||||||
}
|
|
||||||
|
|
||||||
func newModel() model { return model{m: modal.New()} }
|
|
||||||
|
|
||||||
func (m model) Init() tea.Cmd { return m.m.Init() }
|
|
||||||
|
|
||||||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
||||||
switch msg := msg.(type) {
|
|
||||||
case tea.WindowSizeMsg:
|
|
||||||
m.width, m.height = msg.Width, msg.Height
|
|
||||||
case tea.KeyPressMsg:
|
|
||||||
switch msg.String() {
|
|
||||||
case "q":
|
|
||||||
return m, tea.Quit
|
|
||||||
case "o":
|
|
||||||
return m, modal.Show("Hello", modal.Text("This is a modal.\n\nesc: close"))
|
|
||||||
case "esc":
|
|
||||||
if m.m.Open() {
|
|
||||||
return m, modal.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var cmd tea.Cmd
|
|
||||||
m.m, cmd = m.m.Update(msg)
|
|
||||||
return m, cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m model) View() tea.View {
|
|
||||||
background := lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, "o: open q: quit")
|
|
||||||
view := tea.NewView(m.m.Render(background))
|
|
||||||
view.AltScreen = true
|
|
||||||
return view
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if _, err := tea.NewProgram(newModel()).Run(); err != nil {
|
|
||||||
fmt.Println("Error running program:", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
tea "charm.land/bubbletea/v2"
|
|
||||||
"charm.land/lipgloss/v2"
|
|
||||||
|
|
||||||
"github.com/anotherhadi/ilovetui/notification"
|
|
||||||
)
|
|
||||||
|
|
||||||
type model struct {
|
|
||||||
notif notification.Model
|
|
||||||
width, height int
|
|
||||||
}
|
|
||||||
|
|
||||||
func newModel() model { return model{notif: notification.New()} }
|
|
||||||
|
|
||||||
func (m model) Init() tea.Cmd { return m.notif.Init() }
|
|
||||||
|
|
||||||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
||||||
switch msg := msg.(type) {
|
|
||||||
case tea.WindowSizeMsg:
|
|
||||||
m.width, m.height = msg.Width, msg.Height
|
|
||||||
case tea.KeyPressMsg:
|
|
||||||
switch msg.String() {
|
|
||||||
case "q":
|
|
||||||
return m, tea.Quit
|
|
||||||
case "s":
|
|
||||||
return m, notification.Show("Saved", "Config written to disk.", notification.Success)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var cmd tea.Cmd
|
|
||||||
m.notif, cmd = m.notif.Update(msg)
|
|
||||||
return m, cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m model) View() tea.View {
|
|
||||||
background := lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, "s: show q: quit")
|
|
||||||
view := tea.NewView(m.notif.Render(background))
|
|
||||||
view.AltScreen = true
|
|
||||||
return view
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if _, err := tea.NewProgram(newModel()).Run(); err != nil {
|
|
||||||
fmt.Println("Error running program:", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
tea "charm.land/bubbletea/v2"
|
|
||||||
|
|
||||||
"github.com/anotherhadi/ilovetui/style"
|
|
||||||
)
|
|
||||||
|
|
||||||
type model struct{ focused bool }
|
|
||||||
|
|
||||||
func (m model) Init() tea.Cmd { return nil }
|
|
||||||
|
|
||||||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
||||||
switch msg := msg.(type) {
|
|
||||||
case tea.KeyPressMsg:
|
|
||||||
switch msg.String() {
|
|
||||||
case "q":
|
|
||||||
return m, tea.Quit
|
|
||||||
case "tab":
|
|
||||||
m.focused = !m.focused
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m model) View() tea.View {
|
|
||||||
box := style.S.Panel
|
|
||||||
if m.focused {
|
|
||||||
box = style.S.PanelFocused
|
|
||||||
}
|
|
||||||
return tea.NewView(style.RenderWithTitle(box, "Panel", "tab: toggle focus q: quit", 30, 5))
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if _, err := tea.NewProgram(model{}).Run(); err != nil {
|
|
||||||
fmt.Println("Error running program:", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
tea "charm.land/bubbletea/v2"
|
|
||||||
|
|
||||||
"github.com/anotherhadi/ilovetui/tabs"
|
|
||||||
)
|
|
||||||
|
|
||||||
type pane string
|
|
||||||
|
|
||||||
func (p pane) Init() tea.Cmd { return nil }
|
|
||||||
func (p pane) Update(tea.Msg) (tabs.Tab, tea.Cmd) { return p, nil }
|
|
||||||
func (p pane) View() string { return string(p) }
|
|
||||||
|
|
||||||
type model struct{ tabs tabs.Model }
|
|
||||||
|
|
||||||
func newModel() model {
|
|
||||||
items := []tabs.Item{
|
|
||||||
{Title: "First", Model: pane("first content")},
|
|
||||||
{Title: "Second", Model: pane("second content")},
|
|
||||||
{Title: "Third", Model: pane("third content")},
|
|
||||||
}
|
|
||||||
return model{tabs: tabs.New(items)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m model) Init() tea.Cmd { return m.tabs.Init() }
|
|
||||||
|
|
||||||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
||||||
if k, ok := msg.(tea.KeyPressMsg); ok && k.String() == "q" {
|
|
||||||
return m, tea.Quit
|
|
||||||
}
|
|
||||||
if s, ok := msg.(tea.WindowSizeMsg); ok {
|
|
||||||
m.tabs.SetSize(s.Width, s.Height)
|
|
||||||
msg = tea.WindowSizeMsg{Width: m.tabs.ContentWidth(), Height: m.tabs.ContentHeight()}
|
|
||||||
}
|
|
||||||
var cmd tea.Cmd
|
|
||||||
m.tabs, cmd = m.tabs.Update(msg)
|
|
||||||
return m, cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m model) View() tea.View {
|
|
||||||
view := tea.NewView(m.tabs.View())
|
|
||||||
view.AltScreen = true
|
|
||||||
return view
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if _, err := tea.NewProgram(newModel()).Run(); err != nil {
|
|
||||||
fmt.Println("Error running program:", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Generated
-87
@@ -1,87 +0,0 @@
|
|||||||
{
|
|
||||||
"nodes": {
|
|
||||||
"flake-compat": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1767039857,
|
|
||||||
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "flake-compat",
|
|
||||||
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "flake-compat",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"git-hooks": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-compat": "flake-compat",
|
|
||||||
"gitignore": "gitignore",
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1778507602,
|
|
||||||
"narHash": "sha256-kTwur1wV+01SdqskVMSo6JMEpg71ps3HpbFY2GsflKs=",
|
|
||||||
"owner": "cachix",
|
|
||||||
"repo": "git-hooks.nix",
|
|
||||||
"rev": "61ab0e80d9c7ab14c256b5b453d8b3fb0189ba0a",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "cachix",
|
|
||||||
"repo": "git-hooks.nix",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"gitignore": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"git-hooks",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1709087332,
|
|
||||||
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
|
|
||||||
"owner": "hercules-ci",
|
|
||||||
"repo": "gitignore.nix",
|
|
||||||
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "hercules-ci",
|
|
||||||
"repo": "gitignore.nix",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1779560665,
|
|
||||||
"narHash": "sha256-tpyBcxPpcQb8ukyNF7DoCwfSY3VPsxHoYwj00Cayv5o=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "64c08a7ca051951c8eae34e3e3cb1e202fe36786",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "nixos-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": {
|
|
||||||
"inputs": {
|
|
||||||
"git-hooks": "git-hooks",
|
|
||||||
"nixpkgs": "nixpkgs"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": "root",
|
|
||||||
"version": 7
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
{
|
|
||||||
description = "A shared Base16 theme, a themed wrapper around every official component, and a small set of custom Bubble Tea components, in one Go module, so every TUI built with it shares one config file and looks consistent.";
|
|
||||||
|
|
||||||
inputs = {
|
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
|
||||||
git-hooks = {
|
|
||||||
url = "github:cachix/git-hooks.nix";
|
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
outputs = {
|
|
||||||
self,
|
|
||||||
nixpkgs,
|
|
||||||
git-hooks,
|
|
||||||
}: let
|
|
||||||
supportedSystems = ["x86_64-linux" "aarch64-linux"];
|
|
||||||
|
|
||||||
forAllSystems = f:
|
|
||||||
nixpkgs.lib.genAttrs supportedSystems
|
|
||||||
(system: f system (import nixpkgs {inherit system;}));
|
|
||||||
in {
|
|
||||||
devShells = forAllSystems (system: pkgs: {
|
|
||||||
default = import ./nix/shell.nix {
|
|
||||||
inherit pkgs;
|
|
||||||
gitHooksLib = git-hooks.lib.${system};
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
module github.com/anotherhadi/ilovetui
|
|
||||||
|
|
||||||
go 1.25.8
|
|
||||||
|
|
||||||
require (
|
|
||||||
charm.land/bubbles/v2 v2.1.0
|
|
||||||
charm.land/bubbletea/v2 v2.0.2
|
|
||||||
charm.land/glamour/v2 v2.0.0
|
|
||||||
charm.land/lipgloss/v2 v2.0.3
|
|
||||||
github.com/charmbracelet/x/ansi v0.11.7
|
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/alecthomas/chroma/v2 v2.14.0 // indirect
|
|
||||||
github.com/atotto/clipboard v0.1.4 // indirect
|
|
||||||
github.com/aymerick/douceur v0.2.0 // indirect
|
|
||||||
github.com/charmbracelet/colorprofile v0.4.3 // indirect
|
|
||||||
github.com/charmbracelet/harmonica v0.2.0 // indirect
|
|
||||||
github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 // indirect
|
|
||||||
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect
|
|
||||||
github.com/charmbracelet/x/term v0.2.2 // indirect
|
|
||||||
github.com/charmbracelet/x/termios v0.1.1 // indirect
|
|
||||||
github.com/charmbracelet/x/windows v0.2.2 // indirect
|
|
||||||
github.com/clipperhouse/displaywidth v0.11.0 // indirect
|
|
||||||
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
|
||||||
github.com/dlclark/regexp2 v1.11.0 // indirect
|
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
|
||||||
github.com/gorilla/css v1.0.1 // indirect
|
|
||||||
github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
|
|
||||||
github.com/mattn/go-runewidth v0.0.23 // indirect
|
|
||||||
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
|
|
||||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
|
||||||
github.com/sahilm/fuzzy v0.1.1 // indirect
|
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
|
||||||
github.com/yuin/goldmark v1.7.8 // indirect
|
|
||||||
github.com/yuin/goldmark-emoji v1.0.5 // indirect
|
|
||||||
golang.org/x/net v0.39.0 // indirect
|
|
||||||
golang.org/x/sync v0.19.0 // indirect
|
|
||||||
golang.org/x/sys v0.43.0 // indirect
|
|
||||||
golang.org/x/text v0.24.0 // indirect
|
|
||||||
)
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
charm.land/bubbles/v2 v2.1.0 h1:YSnNh5cPYlYjPxRrzs5VEn3vwhtEn3jVGRBT3M7/I0g=
|
|
||||||
charm.land/bubbles/v2 v2.1.0/go.mod h1:l97h4hym2hvWBVfmJDtrEHHCtkIKeTEb3TTJ4ZOB3wY=
|
|
||||||
charm.land/bubbletea/v2 v2.0.2 h1:4CRtRnuZOdFDTWSff9r8QFt/9+z6Emubz3aDMnf/dx0=
|
|
||||||
charm.land/bubbletea/v2 v2.0.2/go.mod h1:3LRff2U4WIYXy7MTxfbAQ+AdfM3D8Xuvz2wbsOD9OHQ=
|
|
||||||
charm.land/glamour/v2 v2.0.0 h1:IDBoqLEy7Hdpb9VOXN+khLP/XSxtJy1VsHuW/yF87+U=
|
|
||||||
charm.land/glamour/v2 v2.0.0/go.mod h1:kjq9WB0s8vuUYZNYey2jp4Lgd9f4cKdzAw88FZtpj/w=
|
|
||||||
charm.land/lipgloss/v2 v2.0.3 h1:yM2zJ4Cf5Y51b7RHIwioil4ApI/aypFXXVHSwlM6RzU=
|
|
||||||
charm.land/lipgloss/v2 v2.0.3/go.mod h1:7myLU9iG/3xluAWzpY/fSxYYHCgoKTie7laxk6ATwXA=
|
|
||||||
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
|
|
||||||
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
|
|
||||||
github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE=
|
|
||||||
github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
|
||||||
github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E=
|
|
||||||
github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I=
|
|
||||||
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
|
||||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
|
||||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
|
||||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
|
||||||
github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o=
|
|
||||||
github.com/aymanbagabas/go-udiff v0.4.1/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w=
|
|
||||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
|
||||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
|
||||||
github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=
|
|
||||||
github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=
|
|
||||||
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
|
|
||||||
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
|
|
||||||
github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 h1:eyFRbAmexyt43hVfeyBofiGSEmJ7krjLOYt/9CF5NKA=
|
|
||||||
github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8/go.mod h1:SQpCTRNBtzJkwku5ye4S3HEuthAlGy2n9VXZnWkEW98=
|
|
||||||
github.com/charmbracelet/x/ansi v0.11.7 h1:kzv1kJvjg2S3r9KHo8hDdHFQLEqn4RBCb39dAYC84jI=
|
|
||||||
github.com/charmbracelet/x/ansi v0.11.7/go.mod h1:9qGpnAVYz+8ACONkZBUWPtL7lulP9No6p1epAihUZwQ=
|
|
||||||
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA=
|
|
||||||
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I=
|
|
||||||
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf h1:rLG0Yb6MQSDKdB52aGX55JT1oi0P0Kuaj7wi1bLUpnI=
|
|
||||||
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf/go.mod h1:B3UgsnsBZS/eX42BlaNiJkD1pPOUa+oF1IYC6Yd2CEU=
|
|
||||||
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
|
|
||||||
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
|
|
||||||
github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=
|
|
||||||
github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=
|
|
||||||
github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM=
|
|
||||||
github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k=
|
|
||||||
github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
|
|
||||||
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
|
|
||||||
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
|
|
||||||
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
|
||||||
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
|
||||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
|
||||||
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
|
||||||
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
|
|
||||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
|
||||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
|
||||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
|
||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
|
||||||
github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4=
|
|
||||||
github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
|
||||||
github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw=
|
|
||||||
github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
|
||||||
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
|
|
||||||
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
|
|
||||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
|
||||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
|
||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
|
||||||
github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA=
|
|
||||||
github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
|
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
|
||||||
github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
|
||||||
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
|
|
||||||
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
|
||||||
github.com/yuin/goldmark-emoji v1.0.5 h1:EMVWyCGPlXJfUXBXpuMu+ii3TIaxbVBnEX9uaDC4cIk=
|
|
||||||
github.com/yuin/goldmark-emoji v1.0.5/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U=
|
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
|
||||||
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
|
||||||
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
|
||||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
|
||||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
|
||||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
|
||||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
|
||||||
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
|
||||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
# helpbar
|
|
||||||
|
|
||||||
A responsive help bar: one line of key bindings that expands, on toggle, into a multi-column view
|
|
||||||
reflowed to use as many columns as the available width allows.
|
|
||||||
|
|
||||||
- Bindings render in order: the `WithToggle` binding first, then `WithGlobal` bindings (set once),
|
|
||||||
then contextual bindings passed to `View` at render time.
|
|
||||||
- Disabled bindings (`key.Binding.SetEnabled(false)`) are dropped before layout, so the reflow never
|
|
||||||
budgets width for something that won't be drawn.
|
|
||||||
- `Height(contextual...)` always matches `lipgloss.Height` of `View` for the same arguments, so a
|
|
||||||
host can reserve exactly the right amount of space. An empty bar renders `""` and takes 0 rows.
|
|
||||||
|
|
||||||
See `examples/helpbar`.
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
package helpbar
|
|
||||||
|
|
||||||
import (
|
|
||||||
"charm.land/bubbles/v2/help"
|
|
||||||
"charm.land/bubbles/v2/key"
|
|
||||||
tea "charm.land/bubbletea/v2"
|
|
||||||
"charm.land/lipgloss/v2"
|
|
||||||
|
|
||||||
"github.com/anotherhadi/ilovetui/bubbles"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Model struct {
|
|
||||||
ShowAll bool
|
|
||||||
|
|
||||||
help help.Model
|
|
||||||
global []key.Binding
|
|
||||||
toggle key.Binding
|
|
||||||
width int
|
|
||||||
}
|
|
||||||
|
|
||||||
type Option func(*Model)
|
|
||||||
|
|
||||||
func WithGlobal(bindings ...key.Binding) Option {
|
|
||||||
return func(m *Model) { m.global = bindings }
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithToggle(b key.Binding) Option {
|
|
||||||
return func(m *Model) { m.toggle = b }
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithStyles(s help.Styles) Option {
|
|
||||||
return func(m *Model) { m.help.Styles = s }
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(opts ...Option) Model {
|
|
||||||
m := Model{help: bubbles.NewHelp()}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(&m)
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Model) SetWidth(w int) {
|
|
||||||
m.width = w
|
|
||||||
m.help.SetWidth(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Model) Width() int { return m.width }
|
|
||||||
|
|
||||||
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
|
|
||||||
if keyMsg, ok := msg.(tea.KeyPressMsg); ok && key.Matches(keyMsg, m.toggle) {
|
|
||||||
m.ShowAll = !m.ShowAll
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Model) View(contextual ...key.Binding) string {
|
|
||||||
bindings := m.bindings(contextual)
|
|
||||||
if len(bindings) == 0 || m.width <= 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
if !m.ShowAll {
|
|
||||||
return m.help.ShortHelpView(bindings)
|
|
||||||
}
|
|
||||||
return m.help.FullHelpView(m.columns(bindings))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Model) Height(contextual ...key.Binding) int {
|
|
||||||
view := m.View(contextual...)
|
|
||||||
if view == "" {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return lipgloss.Height(view)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Model) bindings(contextual []key.Binding) []key.Binding {
|
|
||||||
all := make([]key.Binding, 0, 1+len(m.global)+len(contextual))
|
|
||||||
if m.toggle.Enabled() {
|
|
||||||
all = append(all, m.toggle)
|
|
||||||
}
|
|
||||||
for _, b := range append(append([]key.Binding{}, m.global...), contextual...) {
|
|
||||||
if b.Enabled() {
|
|
||||||
all = append(all, b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return all
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Model) columns(bindings []key.Binding) [][]key.Binding {
|
|
||||||
if m.width <= 0 {
|
|
||||||
return [][]key.Binding{bindings}
|
|
||||||
}
|
|
||||||
for rows := 1; rows < len(bindings); rows++ {
|
|
||||||
groups := chunkColumns(bindings, rows)
|
|
||||||
if m.renderedWidth(groups) <= m.width {
|
|
||||||
return groups
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return chunkColumns(bindings, len(bindings))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Model) renderedWidth(groups [][]key.Binding) int {
|
|
||||||
unbounded := m.help
|
|
||||||
unbounded.SetWidth(0)
|
|
||||||
return lipgloss.Width(unbounded.FullHelpView(groups))
|
|
||||||
}
|
|
||||||
|
|
||||||
func chunkColumns(bindings []key.Binding, rows int) [][]key.Binding {
|
|
||||||
if rows < 1 {
|
|
||||||
rows = 1
|
|
||||||
}
|
|
||||||
groups := make([][]key.Binding, 0, (len(bindings)+rows-1)/rows)
|
|
||||||
for i := 0; i < len(bindings); i += rows {
|
|
||||||
groups = append(groups, bindings[i:min(i+rows, len(bindings))])
|
|
||||||
}
|
|
||||||
return groups
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
package ilovetui
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
# modal
|
|
||||||
|
|
||||||
A centered popup box on top of a dimmed background, triggered from anywhere in a bubbletea program
|
|
||||||
via an exported `tea.Msg` (`ShowMsg`/`Show`), not a direct reference to the `Model` that renders it.
|
|
||||||
Composites over an already-rendered string, so it makes no assumption about how the host builds
|
|
||||||
that string.
|
|
||||||
|
|
||||||
- A modal's content is a `tea.Model`, not a string: it gets `Init`/`Update` while it's on top, and
|
|
||||||
can report back with its own `tea.Msg` (see `modal.Text` for a plain, non-interactive content).
|
|
||||||
- The stack is a plain LIFO with no identity. Only the topmost modal is updated; `Close()` always
|
|
||||||
closes it.
|
|
||||||
- Showing a second modal while one is open pushes it on top, nesting confirmations naturally.
|
|
||||||
- `WithMaxWidth`/`WithMaxHeight` cap growth; a modal narrower than the cap shrinks to fit instead.
|
|
||||||
|
|
||||||
See `examples/modal`.
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user