@hanzo/gui 8.0.1 → 8.1.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/css-check.mjs ADDED
@@ -0,0 +1,678 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * gui-css-check — every class in the markup must have a rule in the CSS that
4
+ * ships with it.
5
+ *
6
+ * A build is green when the code compiles. Nothing in a compiler knows whether
7
+ * the stylesheet a document links actually contains the classes that document
8
+ * uses, so "classes without rules" ships silently and renders a blank-looking
9
+ * page. It has happened three times here: hanzo.app served 18KB of CSS holding
10
+ * zero of its ~240 gui atomic classes; @hanzogui/shell@8.0.3 published 966
11
+ * lines of Tailwind classes with no Tailwind anywhere in its dependencies.
12
+ * Both type-checked. Both built. Both were completely unstyled in production.
13
+ *
14
+ * This reads the built output and compares two sets:
15
+ * used — class tokens in class="..." attributes of the rendered markup
16
+ * defined — class tokens in selector position across every sheet the page
17
+ * delivers: linked .css files AND inline <style>
18
+ * Anything used and not defined is a miss and the process exits 1.
19
+ *
20
+ * Per page, not per build: a rule that exists in some sheet the page never
21
+ * links is not delivered, and the browser agrees.
22
+ *
23
+ * Zero dependencies, one file. It has to run in any app without dragging a
24
+ * toolchain behind it, and it has to keep working when the toolchain is the
25
+ * thing that broke.
26
+ */
27
+ import {
28
+ readFileSync,
29
+ readdirSync,
30
+ statSync,
31
+ existsSync,
32
+ mkdirSync,
33
+ mkdtempSync,
34
+ writeFileSync,
35
+ } from 'node:fs'
36
+ import { join, resolve, relative, basename, sep } from 'node:path'
37
+ import { tmpdir } from 'node:os'
38
+
39
+ // ---------------------------------------------------------------- extraction
40
+
41
+ const stripComments = (css) => css.replace(/\/\*[\s\S]*?\*\//g, ' ')
42
+
43
+ /** Strings and url() can hold braces and dots; neither is ever a selector. */
44
+ const stripLiterals = (css) =>
45
+ css
46
+ .replace(/"(?:[^"\\]|\\[\s\S])*"/g, '""')
47
+ .replace(/'(?:[^'\\]|\\[\s\S])*'/g, "''")
48
+ .replace(/url\([^)]*\)/g, 'url()')
49
+
50
+ /** `\3a ` and `\:` both mean a literal `:` in a class name. */
51
+ const unescapeIdent = (s) =>
52
+ s.replace(/\\([0-9a-fA-F]{1,6})[ \t]?|\\([\s\S])/g, (_, hex, ch) =>
53
+ hex ? String.fromCodePoint(parseInt(hex, 16)) : ch
54
+ )
55
+
56
+ /**
57
+ * A class token. The hex-escape branch comes first and swallows its own
58
+ * terminating space (`.a\3a b` is the single class `a:b`) — without that the
59
+ * token would stop at the space and lose everything after it.
60
+ */
61
+ const CLASS_IN_SELECTOR =
62
+ /\.((?:\\[0-9a-fA-F]{1,6}[ \t]?|\\[\s\S]|[^\s.,:;>+~()[\]{}#*%"'\\!])+)/g
63
+
64
+ /**
65
+ * Class tokens in selector position. Walks the sheet tracking brace depth and
66
+ * collects only the text that PRECEDES a `{` — a declaration body never does,
67
+ * so `padding:.5rem` can't masquerade as a class, while rules nested inside
68
+ * `@media`/`@supports`/`@layer` still get read.
69
+ */
70
+ export function definedClasses(css) {
71
+ const src = stripLiterals(stripComments(css))
72
+ const out = new Set()
73
+ let prelude = ''
74
+ let depth = 0
75
+ for (let i = 0; i < src.length; i++) {
76
+ const c = src[i]
77
+ if (c === '{') {
78
+ const sel = prelude.trim()
79
+ // at-rule preludes hold media queries and numbers, never class selectors
80
+ if (sel && !sel.startsWith('@')) {
81
+ for (const m of sel.matchAll(CLASS_IN_SELECTOR)) out.add(unescapeIdent(m[1]))
82
+ }
83
+ prelude = ''
84
+ depth++
85
+ } else if (c === '}') {
86
+ prelude = ''
87
+ depth = Math.max(0, depth - 1)
88
+ } else if (c === ';' && depth > 0) {
89
+ prelude = ''
90
+ } else {
91
+ prelude += c
92
+ }
93
+ }
94
+ return out
95
+ }
96
+
97
+ /** Scripts carry the RSC flight payload, which quotes markup; never markup. */
98
+ const stripNonMarkup = (html) =>
99
+ html
100
+ .replace(/<!--[\s\S]*?-->/g, ' ')
101
+ .replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, ' ')
102
+ .replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, ' ')
103
+
104
+ const ENTITIES = { '&amp;': '&', '&lt;': '<', '&gt;': '>', '&quot;': '"', '&#39;': "'" }
105
+
106
+ export function usedClasses(html) {
107
+ const out = new Set()
108
+ const markup = stripNonMarkup(html)
109
+ for (const m of markup.matchAll(
110
+ /\bclass(?:Name)?\s*=\s*("([^"]*)"|'([^']*)'|([^\s"'>]+))/gi
111
+ )) {
112
+ const raw = (m[2] ?? m[3] ?? m[4] ?? '').replace(
113
+ /&[a-z#0-9]+;/gi,
114
+ (e) => ENTITIES[e] ?? e
115
+ )
116
+ for (const cls of raw.split(/\s+/)) if (cls) out.add(cls)
117
+ }
118
+ return out
119
+ }
120
+
121
+ export function inlineStyles(html) {
122
+ return [...html.matchAll(/<style\b[^>]*>([\s\S]*?)<\/style>/gi)].map((m) => m[1])
123
+ }
124
+
125
+ export function stylesheetHrefs(html) {
126
+ const out = []
127
+ for (const m of html.matchAll(/<link\b[^>]*>/gi)) {
128
+ const tag = m[0]
129
+ if (!/\brel\s*=\s*["']?[^"'>]*\bstylesheet\b/i.test(tag)) continue
130
+ const href = tag.match(/\bhref\s*=\s*("([^"]*)"|'([^']*)'|([^\s"'>]+))/i)
131
+ if (href)
132
+ out.push((href[2] ?? href[3] ?? href[4]).replace(/&amp;/g, '&').split('?')[0])
133
+ }
134
+ return out
135
+ }
136
+
137
+ // ------------------------------------------------------------------ scanning
138
+
139
+ const SKIP_DIR = new Set(['node_modules', '.git', 'cache', 'sourcemaps'])
140
+
141
+ /** Pages the framework ships, not pages this app wrote. */
142
+ const BOILERPLATE = /^(404|500|_error|_not-found|not-found)\.html?$/i
143
+
144
+ function walk(dir, hit, depth = 0) {
145
+ if (depth > 12) return
146
+ let entries
147
+ try {
148
+ entries = readdirSync(dir, { withFileTypes: true })
149
+ } catch {
150
+ return
151
+ }
152
+ for (const e of entries) {
153
+ if (e.name.startsWith('.') && depth > 0) continue
154
+ const p = join(dir, e.name)
155
+ if (e.isDirectory()) {
156
+ if (!SKIP_DIR.has(e.name)) walk(p, hit, depth + 1)
157
+ } else if (e.isFile()) {
158
+ hit(p)
159
+ }
160
+ }
161
+ }
162
+
163
+ /** Built output as a browser sees it: pages, and every sheet on disk. */
164
+ export function collect(roots) {
165
+ const pages = []
166
+ const sheets = new Map() // absolute path -> css text
167
+ for (const root of roots) {
168
+ const st = statSync(root)
169
+ const add = (p) => {
170
+ if (/\.html?$/i.test(p)) pages.push(p)
171
+ else if (/\.css$/i.test(p) && !/\.map$/.test(p))
172
+ sheets.set(resolve(p), readFileSync(p, 'utf8'))
173
+ }
174
+ if (st.isDirectory()) walk(root, add)
175
+ else add(root)
176
+ }
177
+ return { pages, sheets }
178
+ }
179
+
180
+ /**
181
+ * `/_next/static/css/x.css` has to become a path on disk. Basename is the one
182
+ * key both sides agree on across Next, Vite and a plain static dir, and every
183
+ * bundler content-hashes the name, so it is unique in practice.
184
+ */
185
+ function indexByBase(sheets) {
186
+ const byBase = new Map()
187
+ for (const [p, css] of sheets) {
188
+ const b = basename(p)
189
+ if (!byBase.has(b)) byBase.set(b, { path: p, css })
190
+ }
191
+ return byBase
192
+ }
193
+
194
+ // -------------------------------------------------------------------- render
195
+
196
+ /**
197
+ * A client-rendered app ships an empty shell, so reading its HTML off disk
198
+ * measures nothing. Rendering it is the only honest way to ask the question,
199
+ * and the browser is also the only thing that knows the full sheet list once
200
+ * a runtime has injected into it.
201
+ *
202
+ * Playwright is imported lazily and is not a dependency of this package: an
203
+ * SSR app never needs it, and the apps that do need it already have it.
204
+ */
205
+ export async function render(urls, { dir }) {
206
+ // @playwright/test is what most repos here install and it re-exports the same
207
+ // chromium; asking only for `playwright` would fail in almost all of them.
208
+ let chromium
209
+ for (const pkg of ['playwright', '@playwright/test']) {
210
+ try {
211
+ ;({ chromium } = await import(pkg))
212
+ break
213
+ } catch {}
214
+ }
215
+ if (!chromium) {
216
+ throw new Error(
217
+ `--render needs playwright, and neither "playwright" nor "@playwright/test"\n` +
218
+ ` resolves from ${process.cwd()}.\n` +
219
+ ` npm i -D @playwright/test && npx playwright install chromium\n` +
220
+ ` Or point gui-css-check at pre-rendered HTML instead.`
221
+ )
222
+ }
223
+ mkdirSync(dir, { recursive: true })
224
+ const browser = await chromium.launch()
225
+ const page = await browser.newPage()
226
+ const out = []
227
+ try {
228
+ for (const [i, url] of urls.entries()) {
229
+ const res = await page.goto(url, { waitUntil: 'networkidle', timeout: 60_000 })
230
+ if (!res || !res.ok())
231
+ throw new Error(`${url} returned ${res ? res.status() : 'nothing'}`)
232
+ // Every rule the document actually has, including whatever a runtime
233
+ // injected after load. Linked sheets are kept as separate files under
234
+ // their own basenames and inline ones stay inline, so the shape on disk
235
+ // matches an SSR build exactly and one code path reads both — including
236
+ // the byte split between what a browser caches and what it re-fetches.
237
+ const { linked, inline, html } = await page.evaluate(() => {
238
+ const linked = {}
239
+ const inline = []
240
+ for (const sheet of document.styleSheets) {
241
+ let text
242
+ try {
243
+ text = [...sheet.cssRules].map((r) => r.cssText).join('\n')
244
+ } catch {
245
+ continue // cross-origin sheet, unreadable by design
246
+ }
247
+ const href = sheet.href
248
+ if (href) linked[new URL(href).pathname.split('/').pop()] = text
249
+ else inline.push(text)
250
+ }
251
+ return { linked, inline, html: document.documentElement.outerHTML }
252
+ })
253
+ const stem = `${String(i).padStart(3, '0')}-${url.replace(/[^\w.-]+/g, '_').slice(-60)}`
254
+ for (const [name, text] of Object.entries(linked))
255
+ writeFileSync(join(dir, name), text)
256
+ writeFileSync(
257
+ join(dir, `${stem}.html`),
258
+ inline.map((t) => `<style>${t}</style>`).join('') + html
259
+ )
260
+ out.push(url)
261
+ }
262
+ } finally {
263
+ await browser.close()
264
+ }
265
+ // the directory, not the files: the pages' <link> tags still point at the
266
+ // sheets, which now sit beside them under the same basenames
267
+ return [dir]
268
+ }
269
+
270
+ // -------------------------------------------------------------------- config
271
+
272
+ /**
273
+ * Classes a library provably stamps as an IDENTITY MARKER and never styles.
274
+ * Each line cites where it is emitted — an allowance without a source is a
275
+ * guess, and a guess here is how a real miss gets waved through.
276
+ */
277
+ const DEFAULT_ALLOW = [
278
+ // gui, getSplitStyles.tsx: `is_${componentNameFinal}` on every styled
279
+ // component so devtools and tests can name it. Carries no rule by design.
280
+ 'is_*',
281
+ // gui, Theme.tsx: marks a nested (non-root) Theme. The styling comes from
282
+ // the t_* theme class beside it, which does have rules.
283
+ 't_sub_theme',
284
+ // lucide-react, Icon.js: `mergeClasses("lucide", className)` and
285
+ // createLucideIcon.js: `lucide-${name}` — hooks for consumers to target.
286
+ 'lucide',
287
+ 'lucide-*',
288
+ // next/font mints these to carry a font-family custom property. The rule is
289
+ // emitted by next/font's own pipeline, not by anything in this repo.
290
+ '__variable_*',
291
+ '__className_*',
292
+ // @hanzo/ui, backends/gui/button.js `buttonVariants` and backends/gui/badge.js
293
+ // `badgeVariants`. core/cn.cjs states the contract: "There is no Tailwind
294
+ // conflict-resolution step because there are no Tailwind utilities to resolve:
295
+ // styling lives in gui style props and the token scale, and class names are
296
+ // only stable handles (`btn`, `mono`) a host may select on."
297
+ // The look comes from gui props on the same element.
298
+ //
299
+ // Enumerated, NOT `btn-*`. These used to read `hanzo-button--*`, where the
300
+ // brand prefix made provenance self-evident: nothing but @hanzo/ui would ever
301
+ // emit that. `btn-` is a name anyone might type, so a wildcard here would
302
+ // wave through a hand-written `btn-cta` that has no rule behind it — the
303
+ // exact failure this file exists to catch. The list below is every value
304
+ // ButtonVariant/ButtonSize and BadgeVariant can take; a name outside it is
305
+ // not ours and must still fail.
306
+ 'btn',
307
+ 'btn-default',
308
+ 'btn-destructive',
309
+ 'btn-outline',
310
+ 'btn-secondary',
311
+ 'btn-ghost',
312
+ 'btn-link',
313
+ 'btn-primary',
314
+ 'btn-linkFG',
315
+ 'btn-linkMuted',
316
+ 'btn-sm',
317
+ 'btn-lg',
318
+ 'btn-icon',
319
+ 'btn-icon-sm',
320
+ 'btn-icon-lg',
321
+ 'badge',
322
+ 'badge-default',
323
+ 'badge-secondary',
324
+ 'badge-destructive',
325
+ 'badge-outline',
326
+ 'badge-ghost',
327
+ 'badge-link',
328
+ 'badge-inputAdornment',
329
+ 'badge-tags',
330
+ ]
331
+ // NOT allowed, and deliberately: `mono`, `tnum`, `row`. The same comment calls
332
+ // them handles, but gui-config.js says who writes the rule —
333
+ // "The host self-hosts both faces (its own fonts.css) — this only names them."
334
+ // They are @hanzo/ui's INTERFACE to the host, so an app that stamps them and
335
+ // defines nothing renders ids and figures in the proportional face with
336
+ // proportional digits. That is a miss, and it should read as one.
337
+
338
+ const globToRe = (g) =>
339
+ new RegExp(
340
+ '^' +
341
+ g
342
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
343
+ .replace(/\*/g, '.*')
344
+ .replace(/\?/g, '.') +
345
+ '$'
346
+ )
347
+
348
+ export function loadConfig(cwd) {
349
+ for (const name of ['gui-css-check.json', '.gui-css-check.json']) {
350
+ const p = join(cwd, name)
351
+ if (existsSync(p)) return { ...JSON.parse(readFileSync(p, 'utf8')), _from: p }
352
+ }
353
+ const pkgPath = join(cwd, 'package.json')
354
+ if (existsSync(pkgPath)) {
355
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
356
+ if (pkg['gui-css-check']) return { ...pkg['gui-css-check'], _from: pkgPath }
357
+ }
358
+ return {}
359
+ }
360
+
361
+ // --------------------------------------------------------------------- check
362
+
363
+ /**
364
+ * @returns {{pages: Array, used: Set, missing: Map, sheets: Map, bytes: object}}
365
+ */
366
+ export function check({ roots, allow = [], extraCss = [] }) {
367
+ const { pages, sheets } = collect(roots)
368
+ const byBase = indexByBase(sheets)
369
+ const allowRe = [...DEFAULT_ALLOW, ...allow].map(globToRe)
370
+ const allowed = (c) => allowRe.some((re) => re.test(c))
371
+
372
+ const shared = new Set()
373
+ for (const p of extraCss)
374
+ for (const c of definedClasses(readFileSync(p, 'utf8'))) shared.add(c)
375
+
376
+ // Sheets are parsed once and reused; a page just unions the ones it links.
377
+ const parsed = new Map()
378
+ const classesOf = (path, css) => {
379
+ if (!parsed.has(path)) parsed.set(path, definedClasses(css))
380
+ return parsed.get(path)
381
+ }
382
+
383
+ const results = []
384
+ const allUsed = new Set()
385
+ const missing = new Map() // class -> [page, ...]
386
+ const linkedOnce = new Map() // path -> bytes, counted once however many pages link it
387
+ let inlineBytes = 0
388
+
389
+ for (const page of pages) {
390
+ const html = readFileSync(page, 'utf8')
391
+ const used = usedClasses(html)
392
+ const defined = new Set(shared)
393
+
394
+ for (const css of inlineStyles(html)) {
395
+ inlineBytes += Buffer.byteLength(css)
396
+ for (const c of definedClasses(css)) defined.add(c)
397
+ }
398
+
399
+ const unresolved = []
400
+ const linked = []
401
+ for (const href of stylesheetHrefs(html)) {
402
+ if (/^(https?:)?\/\//i.test(href)) continue // remote sheet, not ours to verify
403
+ const hit = byBase.get(basename(href))
404
+ if (!hit) {
405
+ unresolved.push(href)
406
+ continue
407
+ }
408
+ linked.push(hit.path)
409
+ linkedOnce.set(hit.path, Buffer.byteLength(hit.css))
410
+ for (const c of classesOf(hit.path, hit.css)) defined.add(c)
411
+ }
412
+
413
+ const miss = []
414
+ for (const c of used) {
415
+ allUsed.add(c)
416
+ if (defined.has(c) || allowed(c)) continue
417
+ miss.push(c)
418
+ if (!missing.has(c)) missing.set(c, [])
419
+ missing.get(c).push(page)
420
+ }
421
+ results.push({ page, used: used.size, missing: miss.sort(), linked, unresolved })
422
+ }
423
+
424
+ // A document with no classes at all proves nothing: it is either an empty
425
+ // shell a client will fill in, or a render that failed. Counting it as a
426
+ // pass is how a checker becomes decoration.
427
+ const empty = results.filter((r) => !r.used)
428
+ // Same trap one step along: an app whose routes are all server-rendered
429
+ // prerenders nothing but the framework's own error pages, and scoring THOSE
430
+ // says nothing whatever about the app. Seen on hanzo.id, where the single
431
+ // checked page was Next's built-in 500.
432
+ const boilerplate = results.filter((r) => BOILERPLATE.test(basename(r.page)))
433
+
434
+ return {
435
+ results,
436
+ pages,
437
+ sheets,
438
+ empty,
439
+ boilerplate,
440
+ used: allUsed,
441
+ missing,
442
+ bytes: {
443
+ // cached once by the browser, however many pages link it
444
+ static: [...linkedOnce.values()].reduce((a, b) => a + b, 0),
445
+ staticFiles: linkedOnce.size,
446
+ // re-sent with every single document and cacheable by nobody — the
447
+ // number a static extractor exists to drive to zero
448
+ inlinePerPage: pages.length ? Math.round(inlineBytes / pages.length) : 0,
449
+ inlineTotal: inlineBytes,
450
+ },
451
+ }
452
+ }
453
+
454
+ // ------------------------------------------------------------------------ cli
455
+
456
+ const HELP = `gui-css-check — fail the build when the markup uses a class the CSS never defines
457
+
458
+ gui-css-check [dir|file ...] [options]
459
+
460
+ With no path it looks for .next, dist, out, build, storybook-static in cwd.
461
+
462
+ --render <url> render a running route in a browser and check THAT (repeatable).
463
+ The only honest measurement for a client-rendered app, whose
464
+ built HTML is an empty shell. Needs playwright in the app.
465
+ --css <file> an extra sheet every page gets (repeatable)
466
+ --allow <glob> a class pattern that needs no rule (repeatable)
467
+ --json machine-readable result on stdout
468
+ --quiet only print on failure
469
+
470
+ Allowances also load from gui-css-check.json or a "gui-css-check" key in
471
+ package.json: { "allow": ["swiper-*"] }
472
+ `
473
+
474
+ const DEFAULT_ROOTS = ['.next', 'dist', 'out', 'build', 'storybook-static']
475
+
476
+ /** Utility-framework shapes: `px-3`, `text-white/40`, `hover:bg-white/[0.06]`. */
477
+ const UTILITY =
478
+ /^(-?[a-z][\w-]*:)*-?((p|m|px|py|pt|pb|pl|pr|mx|my|mt|mb|ml|mr|w|h|z|gap|text|bg|border|ring|max|min|top|left|right|bottom|inset|col|row|order|basis|space|leading|tracking|font|opacity|shadow|rounded|aspect|overflow|translate|scale|rotate|duration|delay|ease|from|via|to|fill|stroke|divide|placeholder|outline|decoration|indent|justify|items|content|self|place|object|cursor|backdrop|prose|grid|flex)(-[\w./[\]#%()-]+)?|(flex|grid|block|inline|hidden|absolute|relative|fixed|sticky|static|truncate|italic|underline|uppercase|antialiased|container|prose|sr-only|isolate|visible|invisible))$/
479
+
480
+ /**
481
+ * Misses are grouped by CAUSE, because the fix differs completely and a flat
482
+ * list of 300 class names tells you nothing about which one you have.
483
+ */
484
+ const KINDS = [
485
+ {
486
+ test: (c) => c.startsWith('_'),
487
+ title: 'gui atomic classes — the atomic sheet was never authored',
488
+ cause:
489
+ ` Nothing produced these rules. Exactly one of the two paths has to run:\n` +
490
+ ` · GuiProvider injects at runtime — do NOT set disableInjectCSS, or\n` +
491
+ ` · a compiler plugin extracts them at build time —\n` +
492
+ ` @hanzogui/next-plugin withGui() / @hanzogui/vite-plugin gui().\n` +
493
+ ` disableInjectCSS with no plugin configured leaves nobody writing it.`,
494
+ },
495
+ {
496
+ test: (c) => UTILITY.test(c),
497
+ title: 'utility-framework classes — no such framework is installed',
498
+ cause:
499
+ ` These are Tailwind-shaped, and nothing in this build compiles them.\n` +
500
+ ` They have never rendered. Delete them and use gui props instead;\n` +
501
+ ` adding Tailwind back is not the fix.`,
502
+ },
503
+ {
504
+ test: () => true,
505
+ title: 'authored classes with no stylesheet behind them',
506
+ cause:
507
+ ` Someone wrote these class names and no delivered sheet defines them.\n` +
508
+ ` Either ship the stylesheet that styles them or drop the class.`,
509
+ },
510
+ ]
511
+
512
+ const num = (n) => n.toLocaleString('en-US')
513
+ const kb = (n) => `${num(Math.round(n / 1024))} KB`
514
+
515
+ async function main(argv) {
516
+ const roots = []
517
+ const extraCss = []
518
+ const allow = []
519
+ const urls = []
520
+ let json = false
521
+ let quiet = false
522
+ for (let i = 0; i < argv.length; i++) {
523
+ const a = argv[i]
524
+ if (a === '--help' || a === '-h') return (console.log(HELP), 0)
525
+ else if (a === '--json') json = true
526
+ else if (a === '--quiet') quiet = true
527
+ else if (a === '--css') extraCss.push(argv[++i])
528
+ else if (a === '--allow') allow.push(argv[++i])
529
+ else if (a === '--render') urls.push(argv[++i])
530
+ else if (a.startsWith('-')) return (console.error(`unknown option ${a}\n${HELP}`), 2)
531
+ else roots.push(a)
532
+ }
533
+
534
+ const cwd = process.cwd()
535
+ const cfg = loadConfig(cwd)
536
+ const wanted = [...urls, ...(urls.length ? [] : (cfg.render ?? []))]
537
+
538
+ let found = roots
539
+ if (wanted.length) {
540
+ const dir = mkdtempSync(join(tmpdir(), 'gui-css-check-'))
541
+ try {
542
+ found = await render(wanted, { dir })
543
+ } catch (err) {
544
+ console.error(`gui-css-check: ${err.message}`)
545
+ return 2
546
+ }
547
+ } else if (!found.length) {
548
+ found = DEFAULT_ROOTS.filter((d) => existsSync(join(cwd, d)))
549
+ }
550
+
551
+ if (!found.length) {
552
+ console.error(
553
+ `gui-css-check: no built output.\n` +
554
+ ` Looked for ${DEFAULT_ROOTS.join(', ')} in ${cwd}\n` +
555
+ ` Build first, or pass the directory holding the rendered HTML and CSS,\n` +
556
+ ` or --render <url> against a running server.`
557
+ )
558
+ return 2
559
+ }
560
+ for (const r of found)
561
+ if (!existsSync(r)) return (console.error(`gui-css-check: no such path: ${r}`), 2)
562
+
563
+ const res = check({
564
+ roots: found,
565
+ allow: [...(cfg.allow ?? []), ...allow],
566
+ extraCss: [...(cfg.css ?? []), ...extraCss],
567
+ })
568
+
569
+ const nothingMeasured =
570
+ !res.pages.length ||
571
+ res.empty.length === res.pages.length ||
572
+ res.boilerplate.length === res.pages.length
573
+ if (nothingMeasured) {
574
+ console.error(
575
+ `gui-css-check: ` +
576
+ (!res.pages.length
577
+ ? `found no rendered HTML under ${found.join(', ')}.\n`
578
+ : res.empty.length === res.pages.length
579
+ ? `all ${res.pages.length} document(s) under ${found.join(', ')} use ZERO classes.\n`
580
+ : `the only prerendered page(s) here are the framework's own error pages\n` +
581
+ ` (${res.boilerplate.map((r) => basename(r.page)).join(', ')}).\n`) +
582
+ ` Nothing about this app was checked, so nothing is proven — that is a\n` +
583
+ ` failure, not a pass. Either the routes are all server-rendered or the app\n` +
584
+ ` renders on the client. Check the real thing:\n` +
585
+ ` gui-css-check --render http://localhost:3000/`
586
+ )
587
+ return 2
588
+ }
589
+
590
+ const unresolved = res.results.flatMap((r) => r.unresolved.map((h) => [r.page, h]))
591
+ const total = res.used.size
592
+ const missCount = res.missing.size
593
+ const covered = total - missCount
594
+ const pct = total ? (covered / total) * 100 : 100
595
+
596
+ if (json) {
597
+ console.log(
598
+ JSON.stringify(
599
+ {
600
+ pages: res.pages.length,
601
+ sheets: res.sheets.size,
602
+ bytes: res.bytes,
603
+ used: total,
604
+ covered,
605
+ coverage: Number(pct.toFixed(2)),
606
+ missing: Object.fromEntries(
607
+ [...res.missing].map(([c, pages]) => [c, pages.map((p) => relative(cwd, p))])
608
+ ),
609
+ unresolved: unresolved.map(([p, h]) => ({ page: relative(cwd, p), href: h })),
610
+ },
611
+ null,
612
+ 2
613
+ )
614
+ )
615
+ return missCount || unresolved.length ? 1 : 0
616
+ }
617
+
618
+ const ok = !missCount && !unresolved.length
619
+ if (!ok || !quiet) {
620
+ const b = res.bytes
621
+ console.log(
622
+ `gui-css-check ${num(res.pages.length)} page(s) · ` +
623
+ `${b.staticFiles} cached sheet(s), ${kb(b.static)} · ` +
624
+ `${kb(b.inlinePerPage)} inline per document\n` +
625
+ ` ${num(covered)}/${num(total)} classes covered (${pct.toFixed(1)}%)` +
626
+ // 100% of nothing is still 100%, and that is the shape of a page that
627
+ // failed to render. Say so on the same line as the score.
628
+ (res.empty.length
629
+ ? `\n ${num(res.empty.length)} page(s) use no classes at all`
630
+ : '')
631
+ )
632
+ }
633
+
634
+ if (unresolved.length) {
635
+ console.error(
636
+ `\nFAIL ${unresolved.length} stylesheet link(s) point at a file that is not there:`
637
+ )
638
+ for (const [page, href] of unresolved.slice(0, 20))
639
+ console.error(` ${relative(cwd, page)} -> ${href}`)
640
+ }
641
+
642
+ if (missCount) {
643
+ console.error(
644
+ `\nFAIL ${num(missCount)} of ${num(total)} classes in the markup have NO rule in any\n` +
645
+ ` stylesheet these pages deliver. They render with no styling at all.`
646
+ )
647
+ const bins = KINDS.map(() => [])
648
+ for (const c of res.missing.keys()) bins[KINDS.findIndex((k) => k.test(c))].push(c)
649
+ for (const [i, kind] of KINDS.entries()) {
650
+ const classes = bins[i]
651
+ if (!classes.length) continue
652
+ const where = relative(cwd, res.missing.get(classes[0])[0])
653
+ console.error(`\n ${classes.length}× ${kind.title}`)
654
+ console.error(
655
+ classes
656
+ .slice(0, 10)
657
+ .map((c) => ` ${c}`)
658
+ .join('\n')
659
+ )
660
+ if (classes.length > 10) console.error(` … ${classes.length - 10} more`)
661
+ console.error(` first in ${where}\n${kind.cause}`)
662
+ }
663
+ console.error(
664
+ ` A class that really is styled by something this check cannot see belongs\n` +
665
+ ` in gui-css-check.json {"allow": [...]}, with a note saying who styles it.\n`
666
+ )
667
+ }
668
+
669
+ return ok ? 0 : 1
670
+ }
671
+
672
+ const invokedDirectly =
673
+ process.argv[1] &&
674
+ (import.meta.url === `file://${process.argv[1]}` ||
675
+ resolve(process.argv[1]).endsWith(`${sep}css-check.mjs`) ||
676
+ basename(process.argv[1]) === 'gui-css-check')
677
+
678
+ if (invokedDirectly) process.exit(await main(process.argv.slice(2)))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzo/gui",
3
- "version": "8.0.1",
3
+ "version": "8.1.0",
4
4
  "gitHead": "a49cc7ea6b93ba384e77a4880ae48ac4a5635c14",
5
5
  "description": "Style and UI for React (web and native) meet an optimizing compiler",
6
6
  "repository": {
@@ -9,6 +9,25 @@
9
9
  "directory": "pkgs/ui/hanzogui"
10
10
  },
11
11
  "source": "src/index.ts",
12
+ "files": [
13
+ "bundle-native.mjs",
14
+ "css-check.mjs",
15
+ "dist",
16
+ "hanzogui.config.ts",
17
+ "linear-gradient",
18
+ "native-test.cjs",
19
+ "native-test.d.ts",
20
+ "native-test",
21
+ "native.cjs",
22
+ "native.d.ts",
23
+ "native",
24
+ "react-native-web.cjs",
25
+ "react-native-web",
26
+ "src",
27
+ "types",
28
+ "web.cjs",
29
+ "web"
30
+ ],
12
31
  "type": "module",
13
32
  "sideEffects": [
14
33
  "setup.*"
@@ -16,8 +35,12 @@
16
35
  "main": "dist/cjs",
17
36
  "module": "dist/esm",
18
37
  "types": "./types/index.d.ts",
38
+ "bin": {
39
+ "gui-css-check": "./css-check.mjs"
40
+ },
19
41
  "exports": {
20
42
  "./package.json": "./package.json",
43
+ "./css-check": "./css-check.mjs",
21
44
  ".": {
22
45
  "types": "./types/index.d.ts",
23
46
  "react-native": "./dist/esm/index.native.js",
@@ -74,75 +97,77 @@
74
97
  "clean": "hanzogui-build clean",
75
98
  "clean:build": "hanzogui-build clean:build",
76
99
  "check": "bun run check-circular-deps",
77
- "check-circular-deps": "npx madge --circular ./src/index.ts"
100
+ "check-circular-deps": "npx madge --circular ./src/index.ts",
101
+ "test": "node --test css-check.test.mjs"
78
102
  },
79
103
  "dependencies": {
80
- "@hanzogui/accordion": "8.0.1",
81
- "@hanzogui/adapt": "8.0.1",
82
- "@hanzogui/alert-dialog": "8.0.1",
83
- "@hanzogui/animate": "8.0.0",
84
- "@hanzogui/animate-presence": "8.0.0",
85
- "@hanzogui/avatar": "8.0.1",
86
- "@hanzogui/button": "8.0.1",
87
- "@hanzogui/card": "8.0.1",
88
- "@hanzogui/checkbox": "8.0.1",
89
- "@hanzogui/collapsible": "8.0.1",
90
- "@hanzogui/compose-refs": "8.0.0",
91
- "@hanzogui/constants": "8.0.0",
92
- "@hanzogui/context-menu": "8.0.1",
93
- "@hanzogui/core": "8.0.1",
94
- "@hanzogui/create-context": "8.0.0",
95
- "@hanzogui/create-menu": "8.0.1",
96
- "@hanzogui/dialog": "8.0.1",
97
- "@hanzogui/element": "8.0.0",
98
- "@hanzogui/elements": "8.0.1",
99
- "@hanzogui/fake-react-native": "8.0.0",
100
- "@hanzogui/focusable": "8.0.0",
101
- "@hanzogui/form": "8.0.1",
102
- "@hanzogui/get-button-sized": "8.0.0",
103
- "@hanzogui/get-font-sized": "8.0.0",
104
- "@hanzogui/get-token": "8.0.0",
105
- "@hanzogui/group": "8.0.1",
106
- "@hanzogui/component-helpers": "8.0.0",
107
- "@hanzogui/image": "8.0.1",
108
- "@hanzogui/input": "8.0.1",
109
- "@hanzogui/label": "8.0.0",
110
- "@hanzogui/linear-gradient": "8.0.1",
111
- "@hanzogui/list-item": "8.0.1",
112
- "@hanzogui/menu": "8.0.1",
113
- "@hanzogui/polyfill-dev": "8.0.0",
114
- "@hanzogui/popover": "8.0.1",
115
- "@hanzogui/popper": "8.0.1",
116
- "@hanzogui/portal": "8.0.1",
117
- "@hanzogui/progress": "8.0.1",
118
- "@hanzogui/radio-group": "8.0.1",
119
- "@hanzogui/react-native-media-driver": "8.0.0",
120
- "@hanzogui/scroll-view": "8.0.1",
121
- "@hanzogui/select": "8.0.1",
122
- "@hanzogui/separator": "8.0.1",
123
- "@hanzogui/shapes": "8.0.1",
124
- "@hanzogui/sheet": "8.0.1",
125
- "@hanzogui/slider": "8.0.1",
126
- "@hanzogui/spacer": "8.0.0",
127
- "@hanzogui/spinner": "8.0.1",
128
- "@hanzogui/stacks": "8.0.1",
129
- "@hanzogui/switch": "8.0.1",
130
- "@hanzogui/tabs": "8.0.1",
131
- "@hanzogui/text": "8.0.0",
132
- "@hanzogui/theme": "8.0.0",
133
- "@hanzogui/toast": "8.0.1",
134
- "@hanzogui/toggle-group": "8.0.1",
135
- "@hanzogui/tooltip": "8.0.1",
136
- "@hanzogui/use-controllable-state": "8.0.0",
137
- "@hanzogui/use-debounce": "8.0.0",
138
- "@hanzogui/use-force-update": "8.0.0",
139
- "@hanzogui/use-window-dimensions": "8.0.0",
140
- "@hanzogui/visually-hidden": "8.0.0",
141
- "@hanzogui/font-size": "8.0.1",
142
- "@hanzogui/z-index-stack": "8.0.0"
104
+ "@hanzogui/accordion": "8.1.0",
105
+ "@hanzogui/adapt": "8.1.0",
106
+ "@hanzogui/alert-dialog": "8.1.0",
107
+ "@hanzogui/animate": "8.1.0",
108
+ "@hanzogui/animate-presence": "8.1.0",
109
+ "@hanzogui/avatar": "8.1.0",
110
+ "@hanzogui/button": "8.1.0",
111
+ "@hanzogui/card": "8.1.0",
112
+ "@hanzogui/checkbox": "8.1.0",
113
+ "@hanzogui/collapsible": "8.1.0",
114
+ "@hanzogui/component-helpers": "8.1.0",
115
+ "@hanzogui/compose-refs": "8.1.0",
116
+ "@hanzogui/constants": "8.1.0",
117
+ "@hanzogui/context-menu": "8.1.0",
118
+ "@hanzogui/core": "8.1.0",
119
+ "@hanzogui/create-context": "8.1.0",
120
+ "@hanzogui/create-menu": "8.1.0",
121
+ "@hanzogui/dialog": "8.1.0",
122
+ "@hanzogui/element": "8.1.0",
123
+ "@hanzogui/elements": "8.1.0",
124
+ "@hanzogui/fake-react-native": "8.1.0",
125
+ "@hanzogui/focusable": "8.1.0",
126
+ "@hanzogui/font-size": "8.1.0",
127
+ "@hanzogui/form": "8.1.0",
128
+ "@hanzogui/get-button-sized": "8.1.0",
129
+ "@hanzogui/get-font-sized": "8.1.0",
130
+ "@hanzogui/get-token": "8.1.0",
131
+ "@hanzogui/group": "8.1.0",
132
+ "@hanzogui/image": "8.1.0",
133
+ "@hanzogui/input": "8.1.0",
134
+ "@hanzogui/label": "8.1.0",
135
+ "@hanzogui/linear-gradient": "8.1.0",
136
+ "@hanzogui/list-item": "8.1.0",
137
+ "@hanzogui/menu": "8.1.0",
138
+ "@hanzogui/polyfill-dev": "8.1.0",
139
+ "@hanzogui/popover": "8.1.0",
140
+ "@hanzogui/popper": "8.1.0",
141
+ "@hanzogui/portal": "8.1.0",
142
+ "@hanzogui/progress": "8.1.0",
143
+ "@hanzogui/radio-group": "8.1.0",
144
+ "@hanzogui/react-native-media-driver": "8.1.0",
145
+ "@hanzogui/scroll-view": "8.1.0",
146
+ "@hanzogui/select": "8.1.0",
147
+ "@hanzogui/separator": "8.1.0",
148
+ "@hanzogui/shapes": "8.1.0",
149
+ "@hanzogui/sheet": "8.1.0",
150
+ "@hanzogui/slider": "8.1.0",
151
+ "@hanzogui/spacer": "8.1.0",
152
+ "@hanzogui/spinner": "8.1.0",
153
+ "@hanzogui/stacks": "8.1.0",
154
+ "@hanzogui/switch": "8.1.0",
155
+ "@hanzogui/tabs": "8.1.0",
156
+ "@hanzogui/text": "8.1.0",
157
+ "@hanzogui/theme": "8.1.0",
158
+ "@hanzogui/toast": "8.1.0",
159
+ "@hanzogui/toggle-group": "8.1.0",
160
+ "@hanzogui/tooltip": "8.1.0",
161
+ "@hanzogui/use-controllable-state": "8.1.0",
162
+ "@hanzogui/use-debounce": "8.1.0",
163
+ "@hanzogui/use-force-update": "8.1.0",
164
+ "@hanzogui/use-window-dimensions": "8.1.0",
165
+ "@hanzogui/visually-hidden": "8.1.0",
166
+ "@hanzogui/web": "8.1.0",
167
+ "@hanzogui/z-index-stack": "8.1.0"
143
168
  },
144
169
  "devDependencies": {
145
- "@hanzogui/build": "8.0.0",
170
+ "@hanzogui/build": "8.1.0",
146
171
  "react": ">=19",
147
172
  "react-native": "0.83.2",
148
173
  "react-native-web": "^0.21.0"
@@ -5,11 +5,11 @@ export interface AnchorExtraProps {
5
5
  rel?: string;
6
6
  }
7
7
  export type AnchorProps = SizableTextProps & AnchorExtraProps;
8
- export declare const Anchor: import("@hanzogui/core").GuiComponent<Omit<import("@hanzogui/core").GetFinalProps<import("@hanzogui/core").TextNonStyleProps, import("@hanzogui/core").TextStylePropsBase, {
8
+ export declare const Anchor: import("@hanzogui/web").GuiComponent<Omit<import("@hanzogui/web").GetFinalProps<import("@hanzogui/web").TextNonStyleProps, import("@hanzogui/web").TextStylePropsBase, {
9
9
  unstyled?: boolean | undefined;
10
- size?: import("@hanzogui/core").FontSizeTokens | undefined;
11
- }>, keyof AnchorExtraProps> & AnchorExtraProps, import("@hanzogui/core").GuiTextElement, import("@hanzogui/core").TextNonStyleProps & AnchorExtraProps, import("@hanzogui/core").TextStylePropsBase, {
10
+ size?: import("@hanzogui/web").FontSizeTokens | undefined;
11
+ }>, keyof AnchorExtraProps> & AnchorExtraProps, import("@hanzogui/web").GuiTextElement, import("@hanzogui/web").TextNonStyleProps & AnchorExtraProps, import("@hanzogui/web").TextStylePropsBase, {
12
12
  unstyled?: boolean | undefined;
13
- size?: import("@hanzogui/core").FontSizeTokens | undefined;
14
- }, import("@hanzogui/core").StaticConfigPublic>;
13
+ size?: import("@hanzogui/web").FontSizeTokens | undefined;
14
+ }, import("@hanzogui/web").StaticConfigPublic>;
15
15
  //# sourceMappingURL=Anchor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Anchor.d.ts","sourceRoot":"","sources":["../../src/views/Anchor.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAItD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,MAAM,MAAM,WAAW,GAAG,gBAAgB,GAAG,gBAAgB,CAAA;AAQ7D,eAAO,MAAM,MAAM;;;;;;+CAuBlB,CAAA"}
1
+ {"version":3,"file":"Anchor.d.ts","sourceRoot":"","sources":["../../src/views/Anchor.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAItD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,MAAM,MAAM,WAAW,GAAG,gBAAgB,GAAG,gBAAgB,CAAA;AAQ7D,eAAO,MAAM,MAAM;;;;;;8CAuBlB,CAAA"}
@@ -1,2 +1,2 @@
1
- export declare const EnsureFlexed: import("@hanzogui/core").GuiComponent<import("@hanzogui/core").TamaDefer, import("@hanzogui/core").GuiTextElement, import("@hanzogui/core").RNGuiTextNonStyleProps, import("@hanzogui/core").TextStylePropsBase, {}, import("@hanzogui/core").StaticConfigPublic>;
1
+ export declare const EnsureFlexed: import("@hanzogui/web").GuiComponent<import("@hanzogui/web").TamaDefer, import("@hanzogui/web").GuiTextElement, import("@hanzogui/core").RNGuiTextNonStyleProps, import("@hanzogui/web").TextStylePropsBase, {}, import("@hanzogui/web").StaticConfigPublic>;
2
2
  //# sourceMappingURL=EnsureFlexed.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"EnsureFlexed.d.ts","sourceRoot":"","sources":["../../src/views/EnsureFlexed.tsx"],"names":[],"mappings":"AAIA,eAAO,MAAM,YAAY,mQAQvB,CAAA"}
1
+ {"version":3,"file":"EnsureFlexed.d.ts","sourceRoot":"","sources":["../../src/views/EnsureFlexed.tsx"],"names":[],"mappings":"AAIA,eAAO,MAAM,YAAY,8PAQvB,CAAA"}
@@ -1,8 +1,8 @@
1
1
  import type { GetProps } from '@hanzogui/core';
2
- export declare const Fieldset: import("@hanzogui/core").GuiComponent<import("@hanzogui/core").TamaDefer, import("@hanzogui/core").GuiElement, import("@hanzogui/core").RNGuiViewNonStyleProps, import("@hanzogui/core").StackStyleBase, {
3
- elevation?: number | import("@hanzogui/core").SizeTokens | undefined;
2
+ export declare const Fieldset: import("@hanzogui/web").GuiComponent<import("@hanzogui/web").TamaDefer, import("@hanzogui/web").GuiElement, import("@hanzogui/core").RNGuiViewNonStyleProps, import("@hanzogui/web").StackStyleBase, {
3
+ elevation?: number | import("@hanzogui/web").SizeTokens | undefined;
4
4
  fullscreen?: boolean | undefined;
5
5
  horizontal?: boolean | undefined;
6
- }, import("@hanzogui/core").StaticConfigPublic>;
6
+ }, import("@hanzogui/web").StaticConfigPublic>;
7
7
  export type FieldsetProps = GetProps<typeof Fieldset>;
8
8
  //# sourceMappingURL=Fieldset.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Fieldset.d.ts","sourceRoot":"","sources":["../../src/views/Fieldset.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAI9C,eAAO,MAAM,QAAQ;;;;+CAenB,CAAA;AAEF,MAAM,MAAM,aAAa,GAAG,QAAQ,CAAC,OAAO,QAAQ,CAAC,CAAA"}
1
+ {"version":3,"file":"Fieldset.d.ts","sourceRoot":"","sources":["../../src/views/Fieldset.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAI9C,eAAO,MAAM,QAAQ;;;;8CAenB,CAAA;AAEF,MAAM,MAAM,aAAa,GAAG,QAAQ,CAAC,OAAO,QAAQ,CAAC,CAAA"}
@@ -1,4 +1,4 @@
1
- export declare const Text: import("@hanzogui/core").GuiComponent<import("@hanzogui/core").TamaDefer, import("@hanzogui/core").GuiTextElement, import("@hanzogui/core").RNGuiTextNonStyleProps, import("@hanzogui/core").TextStylePropsBase, {
1
+ export declare const Text: import("@hanzogui/web").GuiComponent<import("@hanzogui/web").TamaDefer, import("@hanzogui/web").GuiTextElement, import("@hanzogui/core").RNGuiTextNonStyleProps, import("@hanzogui/web").TextStylePropsBase, {
2
2
  unstyled?: boolean | undefined;
3
- }, import("@hanzogui/core").StaticConfigPublic>;
3
+ }, import("@hanzogui/web").StaticConfigPublic>;
4
4
  //# sourceMappingURL=Text.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Text.d.ts","sourceRoot":"","sources":["../../src/views/Text.tsx"],"names":[],"mappings":"AAEA,eAAO,MAAM,IAAI;;+CAYf,CAAA"}
1
+ {"version":3,"file":"Text.d.ts","sourceRoot":"","sources":["../../src/views/Text.tsx"],"names":[],"mappings":"AAEA,eAAO,MAAM,IAAI;;8CAYf,CAAA"}
@@ -1,5 +1,5 @@
1
- export declare const VisuallyHidden: import("@hanzogui/core").GuiComponent<import("@hanzogui/core").TamaDefer, import("@hanzogui/core").GuiElement, import("@hanzogui/core").RNGuiViewNonStyleProps, import("@hanzogui/core").StackStyleBase, {
1
+ export declare const VisuallyHidden: import("@hanzogui/web").GuiComponent<import("@hanzogui/web").TamaDefer, import("@hanzogui/web").GuiElement, import("@hanzogui/core").RNGuiViewNonStyleProps, import("@hanzogui/web").StackStyleBase, {
2
2
  visible?: boolean | undefined;
3
3
  preserveDimensions?: boolean | undefined;
4
- }, import("@hanzogui/core").StaticConfigPublic>;
4
+ }, import("@hanzogui/web").StaticConfigPublic>;
5
5
  //# sourceMappingURL=VisuallyHidden.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"VisuallyHidden.d.ts","sourceRoot":"","sources":["../../src/views/VisuallyHidden.tsx"],"names":[],"mappings":"AAEA,eAAO,MAAM,cAAc;;;+CAgCzB,CAAA"}
1
+ {"version":3,"file":"VisuallyHidden.d.ts","sourceRoot":"","sources":["../../src/views/VisuallyHidden.tsx"],"names":[],"mappings":"AAEA,eAAO,MAAM,cAAc;;;8CAgCzB,CAAA"}
@@ -1,4 +0,0 @@
1
- $ hanzogui-build
2
- built @hanzo/gui in 866 ms
3
- Running afterBuild script...
4
- afterBuild completed in 249 ms
package/tsconfig.json DELETED
@@ -1,171 +0,0 @@
1
- {
2
- "extends": "../../../tsconfig.json",
3
- "compilerOptions": {
4
- "composite": true
5
- },
6
- "exclude": [
7
- "hanzogui.config.ts",
8
- "test",
9
- "dist",
10
- "types",
11
- "./linear-gradient",
12
- "./native.d.ts",
13
- "./native-test.d.ts",
14
- "**/__tests__"
15
- ],
16
- "references": [
17
- {
18
- "path": "../../core/compose-refs"
19
- },
20
- {
21
- "path": "../../core/constants"
22
- },
23
- {
24
- "path": "../../core/core"
25
- },
26
- {
27
- "path": "../../core/create-context"
28
- },
29
- {
30
- "path": "../../core/font-size"
31
- },
32
- {
33
- "path": "../../core/get-button-sized"
34
- },
35
- {
36
- "path": "../../core/get-font-sized"
37
- },
38
- {
39
- "path": "../../core/get-token"
40
- },
41
- {
42
- "path": "../../core/component-helpers"
43
- },
44
- {
45
- "path": "../../core/react-native-media-driver"
46
- },
47
- {
48
- "path": "../../core/theme"
49
- },
50
- {
51
- "path": "../../core/use-controllable-state"
52
- },
53
- {
54
- "path": "../../core/use-debounce"
55
- },
56
- {
57
- "path": "../../core/use-force-update"
58
- },
59
- {
60
- "path": "../../core/use-window-dimensions"
61
- },
62
- {
63
- "path": "../../element"
64
- },
65
- {
66
- "path": "../accordion"
67
- },
68
- {
69
- "path": "../adapt"
70
- },
71
- {
72
- "path": "../alert-dialog"
73
- },
74
- {
75
- "path": "../animate-presence"
76
- },
77
- {
78
- "path": "../avatar"
79
- },
80
- {
81
- "path": "../button"
82
- },
83
- {
84
- "path": "../card"
85
- },
86
- {
87
- "path": "../checkbox"
88
- },
89
- {
90
- "path": "../dialog"
91
- },
92
- {
93
- "path": "../elements"
94
- },
95
- {
96
- "path": "../focusable"
97
- },
98
- {
99
- "path": "../form"
100
- },
101
- {
102
- "path": "../group"
103
- },
104
- {
105
- "path": "../image"
106
- },
107
- {
108
- "path": "../label"
109
- },
110
- {
111
- "path": "../linear-gradient"
112
- },
113
- {
114
- "path": "../list-item"
115
- },
116
- {
117
- "path": "../popover"
118
- },
119
- {
120
- "path": "../popper"
121
- },
122
- {
123
- "path": "../portal"
124
- },
125
- {
126
- "path": "../progress"
127
- },
128
- {
129
- "path": "../radio-group"
130
- },
131
- {
132
- "path": "../scroll-view"
133
- },
134
- {
135
- "path": "../select"
136
- },
137
- {
138
- "path": "../separator"
139
- },
140
- {
141
- "path": "../shapes"
142
- },
143
- {
144
- "path": "../sheet"
145
- },
146
- {
147
- "path": "../slider"
148
- },
149
- {
150
- "path": "../stacks"
151
- },
152
- {
153
- "path": "../switch"
154
- },
155
- {
156
- "path": "../tabs"
157
- },
158
- {
159
- "path": "../text"
160
- },
161
- {
162
- "path": "../toggle-group"
163
- },
164
- {
165
- "path": "../tooltip"
166
- },
167
- {
168
- "path": "../visually-hidden"
169
- }
170
- ]
171
- }