@ponchia/ui 0.4.0 → 0.4.1

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.
Files changed (44) hide show
  1. package/CHANGELOG.md +128 -10
  2. package/README.md +20 -13
  3. package/behaviors/index.d.ts +3 -1
  4. package/behaviors/index.js +179 -106
  5. package/classes/index.d.ts +40 -0
  6. package/classes/index.js +41 -0
  7. package/classes/vscode.css-custom-data.json +18 -14
  8. package/css/disclosure.css +29 -0
  9. package/css/dots.css +2 -0
  10. package/css/feedback.css +53 -0
  11. package/css/motion.css +88 -0
  12. package/css/overlay.css +52 -1
  13. package/css/report.css +382 -0
  14. package/css/tokens.css +56 -26
  15. package/dist/bronto.css +1 -1
  16. package/dist/css/disclosure.css +1 -1
  17. package/dist/css/dots.css +1 -1
  18. package/dist/css/feedback.css +1 -1
  19. package/dist/css/motion.css +1 -1
  20. package/dist/css/overlay.css +1 -1
  21. package/dist/css/report.css +1 -0
  22. package/dist/css/tokens.css +1 -1
  23. package/docs/adr/0001-color-system.md +26 -27
  24. package/docs/adr/0002-scope-and-2026-baseline.md +104 -0
  25. package/docs/adr/0003-theme-model.md +94 -0
  26. package/docs/contrast.md +42 -42
  27. package/docs/reference.md +112 -15
  28. package/docs/reporting.md +270 -0
  29. package/docs/stability.md +37 -0
  30. package/docs/theming.md +18 -6
  31. package/docs/usage.md +21 -3
  32. package/llms.txt +61 -4
  33. package/package.json +28 -3
  34. package/qwik/index.d.ts +55 -0
  35. package/qwik/index.js +129 -0
  36. package/react/index.d.ts +35 -14
  37. package/react/index.js +22 -5
  38. package/solid/index.d.ts +35 -14
  39. package/solid/index.js +21 -3
  40. package/tokens/index.d.ts +3 -3
  41. package/tokens/index.js +16 -14
  42. package/tokens/index.json +32 -28
  43. package/tokens/resolved.json +34 -32
  44. package/tokens/tokens.dtcg.json +22 -14
package/llms.txt CHANGED
@@ -20,6 +20,12 @@ CSS (pick one; do not import both):
20
20
  @import '@ponchia/ui/css'; /* css/core.css — @import fan-out, needs a bundler */
21
21
  ```
22
22
 
23
+ `import '@ponchia/ui'` is supported only as a CSS side-effect import in a
24
+ CSS-aware bundler. Do not import the package root from Node/runtime JS; use the
25
+ explicit JS subpaths below.
26
+
27
+ JS subpaths are ESM-only. In CommonJS, use dynamic `import()`.
28
+
23
29
  Everything lands in `@layer bronto`. Consumer styles in no layer (or a
24
30
  later layer) always win over framework styles regardless of selector
25
31
  specificity — this is the intended override mechanism; do not fight it
@@ -46,11 +52,13 @@ Optional vanilla behaviors (theme toggle, etc. — SSR-safe, tree-shakeable):
46
52
  import { applyStoredTheme } from '@ponchia/ui/behaviors';
47
53
  ```
48
54
 
49
- For React/Solid, optional thin hook bindings over those behaviors (peer deps
50
- `react` / `solid-js`, optional — core stays zero-dep):
55
+ For React/Solid/Qwik, optional thin hook bindings over those behaviors (peer
56
+ deps `react` / `solid-js` / `@builder.io/qwik`, optional — core stays zero-dep):
51
57
 
52
58
  ```js
53
- import { useDialog, useToast } from '@ponchia/ui/react'; // or '@ponchia/ui/solid'
59
+ import { useDialog, useToast } from '@ponchia/ui/react'; // or '@ponchia/ui/solid' or '@ponchia/ui/qwik'
60
+ // React: scope with { root: reactRef } or a resolver, not { root: ref.current } during render.
61
+ // Qwik: hooks run in useVisibleTask$; scope with a Qwik signal — useDialog({ root: useSignal() }).
54
62
  ```
55
63
 
56
64
  Optional display glyphs — dot-matrix bitmaps on the `.ui-dotmatrix` primitive
@@ -89,6 +97,47 @@ simulated protan/deutan/tritan), `--chart-seq-*` (sequential), `--chart-div-*`
89
97
  (diverging), and `--chart-pattern-1..8` (dot-matrix fills — pair colour N with
90
98
  pattern N; colour is never the sole signal). Details in `docs/theming.md`.
91
99
 
100
+ Optional static report layer for LLM-authored HTML reports — opt-in, never in
101
+ the default bundle. Pair it with the normal bundle, and add data-viz only when
102
+ the report contains charts. For standalone browser HTML, use real URLs; package
103
+ specifiers only work after a bundler rewrites them:
104
+
105
+ ```html
106
+ <link rel="stylesheet" href="./node_modules/@ponchia/ui/dist/bronto.css" />
107
+ <link rel="stylesheet" href="./node_modules/@ponchia/ui/dist/css/report.css" />
108
+ <link rel="stylesheet" href="./node_modules/@ponchia/ui/dist/css/dataviz.css" />
109
+ ```
110
+
111
+ In Vite/webpack/etc., import `@ponchia/ui`, `@ponchia/ui/css/report.css`, and
112
+ `@ponchia/ui/css/dataviz.css` instead.
113
+
114
+ Canonical report skeleton:
115
+
116
+ ```html
117
+ <main class="ui-report ui-report--numbered">
118
+ <header class="ui-report__cover">
119
+ <p class="ui-eyebrow">Report</p>
120
+ <h1 class="ui-report__title">System review</h1>
121
+ <p class="ui-report__subtitle">Static HTML, Chromium PDF-ready.</p>
122
+ <ol class="ui-report__meta"><li>2026-06-01</li><li>Generated</li></ol>
123
+ </header>
124
+ <section class="ui-report__section">
125
+ <h2 class="ui-report__section-head">Executive summary</h2>
126
+ <div class="ui-report__summary">
127
+ <div class="ui-prose"><p>Use prose only for narrative body content.</p></div>
128
+ </div>
129
+ </section>
130
+ </main>
131
+ ```
132
+
133
+ Report rules for agents: static output by default; do not initialize behaviors
134
+ over untrusted generated content; sanitize arbitrary LLM/CMS/user HTML before
135
+ rendering; use semantic headings; give tables captions and header cells; give
136
+ charts captions, legends/direct labels, and fallback data; use
137
+ `ui-chart__plot`/`ui-chart__bar`/`ui-chart__fill` for simple static bar charts;
138
+ use `ui-report__section--unnumbered` for appendices in numbered reports; never
139
+ use raw chromatic inline colors. Full cookbook: `docs/reporting.md`.
140
+
92
141
  ## Authoritative offline references (shipped in this package)
93
142
 
94
143
  Read these from `node_modules/@ponchia/ui/` — no network needed:
@@ -101,9 +150,14 @@ Read these from `node_modules/@ponchia/ui/` — no network needed:
101
150
  the same source as the types and CI-drift-checked.
102
151
  - `docs/theming.md` — the token contract: which tokens to override, the
103
152
  light/dark model, and the rationed-accent rule.
153
+ - `docs/stability.md` — the public API matrix: stable vs internal surfaces,
154
+ root-import contract, behavior trust boundary, and glyph-mask CSP note.
104
155
  - `docs/usage.md` — the decision guide: badge vs chip vs dot, default
105
156
  density, prose-in-card, when to add a behavior. Read this before
106
157
  re-implementing UI the framework already has an opinion about.
158
+ - `docs/reporting.md` — static/PDF-first report cookbook for LLM-authored
159
+ reports, including report skeletons, chart rules, print utilities, and safety
160
+ boundaries.
107
161
  - `docs/contrast.md` — the published, CI-gated WCAG 2.1 contrast matrix
108
162
  for every contractual token pairing, per theme. Generated from the
109
163
  resolved palette; the build fails below the declared floor.
@@ -123,7 +177,7 @@ Read these from `node_modules/@ponchia/ui/` — no network needed:
123
177
 
124
178
  - Live kitchen-sink demo (every component, light/dark, RTL): https://ponchia.github.io/bronto-ui/
125
179
  - Architecture & the CSS-first decision: https://github.com/Ponchia/bronto-ui/blob/main/docs/architecture.md
126
- - Framework integration guides (Astro / SvelteKit / React-Solid / vanilla):
180
+ - Framework integration guides (Astro / SvelteKit / React-Solid-Qwik / vanilla):
127
181
  https://github.com/Ponchia/bronto-ui/tree/main/docs/getting-started
128
182
 
129
183
  ## Rules an agent should respect
@@ -131,6 +185,9 @@ Read these from `node_modules/@ponchia/ui/` — no network needed:
131
185
  - Never invent `ui-*` class names. Every valid class is a member of
132
186
  `cls` in `classes/index.d.ts`; if it is not there, it does not exist.
133
187
  - Prefer the `ui.*()` recipes over hand-concatenating modifier strings.
188
+ Report/layout classes added in 0.4.1 are the exception: use `cls.report*`,
189
+ `cls.chart*`, `cls.printOnly`, or literal documented class names because
190
+ there are no `ui.report()` / `ui.chart()` recipes yet.
134
191
  - Override framework styles by writing your own rules outside
135
192
  `@layer bronto` — not with higher specificity or `!important`.
136
193
  - Color is tiered (ADR-0001): neutral canvas · one rationed accent · locked
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ponchia/ui",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "type": "module",
5
5
  "description": "CSS-first, framework-agnostic UI framework — token-driven restraint (monochrome by default, one rationed accent), OKLCH colorways, a dot-matrix icon set, a colourblind-safe data-viz palette, dot-matrix motifs, zero runtime dependencies.",
6
6
  "keywords": [
@@ -48,14 +48,19 @@
48
48
  "glyphs",
49
49
  "react",
50
50
  "solid",
51
+ "qwik",
51
52
  "shiki",
52
53
  "llms.txt",
53
54
  "CHANGELOG.md",
54
55
  "docs/reference.md",
55
56
  "docs/theming.md",
56
57
  "docs/contrast.md",
58
+ "docs/stability.md",
57
59
  "docs/usage.md",
58
- "docs/adr/0001-color-system.md"
60
+ "docs/reporting.md",
61
+ "docs/adr/0001-color-system.md",
62
+ "docs/adr/0002-scope-and-2026-baseline.md",
63
+ "docs/adr/0003-theme-model.md"
59
64
  ],
60
65
  "style": "./dist/bronto.css",
61
66
  "scripts": {
@@ -63,6 +68,7 @@
63
68
  "lint:fix": "stylelint \"css/**/*.css\" --fix",
64
69
  "format": "prettier --write .",
65
70
  "check:format": "prettier --check .",
71
+ "size:report": "node scripts/size-report.mjs",
66
72
  "tokens:build": "node scripts/gen-tokens-json.mjs",
67
73
  "dtcg:build": "node scripts/gen-dtcg.mjs",
68
74
  "resolved:build": "node scripts/gen-resolved.mjs",
@@ -84,6 +90,7 @@
84
90
  "check:color-policy": "node scripts/check-color-policy.mjs",
85
91
  "check:skins": "node scripts/check-skins.mjs",
86
92
  "check:charts": "node scripts/check-charts.mjs",
93
+ "check:report": "node scripts/check-report.mjs",
87
94
  "check:types": "tsc -p tsconfig.json",
88
95
  "check:dtcg": "node scripts/check-dtcg.mjs",
89
96
  "check:resolved": "node scripts/check-resolved.mjs",
@@ -94,26 +101,34 @@
94
101
  "check:reference": "node scripts/check-reference.mjs",
95
102
  "check:contrast": "node scripts/check-contrast.mjs",
96
103
  "check:vscode": "node scripts/check-vscode-data.mjs",
97
- "check": "npm run lint && npm run check:format && npm run check:exports && npm run check:tokens && npm run check:classes && npm run check:dts && npm run check:types && npm run check:dtcg && npm run check:resolved && npm run check:shiki && npm run check:dist && npm run check:pack && npm run check:release && npm run check:reference && npm run check:contrast && npm run check:vscode && npm run check:behaviors && npm run check:bindings && npm run check:glyphs && npm run check:color-policy && npm run check:skins && npm run check:charts",
104
+ "check": "npm run lint && npm run check:format && npm run check:exports && npm run check:tokens && npm run check:classes && npm run check:dts && npm run check:types && npm run check:dtcg && npm run check:resolved && npm run check:shiki && npm run check:dist && npm run check:pack && npm run check:release && npm run check:reference && npm run check:contrast && npm run check:vscode && npm run check:behaviors && npm run check:bindings && npm run check:glyphs && npm run check:color-policy && npm run check:skins && npm run check:charts && npm run check:report",
98
105
  "test": "node --test \"test/*.test.mjs\"",
99
106
  "prepack": "npm run tokens:build && npm run dtcg:build && npm run resolved:build && npm run dts:build && npm run reference:build && npm run contrast:build && npm run vscode:build && npm run skins:build && npm run charts:build && npm run dist:build && npm run glyphs:build",
100
107
  "prepublishOnly": "npm run check && npm test"
101
108
  },
102
109
  "devDependencies": {
103
110
  "@axe-core/playwright": "^4.11.3",
111
+ "@builder.io/qwik": "^1.20.0",
104
112
  "@playwright/test": "^1.60.0",
105
113
  "jsdom": "^29.1.1",
106
114
  "prettier": "^3.8.3",
115
+ "react": "^19.2.6",
116
+ "react-dom": "^19.2.6",
117
+ "solid-js": "^1.9.13",
107
118
  "stylelint": "^17.11.1",
108
119
  "stylelint-config-standard": "^40.0.0",
109
120
  "stylelint-use-logical": "^2.1.3",
110
121
  "typescript": "^6.0.3"
111
122
  },
112
123
  "peerDependencies": {
124
+ "@builder.io/qwik": ">=1.5",
113
125
  "react": ">=18",
114
126
  "solid-js": ">=1.6"
115
127
  },
116
128
  "peerDependenciesMeta": {
129
+ "@builder.io/qwik": {
130
+ "optional": true
131
+ },
117
132
  "react": {
118
133
  "optional": true
119
134
  },
@@ -146,6 +161,7 @@
146
161
  "./css/app.css": "./dist/css/app.css",
147
162
  "./css/skins.css": "./dist/css/skins.css",
148
163
  "./css/dataviz.css": "./dist/css/dataviz.css",
164
+ "./css/report.css": "./dist/css/report.css",
149
165
  "./css/unlayered/tokens.css": "./css/tokens.css",
150
166
  "./css/unlayered/fonts.css": "./css/fonts.css",
151
167
  "./css/unlayered/base.css": "./css/base.css",
@@ -163,6 +179,7 @@
163
179
  "./css/unlayered/app.css": "./css/app.css",
164
180
  "./css/unlayered/skins.css": "./css/skins.css",
165
181
  "./css/unlayered/dataviz.css": "./css/dataviz.css",
182
+ "./css/unlayered/report.css": "./css/report.css",
166
183
  "./tokens": {
167
184
  "types": "./tokens/index.d.ts",
168
185
  "default": "./tokens/index.js"
@@ -176,8 +193,12 @@
176
193
  "./docs/reference.md": "./docs/reference.md",
177
194
  "./docs/theming.md": "./docs/theming.md",
178
195
  "./docs/contrast.md": "./docs/contrast.md",
196
+ "./docs/stability.md": "./docs/stability.md",
179
197
  "./docs/usage.md": "./docs/usage.md",
198
+ "./docs/reporting.md": "./docs/reporting.md",
180
199
  "./docs/adr/0001-color-system.md": "./docs/adr/0001-color-system.md",
200
+ "./docs/adr/0002-scope-and-2026-baseline.md": "./docs/adr/0002-scope-and-2026-baseline.md",
201
+ "./docs/adr/0003-theme-model.md": "./docs/adr/0003-theme-model.md",
181
202
  "./classes": {
182
203
  "types": "./classes/index.d.ts",
183
204
  "default": "./classes/index.js"
@@ -198,6 +219,10 @@
198
219
  "types": "./solid/index.d.ts",
199
220
  "default": "./solid/index.js"
200
221
  },
222
+ "./qwik": {
223
+ "types": "./qwik/index.d.ts",
224
+ "default": "./qwik/index.js"
225
+ },
201
226
  "./skins": {
202
227
  "types": "./tokens/skins.d.ts",
203
228
  "default": "./tokens/skins.js"
@@ -0,0 +1,55 @@
1
+ /** @ponchia/ui/qwik — thin Qwik bindings over the SSR-safe behaviors.
2
+ * Optional peer dep `@builder.io/qwik`. Hooks run a delegated behavior on
3
+ * visible and register cleanup on dispose (via `useVisibleTask$`); they take
4
+ * the same options as the behavior and return void. See behaviors/index.d.ts. */
5
+ import type { Cleanup, DelegateOpts, ThemeStorageOpts, ToastOpts } from '../behaviors/index.js';
6
+
7
+ export type BrontoBindingRoot =
8
+ | Document
9
+ | Element
10
+ // Qwik `useSignal()` (and any { value } / { current } ref shape).
11
+ | { value: Document | Element | null | undefined }
12
+ | { current: Document | Element | null | undefined }
13
+ | (() => Document | Element | null | undefined)
14
+ | null
15
+ | undefined;
16
+
17
+ export type BrontoBindingOpts<T extends DelegateOpts = DelegateOpts> = Omit<T, 'root'> & {
18
+ root?: BrontoBindingRoot;
19
+ };
20
+
21
+ export type BrontoBindingOptsResolver<T extends DelegateOpts = DelegateOpts> =
22
+ | BrontoBindingOpts<T>
23
+ | (() => BrontoBindingOpts<T> | null | undefined)
24
+ | null
25
+ | undefined;
26
+
27
+ /** Run any delegated behavior for the component's lifetime (init on visible,
28
+ * its returned cleanup on dispose). Prefer the specific `use*` hooks in Qwik
29
+ * so the optimizer can serialize the task; this generic form is safe only with
30
+ * an optimizer-visible (module-imported) behavior. */
31
+ export declare function useBrontoBehavior(
32
+ init: (opts?: DelegateOpts) => Cleanup | void,
33
+ opts?: BrontoBindingOptsResolver,
34
+ ): void;
35
+
36
+ export declare function useThemeToggle(
37
+ opts?: BrontoBindingOptsResolver<ThemeStorageOpts & DelegateOpts>,
38
+ ): void;
39
+ export declare function useDismissible(opts?: BrontoBindingOptsResolver): void;
40
+ export declare function useDisclosure(opts?: BrontoBindingOptsResolver): void;
41
+ export declare function useMenu(opts?: BrontoBindingOptsResolver): void;
42
+ export declare function useFormValidation(opts?: BrontoBindingOptsResolver): void;
43
+ export declare function useCombobox(opts?: BrontoBindingOptsResolver): void;
44
+ export declare function usePopover(opts?: BrontoBindingOptsResolver): void;
45
+ export declare function useTableSort(opts?: BrontoBindingOptsResolver): void;
46
+ export declare function useTabs(opts?: BrontoBindingOptsResolver): void;
47
+ export declare function useDialog(opts?: BrontoBindingOptsResolver): void;
48
+ export declare function useCarousel(opts?: BrontoBindingOptsResolver): void;
49
+ export declare function useDotGlyph(opts?: BrontoBindingOptsResolver): void;
50
+
51
+ /** The `toast()` imperative (no lifecycle of its own). */
52
+ export declare function useToast(): (message: string, opts?: ToastOpts) => Cleanup;
53
+
54
+ export { applyStoredTheme } from '../behaviors/index.js';
55
+ export { cls, ui, cx } from '../classes/index.js';
package/qwik/index.js ADDED
@@ -0,0 +1,129 @@
1
+ /**
2
+ * @ponchia/ui/qwik — thin Qwik bindings over @ponchia/ui/behaviors.
3
+ *
4
+ * The CSS is the framework; these are *optional* hooks that wrap the SSR-safe
5
+ * vanilla `init*` behaviors in Qwik's client lifecycle. Thin adapters, not a
6
+ * component library (architecture ADR). `@builder.io/qwik` is an optional peer
7
+ * dependency.
8
+ *
9
+ * Qwik is resumable: nothing runs until needed, and our behaviors are exactly
10
+ * the kind of "enhance on the client" glue that fits — they `useVisibleTask$`
11
+ * (run when the owning component first becomes visible) and register cleanup,
12
+ * so a server-rendered page stays zero-JS until interaction. Each hook inlines
13
+ * its specific behavior so the Qwik optimizer can resolve the import inside the
14
+ * extracted task segment.
15
+ *
16
+ * import { component$ } from '@builder.io/qwik';
17
+ * import { useDialog, useToast } from '@ponchia/ui/qwik';
18
+ * export default component$(() => {
19
+ * useDialog(); // wires every .ui-modal under document
20
+ * const toast = useToast();
21
+ * return <button onClick$={() => toast('Saved', { tone: 'success' })}>Save</button>;
22
+ * });
23
+ *
24
+ * Scope a behavior to a subtree by passing a Qwik signal:
25
+ * const root = useSignal();
26
+ * useDialog({ root }); // <section ref={root}> … </section>
27
+ */
28
+ import { useVisibleTask$ } from '@builder.io/qwik';
29
+ import {
30
+ applyStoredTheme,
31
+ initThemeToggle,
32
+ dismissible,
33
+ initDisclosure,
34
+ initMenu,
35
+ initFormValidation,
36
+ initCombobox,
37
+ initPopover,
38
+ initTableSort,
39
+ initTabs,
40
+ initDialog,
41
+ initCarousel,
42
+ initDotGlyph,
43
+ toast,
44
+ } from '../behaviors/index.js';
45
+
46
+ function resolveMaybe(v) {
47
+ return typeof v === 'function' ? v() : v;
48
+ }
49
+
50
+ function resolveRoot(root) {
51
+ const value = resolveMaybe(root);
52
+ if (value && typeof value === 'object') {
53
+ if ('value' in value) return value.value; // Qwik signal (useSignal)
54
+ if ('current' in value) return value.current; // generic ref shape
55
+ }
56
+ return value;
57
+ }
58
+
59
+ function resolveOpts(opts) {
60
+ const value = resolveMaybe(opts);
61
+ if (!value || typeof value !== 'object') return undefined;
62
+ const root = resolveRoot(value.root);
63
+ return root ? { ...value, root } : { ...value, root: undefined };
64
+ }
65
+
66
+ /** Run a delegated behavior on visible and register its cleanup on dispose.
67
+ * `init` and `opts` are resolved inside the visible task, so a Qwik-signal
68
+ * root is read after the element is assigned. Shared, non-QRL, so the
69
+ * optimizer keeps the captured behavior import inside the task segment. */
70
+ function start(init, opts, ctx) {
71
+ const cleanup = init(resolveOpts(opts));
72
+ if (typeof cleanup === 'function') ctx.cleanup(cleanup);
73
+ }
74
+
75
+ /** Generic escape hatch. NOTE for Qwik: prefer the specific `use*` hooks
76
+ * below — they inline a statically-imported behavior so the optimizer can
77
+ * serialize the task. Passing a runtime function here is only safe when it
78
+ * is itself optimizer-visible (a module import). */
79
+ export function useBrontoBehavior(init, opts) {
80
+ // eslint-disable-next-line qwik/no-use-visible-task -- delegated DOM glue + cleanup
81
+ useVisibleTask$((ctx) => start(init, opts, ctx));
82
+ }
83
+
84
+ export const useThemeToggle = (opts) =>
85
+ // eslint-disable-next-line qwik/no-use-visible-task -- delegated DOM glue + cleanup
86
+ useVisibleTask$((ctx) => start(initThemeToggle, opts, ctx));
87
+ export const useDismissible = (opts) =>
88
+ // eslint-disable-next-line qwik/no-use-visible-task -- delegated DOM glue + cleanup
89
+ useVisibleTask$((ctx) => start(dismissible, opts, ctx));
90
+ export const useDisclosure = (opts) =>
91
+ // eslint-disable-next-line qwik/no-use-visible-task -- delegated DOM glue + cleanup
92
+ useVisibleTask$((ctx) => start(initDisclosure, opts, ctx));
93
+ export const useMenu = (opts) =>
94
+ // eslint-disable-next-line qwik/no-use-visible-task -- delegated DOM glue + cleanup
95
+ useVisibleTask$((ctx) => start(initMenu, opts, ctx));
96
+ export const useFormValidation = (opts) =>
97
+ // eslint-disable-next-line qwik/no-use-visible-task -- delegated DOM glue + cleanup
98
+ useVisibleTask$((ctx) => start(initFormValidation, opts, ctx));
99
+ export const useCombobox = (opts) =>
100
+ // eslint-disable-next-line qwik/no-use-visible-task -- delegated DOM glue + cleanup
101
+ useVisibleTask$((ctx) => start(initCombobox, opts, ctx));
102
+ export const usePopover = (opts) =>
103
+ // eslint-disable-next-line qwik/no-use-visible-task -- delegated DOM glue + cleanup
104
+ useVisibleTask$((ctx) => start(initPopover, opts, ctx));
105
+ export const useTableSort = (opts) =>
106
+ // eslint-disable-next-line qwik/no-use-visible-task -- delegated DOM glue + cleanup
107
+ useVisibleTask$((ctx) => start(initTableSort, opts, ctx));
108
+ export const useTabs = (opts) =>
109
+ // eslint-disable-next-line qwik/no-use-visible-task -- delegated DOM glue + cleanup
110
+ useVisibleTask$((ctx) => start(initTabs, opts, ctx));
111
+ export const useDialog = (opts) =>
112
+ // eslint-disable-next-line qwik/no-use-visible-task -- delegated DOM glue + cleanup
113
+ useVisibleTask$((ctx) => start(initDialog, opts, ctx));
114
+ export const useCarousel = (opts) =>
115
+ // eslint-disable-next-line qwik/no-use-visible-task -- delegated DOM glue + cleanup
116
+ useVisibleTask$((ctx) => start(initCarousel, opts, ctx));
117
+ export const useDotGlyph = (opts) =>
118
+ // eslint-disable-next-line qwik/no-use-visible-task -- delegated DOM glue + cleanup
119
+ useVisibleTask$((ctx) => start(initDotGlyph, opts, ctx));
120
+
121
+ /** The `toast()` imperative (no lifecycle of its own). */
122
+ export const useToast = () => toast;
123
+
124
+ // No-flash theme application must run before paint — do it in an inline head
125
+ // script, not on visible. Re-exported for manual/SSR-bootstrap use.
126
+ export { applyStoredTheme };
127
+
128
+ // Convenience: the framework-agnostic class contract, re-exported.
129
+ export { cls, ui, cx } from '../classes/index.js';
package/react/index.d.ts CHANGED
@@ -9,25 +9,46 @@ import type {
9
9
  ToastOpts,
10
10
  } from '../behaviors/index.js';
11
11
 
12
+ export type BrontoBindingRoot =
13
+ | Document
14
+ | Element
15
+ | { current: Document | Element | null | undefined }
16
+ | (() => Document | Element | null | undefined)
17
+ | null
18
+ | undefined;
19
+
20
+ export type BrontoBindingOpts<T extends DelegateOpts = DelegateOpts> = Omit<T, 'root'> & {
21
+ root?: BrontoBindingRoot;
22
+ };
23
+
24
+ export type BrontoBindingOptsResolver<T extends DelegateOpts = DelegateOpts> =
25
+ | BrontoBindingOpts<T>
26
+ | (() => BrontoBindingOpts<T> | null | undefined)
27
+ | null
28
+ | undefined;
29
+
12
30
  /** Run any delegated behavior for the component's lifetime (init on mount,
13
- * its returned cleanup on unmount). The behavior is run once. */
31
+ * its returned cleanup on unmount). The behavior is run once. Options resolve
32
+ * on mount, so scoped roots may be React refs or resolver callbacks. */
14
33
  export declare function useBrontoBehavior(
15
34
  init: (opts?: DelegateOpts) => Cleanup | void,
16
- opts?: DelegateOpts,
35
+ opts?: BrontoBindingOptsResolver,
17
36
  ): void;
18
37
 
19
- export declare function useThemeToggle(opts?: ThemeStorageOpts & DelegateOpts): void;
20
- export declare function useDismissible(opts?: DelegateOpts): void;
21
- export declare function useDisclosure(opts?: DelegateOpts): void;
22
- export declare function useMenu(opts?: DelegateOpts): void;
23
- export declare function useFormValidation(opts?: DelegateOpts): void;
24
- export declare function useCombobox(opts?: DelegateOpts): void;
25
- export declare function usePopover(opts?: DelegateOpts): void;
26
- export declare function useTableSort(opts?: DelegateOpts): void;
27
- export declare function useTabs(opts?: DelegateOpts): void;
28
- export declare function useDialog(opts?: DelegateOpts): void;
29
- export declare function useCarousel(opts?: DelegateOpts): void;
30
- export declare function useDotGlyph(opts?: DelegateOpts): void;
38
+ export declare function useThemeToggle(
39
+ opts?: BrontoBindingOptsResolver<ThemeStorageOpts & DelegateOpts>,
40
+ ): void;
41
+ export declare function useDismissible(opts?: BrontoBindingOptsResolver): void;
42
+ export declare function useDisclosure(opts?: BrontoBindingOptsResolver): void;
43
+ export declare function useMenu(opts?: BrontoBindingOptsResolver): void;
44
+ export declare function useFormValidation(opts?: BrontoBindingOptsResolver): void;
45
+ export declare function useCombobox(opts?: BrontoBindingOptsResolver): void;
46
+ export declare function usePopover(opts?: BrontoBindingOptsResolver): void;
47
+ export declare function useTableSort(opts?: BrontoBindingOptsResolver): void;
48
+ export declare function useTabs(opts?: BrontoBindingOptsResolver): void;
49
+ export declare function useDialog(opts?: BrontoBindingOptsResolver): void;
50
+ export declare function useCarousel(opts?: BrontoBindingOptsResolver): void;
51
+ export declare function useDotGlyph(opts?: BrontoBindingOptsResolver): void;
31
52
 
32
53
  /** The `toast()` imperative (no lifecycle of its own). */
33
54
  export declare function useToast(): (message: string, opts?: ToastOpts) => Cleanup;
package/react/index.js CHANGED
@@ -7,8 +7,8 @@
7
7
  * the architecture ADR. `react` is an optional peer dependency.
8
8
  *
9
9
  * The behaviors delegate from a root (default `document`), so call a hook once
10
- * near the relevant subtree; pass `{ root: ref.current }` to scope it. The root
11
- * is captured on mount keep it stable (a ref), like any effect dependency.
10
+ * near the relevant subtree; pass `{ root: ref }` or a resolver callback to
11
+ * scope it. The options resolve on mount, after refs have been assigned.
12
12
  *
13
13
  * import { useDialog, useToast } from '@ponchia/ui/react';
14
14
  * function App() {
@@ -35,11 +35,28 @@ import {
35
35
  toast,
36
36
  } from '../behaviors/index.js';
37
37
 
38
+ function resolveMaybe(v) {
39
+ return typeof v === 'function' ? v() : v;
40
+ }
41
+
42
+ function resolveRoot(root) {
43
+ const value = resolveMaybe(root);
44
+ if (value && typeof value === 'object' && 'current' in value) return value.current;
45
+ return value;
46
+ }
47
+
48
+ function resolveOpts(opts) {
49
+ const value = resolveMaybe(opts);
50
+ if (!value || typeof value !== 'object') return undefined;
51
+ const root = resolveRoot(value.root);
52
+ return root ? { ...value, root } : { ...value, root: undefined };
53
+ }
54
+
38
55
  /** Run a delegated behavior for the component's lifetime (init on mount, its
39
- * returned cleanup on unmount). The behavior is run once; `opts` is captured
40
- * on mount pass a stable root (a ref) if you scope it. */
56
+ * returned cleanup on unmount). The behavior is run once; `opts` resolves
57
+ * on mount so refs are usable for scoped roots. */
41
58
  export function useBrontoBehavior(init, opts) {
42
- useEffect(() => init(opts), []); // eslint-disable-line react-hooks/exhaustive-deps -- delegated once on mount
59
+ useEffect(() => init(resolveOpts(opts)), []); // eslint-disable-line react-hooks/exhaustive-deps -- delegated once on mount
43
60
  }
44
61
 
45
62
  export const useThemeToggle = (opts) => useBrontoBehavior(initThemeToggle, opts);
package/solid/index.d.ts CHANGED
@@ -9,25 +9,46 @@ import type {
9
9
  ToastOpts,
10
10
  } from '../behaviors/index.js';
11
11
 
12
+ export type BrontoBindingRoot =
13
+ | Document
14
+ | Element
15
+ | { current: Document | Element | null | undefined }
16
+ | (() => Document | Element | null | undefined)
17
+ | null
18
+ | undefined;
19
+
20
+ export type BrontoBindingOpts<T extends DelegateOpts = DelegateOpts> = Omit<T, 'root'> & {
21
+ root?: BrontoBindingRoot;
22
+ };
23
+
24
+ export type BrontoBindingOptsResolver<T extends DelegateOpts = DelegateOpts> =
25
+ | BrontoBindingOpts<T>
26
+ | (() => BrontoBindingOpts<T> | null | undefined)
27
+ | null
28
+ | undefined;
29
+
12
30
  /** Run any delegated behavior for the component's lifetime (init on mount,
13
- * its returned cleanup on dispose). */
31
+ * its returned cleanup on dispose). The behavior is run once. Options resolve
32
+ * on mount, so scoped roots may be refs or resolver callbacks. */
14
33
  export declare function useBrontoBehavior(
15
34
  init: (opts?: DelegateOpts) => Cleanup | void,
16
- opts?: DelegateOpts,
35
+ opts?: BrontoBindingOptsResolver,
17
36
  ): void;
18
37
 
19
- export declare function useThemeToggle(opts?: ThemeStorageOpts & DelegateOpts): void;
20
- export declare function useDismissible(opts?: DelegateOpts): void;
21
- export declare function useDisclosure(opts?: DelegateOpts): void;
22
- export declare function useMenu(opts?: DelegateOpts): void;
23
- export declare function useFormValidation(opts?: DelegateOpts): void;
24
- export declare function useCombobox(opts?: DelegateOpts): void;
25
- export declare function usePopover(opts?: DelegateOpts): void;
26
- export declare function useTableSort(opts?: DelegateOpts): void;
27
- export declare function useTabs(opts?: DelegateOpts): void;
28
- export declare function useDialog(opts?: DelegateOpts): void;
29
- export declare function useCarousel(opts?: DelegateOpts): void;
30
- export declare function useDotGlyph(opts?: DelegateOpts): void;
38
+ export declare function useThemeToggle(
39
+ opts?: BrontoBindingOptsResolver<ThemeStorageOpts & DelegateOpts>,
40
+ ): void;
41
+ export declare function useDismissible(opts?: BrontoBindingOptsResolver): void;
42
+ export declare function useDisclosure(opts?: BrontoBindingOptsResolver): void;
43
+ export declare function useMenu(opts?: BrontoBindingOptsResolver): void;
44
+ export declare function useFormValidation(opts?: BrontoBindingOptsResolver): void;
45
+ export declare function useCombobox(opts?: BrontoBindingOptsResolver): void;
46
+ export declare function usePopover(opts?: BrontoBindingOptsResolver): void;
47
+ export declare function useTableSort(opts?: BrontoBindingOptsResolver): void;
48
+ export declare function useTabs(opts?: BrontoBindingOptsResolver): void;
49
+ export declare function useDialog(opts?: BrontoBindingOptsResolver): void;
50
+ export declare function useCarousel(opts?: BrontoBindingOptsResolver): void;
51
+ export declare function useDotGlyph(opts?: BrontoBindingOptsResolver): void;
31
52
 
32
53
  /** The `toast()` imperative (no lifecycle of its own). */
33
54
  export declare function useToast(): (message: string, opts?: ToastOpts) => Cleanup;
package/solid/index.js CHANGED
@@ -7,7 +7,8 @@
7
7
  * `solid-js` is an optional peer dependency.
8
8
  *
9
9
  * Behaviors delegate from a root (default `document`); call a primitive once in
10
- * a component that owns the relevant subtree, `{ root: el }` to scope it.
10
+ * a component that owns the relevant subtree, and pass a root resolver to scope
11
+ * it when the element is assigned by Solid's ref lifecycle.
11
12
  *
12
13
  * import { useDialog, useToast } from '@ponchia/ui/solid';
13
14
  * function App() {
@@ -34,11 +35,28 @@ import {
34
35
  toast,
35
36
  } from '../behaviors/index.js';
36
37
 
38
+ function resolveMaybe(v) {
39
+ return typeof v === 'function' ? v() : v;
40
+ }
41
+
42
+ function resolveRoot(root) {
43
+ const value = resolveMaybe(root);
44
+ if (value && typeof value === 'object' && 'current' in value) return value.current;
45
+ return value;
46
+ }
47
+
48
+ function resolveOpts(opts) {
49
+ const value = resolveMaybe(opts);
50
+ if (!value || typeof value !== 'object') return undefined;
51
+ const root = resolveRoot(value.root);
52
+ return root ? { ...value, root } : { ...value, root: undefined };
53
+ }
54
+
37
55
  /** Run a delegated behavior for the component's lifetime (init on mount, its
38
- * returned cleanup on dispose). */
56
+ * returned cleanup on dispose). Options resolve on mount, after refs exist. */
39
57
  export function useBrontoBehavior(init, opts) {
40
58
  onMount(() => {
41
- const cleanup = init(opts);
59
+ const cleanup = init(resolveOpts(opts));
42
60
  if (typeof cleanup === 'function') onCleanup(cleanup);
43
61
  });
44
62
  }