@joewinke/jatui 0.38.5 → 0.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +32 -1
- package/THIRD_PARTY_NOTICES.md +51 -0
- package/dist/components/BulkActionBar.svelte +32 -2
- package/dist/components/BulkActionBar.svelte.d.ts +5 -1
- package/dist/components/FloatingActionBar.svelte +102 -34
- package/dist/components/FloatingActionBar.svelte.d.ts +9 -0
- package/dist/components/MediaExplorer.svelte +249 -0
- package/dist/components/MediaExplorer.svelte.d.ts +37 -0
- package/dist/components/NotificationCenter.svelte +481 -0
- package/dist/components/NotificationCenter.svelte.d.ts +29 -0
- package/dist/components/SearchDropdown.svelte +54 -8
- package/dist/components/SearchDropdown.svelte.d.ts +12 -0
- package/dist/components/VideoPlayer.svelte +269 -3
- package/dist/components/agent/AgentLog.svelte +217 -0
- package/dist/components/agent/AgentLog.svelte.d.ts +72 -0
- package/dist/components/agent/AgentLogRow.svelte +281 -0
- package/dist/components/agent/AgentLogRow.svelte.d.ts +21 -0
- package/dist/components/agent/AgentTaskList.svelte +607 -0
- package/dist/components/agent/AgentTaskList.svelte.d.ts +40 -0
- package/dist/components/agent/AgentThinking.svelte +280 -0
- package/dist/components/agent/AgentThinking.svelte.d.ts +22 -0
- package/dist/components/agent/ComposerGlow.svelte +265 -0
- package/dist/components/agent/ComposerGlow.svelte.d.ts +18 -0
- package/dist/components/agent/ContextMeter.svelte +277 -0
- package/dist/components/agent/ContextMeter.svelte.d.ts +21 -0
- package/dist/components/agent/Questionnaire.svelte +767 -0
- package/dist/components/agent/Questionnaire.svelte.d.ts +29 -0
- package/dist/components/agent/ShimmerText.svelte +100 -0
- package/dist/components/agent/ShimmerText.svelte.d.ts +19 -0
- package/dist/components/agent/StreamedText.svelte +135 -0
- package/dist/components/agent/StreamedText.svelte.d.ts +13 -0
- package/dist/components/agent/agentLog.d.ts +22 -0
- package/dist/components/agent/agentLog.js +26 -0
- package/dist/components/agent/agentTaskList.d.ts +90 -0
- package/dist/components/agent/agentTaskList.js +93 -0
- package/dist/components/agent/agentThinking.d.ts +46 -0
- package/dist/components/agent/agentThinking.js +78 -0
- package/dist/components/agent/contextMeter.d.ts +97 -0
- package/dist/components/agent/contextMeter.js +156 -0
- package/dist/components/agent/questionnaire.d.ts +104 -0
- package/dist/components/agent/questionnaire.js +225 -0
- package/dist/components/agent/revealCore.d.ts +74 -0
- package/dist/components/agent/revealCore.js +160 -0
- package/dist/components/agent/revealTicker.d.ts +34 -0
- package/dist/components/agent/revealTicker.js +73 -0
- package/dist/components/agent/streamedText.d.ts +28 -0
- package/dist/components/agent/streamedText.js +59 -0
- package/dist/components/messaging/MessageInput.svelte +34 -4
- package/dist/components/messaging/MessageInput.svelte.d.ts +7 -0
- package/dist/components/messaging/MessageItem.svelte +2 -2
- package/dist/components/notificationCenter.d.ts +65 -0
- package/dist/components/notificationCenter.js +110 -0
- package/dist/components/progress/Steps.svelte +269 -5
- package/dist/components/progress/Steps.svelte.d.ts +16 -1
- package/dist/components/progress/stepsSummary.d.ts +14 -0
- package/dist/components/progress/stepsSummary.js +32 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +29 -0
- package/dist/selection/clickSelection.d.ts +89 -0
- package/dist/selection/clickSelection.js +152 -0
- package/dist/selection/createClickSelection.d.ts +79 -0
- package/dist/selection/createClickSelection.js +134 -0
- package/dist/video/keys.d.ts +5 -5
- package/dist/video/keys.js +9 -5
- package/dist/video/transcriptSearch.d.ts +80 -0
- package/dist/video/transcriptSearch.js +153 -0
- package/package.json +3 -2
package/CLAUDE.md
CHANGED
|
@@ -81,6 +81,37 @@ Then import:
|
|
|
81
81
|
| `VariantMatrixPicker` | Category/option chip grid for building a combinatorial variant matrix (harvested from vid-gen's FrameVariantGenerator, presets stripped — caller-supplied `categories`). Each category tab shows a live selection count and an additive/multiplicative badge: multiplicative selections cartesian-multiply the total, additive ones stack flat onto every combination. Live combination count in the footer; algebra exported separately as pure functions (`generateCombinations`, `countCombinations`, `variantCategoryMode`, `findVariantOption`, `generateVariantFilename`). | `categories: VariantCategory[]`, `selected` (bindable `VariantSelection`), `forceMode?`, `showCount?` (true), `countLabel?`, `onChange?`, `class?` |
|
|
82
82
|
| `BottomSheet` | Mobile bottom sheet (upstreamed from the JAT IDE — jatui-rl7w6.2): drag handle with pointer capture, backdrop close, optional header. `snap='dual'` enables the inspector pattern — peek → expanded snap heights with flick-momentum release (fast flick ≥ 0.5 px/ms wins over position; flick down from peek dismisses) and `visualViewport` keyboard avoidance so content clears the soft keyboard. | `open` (bindable), `title?`, `zIndex?` (70), `fullHeight?`, `snap?` ('single'\|'dual'), `peekFraction?` (0.4), `expandedFraction?` (0.9), `onclose?`, `children` (snippet) |
|
|
83
83
|
|
|
84
|
+
### Agent surfaces (BoardUI wave 2)
|
|
85
|
+
|
|
86
|
+
"Agent is working" UI. Free BoardUI items (agent-thinking, agent-log, composer-loader, notification-center) were ported under MIT — see `THIRD_PARTY_NOTICES.md`; AgentTaskList, Questionnaire and ContextMeter are clean-room. Demos: `/components/agent-thinking`, `agent-log`, `agent-task-list`, `questionnaire`, `context-meter`, `notification-center`, `composer-glow`, `streamed-text`.
|
|
87
|
+
|
|
88
|
+
| Component | Purpose | Key Props |
|
|
89
|
+
|-----------|---------|-----------|
|
|
90
|
+
| `ShimmerText` | A highlight that sweeps across text. Every gradient stop comes from ONE tone via `color-mix`, so any token colour shimmers the same way. Decorative (`.jatui-motion-ambient`): solid text in the tone under `data-motion="calm"`, reduced motion or `active={false}`. | `text?` / `children`, `tone?` ('subtle'\|'default'\|'strong'\|'primary' or any CSS colour), `duration?` (2600 ms), `active?`, `as?`, `class?` |
|
|
91
|
+
| `AgentThinking` | The "agent is working" status line: an indicator, a shimmering label and an elapsed timer. Variants: `wave` / `spin` (3×3 dot comet, pure CSS), `stars` (sparkles), `infinity` (comet on a figure-eight). One `role="status"`; the timer is `aria-hidden`, so screen readers hear the label once. The indicator keeps running on calm (it carries meaning) but stops under reduced motion; the shimmer stops on calm. `startedAt` keeps the true elapsed time when a row re-mounts. | `variant?`, `label?` ('Thinking'), `tone?`, `shimmer?`, `showTimer?`, `startedAt?` (epoch ms), `size?` ('sm'\|'md'), `class?` |
|
|
92
|
+
| `AgentLog` | Streaming agent transcript. Rows grow in: height 0→auto, a brief blur and lift, and a soft fading clip edge (a registered `--jatui-reveal-fade` mask, removed once the row settles). A curved tree guide draws beside each row; the elbow is fixed at 14px so wrapped labels branch at their first line, the trunk is 1px, and it mirrors in RTL. An optional `working` line (AgentThinking "stars") sits at the tail, off the guide. Durations come from `--motion-*` with glide easing. Rows present on first render don't animate, and nothing animates under reduced motion. Fill it with `items` + a `row` snippet, or with `AgentLogRow` children. `bridge` connects a nested list to its parent row's glyph. Pace it with `createRevealTicker`. | `items`, `row` (snippet), `key?`, `working?`, `workingTimer?`, `guide?`, `bridge?`, `appear?`, `live?` (role="log"), `ariaLabel?`, `children?` |
|
|
93
|
+
| `AgentLogRow` | One row of an `AgentLog`: grow-in, soft clip edge and its piece of the guide. Use it directly in children mode. | `first?`, `last?`, `guide?`, `animate?`, `as?` ('li'\|'div'), `class?`, `children` |
|
|
94
|
+
| `AgentTaskList` | An agent's plan as it runs, built on `AgentLog`: tasks → steps. Each task header and each step is one reveal unit. With `revealed` unset a timer paces them (`startDelay` / `stepInterval` / `delayFor`); set it to drive from real events (`Infinity` shows everything). A running task's title shimmers; a done or failed task still reads as running until its steps have appeared. Steps take truncating resource `chips` (icon component or glyph, optional `href`). A step with `sources` branches into a "Sources" row of overlapping site marks: `maxSources` (6) then "+N", a title + domain tooltip on hover/focus, a link when `href` is set, the host's `favicon` URL or a letter tile (no bundled logos). `collapseOnComplete`: `true` folds each finished task once the log moves past it, `'all'` folds them together at the end; failed tasks stay open. The header is a real button (`aria-expanded` / `aria-controls`). `onComplete` fires once. | `tasks: AgentTask[]`, `revealed?`, `stepInterval?`, `startDelay?`, `delayFor?`, `collapseOnComplete?: boolean \| 'all'`, `working?`, `maxSources?`, `sourcesLabel?`, `statusLabels?`, `foldedSummary?`, `onComplete?`, `onToggle?`, `ariaLabel?` |
|
|
95
|
+
| `Questionnaire` | Plan-mode question card shaped like Claude Code's AskUserQuestion: pass a tool call's `questions` (`{question, header?, options: {label, description?, preview?}[], multiSelect?}[]`) straight through; answers come back aligned as `{ values: string[] (option labels), other? }[]`. One question per step, each a `fieldset` / `legend`. Single = radiogroup with roving tabindex (arrows move focus, Space/Enter/click pick, auto-advance after `advanceDelay`); multi = checkbox group. Digit keys 1..N pick and N+1 ticks Other (a component-scoped keydown, never a window listener, ignored while typing). Free-text "Other" row: typing ticks it, Enter sends in single mode. An option's `preview` shows as a monospace pane while it is focused or selected. Footer step pills (jump to any; `aria-current="step"`; answered pills marked) + Previous / Next / Done → `onComplete`. "Question 2 of 4" in the header and in a polite live region. Card height eases between steps on `--motion-move` and content glides in the direction of travel; neither under reduced motion. Pure logic in `agent/questionnaire.ts`. | `questions`, `answers?` (bindable), `step?` (bindable), `onComplete?`, `onChange?`, `allowOther?` (true), `otherLabel?`, `otherPlaceholder?`, `advanceDelay?` (180, `false` disables), `submitLabel?` ('Done'), `nextLabel?`, `previousLabel?`, `title?`, `requireAll?` (true), `disabled?`, `class?` |
|
|
96
|
+
| `ContextMeter` | Context-window meter split by token bucket. Stacked bar coloured from a token palette (primary / secondary / accent / info / success / warning, then `color-mix` variants), with free space as the track. Total line reads "820k / 1M (82%)". `role="meter"` carries valuenow/min/max/valuetext. An `aria-expanded` button reveals a `<table>` breakdown with a "Free space" row; segments with `group` fold under a collapsible header row. Deferred buckets are listed with a dash and left out of both the bar and the total. Warning tint at `warnAt`, error at ≥100% (bar widths rescale to fill exactly 100%). Pairs with `QuotaMeter` for plan limits. | `max`, `segments: {id?, label, tokens, deferred?, color?, group?}[]`, `groups?: {id, label, defaultExpanded?}[]`, `label?` ('Context'), `unit?` ('tokens'), `expanded` (bindable), `warnAt?` (0.8), `collapsible?` (true), `class?` |
|
|
97
|
+
| `NotificationCenter` | Inbox panel: title + "N unread" + "Mark all read"; a tab list with count chips (default All / Mentions / System by `category`; `bind:tab` or uncontrolled `defaultTab`; arrows / Home / End switch tabs). Items are newest first under Today / Yesterday / This week / Earlier headers (`groupNotificationsByDate`, `now` injectable). Roving focus via `createListNav`, scoped to the list: j/k/arrows move, Enter opens → `onOpen(item)` or follows `href`. Opening an item or clicking one of its actions marks it read. The host owns read state (`read`); the component also tracks read ids locally, so it works uncontrolled. The unread dot is announced (sr-only "Unread"). Status glyph or `Avatar`; `<time>` via `formatRelativeTime`, refreshed each minute. Empty state via `Empty` ("You're all caught up"). | `items: NotificationItem[]`, `tabs?: {id, label, filter?}[]`, `tab` (bindable), `defaultTab?`, `onTabChange?`, `onOpen?`, `onMarkRead?(ids)`, `onMarkAllRead?` (omitted → `onMarkRead` gets every unread id), `groupByDate?` (true), `now?`, `maxHeight?` ('32rem'), `title?`, `emptyTitle?`, `emptyDescription?` |
|
|
98
|
+
| `ComposerGlow` | Opt-in ambient rim light around a composer while the agent works: an SVG `<rect rx>` stroke with a pathLength=100 dash laps the box in three layers (crisp line, glow, bloom clipped inside; SVG `feGaussianBlur`, so iOS stays soft); a ResizeObserver keeps the viewBox equal to the px box. Token colours only (info → primary → `color-mix` midpoint → secondary → accent). Fades in/out on `active`; the lap runs only while visible. Under `data-motion="calm"` / reduced motion there is no frozen arc: it shows a static primary focus-ring tint. Paints a `--color-base-100` surface, so the wrapped composer must be transparent. | `active`, `speed?` (4.5 s/lap), `intensity?` ('subtle'\|'normal'\|'vivid'), `radius?` (px or CSS; default box radius), `class?`, `children` |
|
|
99
|
+
| `StreamedText` | Streamed assistant text revealed per paragraph/line, never per token: an append-only splitter (`splitStreamedText`, index keys, code fences kept whole) mounts each block once with a short fade + un-blur on motion tokens, after which it only grows. History doesn't animate; reduced motion is static; a subtle caret shows while `streaming`. `renderBlock` lets the host render markdown per block. Recipe: show `AgentThinking` until the first *text* part exists, not while the request status is "submitted". | `text`, `streaming?`, `split?` ('paragraph'\|'line'), `as?`, `class?`, `renderBlock?: Snippet<[string, number]>` |
|
|
100
|
+
|
|
101
|
+
**Upgrades in this wave (all additive):**
|
|
102
|
+
- **`Steps`** (`progress/Steps.svelte`) — `collapsible?`, `collapsed?` (bindable), `summary?: string | Snippet` (default "3 steps left" / "All steps done", live while folded; the header is a real `aria-expanded` / `aria-controls` button), `orientation?` ('horizontal'\|'vertical'; vertical also shows descriptions), `activeProgress?` (0–1, a mini ProgressRing on the active node and in the folded header), `completionDelay?` + `onFinished?` (fires once after `current` passes the last step; re-arms if `current` drops back). Unset, the markup is unchanged. Helpers: `stepsSummary`, `stepsRemaining`, `isStepsFinished`, `clampStepProgress`.
|
|
103
|
+
- **`MessageInput`** — `busy?` + `onStop?` swap the send button for a same-size "Stop generating" button; Escape in the input stops (an open mention list still takes Escape first); while busy, Enter doesn't send and the draft is kept. `statusTab?: Snippet` renders a slim strip attached to the input's top edge.
|
|
104
|
+
- **`MessageItem`** — the action toolbar and timestamp also appear on keyboard focus (`group-focus-within`), not only on hover.
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
// Reveal pacing for AgentLog / AgentTaskList. Every option may be a value or a getter.
|
|
108
|
+
import { createRevealTicker } from "jatui"
|
|
109
|
+
// const t = createRevealTicker({ total: () => n, stepInterval?, startDelay?, revealed?: () => count | undefined,
|
|
110
|
+
// delayFor?: (i) => ms, run?, onComplete }) → t.revealed, t.complete, t.replay()
|
|
111
|
+
// `revealed` defined = event-driven (no timer). onComplete fires once per completion.
|
|
112
|
+
// The timing core is also exported as a plain class: RevealCore (injectable clock).
|
|
113
|
+
```
|
|
114
|
+
|
|
84
115
|
### Tables (subpath: `jatui/table`)
|
|
85
116
|
|
|
86
117
|
Requires peer `@tanstack/table-core`. `import { DataTable, flexRender, columnResize } from 'jatui/table'`.
|
|
@@ -119,7 +150,7 @@ Item-by-item review surfaces (MOS review-ux, design mos-z8vgx). One `VerdictOpti
|
|
|
119
150
|
| `ThumbnailGrid` | Generic thumbnail strip (chimaro port) — image tiles with text-tile fallback, +N overflow counter, remove/add affordances. Decoupled from any media library: pass `{id, url, alt}` items, or a `cell` snippet for full control of tile contents. `removeMode` — `'hover'` (default) reveals the × on hover/focus and keeps it visible on coarse (touch) pointers (ContextMenuTrigger pattern); `'always'` pins it for touch-first flows. `gridCols` (e.g. `'grid-cols-2 sm:grid-cols-4'`) switches the strip to a responsive CSS grid with column-filling tiles. | `items: ThumbnailItem[]`, `max`, `size` (`xs\|sm\|md\|lg`), `removable`+`onRemove`, `removeMode` (`hover\|always`), `addable`+`onAdd`, `onSelect`, `gridCols`, `class`, `cell` snippet |
|
|
120
151
|
| `MediaWorkbench` | Full-featured image editor modal — upload, AI generate, AI edit, background removal, annotation canvas. **Preferred over `SmartImageEditor` for new image fields.** Two-column modal: preview left, 4 action tabs right (Generate, Edit, Remove BG, Annotate). Save uploads blob to `uploadEndpoint` or emits `onComplete`. Requires proxy endpoints from jst-23w1l.2. | `value` (bindable `string \| null`), `uploadEndpoint?` (default `/api/media`), `onComplete?`, `accept?` |
|
|
121
152
|
| `MediaLightbox` | Full-screen media preview — z-9040 backdrop, arrow-key paging when given an array, `+/-/0` zoom, download action, Escape/backdrop close. **Use this for the general "just show it big" case** — replaces hand-rolled `fixed inset-0 z-[9040]` modals. For annotated / markup-overlaid image previews, use `ImageLightbox` instead. | `src?`, `filename?`, `kind?: 'image' \| 'video'`, `downloadUrl?`, `items?: MediaItem[]`, `initialIndex?`, `onClose?`, `zIndex?` |
|
|
122
|
-
| `VideoPlayer` | **The default video surface — reach for this over a bare `<video controls>`.** One timeline band carrying a waveform-envelope scrubber, chapter flags (top edge), bookmark diamonds (bottom edge), diarized speaker colouring and scroll-to-zoom, plus an optional transcript pane (click to seek, active line auto-scroll, solo/skip per speaker, `Shift+T` toggle) and a "Talk time" legend (2+ speakers only; one voice gets no legend and no per-row name). **`kind="audio"` renders no stage**, and **`transport="band"`** (the audio default) drops the native `<video controls>` bar for a transport row on the band (play/pause, `now / total`, speed, mute + volume, fullscreen for video), so the band is the ONE timeline. **Every intelligence prop is optional** — a bare mp4 still gets the scrubber, hover-time card, wheel volume and the full keyboard map (Space/K, arrows/J/L, vol/mute, −/= speed + Backspace reset, </> frame step, [/] chapters, B bookmark, t theater, ? help overlay), and each pane lights up as its data arrives (nothing shifts layout when diarization lands late). Wheel means volume over the video (shift = seek, alt = speed; a wheel/keyboard volume change also flashes a clear volume OSD — glyph, level bar and %, "Muted" at 0, opacity-only fade) and zoom over the band. Internals live in `src/lib/video/` (band/keys/rate/deepLink/settings + contracts for audio/night/presence — see that README; gap audit vs the extension: `VIDEO-PLAYER-GAP-AUDIT.md`). | `src`, `kind?` (`'video' \| 'audio'`), `transport?` (`'native' \| 'band'`), `chapters?`, `speakers?`, `transcript?`, `bookmarks?`, `waveform?`, `talkTime?`, `startAt?`, `poster?`, `captionsSrc?`, `crossorigin?` (`'anonymous'` — what frame-reading features need), `syncUrlTime?` (`?t=` deep-link, opt-in), `transcriptPosition?` (`'below' \| 'side'`), `transcriptMaxHeight?` (pane bound — default `min(40vh, 28rem)` below, the player's height beside; the active-line follow scrolls only the pane; only a gesture that actually moves the PANE pauses it — a wheel that scrolls the page doesn't — behind a "Resume following" pill, and it resumes by itself on seek / line or chapter click / play after pause / `followResumeMs` (6000) idle while playing; a hovered chapter fills its whole span in the theme accent with full-height edges), `followResumeMs?`, `theater?` (bindable — theater mode, NOT fullscreen: control-bar button + `t`, the HOST lays the page out full-width; a side transcript moves below; remembered per viewer as the `player.theater` setting via `settingsStore`, else guarded localStorage), `theaterButton?` (true), `onTheaterChange?`, `onTimeUpdate?`, `onAddBookmark?`, `onBookmarkContext?` |
|
|
153
|
+
| `VideoPlayer` | **The default video surface — reach for this over a bare `<video controls>`.** One timeline band carrying a waveform-envelope scrubber, chapter flags (top edge), bookmark diamonds (bottom edge), diarized speaker colouring and scroll-to-zoom, plus an optional transcript pane (click to seek, active line auto-scroll, solo/skip per speaker, `Shift+T` toggle) and a "Talk time" legend (2+ speakers only; one voice gets no legend and no per-row name). **`kind="audio"` renders no stage**, and **`transport="band"`** (the audio default) drops the native `<video controls>` bar for a transport row on the band (play/pause, `now / total`, speed, mute + volume, fullscreen for video), so the band is the ONE timeline. **Every intelligence prop is optional** — a bare mp4 still gets the scrubber, hover-time card, wheel volume and the full keyboard map (Space/K, arrows/J/L, vol/mute, −/= speed + Backspace reset, </> frame step, [/] chapters, B bookmark, t theater, ? help overlay), and each pane lights up as its data arrives (nothing shifts layout when diarization lands late). Wheel means volume over the video (shift = seek, alt = speed; a wheel/keyboard volume change also flashes a clear volume OSD — glyph, level bar and %, "Muted" at 0, opacity-only fade) and zoom over the band; Shift+wheel over the transcript steps line by line (a plain wheel over the transcript scrolls the pane natively — the page is never trapped). Internals live in `src/lib/video/` (band/keys/rate/deepLink/settings + contracts for audio/night/presence — see that README; gap audit vs the extension: `VIDEO-PLAYER-GAP-AUDIT.md`). | `src`, `kind?` (`'video' \| 'audio'`), `transport?` (`'native' \| 'band'`), `chapters?`, `speakers?`, `transcript?`, `bookmarks?`, `waveform?`, `talkTime?`, `startAt?`, `poster?`, `captionsSrc?`, `crossorigin?` (`'anonymous'` — what frame-reading features need), `syncUrlTime?` (`?t=` deep-link, opt-in), `transcriptPosition?` (`'below' \| 'side'`), `transcriptMaxHeight?` (pane bound — default `min(40vh, 28rem)` below, the player's height beside; the active-line follow scrolls only the pane; only a gesture that actually moves the PANE pauses it — a wheel that scrolls the page doesn't — behind a "Resume following" pill, and it resumes by itself on seek / line or chapter click / play after pause / `followResumeMs` (6000) idle while playing; a hovered chapter fills its whole span in the theme accent with full-height edges), `followResumeMs?`, `theater?` (bindable — theater mode, NOT fullscreen: control-bar button + `t`, the HOST lays the page out full-width; a side transcript moves below; remembered per viewer as the `player.theater` setting via `settingsStore`, else guarded localStorage), `theaterButton?` (true), `onTheaterChange?`, `onTimeUpdate?`, `onAddBookmark?`, `onBookmarkContext?` |
|
|
123
154
|
| `MediaCapture` | One-tap audio/selfie-video/screen(+mic) recording — a host-agnostic PRIMITIVE. Owns permission requests, MIME negotiation, the record/stop state machine, live self-preview, and mixes screen+mic audio into one track via AudioContext. Never uploads anything — hands the finished clip to `onCapture` as a Blob and the host decides what to do with it. `onCapture` may be async; the component shows "processing" until it resolves. | `onCapture: (result: MediaCaptureResult) => void \| Promise<void>`, `modes?` (default all three), `maxDurationMs?` (default 120000), `label?`, `hint?`, `compact?`, `disabled?` |
|
|
124
155
|
| `MultimodalCaptureField` | "Upload a file, or record yourself going over it" as one input surface — composes `FileDropzone` (capped to one file) with `MediaCapture`. File and recording are independent (either, both, or neither); neither half uploads anything, same "validate and hand back" contract as `FileDropzone`. | `onFileSelected?`, `onFileRemoved?`, `onCapture?`, `onRecordingRemoved?`, `accept?`, `maxFileSize?`, `modes?`, `maxDurationMs?`, `file?`/`fileUrl?`/`fileName?`, `recordingSummary?` |
|
|
125
156
|
| `ListenControl` | "Listen to this page" — a compact pill that expands into a TWO-ROW playback bar over the host's PER-SENTENCE audio (TTS): row 1 = play/pause · chapter chip + current sentence (full remaining width) · ONE settings pill ("1.25× · Sarah" → speed 0.75–2× + voice menu) · close; row 2 = prev/next sentence and −15s/+15s · a document-wide progress bar with chapter ticks (a hatched tail marks the not-yet-synthesized part), time, close. The host supplies an async `source` (sentences/paragraphs with ids → audio URLs, streamed page by page so playback starts on the first chunk while the rest synthesizes; pages merge by id so late URLs fill in) and gets `onSegmentChange(id)` at every boundary for highlight + scroll-follow. Media Session (lock-screen/headset controls, one document-wide position), keyboard scoped to the player (Space/K, ←/→ sentences, Shift+←/→ ±15s, [ / ] speed, Escape), speed+voice persisted in localStorage, `bind:api` for hosts wiring their own global keys, reduced-motion aware, token-only theming, never overflows its host (border-box, `max-width: 100%`, `min-width: 0` chain), fixed sticky mini-bar (same two rows) on narrow screens. Internals in `src/lib/listen/` (one timeline across per-sentence files — `buildTimeline` offsets; see `video/` for the doctrine). Demo synthesizes real WAVs locally. | `source`, `voices?`, `voiceId?` (bindable), `chapters?`, `title?`, `triggerLabel?`, `open?` (bindable), `preload?`, `estimateSec?`, `storageKey?`, `onSegmentChange?`, `onOpenChange?`, `api?` (bindable), `class?`, `children?` (custom trigger) |
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Third-party notices
|
|
2
|
+
|
|
3
|
+
jatui is MIT licensed (see `LICENSE`). Some files adapt code from the projects
|
|
4
|
+
below; each such file also carries the notice in its header comment.
|
|
5
|
+
|
|
6
|
+
## BoardUI (free tier)
|
|
7
|
+
|
|
8
|
+
<https://github.com/BoardUI/boardui> — MIT License
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
MIT License
|
|
12
|
+
|
|
13
|
+
Copyright (c) 2026 Mertcan Dundar Esmergul (BoardUI)
|
|
14
|
+
|
|
15
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
16
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
17
|
+
in the Software without restriction, including without limitation the rights
|
|
18
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
19
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
20
|
+
furnished to do so, subject to the following conditions:
|
|
21
|
+
|
|
22
|
+
The above copyright notice and this permission notice shall be included in all
|
|
23
|
+
copies or substantial portions of the Software.
|
|
24
|
+
|
|
25
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
26
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
27
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
28
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
29
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
30
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
31
|
+
SOFTWARE.
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Files adapted from BoardUI free components:
|
|
35
|
+
|
|
36
|
+
- `src/lib/components/agent/agentThinking.ts`, `AgentThinking.svelte`,
|
|
37
|
+
`ShimmerText.svelte` — from `agent-thinking` (dot-grid comet curve, sparkle
|
|
38
|
+
and figure-eight geometry, single-tone shimmer gradient).
|
|
39
|
+
- `src/lib/components/agent/AgentLog.svelte`, `AgentLogRow.svelte`, `agentLog.ts`,
|
|
40
|
+
`revealCore.ts` — from `agent-log` (grow-in row, soft reveal mask, one-pen
|
|
41
|
+
tree guide, reveal ticker).
|
|
42
|
+
- `src/lib/components/agent/ComposerGlow.svelte` — from `composer-loader`
|
|
43
|
+
(perimeter/dash math, layered SVG blur).
|
|
44
|
+
- `src/lib/components/agent/StreamedText.svelte` — the per-block reveal idea from
|
|
45
|
+
`agent-chat`.
|
|
46
|
+
- `src/lib/components/NotificationCenter.svelte`, `notificationCenter.ts` — from
|
|
47
|
+
`notification-center`.
|
|
48
|
+
|
|
49
|
+
BoardUI Pro items are not covered by the MIT licence. `AgentTaskList`,
|
|
50
|
+
`Questionnaire` and `ContextMeter` were built from public behaviour only; no Pro
|
|
51
|
+
source was read or copied.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
/**
|
|
3
|
-
* BulkActionBar — floating action bar for listNav selections.
|
|
3
|
+
* BulkActionBar — floating action bar for listNav or clickSelection selections.
|
|
4
4
|
*
|
|
5
5
|
* Simpler API than `FloatingActionBar`: takes a `Set` or `Array` of
|
|
6
6
|
* selected ids and a flat `actions[]`. Renders nothing when the
|
|
@@ -47,6 +47,27 @@
|
|
|
47
47
|
* onClear={() => nav.clearSelection()}
|
|
48
48
|
* />
|
|
49
49
|
*
|
|
50
|
+
* Usage — with the mouse (createClickSelection): click selects one,
|
|
51
|
+
* Ctrl/⌘-click toggles, Shift-click selects a range in display order,
|
|
52
|
+
* Shift+arrows extend, Ctrl/⌘ A selects all, Esc clears. Feed its
|
|
53
|
+
* `ordered` (display order) or `picked` (selection order) to the bar:
|
|
54
|
+
*
|
|
55
|
+
* const sel = createClickSelection({ ids: () => rows.map((r) => r.id) });
|
|
56
|
+
*
|
|
57
|
+
* {#each rows as r (r.id)}
|
|
58
|
+
* <button class:selected={sel.has(r.id)} onclick={(e) => sel.click(r.id, e)}>…</button>
|
|
59
|
+
* {/each}
|
|
60
|
+
* <svelte:window onkeydown={(e) => sel.handleKeydown(e, { next: neighbour })} />
|
|
61
|
+
*
|
|
62
|
+
* <BulkActionBar
|
|
63
|
+
* selection={sel.ordered}
|
|
64
|
+
* actions={[{ label: 'Delete', onSelect: () => del(sel.ordered), destructive: true }]}
|
|
65
|
+
* onClear={() => sel.clear()}
|
|
66
|
+
* />
|
|
67
|
+
*
|
|
68
|
+
* listNav is the keyboard-only alternative (x / V / Shift+J/K). Pick ONE
|
|
69
|
+
* selection owner per surface; see createClickSelection for combining them.
|
|
70
|
+
*
|
|
50
71
|
* Usage — grouped:
|
|
51
72
|
*
|
|
52
73
|
* <BulkActionBar
|
|
@@ -93,7 +114,9 @@
|
|
|
93
114
|
label: string;
|
|
94
115
|
/** Optional SVG path string for a 24×24 icon on the group button. */
|
|
95
116
|
icon?: string;
|
|
96
|
-
/** Actions rendered inside the dropdown
|
|
117
|
+
/** Actions rendered inside the dropdown; a nested action's `key` shows as a
|
|
118
|
+
* hint at the item's right edge but is NOT bound (bind it in the host).
|
|
119
|
+
* `destructive` on a nested action
|
|
97
120
|
* currently only affects semantics for readers — the dropdown item row
|
|
98
121
|
* is styled uniformly. */
|
|
99
122
|
actions: BulkAction[];
|
|
@@ -120,6 +143,8 @@
|
|
|
120
143
|
loadingMessage?: string | null;
|
|
121
144
|
/** Label for the count. Default "selected". */
|
|
122
145
|
countLabel?: string;
|
|
146
|
+
/** 'floating' (default, fixed bottom-centre) or 'inline' (in the host's own bar). */
|
|
147
|
+
placement?: 'floating' | 'inline';
|
|
123
148
|
}
|
|
124
149
|
|
|
125
150
|
let {
|
|
@@ -130,6 +155,7 @@
|
|
|
130
155
|
loading = false,
|
|
131
156
|
loadingMessage = null,
|
|
132
157
|
countLabel = 'selected',
|
|
158
|
+
placement = 'floating',
|
|
133
159
|
}: Props = $props();
|
|
134
160
|
|
|
135
161
|
const count = $derived(
|
|
@@ -179,6 +205,9 @@
|
|
|
179
205
|
items: g.actions.map((a) => ({
|
|
180
206
|
label: a.label,
|
|
181
207
|
onclick: a.onSelect,
|
|
208
|
+
// Inside a group a key is a hint only: the host binds it (BulkActionBar
|
|
209
|
+
// registers hotkeys for flat actions alone).
|
|
210
|
+
hint: a.key,
|
|
182
211
|
})),
|
|
183
212
|
})),
|
|
184
213
|
);
|
|
@@ -192,4 +221,5 @@
|
|
|
192
221
|
{loadingMessage}
|
|
193
222
|
{onClear}
|
|
194
223
|
{countLabel}
|
|
224
|
+
{placement}
|
|
195
225
|
/>
|
|
@@ -27,7 +27,9 @@ export interface BulkActionGroup {
|
|
|
27
27
|
label: string;
|
|
28
28
|
/** Optional SVG path string for a 24×24 icon on the group button. */
|
|
29
29
|
icon?: string;
|
|
30
|
-
/** Actions rendered inside the dropdown
|
|
30
|
+
/** Actions rendered inside the dropdown; a nested action's `key` shows as a
|
|
31
|
+
* hint at the item's right edge but is NOT bound (bind it in the host).
|
|
32
|
+
* `destructive` on a nested action
|
|
31
33
|
* currently only affects semantics for readers — the dropdown item row
|
|
32
34
|
* is styled uniformly. */
|
|
33
35
|
actions: BulkAction[];
|
|
@@ -53,6 +55,8 @@ interface Props {
|
|
|
53
55
|
loadingMessage?: string | null;
|
|
54
56
|
/** Label for the count. Default "selected". */
|
|
55
57
|
countLabel?: string;
|
|
58
|
+
/** 'floating' (default, fixed bottom-centre) or 'inline' (in the host's own bar). */
|
|
59
|
+
placement?: 'floating' | 'inline';
|
|
56
60
|
}
|
|
57
61
|
declare const BulkActionBar: import("svelte").Component<Props, {}, "">;
|
|
58
62
|
type BulkActionBar = ReturnType<typeof BulkActionBar>;
|
|
@@ -20,6 +20,22 @@
|
|
|
20
20
|
* Per-action `disabled` renders the button dimmed and non-interactive;
|
|
21
21
|
* per-action `loadingText` disables it and swaps the label for a
|
|
22
22
|
* spinner + that text (mardukos fork) while other actions stay active.
|
|
23
|
+
*
|
|
24
|
+
* `placement="inline"` puts the bar in the normal flow at its container's
|
|
25
|
+
* full width (for a host with its own sticky bar); the default floats
|
|
26
|
+
* bottom-centre. Either way it wraps rather than run off a narrow screen.
|
|
27
|
+
*
|
|
28
|
+
* The root class is `fab-bar`, not `fab`: daisyUI 5 ships a `.fab`
|
|
29
|
+
* (speed-dial) component whose rules hid every button of this bar in a
|
|
30
|
+
* daisyUI 5 host (qimg-x9ifp.1).
|
|
31
|
+
*
|
|
32
|
+
* Theming: every colour is a custom property with the original dark value
|
|
33
|
+
* as its fallback, so a host can map the bar onto its theme without
|
|
34
|
+
* overriding internals: --fab-bg, --fab-fg, --fab-muted, --fab-border,
|
|
35
|
+
* --fab-shadow, --fab-radius, --fab-btn-bg/-fg/-border/-hover/-radius,
|
|
36
|
+
* --fab-primary-bg/-fg/-border/-hover, --fab-danger-bg/-fg/-border/-hover,
|
|
37
|
+
* --fab-menu-bg/-radius/-shadow, --fab-focus, --fab-spinner. Set them on
|
|
38
|
+
* any ancestor (the bar inherits them).
|
|
23
39
|
*/
|
|
24
40
|
|
|
25
41
|
import { fade } from 'svelte/transition';
|
|
@@ -49,6 +65,8 @@
|
|
|
49
65
|
label: string;
|
|
50
66
|
/** Optional colored dot (oklch string) */
|
|
51
67
|
dotColor?: string;
|
|
68
|
+
/** Optional key hint shown at the item's right edge (display only; the host binds it). */
|
|
69
|
+
hint?: string;
|
|
52
70
|
/** Optional SVG snippet rendered before the label */
|
|
53
71
|
iconSnippet?: import('svelte').Snippet;
|
|
54
72
|
/** Called when this item is clicked */
|
|
@@ -86,6 +104,13 @@
|
|
|
86
104
|
onClear?: () => void;
|
|
87
105
|
/** Label for the selection count (default: "selected") */
|
|
88
106
|
countLabel?: string;
|
|
107
|
+
/**
|
|
108
|
+
* 'floating' (default): fixed, bottom-centre over the page.
|
|
109
|
+
* 'inline': in the normal flow, full width of its container — for a
|
|
110
|
+
* host that already has a sticky bar to put it in. Both wrap onto more
|
|
111
|
+
* lines rather than run off a narrow screen.
|
|
112
|
+
*/
|
|
113
|
+
placement?: 'floating' | 'inline';
|
|
89
114
|
}
|
|
90
115
|
|
|
91
116
|
let {
|
|
@@ -96,6 +121,7 @@
|
|
|
96
121
|
loadingMessage = null,
|
|
97
122
|
onClear,
|
|
98
123
|
countLabel = 'selected',
|
|
124
|
+
placement = 'floating',
|
|
99
125
|
}: Props = $props();
|
|
100
126
|
|
|
101
127
|
let openDropdownId = $state<string | null>(null);
|
|
@@ -129,7 +155,7 @@
|
|
|
129
155
|
</script>
|
|
130
156
|
|
|
131
157
|
{#if count > 0}
|
|
132
|
-
<div class="fab" transition:fade={{ duration: 150 }}>
|
|
158
|
+
<div class="fab-bar" class:fab-inline={placement === 'inline'} role="group" aria-label="{count} {countLabel}" transition:fade={{ duration: 150 }}>
|
|
133
159
|
<span class="fab-count">{count} {countLabel}</span>
|
|
134
160
|
|
|
135
161
|
{#if loadingMessage}
|
|
@@ -176,13 +202,22 @@
|
|
|
176
202
|
class="fab-dropdown-wrapper"
|
|
177
203
|
role="group"
|
|
178
204
|
onclick={(e) => e.stopPropagation()}
|
|
179
|
-
onkeydown={(e) =>
|
|
205
|
+
onkeydown={(e) => {
|
|
206
|
+
e.stopPropagation();
|
|
207
|
+
if (e.key === 'Escape' && openDropdownId === dropdown.id) {
|
|
208
|
+
e.preventDefault();
|
|
209
|
+
openDropdownId = null;
|
|
210
|
+
(e.currentTarget.querySelector('.fab-btn') as HTMLElement | null)?.focus();
|
|
211
|
+
}
|
|
212
|
+
}}
|
|
180
213
|
>
|
|
181
214
|
<button
|
|
182
215
|
type="button"
|
|
183
216
|
class="fab-btn {variantClass(dropdown.variant)}"
|
|
184
217
|
onclick={() => openDropdownId = openDropdownId === dropdown.id ? null : dropdown.id}
|
|
185
218
|
disabled={loading}
|
|
219
|
+
aria-haspopup="true"
|
|
220
|
+
aria-expanded={openDropdownId === dropdown.id}
|
|
186
221
|
>
|
|
187
222
|
{#if dropdown.icon}
|
|
188
223
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14">
|
|
@@ -205,7 +240,10 @@
|
|
|
205
240
|
{#if item.iconSnippet}
|
|
206
241
|
{@render item.iconSnippet()}
|
|
207
242
|
{/if}
|
|
208
|
-
{item.label}
|
|
243
|
+
<span class="fab-item-label">{item.label}</span>
|
|
244
|
+
{#if item.hint}
|
|
245
|
+
<kbd class="fab-key fab-item-hint">{item.hint}</kbd>
|
|
246
|
+
{/if}
|
|
209
247
|
</button>
|
|
210
248
|
{/each}
|
|
211
249
|
</div>
|
|
@@ -229,8 +267,11 @@
|
|
|
229
267
|
{/if}
|
|
230
268
|
|
|
231
269
|
<style>
|
|
232
|
-
.fab {
|
|
270
|
+
.fab-bar {
|
|
233
271
|
position: fixed;
|
|
272
|
+
max-width: calc(100vw - 1rem);
|
|
273
|
+
flex-wrap: wrap;
|
|
274
|
+
justify-content: center;
|
|
234
275
|
bottom: 1.5rem;
|
|
235
276
|
left: 50%;
|
|
236
277
|
transform: translateX(-50%);
|
|
@@ -239,23 +280,35 @@
|
|
|
239
280
|
align-items: center;
|
|
240
281
|
gap: 0.5rem;
|
|
241
282
|
padding: 0.5rem 0.75rem;
|
|
242
|
-
background: oklch(0.16 0.02 250);
|
|
243
|
-
border: 1px solid oklch(0.30 0.02 250);
|
|
244
|
-
border-radius: 0.75rem;
|
|
245
|
-
box-shadow: 0 8px 32px oklch(0.05 0 0 / 0.6), 0 2px 8px oklch(0.05 0 0 / 0.3);
|
|
283
|
+
background: var(--fab-bg, oklch(0.16 0.02 250));
|
|
284
|
+
border: 1px solid var(--fab-border, oklch(0.30 0.02 250));
|
|
285
|
+
border-radius: var(--fab-radius, 0.75rem);
|
|
286
|
+
box-shadow: var(--fab-shadow, 0 8px 32px oklch(0.05 0 0 / 0.6), 0 2px 8px oklch(0.05 0 0 / 0.3));
|
|
287
|
+
color: var(--fab-fg, oklch(0.85 0.02 250));
|
|
288
|
+
}
|
|
289
|
+
/* Inline: in the host's own bar, so no float, no shadow, its full width. */
|
|
290
|
+
.fab-bar.fab-inline {
|
|
291
|
+
position: static;
|
|
292
|
+
left: auto;
|
|
293
|
+
bottom: auto;
|
|
294
|
+
transform: none;
|
|
295
|
+
max-width: none;
|
|
296
|
+
width: 100%;
|
|
297
|
+
justify-content: flex-start;
|
|
298
|
+
box-shadow: var(--fab-shadow-inline, none);
|
|
246
299
|
}
|
|
247
300
|
|
|
248
301
|
.fab-count {
|
|
249
302
|
font-size: 0.8125rem;
|
|
250
303
|
font-weight: 600;
|
|
251
|
-
color: oklch(0.85 0.02 250);
|
|
304
|
+
color: var(--fab-fg, oklch(0.85 0.02 250));
|
|
252
305
|
white-space: nowrap;
|
|
253
306
|
}
|
|
254
307
|
|
|
255
308
|
.fab-divider {
|
|
256
309
|
width: 1px;
|
|
257
310
|
height: 1.25rem;
|
|
258
|
-
background: oklch(0.30 0.02 250);
|
|
311
|
+
background: var(--fab-border, oklch(0.30 0.02 250));
|
|
259
312
|
}
|
|
260
313
|
|
|
261
314
|
.fab-btn {
|
|
@@ -264,7 +317,7 @@
|
|
|
264
317
|
gap: 0.375rem;
|
|
265
318
|
padding: 0.375rem 0.625rem;
|
|
266
319
|
border: 1px solid transparent;
|
|
267
|
-
border-radius: 0.375rem;
|
|
320
|
+
border-radius: var(--fab-btn-radius, 0.375rem);
|
|
268
321
|
font-size: 0.75rem;
|
|
269
322
|
font-weight: 500;
|
|
270
323
|
cursor: pointer;
|
|
@@ -278,25 +331,25 @@
|
|
|
278
331
|
|
|
279
332
|
/* Variants */
|
|
280
333
|
.fab-btn-default {
|
|
281
|
-
background: oklch(0.25 0.02 250 / 0.5);
|
|
282
|
-
color: oklch(0.75 0.02 250);
|
|
283
|
-
border-color: oklch(0.35 0.02 250);
|
|
334
|
+
background: var(--fab-btn-bg, oklch(0.25 0.02 250 / 0.5));
|
|
335
|
+
color: var(--fab-btn-fg, oklch(0.75 0.02 250));
|
|
336
|
+
border-color: var(--fab-btn-border, oklch(0.35 0.02 250));
|
|
284
337
|
}
|
|
285
|
-
.fab-btn-default:hover { background: oklch(0.30 0.02 250); }
|
|
338
|
+
.fab-btn-default:hover { background: var(--fab-btn-hover, oklch(0.30 0.02 250)); }
|
|
286
339
|
|
|
287
340
|
.fab-btn-primary {
|
|
288
|
-
background: oklch(0.65 0.18 250 / 0.15);
|
|
289
|
-
color: oklch(0.82 0.14 250);
|
|
290
|
-
border-color: oklch(0.65 0.18 250 / 0.3);
|
|
341
|
+
background: var(--fab-primary-bg, oklch(0.65 0.18 250 / 0.15));
|
|
342
|
+
color: var(--fab-primary-fg, oklch(0.82 0.14 250));
|
|
343
|
+
border-color: var(--fab-primary-border, oklch(0.65 0.18 250 / 0.3));
|
|
291
344
|
}
|
|
292
|
-
.fab-btn-primary:hover { background: oklch(0.65 0.18 250 / 0.25); }
|
|
345
|
+
.fab-btn-primary:hover { background: var(--fab-primary-hover, oklch(0.65 0.18 250 / 0.25)); }
|
|
293
346
|
|
|
294
347
|
.fab-btn-danger {
|
|
295
|
-
background: oklch(0.55 0.18 30 / 0.15);
|
|
296
|
-
color: oklch(0.80 0.15 30);
|
|
297
|
-
border-color: oklch(0.55 0.18 30 / 0.3);
|
|
348
|
+
background: var(--fab-danger-bg, oklch(0.55 0.18 30 / 0.15));
|
|
349
|
+
color: var(--fab-danger-fg, oklch(0.80 0.15 30));
|
|
350
|
+
border-color: var(--fab-danger-border, oklch(0.55 0.18 30 / 0.3));
|
|
298
351
|
}
|
|
299
|
-
.fab-btn-danger:hover { background: oklch(0.55 0.18 30 / 0.25); }
|
|
352
|
+
.fab-btn-danger:hover { background: var(--fab-danger-hover, oklch(0.55 0.18 30 / 0.25)); }
|
|
300
353
|
|
|
301
354
|
.fab-btn-warning {
|
|
302
355
|
background: oklch(0.75 0.15 85 / 0.12);
|
|
@@ -328,19 +381,23 @@
|
|
|
328
381
|
|
|
329
382
|
.fab-btn-clear {
|
|
330
383
|
background: transparent;
|
|
331
|
-
color: oklch(0.65 0.02 250);
|
|
384
|
+
color: var(--fab-muted, oklch(0.65 0.02 250));
|
|
332
385
|
border-color: transparent;
|
|
333
386
|
}
|
|
334
387
|
.fab-btn-clear:hover {
|
|
335
|
-
background: oklch(0.25 0.02 250);
|
|
336
|
-
color: oklch(0.80 0.02 250);
|
|
388
|
+
background: var(--fab-btn-hover, oklch(0.25 0.02 250));
|
|
389
|
+
color: var(--fab-fg, oklch(0.80 0.02 250));
|
|
390
|
+
}
|
|
391
|
+
.fab-btn:focus-visible {
|
|
392
|
+
outline: 2px solid var(--fab-focus, currentColor);
|
|
393
|
+
outline-offset: 1px;
|
|
337
394
|
}
|
|
338
395
|
|
|
339
396
|
.fab-spinner {
|
|
340
397
|
width: 14px;
|
|
341
398
|
height: 14px;
|
|
342
|
-
border: 2px solid oklch(0.35 0.02 250);
|
|
343
|
-
border-top-color: oklch(0.70 0.15 240);
|
|
399
|
+
border: 2px solid var(--fab-border, oklch(0.35 0.02 250));
|
|
400
|
+
border-top-color: var(--fab-spinner, oklch(0.70 0.15 240));
|
|
344
401
|
border-radius: 50%;
|
|
345
402
|
animation: fab-spin 0.6s linear infinite;
|
|
346
403
|
flex-shrink: 0;
|
|
@@ -389,13 +446,17 @@
|
|
|
389
446
|
left: 50%;
|
|
390
447
|
transform: translateX(-50%);
|
|
391
448
|
min-width: 160px;
|
|
392
|
-
background: oklch(0.18 0.02 250);
|
|
393
|
-
border: 1px solid oklch(0.30 0.02 250);
|
|
394
|
-
border-radius: 0.5rem;
|
|
449
|
+
background: var(--fab-menu-bg, oklch(0.18 0.02 250));
|
|
450
|
+
border: 1px solid var(--fab-border, oklch(0.30 0.02 250));
|
|
451
|
+
border-radius: var(--fab-menu-radius, 0.5rem);
|
|
395
452
|
padding: 0.375rem;
|
|
396
|
-
box-shadow: 0 8px 24px oklch(0.05 0 0 / 0.5);
|
|
453
|
+
box-shadow: var(--fab-menu-shadow, 0 8px 24px oklch(0.05 0 0 / 0.5));
|
|
397
454
|
z-index: 60;
|
|
398
455
|
}
|
|
456
|
+
.fab-inline .fab-dropdown {
|
|
457
|
+
left: 0;
|
|
458
|
+
transform: none;
|
|
459
|
+
}
|
|
399
460
|
.fab-dropdown-item {
|
|
400
461
|
display: flex;
|
|
401
462
|
align-items: center;
|
|
@@ -404,7 +465,7 @@
|
|
|
404
465
|
padding: 0.375rem 0.625rem;
|
|
405
466
|
border: none;
|
|
406
467
|
background: transparent;
|
|
407
|
-
color: oklch(0.80 0.02 250);
|
|
468
|
+
color: var(--fab-fg, oklch(0.80 0.02 250));
|
|
408
469
|
font-size: 0.75rem;
|
|
409
470
|
text-align: left;
|
|
410
471
|
border-radius: 0.375rem;
|
|
@@ -412,8 +473,15 @@
|
|
|
412
473
|
transition: background 0.1s;
|
|
413
474
|
white-space: nowrap;
|
|
414
475
|
}
|
|
415
|
-
.fab-dropdown-item:hover
|
|
476
|
+
.fab-dropdown-item:hover,
|
|
477
|
+
.fab-dropdown-item:focus-visible { background: var(--fab-btn-hover, oklch(0.25 0.02 250)); outline: none; }
|
|
416
478
|
|
|
479
|
+
.fab-item-label {
|
|
480
|
+
flex: 1;
|
|
481
|
+
}
|
|
482
|
+
.fab-item-hint {
|
|
483
|
+
margin-left: 1rem;
|
|
484
|
+
}
|
|
417
485
|
.fab-dot {
|
|
418
486
|
width: 8px;
|
|
419
487
|
height: 8px;
|
|
@@ -22,6 +22,8 @@ export interface DropdownItem {
|
|
|
22
22
|
label: string;
|
|
23
23
|
/** Optional colored dot (oklch string) */
|
|
24
24
|
dotColor?: string;
|
|
25
|
+
/** Optional key hint shown at the item's right edge (display only; the host binds it). */
|
|
26
|
+
hint?: string;
|
|
25
27
|
/** Optional SVG snippet rendered before the label */
|
|
26
28
|
iconSnippet?: import('svelte').Snippet;
|
|
27
29
|
/** Called when this item is clicked */
|
|
@@ -57,6 +59,13 @@ interface Props {
|
|
|
57
59
|
onClear?: () => void;
|
|
58
60
|
/** Label for the selection count (default: "selected") */
|
|
59
61
|
countLabel?: string;
|
|
62
|
+
/**
|
|
63
|
+
* 'floating' (default): fixed, bottom-centre over the page.
|
|
64
|
+
* 'inline': in the normal flow, full width of its container — for a
|
|
65
|
+
* host that already has a sticky bar to put it in. Both wrap onto more
|
|
66
|
+
* lines rather than run off a narrow screen.
|
|
67
|
+
*/
|
|
68
|
+
placement?: 'floating' | 'inline';
|
|
60
69
|
}
|
|
61
70
|
declare const FloatingActionBar: import("svelte").Component<Props, {}, "">;
|
|
62
71
|
type FloatingActionBar = ReturnType<typeof FloatingActionBar>;
|