fernwell 0.3.0 → 0.5.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/CHANGELOG.md CHANGED
@@ -4,6 +4,55 @@ All notable changes to this project are documented here. The format follows [Kee
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.5.0] — 2026-09-29
8
+
9
+ ### Added
10
+
11
+ - Docs: new **API Usage** page (`docs/api.html`, styles in `docs/styles/api.css`), linked from the docs-wide page nav on the Overview and Components pages. It covers installing via CDN, npm, CSS-only, fonts and page setup; initialising with `init(root?)` (including after injecting markup, manual start with `data-fw-no-init`, and React); building custom components on `--fw-*` tokens with live pricing-card, copy-to-clipboard and scoped-retheme (`theme.setTokens()`) examples; and reference tables for the JavaScript API, `data-fw-*` hooks and `fw:*` events. Version numbers in its code samples are filled in from the published `Fernwell.version` at load time rather than hardcoded.
12
+ - **Tokens module** (`src/js/tokens.ts`, exported as `tokens` / `Fernwell.tokens`): the runtime token-override API now lives in its own module and grows two ways to theme beyond a single page load. `theme.setTokens` / `resetTokens` / `getToken` are unchanged and remain as aliases of `tokens.setTokens` / `resetTokens` / `getToken`.
13
+ - **App-wide persistence.** `setTokens(tokens, { persist: true })` saves that scope to `localStorage` (`fw-tokens`, plus a pre-rendered `fw-tokens-css`) and `init()` re-applies it on every page and reload; `resetTokens()` clears persisted overrides too. Without `persist`, overrides last for the current page load. A one-line inline `<head>` snippet (in the README and `docs/api.html`) applies them before first paint so there's no flash of the default palette. Also exports `restoreTokens()` for re-reading storage by hand.
14
+ - **Page and subtree scoping helpers.** `<script type="application/json" data-fw-theme-tokens>` themes the page from JSON (a flat map or `{ light, dark }`; `data-fw-scope="…"` targets a subtree, `data-fw-persist` makes it app-wide), and `data-fw-theme-scope='{…}'` themes just one element's subtree. `setTokens` now returns a function that undoes just that call — for SPA routes that theme themselves on mount.
15
+ - **`fw:tokenschange`** on `document` (`detail: { scope? }`), and exported `TokenName` / `TokenKey` / `SetTokensOptions` types so token names autocomplete in editors. `TokenOverrides` and `TokenThemeOverrides` are unchanged apart from now being declared in `tokens.ts`.
16
+ - `scripts/gen-tokens.mjs` now also writes `src/js/_tokenData.ts` (the token names and the derived-token formulas the runtime needs), generated from `tokens.json` alongside `tokens.css`.
17
+
18
+ ### Changed
19
+
20
+ - **`setTokens()` now rejects unsafe input with a console warning** instead of writing it into the stylesheet: token values containing `{`, `}`, `;` or `/*` or an unclosed `(` or quote, token names that aren't a plain identifier, and scope selectors the browser can't parse. Bare and full names (`primary` / `--fw-primary`) now merge into one override instead of being kept as two.
21
+
22
+ ### Fixed
23
+
24
+ - **`import 'fernwell/css'` no longer fails type-checking** with "Cannot find module or type declarations for side-effect import". The `./css`, `./css/min`, and `./tokens.css` entries in the `exports` map now carry a `types` condition pointing at a new `dist/css.d.ts` stub, and a matching `typesVersions` map covers projects still on `moduleResolution: "node10"` (which ignores `exports`). The runtime target of each entry is unchanged.
25
+ - **The main `fernwell` entry now type-checks under `moduleResolution: "node16"` / `"nodenext"`.** This covers CommonJS files too: the `require` condition now resolves to CJS-typed `dist/*.d.cts` declarations (generated by `scripts/cjs-types.mjs`) instead of the ESM `.d.ts`, which a `.cts` file rejected with TS1479. `npm run test:types` (vitest, `__tests__/types.test.ts`) type-checks ESM, CJS, `bundler` and `node10` consumers against the built package. The generated `dist/index.d.ts` (and the other declaration files) re-exported components through extensionless relative paths such as `./modal`, which strict ESM resolution rejects with TS2834. Relative imports in `src/js` now use explicit `.js` extensions, so the emitted declarations resolve in every `moduleResolution` mode. No change for `bundler` or `node10` consumers.
26
+ - **Comma-separated `scope` selectors no longer leak dark values into light mode.** For `scope: '.a, .b'` the dark rule was built as `[data-theme="dark"] .a, .b, .a, .b[data-theme="dark"]`, so a bare `.b` got the dark values in every theme. Each list item is now prefixed on its own (commas inside `:is()`, attribute values and strings are left alone).
27
+ - **Declarative token hooks clean up after themselves.** A `data-fw-theme-scope` element or non-persisted `<script data-fw-theme-tokens>` that has left the page is undone on the next `init()`, so re-rendered route markup no longer piles up rules; putting the same node back and calling `init()` re-applies it. `data-fw-persist` hooks are unaffected.
28
+ - **`fw:tokenschange` fires once** for the first `setTokens()` / `resetTokens()` of a page load, not once for loading persisted storage and again for the call.
29
+ - **`version` is typed `string`** in the published declarations; it was emitted as the literal `"__FW_VERSION__"`, so comparing it to a real version string was a type error.
30
+ - **`data-fw-theme-scope` / `data-fw-theme-tokens` no longer stop other enhancers initialising.** They marked elements with the shared `data-fw-bound` flag, so a themed `<table data-fw-table>`, modal or menu was skipped by its own `init()`. They now use a private `data-fw-tokens-bound` marker.
31
+ - **Persisted / pre-paint token overrides now win over your stylesheet, as documented.** The inline `<head>` snippet inserts `<style data-fw-tokens>` ahead of your CSS; `setTokens` now moves it to the end of `<head>` when it first renders.
32
+ - **Scoped token overrides now retint derived tokens too.** `setTokens({ primary }, { scope: '.tenant' })` used to leave `--fw-primary-hover`, `--fw-primary-soft`, `--fw-focus-ring`, the `*-bg` tints and the `--fw-shadow-primary` / `-success` glows on the root palette, because derived tokens are declared on `:root` and `var()` resolves where it's declared. `setTokens` now re-declares every derived token that reads an overridden seed at the scope, in both themes. A hand-written `.tenant { --fw-primary: … }` in your own stylesheet still behaves as before — redeclare the derived tokens you need there, or use `setTokens` with a `scope`.
33
+
34
+ ## [0.4.0] — 2026-09-16
35
+
36
+ ### Changed (breaking)
37
+
38
+ - **`.fw-modal` markup requires a new `.fw-modal-scroll` wrapper around its head/body/foot.** `.fw-modal` itself now only holds the rounded shape (radius, shadow, clip); `.fw-modal-scroll` is the actual scrolling region (`max-height`, `overflow-y: auto`, padding). Splitting them keeps the custom scrollbar clipped to the panel's rounded corners instead of cutting through them — `overflow` on an element doesn't clip that same element's own scrollbar decoration.
39
+ - **`.fw-table-wrap` markup requires a new `.fw-table-scroll` wrapper around the `<table>`.** `.fw-table-wrap` now only holds the rounded card shape (border, radius, shadow, clip); `.fw-table-scroll` is the scrolling region and carries what used to live on `.fw-table-wrap` — `tabindex="0"`, `role="region"`, the horizontal `overflow-x: auto`, and (with `.fw-table-sticky`) `--fw-table-max-height`. Same clipping rationale as the Modal change above.
40
+
41
+ ### Added
42
+
43
+ - **Icon component** (`src/css/components/icon.css`, `src/js/icon.ts`): a built-in set of ~45 SVG icons rendered into `[data-fw-icon="name"]` elements (`<span class="fw-icon" data-fw-icon="trash" aria-hidden="true">`), sized in `em` and colored via inherited `color` so an icon always matches the surrounding text. Decorative by default — give the accessible name to a wrapping interactive element (e.g. `aria-label` on a button). Exports `render(name)` and the `IconName` type; docs and other components (nav toggle, theme toggle, table actions, popover close button) now use it in place of emoji.
44
+ - **Popover component** (`src/css/components/popover.css`, `src/js/popover.ts`): a non-modal floating panel anchored to a trigger via `data-fw-popover-trigger="id"`, with `-placement` (`top`/`bottom`/`left`/`right` or a corner variant) and `-offset` options. Flips/shifts to stay in the viewport, closes on Escape, outside-click, focus-out, or `[data-fw-popover-close]`, and returns focus to the trigger. Renders `aria-modal="false"` per WAI-ARIA APG, so it does not trap Tab. Moves to the end of `<body>` while open so its `position: fixed` isn't broken by an ancestor with `transform`/`filter`/`contain`. Emits `fw:open` / `fw:close`. Exports `popover()`, `PopoverOptions`, `PopoverInstance`.
45
+ - **Tooltip component** (`src/css/components/tooltip.css`, `src/js/tooltip.ts`): a short hover/focus annotation sourced from the trigger's `title` attribute (`data-fw-tooltip`), with `-placement`, `-offset`, and `-delay` options. A single shared bubble instance (like `toast.ts`) is hoverable and dismissible per WCAG 2.1 SC 1.4.13, and closes on Escape. Exports `tooltip()`, `TooltipOptions`, `TooltipInstance`.
46
+ - **Tabs component** (`src/css/components/tabs.css`, `src/js/tabs.ts`): a single-selection `role="tablist"` that switches `role="tabpanel"` visibility, authored via `aria-selected` and wired up by `data-fw-tabs`. Arrow-key, Home/End navigation with roving `tabindex`; `aria-orientation="vertical"` swaps to Up/Down; `data-fw-tabs-activation="manual"` switches from automatic (arrow keys select) to manual (arrow keys move focus, Space/Enter selects) activation. A tab may carry `aria-disabled="true"` to be skipped by navigation. Emits `fw:tabs-change`. Exports `TabsOptions`, `TabsInstance`, `TabsActivation`, `TabsChangeDetail`.
47
+ - **Divider component** (`src/css/components/divider.css`): `.fw-divider` (default `<hr>`), horizontal or vertical via `aria-orientation="vertical"` (both the semantic and visual trigger — no separate modifier class), `-dashed` / `-dotted` style variants, and a labeled variant (`.fw-divider-labeled`, with `-start` / `-end` alignment) for "── text ──" section breaks.
48
+ - **Spacer component** (`src/css/components/spacer.css`): `.fw-spacer`, a fixed-size empty box for manual gap control where `gap` on `.fw-row`/`.fw-grid-2` isn't applicable. `data-fw-axis` (`horizontal` / `both`, default vertical) and `data-fw-size` (`1`–`9`, or `auto` for a flex filler that absorbs remaining space) control direction and size via `--fw-sp-*` tokens.
49
+ - **Aspect-ratio component** (`src/css/components/aspect-ratio.css`): `.fw-aspect-ratio` crops a wrapped `<img>`/`<video>`/`<iframe>` (or `.fw-aspect-ratio-media`) to a fixed ratio via `object-fit: cover`. `data-fw-ratio` presets (`1:1`, `4:3`, `16:9`, `21:9`) or an inline `--fw-aspect-ratio` for arbitrary ratios; `data-fw-fit="contain"` switches the fit mode.
50
+ - **Token-colored custom scrollbars** (`src/css/components/scrollbar.css`): `.fw-root` and Fernwell's own internal scroll regions (`.fw-modal-scroll`, `.fw-drawer-body`, `.fw-table-scroll`) now render a thin, theme-aware scrollbar (`scrollbar-width`/`scrollbar-color`, with a `::-webkit-scrollbar-*` fallback for older WebKit/Chromium) instead of the browser default. New tokens: `--fw-scrollbar-thumb`, `--fw-scrollbar-thumb-hover`, `--fw-scrollbar-track` (Table overrides the track to `--fw-bg-dim` so its scrollbar reads as clearly present, since tabular data needs an obvious "more to scroll" cue). `.fw-tablist` also gets a static two-edge fade mask on its scroll axis as a discoverability cue for overflow tabs.
51
+ - Theme toggle (`theme.ts`) now supports a real icon instead of an emoji via `data-fw-theme-icon-dark` / `data-fw-theme-icon-light` (icon names from the new icon component), optionally alongside a `-label-` pair which is appended as text after the icon.
52
+ - `theme.init()` no longer animates the theme it applies on page load — only a later toggle or live OS change animates — so a themed page never flashes a cross-fade of its own initial colors.
53
+ - Docs: new **Popover**, **Tooltip**, and **Tabs** sections with live demos on the Components page; icon usage throughout the docs replaces the previous emoji placeholders.
54
+ - Dev workflow: `npm run dev` now runs `scripts/dev.mjs`, a hot-reloading dev server (previously a one-shot `build && docs`), watching source files and rebuilding/reloading automatically.
55
+
7
56
  ## [0.3.0] — 2026-09-15
8
57
 
9
58
  ### Added
package/README.md CHANGED
@@ -69,7 +69,7 @@ Set `data-theme="dark"` on `<html>` (or any ancestor) and every token flips. A `
69
69
  data-fw-theme-label-light="🌙 Dark" data-fw-theme-label-dark="☀️ Light">🌙 Dark</button>
70
70
  ```
71
71
 
72
- To avoid a flash of the wrong theme, inline this in `<head>` before the stylesheet:
72
+ `init()` never animates the theme it applies on load (toggling and live OS changes still do), but to also avoid a flash of the *wrong* theme's colours before that script runs at all, inline this in `<head>` before the stylesheet:
73
73
 
74
74
  ```html
75
75
  <script>document.documentElement.dataset.theme=localStorage.getItem('fw-theme')||(matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light')</script>
@@ -140,19 +140,57 @@ Override any **seed** token and every derived token follows automatically, in bo
140
140
 
141
141
  This works because Fernwell's own token declarations live in `@layer fw-tokens` — an unlayered rule (yours, unless you also use `@layer`) always beats a layered one, regardless of selector specificity or source order. If your app declares its own CSS layers, list them (e.g. `@layer reset, base, components;`) before importing Fernwell's CSS so `fw-tokens` isn't implicitly nested inside one of them.
142
142
 
143
+ Override seeds on `:root` (or `[data-theme="dark"]`) and derived tokens follow. On any *other* selector, `var()` still resolves where the derived token is declared (`:root`), so a hand-written `.tenant { --fw-primary: … }` retints `--fw-primary` only — use `tokens.setTokens(…, { scope })` below, which re-declares the derived tokens at the scope for you.
144
+
143
145
  **JS — runtime.** For per-tenant branding, a user-picked accent, or a live preview:
144
146
 
145
147
  ```js
146
- import { theme } from 'fernwell';
148
+ import { tokens } from 'fernwell';
149
+
150
+ tokens.setTokens({ primary: '#2266ff' }); // both themes
151
+ tokens.setTokens({ light: { primary: '#2266ff' }, dark: { primary: '#5c9dff' } }); // per theme
152
+ tokens.setTokens({ secondary: '#a33' }, { scope: '.tenant-acme' }); // one subtree
153
+ tokens.getToken('primary'); // live resolved value, e.g. '#2266ff'
154
+ tokens.resetTokens(); // clear every runtime override
155
+ ```
156
+
157
+ `theme.setTokens` / `resetTokens` / `getToken` are the same functions, kept as aliases so existing code keeps working.
158
+
159
+ Runtime overrides render into one `<style data-fw-tokens>` appended to `<head>` and win over both the `@layer fw-tokens` defaults and any static override, so `setTokens` always has the last word. Derived tokens (hovers, tints, the focus ring, CTA glows) follow the seed they're built from — including inside a `scope`, where Fernwell re-declares them at that selector so they don't stay stuck on the root value. Values that could break out of a declaration (`{`, `}`, `;`, `/*`) and invalid selectors are rejected with a console warning.
160
+
161
+ **App-wide — persist it.** Pass `persist: true` and the scope is saved to `localStorage`, then applied on every page and reload by `init()`:
147
162
 
148
- theme.setTokens({ primary: '#2266ff' }); // both themes
149
- theme.setTokens({ light: { primary: '#2266ff' }, dark: { primary: '#5c9dff' } }); // per theme
150
- theme.setTokens({ secondary: '#a33' }, { scope: '.tenant-acme' }); // one subtree
151
- theme.getToken('primary'); // live resolved value, e.g. '#2266ff'
152
- theme.resetTokens(); // clear every runtime override
163
+ ```js
164
+ tokens.setTokens({ primary: '#2266ff' }, { persist: true });
165
+ ```
166
+
167
+ Without `persist`, an override lasts for the current page load — that's the "this page only" case in a multi-page app. Page-level overrides render after persisted ones, so a page can still tweak an app-wide value. `resetTokens()` also clears persisted overrides. To apply them before first paint (no flash of the default palette), inline this in `<head>` ahead of your stylesheet, next to the theme snippet above:
168
+
169
+ ```html
170
+ <script>try{var c=localStorage.getItem('fw-tokens-css');if(c){var s=document.createElement('style');s.setAttribute('data-fw-tokens','');s.textContent=c;document.head.appendChild(s)}}catch(e){}</script>
171
+ ```
172
+
173
+ **This page or section only — declaratively.** `init()` also reads two hooks, so you can theme without writing JS:
174
+
175
+ ```html
176
+ <!-- this page (add data-fw-persist to make it app-wide; data-fw-scope="…" to theme a subtree) -->
177
+ <script type="application/json" data-fw-theme-tokens>{ "primary": "#2266ff" }</script>
178
+
179
+ <!-- just this element's subtree; never persisted -->
180
+ <section data-fw-theme-scope='{ "secondary": "#a33" }'>…</section>
181
+ ```
182
+
183
+ The `<script>` form takes the same shapes as `setTokens` — a flat map or `{ "light": {…}, "dark": {…} }`.
184
+
185
+ **Undoing a page's theme (SPAs).** `setTokens` returns a function that reverts just that call — call it when a route unmounts:
186
+
187
+ ```js
188
+ const undo = tokens.setTokens({ primary: '#a33' }, { scope: '#checkout' });
189
+ // …later
190
+ undo();
153
191
  ```
154
192
 
155
- Runtime overrides render into one `<style data-fw-tokens>` appended to `<head>` and win over both the `@layer fw-tokens` defaults and any static override, so `setTokens` always has the last word.
193
+ A light-only scoped override (`{ light: { … } }` with no `dark`) also applies in the dark theme — give a `dark` value if it should differ. Removing a `data-fw-theme-*` element doesn't clear its overrides; use `undo()` or `resetTokens(scope)`. Listen for `fw:tokenschange` on `document` (`detail.scope`) to react to any change.
156
194
 
157
195
  **Browser support.** `color-mix()` and `@layer` need Chrome 111+, Safari 16.2+, Firefox 113+. There's no polyfill shipped — on an older browser the seed tokens still apply, but derived tokens fall back to whatever the browser does with an unsupported `color-mix()` value (typically the property's initial value), so hovers/tints/focus rings may go missing rather than mis-colour.
158
196
 
@@ -175,13 +213,15 @@ All class names are prefixed `fw-`; state classes are `is-*`. Each stylesheet in
175
213
  | Nav | `.fw-nav` (`-sticky`), `-brand`, `-dot` / `-hex`, `-links`, `-right`, `-avatar`, `-btn`, `-toggle`, `-dropdown` (`-menu`) |
176
214
  | Stat | `.fw-stat-label` `-value` (`-unit`) `-delta` (`.is-up` / `.is-down`) |
177
215
  | Empty state | `.fw-empty`, `-blob`, `-title`, `-body` |
178
- | Modal | `.fw-modal-overlay` > `.fw-modal` (`-wide`); `-head`, `-title`, `-sub`, `-foot` |
216
+ | Modal | `.fw-modal-overlay` > `.fw-modal` (`-wide`) > `.fw-modal-scroll` (the scrolling region — keeps the scrollbar clipped to the panel's rounded corners) > `-head`, `-title`, `-sub`, `-foot` |
179
217
  | Drawer | `.fw-drawer-overlay` + `.fw-drawer`; `-head`, `-eyebrow`, `-title`, `-body`, `-foot` |
218
+ | Popover | `.fw-popover` (`role="dialog"`, non-modal); `-head`, `-title`, `-body` |
219
+ | Tooltip | `.fw-tooltip` (`role="tooltip"`, singleton, created on first show) |
180
220
  | Toast | `.fw-toast` + `-error` `-info` `-pending` (created by `toast.show`) |
181
221
  | List row | `.fw-list-row` (`.is-locked`), `-main`, `-title`, `-meta`; `.fw-sub-list` |
182
222
  | Disclosure | `details.fw-disclosure`, `-chevron`, `-count`, `-body` |
183
223
  | Pagination | `.fw-pagination`, `.fw-pag-info`, `.fw-pag-controls`, `.fw-pag-label`, `.fw-btn-page` |
184
- | Table | `.fw-table-wrap` (`.fw-table-sticky` + `--fw-table-max-height`) > `.fw-table` (`-striped`, `-compact`); cells `.fw-table-num`, `.fw-table-actions`, `.fw-table-select`; `th[aria-sort] > .fw-table-sort`; rows `.fw-table-empty`, `.fw-table-loading`, `.is-selected`; `[aria-busy="true"]` dims the body |
224
+ | Table | `.fw-table-wrap` (`.fw-table-sticky`) > `.fw-table-scroll` (the scrolling region — carries `tabindex="0" role="region"`, bounded by `--fw-table-max-height` when sticky, kept separate from `.fw-table-wrap` so the scrollbar stays clipped to the card's rounded corners) > `.fw-table` (`-striped`, `-compact`); cells `.fw-table-num`, `.fw-table-actions`, `.fw-table-select`; `th[aria-sort] > .fw-table-sort`; rows `.fw-table-empty`, `.fw-table-loading`, `.is-selected`; `[aria-busy="true"]` dims the body |
185
225
  | Collapsible menu | `.fw-menu` (`-accordion`) > `.fw-menu-list` > `.fw-menu-item` (`.fw-menu-split`) > `.fw-menu-link` or `details.fw-menu-group` > `.fw-menu-summary` (`-icon`, `.is-current`) + nested `.fw-menu-list` or `.fw-menu-panel`; `[aria-current="page"]` marks the current link; `--fw-menu-indent`, `--fw-menu-row-h` |
186
226
  | Layout | `.fw-wrap`, `.fw-row`, `.fw-grid-2`, `.fw-sr-only` |
187
227
 
@@ -195,6 +235,8 @@ Everything is progressive enhancement over plain HTML. `init(root?)` is idempote
195
235
  | `input[data-fw-combobox]` | typeahead over the `<datalist>` in `list=` (↑ ↓ Enter Esc, `aria-activedescendant`) |
196
236
  | `[data-fw-open-modal="id"]` / `[data-fw-close-modal]` | modal with backdrop-click (drag-safe), Escape, focus return, scroll lock |
197
237
  | `[data-fw-open-drawer="id"]` / `[data-fw-close-drawer]` / `[data-fw-drawer-overlay="id"]` | same for drawers |
238
+ | `[data-fw-popover-trigger="id"]` (+ `-placement`, `-offset`) / `[data-fw-popover-close]` | non-modal floating panel: flip/shift to stay in the viewport, Escape, outside-click, focus-out, and close-button dismissal, focus return to trigger |
239
+ | `[data-fw-tooltip]` (+ `-placement`, `-offset`, `-delay`) | hover (with delay) or focus (instant) tooltip sourced from the trigger's `title`; hoverable per WCAG 1.4.13, dismisses on Escape |
198
240
  | `[data-fw-nav-toggle][aria-controls]` | dropdown open/close, outside-click and Escape |
199
241
  | `form[data-fw-loading]` | submit button gets a spinner + `data-fw-loading-text` label on submit |
200
242
  | `table[data-fw-table]` with `th[data-fw-sort]`, `input[data-fw-select-all]` / `[data-fw-select-row]` | client-side column sort (cycles `aria-sort`, honours `td[data-fw-sort-value]`), row selection with select-all + indeterminate, `.is-selected` / `aria-selected` on rows |
@@ -203,12 +245,12 @@ Everything is progressive enhancement over plain HTML. `init(root?)` is idempote
203
245
  Programmatic API:
204
246
 
205
247
  ```js
206
- import { theme, combobox, modal, nav, loading, toast, table, menu } from 'fernwell';
248
+ import { theme, combobox, modal, nav, loading, toast, table, menu, popover, tooltip } from 'fernwell';
207
249
 
208
250
  theme.get();
209
251
  theme.set('dark');
210
252
  theme.toggle();
211
- theme.setTokens({ primary: '#2266ff' }); theme.resetTokens(); theme.getToken('primary'); // see Theming
253
+ tokens.setTokens({ primary: '#2266ff' }); tokens.resetTokens(); tokens.getToken('primary'); // see Theming
212
254
 
213
255
  const cb = combobox(inputEl,
214
256
  {
@@ -229,9 +271,13 @@ table.selectAll(tableEl, false);
229
271
  menu.open(groupEl); menu.close(groupEl); menu.toggle(groupEl); // groupEl = details.fw-menu-group
230
272
  menu.reveal(linkEl); // open every ancestor group of an element
231
273
  menu.expandAll(menuEl); menu.collapseAll(menuEl);
274
+ const pop = popover(triggerEl, panelEl, { placement: 'bottom', offset: 8 });
275
+ pop.open(); pop.close(); pop.toggle(); pop.destroy();
276
+ const tip = tooltip(triggerEl, { placement: 'top', delay: 400 });
277
+ tip.show(); tip.hide(); tip.destroy();
232
278
  ```
233
279
 
234
- Events: `fw:themechange` on `document` (detail: `'light' | 'dark'`), `fw:open` / `fw:close` bubbling from the modal or drawer, `fw:sort` (detail: `{ column, direction, th }`) and `fw:select` (detail: `{ rows, all }`) bubbling from the table, `fw:toggle` (detail: `{ group, open }`) bubbling from a `.fw-menu-group`.
280
+ Events: `fw:themechange` on `document` (detail: `'light' | 'dark'`), `fw:tokenschange` on `document` (detail: `{ scope? }`), `fw:open` / `fw:close` bubbling from the modal, drawer, or popover, `fw:sort` (detail: `{ column, direction, th }`) and `fw:select` (detail: `{ rows, all }`) bubbling from the table, `fw:toggle` (detail: `{ group, open }`) bubbling from a `.fw-menu-group`.
235
281
 
236
282
  ## Principles
237
283
 
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Internal positioning helpers shared by popover.ts and tooltip.ts. Not part
3
+ * of the public API — not re-exported from index.ts.
4
+ *
5
+ * Both callers use `position: fixed`, so all math here is viewport-relative
6
+ * (matching `getBoundingClientRect()`) rather than document-relative.
7
+ */
8
+ export type Side = 'top' | 'bottom' | 'left' | 'right';
9
+ /**
10
+ * `top` / `bottom` / `left` / `right` center the floating element on the
11
+ * anchor's cross-axis midpoint. `top-left` / `top-right` / `bottom-left` /
12
+ * `bottom-right` keep the anchor above/below but shift the floating element
13
+ * so it leans toward that corner instead of centering — useful to steer a
14
+ * panel away from something else on the page (another fixed control, a
15
+ * viewport edge) without losing the "above/below the trigger" placement.
16
+ *
17
+ * "Lean right" means the floating element's LEFT edge lines up with the
18
+ * anchor's left edge, so the (usually wider) box extends further right of
19
+ * the anchor than a centered box would; "lean left" mirrors that off the
20
+ * anchor's right edge.
21
+ */
22
+ export type Placement = Side | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
23
+ export interface PositionOptions {
24
+ /** Preferred placement. */
25
+ placement?: Placement;
26
+ /** Gap between anchor and floating element, px. */
27
+ offset?: number;
28
+ /** Minimum distance from the viewport edge, px. */
29
+ padding?: number;
30
+ }
31
+ export interface PositionResult {
32
+ /** Final placement, after a possible flip. */
33
+ placement: Placement;
34
+ top: number;
35
+ left: number;
36
+ /**
37
+ * Distance, in px, from the floating element's near edge (left, for a
38
+ * top/bottom placement; top, for a left/right placement) to the anchor's
39
+ * center — for positioning an optional arrow so it keeps pointing at the
40
+ * anchor even after the cross-axis got clamped to stay in the viewport.
41
+ * Already inset from the floating element's own edges so the arrow never
42
+ * overhangs a rounded corner.
43
+ */
44
+ arrowOffset: number;
45
+ }
46
+ /**
47
+ * Positions `floating` relative to `anchor`: flips to the opposite side when
48
+ * the preferred side doesn't fit and the opposite side has more room (a
49
+ * 2-entry fallback, not a 4-way rotation — both callers are single-edge
50
+ * anchored, unlike a corner-anchored menu), and clamps the cross-axis into
51
+ * the viewport ("shift") rather than flipping it.
52
+ *
53
+ * The cross-axis is centered on the anchor by default (`align: 'center'`,
54
+ * implied by a bare `top`/`bottom`/`left`/`right` placement) — the corner
55
+ * placements (`top-left`, etc.) opt into edge-alignment instead. Centering
56
+ * is what makes an arrow drawn at the floating element's own midpoint line
57
+ * up with the anchor; `arrowOffset` carries the exact anchor-relative
58
+ * position so the arrow still lines up even when a corner placement or a
59
+ * viewport clamp shifts the box off-center.
60
+ */
61
+ export declare function computePosition(anchor: HTMLElement, floating: HTMLElement, opts?: PositionOptions): PositionResult;
62
+ /**
63
+ * Fires `onDismiss` only when a click lands outside every element in
64
+ * `anchors` — generalizes modal.ts's `bindOutsideClickToClose` (which checks
65
+ * a single backdrop) to an arbitrary set of "not outside" elements (e.g. a
66
+ * popover's trigger AND its panel).
67
+ *
68
+ * Guards the same false positive: a browser's `click` fires on the nearest
69
+ * common ancestor of mousedown and mouseup, so a text-selection drag that
70
+ * starts inside an anchor and ends outside it would otherwise look like an
71
+ * outside click. Close only when BOTH mousedown and click landed outside.
72
+ */
73
+ export declare function bindOutsideDismiss(anchors: HTMLElement[], onDismiss: () => void): () => void;
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Internal positioning helpers shared by popover.ts and tooltip.ts. Not part
3
+ * of the public API — not re-exported from index.ts.
4
+ *
5
+ * Both callers use `position: fixed`, so all math here is viewport-relative
6
+ * (matching `getBoundingClientRect()`) rather than document-relative.
7
+ */
8
+ export type Side = 'top' | 'bottom' | 'left' | 'right';
9
+ /**
10
+ * `top` / `bottom` / `left` / `right` center the floating element on the
11
+ * anchor's cross-axis midpoint. `top-left` / `top-right` / `bottom-left` /
12
+ * `bottom-right` keep the anchor above/below but shift the floating element
13
+ * so it leans toward that corner instead of centering — useful to steer a
14
+ * panel away from something else on the page (another fixed control, a
15
+ * viewport edge) without losing the "above/below the trigger" placement.
16
+ *
17
+ * "Lean right" means the floating element's LEFT edge lines up with the
18
+ * anchor's left edge, so the (usually wider) box extends further right of
19
+ * the anchor than a centered box would; "lean left" mirrors that off the
20
+ * anchor's right edge.
21
+ */
22
+ export type Placement = Side | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
23
+ export interface PositionOptions {
24
+ /** Preferred placement. */
25
+ placement?: Placement;
26
+ /** Gap between anchor and floating element, px. */
27
+ offset?: number;
28
+ /** Minimum distance from the viewport edge, px. */
29
+ padding?: number;
30
+ }
31
+ export interface PositionResult {
32
+ /** Final placement, after a possible flip. */
33
+ placement: Placement;
34
+ top: number;
35
+ left: number;
36
+ /**
37
+ * Distance, in px, from the floating element's near edge (left, for a
38
+ * top/bottom placement; top, for a left/right placement) to the anchor's
39
+ * center — for positioning an optional arrow so it keeps pointing at the
40
+ * anchor even after the cross-axis got clamped to stay in the viewport.
41
+ * Already inset from the floating element's own edges so the arrow never
42
+ * overhangs a rounded corner.
43
+ */
44
+ arrowOffset: number;
45
+ }
46
+ /**
47
+ * Positions `floating` relative to `anchor`: flips to the opposite side when
48
+ * the preferred side doesn't fit and the opposite side has more room (a
49
+ * 2-entry fallback, not a 4-way rotation — both callers are single-edge
50
+ * anchored, unlike a corner-anchored menu), and clamps the cross-axis into
51
+ * the viewport ("shift") rather than flipping it.
52
+ *
53
+ * The cross-axis is centered on the anchor by default (`align: 'center'`,
54
+ * implied by a bare `top`/`bottom`/`left`/`right` placement) — the corner
55
+ * placements (`top-left`, etc.) opt into edge-alignment instead. Centering
56
+ * is what makes an arrow drawn at the floating element's own midpoint line
57
+ * up with the anchor; `arrowOffset` carries the exact anchor-relative
58
+ * position so the arrow still lines up even when a corner placement or a
59
+ * viewport clamp shifts the box off-center.
60
+ */
61
+ export declare function computePosition(anchor: HTMLElement, floating: HTMLElement, opts?: PositionOptions): PositionResult;
62
+ /**
63
+ * Fires `onDismiss` only when a click lands outside every element in
64
+ * `anchors` — generalizes modal.ts's `bindOutsideClickToClose` (which checks
65
+ * a single backdrop) to an arbitrary set of "not outside" elements (e.g. a
66
+ * popover's trigger AND its panel).
67
+ *
68
+ * Guards the same false positive: a browser's `click` fires on the nearest
69
+ * common ancestor of mousedown and mouseup, so a text-selection drag that
70
+ * starts inside an anchor and ends outside it would otherwise look like an
71
+ * outside click. Close only when BOTH mousedown and click landed outside.
72
+ */
73
+ export declare function bindOutsideDismiss(anchors: HTMLElement[], onDismiss: () => void): () => void;
@@ -0,0 +1,12 @@
1
+ /** Every token name, without the `--fw-` prefix. */
2
+ export type TokenName = 'text' | 'text-soft' | 'bg' | 'bg-dim' | 'surface' | 'primary' | 'primary-hover' | 'primary-soft' | 'secondary' | 'secondary-hover' | 'secondary-bg' | 'success' | 'success-bg' | 'warning' | 'warning-bg' | 'danger' | 'danger-bg' | 'info' | 'info-bg' | 'on-primary' | 'on-secondary' | 'on-secondary-soft' | 'on-secondary-fill' | 'on-secondary-line' | 'overlay' | 'overlay-soft' | 'focus-ring' | 'scrollbar-thumb' | 'scrollbar-thumb-hover' | 'scrollbar-track' | 'font-display' | 'font-body' | 'font-mono' | 'sp-1' | 'sp-2' | 'sp-3' | 'sp-4' | 'sp-5' | 'sp-6' | 'sp-7' | 'sp-8' | 'sp-9' | 'r-sm' | 'r-md' | 'r-lg' | 'r-xl' | 'r-full' | 'shadow-sm' | 'shadow-md' | 'shadow-lg' | 'shadow-primary' | 'shadow-success' | 'border-w' | 'motion-fast' | 'motion-base' | 'motion-ease';
3
+ /** A token whose value is computed from other tokens (hovers, tints, focus ring, glows). */
4
+ export interface DerivedToken {
5
+ readonly name: TokenName;
6
+ /** The CSS value in the light theme, with `{ref}`s already resolved to `var(--fw-…)`. */
7
+ readonly light: string;
8
+ readonly dark: string;
9
+ /** Names of the tokens this one is computed from (either theme). */
10
+ readonly deps: readonly string[];
11
+ }
12
+ export declare const DERIVED: readonly DerivedToken[];
@@ -0,0 +1,12 @@
1
+ /** Every token name, without the `--fw-` prefix. */
2
+ export type TokenName = 'text' | 'text-soft' | 'bg' | 'bg-dim' | 'surface' | 'primary' | 'primary-hover' | 'primary-soft' | 'secondary' | 'secondary-hover' | 'secondary-bg' | 'success' | 'success-bg' | 'warning' | 'warning-bg' | 'danger' | 'danger-bg' | 'info' | 'info-bg' | 'on-primary' | 'on-secondary' | 'on-secondary-soft' | 'on-secondary-fill' | 'on-secondary-line' | 'overlay' | 'overlay-soft' | 'focus-ring' | 'scrollbar-thumb' | 'scrollbar-thumb-hover' | 'scrollbar-track' | 'font-display' | 'font-body' | 'font-mono' | 'sp-1' | 'sp-2' | 'sp-3' | 'sp-4' | 'sp-5' | 'sp-6' | 'sp-7' | 'sp-8' | 'sp-9' | 'r-sm' | 'r-md' | 'r-lg' | 'r-xl' | 'r-full' | 'shadow-sm' | 'shadow-md' | 'shadow-lg' | 'shadow-primary' | 'shadow-success' | 'border-w' | 'motion-fast' | 'motion-base' | 'motion-ease';
3
+ /** A token whose value is computed from other tokens (hovers, tints, focus ring, glows). */
4
+ export interface DerivedToken {
5
+ readonly name: TokenName;
6
+ /** The CSS value in the light theme, with `{ref}`s already resolved to `var(--fw-…)`. */
7
+ readonly light: string;
8
+ readonly dark: string;
9
+ /** Names of the tokens this one is computed from (either theme). */
10
+ readonly deps: readonly string[];
11
+ }
12
+ export declare const DERIVED: readonly DerivedToken[];
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Combobox / typeahead — progressive enhancement over an <input> + <datalist>.
3
+ *
4
+ * <div class="fw-combobox">
5
+ * <input class="fw-input" data-fw-combobox list="cities" placeholder="Start typing…">
6
+ * <datalist id="cities"><option value="Seattle"></option>…</datalist>
7
+ * </div>
8
+ *
9
+ * Or programmatically: combobox(input, { options: ['Seattle', …], onSelect })
10
+ */
11
+ export interface ComboboxOptions {
12
+ /** Static option list. Defaults to the <datalist> the input's `list` attribute points at. */
13
+ options?: string[];
14
+ /** Filter function; default is case-insensitive substring match. */
15
+ filter?: (option: string, query: string) => boolean;
16
+ /** Text shown when nothing matches. */
17
+ emptyText?: string;
18
+ onSelect?: (value: string) => void;
19
+ }
20
+ export interface ComboboxInstance {
21
+ open(): void;
22
+ close(): void;
23
+ setOptions(options: string[]): void;
24
+ destroy(): void;
25
+ }
26
+ export declare function combobox(input: HTMLInputElement, opts?: ComboboxOptions): ComboboxInstance;
27
+ /** Enhance every `[data-fw-combobox]` input under `root`. */
28
+ export declare function init(root?: ParentNode): void;
package/dist/css.d.cts ADDED
@@ -0,0 +1,2 @@
1
+ // Type stub so `import "fernwell/css"` (and /css/min, /tokens.css) resolves.
2
+ export {};
package/dist/css.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ // Type stub so `import "fernwell/css"` (and /css/min, /tokens.css) resolves.
2
+ export {};
package/dist/fernwell.cjs CHANGED
@@ -1,4 +1,6 @@
1
- /*! fernwell v0.3.0 | MIT | https://github.com/JacobKnaack/fernwell */
2
- "use strict";var V=Object.defineProperty;var Ze=Object.getOwnPropertyDescriptor;var et=Object.getOwnPropertyNames;var tt=Object.prototype.hasOwnProperty;var T=(e,t)=>{for(var n in t)V(e,n,{get:t[n],enumerable:!0})},nt=(e,t,n,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of et(t))!tt.call(e,r)&&r!==n&&V(e,r,{get:()=>t[r],enumerable:!(o=Ze(t,r))||o.enumerable});return e};var ot=e=>nt(V({},"__esModule",{value:!0}),e);var Ot={};T(Ot,{combobox:()=>C,default:()=>St,init:()=>Xe,loading:()=>N,menu:()=>K,modal:()=>D,nav:()=>B,table:()=>R,theme:()=>O,toast:()=>I,version:()=>Ye});module.exports=ot(Ot);var O={};T(O,{get:()=>W,getToken:()=>ct,init:()=>_,resetTokens:()=>at,set:()=>S,setTokens:()=>lt,toggle:()=>xe});var G="fw-theme",rt="fw:themechange",st="--fw-",Me="data-fw-tokens";function U(){try{return window.localStorage}catch{return null}}function W(){var t;let e=document.documentElement.getAttribute("data-theme");return e==="dark"||e==="light"?e:(t=window.matchMedia)!=null&&t.call(window,"(prefers-color-scheme: dark)").matches?"dark":"light"}function S(e,{persist:t=!0}={}){var n;document.documentElement.setAttribute("data-theme",e),t&&((n=U())==null||n.setItem(G,e)),document.querySelectorAll("[data-fw-theme-toggle]").forEach(o=>ke(o,e)),document.dispatchEvent(new CustomEvent(rt,{detail:e}))}function xe(){let e=W()==="dark"?"light":"dark";return S(e),e}function ke(e,t){e.setAttribute("aria-pressed",String(t==="dark"));let n=t==="dark"?e.dataset.fwThemeLabelDark:e.dataset.fwThemeLabelLight;n!==void 0&&(e.textContent=n)}function _(e=document){var o,r,s,l;let t=(o=U())==null?void 0:o.getItem(G),n=t==="dark"||t==="light"?t:W();S(n,{persist:!1}),e.querySelectorAll("[data-fw-theme-toggle]").forEach(d=>{d.dataset.fwBound||(d.dataset.fwBound="1",ke(d,n),d.addEventListener("click",()=>xe()))}),(l=(r=window.matchMedia)==null?void 0:(s=r.call(window,"(prefers-color-scheme: dark)")).addEventListener)==null||l.call(s,"change",d=>{var f;(f=U())!=null&&f.getItem(G)||S(d.matches?"dark":"light",{persist:!1})})}var b=new Map;function Ae(e){return e.startsWith("--")?e:`${st}${e}`}function it(e){return Object.values(e).some(t=>t!==null&&typeof t=="object")}function He(e){return Object.entries(e).map(([t,n])=>`${Ae(t)}:${n};`).join("")}function ye(e){let t=document.head.querySelector(`style[${Me}]`);if(t||!e)return t;let n=document.createElement("style");return n.setAttribute(Me,""),document.head.appendChild(n),n}function Se(){var t;if(b.size===0){(t=ye(!1))==null||t.remove();return}let e=[];b.forEach(({light:n,dark:o},r)=>{if(Object.keys(n).length&&e.push(`${r}{${He(n)}}`),Object.keys(o).length){let s=r===":root"?'[data-theme="dark"]':`[data-theme="dark"] ${r}, ${r}[data-theme="dark"]`;e.push(`${s}{${He(o)}}`)}}),ye(!0).textContent=e.join(`
3
- `)}function lt(e,t={}){var r,s;let n=(r=t.scope)!=null?r:":root",o=(s=b.get(n))!=null?s:{light:{},dark:{}};it(e)?(Object.assign(o.light,e.light),Object.assign(o.dark,e.dark)):(Object.assign(o.light,e),Object.assign(o.dark,e)),b.set(n,o),Se()}function at(e){e?b.delete(e):b.clear(),Se()}function ct(e,t=document.documentElement){return getComputedStyle(t).getPropertyValue(Ae(e)).trim()}var C={};T(C,{combobox:()=>Oe,init:()=>z});var dt=0;function x(e){return e.replace(/[&<>"']/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[t])}function Oe(e,t={}){var ve,he,be,ge;let n=(ve=t.filter)!=null?ve:((i,c)=>i.toLowerCase().includes(c)),o=(he=t.emptyText)!=null?he:"No matches",r=(be=t.options)!=null?be:ut(e),s=[],l=-1,d=!1,f=e.getAttribute("list");f&&(e.dataset.fwList=f,e.removeAttribute("list"));let u=(ge=e.closest(".fw-combobox"))!=null?ge:ft(e),a=document.createElement("ul"),F=`fw-combobox-${++dt}`;a.className="fw-combobox-list",a.id=F,a.setAttribute("role","listbox"),u.appendChild(a),e.setAttribute("role","combobox"),e.setAttribute("aria-autocomplete","list"),e.setAttribute("aria-expanded","false"),e.setAttribute("aria-controls",F),e.autocomplete="off";function Je(i){s=i,l=-1;let c=e.value.trim().toLowerCase();if(i.length===0){a.innerHTML=`<li class="fw-combobox-empty">${x(o)}</li>`;return}a.innerHTML=i.map((E,H)=>{let y=c?E.toLowerCase().indexOf(c):-1,Qe=y>-1?`${x(E.slice(0,y))}<b>${x(E.slice(y,y+c.length))}</b>${x(E.slice(y+c.length))}`:x(E);return`<li class="fw-combobox-option" role="option" id="${F}-${H}" data-index="${H}" aria-selected="false">${Qe}</li>`}).join("")}function M(){let i=e.value.trim().toLowerCase();Je(i?r.filter(c=>n(c,i)):r),a.classList.add("is-open"),e.setAttribute("aria-expanded","true")}function A(){a.classList.remove("is-open"),e.setAttribute("aria-expanded","false"),e.removeAttribute("aria-activedescendant"),l=-1}function fe(i){let c=a.querySelectorAll(".fw-combobox-option");c.forEach(H=>{H.classList.remove("is-active"),H.setAttribute("aria-selected","false")});let E=c[i];E&&(E.classList.add("is-active"),E.setAttribute("aria-selected","true"),E.scrollIntoView({block:"nearest"}),e.setAttribute("aria-activedescendant",E.id)),l=i}function me(i){var c;e.value=i,A(),d=!0,e.focus(),e.dispatchEvent(new Event("input",{bubbles:!0})),e.dispatchEvent(new Event("change",{bubbles:!0})),d=!1,(c=t.onSelect)==null||c.call(t,i)}let Ee=()=>{d||M()},pe=()=>{d||M()},Te=i=>{i.key==="ArrowDown"?(i.preventDefault(),a.classList.contains("is-open")||M(),fe(Math.min(l+1,s.length-1))):i.key==="ArrowUp"?(i.preventDefault(),fe(Math.max(l-1,0))):i.key==="Enter"?l>-1&&s[l]!==void 0&&(i.preventDefault(),me(s[l])):i.key==="Escape"&&A()},Le=i=>{let c=i.target.closest(".fw-combobox-option");c&&(i.preventDefault(),me(s[Number(c.dataset.index)]))},we=i=>{u.contains(i.target)||A()};return e.addEventListener("input",Ee),e.addEventListener("focus",pe),e.addEventListener("keydown",Te),a.addEventListener("mousedown",Le),document.addEventListener("click",we),{open:M,close:A,setOptions(i){r=i,a.classList.contains("is-open")&&M()},destroy(){e.removeEventListener("input",Ee),e.removeEventListener("focus",pe),e.removeEventListener("keydown",Te),a.removeEventListener("mousedown",Le),document.removeEventListener("click",we),a.remove(),e.dataset.fwList&&e.setAttribute("list",e.dataset.fwList),delete e.dataset.fwBound}}}function ut(e){var o;let t=(o=e.getAttribute("list"))!=null?o:e.dataset.fwList,n=t?document.getElementById(t):null;return n?Array.from(n.querySelectorAll("option")).map(r=>r.value||r.textContent||""):[]}function ft(e){var n;let t=document.createElement("div");return t.className="fw-combobox",(n=e.parentNode)==null||n.insertBefore(t,e),t.appendChild(e),t}function z(e=document){e.querySelectorAll("input[data-fw-combobox]").forEach(t=>{t.dataset.fwBound||(t.dataset.fwBound="1",Oe(t))})}var D={};T(D,{bindOutsideClickToClose:()=>X,close:()=>L,init:()=>ee,open:()=>Z,toggle:()=>Et});var w="is-open",De="fw-scroll-lock",v=[],Y=new WeakMap,mt='a[href], button:not([disabled]), input:not([disabled]):not([type="hidden"]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';function J(e){return typeof e=="string"?document.getElementById(e):e}function Q(e){if(e.classList.contains("fw-modal-overlay"))return e;let t=e.id?document.querySelector(`[data-fw-drawer-overlay="${e.id}"]`):null;if(t)return t;let n=e.previousElementSibling;return n instanceof HTMLElement&&n.classList.contains("fw-drawer-overlay")?n:null}function Z(e,t=(n=>(n=document.activeElement)!=null?n:null)()){var s;let o=J(e);if(!o||o.classList.contains(w))return;o.classList.add(w),(s=Q(o))==null||s.classList.add(w),Y.set(o,t),v.push(o),document.body.classList.add(De),o.dispatchEvent(new CustomEvent("fw:open",{bubbles:!0}));let r=o.querySelector(mt);r?r.focus({preventScroll:!0}):(o.hasAttribute("tabindex")||o.setAttribute("tabindex","-1"),o.focus({preventScroll:!0}))}function L(e){var o,r,s;let t=J(e);if(!t||!t.classList.contains(w))return;t.classList.remove(w),(o=Q(t))==null||o.classList.remove(w);let n=v.indexOf(t);n>-1&&v.splice(n,1),v.length===0&&document.body.classList.remove(De),t.dispatchEvent(new CustomEvent("fw:close",{bubbles:!0})),(s=(r=Y.get(t))==null?void 0:r.focus)==null||s.call(r,{preventScroll:!0}),Y.delete(t)}function Et(e){let t=J(e);t&&(t.classList.contains(w)?L:Z)(t)}function X(e,t){let n=!1;e.addEventListener("mousedown",o=>{n=o.target===e}),e.addEventListener("click",o=>{o.target===e&&n&&t()})}var Ce=!1;function ee(e=document){e.querySelectorAll("[data-fw-open-modal], [data-fw-open-drawer]").forEach(t=>{var o,r;if(t.dataset.fwBound)return;t.dataset.fwBound="1";let n=(r=(o=t.dataset.fwOpenModal)!=null?o:t.dataset.fwOpenDrawer)!=null?r:"";t.addEventListener("click",()=>Z(n,t))}),e.querySelectorAll(".fw-modal-overlay").forEach(t=>{t.dataset.fwBound||(t.dataset.fwBound="1",X(t,()=>L(t)),t.querySelectorAll("[data-fw-close-modal]").forEach(n=>n.addEventListener("click",()=>L(t))))}),e.querySelectorAll(".fw-drawer").forEach(t=>{if(t.dataset.fwBound)return;t.dataset.fwBound="1";let n=Q(t);n&&X(n,()=>L(t)),t.querySelectorAll("[data-fw-close-drawer]").forEach(o=>o.addEventListener("click",()=>L(t)))}),Ce||(Ce=!0,document.addEventListener("keydown",t=>{t.key==="Escape"&&v.length&&L(v[v.length-1])}))}var B={};T(B,{close:()=>q,init:()=>oe,open:()=>qe});var te="is-open";function qe(e){let t=ne(e);t&&(t.classList.add(te),e.setAttribute("aria-expanded","true"))}function q(e){let t=ne(e);t&&(t.classList.remove(te),e.setAttribute("aria-expanded","false"))}function ne(e){let t=e.getAttribute("aria-controls");return t?document.getElementById(t):null}function oe(e=document){e.querySelectorAll("[data-fw-nav-toggle]").forEach(t=>{var r,s;if(t.dataset.fwBound)return;t.dataset.fwBound="1";let n=ne(t);if(!n)return;let o=(s=(r=t.closest(".fw-nav"))!=null?r:t.parentElement)!=null?s:t;t.addEventListener("click",l=>{l.stopPropagation(),(n.classList.contains(te)?q:qe)(t)}),document.addEventListener("click",l=>{!o.contains(l.target)&&!n.contains(l.target)&&q(t)}),document.addEventListener("keydown",l=>{l.key==="Escape"&&q(t)})})}var N={};T(N,{init:()=>re,resetAll:()=>$e,resetButtonLoading:()=>Ie,setButtonLoading:()=>Ne});var k="fw-btn-loading";function Ne(e,t){var o,r;if(!e||e.classList.contains(k))return;e.dataset.fwOriginalHtml=e.innerHTML,e.disabled=!0,e.setAttribute("aria-busy","true"),e.classList.add(k);let n=(r=(o=t!=null?t:e.dataset.fwLoadingText)!=null?o:e.textContent)!=null?r:"";e.innerHTML='<span class="fw-spinner fw-spinner-sm" aria-hidden="true"></span>',e.appendChild(document.createTextNode(n))}function Ie(e){!e||!e.classList.contains(k)||(e.disabled=!1,e.removeAttribute("aria-busy"),e.classList.remove(k),e.dataset.fwOriginalHtml!==void 0&&(e.innerHTML=e.dataset.fwOriginalHtml,delete e.dataset.fwOriginalHtml))}function $e(e=document){e.querySelectorAll(`.${k}`).forEach(Ie)}var Be=!1;function re(e=document){e.querySelectorAll("form[data-fw-loading]").forEach(t=>{t.dataset.fwBound||(t.dataset.fwBound="1",t.addEventListener("submit",()=>{let n=t.querySelector('button[type="submit"], button:not([type])');Ne(n)}))}),Be||(Be=!0,window.addEventListener("pageshow",t=>{t.persisted&&$e()}))}var I={};T(I,{hide:()=>Re,show:()=>Tt});var m=null,g=null;function pt(){return m&&m.isConnected||(m=document.createElement("div"),m.className="fw-toast",m.setAttribute("role","status"),m.setAttribute("aria-live","polite"),m.innerHTML='<span class="fw-toast-dot" aria-hidden="true"></span><span class="fw-toast-text"></span>',document.body.appendChild(m)),m}function Tt(e,{variant:t="success",duration:n=4e3}={}){let o=pt();return o.className=`fw-toast${t==="success"?"":` fw-toast-${t}`}`,o.querySelector(".fw-toast-text").textContent=e,g&&clearTimeout(g),o.classList.remove("is-show"),o.offsetWidth,o.classList.add("is-show"),n>0&&(g=setTimeout(Re,n)),o}function Re(){m==null||m.classList.remove("is-show"),g&&clearTimeout(g),g=null}var R={};T(R,{getSelected:()=>ce,init:()=>de,selectAll:()=>Ve,setRowSelected:()=>Fe,sort:()=>se});var Lt="is-selected";function ie(e,t,n){e.dispatchEvent(new CustomEvent(t,{bubbles:!0,detail:n}))}function Pe(e,t){var o,r,s;let n=e.cells[t];return(s=(r=n==null?void 0:n.dataset.fwSortValue)!=null?r:(o=n==null?void 0:n.textContent)==null?void 0:o.trim())!=null?s:""}function wt(e,t){let n=Number(e),o=Number(t);return e!==""&&t!==""&&Number.isFinite(n)&&Number.isFinite(o)?n-o:e.localeCompare(t,void 0,{numeric:!0,sensitivity:"base"})}function Ke(e){var t,n;return Array.from((n=(t=e.tHead)==null?void 0:t.querySelectorAll("th[data-fw-sort]"))!=null?n:[])}function je(e){return e.getAttribute("aria-sort")==="ascending"?"descending":"ascending"}function se(e,t,n){let o=e.tBodies[0];if(!o)return;let r=Array.from(o.rows),s=r.filter(u=>u.cells.length>t),l=r.filter(u=>u.cells.length<=t),d=n==="ascending"?1:-1;s.sort((u,a)=>d*wt(Pe(u,t),Pe(a,t))),[...s,...l].forEach(u=>o.appendChild(u));let f=null;Ke(e).forEach(u=>{let a=u.cellIndex===t;u.setAttribute("aria-sort",a?n:"none"),a&&(f=u)}),f&&ie(e,"fw:sort",{column:t,direction:n,th:f})}function $(e){return Array.from(e.querySelectorAll("tbody input[data-fw-select-row]"))}function le(e,t){e.classList.toggle(Lt,t),e.setAttribute("aria-selected",t?"true":"false")}function ae(e){let t=e.querySelector("[data-fw-select-all]");if(!t)return;let n=$(e),o=n.filter(r=>r.checked).length;t.checked=o>0&&o===n.length,t.indeterminate=o>0&&o<n.length}function ce(e){return $(e).filter(t=>t.checked).map(t=>t.closest("tr")).filter(t=>t!==null)}function Fe(e,t){let n=e.querySelector("input[data-fw-select-row]");n&&(n.checked=t),le(e,t);let o=e.closest("table");o&&(ae(o),ie(o,"fw:select",{rows:ce(o),all:!1}))}function Ve(e,t){$(e).forEach(n=>{n.checked=t;let o=n.closest("tr");o&&le(o,t)}),ae(e),ie(e,"fw:select",{rows:ce(e),all:!0})}function de(e=document){e.querySelectorAll("table[data-fw-table]").forEach(t=>{t.dataset.fwBound||(t.dataset.fwBound="1",Ke(t).forEach(n=>{n.hasAttribute("aria-sort")||n.setAttribute("aria-sort","none")}),t.addEventListener("click",n=>{let o=n.target.closest("th[data-fw-sort]");o&&t.contains(o)&&se(t,o.cellIndex,je(o))}),t.addEventListener("keydown",n=>{if(n.key!=="Enter"&&n.key!==" ")return;let o=n.target;if(!o.matches("th[data-fw-sort], th[data-fw-sort] .fw-table-sort"))return;let r=o.closest("th[data-fw-sort]");r&&(n.preventDefault(),se(t,r.cellIndex,je(r)))}),t.addEventListener("change",n=>{let o=n.target;if(o.matches("[data-fw-select-all]"))Ve(t,o.checked);else if(o.matches("[data-fw-select-row]")){let r=o.closest("tr");r&&Fe(r,o.checked)}}),$(t).forEach(n=>{let o=n.closest("tr");o&&n.checked&&le(o,!0)}),ae(t))})}var K={};T(K,{close:()=>j,collapseAll:()=>Mt,expandAll:()=>gt,init:()=>ue,open:()=>P,reveal:()=>We,toggle:()=>bt});var p=".fw-menu-group",Ue=".fw-menu-link, .fw-menu-summary",vt="is-current";function ht(e,t){e.dispatchEvent(new CustomEvent("fw:toggle",{bubbles:!0,detail:t}))}function P(e){e.open=!0}function j(e){e.open=!1}function bt(e,t){e.open=t!=null?t:!e.open}function gt(e){e.querySelectorAll(p).forEach(P)}function Mt(e){e.querySelectorAll(p).forEach(j)}function We(e){var n,o,r,s;let t=(o=(n=e.parentElement)==null?void 0:n.closest(p))!=null?o:null;for(;t;)P(t),t=(s=(r=t.parentElement)==null?void 0:r.closest(p))!=null?s:null}function Ge(e){e.querySelectorAll(p).forEach(t=>{let n=t.querySelector(":scope > .fw-menu-summary");if(!n)return;let o=!t.open&&t.querySelector("[aria-current]")!==null;n.classList.toggle(vt,o)})}function _e(e){return e.matches(".fw-menu-summary")?e.closest(p):null}function ze(e){var n,o,r;return(r=(o=((n=_e(e))!=null?n:e).parentElement)==null?void 0:o.closest(p))!=null?r:null}function Ht(e){var t,n;for(let o=ze(e);o;o=(n=(t=o.parentElement)==null?void 0:t.closest(p))!=null?n:null)if(!o.open)return!1;return!0}function yt(e){return Array.from(e.querySelectorAll(Ue)).filter(Ht)}function h(e){return e?(e.focus(),!0):!1}function xt(e){var n;let t=e.closest(".fw-menu-split");return(n=t==null?void 0:t.querySelector(":scope > .fw-menu-group > .fw-menu-summary"))!=null?n:null}function kt(e,t){var d;if(t.altKey||t.ctrlKey||t.metaKey||t.shiftKey)return;let n=t.target instanceof HTMLElement&&t.target.matches(Ue)?t.target:null;if(!n||!e.contains(n))return;let o=yt(e),r=o.indexOf(n),s=_e(n),l=!0;switch(t.key){case"ArrowDown":h(o[r+1]);break;case"ArrowUp":h(o[r-1]);break;case"Home":h(o[0]);break;case"End":h(o[o.length-1]);break;case"ArrowRight":s?s.open?s.contains((d=o[r+1])!=null?d:e)&&h(o[r+1]):P(s):h(xt(n));break;case"ArrowLeft":if(s!=null&&s.open)j(s);else{let f=ze(n);h(f==null?void 0:f.querySelector(":scope > .fw-menu-summary"))}break;default:l=!1}l&&t.preventDefault()}function ue(e=document){e.querySelectorAll("[data-fw-menu]").forEach(t=>{if(t.dataset.fwBound)return;t.dataset.fwBound="1";let n=t.hasAttribute("data-fw-menu-exclusive");t.addEventListener("toggle",o=>{let r=o.target;!(r instanceof HTMLDetailsElement)||!r.matches(p)||(n&&r.open&&r.parentElement&&Array.from(r.parentElement.children).forEach(s=>{s!==r&&s instanceof HTMLDetailsElement&&s.matches(p)&&j(s)}),Ge(t),ht(r,{group:r,open:r.open}))},!0),t.addEventListener("keydown",o=>kt(t,o)),t.querySelectorAll("[aria-current]").forEach(We),Ge(t)})}var Ye="0.3.0";function Xe(e=document){_(e),z(e),ee(e),oe(e),re(e),de(e),ue(e)}var At={version:Ye,init:Xe,theme:O,combobox:C,modal:D,nav:B,loading:N,toast:I,table:R,menu:K},St=At;
1
+ /*! fernwell v0.5.0 | MIT | https://github.com/JacobKnaack/fernwell */
2
+ "use strict";var ke=Object.defineProperty;var fn=Object.getOwnPropertyDescriptor;var mn=Object.getOwnPropertyNames;var pn=Object.prototype.hasOwnProperty;var b=(e,t)=>{for(var n in t)ke(e,n,{get:t[n],enumerable:!0})},vn=(e,t,n,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of mn(t))!pn.call(e,r)&&r!==n&&ke(e,r,{get:()=>t[r],enumerable:!(o=fn(t,r))||o.enumerable});return e};var hn=e=>vn(ke({},"__esModule",{value:!0}),e);var io={};b(io,{combobox:()=>le,default:()=>ro,icon:()=>Q,init:()=>dn,loading:()=>fe,menu:()=>ye,modal:()=>ce,nav:()=>ue,popover:()=>Te,table:()=>ve,tabs:()=>Me,theme:()=>ae,toast:()=>me,tokens:()=>ie,tooltip:()=>Le,version:()=>cn});module.exports=hn(io);var ae={};b(ae,{get:()=>Re,getToken:()=>Ie,init:()=>$e,resetTokens:()=>De,set:()=>se,setTokens:()=>W,toggle:()=>Ht});var Q={};b(Q,{init:()=>He,render:()=>X});var wn={add:'<line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/>',close:'<line x1="6" y1="6" x2="18" y2="18"/><line x1="18" y1="6" x2="6" y2="18"/>',check:'<polyline points="5,13 10,18 19,7"/>',edit:'<path d="M16.8 3.5a2.1 2.1 0 0 1 3 3L8.5 17.8 4 19l1.2-4.5Z"/><line x1="14.5" y1="5.8" x2="17.7" y2="9"/>',trash:'<path d="M4 7h16"/><path d="M9 7V4.6a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1V7"/><path d="M6.5 7l.8 12.4A2 2 0 0 0 9.3 21h5.4a2 2 0 0 0 2-1.6L18 7"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/>',save:'<path d="M5 4a1 1 0 0 1 1-1h9.2a1 1 0 0 1 .7.3l2.8 2.8a1 1 0 0 1 .3.7V20a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V4Z"/><path d="M8 3v5h7V3"/><path d="M8 21v-6h8v6"/>',search:'<circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.6" y2="16.6"/>',settings:'<circle cx="12" cy="12" r="3"/><path d="M12 2v3M12 19v3M4.2 4.2l2.1 2.1M17.7 17.7l2.1 2.1M2 12h3M19 12h3M4.2 19.8l2.1-2.1M17.7 6.3l2.1-2.1"/>',download:'<line x1="12" y1="3" x2="12" y2="15"/><polyline points="7,10 12,15 17,10"/><line x1="5" y1="21" x2="19" y2="21"/>',upload:'<line x1="12" y1="15" x2="12" y2="3"/><polyline points="7,8 12,3 17,8"/><line x1="5" y1="21" x2="19" y2="21"/>',share:'<circle cx="6" cy="12" r="2.3"/><circle cx="18" cy="6" r="2.3"/><circle cx="18" cy="18" r="2.3"/><line x1="8.1" y1="10.8" x2="15.9" y2="7.2"/><line x1="8.1" y1="13.2" x2="15.9" y2="16.8"/>',copy:'<rect x="9" y="9" width="12" height="12" rx="2"/><path d="M5 15H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1"/>',menu:'<line x1="4" y1="6" x2="20" y2="6"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="18" x2="20" y2="18"/>',more:'<circle cx="5" cy="12" r="1.4" fill="currentColor" stroke="none"/><circle cx="12" cy="12" r="1.4" fill="currentColor" stroke="none"/><circle cx="19" cy="12" r="1.4" fill="currentColor" stroke="none"/>',user:'<circle cx="12" cy="8" r="4"/><path d="M4 21c0-4.4 3.6-8 8-8s8 3.6 8 8"/>',bell:'<path d="M6 8a6 6 0 0 1 12 0c0 5 2 6 2 6H4s2-1 2-6"/><path d="M10 20a2 2 0 0 0 4 0"/>',heart:'<path d="M12 20s-7-4.4-9.5-8.5C.8 8 2 4.5 5.5 4.5c2 0 3.5 1 6.5 4 3-3 4.5-4 6.5-4 3.5 0 4.7 3.5 3 7-2.5 4.1-9.5 8.5-9.5 8.5Z"/>',star:'<polygon points="12,3 14.6,9.1 21.2,9.3 16,13.4 17.8,19.8 12,16.3 6.2,19.8 8,13.4 2.8,9.3 9.4,9.1"/>',calendar:'<rect x="3" y="5" width="18" height="16" rx="2"/><line x1="8" y1="3" x2="8" y2="7"/><line x1="16" y1="3" x2="16" y2="7"/><line x1="3" y1="10" x2="21" y2="10"/>',refresh:'<path d="M20 11A8 8 0 0 0 6.3 6.3L4 8.6"/><path d="M4 4v5h5"/><path d="M4 13a8 8 0 0 0 13.7 4.7L20 15.4"/><path d="M20 20v-5h-5"/>',filter:'<polygon points="4,4 20,4 14,12 14,19 10,21 10,12"/>',info:'<circle cx="12" cy="12" r="9"/><line x1="12" y1="11" x2="12" y2="16"/><circle cx="12" cy="8" r="1" fill="currentColor" stroke="none"/>',warning:'<path d="M12 3 22 20H2Z"/><line x1="12" y1="9.5" x2="12" y2="14.5"/><circle cx="12" cy="17.3" r="1" fill="currentColor" stroke="none"/>',lock:'<rect x="5" y="11" width="14" height="10" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/>',eye:'<path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/>',"chevron-down":'<polyline points="6,9 12,15 18,9"/>',"arrow-left":'<line x1="19" y1="12" x2="5" y2="12"/><polyline points="12,5 5,12 12,19"/>',"arrow-right":'<line x1="5" y1="12" x2="19" y2="12"/><polyline points="12,5 19,12 12,19"/>',folder:'<path d="M3.5 6.5a1 1 0 0 1 1-1H10l2 2.2h7.5a1 1 0 0 1 1 1V18a1 1 0 0 1-1 1H4.5a1 1 0 0 1-1-1Z"/>',bookmark:'<path d="M6 3h12a1 1 0 0 1 1 1v17l-7-4-7 4V4a1 1 0 0 1 1-1Z"/>',home:'<path d="M4 11.5 12 4l8 7.5"/><path d="M6 10v10a1 1 0 0 0 1 1h3.5v-6h2.8v6H17a1 1 0 0 0 1-1V10"/>',logout:'<path d="M9 4H5.5a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1H9"/><line x1="14" y1="12" x2="21" y2="12"/><polyline points="17.5,8.5 21,12 17.5,15.5"/>',paperclip:'<path d="M21 11.5 12.4 20a4.3 4.3 0 0 1-6-6.1L15 5.3a2.9 2.9 0 0 1 4.1 4.1l-8.6 8.6a1.4 1.4 0 0 1-2-2l7.9-7.9"/>',envelope:'<rect x="3" y="5" width="18" height="14" rx="2"/><path d="M4 6.5 12 13l8-6.5"/>',"chevron-up":'<polyline points="6,15 12,9 18,15"/>',"chevron-left":'<polyline points="15,6 9,12 15,18"/>',"chevron-right":'<polyline points="9,6 15,12 9,18"/>',"arrow-up":'<line x1="12" y1="19" x2="12" y2="5"/><polyline points="5,12 12,5 19,12"/>',"arrow-down":'<line x1="12" y1="5" x2="12" y2="19"/><polyline points="5,12 12,19 19,12"/>',unlock:'<rect x="5" y="11" width="14" height="10" rx="2"/><path d="M8 11V7a4 4 0 0 1 7.8-1.3"/>',"thumbs-up":'<path d="M7 11v9H4.5a1 1 0 0 1-1-1v-7a1 1 0 0 1 1-1H7Z"/><path d="M7 11l4.3-7.3a1.4 1.4 0 0 1 2.6.8L13 9h5.6a2 2 0 0 1 1.9 2.6l-2 6.5A2 2 0 0 1 16.6 19.5H10a3 3 0 0 1-3-3"/>',"thumbs-down":'<path d="M7 13V4h2.5a1 1 0 0 1 1 1v7a1 1 0 0 1-1 1H7Z"/><path d="M7 13l4.3 7.3a1.4 1.4 0 0 0 2.6-.8L13 15h5.6a2 2 0 0 0 1.9-2.6l-2-6.5A2 2 0 0 0 16.6 4.5H10a3 3 0 0 0-3 3"/>',"eye-closed":'<path d="M3.5 12S7 6 12 6s8.5 6 8.5 6-3.5 6-8.5 6-8.5-6-8.5-6Z"/><line x1="4" y1="19" x2="20" y2="5"/>',"help-circle":'<circle cx="12" cy="12" r="9"/><path d="M9.2 9.2a2.8 2.8 0 1 1 4.3 2.4c-.9.6-1.5 1.1-1.5 2.2"/><circle cx="12" cy="17" r="1" fill="currentColor" stroke="none"/>',chat:'<path d="M4 5.5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1H9.5L5 20.5V16.5H5a1 1 0 0 1-1-1Z"/>',kebab:'<circle cx="12" cy="5" r="1.4" fill="currentColor" stroke="none"/><circle cx="12" cy="12" r="1.4" fill="currentColor" stroke="none"/><circle cx="12" cy="19" r="1.4" fill="currentColor" stroke="none"/>',sun:'<circle cx="12" cy="12" r="4"/><line x1="12" y1="2" x2="12" y2="4.5"/><line x1="12" y1="19.5" x2="12" y2="22"/><line x1="2" y1="12" x2="4.5" y2="12"/><line x1="19.5" y1="12" x2="22" y2="12"/><line x1="4.9" y1="4.9" x2="6.6" y2="6.6"/><line x1="17.4" y1="17.4" x2="19.1" y2="19.1"/><line x1="4.9" y1="19.1" x2="6.6" y2="17.4"/><line x1="17.4" y1="6.6" x2="19.1" y2="4.9"/>',moon:'<path d="M20.5 14.2A8.5 8.5 0 1 1 9.8 3.5a7 7 0 0 0 10.7 10.7Z"/>'};function X(e){let t=wn[e];return t?`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">${t}</svg>`:""}function He(e=document){e.querySelectorAll("[data-fw-icon]").forEach(t=>{var r;if(t.dataset.fwBound)return;t.dataset.fwBound="1";let n=(r=t.dataset.fwIcon)!=null?r:"",o=X(n);o||console.warn(`fernwell: unknown icon "${n}"`),t.innerHTML=o,t.classList.add("fw-icon")})}var ie={};b(ie,{getToken:()=>Ie,init:()=>Pe,resetTokens:()=>De,restoreTokens:()=>Mt,setTokens:()=>W});var ft=[{name:"primary-hover",light:"color-mix(in srgb, var(--fw-primary), var(--fw-text) 12%)",dark:"color-mix(in srgb, var(--fw-primary), var(--fw-text) 12%)",deps:["primary","text"]},{name:"primary-soft",light:"color-mix(in srgb, var(--fw-primary), white 35%)",dark:"color-mix(in srgb, var(--fw-primary), white 35%)",deps:["primary"]},{name:"secondary-hover",light:"color-mix(in srgb, var(--fw-secondary), var(--fw-text) 15%)",dark:"color-mix(in srgb, var(--fw-secondary), var(--fw-text) 15%)",deps:["secondary","text"]},{name:"secondary-bg",light:"color-mix(in srgb, var(--fw-secondary) 10%, var(--fw-surface))",dark:"color-mix(in srgb, var(--fw-secondary) 16%, transparent)",deps:["secondary","surface"]},{name:"success-bg",light:"color-mix(in srgb, var(--fw-success) 12%, var(--fw-surface))",dark:"color-mix(in srgb, var(--fw-success) 14%, transparent)",deps:["success","surface"]},{name:"warning-bg",light:"color-mix(in srgb, var(--fw-warning) 14%, var(--fw-surface))",dark:"color-mix(in srgb, var(--fw-warning) 16%, transparent)",deps:["warning","surface"]},{name:"danger-bg",light:"color-mix(in srgb, var(--fw-danger) 13%, var(--fw-surface))",dark:"color-mix(in srgb, var(--fw-danger) 14%, transparent)",deps:["danger","surface"]},{name:"info-bg",light:"color-mix(in srgb, var(--fw-info) 12%, var(--fw-surface))",dark:"color-mix(in srgb, var(--fw-info) 14%, transparent)",deps:["info","surface"]},{name:"on-secondary-soft",light:"color-mix(in srgb, var(--fw-on-secondary) 72%, transparent)",dark:"color-mix(in srgb, var(--fw-on-secondary) 72%, transparent)",deps:["on-secondary"]},{name:"on-secondary-fill",light:"color-mix(in srgb, var(--fw-on-secondary) 10%, transparent)",dark:"color-mix(in srgb, var(--fw-on-secondary) 10%, transparent)",deps:["on-secondary"]},{name:"on-secondary-line",light:"color-mix(in srgb, var(--fw-on-secondary) 16%, transparent)",dark:"color-mix(in srgb, var(--fw-on-secondary) 16%, transparent)",deps:["on-secondary"]},{name:"focus-ring",light:"0 0 0 4px color-mix(in srgb, var(--fw-primary) 25%, transparent)",dark:"0 0 0 4px color-mix(in srgb, var(--fw-primary) 25%, transparent)",deps:["primary"]},{name:"scrollbar-thumb",light:"var(--fw-text-soft)",dark:"var(--fw-text-soft)",deps:["text-soft"]},{name:"scrollbar-thumb-hover",light:"color-mix(in srgb, var(--fw-text-soft), var(--fw-text) 15%)",dark:"color-mix(in srgb, var(--fw-text-soft), var(--fw-text) 15%)",deps:["text-soft","text"]},{name:"shadow-primary",light:"0 6px 16px color-mix(in srgb, var(--fw-primary) 35%, transparent)",dark:"0 6px 16px color-mix(in srgb, var(--fw-primary) 22%, transparent)",deps:["primary"]},{name:"shadow-success",light:"0 6px 16px color-mix(in srgb, var(--fw-success) 28%, transparent)",dark:"0 6px 16px color-mix(in srgb, var(--fw-success) 22%, transparent)",deps:["success"]}];var K="--fw-",mt="data-fw-tokens",Ae="fw-tokens",pt="fw-tokens-css",yn="fw:tokenschange",oe=":root",k=new Map,ee=new Map,re=!1,En=0,_=new Map,Ce=new WeakSet;function xt(){try{return window.localStorage}catch{return null}}function Lt(e){return e.startsWith("--")?e:`${K}${e}`}function bn(e){return e.startsWith(K)?e.slice(K.length):e}function te(e){console.warn(`fernwell: ${e}`)}function gt(e){try{return document.querySelector(e),!0}catch{return!1}}function Tn(e){let t=0,n="";for(let o=0;o<e.length;o++){let r=e[o];if(r==="\\")o++;else if(n){if(r===n)n="";else if(r===`
3
+ `)return!1}else if(r==='"'||r==="'")n=r;else if(r==="(")t++;else if(r===")"&&--t<0)return!1}return t===0&&!n}function V(e){let t={};if(e===null||typeof e!="object")return t;for(let[n,o]of Object.entries(e))/^(--)?[\w-]+$/.test(n)?typeof o!="string"||/[{};]|\/\*/.test(o)||!Tn(o)?te(`ignoring invalid value for token "${n}"`):t[Lt(n)]=o.trim():te(`ignoring token with invalid name "${n}"`);return t}function xn(e){return Object.values(e).some(t=>t!==null&&typeof t=="object")}function Ln(e){let t=[],n=0,o="",r=0;for(let s=0;s<e.length;s++){let i=e[s];i==="\\"?s++:o?i===o&&(o=""):i==='"'||i==="'"?o=i:i==="("||i==="["?n++:i===")"||i==="]"?n--:i===","&&n===0&&(t.push(e.slice(r,s).trim()),r=s+1)}return t.push(e.slice(r).trim()),t.filter(Boolean)}function gn(e){return e===oe?'[data-theme="dark"]':Ln(e).map(t=>`[data-theme="dark"] ${t}, ${t}[data-theme="dark"]`).join(", ")}function vt(e){return Object.entries(e).map(([t,n])=>`${t}:${n};`).join("")}function ht(e,t){let n=new Set(Object.keys(e).map(bn)),o=new Set(n),r={};for(let s=!0;s;){s=!1;for(let i of ft)n.has(i.name)||`${K}${i.name}`in r||i.deps.some(l=>o.has(l))&&(r[`${K}${i.name}`]=i[t],o.add(i.name),s=!0)}return r}function Se(e){let t=[];return e.forEach(({light:n,dark:o},r)=>{let s=r===oe,i=s?n:{...ht(n,"light"),...n},l=s?o:{...ht(o,"dark"),...o};Object.keys(i).length&&t.push(`${r}{${vt(i)}}`),Object.keys(l).length&&t.push(`${gn(r)}{${vt(l)}}`)}),t}function wt(e){let t=document.head.querySelector(`style[${mt}]`);if(t)return e&&t!==document.head.lastElementChild&&document.head.appendChild(t),t;if(!e)return null;let n=document.createElement("style");return n.setAttribute(mt,""),document.head.appendChild(n),n}function Oe(){let e=xt();if(e)try{if(k.size===0){e.removeItem(Ae),e.removeItem(pt);return}let t={};k.forEach((n,o)=>t[o]=n),e.setItem(Ae,JSON.stringify({v:1,scopes:t})),e.setItem(pt,Se(k).join(`
4
+ `))}catch{}}function ne(e){var n;let t=[...Se(k),...Se(ee)];t.length===0?(n=wt(!1))==null||n.remove():wt(!0).textContent=t.join(`
5
+ `),document.dispatchEvent(new CustomEvent(yn,{detail:{scope:e}}))}function yt(e,t){let n=new Map(Object.keys(t).map(o=>[o,o in e?e[o]:void 0]));return Object.assign(e,t),()=>n.forEach((o,r)=>{e[r]===t[r]&&(o===void 0?delete e[r]:e[r]=o)})}function W(e,t={}){var a,u;let n=(a=t.scope)!=null?a:oe;if(!gt(n))return te(`ignoring tokens for invalid scope "${n}"`),()=>{};re||Ne();let o,r;xn(e)?(o=V(e.light),r=V(e.dark)):(o=V(e),r={...o});let s=t.persist?k:ee,i=(u=s.get(n))!=null?u:{light:{},dark:{}},l=yt(i.light,o),c=yt(i.dark,r);s.set(n,i),t.persist&&Oe(),ne(n);let d=!1;return()=>{d||(d=!0,l(),c(),!Object.keys(i.light).length&&!Object.keys(i.dark).length&&s.get(n)===i&&s.delete(n),s===k&&Oe(),ne(n))}}function De(e){re||Ne(),_.forEach((n,o)=>{(!e||n.scope===e)&&_.delete(o)});let t=e?k.delete(e):k.size>0;e?ee.delete(e):(k.clear(),ee.clear()),t&&Oe(),ne(e)}function Mt(){Ne(),ne()}function Ne(){var n;re=!0,k.clear();let e=null;try{let o=(n=xt())==null?void 0:n.getItem(Ae);o&&(e=JSON.parse(o))}catch{}let t=e==null?void 0:e.scopes;if((e==null?void 0:e.v)===1&&t&&typeof t=="object")for(let[o,r]of Object.entries(t)){if(!gt(o))continue;let s=V(r==null?void 0:r.light),i=V(r==null?void 0:r.dark);(Object.keys(s).length||Object.keys(i).length)&&k.set(o,{light:s,dark:i})}}function Ie(e,t=document.documentElement){return getComputedStyle(t).getPropertyValue(Lt(e)).trim()}function Et(e,t){try{let n=JSON.parse(e!=null?e:"");if(n!==null&&typeof n=="object"&&!Array.isArray(n))return n}catch{}return te(`${t} must be a JSON object of tokens`),null}function Mn(){_.forEach((e,t)=>{t.isConnected||(_.delete(t),Ce.add(t),e.undo())})}function bt(e){return!!e.dataset.fwTokensBound&&!Ce.has(e)}function Tt(e,t,n){e.dataset.fwTokensBound="1",Ce.delete(e),e.isConnected&&_.set(e,{scope:t,undo:n})}function Pe(e=document){re||Mt(),Mn(),e.querySelectorAll('script[type="application/json"][data-fw-theme-tokens]').forEach(t=>{var i;if(bt(t))return;let n=Et(t.textContent,"data-fw-theme-tokens");if(t.dataset.fwTokensBound="1",!n)return;let o=t.hasAttribute("data-fw-persist"),r=(i=t.dataset.fwScope)!=null?i:oe,s=W(n,{scope:r,persist:o});o||Tt(t,r,s)}),e.querySelectorAll("[data-fw-theme-scope]").forEach(t=>{var s;if(bt(t))return;let n=Et(t.getAttribute("data-fw-theme-scope"),"data-fw-theme-scope");if(t.dataset.fwTokensBound="1",!n)return;let o=(s=t.getAttribute("data-fw-scope-id"))!=null?s:`fw-s${++En}`;t.setAttribute("data-fw-scope-id",o);let r=`[data-fw-scope-id="${o}"]`;Tt(t,r,W(n,{scope:r}))})}var Be="fw-theme",kn="fw:themechange",kt="data-fw-theme-init";function qe(){try{return window.localStorage}catch{return null}}function Re(){var t;let e=document.documentElement.getAttribute("data-theme");return e==="dark"||e==="light"?e:(t=window.matchMedia)!=null&&t.call(window,"(prefers-color-scheme: dark)").matches?"dark":"light"}function se(e,{persist:t=!0}={}){var n;document.documentElement.setAttribute("data-theme",e),t&&((n=qe())==null||n.setItem(Be,e)),document.querySelectorAll("[data-fw-theme-toggle]").forEach(o=>At(o,e)),document.dispatchEvent(new CustomEvent(kn,{detail:e}))}function Ht(){let e=Re()==="dark"?"light":"dark";return se(e),e}function At(e,t){e.setAttribute("aria-pressed",String(t==="dark"));let n=t==="dark"?e.dataset.fwThemeIconDark:e.dataset.fwThemeIconLight,o=t==="dark"?e.dataset.fwThemeLabelDark:e.dataset.fwThemeLabelLight;if(n!==void 0){let r=`<span class="fw-icon" aria-hidden="true">${X(n)}</span>`;e.innerHTML=o!==void 0?`${r} ${o}`:r}else o!==void 0&&(e.textContent=o)}function Hn(e){let t=typeof requestAnimationFrame=="function"?requestAnimationFrame:n=>setTimeout(n,16);t(()=>t(e))}function $e(e=document){var r,s,i,l;let t=(r=qe())==null?void 0:r.getItem(Be),n=t==="dark"||t==="light"?t:Re(),o=document.documentElement;o.setAttribute(kt,""),se(n,{persist:!1}),Hn(()=>o.removeAttribute(kt)),e.querySelectorAll("[data-fw-theme-toggle]").forEach(c=>{c.dataset.fwBound||(c.dataset.fwBound="1",At(c,n),c.addEventListener("click",()=>Ht()))}),(l=(s=window.matchMedia)==null?void 0:(i=s.call(window,"(prefers-color-scheme: dark)")).addEventListener)==null||l.call(i,"change",c=>{var d;(d=qe())!=null&&d.getItem(Be)||se(c.matches?"dark":"light",{persist:!1})})}var le={};b(le,{combobox:()=>St,init:()=>Fe});var An=0;function Z(e){return e.replace(/[&<>"']/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[t])}function St(e,t={}){var w,B,C,ut;let n=(w=t.filter)!=null?w:((f,y)=>f.toLowerCase().includes(y)),o=(B=t.emptyText)!=null?B:"No matches",r=(C=t.options)!=null?C:Sn(e),s=[],i=-1,l=!1,c=e.getAttribute("list");c&&(e.dataset.fwList=c,e.removeAttribute("list"));let d=(ut=e.closest(".fw-combobox"))!=null?ut:On(e),a=document.createElement("ul"),u=`fw-combobox-${++An}`;a.className="fw-combobox-list",a.id=u,a.setAttribute("role","listbox"),d.appendChild(a),e.setAttribute("role","combobox"),e.setAttribute("aria-autocomplete","list"),e.setAttribute("aria-expanded","false"),e.setAttribute("aria-controls",u),e.autocomplete="off";function h(f){s=f,i=-1;let y=e.value.trim().toLowerCase();if(f.length===0){a.innerHTML=`<li class="fw-combobox-empty">${Z(o)}</li>`;return}a.innerHTML=f.map((M,F)=>{let j=y?M.toLowerCase().indexOf(y):-1,un=j>-1?`${Z(M.slice(0,j))}<b>${Z(M.slice(j,j+y.length))}</b>${Z(M.slice(j+y.length))}`:Z(M);return`<li class="fw-combobox-option" role="option" id="${u}-${F}" data-index="${F}" aria-selected="false">${un}</li>`}).join("")}function m(){let f=e.value.trim().toLowerCase();h(f?r.filter(y=>n(y,f)):r),a.classList.add("is-open"),e.setAttribute("aria-expanded","true")}function p(){a.classList.remove("is-open"),e.setAttribute("aria-expanded","false"),e.removeAttribute("aria-activedescendant"),i=-1}function v(f){let y=a.querySelectorAll(".fw-combobox-option");y.forEach(F=>{F.classList.remove("is-active"),F.setAttribute("aria-selected","false")});let M=y[f];M&&(M.classList.add("is-active"),M.setAttribute("aria-selected","true"),M.scrollIntoView({block:"nearest"}),e.setAttribute("aria-activedescendant",M.id)),i=f}function x(f){var y;e.value=f,p(),l=!0,e.focus(),e.dispatchEvent(new Event("input",{bubbles:!0})),e.dispatchEvent(new Event("change",{bubbles:!0})),l=!1,(y=t.onSelect)==null||y.call(t,f)}let g=()=>{l||m()},E=()=>{l||m()},S=f=>{f.key==="ArrowDown"?(f.preventDefault(),a.classList.contains("is-open")||m(),v(Math.min(i+1,s.length-1))):f.key==="ArrowUp"?(f.preventDefault(),v(Math.max(i-1,0))):f.key==="Enter"?i>-1&&s[i]!==void 0&&(f.preventDefault(),x(s[i])):f.key==="Escape"&&p()},O=f=>{let y=f.target.closest(".fw-combobox-option");y&&(f.preventDefault(),x(s[Number(y.dataset.index)]))},$=f=>{d.contains(f.target)||p()};return e.addEventListener("input",g),e.addEventListener("focus",E),e.addEventListener("keydown",S),a.addEventListener("mousedown",O),document.addEventListener("click",$),{open:m,close:p,setOptions(f){r=f,a.classList.contains("is-open")&&m()},destroy(){e.removeEventListener("input",g),e.removeEventListener("focus",E),e.removeEventListener("keydown",S),a.removeEventListener("mousedown",O),document.removeEventListener("click",$),a.remove(),e.dataset.fwList&&e.setAttribute("list",e.dataset.fwList),delete e.dataset.fwBound}}}function Sn(e){var o;let t=(o=e.getAttribute("list"))!=null?o:e.dataset.fwList,n=t?document.getElementById(t):null;return n?Array.from(n.querySelectorAll("option")).map(r=>r.value||r.textContent||""):[]}function On(e){var n;let t=document.createElement("div");return t.className="fw-combobox",(n=e.parentNode)==null||n.insertBefore(t,e),t.appendChild(e),t}function Fe(e=document){e.querySelectorAll("input[data-fw-combobox]").forEach(t=>{t.dataset.fwBound||(t.dataset.fwBound="1",St(t))})}var ce={};b(ce,{bindOutsideClickToClose:()=>Ve,close:()=>D,init:()=>Ze,open:()=>We,toggle:()=>Dn});var N="is-open",Ct="fw-scroll-lock",I=[],je=new WeakMap,Cn='a[href], button:not([disabled]), input:not([disabled]):not([type="hidden"]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';function Ke(e){return typeof e=="string"?document.getElementById(e):e}function _e(e){if(e.classList.contains("fw-modal-overlay"))return e;let t=e.id?document.querySelector(`[data-fw-drawer-overlay="${e.id}"]`):null;if(t)return t;let n=e.previousElementSibling;return n instanceof HTMLElement&&n.classList.contains("fw-drawer-overlay")?n:null}function We(e,t=(n=>(n=document.activeElement)!=null?n:null)()){var s;let o=Ke(e);if(!o||o.classList.contains(N))return;o.classList.add(N),(s=_e(o))==null||s.classList.add(N),je.set(o,t),I.push(o),document.body.classList.add(Ct),o.dispatchEvent(new CustomEvent("fw:open",{bubbles:!0}));let r=o.querySelector(Cn);r?r.focus({preventScroll:!0}):(o.hasAttribute("tabindex")||o.setAttribute("tabindex","-1"),o.focus({preventScroll:!0}))}function D(e){var o,r,s;let t=Ke(e);if(!t||!t.classList.contains(N))return;t.classList.remove(N),(o=_e(t))==null||o.classList.remove(N);let n=I.indexOf(t);n>-1&&I.splice(n,1),I.length===0&&document.body.classList.remove(Ct),t.dispatchEvent(new CustomEvent("fw:close",{bubbles:!0})),(s=(r=je.get(t))==null?void 0:r.focus)==null||s.call(r,{preventScroll:!0}),je.delete(t)}function Dn(e){let t=Ke(e);t&&(t.classList.contains(N)?D:We)(t)}function Ve(e,t){let n=!1;e.addEventListener("mousedown",o=>{n=o.target===e}),e.addEventListener("click",o=>{o.target===e&&n&&t()})}var Ot=!1;function Ze(e=document){e.querySelectorAll("[data-fw-open-modal], [data-fw-open-drawer]").forEach(t=>{var o,r;if(t.dataset.fwBound)return;t.dataset.fwBound="1";let n=(r=(o=t.dataset.fwOpenModal)!=null?o:t.dataset.fwOpenDrawer)!=null?r:"";t.addEventListener("click",()=>We(n,t))}),e.querySelectorAll(".fw-modal-overlay").forEach(t=>{t.dataset.fwBound||(t.dataset.fwBound="1",Ve(t,()=>D(t)),t.querySelectorAll("[data-fw-close-modal]").forEach(n=>n.addEventListener("click",()=>D(t))))}),e.querySelectorAll(".fw-drawer").forEach(t=>{if(t.dataset.fwBound)return;t.dataset.fwBound="1";let n=_e(t);n&&Ve(n,()=>D(t)),t.querySelectorAll("[data-fw-close-drawer]").forEach(o=>o.addEventListener("click",()=>D(t)))}),Ot||(Ot=!0,document.addEventListener("keydown",t=>{t.key==="Escape"&&I.length&&D(I[I.length-1])}))}var ue={};b(ue,{close:()=>de,init:()=>ze,open:()=>Dt});var Ue="is-open";function Dt(e){let t=Ge(e);t&&(t.classList.add(Ue),e.setAttribute("aria-expanded","true"))}function de(e){let t=Ge(e);t&&(t.classList.remove(Ue),e.setAttribute("aria-expanded","false"))}function Ge(e){let t=e.getAttribute("aria-controls");return t?document.getElementById(t):null}function ze(e=document){e.querySelectorAll("[data-fw-nav-toggle]").forEach(t=>{var r,s;if(t.dataset.fwBound)return;t.dataset.fwBound="1";let n=Ge(t);if(!n)return;let o=(s=(r=t.closest(".fw-nav"))!=null?r:t.parentElement)!=null?s:t;t.addEventListener("click",i=>{i.stopPropagation(),(n.classList.contains(Ue)?de:Dt)(t)}),document.addEventListener("click",i=>{!o.contains(i.target)&&!n.contains(i.target)&&de(t)}),document.addEventListener("keydown",i=>{i.key==="Escape"&&de(t)})})}var fe={};b(fe,{init:()=>Je,resetAll:()=>Bt,resetButtonLoading:()=>Pt,setButtonLoading:()=>It});var U="fw-btn-loading";function It(e,t){var o,r;if(!e||e.classList.contains(U))return;e.dataset.fwOriginalHtml=e.innerHTML,e.disabled=!0,e.setAttribute("aria-busy","true"),e.classList.add(U);let n=(r=(o=t!=null?t:e.dataset.fwLoadingText)!=null?o:e.textContent)!=null?r:"";e.innerHTML='<span class="fw-spinner fw-spinner-sm" aria-hidden="true"></span>',e.appendChild(document.createTextNode(n))}function Pt(e){!e||!e.classList.contains(U)||(e.disabled=!1,e.removeAttribute("aria-busy"),e.classList.remove(U),e.dataset.fwOriginalHtml!==void 0&&(e.innerHTML=e.dataset.fwOriginalHtml,delete e.dataset.fwOriginalHtml))}function Bt(e=document){e.querySelectorAll(`.${U}`).forEach(Pt)}var Nt=!1;function Je(e=document){e.querySelectorAll("form[data-fw-loading]").forEach(t=>{t.dataset.fwBound||(t.dataset.fwBound="1",t.addEventListener("submit",()=>{let n=t.querySelector('button[type="submit"], button:not([type])');It(n)}))}),Nt||(Nt=!0,window.addEventListener("pageshow",t=>{t.persisted&&Bt()}))}var me={};b(me,{hide:()=>qt,show:()=>In});var T=null,q=null;function Nn(){return T&&T.isConnected||(T=document.createElement("div"),T.className="fw-toast",T.setAttribute("role","status"),T.setAttribute("aria-live","polite"),T.innerHTML='<span class="fw-toast-dot" aria-hidden="true"></span><span class="fw-toast-text"></span>',document.body.appendChild(T)),T}function In(e,{variant:t="success",duration:n=4e3}={}){let o=Nn();return o.className=`fw-toast${t==="success"?"":` fw-toast-${t}`}`,o.querySelector(".fw-toast-text").textContent=e,q&&clearTimeout(q),o.classList.remove("is-show"),o.offsetWidth,o.classList.add("is-show"),n>0&&(q=setTimeout(qt,n)),o}function qt(){T==null||T.classList.remove("is-show"),q&&clearTimeout(q),q=null}var ve={};b(ve,{getSelected:()=>tt,init:()=>nt,selectAll:()=>Vt,setRowSelected:()=>jt,sort:()=>Ye});var Pn="is-selected";function Xe(e,t,n){e.dispatchEvent(new CustomEvent(t,{bubbles:!0,detail:n}))}function Rt(e,t){var o,r,s;let n=e.cells[t];return(s=(r=n==null?void 0:n.dataset.fwSortValue)!=null?r:(o=n==null?void 0:n.textContent)==null?void 0:o.trim())!=null?s:""}function Bn(e,t){let n=Number(e),o=Number(t);return e!==""&&t!==""&&Number.isFinite(n)&&Number.isFinite(o)?n-o:e.localeCompare(t,void 0,{numeric:!0,sensitivity:"base"})}function Ft(e){var t,n;return Array.from((n=(t=e.tHead)==null?void 0:t.querySelectorAll("th[data-fw-sort]"))!=null?n:[])}function $t(e){return e.getAttribute("aria-sort")==="ascending"?"descending":"ascending"}function Ye(e,t,n){let o=e.tBodies[0];if(!o)return;let r=Array.from(o.rows),s=r.filter(d=>d.cells.length>t),i=r.filter(d=>d.cells.length<=t),l=n==="ascending"?1:-1;s.sort((d,a)=>l*Bn(Rt(d,t),Rt(a,t))),[...s,...i].forEach(d=>o.appendChild(d));let c=null;Ft(e).forEach(d=>{let a=d.cellIndex===t;d.setAttribute("aria-sort",a?n:"none"),a&&(c=d)}),c&&Xe(e,"fw:sort",{column:t,direction:n,th:c})}function pe(e){return Array.from(e.querySelectorAll("tbody input[data-fw-select-row]"))}function Qe(e,t){e.classList.toggle(Pn,t),e.setAttribute("aria-selected",t?"true":"false")}function et(e){let t=e.querySelector("[data-fw-select-all]");if(!t)return;let n=pe(e),o=n.filter(r=>r.checked).length;t.checked=o>0&&o===n.length,t.indeterminate=o>0&&o<n.length}function tt(e){return pe(e).filter(t=>t.checked).map(t=>t.closest("tr")).filter(t=>t!==null)}function jt(e,t){let n=e.querySelector("input[data-fw-select-row]");n&&(n.checked=t),Qe(e,t);let o=e.closest("table");o&&(et(o),Xe(o,"fw:select",{rows:tt(o),all:!1}))}function Vt(e,t){pe(e).forEach(n=>{n.checked=t;let o=n.closest("tr");o&&Qe(o,t)}),et(e),Xe(e,"fw:select",{rows:tt(e),all:!0})}function nt(e=document){e.querySelectorAll("table[data-fw-table]").forEach(t=>{t.dataset.fwBound||(t.dataset.fwBound="1",Ft(t).forEach(n=>{n.hasAttribute("aria-sort")||n.setAttribute("aria-sort","none")}),t.addEventListener("click",n=>{let o=n.target.closest("th[data-fw-sort]");o&&t.contains(o)&&Ye(t,o.cellIndex,$t(o))}),t.addEventListener("keydown",n=>{if(n.key!=="Enter"&&n.key!==" ")return;let o=n.target;if(!o.matches("th[data-fw-sort], th[data-fw-sort] .fw-table-sort"))return;let r=o.closest("th[data-fw-sort]");r&&(n.preventDefault(),Ye(t,r.cellIndex,$t(r)))}),t.addEventListener("change",n=>{let o=n.target;if(o.matches("[data-fw-select-all]"))Vt(t,o.checked);else if(o.matches("[data-fw-select-row]")){let r=o.closest("tr");r&&jt(r,o.checked)}}),pe(t).forEach(n=>{let o=n.closest("tr");o&&n.checked&&Qe(o,!0)}),et(t))})}var ye={};b(ye,{close:()=>we,collapseAll:()=>jn,expandAll:()=>Fn,init:()=>ot,open:()=>he,reveal:()=>Wt,toggle:()=>$n});var H=".fw-menu-group",_t=".fw-menu-link, .fw-menu-summary",qn="is-current";function Rn(e,t){e.dispatchEvent(new CustomEvent("fw:toggle",{bubbles:!0,detail:t}))}function he(e){e.open=!0}function we(e){e.open=!1}function $n(e,t){e.open=t!=null?t:!e.open}function Fn(e){e.querySelectorAll(H).forEach(he)}function jn(e){e.querySelectorAll(H).forEach(we)}function Wt(e){var n,o,r,s;let t=(o=(n=e.parentElement)==null?void 0:n.closest(H))!=null?o:null;for(;t;)he(t),t=(s=(r=t.parentElement)==null?void 0:r.closest(H))!=null?s:null}function Kt(e){e.querySelectorAll(H).forEach(t=>{let n=t.querySelector(":scope > .fw-menu-summary");if(!n)return;let o=!t.open&&t.querySelector("[aria-current]")!==null;n.classList.toggle(qn,o)})}function Zt(e){return e.matches(".fw-menu-summary")?e.closest(H):null}function Ut(e){var n,o,r;return(r=(o=((n=Zt(e))!=null?n:e).parentElement)==null?void 0:o.closest(H))!=null?r:null}function Vn(e){var t,n;for(let o=Ut(e);o;o=(n=(t=o.parentElement)==null?void 0:t.closest(H))!=null?n:null)if(!o.open)return!1;return!0}function Kn(e){return Array.from(e.querySelectorAll(_t)).filter(Vn)}function P(e){return e?(e.focus(),!0):!1}function _n(e){var n;let t=e.closest(".fw-menu-split");return(n=t==null?void 0:t.querySelector(":scope > .fw-menu-group > .fw-menu-summary"))!=null?n:null}function Wn(e,t){var l;if(t.altKey||t.ctrlKey||t.metaKey||t.shiftKey)return;let n=t.target instanceof HTMLElement&&t.target.matches(_t)?t.target:null;if(!n||!e.contains(n))return;let o=Kn(e),r=o.indexOf(n),s=Zt(n),i=!0;switch(t.key){case"ArrowDown":P(o[r+1]);break;case"ArrowUp":P(o[r-1]);break;case"Home":P(o[0]);break;case"End":P(o[o.length-1]);break;case"ArrowRight":s?s.open?s.contains((l=o[r+1])!=null?l:e)&&P(o[r+1]):he(s):P(_n(n));break;case"ArrowLeft":if(s!=null&&s.open)we(s);else{let c=Ut(n);P(c==null?void 0:c.querySelector(":scope > .fw-menu-summary"))}break;default:i=!1}i&&t.preventDefault()}function ot(e=document){e.querySelectorAll("[data-fw-menu]").forEach(t=>{if(t.dataset.fwBound)return;t.dataset.fwBound="1";let n=t.hasAttribute("data-fw-menu-exclusive");t.addEventListener("toggle",o=>{let r=o.target;!(r instanceof HTMLDetailsElement)||!r.matches(H)||(n&&r.open&&r.parentElement&&Array.from(r.parentElement.children).forEach(s=>{s!==r&&s instanceof HTMLDetailsElement&&s.matches(H)&&we(s)}),Kt(t),Rn(r,{group:r,open:r.open}))},!0),t.addEventListener("keydown",o=>Wn(t,o)),t.querySelectorAll("[aria-current]").forEach(Wt),Kt(t)})}var Te={};b(Te,{init:()=>it,popover:()=>Yt});var Zn={top:"bottom",bottom:"top",left:"right",right:"left"},R=10;function Un(e){switch(e){case"top-left":return{side:"top",align:"end"};case"top-right":return{side:"top",align:"start"};case"bottom-left":return{side:"bottom",align:"end"};case"bottom-right":return{side:"bottom",align:"start"};default:return{side:e,align:"center"}}}var Gn={start:"right",end:"left"};function zn(e,t){return t==="center"?e:`${e}-${Gn[t]}`}function rt(e,t,n,o){switch(e){case"top":return t.top;case"bottom":return o-t.bottom;case"left":return t.left;case"right":return n-t.right}}function Gt(e,t,n,o){switch(e){case"top":return t.top-o-n.height;case"bottom":return t.bottom+o;case"left":return t.left-o-n.width;case"right":return t.right+o}}function zt(e,t,n,o){switch(e){case"start":return t;case"end":return t+n-o;case"center":return t+n/2-o/2}}function Ee(e,t,n){return Math.min(Math.max(e,t),n>t?n:t)}function be(e,t,n={}){var E,S,O;let o=(E=n.placement)!=null?E:"bottom",r=(S=n.offset)!=null?S:8,s=(O=n.padding)!=null?O:8,i=e.getBoundingClientRect(),l=t.getBoundingClientRect(),c=window.innerWidth,d=window.innerHeight,{side:a,align:u}=Un(o),h=Zn[a],m=a==="top"||a==="bottom"?l.height:l.width,p=rt(a,i,c,d)-r<m&&rt(h,i,c,d)-r>rt(a,i,c,d)-r?h:a,v,x,g;return p==="top"||p==="bottom"?(v=Gt(p,i,l,r),x=Ee(zt(u,i.left,i.width,l.width),s,c-l.width-s),g=Ee(i.left+i.width/2-x,R,Math.max(l.width-R,R))):(x=Gt(p,i,l,r),v=Ee(zt("center",i.top,i.height,l.height),s,d-l.height-s),g=Ee(i.top+i.height/2-v,R,Math.max(l.height-R,R))),{placement:zn(p,u),top:v,left:x,arrowOffset:g}}function Jt(e,t){let n=i=>!e.some(l=>l.contains(i)),o=!1,r=i=>{o=n(i.target)},s=i=>{o&&n(i.target)&&t()};return document.addEventListener("mousedown",r,!0),document.addEventListener("click",s,!0),()=>{document.removeEventListener("mousedown",r,!0),document.removeEventListener("click",s,!0)}}var Jn='a[href], button:not([disabled]), input:not([disabled]):not([type="hidden"]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';function Yt(e,t,n={}){var O,$;let o=(O=n.placement)!=null?O:"bottom",r=($=n.offset)!=null?$:8;e.setAttribute("aria-haspopup","dialog"),e.setAttribute("aria-controls",t.id),e.setAttribute("aria-expanded","false"),t.setAttribute("role","dialog"),t.setAttribute("aria-modal","false"),t.classList.add("fw-popover");let s=t.parentNode,i=t.nextSibling,l=!1,c=null,d=0;function a(){let w=be(e,t,{placement:o,offset:r});t.style.top=`${w.top}px`,t.style.left=`${w.left}px`,t.setAttribute("data-placement",w.placement)}function u(){cancelAnimationFrame(d),d=requestAnimationFrame(a)}function h(w){w.key==="Escape"&&(w.stopPropagation(),v())}function m(w){let B=w.relatedTarget;B&&(t.contains(B)||e.contains(B))||setTimeout(()=>{if(!l)return;let C=document.activeElement;C===document.body||C===null||t.contains(C)||e.contains(C)||v()},0)}function p(){if(l)return;l=!0,t.parentNode!==document.body&&document.body.appendChild(t),a(),t.classList.add("is-open"),e.setAttribute("aria-expanded","true");let w=t.querySelector(Jn);w?w.focus({preventScroll:!0}):(t.hasAttribute("tabindex")||t.setAttribute("tabindex","-1"),t.focus({preventScroll:!0})),c=Jt([e,t],v),window.addEventListener("scroll",u,!0),window.addEventListener("resize",u),t.addEventListener("keydown",h),t.addEventListener("focusout",m),t.dispatchEvent(new CustomEvent("fw:open",{bubbles:!0}))}function v(){if(!l)return;l=!1,t.classList.remove("is-open"),e.setAttribute("aria-expanded","false"),c==null||c(),c=null,window.removeEventListener("scroll",u,!0),window.removeEventListener("resize",u),t.removeEventListener("keydown",h),t.removeEventListener("focusout",m),cancelAnimationFrame(d),i===null||i.parentNode===s?s.insertBefore(t,i):s.appendChild(t),t.dispatchEvent(new CustomEvent("fw:close",{bubbles:!0})),e.focus({preventScroll:!0})}function x(){(l?v:p)()}let g=()=>x(),E=Array.from(t.querySelectorAll("[data-fw-popover-close]")),S=()=>v();return e.addEventListener("click",g),E.forEach(w=>w.addEventListener("click",S)),{open:p,close:v,toggle:x,destroy(){l&&v(),e.removeEventListener("click",g),E.forEach(w=>w.removeEventListener("click",S)),delete e.dataset.fwBound}}}function it(e=document){e.querySelectorAll("[data-fw-popover-trigger]").forEach(t=>{var r;if(t.dataset.fwBound)return;let n=(r=t.dataset.fwPopoverTrigger)!=null?r:"",o=document.getElementById(n);if(!o){console.warn(`fernwell: popover trigger references missing panel #${n}`);return}t.dataset.fwBound="1",Yt(t,o,{placement:t.dataset.fwPopoverPlacement||void 0,offset:t.dataset.fwPopoverOffset?Number(t.dataset.fwPopoverOffset):void 0})})}var Le={};b(Le,{init:()=>lt,tooltip:()=>on});var Yn=400,Xn=300,Qn=100,z=new WeakMap,L=null,A=null,J=0,Y=0,at=0,Qt=0;function eo(){if(L&&L.isConnected)return L;let e=document.createElement("div");return e.className="fw-tooltip",e.id="fw-tooltip",e.setAttribute("role","tooltip"),document.body.appendChild(e),e.addEventListener("mouseenter",en),e.addEventListener("mouseleave",()=>nn()),L=e,e}function en(){clearTimeout(Y)}function tn(){if(!A||!L)return;let e=z.get(A);if(!e)return;let t=be(A,L,{placement:e.placement,offset:e.offset});L.style.top=`${t.top}px`,L.style.left=`${t.left}px`,L.style.setProperty("--fw-tooltip-arrow-pos",`${t.arrowOffset}px`),L.setAttribute("data-placement",t.placement)}function xe(){cancelAnimationFrame(at),at=requestAnimationFrame(tn)}function Xt(e){var o;if(clearTimeout(J),en(),!z.get(e))return;A=e;let n=eo();n.textContent=(o=e.dataset.fwTooltipText)!=null?o:"",tn(),n.classList.add("is-open"),window.addEventListener("scroll",xe,!0),window.addEventListener("resize",xe)}function G(){clearTimeout(J),clearTimeout(Y),L&&L.classList.remove("is-open"),window.removeEventListener("scroll",xe,!0),window.removeEventListener("resize",xe),cancelAnimationFrame(at),A&&(Qt=performance.now()),A=null}function st(e,t){clearTimeout(Y),clearTimeout(J);let n=z.get(e);n&&(t||performance.now()-Qt<Xn?Xt(e):J=window.setTimeout(()=>Xt(e),n.delay))}function nn(){clearTimeout(J),clearTimeout(Y),Y=window.setTimeout(G,Qn)}function on(e,t={}){var d,a,u;let n={placement:(d=t.placement)!=null?d:"top",offset:(a=t.offset)!=null?a:6,delay:(u=t.delay)!=null?u:Yn};z.set(e,n);let o=e.getAttribute("title");o!==null&&(e.dataset.fwTooltipText=o,e.removeAttribute("title")),e.setAttribute("aria-describedby","fw-tooltip");let r=()=>st(e,!1),s=()=>nn(),i=()=>st(e,!0),l=()=>{A===e&&G()},c=h=>{h.key==="Escape"&&A===e&&G()};return e.addEventListener("mouseenter",r),e.addEventListener("mouseleave",s),e.addEventListener("focus",i),e.addEventListener("blur",l),e.addEventListener("keydown",c),{show:()=>st(e,!0),hide:()=>{A===e&&G()},destroy(){A===e&&G(),e.removeEventListener("mouseenter",r),e.removeEventListener("mouseleave",s),e.removeEventListener("focus",i),e.removeEventListener("blur",l),e.removeEventListener("keydown",c),e.removeAttribute("aria-describedby"),e.dataset.fwTooltipText!==void 0&&(e.setAttribute("title",e.dataset.fwTooltipText),delete e.dataset.fwTooltipText),z.delete(e),delete e.dataset.fwBound}}}function lt(e=document){e.querySelectorAll("[data-fw-tooltip]").forEach(t=>{t.dataset.fwBound||(t.dataset.fwBound="1",on(t,{placement:t.dataset.fwTooltipPlacement||void 0,offset:t.dataset.fwTooltipOffset?Number(t.dataset.fwTooltipOffset):void 0,delay:t.dataset.fwTooltipDelay?Number(t.dataset.fwTooltipDelay):void 0}))})}var Me={};b(Me,{init:()=>dt,tabs:()=>ln});function to(e,t){e.dispatchEvent(new CustomEvent("fw:tabs-change",{bubbles:!0,detail:t}))}function ge(e){return e.getAttribute("aria-disabled")==="true"}function ct(e){let t=e.getAttribute("aria-controls");return t?document.getElementById(t):null}function rn(e){return e instanceof Element?e.closest('[role="tab"]'):null}function sn(e,t,n){for(let o=1;o<=e.length;o++){let r=((t+n*o)%e.length+e.length)%e.length;if(!ge(e[r]))return e[r]}}function an(e){return e.find(t=>!ge(t))}function no(e){for(let t=e.length-1;t>=0;t--)if(!ge(e[t]))return e[t]}function ln(e,t={}){var d;let n=(d=t.activation)!=null?d:"automatic";function o(){return Array.from(e.querySelectorAll('[role="tab"]'))}function r(a,u=!1){if(ge(a))return;o().forEach(m=>{let p=m===a;m.setAttribute("aria-selected",String(p)),m.tabIndex=p?0:-1;let v=ct(m);v&&(v.hidden=!p)}),u&&a.focus();let h=ct(a);h&&to(e,{tab:a,panel:h})}function s(a){o().forEach(u=>{u.tabIndex=u===a?0:-1}),a.focus()}function i(){var h;let a=o(),u=(h=a.find(m=>m.getAttribute("aria-selected")==="true"))!=null?h:an(a);a.forEach(m=>{let p=m===u;m.setAttribute("aria-selected",String(p)),m.tabIndex=p?0:-1;let v=ct(m);v&&(v.hidden=!p)})}function l(a){let u=rn(a.target);u&&e.contains(u)&&r(u,!1)}function c(a){let u=rn(a.target);if(!u)return;let h=o(),m=h.indexOf(u);if(m===-1)return;if(a.key===" "||a.key==="Enter"){n==="manual"&&(a.preventDefault(),r(u,!1));return}let p=e.getAttribute("aria-orientation")==="vertical",v=getComputedStyle(e).direction==="rtl",x=p?"ArrowDown":v?"ArrowLeft":"ArrowRight",g=p?"ArrowUp":v?"ArrowRight":"ArrowLeft",E;if(a.key===x)E=sn(h,m,1);else if(a.key===g)E=sn(h,m,-1);else if(a.key==="Home")E=an(h);else if(a.key==="End")E=no(h);else return;E&&(a.preventDefault(),n==="manual"?s(E):r(E,!0))}return e.addEventListener("click",l),e.addEventListener("keydown",c),i(),{activate:r,destroy(){e.removeEventListener("click",l),e.removeEventListener("keydown",c),delete e.dataset.fwBound}}}function dt(e=document){e.querySelectorAll('[role="tablist"][data-fw-tabs]').forEach(t=>{if(t.dataset.fwBound)return;t.dataset.fwBound="1";let n=t.dataset.fwTabsActivation==="manual"?"manual":"automatic";ln(t,{activation:n})})}var cn="0.5.0";function dn(e=document){$e(e),Pe(e),Fe(e),Ze(e),ze(e),Je(e),nt(e),ot(e),He(e),it(e),lt(e),dt(e)}var oo={version:cn,init:dn,theme:ae,tokens:ie,combobox:le,modal:ce,nav:ue,loading:fe,toast:me,table:ve,menu:ye,icon:Q,popover:Te,tooltip:Le,tabs:Me},ro=oo;
4
6
  //# sourceMappingURL=fernwell.cjs.map