fernwell 0.4.0 → 0.6.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 +39 -0
- package/README.md +52 -10
- package/dist/_position.d.cts +73 -0
- package/dist/_tokenData.d.cts +12 -0
- package/dist/_tokenData.d.ts +12 -0
- package/dist/combobox.d.cts +28 -0
- package/dist/css.d.cts +2 -0
- package/dist/css.d.ts +2 -0
- package/dist/fernwell.cjs +5 -3
- package/dist/fernwell.cjs.map +4 -4
- package/dist/fernwell.css +211 -5
- package/dist/fernwell.esm.js +5 -3
- package/dist/fernwell.esm.js.map +4 -4
- package/dist/fernwell.iife.js +5 -3
- package/dist/fernwell.iife.js.map +4 -4
- package/dist/fernwell.min.css +2 -2
- package/dist/fernwell.tokens.css +22 -1
- package/dist/icon.d.cts +30 -0
- package/dist/icon.d.ts +14 -0
- package/dist/iife.d.cts +2 -0
- package/dist/iife.d.ts +2 -2
- package/dist/index.d.cts +61 -0
- package/dist/index.d.ts +30 -23
- package/dist/loading.d.cts +5 -0
- package/dist/menu.d.cts +49 -0
- package/dist/modal.d.cts +13 -0
- package/dist/nav.d.cts +3 -0
- package/dist/number.d.cts +31 -0
- package/dist/number.d.ts +31 -0
- package/dist/popover.d.cts +48 -0
- package/dist/popover.d.ts +1 -1
- package/dist/table.d.cts +40 -0
- package/dist/tabs.d.cts +43 -0
- package/dist/theme.d.cts +12 -0
- package/dist/theme.d.ts +2 -29
- package/dist/toast.d.cts +14 -0
- package/dist/tokens.d.cts +105 -0
- package/dist/tokens.d.ts +105 -0
- package/dist/tokens.json +12 -0
- package/dist/tooltip.d.cts +51 -0
- package/dist/tooltip.d.ts +1 -1
- package/package.json +31 -9
package/dist/number.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type NumberMode = 'stepper' | 'slider';
|
|
2
|
+
export interface NumberOptions {
|
|
3
|
+
/** `stepper` (default) or `slider`. */
|
|
4
|
+
mode?: NumberMode;
|
|
5
|
+
min?: number;
|
|
6
|
+
max?: number;
|
|
7
|
+
/** Interval for buttons, arrow keys and the slider. Default 1. */
|
|
8
|
+
step?: number;
|
|
9
|
+
/** Suffix for the slider readout and `aria-valuetext` (e.g. `%`, `px`). */
|
|
10
|
+
unit?: string;
|
|
11
|
+
/** Accessible names for the stepper buttons. */
|
|
12
|
+
decreaseLabel?: string;
|
|
13
|
+
increaseLabel?: string;
|
|
14
|
+
onChange?: (value: number) => void;
|
|
15
|
+
}
|
|
16
|
+
export interface NumberInstance {
|
|
17
|
+
/** Current value, or `NaN` when a stepper field is empty. */
|
|
18
|
+
getValue(): number;
|
|
19
|
+
/** Clamp, round to the step precision, and emit change events. */
|
|
20
|
+
setValue(value: number): void;
|
|
21
|
+
stepUp(count?: number): void;
|
|
22
|
+
stepDown(count?: number): void;
|
|
23
|
+
destroy(): void;
|
|
24
|
+
}
|
|
25
|
+
export interface NumberChangeDetail {
|
|
26
|
+
value: number;
|
|
27
|
+
mode: NumberMode;
|
|
28
|
+
}
|
|
29
|
+
export declare function numberInput(input: HTMLInputElement, opts?: NumberOptions): NumberInstance;
|
|
30
|
+
/** Enhance every `[data-fw-number]` input under `root`. */
|
|
31
|
+
export declare function init(root?: ParentNode): void;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Popover — a non-modal floating panel anchored to its trigger.
|
|
3
|
+
*
|
|
4
|
+
* <button type="button" data-fw-popover-trigger="settings-pop">Settings</button>
|
|
5
|
+
*
|
|
6
|
+
* <div class="fw-popover" id="settings-pop">
|
|
7
|
+
* <div class="fw-popover-head">
|
|
8
|
+
* <h3 class="fw-popover-title">Settings</h3>
|
|
9
|
+
* <button type="button" class="fw-icon-btn" data-fw-popover-close aria-label="Close">
|
|
10
|
+
* <span data-fw-icon="close" aria-hidden="true"></span>
|
|
11
|
+
* </button>
|
|
12
|
+
* </div>
|
|
13
|
+
* <div class="fw-popover-body">…</div>
|
|
14
|
+
* </div>
|
|
15
|
+
*
|
|
16
|
+
* Optional trigger attributes: `data-fw-popover-placement` ("top" | "bottom" |
|
|
17
|
+
* "left" | "right" — centered on the trigger's cross-axis midpoint — or a
|
|
18
|
+
* corner variant "top-left" | "top-right" | "bottom-left" | "bottom-right"
|
|
19
|
+
* that leans the panel toward that corner instead of centering it, e.g. to
|
|
20
|
+
* steer it clear of something else on the page; default "bottom"),
|
|
21
|
+
* `data-fw-popover-offset` (px, default 8).
|
|
22
|
+
*
|
|
23
|
+
* While open, the panel is moved to the end of <body> so its `position: fixed`
|
|
24
|
+
* containing block is always the viewport — not an ancestor with `transform`,
|
|
25
|
+
* `filter` or `contain` (e.g. a card, or another modal) that would otherwise
|
|
26
|
+
* break the positioning math — and moved back to its authored location on close.
|
|
27
|
+
*
|
|
28
|
+
* This is a non-modal dialog (`aria-modal="false"`), so per WAI-ARIA APG it
|
|
29
|
+
* does NOT trap Tab — focus is allowed to move naturally out of the panel.
|
|
30
|
+
* Closing on focus leaving the panel/trigger (`focusout`) does the equivalent
|
|
31
|
+
* job without fighting the surrounding page's tab order.
|
|
32
|
+
*
|
|
33
|
+
* Events (bubbling, from the panel): `fw:open`, `fw:close`.
|
|
34
|
+
*/
|
|
35
|
+
import { type Placement } from './_position.cjs';
|
|
36
|
+
export interface PopoverOptions {
|
|
37
|
+
placement?: Placement;
|
|
38
|
+
offset?: number;
|
|
39
|
+
}
|
|
40
|
+
export interface PopoverInstance {
|
|
41
|
+
open(): void;
|
|
42
|
+
close(): void;
|
|
43
|
+
toggle(): void;
|
|
44
|
+
destroy(): void;
|
|
45
|
+
}
|
|
46
|
+
export declare function popover(trigger: HTMLElement, panel: HTMLElement, opts?: PopoverOptions): PopoverInstance;
|
|
47
|
+
/** Wire every `[data-fw-popover-trigger]` under `root`. */
|
|
48
|
+
export declare function init(root?: ParentNode): void;
|
package/dist/popover.d.ts
CHANGED
package/dist/table.d.cts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Table — client-side column sorting and row selection for `.fw-table`.
|
|
3
|
+
*
|
|
4
|
+
* <table class="fw-table" data-fw-table>
|
|
5
|
+
* <thead><tr>
|
|
6
|
+
* <th class="fw-table-select"><label class="fw-checkbox"><input type="checkbox" data-fw-select-all>…</label></th>
|
|
7
|
+
* <th scope="col" aria-sort="none" data-fw-sort><button type="button" class="fw-table-sort">Client</button></th>
|
|
8
|
+
* </tr></thead>
|
|
9
|
+
* <tbody><tr>
|
|
10
|
+
* <td class="fw-table-select"><label class="fw-checkbox"><input type="checkbox" data-fw-select-row>…</label></td>
|
|
11
|
+
* <td data-fw-sort-value="1250">$1,250.00</td>
|
|
12
|
+
* </tr></tbody>
|
|
13
|
+
* </table>
|
|
14
|
+
*
|
|
15
|
+
* Clicking a `th[data-fw-sort]` cycles aria-sort (none → ascending → descending → …)
|
|
16
|
+
* and reorders the first <tbody>. `data-fw-sort-value` on a cell overrides its
|
|
17
|
+
* text for comparison (numbers, ISO dates). Rows without a cell in the sorted
|
|
18
|
+
* column (e.g. a colspan empty row) stay at the bottom.
|
|
19
|
+
*
|
|
20
|
+
* Row checkboxes toggle `.is-selected` / aria-selected on their row and keep the
|
|
21
|
+
* select-all checkbox in sync (checked / indeterminate).
|
|
22
|
+
*
|
|
23
|
+
* Events (bubbling, on the <table>): `fw:sort` { column, direction, th } and
|
|
24
|
+
* `fw:select` { rows, all }.
|
|
25
|
+
*/
|
|
26
|
+
export type SortDirection = 'ascending' | 'descending';
|
|
27
|
+
export interface SortDetail {
|
|
28
|
+
column: number;
|
|
29
|
+
direction: SortDirection;
|
|
30
|
+
th: HTMLTableCellElement;
|
|
31
|
+
}
|
|
32
|
+
export interface SelectDetail {
|
|
33
|
+
rows: HTMLTableRowElement[];
|
|
34
|
+
all: boolean;
|
|
35
|
+
}
|
|
36
|
+
export declare function sort(table: HTMLTableElement, column: number, direction: SortDirection): void;
|
|
37
|
+
export declare function getSelected(table: HTMLTableElement): HTMLTableRowElement[];
|
|
38
|
+
export declare function setRowSelected(row: HTMLTableRowElement, on: boolean): void;
|
|
39
|
+
export declare function selectAll(table: HTMLTableElement, on: boolean): void;
|
|
40
|
+
export declare function init(root?: ParentNode): void;
|
package/dist/tabs.d.cts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tabs — a single-selection tablist that switches between panels.
|
|
3
|
+
*
|
|
4
|
+
* <div class="fw-tablist" role="tablist" aria-label="Account settings" data-fw-tabs>
|
|
5
|
+
* <button type="button" class="fw-tab" id="tab-profile" role="tab" aria-selected="true" aria-controls="panel-profile">Profile</button>
|
|
6
|
+
* <button type="button" class="fw-tab" id="tab-billing" role="tab" aria-selected="false" aria-controls="panel-billing">Billing</button>
|
|
7
|
+
* </div>
|
|
8
|
+
* <div class="fw-tabpanel" id="panel-profile" role="tabpanel" aria-labelledby="tab-profile" tabindex="0">…</div>
|
|
9
|
+
* <div class="fw-tabpanel" id="panel-billing" role="tabpanel" aria-labelledby="tab-billing" tabindex="0" hidden>…</div>
|
|
10
|
+
*
|
|
11
|
+
* Author `aria-selected="true"` on the tab that should start selected (the
|
|
12
|
+
* rest `"false"`); `init()` derives roving `tabIndex` (0 on the selected tab,
|
|
13
|
+
* -1 on the rest) and each panel's `hidden` from that. Falls back to the
|
|
14
|
+
* first non-disabled tab if none is marked selected. A tab may carry
|
|
15
|
+
* `aria-disabled="true"` to make it non-interactive — it is skipped by
|
|
16
|
+
* click, arrow-key, and Home/End navigation.
|
|
17
|
+
*
|
|
18
|
+
* `data-fw-tabs-activation="manual"` on the tablist switches from the
|
|
19
|
+
* default "automatic" activation (arrow keys move focus AND select) to
|
|
20
|
+
* "manual" (arrow keys only move focus; Space/Enter selects the focused
|
|
21
|
+
* tab) — use manual when selecting a tab is expensive (e.g. triggers a
|
|
22
|
+
* fetch).
|
|
23
|
+
*
|
|
24
|
+
* `aria-orientation="vertical"` on the tablist swaps the primary arrow keys
|
|
25
|
+
* from Left/Right to Up/Down, per WAI-ARIA APG. Left/Right invert under RTL.
|
|
26
|
+
*
|
|
27
|
+
* Events (bubbling, from the tablist): `fw:tabs-change` { tab, panel }.
|
|
28
|
+
*/
|
|
29
|
+
export type TabsActivation = 'automatic' | 'manual';
|
|
30
|
+
export interface TabsOptions {
|
|
31
|
+
activation?: TabsActivation;
|
|
32
|
+
}
|
|
33
|
+
export interface TabsInstance {
|
|
34
|
+
activate(tab: HTMLElement, focus?: boolean): void;
|
|
35
|
+
destroy(): void;
|
|
36
|
+
}
|
|
37
|
+
export interface TabsChangeDetail {
|
|
38
|
+
tab: HTMLElement;
|
|
39
|
+
panel: HTMLElement;
|
|
40
|
+
}
|
|
41
|
+
export declare function tabs(tablist: HTMLElement, opts?: TabsOptions): TabsInstance;
|
|
42
|
+
/** Wire every `[role="tablist"][data-fw-tabs]` under `root`. */
|
|
43
|
+
export declare function init(root?: ParentNode): void;
|
package/dist/theme.d.cts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type Theme = 'light' | 'dark';
|
|
2
|
+
/** The theme currently applied to <html>, or the system preference if none is set. */
|
|
3
|
+
export declare function get(): Theme;
|
|
4
|
+
/** Apply a theme, persist it, and update every toggle button on the page. */
|
|
5
|
+
export declare function set(theme: Theme, { persist }?: {
|
|
6
|
+
persist?: boolean;
|
|
7
|
+
}): void;
|
|
8
|
+
export declare function toggle(): Theme;
|
|
9
|
+
/** Apply the stored / system theme and wire every `[data-fw-theme-toggle]` under `root`. */
|
|
10
|
+
export declare function init(root?: ParentNode): void;
|
|
11
|
+
export { setTokens, resetTokens, getToken } from './tokens.cjs';
|
|
12
|
+
export type { TokenOverrides, TokenThemeOverrides, SetTokensOptions } from './tokens.cjs';
|
package/dist/theme.d.ts
CHANGED
|
@@ -8,32 +8,5 @@ export declare function set(theme: Theme, { persist }?: {
|
|
|
8
8
|
export declare function toggle(): Theme;
|
|
9
9
|
/** Apply the stored / system theme and wire every `[data-fw-theme-toggle]` under `root`. */
|
|
10
10
|
export declare function init(root?: ParentNode): void;
|
|
11
|
-
|
|
12
|
-
export type TokenOverrides
|
|
13
|
-
/** Split form: set different values per theme instead of both at once. */
|
|
14
|
-
export interface TokenThemeOverrides {
|
|
15
|
-
light?: TokenOverrides;
|
|
16
|
-
dark?: TokenOverrides;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Override Fernwell design tokens at runtime — every derived token (hovers,
|
|
20
|
-
* tints, focus ring, glows) follows automatically, in both themes:
|
|
21
|
-
*
|
|
22
|
-
* theme.setTokens({ primary: '#2266ff' });
|
|
23
|
-
*
|
|
24
|
-
* Pass `{ light, dark }` to set different values per theme, and `scope` to
|
|
25
|
-
* theme a subtree instead of the whole page:
|
|
26
|
-
*
|
|
27
|
-
* theme.setTokens({ light: { primary: '#2266ff' }, dark: { primary: '#5c9dff' } });
|
|
28
|
-
* theme.setTokens({ secondary: '#a33' }, { scope: '.tenant-acme' });
|
|
29
|
-
*
|
|
30
|
-
* Calls accumulate (merge into whatever overrides are already set for that
|
|
31
|
-
* scope) — call `resetTokens()` to clear them.
|
|
32
|
-
*/
|
|
33
|
-
export declare function setTokens(tokens: TokenOverrides | TokenThemeOverrides, opts?: {
|
|
34
|
-
scope?: string;
|
|
35
|
-
}): void;
|
|
36
|
-
/** Clear runtime token overrides — for one `scope`, or every scope if omitted. */
|
|
37
|
-
export declare function resetTokens(scope?: string): void;
|
|
38
|
-
/** The live, resolved value of a token (e.g. `getToken('primary')` → `'#ffc145'`), including runtime overrides. */
|
|
39
|
-
export declare function getToken(name: string, el?: Element): string;
|
|
11
|
+
export { setTokens, resetTokens, getToken } from './tokens.js';
|
|
12
|
+
export type { TokenOverrides, TokenThemeOverrides, SetTokensOptions } from './tokens.js';
|
package/dist/toast.d.cts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Toast — a single, reused, bottom-right notice.
|
|
3
|
+
*
|
|
4
|
+
* toast.show('Invoice sent');
|
|
5
|
+
* toast.show('That card was declined', { variant: 'error', duration: 6000 });
|
|
6
|
+
*/
|
|
7
|
+
export type ToastVariant = 'success' | 'error' | 'info' | 'pending';
|
|
8
|
+
export interface ToastOptions {
|
|
9
|
+
variant?: ToastVariant;
|
|
10
|
+
/** ms before it hides; 0 keeps it until `hide()` or the next `show()`. */
|
|
11
|
+
duration?: number;
|
|
12
|
+
}
|
|
13
|
+
export declare function show(message: string, { variant, duration }?: ToastOptions): HTMLElement;
|
|
14
|
+
export declare function hide(): void;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tokens — override any `--fw-*` design token from JS, for the whole app or
|
|
3
|
+
* for one page or subtree, in both themes or per-theme. Every derived token
|
|
4
|
+
* (hovers, tints, focus ring, glows) follows the seed it is built from.
|
|
5
|
+
*
|
|
6
|
+
* tokens.setTokens({ primary: '#2266ff' }); // both themes
|
|
7
|
+
* tokens.setTokens({ light: { primary: '#2266ff' }, dark: { primary: '#5c9dff' } });
|
|
8
|
+
* tokens.setTokens({ secondary: '#a33' }, { scope: '.tenant-acme' }); // one subtree
|
|
9
|
+
* const undo = tokens.setTokens({ primary: '#a33' }); undo(); // route-scoped
|
|
10
|
+
* tokens.setTokens({ primary: '#2266ff' }, { persist: true }); // app-wide
|
|
11
|
+
*
|
|
12
|
+
* Overrides render into one `<style data-fw-tokens>` appended to `<head>`:
|
|
13
|
+
* being unlayered and last, it wins over Fernwell's own `@layer fw-tokens`
|
|
14
|
+
* *and* any earlier static override in the consumer's stylesheet.
|
|
15
|
+
*
|
|
16
|
+
* Persistence — `persist: true` saves the scope to localStorage so every page
|
|
17
|
+
* of the app (and every reload) gets it. Without it an override lives only
|
|
18
|
+
* for the current page load, which is what "this page only" means in a
|
|
19
|
+
* multi-page app. Persisted overrides sit *below* page-level ones, so a page
|
|
20
|
+
* can still tweak an app-wide value. To apply them before first paint, inline
|
|
21
|
+
* this ahead of your CSS (next to the theme snippet — see theme.ts):
|
|
22
|
+
* <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>
|
|
23
|
+
*
|
|
24
|
+
* Declarative hooks (wired by `init()`):
|
|
25
|
+
*
|
|
26
|
+
* <script type="application/json" data-fw-theme-tokens>{"primary":"#2266ff"}</script>
|
|
27
|
+
* <script type="application/json" data-fw-theme-tokens data-fw-scope=".tenant-acme" data-fw-persist>{…}</script>
|
|
28
|
+
* <section data-fw-theme-scope='{"secondary":"#a33"}'>…</section>
|
|
29
|
+
*
|
|
30
|
+
* The `<script>` form takes the same shapes as `setTokens()` (flat map or
|
|
31
|
+
* `{ light, dark }`); `data-fw-scope` and `data-fw-persist` are optional. The
|
|
32
|
+
* element form themes that element's subtree only, and is never persisted.
|
|
33
|
+
* Both are undone on the next `init()` after their element is removed from
|
|
34
|
+
* the page; call the function `setTokens()` returns, or `resetTokens(scope)`,
|
|
35
|
+
* to undo one sooner.
|
|
36
|
+
*
|
|
37
|
+
* Events (on `document`): `fw:tokenschange` { scope } — `scope` is absent
|
|
38
|
+
* when every scope changed (a reset or a restore).
|
|
39
|
+
*/
|
|
40
|
+
import { type TokenName } from './_tokenData.cjs';
|
|
41
|
+
/** A token to override: a known name (autocompletes), a bare custom name, or a full `--fw-*` / `--custom` property. */
|
|
42
|
+
export type TokenKey = TokenName | (string & {});
|
|
43
|
+
/** Token name → CSS value. Bare names (`primary`) and full names (`--fw-primary`) both work. */
|
|
44
|
+
export type TokenOverrides = Partial<Record<TokenName, string>> & Record<string, string>;
|
|
45
|
+
/** Split form: set different values per theme instead of both at once. */
|
|
46
|
+
export interface TokenThemeOverrides {
|
|
47
|
+
light?: TokenOverrides;
|
|
48
|
+
dark?: TokenOverrides;
|
|
49
|
+
}
|
|
50
|
+
export interface SetTokensOptions {
|
|
51
|
+
/** A CSS selector to theme a subtree instead of the whole page. Default `:root`. */
|
|
52
|
+
scope?: string;
|
|
53
|
+
/** Save this scope to localStorage so it applies on every page and reload. Default `false`. */
|
|
54
|
+
persist?: boolean;
|
|
55
|
+
}
|
|
56
|
+
export interface TokensChangeDetail {
|
|
57
|
+
scope?: string;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Override Fernwell design tokens at runtime — every derived token (hovers,
|
|
61
|
+
* tints, focus ring, glows) follows automatically, in both themes:
|
|
62
|
+
*
|
|
63
|
+
* tokens.setTokens({ primary: '#2266ff' });
|
|
64
|
+
*
|
|
65
|
+
* Pass `{ light, dark }` to set different values per theme, `scope` to theme
|
|
66
|
+
* a subtree instead of the whole page, and `persist` to keep the scope for
|
|
67
|
+
* every page of the app:
|
|
68
|
+
*
|
|
69
|
+
* tokens.setTokens({ light: { primary: '#2266ff' }, dark: { primary: '#5c9dff' } });
|
|
70
|
+
* tokens.setTokens({ secondary: '#a33' }, { scope: '.tenant-acme' });
|
|
71
|
+
* tokens.setTokens({ primary: '#2266ff' }, { persist: true });
|
|
72
|
+
*
|
|
73
|
+
* Calls accumulate (merge into whatever overrides are already set for that
|
|
74
|
+
* scope) — call `resetTokens()` to clear them. The returned function undoes
|
|
75
|
+
* just this call, e.g. when a route unmounts; call it in reverse order if
|
|
76
|
+
* several calls touched the same token.
|
|
77
|
+
*/
|
|
78
|
+
export declare function setTokens(tokens: TokenOverrides | TokenThemeOverrides, opts?: SetTokensOptions): () => void;
|
|
79
|
+
/**
|
|
80
|
+
* Clear runtime token overrides — for one `scope`, or every scope if
|
|
81
|
+
* omitted. Persisted overrides are removed from localStorage too.
|
|
82
|
+
*/
|
|
83
|
+
export declare function resetTokens(scope?: string): void;
|
|
84
|
+
/**
|
|
85
|
+
* Reload persisted overrides from localStorage, replacing whatever persisted
|
|
86
|
+
* scopes are in memory. `init()` does this once for you; call it yourself
|
|
87
|
+
* only if something else rewrote storage.
|
|
88
|
+
*/
|
|
89
|
+
export declare function restoreTokens(): void;
|
|
90
|
+
/** The live, resolved value of a token (e.g. `getToken('primary')` → `'#ffc145'`), including runtime overrides. */
|
|
91
|
+
export declare function getToken(name: TokenKey, el?: Element): string;
|
|
92
|
+
/**
|
|
93
|
+
* Restore persisted overrides (once), then apply every declarative hook
|
|
94
|
+
* under `root` — `<script type="application/json" data-fw-theme-tokens>` and
|
|
95
|
+
* `data-fw-theme-scope="{…}"`. Safe to call again after injecting markup.
|
|
96
|
+
*
|
|
97
|
+
* Bound elements are marked `data-fw-tokens-bound`, not the shared `fwBound`
|
|
98
|
+
* the other enhancers use — a themed element is often also a table, modal or
|
|
99
|
+
* menu root, and must still be picked up by that component's own `init()`.
|
|
100
|
+
*
|
|
101
|
+
* A hook that isn't persisted is undone on the next `init()` after its element
|
|
102
|
+
* has been removed from the page, so route-scoped markup doesn't accumulate
|
|
103
|
+
* rules. (`data-fw-persist` hooks are app-wide and stay until `resetTokens()`.)
|
|
104
|
+
*/
|
|
105
|
+
export declare function init(root?: ParentNode): void;
|
package/dist/tokens.d.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tokens — override any `--fw-*` design token from JS, for the whole app or
|
|
3
|
+
* for one page or subtree, in both themes or per-theme. Every derived token
|
|
4
|
+
* (hovers, tints, focus ring, glows) follows the seed it is built from.
|
|
5
|
+
*
|
|
6
|
+
* tokens.setTokens({ primary: '#2266ff' }); // both themes
|
|
7
|
+
* tokens.setTokens({ light: { primary: '#2266ff' }, dark: { primary: '#5c9dff' } });
|
|
8
|
+
* tokens.setTokens({ secondary: '#a33' }, { scope: '.tenant-acme' }); // one subtree
|
|
9
|
+
* const undo = tokens.setTokens({ primary: '#a33' }); undo(); // route-scoped
|
|
10
|
+
* tokens.setTokens({ primary: '#2266ff' }, { persist: true }); // app-wide
|
|
11
|
+
*
|
|
12
|
+
* Overrides render into one `<style data-fw-tokens>` appended to `<head>`:
|
|
13
|
+
* being unlayered and last, it wins over Fernwell's own `@layer fw-tokens`
|
|
14
|
+
* *and* any earlier static override in the consumer's stylesheet.
|
|
15
|
+
*
|
|
16
|
+
* Persistence — `persist: true` saves the scope to localStorage so every page
|
|
17
|
+
* of the app (and every reload) gets it. Without it an override lives only
|
|
18
|
+
* for the current page load, which is what "this page only" means in a
|
|
19
|
+
* multi-page app. Persisted overrides sit *below* page-level ones, so a page
|
|
20
|
+
* can still tweak an app-wide value. To apply them before first paint, inline
|
|
21
|
+
* this ahead of your CSS (next to the theme snippet — see theme.ts):
|
|
22
|
+
* <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>
|
|
23
|
+
*
|
|
24
|
+
* Declarative hooks (wired by `init()`):
|
|
25
|
+
*
|
|
26
|
+
* <script type="application/json" data-fw-theme-tokens>{"primary":"#2266ff"}</script>
|
|
27
|
+
* <script type="application/json" data-fw-theme-tokens data-fw-scope=".tenant-acme" data-fw-persist>{…}</script>
|
|
28
|
+
* <section data-fw-theme-scope='{"secondary":"#a33"}'>…</section>
|
|
29
|
+
*
|
|
30
|
+
* The `<script>` form takes the same shapes as `setTokens()` (flat map or
|
|
31
|
+
* `{ light, dark }`); `data-fw-scope` and `data-fw-persist` are optional. The
|
|
32
|
+
* element form themes that element's subtree only, and is never persisted.
|
|
33
|
+
* Both are undone on the next `init()` after their element is removed from
|
|
34
|
+
* the page; call the function `setTokens()` returns, or `resetTokens(scope)`,
|
|
35
|
+
* to undo one sooner.
|
|
36
|
+
*
|
|
37
|
+
* Events (on `document`): `fw:tokenschange` { scope } — `scope` is absent
|
|
38
|
+
* when every scope changed (a reset or a restore).
|
|
39
|
+
*/
|
|
40
|
+
import { type TokenName } from './_tokenData.js';
|
|
41
|
+
/** A token to override: a known name (autocompletes), a bare custom name, or a full `--fw-*` / `--custom` property. */
|
|
42
|
+
export type TokenKey = TokenName | (string & {});
|
|
43
|
+
/** Token name → CSS value. Bare names (`primary`) and full names (`--fw-primary`) both work. */
|
|
44
|
+
export type TokenOverrides = Partial<Record<TokenName, string>> & Record<string, string>;
|
|
45
|
+
/** Split form: set different values per theme instead of both at once. */
|
|
46
|
+
export interface TokenThemeOverrides {
|
|
47
|
+
light?: TokenOverrides;
|
|
48
|
+
dark?: TokenOverrides;
|
|
49
|
+
}
|
|
50
|
+
export interface SetTokensOptions {
|
|
51
|
+
/** A CSS selector to theme a subtree instead of the whole page. Default `:root`. */
|
|
52
|
+
scope?: string;
|
|
53
|
+
/** Save this scope to localStorage so it applies on every page and reload. Default `false`. */
|
|
54
|
+
persist?: boolean;
|
|
55
|
+
}
|
|
56
|
+
export interface TokensChangeDetail {
|
|
57
|
+
scope?: string;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Override Fernwell design tokens at runtime — every derived token (hovers,
|
|
61
|
+
* tints, focus ring, glows) follows automatically, in both themes:
|
|
62
|
+
*
|
|
63
|
+
* tokens.setTokens({ primary: '#2266ff' });
|
|
64
|
+
*
|
|
65
|
+
* Pass `{ light, dark }` to set different values per theme, `scope` to theme
|
|
66
|
+
* a subtree instead of the whole page, and `persist` to keep the scope for
|
|
67
|
+
* every page of the app:
|
|
68
|
+
*
|
|
69
|
+
* tokens.setTokens({ light: { primary: '#2266ff' }, dark: { primary: '#5c9dff' } });
|
|
70
|
+
* tokens.setTokens({ secondary: '#a33' }, { scope: '.tenant-acme' });
|
|
71
|
+
* tokens.setTokens({ primary: '#2266ff' }, { persist: true });
|
|
72
|
+
*
|
|
73
|
+
* Calls accumulate (merge into whatever overrides are already set for that
|
|
74
|
+
* scope) — call `resetTokens()` to clear them. The returned function undoes
|
|
75
|
+
* just this call, e.g. when a route unmounts; call it in reverse order if
|
|
76
|
+
* several calls touched the same token.
|
|
77
|
+
*/
|
|
78
|
+
export declare function setTokens(tokens: TokenOverrides | TokenThemeOverrides, opts?: SetTokensOptions): () => void;
|
|
79
|
+
/**
|
|
80
|
+
* Clear runtime token overrides — for one `scope`, or every scope if
|
|
81
|
+
* omitted. Persisted overrides are removed from localStorage too.
|
|
82
|
+
*/
|
|
83
|
+
export declare function resetTokens(scope?: string): void;
|
|
84
|
+
/**
|
|
85
|
+
* Reload persisted overrides from localStorage, replacing whatever persisted
|
|
86
|
+
* scopes are in memory. `init()` does this once for you; call it yourself
|
|
87
|
+
* only if something else rewrote storage.
|
|
88
|
+
*/
|
|
89
|
+
export declare function restoreTokens(): void;
|
|
90
|
+
/** The live, resolved value of a token (e.g. `getToken('primary')` → `'#ffc145'`), including runtime overrides. */
|
|
91
|
+
export declare function getToken(name: TokenKey, el?: Element): string;
|
|
92
|
+
/**
|
|
93
|
+
* Restore persisted overrides (once), then apply every declarative hook
|
|
94
|
+
* under `root` — `<script type="application/json" data-fw-theme-tokens>` and
|
|
95
|
+
* `data-fw-theme-scope="{…}"`. Safe to call again after injecting markup.
|
|
96
|
+
*
|
|
97
|
+
* Bound elements are marked `data-fw-tokens-bound`, not the shared `fwBound`
|
|
98
|
+
* the other enhancers use — a themed element is often also a table, modal or
|
|
99
|
+
* menu root, and must still be picked up by that component's own `init()`.
|
|
100
|
+
*
|
|
101
|
+
* A hook that isn't persisted is undone on the next `init()` after its element
|
|
102
|
+
* has been removed from the page, so route-scoped markup doesn't accumulate
|
|
103
|
+
* rules. (`data-fw-persist` hooks are app-wide and stay until `resetTokens()`.)
|
|
104
|
+
*/
|
|
105
|
+
export declare function init(root?: ParentNode): void;
|
package/dist/tokens.json
CHANGED
|
@@ -200,6 +200,18 @@
|
|
|
200
200
|
"comment": "derived"
|
|
201
201
|
}
|
|
202
202
|
},
|
|
203
|
+
"icon": {
|
|
204
|
+
"size-xs": { "value": "14px", "wide": "12px", "comment": "icon box; `value` below 640px, `wide` from 640px up (touch targets read larger)" },
|
|
205
|
+
"size-sm": { "value": "18px", "wide": "16px", "comment": "default inside .fw-icon-btn and inline with body text" },
|
|
206
|
+
"size-md": { "value": "22px", "wide": "20px" },
|
|
207
|
+
"size-lg": { "value": "28px", "wide": "24px" },
|
|
208
|
+
"size-xl": { "value": "36px", "wide": "32px" },
|
|
209
|
+
"stroke-xs": { "value": "1.75", "comment": "stroke in 24-grid units, unitless — small icons get a heavier unit stroke so lines don't vanish" },
|
|
210
|
+
"stroke-sm": { "value": "1.5" },
|
|
211
|
+
"stroke-md": { "value": "1.4" },
|
|
212
|
+
"stroke-lg": { "value": "1.3", "comment": "large icons get a lighter unit stroke so they don't look heavy" },
|
|
213
|
+
"stroke-xl": { "value": "1.2" }
|
|
214
|
+
},
|
|
203
215
|
"border": {
|
|
204
216
|
"w": { "value": "1.5px", "comment": "the system's one border weight" }
|
|
205
217
|
},
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tooltip — a short hover/focus annotation, anchored to its trigger.
|
|
3
|
+
*
|
|
4
|
+
* <button type="button" class="fw-icon-btn" title="Copy to clipboard" data-fw-tooltip>
|
|
5
|
+
* <span data-fw-icon="copy" aria-hidden="true"></span>
|
|
6
|
+
* </button>
|
|
7
|
+
*
|
|
8
|
+
* The `title` attribute is the content source — it's moved into
|
|
9
|
+
* `dataset.fwTooltipText` and removed on init (same detach-and-mirror trick
|
|
10
|
+
* combobox.ts uses for the `list` attribute), which gives a real no-JS
|
|
11
|
+
* fallback (the native OS tooltip) and stops the two tooltips firing at once.
|
|
12
|
+
*
|
|
13
|
+
* Optional trigger attributes: `data-fw-tooltip-placement` (default "top";
|
|
14
|
+
* also accepts "bottom" | "left" | "right", or a corner variant —
|
|
15
|
+
* "top-left" | "top-right" | "bottom-left" | "bottom-right" — that leans the
|
|
16
|
+
* bubble toward that corner instead of centering it, for triggers near a
|
|
17
|
+
* layout edge), `data-fw-tooltip-offset` (px, default 6),
|
|
18
|
+
* `data-fw-tooltip-delay` (ms, default 400).
|
|
19
|
+
*
|
|
20
|
+
* The bubble is centered on the trigger by default so the arrow (drawn at
|
|
21
|
+
* `computePosition`'s returned `arrowOffset`) always points at the trigger's
|
|
22
|
+
* midpoint — including when a corner placement or a viewport-edge clamp
|
|
23
|
+
* shifts the bubble off-center.
|
|
24
|
+
*
|
|
25
|
+
* Only one tooltip bubble exists for the whole page (a lazily-created
|
|
26
|
+
* singleton, same pattern as toast.ts) — a second trigger's hover pre-empts
|
|
27
|
+
* the first. Acceptable for a short annotation; not meant for concurrent
|
|
28
|
+
* multi-tooltip UIs.
|
|
29
|
+
*
|
|
30
|
+
* WCAG 2.1 SC 1.4.13 requires hover/focus-revealed content to be dismissible,
|
|
31
|
+
* *hoverable* (the pointer can move onto it without it disappearing) and
|
|
32
|
+
* persistent. `pointer-events: none` on the bubble would fail "hoverable"
|
|
33
|
+
* outright — the bubble couldn't receive a hover at all — so this
|
|
34
|
+
* deliberately does NOT set it while open. Instead the bubble gets its own
|
|
35
|
+
* mouseenter/mouseleave, and a short cancelable hide-grace timer covers the
|
|
36
|
+
* gap while the pointer travels from trigger to bubble.
|
|
37
|
+
*/
|
|
38
|
+
import { type Placement } from './_position.cjs';
|
|
39
|
+
export interface TooltipOptions {
|
|
40
|
+
placement?: Placement;
|
|
41
|
+
offset?: number;
|
|
42
|
+
delay?: number;
|
|
43
|
+
}
|
|
44
|
+
export interface TooltipInstance {
|
|
45
|
+
show(): void;
|
|
46
|
+
hide(): void;
|
|
47
|
+
destroy(): void;
|
|
48
|
+
}
|
|
49
|
+
export declare function tooltip(trigger: HTMLElement, opts?: TooltipOptions): TooltipInstance;
|
|
50
|
+
/** Wire every `[data-fw-tooltip]` under `root`. */
|
|
51
|
+
export declare function init(root?: ParentNode): void;
|
package/dist/tooltip.d.ts
CHANGED
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
* mouseenter/mouseleave, and a short cancelable hide-grace timer covers the
|
|
36
36
|
* gap while the pointer travels from trigger to bubble.
|
|
37
37
|
*/
|
|
38
|
-
import { type Placement } from './_position';
|
|
38
|
+
import { type Placement } from './_position.js';
|
|
39
39
|
export interface TooltipOptions {
|
|
40
40
|
placement?: Placement;
|
|
41
41
|
offset?: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fernwell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Fernwell — the HunnyDo design system. Design tokens, CSS components, and small vanilla JS enhancers that work in any web app, with or without a build step.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,23 +33,45 @@
|
|
|
33
33
|
"jsdelivr": "./dist/fernwell.iife.js",
|
|
34
34
|
"exports": {
|
|
35
35
|
".": {
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
"import": {
|
|
37
|
+
"types": "./dist/index.d.ts",
|
|
38
|
+
"default": "./dist/fernwell.esm.js"
|
|
39
|
+
},
|
|
40
|
+
"require": {
|
|
41
|
+
"types": "./dist/index.d.cts",
|
|
42
|
+
"default": "./dist/fernwell.cjs"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"./css": {
|
|
46
|
+
"types": "./dist/css.d.ts",
|
|
47
|
+
"default": "./dist/fernwell.css"
|
|
48
|
+
},
|
|
49
|
+
"./css/min": {
|
|
50
|
+
"types": "./dist/css.d.ts",
|
|
51
|
+
"default": "./dist/fernwell.min.css"
|
|
52
|
+
},
|
|
53
|
+
"./tokens.css": {
|
|
54
|
+
"types": "./dist/css.d.ts",
|
|
55
|
+
"default": "./dist/fernwell.tokens.css"
|
|
39
56
|
},
|
|
40
|
-
"./css": "./dist/fernwell.css",
|
|
41
|
-
"./css/min": "./dist/fernwell.min.css",
|
|
42
|
-
"./tokens.css": "./dist/fernwell.tokens.css",
|
|
43
57
|
"./tokens.json": "./dist/tokens.json",
|
|
44
58
|
"./package.json": "./package.json"
|
|
45
59
|
},
|
|
60
|
+
"typesVersions": {
|
|
61
|
+
"*": {
|
|
62
|
+
"css": ["./dist/css.d.ts"],
|
|
63
|
+
"css/min": ["./dist/css.d.ts"],
|
|
64
|
+
"tokens.css": ["./dist/css.d.ts"]
|
|
65
|
+
}
|
|
66
|
+
},
|
|
46
67
|
"scripts": {
|
|
47
68
|
"dev": "node scripts/dev.mjs",
|
|
48
69
|
"tokens": "node scripts/gen-tokens.mjs",
|
|
49
70
|
"lint": "node scripts/lint-tokens.mjs",
|
|
50
|
-
"test": "vitest run",
|
|
71
|
+
"test": "vitest run && npm run test:types",
|
|
51
72
|
"test:watch": "vitest",
|
|
52
|
-
"
|
|
73
|
+
"test:types": "npm run build && vitest run --config __tests__/config/vitest.types.config.ts",
|
|
74
|
+
"build": "npm run tokens && npm run lint && node scripts/build.mjs && tsc -p tsconfig.build.json && node scripts/cjs-types.mjs",
|
|
53
75
|
"docs": "serve docs/",
|
|
54
76
|
"prepublishOnly": "npm run build"
|
|
55
77
|
},
|