fernwell 0.4.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.
@@ -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;
@@ -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;
@@ -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.4.0",
3
+ "version": "0.5.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
- "types": "./dist/index.d.ts",
37
- "import": "./dist/fernwell.esm.js",
38
- "require": "./dist/fernwell.cjs"
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
- "build": "npm run tokens && npm run lint && node scripts/build.mjs && tsc -p tsconfig.build.json",
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
  },