@evinvest/uikit 0.30.0 → 0.31.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/README.md +266 -72
- package/dist/{chunk-3IPB4QQJ.js → chunk-35SPIU4U.js} +10 -6
- package/dist/chunk-35SPIU4U.js.map +1 -0
- package/dist/components/account-list.js +128 -0
- package/dist/components/account-list.js.map +1 -0
- package/dist/components/account-menu-profile.js +114 -0
- package/dist/components/account-menu-profile.js.map +1 -0
- package/dist/components/account-menu.js +50 -23
- package/dist/components/account-menu.js.map +1 -1
- package/dist/components/account-settings-dialog.js +84 -0
- package/dist/components/account-settings-dialog.js.map +1 -0
- package/dist/components/alert-dialog.js +3 -2
- package/dist/components/alert-dialog.js.map +1 -1
- package/dist/components/animated-number-columns.js +105 -0
- package/dist/components/animated-number-columns.js.map +1 -0
- package/dist/components/animated-number.js +157 -0
- package/dist/components/animated-number.js.map +1 -0
- package/dist/components/app-shell.js +9 -0
- package/dist/components/app-shell.js.map +1 -1
- package/dist/components/aside.js +140 -0
- package/dist/components/aside.js.map +1 -0
- package/dist/components/badge.js +1 -1
- package/dist/components/badge.js.map +1 -1
- package/dist/components/button.js +3 -11
- package/dist/components/button.js.map +1 -1
- package/dist/components/chat-composer.js +141 -0
- package/dist/components/chat-composer.js.map +1 -0
- package/dist/components/chat-message.js +195 -0
- package/dist/components/chat-message.js.map +1 -0
- package/dist/components/chat-panel.js +137 -0
- package/dist/components/chat-panel.js.map +1 -0
- package/dist/components/chat-stream-text.js +69 -0
- package/dist/components/chat-stream-text.js.map +1 -0
- package/dist/components/chat-typing.js +26 -0
- package/dist/components/chat-typing.js.map +1 -0
- package/dist/components/command-palette.js +119 -0
- package/dist/components/command-palette.js.map +1 -0
- package/dist/components/command.js +3 -1
- package/dist/components/command.js.map +1 -1
- package/dist/components/confirm-dialog.js +123 -0
- package/dist/components/confirm-dialog.js.map +1 -0
- package/dist/components/context-menu.js +53 -5
- package/dist/components/context-menu.js.map +1 -1
- package/dist/components/dialog.js +3 -2
- package/dist/components/dialog.js.map +1 -1
- package/dist/components/drawer.js +3 -1
- package/dist/components/drawer.js.map +1 -1
- package/dist/components/dropdown-menu.js +64 -5
- package/dist/components/dropdown-menu.js.map +1 -1
- package/dist/components/form-row.js +51 -0
- package/dist/components/form-row.js.map +1 -0
- package/dist/components/header-controls.js +149 -0
- package/dist/components/header-controls.js.map +1 -0
- package/dist/components/icon-button.js +35 -0
- package/dist/components/icon-button.js.map +1 -0
- package/dist/components/key-value.js +68 -0
- package/dist/components/key-value.js.map +1 -0
- package/dist/components/menubar.js +39 -5
- package/dist/components/menubar.js.map +1 -1
- package/dist/components/native-select.js +3 -4
- package/dist/components/native-select.js.map +1 -1
- package/dist/components/nav-collapse.js +142 -0
- package/dist/components/nav-collapse.js.map +1 -0
- package/dist/components/nav-model.js.map +1 -1
- package/dist/components/navigation-menu.js +4 -5
- package/dist/components/navigation-menu.js.map +1 -1
- package/dist/components/page-frame.js +2 -5
- package/dist/components/page-frame.js.map +1 -1
- package/dist/components/popover-form.js +60 -0
- package/dist/components/popover-form.js.map +1 -0
- package/dist/components/resource-error.js +1 -1
- package/dist/components/resource-error.js.map +1 -1
- package/dist/components/section-nav.js +73 -20
- package/dist/components/section-nav.js.map +1 -1
- package/dist/components/select.js +16 -6
- package/dist/components/select.js.map +1 -1
- package/dist/components/settings.js +94 -0
- package/dist/components/settings.js.map +1 -0
- package/dist/components/sheet.js +3 -2
- package/dist/components/sheet.js.map +1 -1
- package/dist/components/shell-nav.js +80 -38
- package/dist/components/shell-nav.js.map +1 -1
- package/dist/components/smart-header.js +53 -0
- package/dist/components/smart-header.js.map +1 -0
- package/dist/components/status-item.js +42 -0
- package/dist/components/status-item.js.map +1 -0
- package/dist/components/system-banner.js +22 -12
- package/dist/components/system-banner.js.map +1 -1
- package/dist/components/top-bar.js +4 -3
- package/dist/components/top-bar.js.map +1 -1
- package/dist/components/ui-icons.js +77 -0
- package/dist/components/ui-icons.js.map +1 -0
- package/dist/components/use-count-tween.js +42 -0
- package/dist/components/use-count-tween.js.map +1 -0
- package/dist/components/workspace-tab.js +140 -0
- package/dist/components/workspace-tab.js.map +1 -0
- package/dist/generated/accent.js +2 -2
- package/dist/generated/accent.js.map +1 -1
- package/dist/generated/account-list.js +35 -0
- package/dist/generated/account-list.js.map +1 -0
- package/dist/generated/account-settings-dialog.js +27 -0
- package/dist/generated/account-settings-dialog.js.map +1 -0
- package/dist/generated/alert-dialog.js +1 -1
- package/dist/generated/alert-dialog.js.map +1 -1
- package/dist/generated/alert.js +2 -2
- package/dist/generated/alert.js.map +1 -1
- package/dist/generated/aside.js +36 -0
- package/dist/generated/aside.js.map +1 -0
- package/dist/generated/badge.js +7 -6
- package/dist/generated/badge.js.map +1 -1
- package/dist/generated/band.js +1 -1
- package/dist/generated/band.js.map +1 -1
- package/dist/generated/breadcrumb.js +2 -2
- package/dist/generated/breadcrumb.js.map +1 -1
- package/dist/generated/button-group.js +2 -2
- package/dist/generated/button-group.js.map +1 -1
- package/dist/generated/button.js +4 -4
- package/dist/generated/button.js.map +1 -1
- package/dist/generated/card.js +6 -6
- package/dist/generated/card.js.map +1 -1
- package/dist/generated/chat-composer.js +21 -0
- package/dist/generated/chat-composer.js.map +1 -0
- package/dist/generated/chat-message.js +86 -0
- package/dist/generated/chat-message.js.map +1 -0
- package/dist/generated/chat-typing.js +9 -0
- package/dist/generated/chat-typing.js.map +1 -0
- package/dist/generated/command-palette.js +17 -0
- package/dist/generated/command-palette.js.map +1 -0
- package/dist/generated/command.js +2 -2
- package/dist/generated/command.js.map +1 -1
- package/dist/generated/confirm-dialog.js +28 -0
- package/dist/generated/confirm-dialog.js.map +1 -0
- package/dist/generated/context-menu.js +1 -1
- package/dist/generated/context-menu.js.map +1 -1
- package/dist/generated/date-time-picker.js +1 -1
- package/dist/generated/date-time-picker.js.map +1 -1
- package/dist/generated/dialog.js +1 -1
- package/dist/generated/dialog.js.map +1 -1
- package/dist/generated/drawer.js +1 -1
- package/dist/generated/drawer.js.map +1 -1
- package/dist/generated/dropdown-menu.js +1 -1
- package/dist/generated/dropdown-menu.js.map +1 -1
- package/dist/generated/form-row.js +17 -0
- package/dist/generated/form-row.js.map +1 -0
- package/dist/generated/header-controls.js +56 -0
- package/dist/generated/header-controls.js.map +1 -0
- package/dist/generated/icon-button.js +13 -0
- package/dist/generated/icon-button.js.map +1 -0
- package/dist/generated/key-value.js +15 -0
- package/dist/generated/key-value.js.map +1 -0
- package/dist/generated/menubar.js +1 -1
- package/dist/generated/menubar.js.map +1 -1
- package/dist/generated/navigation-menu.js +1 -1
- package/dist/generated/navigation-menu.js.map +1 -1
- package/dist/generated/popover-form.js +13 -0
- package/dist/generated/popover-form.js.map +1 -0
- package/dist/generated/section-label.js +10 -0
- package/dist/generated/section-label.js.map +1 -0
- package/dist/generated/select.js +15 -3
- package/dist/generated/select.js.map +1 -1
- package/dist/generated/settings.js +36 -0
- package/dist/generated/settings.js.map +1 -0
- package/dist/generated/sheet.js +1 -1
- package/dist/generated/sheet.js.map +1 -1
- package/dist/generated/slider.js +1 -1
- package/dist/generated/slider.js.map +1 -1
- package/dist/generated/smart-header.js +11 -0
- package/dist/generated/smart-header.js.map +1 -0
- package/dist/generated/sonner.js +4 -4
- package/dist/generated/sonner.js.map +1 -1
- package/dist/generated/status-item.js +17 -0
- package/dist/generated/status-item.js.map +1 -0
- package/dist/generated/system-banner.js +33 -0
- package/dist/generated/system-banner.js.map +1 -0
- package/dist/generated/top-bar.js +9 -0
- package/dist/generated/top-bar.js.map +1 -0
- package/dist/generated/workspace-tab.js +17 -0
- package/dist/generated/workspace-tab.js.map +1 -0
- package/dist/index.d.ts +852 -156
- package/dist/index.js +21 -1
- package/dist/lib/cn.js +8 -1
- package/dist/lib/cn.js.map +1 -1
- package/dist/lib/number-parts.js +90 -0
- package/dist/lib/number-parts.js.map +1 -0
- package/dist/palette-cli.js +1 -1
- package/dist/palette.d.ts +6 -4
- package/dist/palette.js +3 -1
- package/dist/primitives/dismissable-layer.js +76 -4
- package/dist/primitives/dismissable-layer.js.map +1 -1
- package/dist/primitives/focus-scope.js +20 -5
- package/dist/primitives/focus-scope.js.map +1 -1
- package/dist/primitives/use-floating.js +16 -1
- package/dist/primitives/use-floating.js.map +1 -1
- package/dist/primitives/use-reduced-motion.js +20 -0
- package/dist/primitives/use-reduced-motion.js.map +1 -0
- package/package.json +1 -1
- package/styles/ev.css +281 -49
- package/styles/theme.css +335 -39
- package/styles/tokens.css +643 -103
- package/dist/chunk-3IPB4QQJ.js.map +0 -1
- package/dist/components/terminal.js +0 -206
- package/dist/components/terminal.js.map +0 -1
- package/dist/generated/terminal.js +0 -70
- package/dist/generated/terminal.js.map +0 -1
package/README.md
CHANGED
|
@@ -60,14 +60,23 @@ your Tailwind v4 entrypoint — this is the load-bearing part of the kit:
|
|
|
60
60
|
|
|
61
61
|
| group | names |
|
|
62
62
|
|---|---|
|
|
63
|
-
| scope | `:root` · `[data-brand="…"]` — a theme root; `.light` · `.dark` — a polarity beneath it |
|
|
64
|
-
| surfaces | `background` `card` `
|
|
65
|
-
|
|
|
66
|
-
|
|
|
67
|
-
|
|
|
68
|
-
|
|
|
63
|
+
| scope | `:root` · `[data-brand="…"]` — a theme root; `.light` · `.dark` — a polarity beneath it; `[data-theme="oldschool"]` — EV's navy look |
|
|
64
|
+
| surfaces | `background` (page) · `card` (surface) · `secondary` (raised) · `popover` (overlay) · `muted` (inset, input fill) |
|
|
65
|
+
| states | `hover` · `active` · `selected` — translucent tints; `scrim` — behind a modal |
|
|
66
|
+
| ink | `ink` · `ink-mid` · `ink-soft` — hierarchy, loudest first; `ink-faint` — disabled only |
|
|
67
|
+
| lines | `border-subtle` (row rule) · `border` (hairline, 14 %) · `border-strong` (state and overlay edges) · `input` (control edge, ≥ 3:1) · `ring` |
|
|
68
|
+
| roles | `brand` `primary` `secondary` `positive` `accent-trace` `accent-debug` `accent-info` `accent-warn` `accent-error`, each with `on-*` where it gets filled; `primary-ink` — the primary role as ink on a surface; `primary-hover` · `primary-border` — the primary button's states |
|
|
69
|
+
| status inks | `positive-ink` · `accent-info-ink` — a quiet status role as text on a surface (derived, a palette may pin them); warn and error text wears `accent-warn` · `accent-error` itself, held to 3:1 |
|
|
70
|
+
| faces | `button-danger-*` (`bg` `hover` `border` `ink`) — the destructive `Button`; `chip-{neutral,primary,positive,info,warn,error}-{bg,border}` — the `Badge`; `button-primary-*` — no kit component paints it; derived for a brand, pinned per theme by EV |
|
|
71
|
+
| type | `text-caption` 11/14 · `body-s` 12/16 · `body` 13/18 · `body-l` 14/20 · `h3` 14/20 · `h2` 24/30 · `h1` 32/38 · `figure-m` 20/24 · `figure-l` 28/32 (size and line height each) |
|
|
72
|
+
| scalars | `radius` (panel, 6 px) · `control-radius` (4 px) · `chip-radius` (4 px) · `depth-card` `depth-elevated` `depth-overlay` · `control-py` `display-scale` `band-py` `page-max` `page-px` `shell-rail-w` `shell-tab-bar-h` · `font-*` |
|
|
69
73
|
| motion | `ev-ease-out` `ev-ease-in-out` `ev-dur-fast` `ev-dur-base` `ev-dur-slow` `ev-rise` `ev-stagger` `ev-stagger-section` |
|
|
70
|
-
| charts | `chart-1` … `chart-
|
|
74
|
+
| charts | `chart-1` … `chart-6` |
|
|
75
|
+
|
|
76
|
+
A palette owes **29 names**: the colours that are not derived, which is every
|
|
77
|
+
role and surface above plus `chart-1` … `chart-6`. The states, lines, inks,
|
|
78
|
+
status inks, faces and chips are derived from them with formulas, so a brand
|
|
79
|
+
file that pins none of them still renders.
|
|
71
80
|
|
|
72
81
|
Custom properties inherit, so a scope class on a `<section>` re-themes its
|
|
73
82
|
subtree — `bg-card text-ink` is correct on both polarities with no prop. That is
|
|
@@ -81,14 +90,38 @@ polarity — so `on-{role}` exists exactly there.
|
|
|
81
90
|
|
|
82
91
|
A fill and an ink are different jobs, and `primary` is the one role that
|
|
83
92
|
names them separately: `primary` is the fill (`bg-primary text-on-primary` on
|
|
84
|
-
a button, a checked switch or checkbox — `#
|
|
85
|
-
4.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
`
|
|
90
|
-
|
|
91
|
-
|
|
93
|
+
a primary button, a checked switch or checkbox — `#138377`, white reads on it at
|
|
94
|
+
4.62:1 and it clears 3:1 against every plane) and `primary-ink` is the same hue
|
|
95
|
+
as ink (`text-primary-ink` on a link or eyebrow, a radio dot, a slider range,
|
|
96
|
+
the focus `ring` — `#35a395` in Dark, `#116f65` in Light). The status roles
|
|
97
|
+
follow the same rule with their `-ink` values. A consumer sheet must define
|
|
98
|
+
`--primary-ink`; see [`docs/spec/accents.md`](../../docs/spec/accents.md).
|
|
99
|
+
|
|
100
|
+
### Themes
|
|
101
|
+
|
|
102
|
+
EV ships three, each a zero-specificity rule that pins every EV token:
|
|
103
|
+
|
|
104
|
+
| theme | how to switch | look |
|
|
105
|
+
|---|---|---|
|
|
106
|
+
| **Dark** (default) | nothing: no class, no attribute | near-black neutrals, warm ink, one teal accent, flat |
|
|
107
|
+
| **Light** | `class="light"` on `<html>` or on any wrapper | warm paper planes, same shapes |
|
|
108
|
+
| **Oldschool** | `data-theme="oldschool"` on `<html>` or on any wrapper | the navy look: radius 8 / control 6 / pill chips, tinted chips, real shadows |
|
|
109
|
+
|
|
110
|
+
Dark and Light are flat: depth is surface steps and hairlines, and the
|
|
111
|
+
`--depth-*` tiers behind `shadow-card`, `shadow-elevated` and `shadow-overlay`
|
|
112
|
+
are a zero shadow. Oldschool gives them back.
|
|
113
|
+
|
|
114
|
+
Scoping works the way the polarity classes always did. A `.light` or
|
|
115
|
+
`[data-theme="oldschool"]` wrapper inside a Dark page takes its own full token
|
|
116
|
+
set; a `.light` or `.dark` inside an Oldschool region also takes Oldschool.
|
|
117
|
+
EV's rules do not match inside a `[data-brand]` scope or on its element, so a
|
|
118
|
+
brand's own `.light` and `.dark` never pick EV's values up. Keep `data-theme`
|
|
119
|
+
and `data-brand` on separate elements. For a side-by-side view give each scoped
|
|
120
|
+
wrapper its own `PortalProvider` container so overlays stay inside the scope.
|
|
121
|
+
|
|
122
|
+
The planes sit only 1.04–1.14:1 apart on purpose, so what separates them is the
|
|
123
|
+
`--border` hairline (measured 1.37–1.46:1 on Dark, 1.35:1 on Light). Do not lift
|
|
124
|
+
a plane to chase the ratio; see [`docs/spec/accents.md`](../../docs/spec/accents.md).
|
|
92
125
|
|
|
93
126
|
Accents are decorative and **ordered by significance**, quiet to loud. Pick a
|
|
94
127
|
rung by how loud the thing should be, never by what it means; see
|
|
@@ -106,29 +139,29 @@ It has two halves, each also shipped as a flat sheet of its own —
|
|
|
106
139
|
`styles/theme.css` (the contract) and `styles/ev.css` (EV's palette):
|
|
107
140
|
|
|
108
141
|
- **the contract** — the `@theme inline` mapping, the geometry, and the tokens
|
|
109
|
-
*derived* from a palette (`hover
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
`:root`, `[data-brand]`, `.light
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
142
|
+
*derived* from a palette (`hover` `active` `selected` `border-*` `ink-mid`
|
|
143
|
+
`ink-soft` `ink-faint` `input` from `ink`; `scrim`; `ring` from `primary-ink`;
|
|
144
|
+
`primary-hover` `primary-border`; the status inks; the button faces and
|
|
145
|
+
chips). The derived ones are redeclared on `:root`, `[data-brand]`, `.light`,
|
|
146
|
+
`.dark` and `[data-theme]` at zero specificity, so a brand scope gets its own
|
|
147
|
+
borders, hover and focus ring rather than inheriting the root's, and a
|
|
148
|
+
palette may still pin any of them outright. The geometry (`radius`,
|
|
149
|
+
`page-px`, `band-py`, …) and its responsive scale-ups restart on every
|
|
150
|
+
`[data-brand]` the same way.
|
|
151
|
+
- **EV's palette** — Dark on `:root`, `[data-brand="ev"]` and `.dark`; Light on
|
|
152
|
+
`.light`; Oldschool on `[data-theme="oldschool"]`. None of them matches inside
|
|
153
|
+
another brand's scope.
|
|
121
154
|
|
|
122
155
|
### Brand palettes
|
|
123
156
|
|
|
124
|
-
A second brand is a palette against the same names, scoped to
|
|
157
|
+
A second brand is a palette against the same names (29 of them, `chart-6` among them), scoped to
|
|
125
158
|
`[data-brand="<slug>"]`; light is its default and `.light` / `.dark` beneath it
|
|
126
159
|
pick a polarity, so a `<Section polarity="dark">` inside the brand takes the
|
|
127
160
|
brand's dark values. The package ships a generator for it, reading the
|
|
128
161
|
`brand.toml` shape (`[colors.light]` and `[colors.dark]`, keyed by token name;
|
|
129
162
|
optional top-level `mark` and `mark-aspect` become `--brand-mark` and
|
|
130
163
|
`--brand-aspect`; the family names in `[fonts]` — `sans` (or aquafix's `text`),
|
|
131
|
-
`
|
|
164
|
+
`serif`, `mono` — become the [font parameters](#fonts); other keys and tables
|
|
132
165
|
are ignored). Each polarity block also declares `--scheme: light | dark`, which
|
|
133
166
|
the platform-drawn parts of the kit read (a `NativeSelect`'s popup):
|
|
134
167
|
|
|
@@ -193,13 +226,16 @@ under a brand under another brand may match either.
|
|
|
193
226
|
|
|
194
227
|
### Fonts
|
|
195
228
|
|
|
196
|
-
The families are parameters, one per role
|
|
229
|
+
The families are parameters, one per role, and not part of the 29 colour names:
|
|
230
|
+
a palette is complete without them, and the generator only carries what a
|
|
231
|
+
brand file's `[fonts]` declares. Unset, each is EV's, and Oldschool carries the
|
|
232
|
+
families the navy look shipped with:
|
|
197
233
|
|
|
198
|
-
| parameter | utilities | default (EV) |
|
|
199
|
-
|
|
200
|
-
| `--brand-font-sans` | `font-sans`, the page's base family | `--font-inter`, else `"Inter"` |
|
|
201
|
-
| `--brand-font-
|
|
202
|
-
| `--brand-font-mono` | `font-mono` | `--font-inter`, else `"Inter"` |
|
|
234
|
+
| parameter | utilities | default (EV) | Oldschool |
|
|
235
|
+
|---|---|---|---|
|
|
236
|
+
| `--brand-font-sans` | `font-sans`, the page's base family | `--font-inter`, else `"Inter"` | same |
|
|
237
|
+
| `--brand-font-serif` | `font-serif` | `--font-inter-tight`, else `"Inter Tight"` | `--font-playfair`, else `"Playfair Display"` |
|
|
238
|
+
| `--brand-font-mono` | `font-mono` | `--font-jetbrains-mono`, else `"JetBrains Mono"` | `--font-inter`, else `"Inter"` |
|
|
203
239
|
|
|
204
240
|
Declare them on the brand scope (the palette generator writes them from
|
|
205
241
|
`[fonts]`) and load the files yourself — `next/font`, or your own `@font-face`;
|
|
@@ -207,10 +243,13 @@ the kit ships no font files:
|
|
|
207
243
|
|
|
208
244
|
```css
|
|
209
245
|
[data-brand="aquafix"] {
|
|
210
|
-
--brand-font-
|
|
246
|
+
--brand-font-serif: "Archivo";
|
|
211
247
|
}
|
|
212
248
|
```
|
|
213
249
|
|
|
250
|
+
The serif role is the second face, for headings, figures, wordmarks, italic
|
|
251
|
+
accents and the avatar initial; it need not have serifs.
|
|
252
|
+
|
|
214
253
|
The utilities resolve the chain on the element, so a scope re-fonts everything
|
|
215
254
|
under it that carries one. The page's base family resolves once, on `<html>`: a
|
|
216
255
|
brand scoped lower than `<html>` puts `font-sans` on its own element so plain
|
|
@@ -306,12 +345,12 @@ through hydration. Three parts of the kit make that hold:
|
|
|
306
345
|
```
|
|
307
346
|
|
|
308
347
|
**Focus.** A control that paints a fill — `Button` `primary` / `secondary` /
|
|
309
|
-
`destructive` or any accent fill, a
|
|
310
|
-
shows focus as the solid `ring` standing 2px off it, so both edges of
|
|
311
|
-
indicator are against the surface: the ring reads on every surface at
|
|
312
|
-
and so does the fill, on every palette and polarity `test/tokens.test.ts`
|
|
348
|
+
`destructive` or any accent fill, a `Badge` link, `Switch`, `Checkbox`,
|
|
349
|
+
`Toggle` — shows focus as the solid `ring` standing 2px off it, so both edges of
|
|
350
|
+
the indicator are against the surface: the ring reads on every surface at
|
|
351
|
+
≥ 3:1 and so does the fill, on every palette and polarity `test/tokens.test.ts`
|
|
313
352
|
measures. Controls that sit on the surface (`outline`, `ghost`, inputs) keep
|
|
314
|
-
the `ring` border plus its halo.
|
|
353
|
+
the `ring` border plus its halo; list rows draw an inset ring.
|
|
315
354
|
|
|
316
355
|
## App shell
|
|
317
356
|
|
|
@@ -319,7 +358,8 @@ The frame a signed-in app sits in — a rail beside the page on a wide screen, a
|
|
|
319
358
|
tab bar under it on a phone, one page inset and title scale, and the motion
|
|
320
359
|
between screens. It knows nothing about routes, roles or data: the nav is a
|
|
321
360
|
list you build, visibility is a list you filter, and the router comes in as two
|
|
322
|
-
props. TS-only (see [Limitations](#limitations))
|
|
361
|
+
props. TS-only (see [Limitations](#limitations)), except `TopBar`,
|
|
362
|
+
`SystemBanner` and `SectionLabel`, which have Dioxus twins.
|
|
323
363
|
|
|
324
364
|
```tsx
|
|
325
365
|
// app/(app)/layout.tsx — the layout stays a Server Component; the nav is a client leaf
|
|
@@ -394,6 +434,15 @@ export function Nav() {
|
|
|
394
434
|
middle click does not. One marker per group slides between rows on a CSS
|
|
395
435
|
`transform` transition — it is placed by measurement, so until hydration the
|
|
396
436
|
marked row carries the fill itself.
|
|
437
|
+
*Collapsible sections* are opt-in: set `collapsible` on a group (or on
|
|
438
|
+
`ShellNav` for every labelled group; a group's own value wins) and its label
|
|
439
|
+
becomes a header with a chevron. `defaultOpen`, controlled `open` /
|
|
440
|
+
`onOpenChange`, and `href` (the label links to a landing page, only the
|
|
441
|
+
chevron toggles) go on the group; the group holding the current route opens
|
|
442
|
+
when the route moves into it. Height runs on a `0fr → 1fr` grid track with a
|
|
443
|
+
180 ms fade and chevron turn (`--ev-dur-fast`, `--ev-ease-in-out`), none under
|
|
444
|
+
reduced motion; closed rows are `inert`. `labels` also takes `expand` /
|
|
445
|
+
`collapse` (the chevron reads "Expand Guides"). `SectionNav` takes the same.
|
|
397
446
|
- **`BottomTabBar`** — `items` (five fit a phone), the same router props,
|
|
398
447
|
callbacks and optimistic mark, `labels` `{ nav, badge(n) }`. One rule slides by
|
|
399
448
|
whole tab widths and fades on a route no tab claims; a numeric `badge` sits
|
|
@@ -416,8 +465,8 @@ export function Nav() {
|
|
|
416
465
|
onValueChange?, linkComponent?, labels: { nav } }` — a page's own section
|
|
417
466
|
rail (settings panes); items are buttons, or links when they have an `href`.
|
|
418
467
|
- **`ResourceError`** `{ message, variant: "inline" (onRetry?, retrying?) |
|
|
419
|
-
"alert" (title?), labels: { retry } }` and **`SystemBanner`** `{ tone: "
|
|
420
|
-
"info", title?, icon?, onDismiss?, labels: { dismiss } }` — presentation
|
|
468
|
+
"alert" (title?), labels: { retry } }` and **`SystemBanner`** `{ tone: "neutral" |
|
|
469
|
+
"primary" | "info" | "warn", title?, icon?, onDismiss?, labels: { dismiss } }` — presentation
|
|
421
470
|
only; what failed and whether a dismissal sticks are yours.
|
|
422
471
|
- **`Settled`** `{ loading, skeleton, children }` — the skeleton → content
|
|
423
472
|
handover: the content fades and rises in after a skeleton was shown, and cuts
|
|
@@ -464,6 +513,57 @@ The timing is tokens on every theme root, for your own transitions too
|
|
|
464
513
|
`--ev-rise` (8px), `--ev-stagger` (35ms), `--ev-stagger-section` (50ms). The
|
|
465
514
|
shell's geometry is `--shell-rail-w` (248px) and `--shell-tab-bar-h` (64px).
|
|
466
515
|
|
|
516
|
+
|
|
517
|
+
### Animated numbers
|
|
518
|
+
|
|
519
|
+
`AnimatedNumber` shows a number that moves to its new value. It needs no
|
|
520
|
+
animation library: the digits are CSS transforms and transitions, and the
|
|
521
|
+
count-up is `requestAnimationFrame`.
|
|
522
|
+
|
|
523
|
+
```tsx
|
|
524
|
+
<AnimatedNumber value={balance} format={{ style: "currency", currency: "USD" }} />
|
|
525
|
+
<AnimatedNumber value={change} format={{ style: "percent", signDisplay: "exceptZero" }} />
|
|
526
|
+
<AnimatedNumber value={volume} format={{ notation: "compact" }} mode="count" animateOnMount />
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
| prop | |
|
|
530
|
+
|---|---|
|
|
531
|
+
| `value` | the number |
|
|
532
|
+
| `format`, `locales` | passed to `Intl.NumberFormat` |
|
|
533
|
+
| `mode` | `roll` (default): digit columns spin. `count`: the number is tweened and re-formatted each frame |
|
|
534
|
+
| `trend` | roll only. `1` every column spins up, `-1` down, `0` each column takes its shorter way. Default: the way the value moved |
|
|
535
|
+
| `duration` | ms per change: spin 750 (roll), whole tween 800 (count) |
|
|
536
|
+
| `prefix`, `suffix` | React nodes beside the figure, not animated |
|
|
537
|
+
| `live` | announce each new value politely; off by default |
|
|
538
|
+
| `animateOnMount`, `from` | animate the first render from `from` (default 0) |
|
|
539
|
+
|
|
540
|
+
It inherits font, size and colour, so it fits `text-figure-l font-serif
|
|
541
|
+
font-light` as well as a `text-body` table cell. `tabular-nums` is on.
|
|
542
|
+
`useCountTween(value, { duration, easing, from, animateOnMount })` returns the
|
|
543
|
+
in-flight number for a chart or a label of your own. `HeaderBalance` takes
|
|
544
|
+
`amount` (USD unless `format` says otherwise); any `figure`, `value` or other
|
|
545
|
+
`ReactNode` slot takes an `<AnimatedNumber />` as is.
|
|
546
|
+
|
|
547
|
+
**How roll works.** `formatToParts` splits the value. Integer digits are keyed
|
|
548
|
+
by place value from the right, fraction digits by position, so `9 -> 10` keeps
|
|
549
|
+
the existing column and only a new one enters. Symbols (currency, sign, group,
|
|
550
|
+
decimal, percent, compact suffix) are keyed by type. A digit is a clipped
|
|
551
|
+
one-line window over a strip moved with `translateY` on a spring-shaped
|
|
552
|
+
`linear()` easing (~4 % overshoot), with soft top and bottom edges. The strip
|
|
553
|
+
is unbounded, so a forced direction wraps (9 to 0 going up). Entering and leaving
|
|
554
|
+
parts animate width (`0fr` to `1fr`) and opacity in 350 ms, so their neighbours
|
|
555
|
+
slide.
|
|
556
|
+
|
|
557
|
+
**Accessibility.** The final formatted value is in the DOM once, as
|
|
558
|
+
visually hidden text; the moving glyphs are `aria-hidden` and not selectable,
|
|
559
|
+
so a copy gives the formatted text.
|
|
560
|
+
|
|
561
|
+
**Reduced motion.** Under `prefers-reduced-motion: reduce` a new value swaps in
|
|
562
|
+
at once: no spin, no width animation, no tween frames.
|
|
563
|
+
|
|
564
|
+
**SSR.** The server prints the final formatted value, also with `animateOnMount`,
|
|
565
|
+
so hydration does not jump; the count-up starts after hydration.
|
|
566
|
+
|
|
467
567
|
## Usage
|
|
468
568
|
|
|
469
569
|
```tsx
|
|
@@ -474,7 +574,7 @@ export function Example() {
|
|
|
474
574
|
<Card>
|
|
475
575
|
<CardHeader>
|
|
476
576
|
<CardTitle>Portfolio</CardTitle>
|
|
477
|
-
<Badge variant="
|
|
577
|
+
<Badge variant="positive">+12.4%</Badge>
|
|
478
578
|
</CardHeader>
|
|
479
579
|
<CardContent>
|
|
480
580
|
<Button onClick={() => {}}>Invest</Button>
|
|
@@ -602,8 +702,13 @@ the cell says so.
|
|
|
602
702
|
| optional slot present | an explicit prop, never read off `children`: `FieldSeparator { content: true, "or" }` renders the copy pill; `FormMessage { error, .. }` renders only while `error` (the same flag as `FormControl`'s) | `!!children` / `if (!children) return null` |
|
|
603
703
|
| option label | `SelectItem { value, text_value, .. }`: the text type-ahead matches, an explicit prop in both ports — never read off `children` | `SelectItem`'s `textValue`, else the text of its children |
|
|
604
704
|
| toasts | `ToasterProvider` + `use_toaster()` → `ToasterHandle` (`.toast` `.positive` `.error` `.info` `.warn` `.show` `.dismiss`), same in both ports | global `toast(...)` / `toast.dismiss` |
|
|
705
|
+
| children present | `SettingsSection` and `SystemBanner`: Dioxus checks that the children render something, Leptos that children were passed | `children !== undefined` |
|
|
706
|
+
| `SystemBanner` glyph | `icon` replaces the tone's glyph; Dioxus drops it with an empty `icon: rsx! {}`, Leptos with `icon=None` | `icon={null}` |
|
|
707
|
+
| `KeyValue` label | Dioxus: an `Element`; Leptos: `KeyValueLabel::{Text, View}` (`.into()` from a string), since only text names the copy button | `label` (`ReactNode`) |
|
|
708
|
+
| time-varying props | Dioxus: plain values; Leptos: signals — `HeaderBalance`, `NotificationBell`, `ModelChip`, `ChatComposer` `streaming` / `disabled`, `ConfirmDialog` `pending` / `error`, `ChatAttachment` `progress`, `AsidePanel` `open` | props |
|
|
709
|
+
| Ctrl+K hook | `use_command_palette_shortcut(on_trigger, enabled)`: `enabled` is a `bool` in Dioxus, a `Signal<bool>` in Leptos | `useCommandPaletteShortcut` |
|
|
605
710
|
|
|
606
|
-
### Component inventory (all
|
|
711
|
+
### Component inventory (all bricks)
|
|
607
712
|
|
|
608
713
|
- **Tier A — static (23):** badge, button, button-group, card, input, textarea,
|
|
609
714
|
label, field, separator, skeleton, spinner, kbd, table, container, alert,
|
|
@@ -621,27 +726,99 @@ the cell says so.
|
|
|
621
726
|
/ `Forbidden` / `ServerError` presets (404 / 403 / 500). The kit ships no
|
|
622
727
|
artwork: `Logo` masks whatever `--brand-mark` / `--brand-aspect` the consumer
|
|
623
728
|
declares beside the palette, and the status pages use it for their mark.
|
|
624
|
-
- **App shell — TS-only (
|
|
625
|
-
nav-badge (`NavBadge` / `NavDot`),
|
|
626
|
-
`
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
`
|
|
631
|
-
`
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
729
|
+
- **App shell — TS-only (10):** app-shell, shell-nav, bottom-tab-bar,
|
|
730
|
+
nav-badge (`NavBadge` / `NavDot`), account-menu, mobile-app-bar, page-frame
|
|
731
|
+
(`PageFrame` / `PageHeading`), section-nav, resource-error, settled — see
|
|
732
|
+
[App shell](#app-shell). `TopBar`, `SystemBanner` and `SectionLabel` have
|
|
733
|
+
Dioxus twins (next item).
|
|
734
|
+
- **Composed parts — Rust and TS:** the class strings live once in
|
|
735
|
+
`rust/classes/src/<name>.rs`, and `nix run .#gen` writes them to
|
|
736
|
+
`ts/uikit/src/generated/<name>.ts`.
|
|
737
|
+
- *elements:* `IconButton` (required `aria-label`), `StatusItem` / `StatusDot`,
|
|
738
|
+
`WorkspaceTabs` / `WorkspaceTab`, `KeyValue` (copy button), `FormRow`,
|
|
739
|
+
`SystemBanner`, `SectionLabel`;
|
|
740
|
+
- *settings:* `SettingsLayout` / `SettingsSection` / `SettingsRow`,
|
|
741
|
+
`AccountSettingsDialog`, `ConfirmDialog` (async confirm), `AccountList` /
|
|
742
|
+
`AccountListItem`, `PopoverForm`, `CommandPalette` and
|
|
743
|
+
`useCommandPaletteShortcut` (Rust: `use_command_palette_shortcut`);
|
|
744
|
+
- *header and asides:* `TopBar`, `SmartHeader` (`default`, `collapsed`,
|
|
745
|
+
`banner`, `search`), `HeaderBalance`, `HeaderSearch`, `NotificationBell`,
|
|
746
|
+
`AsideHeader`, `AsideRail`, `AsidePanel` (`docked` or `overlay`);
|
|
747
|
+
- *AI chat:* `ChatMessage`, `ChatBubble` (`streaming` shows a caret),
|
|
748
|
+
`ChatMarker`, `ChatAttachment`, `ChatComposer`, `ModelChip` and `ChatTyping`
|
|
749
|
+
(the "thinking" indicator).
|
|
750
|
+
- **Composed parts — TS-only:** `AnimatedNumber` / `useCountTween`,
|
|
751
|
+
`ChatPanel`, `ChatDock`, `ChatStreamText` (pass the whole growing string; only
|
|
752
|
+
the new words fade in), the `AppShell` `aside` slot, and the profile form on
|
|
753
|
+
`AccountMenu`. How the Rust twins differ is in [Limitations](#limitations).
|
|
754
|
+
|
|
755
|
+
### Variant vocabulary
|
|
756
|
+
|
|
757
|
+
A variant names the state ([`docs/spec/variants.md`](../../docs/spec/variants.md)).
|
|
758
|
+
|
|
759
|
+
| component | values |
|
|
760
|
+
|---|---|
|
|
761
|
+
| `Button` | `primary` (the filled call to action, default) · `secondary` · `outline` · `ghost` · `destructive` · `link` |
|
|
762
|
+
| `Badge` | `neutral` (default) · `primary` · `positive` · `info` · `warn` · `error` |
|
|
763
|
+
| `Alert` | `neutral` (default) · `destructive` · `success` · `info` |
|
|
764
|
+
| menu item (`DropdownMenuItem`, `ContextMenuItem`, `MenubarItem`) | `neutral` · `destructive` |
|
|
765
|
+
| `SystemBanner` | `neutral` · `primary` · `info` (default) · `warn` |
|
|
635
766
|
|
|
636
767
|
The canonical variant set is the **superset** of the original cabinet (Rust) and
|
|
637
|
-
landing (TS) sources
|
|
638
|
-
|
|
639
|
-
|
|
768
|
+
landing (TS) sources: `Button` keeps landing's square icon shape at every size,
|
|
769
|
+
as the separate `icon` boolean it always was in Rust. A `destructive` button
|
|
770
|
+
wears the `button-danger-*` face: an outline in Dark and Light, a fill in
|
|
771
|
+
Oldschool.
|
|
640
772
|
|
|
641
773
|
`size="touch"` is the 44 px pointer-target floor (WCAG 2.5.5) for touch-first
|
|
642
774
|
screens: `min-h-11` with `lg`'s padding, and a 44 × 44 square with `icon`. It is
|
|
643
775
|
a button step only — `Input` and `SelectTrigger` already clear it at `lg`.
|
|
644
776
|
|
|
777
|
+
## Upgrading from 0.30
|
|
778
|
+
|
|
779
|
+
**Renames and removals**
|
|
780
|
+
|
|
781
|
+
| 0.30 | now |
|
|
782
|
+
|---|---|
|
|
783
|
+
| `Badge variant="outline"` · `"secondary"` | `neutral` |
|
|
784
|
+
| `Badge variant="destructive"` · `"success"` | `error` · `positive` (new: `primary`, `info`, `warn`) |
|
|
785
|
+
| `SystemBanner` tones `warn`, `info` | the same, plus `neutral` and `primary`; `info` draws its icon in `accent-info-ink` |
|
|
786
|
+
| `accent-error-ink` (`text-accent-error-ink`, a brand file's `accent-error-ink`) | `accent-error`; destructive menu rows read in it, and `evinvest-palette` refuses the key |
|
|
787
|
+
| `Terminal`, `TerminalPane*`, `TerminalChart`, `TerminalTicker`, `TickerStat`, `OrderBook*`, `TradesTapeRow`, `OrderForm*`, `OpenOrdersEmpty` and their types | removed from the TS kit (the Rust terminal stays) |
|
|
788
|
+
|
|
789
|
+
**Silent changes: the compiler does not flag these.**
|
|
790
|
+
|
|
791
|
+
- A `Badge` with no `variant` was filled `primary` and is now `neutral`:
|
|
792
|
+
`rg -n '<Badge(\s|>)' --glob '*.tsx' | rg -v 'variant='`.
|
|
793
|
+
- A `destructive` `Button` wears the theme's `button-danger-*` face, an outline
|
|
794
|
+
in Dark and Light; an `outline` one is raised (`secondary` fill and a hairline).
|
|
795
|
+
|
|
796
|
+
**Tokens and themes**
|
|
797
|
+
|
|
798
|
+
- EV's navy palette is opt-in: `data-theme="oldschool"`. Dark is the default
|
|
799
|
+
and is a different colour set; `.light` is Light.
|
|
800
|
+
- Font defaults are Inter (sans), Inter Tight (serif role) and JetBrains Mono
|
|
801
|
+
(mono). `data-theme="oldschool"` sets Playfair Display and Inter for the serif
|
|
802
|
+
and mono roles, as the navy look shipped. Load the families a theme uses
|
|
803
|
+
(`--font-inter-tight`, `--font-jetbrains-mono`, or `--font-playfair` for
|
|
804
|
+
Oldschool); the kit ships no font files.
|
|
805
|
+
- The font role `display` is `serif`: `--brand-font-display` / `font-display`
|
|
806
|
+
become `--brand-font-serif` / `font-serif` (see [Fonts](#fonts)).
|
|
807
|
+
- A brand file must add `chart-6` (29 names now); `evinvest-palette` names it.
|
|
808
|
+
- Panel radius is 6 px at every width (the `sm` bump to 8 px is gone), controls
|
|
809
|
+
and chips 4 px, and the depth tiers are flat. **Aquafix, vifnet and any other
|
|
810
|
+
brand take these and lose their card shadow** unless their scope overrides
|
|
811
|
+
`--radius`, `--control-radius`, `--chip-radius` and `--depth-card`,
|
|
812
|
+
`--depth-elevated`, `--depth-overlay`. Check each brand before releasing.
|
|
813
|
+
|
|
814
|
+
**Layers across bundles**
|
|
815
|
+
|
|
816
|
+
- The shared layer stack (Escape order, outside clicks, the one shared scrim)
|
|
817
|
+
keeps its global key `@1`, so a micro-frontend bundling uikit 0.30 and a newer
|
|
818
|
+
host still share one stack. A 0.30 copy never notifies, so a newer modal's
|
|
819
|
+
shared scrim may not update when a 0.30 layer opens or closes under it; move
|
|
820
|
+
hosts and the MFEs they mount together to get the full behaviour.
|
|
821
|
+
|
|
645
822
|
## Limitations
|
|
646
823
|
|
|
647
824
|
Reproducing everything dep-light means some behaviour is intentionally reduced,
|
|
@@ -684,11 +861,6 @@ measuring needs host-only `web-sys`). Known gaps:
|
|
|
684
861
|
`onkeydown` — and `AlertDialog`, `Sheet`, `Drawer`, `ContextMenu`, `Menubar`,
|
|
685
862
|
`NavigationMenu`, `HoverCard`, `Tooltip`, `InfoTip`, `DateTimePicker`,
|
|
686
863
|
`CommandDialog` and `DropdownMenuSub` are not on the stack yet.
|
|
687
|
-
- **terminal:** no plotting engine and no state. `TerminalChart` is a sized
|
|
688
|
-
`relative` host that hands its element out (`ref` in TS, `id` in Rust) for a
|
|
689
|
-
consumer-chosen charting library to mount into; `OrderForm` only prevents the
|
|
690
|
-
submit's default — inputs, validation and the order itself are the
|
|
691
|
-
consumer's. The open-orders pane reuses `Table` and `Tabs`.
|
|
692
864
|
- **calendar:** single month, single-date selection (no range/multi-month, no
|
|
693
865
|
dropdown captions). The grid is always six fixed weeks of 36px cells, so its
|
|
694
866
|
size never changes with the month or the locale's weekday labels, and it
|
|
@@ -700,7 +872,7 @@ measuring needs host-only `web-sys`). Known gaps:
|
|
|
700
872
|
locale whose short form contains whitespace (vi's "Thứ 2" → "T2", he's
|
|
701
873
|
"יום ב׳" → "ב׳") as long as narrow still yields seven distinct labels. Rust has no `Intl`, so its captions
|
|
702
874
|
stay English. Rust does manual date math; TS uses the built-in `Date`.
|
|
703
|
-
- **date-time-picker:** the kit's own bricks only —
|
|
875
|
+
- **date-time-picker:** the kit's own bricks only — a raised trigger, a
|
|
704
876
|
`Popover` with the `Calendar` and two numeric 24-hour hours/minutes fields (no
|
|
705
877
|
native `datetime-local` / `time` input, so the browser's locale popup never
|
|
706
878
|
appears). Its ARIA and button strings are `labels` overrides with English
|
|
@@ -748,12 +920,34 @@ measuring needs host-only `web-sys`). Known gaps:
|
|
|
748
920
|
The lock-up is composed from `mark` + `brand` (display family) + `tagline`,
|
|
749
921
|
or passed whole as `lockup`. Brand-coloured text reads `primary-ink`.
|
|
750
922
|
- **TS-only for now:** `NativeSelect`, the `Field` id hand-off,
|
|
751
|
-
`SelectValue` labels, `ListRows` / `ListRow` (a table's phone form),
|
|
752
|
-
|
|
753
|
-
`
|
|
754
|
-
`
|
|
755
|
-
|
|
756
|
-
`
|
|
923
|
+
`SelectValue` labels, `ListRows` / `ListRow` (a table's phone form), the
|
|
924
|
+
rest of the [app shell](#app-shell) (`AppShell`, `ShellNav`, `BottomTabBar`,
|
|
925
|
+
`NavBadge` / `NavDot`, `AccountMenu` and its profile form, `MobileAppBar`,
|
|
926
|
+
`PageFrame` / `PageHeading`, `SectionNav`, `ResourceError`, `Settled`) and
|
|
927
|
+
the parts that need a browser API Rust does not wrap: `AnimatedNumber` /
|
|
928
|
+
`useCountTween` (a frame loop and `Intl`), `ChatStreamText` (word diffing and
|
|
929
|
+
timers), and `ChatPanel` / `ChatDock` (`ResizeObserver`, scroll pinning).
|
|
930
|
+
Collapsible `ShellNav` / `SectionNav` groups are TS-only too. Their CSS (the
|
|
931
|
+
entrances, the markers, the motion and shell tokens) ships in the shared
|
|
932
|
+
sheet, so a Rust app can use the `data-enter` contract today.
|
|
933
|
+
- **Rust twins of the composed parts:** they keep the TS markup and class strings, with
|
|
934
|
+
these differences. Overlays keep the limits of the older Rust overlays (inline
|
|
935
|
+
`position: fixed`, no portal, no focus trap, not on the layer stack).
|
|
936
|
+
`HeaderBalance` takes a formatted `String` (no rolling digits, no `Settled`).
|
|
937
|
+
`ConfirmDialog` has `pending` and `error` props in place of a promise, and a
|
|
938
|
+
`pending` dialog ignores Esc, the scrim and the buttons. `KeyValue` takes a
|
|
939
|
+
`copy` callback; with the `wasm` feature and no callback it writes through
|
|
940
|
+
`web-sys`. `CommandPalette` is controllable (`open`, `default_open`,
|
|
941
|
+
`on_open_change`), and `use_command_palette_shortcut` listens for Ctrl or Cmd
|
|
942
|
+
K by key code on the `wasm` feature only, so it does nothing in an SSR build.
|
|
943
|
+
`AsidePanel` has no `matchMedia`: with `AsideCollapse::Md` it renders the
|
|
944
|
+
docked or sheet form and the drawer form together, and breakpoint classes show
|
|
945
|
+
one, so its children mount twice. Dialogs, popovers and menus do not name
|
|
946
|
+
themselves: pass `aria_labelledby` and the title `id`, or `aria_label`. The
|
|
947
|
+
TS tests that stub `matchMedia`, or that need `SectionNav` or `AppShell`, are
|
|
948
|
+
ported as markup checks or not at all. `dioxus-ssr` runs no effects, so the
|
|
949
|
+
clipboard write, the Ctrl+K listener and focus moves are checked in the
|
|
950
|
+
gallery only.
|
|
757
951
|
- **app shell:** `ShellNav`'s marker is measured (one forced layout read per
|
|
758
952
|
navigation); the tab bar's rule is arithmetic and assumes equal tabs. A
|
|
759
953
|
`data-enter` nested inside another still entering composes both rises (the
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
function references(value) {
|
|
3
3
|
return [...value.matchAll(/var\(--([A-Za-z0-9-]+)/g)].flatMap((m) => m[1] ? [m[1]] : []);
|
|
4
4
|
}
|
|
5
|
-
var DERIVED_SCOPE = ":where(:root, .dark, .light, [data-brand])";
|
|
5
|
+
var DERIVED_SCOPE = ":where(:root, .dark, .light, [data-brand], [data-theme])";
|
|
6
|
+
var FACES_SCOPE = ":where([data-theme], [data-brand], .light, .dark, :root)";
|
|
6
7
|
function readRules(css) {
|
|
7
8
|
const rules = [];
|
|
8
9
|
const src = css.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
@@ -48,10 +49,12 @@ function readContract(css) {
|
|
|
48
49
|
}
|
|
49
50
|
const derivedRule = rules.find((r) => r.selector === DERIVED_SCOPE);
|
|
50
51
|
if (!derivedRule) throw new Error(`token sheet has no \`${DERIVED_SCOPE}\` rule \u2014 not the kit's contract`);
|
|
51
|
-
const
|
|
52
|
+
const facesRule = rules.find((r) => r.selector === FACES_SCOPE);
|
|
53
|
+
const formulas = new Map([...derivedRule.declarations, ...facesRule?.declarations ?? []]);
|
|
54
|
+
const derived = [...formulas.keys()];
|
|
52
55
|
const stray = derived.filter((d) => !painted.includes(d));
|
|
53
56
|
if (stray.length > 0) throw new Error(`derived tokens with no @theme mapping: ${stray.join(", ")}`);
|
|
54
|
-
const derivedFrom = Object.fromEntries([...
|
|
57
|
+
const derivedFrom = Object.fromEntries([...formulas].map(([name, value]) => [name, references(value)]));
|
|
55
58
|
return { required: painted.filter((p) => !derived.includes(p)), derived, derivedFrom };
|
|
56
59
|
}
|
|
57
60
|
|
|
@@ -144,8 +147,8 @@ function splitTopLevel(inner) {
|
|
|
144
147
|
|
|
145
148
|
// src/palette/brand.ts
|
|
146
149
|
var POLARITIES = ["light", "dark"];
|
|
147
|
-
var FONT_ROLES = ["sans", "
|
|
148
|
-
var FONT_KEYS = { sans: "sans", text: "sans",
|
|
150
|
+
var FONT_ROLES = ["sans", "serif", "mono"];
|
|
151
|
+
var FONT_KEYS = { sans: "sans", text: "sans", serif: "serif", mono: "mono" };
|
|
149
152
|
var PaletteError = class extends Error {
|
|
150
153
|
constructor(problems) {
|
|
151
154
|
super(`invalid brand palette:
|
|
@@ -293,6 +296,7 @@ ${extras.join("")}}
|
|
|
293
296
|
|
|
294
297
|
export {
|
|
295
298
|
DERIVED_SCOPE,
|
|
299
|
+
FACES_SCOPE,
|
|
296
300
|
readRules,
|
|
297
301
|
readContract,
|
|
298
302
|
parseToml,
|
|
@@ -303,4 +307,4 @@ export {
|
|
|
303
307
|
validateBrand,
|
|
304
308
|
renderPalette
|
|
305
309
|
};
|
|
306
|
-
//# sourceMappingURL=chunk-
|
|
310
|
+
//# sourceMappingURL=chunk-35SPIU4U.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/palette/contract.ts","../src/palette/toml.ts","../src/palette/brand.ts"],"sourcesContent":["/**\n * The colour contract, read out of the kit's own token sheet rather than\n * restated here: `@theme inline` maps every name the class tables can paint\n * (`--color-X: var(--X)`), and the derived rule lists the ones the contract\n * computes from the rest. A palette owes the difference.\n */\nexport interface Contract {\n /** Names every palette scope must declare. */\n required: readonly string[];\n /** Names the contract derives from the palette; a palette may override them. */\n derived: readonly string[];\n /** The tokens each derived default reads, so a palette's references can be checked for cycles through them. */\n derivedFrom: Readonly<Record<string, readonly string[]>>;\n}\n\n/** The `--name`s a value reads through `var()`. */\nexport function references(value: string): string[] {\n return [...value.matchAll(/var\\(--([A-Za-z0-9-]+)/g)].flatMap((m) => (m[1] ? [m[1]] : []));\n}\n\n/** The rule the derived tokens live on — see `theme.css`, the contract half of `tokens.css`. */\nexport const DERIVED_SCOPE = \":where(:root, .dark, .light, [data-brand], [data-theme])\";\n\n/** The same scopes in another order: the button faces and chips, derived the same way. */\nexport const FACES_SCOPE = \":where([data-theme], [data-brand], .light, .dark, :root)\";\n\nexport interface CssRule {\n selector: string;\n /** Custom properties declared directly in the rule, in source order. */\n declarations: Map<string, string>;\n}\n\n/**\n * Top-level and `@media`-nested rules of a flat sheet, comments stripped. Only\n * as much CSS as a token sheet is made of: no strings containing braces.\n */\nexport function readRules(css: string): CssRule[] {\n const rules: CssRule[] = [];\n const src = css.replace(/\\/\\*[\\s\\S]*?\\*\\//g, \"\");\n const stack: string[] = [];\n let start = 0;\n for (let i = 0; i < src.length; i++) {\n const c = src[i];\n if (c === \"{\") {\n stack.push(src.slice(start, i).trim());\n start = i + 1;\n } else if (c === \"}\") {\n const selector = stack.pop();\n if (selector === undefined) throw new Error(\"unbalanced `}` in token sheet\");\n const body = src.slice(start, i);\n if (!body.includes(\"{\")) rules.push({ selector: normalise(selector), declarations: declarations(body) });\n start = i + 1;\n } else if (c === \";\" && stack.length === 0) {\n start = i + 1;\n }\n }\n if (stack.length > 0) throw new Error(\"unbalanced `{` in token sheet\");\n return rules;\n}\n\nfunction declarations(body: string): Map<string, string> {\n const out = new Map<string, string>();\n for (const decl of body.split(\";\")) {\n const match = /^\\s*--([A-Za-z0-9-]+)\\s*:\\s*([\\s\\S]+?)\\s*$/.exec(decl);\n if (match?.[1] && match[2]) out.set(match[1], match[2]);\n }\n return out;\n}\n\nexport function normalise(selector: string): string {\n return selector.replace(/\\s+/g, \" \").replace(/\\s*,\\s*/g, \", \").trim();\n}\n\nexport function readContract(css: string): Contract {\n const rules = readRules(css);\n const theme = rules.find((r) => r.selector === \"@theme inline\");\n if (!theme) throw new Error(\"token sheet has no `@theme inline` block — not the kit's contract\");\n const painted: string[] = [];\n for (const [name, value] of theme.declarations) {\n const match = /^color-(.+)$/.exec(name);\n if (match?.[1] && value === `var(--${match[1]})`) painted.push(match[1]);\n }\n const derivedRule = rules.find((r) => r.selector === DERIVED_SCOPE);\n if (!derivedRule) throw new Error(`token sheet has no \\`${DERIVED_SCOPE}\\` rule — not the kit's contract`);\n const facesRule = rules.find((r) => r.selector === FACES_SCOPE);\n const formulas = new Map([...derivedRule.declarations, ...(facesRule?.declarations ?? [])]);\n const derived = [...formulas.keys()];\n const stray = derived.filter((d) => !painted.includes(d));\n if (stray.length > 0) throw new Error(`derived tokens with no @theme mapping: ${stray.join(\", \")}`);\n const derivedFrom = Object.fromEntries([...formulas].map(([name, value]) => [name, references(value)]));\n return { required: painted.filter((p) => !derived.includes(p)), derived, derivedFrom };\n}\n","/**\n * The subset of TOML a brand file is written in: `[table.headers]`, bare keys,\n * and single-line values that are a basic string, a number, a boolean, or a\n * flat array of those. Anything else is an error naming the line, rather than a\n * silent misread — the kit stays dependency-free, and a brand file has no use\n * for the rest of the language.\n */\n\nexport type TomlValue = string | number | boolean | TomlValue[];\nexport interface TomlTable {\n [key: string]: TomlValue | TomlTable;\n}\n\nconst BARE_KEY = /^[A-Za-z0-9_-]+$/;\n\nexport function parseToml(text: string): TomlTable {\n const root: TomlTable = {};\n let table = root;\n const headers = new Set<string>();\n text.split(/\\r?\\n/).forEach((raw, index) => {\n const line = stripComment(raw).trim();\n if (line === \"\") return;\n const fail = (why: string): never => {\n throw new Error(`TOML line ${index + 1}: ${why}: ${raw.trim()}`);\n };\n\n if (line.startsWith(\"[\")) {\n if (!line.endsWith(\"]\") || line.startsWith(\"[[\")) fail(\"only plain [table] headers are supported\");\n const path = line.slice(1, -1).trim().split(\".\").map((part) => part.trim());\n // Re-opening a table would merge two halves of it silently.\n if (headers.has(path.join(\".\"))) fail(\"duplicate table header\");\n headers.add(path.join(\".\"));\n table = descend(root, path, fail);\n return;\n }\n\n const eq = line.indexOf(\"=\");\n if (eq < 0) fail(\"expected `key = value`\");\n const key = line.slice(0, eq).trim();\n if (!BARE_KEY.test(key)) fail(\"only bare keys are supported\");\n if (key in table) fail(`duplicate key \\`${key}\\``);\n table[key] = parseValue(line.slice(eq + 1).trim(), fail);\n });\n return root;\n}\n\nfunction descend(root: TomlTable, path: string[], fail: (why: string) => never): TomlTable {\n let table = root;\n for (const part of path) {\n const key = part.trim();\n if (!BARE_KEY.test(key)) fail(\"only bare keys are supported in a header\");\n const next = table[key] ?? {};\n if (typeof next !== \"object\" || Array.isArray(next)) fail(`\\`${key}\\` is already a value`);\n table[key] = next;\n table = next as TomlTable;\n }\n return table;\n}\n\n// A `#` inside a string is not a comment; the brand files carry `\"#rrggbb\"`.\nfunction stripComment(line: string): string {\n let quoted = false;\n for (let i = 0; i < line.length; i++) {\n const c = line[i];\n if (c === \"\\\\\" && quoted) i++;\n else if (c === '\"') quoted = !quoted;\n else if (c === \"#\" && !quoted) return line.slice(0, i);\n }\n return line;\n}\n\nfunction parseValue(src: string, fail: (why: string) => never): TomlValue {\n if (src.startsWith('\"')) {\n if (src.length < 2 || !src.endsWith('\"')) fail(\"unterminated string\");\n const body = src.slice(1, -1);\n if (/(^|[^\\\\])(\\\\\\\\)*\"/.test(body)) fail(\"one string per value\");\n // Only the escapes decoded below; `\\u…` and friends would otherwise pass through as literal text.\n if (/\\\\(?![\"\\\\nt])/.test(body.replace(/\\\\\\\\/g, \"\"))) fail(\"unsupported escape\");\n return body.replace(/\\\\([\"\\\\nt])/g, (_, c: string) => ({ n: \"\\n\", t: \"\\t\" })[c] ?? c);\n }\n if (src.startsWith(\"[\")) {\n if (!src.endsWith(\"]\")) fail(\"arrays must close on the same line\");\n const inner = src.slice(1, -1).trim();\n if (inner === \"\") return [];\n return splitTopLevel(inner).map((item) => parseValue(item.trim(), fail));\n }\n if (src === \"true\" || src === \"false\") return src === \"true\";\n if (/^[+-]?\\d+(\\.\\d+)?$/.test(src)) return Number(src);\n return fail(\"unsupported value\");\n}\n\nfunction splitTopLevel(inner: string): string[] {\n const items: string[] = [];\n let quoted = false;\n let start = 0;\n for (let i = 0; i < inner.length; i++) {\n const c = inner[i];\n if (c === \"\\\\\" && quoted) i++;\n else if (c === '\"') quoted = !quoted;\n else if (c === \",\" && !quoted) {\n items.push(inner.slice(start, i));\n start = i + 1;\n }\n }\n const last = inner.slice(start);\n if (last.trim() !== \"\") items.push(last);\n return items;\n}\n","import { references, type Contract } from \"./contract\";\nimport { parseToml, type TomlTable, type TomlValue } from \"./toml\";\n\nexport type PolarityName = \"light\" | \"dark\";\nexport const POLARITIES: readonly PolarityName[] = [\"light\", \"dark\"];\n\n/** A brand's palette: one value per contract name, for each polarity. */\nexport interface BrandConfig {\n colors: Record<PolarityName, Record<string, string>>;\n /** `--brand-mark`: the URL `<Logo>` masks. */\n mark?: string;\n /** `--brand-aspect`: the mark's width / height, e.g. `\"387 / 335\"`. */\n markAspect?: string;\n /** `--brand-font-<role>`: a family name per role; unset roles keep the kit's. */\n fonts?: Partial<Record<FontRole, string>>;\n}\n\nexport type FontRole = \"sans\" | \"serif\" | \"mono\";\nexport const FONT_ROLES: readonly FontRole[] = [\"sans\", \"serif\", \"mono\"];\n// aquafix's file calls the body family `text`.\nconst FONT_KEYS: Record<string, FontRole> = { sans: \"sans\", text: \"sans\", serif: \"serif\", mono: \"mono\" };\n\n/** Every problem with a brand file at once, so one run fixes them all. */\nexport class PaletteError extends Error {\n constructor(readonly problems: readonly string[]) {\n super(`invalid brand palette:\\n - ${problems.join(\"\\n - \")}`);\n this.name = \"PaletteError\";\n }\n}\n\n// Written into CSS verbatim, so a value is held to a shape that cannot close\n// the declaration: a hex colour, or a reference to another token.\nconst COLOUR = /^(#[0-9a-fA-F]{3}|#[0-9a-fA-F]{6}|#[0-9a-fA-F]{8}|var\\(--[a-z0-9-]+\\))$/;\nconst SLUG = /^[a-z0-9][a-z0-9-]*$/;\nconst ASPECT = /^\\d+(\\.\\d+)?\\s*\\/\\s*\\d+(\\.\\d+)?$/;\n// Written inside `url(\"…\")`: nothing that closes the string or the function,\n// and no markup characters for a sheet that ends up inlined in HTML.\nconst MARK_URL = /^[^\"'`\\\\\\s()<>]+$/;\nconst FAMILY = /^[A-Za-z0-9][A-Za-z0-9 _-]*$/;\n\n/**\n * Reads the aquafix `brand.toml` shape: `[colors.light]` and `[colors.dark]`\n * keyed by contract name, optional top-level `mark` / `mark-aspect`, and the\n * family names in `[fonts]` (`sans` or `text`, `serif`, `mono`). The rest of\n * `[fonts]` and every other table (`[print]`, …) belong to other tools and are\n * ignored — loading the font files is the site's job, not the palette's.\n */\nexport function brandFromToml(text: string): BrandConfig {\n const doc = parseToml(text);\n const problems: string[] = [];\n const colors = table(doc[\"colors\"]);\n if (!colors) problems.push(\"no [colors] table\");\n const scope = (name: PolarityName): Record<string, string> => {\n const t = colors ? table(colors[name]) : undefined;\n if (colors && !t) problems.push(`no [colors.${name}] table`);\n const out: Record<string, string> = {};\n for (const [key, value] of Object.entries(t ?? {})) {\n if (typeof value === \"string\") out[key] = value;\n else problems.push(`colors.${name}.${key} is not a string`);\n }\n return out;\n };\n for (const key of Object.keys(colors ?? {})) {\n if (!(POLARITIES as readonly string[]).includes(key)) problems.push(`[colors.${key}] is not a polarity (light, dark)`);\n }\n const config: BrandConfig = { colors: { light: scope(\"light\"), dark: scope(\"dark\") } };\n const mark = optionalString(doc[\"mark\"], \"mark\", problems);\n const markAspect = optionalString(doc[\"mark-aspect\"], \"mark-aspect\", problems);\n if (mark !== undefined) config.mark = mark;\n if (markAspect !== undefined) config.markAspect = markAspect;\n const fonts = table(doc[\"fonts\"]);\n for (const [key, role] of Object.entries(FONT_KEYS)) {\n const family = optionalString(fonts?.[key], `fonts.${key}`, problems);\n if (family !== undefined) config.fonts = { ...config.fonts, [role]: family };\n }\n if (problems.length > 0) throw new PaletteError(problems);\n return config;\n}\n\nfunction table(value: TomlValue | TomlTable | undefined): TomlTable | undefined {\n return typeof value === \"object\" && !Array.isArray(value) ? value : undefined;\n}\n\nfunction optionalString(value: TomlValue | TomlTable | undefined, key: string, problems: string[]) {\n if (value === undefined || typeof value === \"string\") return value;\n problems.push(`${key} is not a string`);\n return undefined;\n}\n\n/** The problems with `config` against `contract`; empty when it is complete. */\nexport function validateBrand(config: BrandConfig, contract: Contract): string[] {\n const problems: string[] = [];\n const known = new Set([...contract.required, ...contract.derived]);\n for (const polarity of POLARITIES) {\n const values = config.colors[polarity];\n const missing = contract.required.filter((t) => !(t in values));\n if (missing.length > 0) problems.push(`${polarity} is missing ${missing.join(\", \")}`);\n for (const [key, value] of Object.entries(values)) {\n if (!known.has(key)) problems.push(`${polarity}.${key} is not a token of the contract`);\n else if (!COLOUR.test(value)) problems.push(`${polarity}.${key} = ${JSON.stringify(value)} is not #hex or var(--token)`);\n else {\n for (const ref of references(value)) {\n if (!known.has(ref)) problems.push(`${polarity}.${key} refers to --${ref}, which is not a token of the contract`);\n }\n }\n }\n const cycle = findCycle(values, contract, known);\n if (cycle) problems.push(`${polarity} has a reference cycle: ${cycle.map((t) => `--${t}`).join(\" → \")}`);\n }\n if (config.mark !== undefined && !MARK_URL.test(config.mark)) problems.push(`mark ${JSON.stringify(config.mark)} is not a plain URL`);\n if (config.markAspect !== undefined && !ASPECT.test(config.markAspect)) {\n problems.push(`mark-aspect ${JSON.stringify(config.markAspect)} is not \"width / height\"`);\n }\n for (const [role, family] of Object.entries(config.fonts ?? {})) {\n if (!FAMILY.test(family)) problems.push(`fonts.${role} ${JSON.stringify(family)} is not a plain family name`);\n }\n return problems;\n}\n\n/**\n * A `var()` cycle makes every token on it invalid at computed-value time — the\n * same silent hole as a missing token. Edges are the palette's own references,\n * and for a derived token it leaves to the contract, the contract's formula\n * (`border` reads `ink`), so `ink = \"var(--border)\"` is caught too.\n */\nfunction findCycle(values: Record<string, string>, contract: Contract, known: Set<string>): string[] | undefined {\n const edges = (name: string): readonly string[] => {\n const value = values[name];\n if (value !== undefined) return references(value).filter((r) => known.has(r));\n return contract.derivedFrom[name] ?? [];\n };\n const done = new Set<string>();\n const path: string[] = [];\n const visit = (name: string): string[] | undefined => {\n const at = path.indexOf(name);\n if (at >= 0) return [...path.slice(at), name];\n if (done.has(name)) return undefined;\n path.push(name);\n for (const next of edges(name)) {\n const cycle = visit(next);\n if (cycle) return cycle;\n }\n path.pop();\n done.add(name);\n return undefined;\n };\n for (const name of known) {\n const cycle = visit(name);\n if (cycle) return cycle;\n }\n return undefined;\n}\n\n/**\n * A flat sheet scoping `config` under `[data-brand=\"<brand>\"]`: light is the\n * brand's default and `.light` / `.dark` beneath it pick a polarity. Selectors\n * outrank the zero-specificity EV palette, so import order does not matter.\n * Throws a {@link PaletteError} when a scope is incomplete — Tailwind answers an\n * undefined token with no rule at all, so a hole would be silent on the page.\n */\nexport function renderPalette(brand: string, config: BrandConfig, contract: Contract, source = \"a brand file\"): string {\n const problems = SLUG.test(brand) ? [] : [`brand ${JSON.stringify(brand)} is not a lowercase slug`];\n problems.push(...validateBrand(config, contract));\n if (problems.length > 0) throw new PaletteError(problems);\n\n const at = `[data-brand=\"${brand}\"]`;\n const order = [...contract.required, ...contract.derived];\n const block = (selectors: string[], polarity: PolarityName) => {\n const values = config.colors[polarity];\n const body = order.filter((t) => t in values).map((t) => ` --${t}: ${values[t]};\\n`);\n return `${selectors.join(\",\\n\")} {\\n --scheme: ${polarity};\\n${body.join(\"\")}}\\n`;\n };\n const extras: string[] = [];\n if (config.mark !== undefined) extras.push(` --brand-mark: url(\"${config.mark}\");\\n`);\n if (config.markAspect !== undefined) extras.push(` --brand-aspect: ${config.markAspect};\\n`);\n for (const role of FONT_ROLES) {\n const family = config.fonts?.[role];\n if (family !== undefined) extras.push(` --brand-font-${role}: \"${family}\";\\n`);\n }\n\n return [\n // `source` is a file name, free text as far as CSS is concerned: it must not close the comment.\n `/* GENERATED by evinvest-palette from ${source.replaceAll(\"*/\", \"*\\\\/\")} — do not edit. */\\n`,\n block([at, `${at}.light`, `${at} .light`], \"light\"),\n block([`${at}.dark`, `${at} .dark`], \"dark\"),\n ...(extras.length > 0 ? [`${at} {\\n${extras.join(\"\")}}\\n`] : []),\n ].join(\"\\n\");\n}\n"],"mappings":";AAgBO,SAAS,WAAW,OAAyB;AAClD,SAAO,CAAC,GAAG,MAAM,SAAS,yBAAyB,CAAC,EAAE,QAAQ,CAAC,MAAO,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAE;AAC3F;AAGO,IAAM,gBAAgB;AAGtB,IAAM,cAAc;AAYpB,SAAS,UAAU,KAAwB;AAChD,QAAM,QAAmB,CAAC;AAC1B,QAAM,MAAM,IAAI,QAAQ,qBAAqB,EAAE;AAC/C,QAAM,QAAkB,CAAC;AACzB,MAAI,QAAQ;AACZ,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,UAAM,IAAI,IAAI,CAAC;AACf,QAAI,MAAM,KAAK;AACb,YAAM,KAAK,IAAI,MAAM,OAAO,CAAC,EAAE,KAAK,CAAC;AACrC,cAAQ,IAAI;AAAA,IACd,WAAW,MAAM,KAAK;AACpB,YAAM,WAAW,MAAM,IAAI;AAC3B,UAAI,aAAa,OAAW,OAAM,IAAI,MAAM,+BAA+B;AAC3E,YAAM,OAAO,IAAI,MAAM,OAAO,CAAC;AAC/B,UAAI,CAAC,KAAK,SAAS,GAAG,EAAG,OAAM,KAAK,EAAE,UAAU,UAAU,QAAQ,GAAG,cAAc,aAAa,IAAI,EAAE,CAAC;AACvG,cAAQ,IAAI;AAAA,IACd,WAAW,MAAM,OAAO,MAAM,WAAW,GAAG;AAC1C,cAAQ,IAAI;AAAA,IACd;AAAA,EACF;AACA,MAAI,MAAM,SAAS,EAAG,OAAM,IAAI,MAAM,+BAA+B;AACrE,SAAO;AACT;AAEA,SAAS,aAAa,MAAmC;AACvD,QAAM,MAAM,oBAAI,IAAoB;AACpC,aAAW,QAAQ,KAAK,MAAM,GAAG,GAAG;AAClC,UAAM,QAAQ,6CAA6C,KAAK,IAAI;AACpE,QAAI,QAAQ,CAAC,KAAK,MAAM,CAAC,EAAG,KAAI,IAAI,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC;AAAA,EACxD;AACA,SAAO;AACT;AAEO,SAAS,UAAU,UAA0B;AAClD,SAAO,SAAS,QAAQ,QAAQ,GAAG,EAAE,QAAQ,YAAY,IAAI,EAAE,KAAK;AACtE;AAEO,SAAS,aAAa,KAAuB;AAClD,QAAM,QAAQ,UAAU,GAAG;AAC3B,QAAM,QAAQ,MAAM,KAAK,CAAC,MAAM,EAAE,aAAa,eAAe;AAC9D,MAAI,CAAC,MAAO,OAAM,IAAI,MAAM,wEAAmE;AAC/F,QAAM,UAAoB,CAAC;AAC3B,aAAW,CAAC,MAAM,KAAK,KAAK,MAAM,cAAc;AAC9C,UAAM,QAAQ,eAAe,KAAK,IAAI;AACtC,QAAI,QAAQ,CAAC,KAAK,UAAU,SAAS,MAAM,CAAC,CAAC,IAAK,SAAQ,KAAK,MAAM,CAAC,CAAC;AAAA,EACzE;AACA,QAAM,cAAc,MAAM,KAAK,CAAC,MAAM,EAAE,aAAa,aAAa;AAClE,MAAI,CAAC,YAAa,OAAM,IAAI,MAAM,wBAAwB,aAAa,uCAAkC;AACzG,QAAM,YAAY,MAAM,KAAK,CAAC,MAAM,EAAE,aAAa,WAAW;AAC9D,QAAM,WAAW,IAAI,IAAI,CAAC,GAAG,YAAY,cAAc,GAAI,WAAW,gBAAgB,CAAC,CAAE,CAAC;AAC1F,QAAM,UAAU,CAAC,GAAG,SAAS,KAAK,CAAC;AACnC,QAAM,QAAQ,QAAQ,OAAO,CAAC,MAAM,CAAC,QAAQ,SAAS,CAAC,CAAC;AACxD,MAAI,MAAM,SAAS,EAAG,OAAM,IAAI,MAAM,0CAA0C,MAAM,KAAK,IAAI,CAAC,EAAE;AAClG,QAAM,cAAc,OAAO,YAAY,CAAC,GAAG,QAAQ,EAAE,IAAI,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM,WAAW,KAAK,CAAC,CAAC,CAAC;AACtG,SAAO,EAAE,UAAU,QAAQ,OAAO,CAAC,MAAM,CAAC,QAAQ,SAAS,CAAC,CAAC,GAAG,SAAS,YAAY;AACvF;;;AC9EA,IAAM,WAAW;AAEV,SAAS,UAAU,MAAyB;AACjD,QAAM,OAAkB,CAAC;AACzB,MAAIA,SAAQ;AACZ,QAAM,UAAU,oBAAI,IAAY;AAChC,OAAK,MAAM,OAAO,EAAE,QAAQ,CAAC,KAAK,UAAU;AAC1C,UAAM,OAAO,aAAa,GAAG,EAAE,KAAK;AACpC,QAAI,SAAS,GAAI;AACjB,UAAM,OAAO,CAAC,QAAuB;AACnC,YAAM,IAAI,MAAM,aAAa,QAAQ,CAAC,KAAK,GAAG,KAAK,IAAI,KAAK,CAAC,EAAE;AAAA,IACjE;AAEA,QAAI,KAAK,WAAW,GAAG,GAAG;AACxB,UAAI,CAAC,KAAK,SAAS,GAAG,KAAK,KAAK,WAAW,IAAI,EAAG,MAAK,0CAA0C;AACjG,YAAM,OAAO,KAAK,MAAM,GAAG,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,EAAE,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC;AAE1E,UAAI,QAAQ,IAAI,KAAK,KAAK,GAAG,CAAC,EAAG,MAAK,wBAAwB;AAC9D,cAAQ,IAAI,KAAK,KAAK,GAAG,CAAC;AAC1B,MAAAA,SAAQ,QAAQ,MAAM,MAAM,IAAI;AAChC;AAAA,IACF;AAEA,UAAM,KAAK,KAAK,QAAQ,GAAG;AAC3B,QAAI,KAAK,EAAG,MAAK,wBAAwB;AACzC,UAAM,MAAM,KAAK,MAAM,GAAG,EAAE,EAAE,KAAK;AACnC,QAAI,CAAC,SAAS,KAAK,GAAG,EAAG,MAAK,8BAA8B;AAC5D,QAAI,OAAOA,OAAO,MAAK,mBAAmB,GAAG,IAAI;AACjD,IAAAA,OAAM,GAAG,IAAI,WAAW,KAAK,MAAM,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI;AAAA,EACzD,CAAC;AACD,SAAO;AACT;AAEA,SAAS,QAAQ,MAAiB,MAAgB,MAAyC;AACzF,MAAIA,SAAQ;AACZ,aAAW,QAAQ,MAAM;AACvB,UAAM,MAAM,KAAK,KAAK;AACtB,QAAI,CAAC,SAAS,KAAK,GAAG,EAAG,MAAK,0CAA0C;AACxE,UAAM,OAAOA,OAAM,GAAG,KAAK,CAAC;AAC5B,QAAI,OAAO,SAAS,YAAY,MAAM,QAAQ,IAAI,EAAG,MAAK,KAAK,GAAG,uBAAuB;AACzF,IAAAA,OAAM,GAAG,IAAI;AACb,IAAAA,SAAQ;AAAA,EACV;AACA,SAAOA;AACT;AAGA,SAAS,aAAa,MAAsB;AAC1C,MAAI,SAAS;AACb,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,IAAI,KAAK,CAAC;AAChB,QAAI,MAAM,QAAQ,OAAQ;AAAA,aACjB,MAAM,IAAK,UAAS,CAAC;AAAA,aACrB,MAAM,OAAO,CAAC,OAAQ,QAAO,KAAK,MAAM,GAAG,CAAC;AAAA,EACvD;AACA,SAAO;AACT;AAEA,SAAS,WAAW,KAAa,MAAyC;AACxE,MAAI,IAAI,WAAW,GAAG,GAAG;AACvB,QAAI,IAAI,SAAS,KAAK,CAAC,IAAI,SAAS,GAAG,EAAG,MAAK,qBAAqB;AACpE,UAAM,OAAO,IAAI,MAAM,GAAG,EAAE;AAC5B,QAAI,oBAAoB,KAAK,IAAI,EAAG,MAAK,sBAAsB;AAE/D,QAAI,gBAAgB,KAAK,KAAK,QAAQ,SAAS,EAAE,CAAC,EAAG,MAAK,oBAAoB;AAC9E,WAAO,KAAK,QAAQ,gBAAgB,CAAC,GAAG,OAAe,EAAE,GAAG,MAAM,GAAG,IAAK,GAAG,CAAC,KAAK,CAAC;AAAA,EACtF;AACA,MAAI,IAAI,WAAW,GAAG,GAAG;AACvB,QAAI,CAAC,IAAI,SAAS,GAAG,EAAG,MAAK,oCAAoC;AACjE,UAAM,QAAQ,IAAI,MAAM,GAAG,EAAE,EAAE,KAAK;AACpC,QAAI,UAAU,GAAI,QAAO,CAAC;AAC1B,WAAO,cAAc,KAAK,EAAE,IAAI,CAAC,SAAS,WAAW,KAAK,KAAK,GAAG,IAAI,CAAC;AAAA,EACzE;AACA,MAAI,QAAQ,UAAU,QAAQ,QAAS,QAAO,QAAQ;AACtD,MAAI,qBAAqB,KAAK,GAAG,EAAG,QAAO,OAAO,GAAG;AACrD,SAAO,KAAK,mBAAmB;AACjC;AAEA,SAAS,cAAc,OAAyB;AAC9C,QAAM,QAAkB,CAAC;AACzB,MAAI,SAAS;AACb,MAAI,QAAQ;AACZ,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,IAAI,MAAM,CAAC;AACjB,QAAI,MAAM,QAAQ,OAAQ;AAAA,aACjB,MAAM,IAAK,UAAS,CAAC;AAAA,aACrB,MAAM,OAAO,CAAC,QAAQ;AAC7B,YAAM,KAAK,MAAM,MAAM,OAAO,CAAC,CAAC;AAChC,cAAQ,IAAI;AAAA,IACd;AAAA,EACF;AACA,QAAM,OAAO,MAAM,MAAM,KAAK;AAC9B,MAAI,KAAK,KAAK,MAAM,GAAI,OAAM,KAAK,IAAI;AACvC,SAAO;AACT;;;ACvGO,IAAM,aAAsC,CAAC,SAAS,MAAM;AAc5D,IAAM,aAAkC,CAAC,QAAQ,SAAS,MAAM;AAEvE,IAAM,YAAsC,EAAE,MAAM,QAAQ,MAAM,QAAQ,OAAO,SAAS,MAAM,OAAO;AAGhG,IAAM,eAAN,cAA2B,MAAM;AAAA,EACtC,YAAqB,UAA6B;AAChD,UAAM;AAAA,MAA+B,SAAS,KAAK,QAAQ,CAAC,EAAE;AAD3C;AAEnB,SAAK,OAAO;AAAA,EACd;AAAA,EAHqB;AAIvB;AAIA,IAAM,SAAS;AACf,IAAM,OAAO;AACb,IAAM,SAAS;AAGf,IAAM,WAAW;AACjB,IAAM,SAAS;AASR,SAAS,cAAc,MAA2B;AACvD,QAAM,MAAM,UAAU,IAAI;AAC1B,QAAM,WAAqB,CAAC;AAC5B,QAAM,SAAS,MAAM,IAAI,QAAQ,CAAC;AAClC,MAAI,CAAC,OAAQ,UAAS,KAAK,mBAAmB;AAC9C,QAAM,QAAQ,CAAC,SAA+C;AAC5D,UAAM,IAAI,SAAS,MAAM,OAAO,IAAI,CAAC,IAAI;AACzC,QAAI,UAAU,CAAC,EAAG,UAAS,KAAK,cAAc,IAAI,SAAS;AAC3D,UAAM,MAA8B,CAAC;AACrC,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,CAAC,CAAC,GAAG;AAClD,UAAI,OAAO,UAAU,SAAU,KAAI,GAAG,IAAI;AAAA,UACrC,UAAS,KAAK,UAAU,IAAI,IAAI,GAAG,kBAAkB;AAAA,IAC5D;AACA,WAAO;AAAA,EACT;AACA,aAAW,OAAO,OAAO,KAAK,UAAU,CAAC,CAAC,GAAG;AAC3C,QAAI,CAAE,WAAiC,SAAS,GAAG,EAAG,UAAS,KAAK,WAAW,GAAG,mCAAmC;AAAA,EACvH;AACA,QAAM,SAAsB,EAAE,QAAQ,EAAE,OAAO,MAAM,OAAO,GAAG,MAAM,MAAM,MAAM,EAAE,EAAE;AACrF,QAAM,OAAO,eAAe,IAAI,MAAM,GAAG,QAAQ,QAAQ;AACzD,QAAM,aAAa,eAAe,IAAI,aAAa,GAAG,eAAe,QAAQ;AAC7E,MAAI,SAAS,OAAW,QAAO,OAAO;AACtC,MAAI,eAAe,OAAW,QAAO,aAAa;AAClD,QAAM,QAAQ,MAAM,IAAI,OAAO,CAAC;AAChC,aAAW,CAAC,KAAK,IAAI,KAAK,OAAO,QAAQ,SAAS,GAAG;AACnD,UAAM,SAAS,eAAe,QAAQ,GAAG,GAAG,SAAS,GAAG,IAAI,QAAQ;AACpE,QAAI,WAAW,OAAW,QAAO,QAAQ,EAAE,GAAG,OAAO,OAAO,CAAC,IAAI,GAAG,OAAO;AAAA,EAC7E;AACA,MAAI,SAAS,SAAS,EAAG,OAAM,IAAI,aAAa,QAAQ;AACxD,SAAO;AACT;AAEA,SAAS,MAAM,OAAiE;AAC9E,SAAO,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,IAAI,QAAQ;AACtE;AAEA,SAAS,eAAe,OAA0C,KAAa,UAAoB;AACjG,MAAI,UAAU,UAAa,OAAO,UAAU,SAAU,QAAO;AAC7D,WAAS,KAAK,GAAG,GAAG,kBAAkB;AACtC,SAAO;AACT;AAGO,SAAS,cAAc,QAAqB,UAA8B;AAC/E,QAAM,WAAqB,CAAC;AAC5B,QAAM,QAAQ,oBAAI,IAAI,CAAC,GAAG,SAAS,UAAU,GAAG,SAAS,OAAO,CAAC;AACjE,aAAW,YAAY,YAAY;AACjC,UAAM,SAAS,OAAO,OAAO,QAAQ;AACrC,UAAM,UAAU,SAAS,SAAS,OAAO,CAAC,MAAM,EAAE,KAAK,OAAO;AAC9D,QAAI,QAAQ,SAAS,EAAG,UAAS,KAAK,GAAG,QAAQ,eAAe,QAAQ,KAAK,IAAI,CAAC,EAAE;AACpF,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,UAAI,CAAC,MAAM,IAAI,GAAG,EAAG,UAAS,KAAK,GAAG,QAAQ,IAAI,GAAG,iCAAiC;AAAA,eAC7E,CAAC,OAAO,KAAK,KAAK,EAAG,UAAS,KAAK,GAAG,QAAQ,IAAI,GAAG,MAAM,KAAK,UAAU,KAAK,CAAC,8BAA8B;AAAA,WAClH;AACH,mBAAW,OAAO,WAAW,KAAK,GAAG;AACnC,cAAI,CAAC,MAAM,IAAI,GAAG,EAAG,UAAS,KAAK,GAAG,QAAQ,IAAI,GAAG,gBAAgB,GAAG,wCAAwC;AAAA,QAClH;AAAA,MACF;AAAA,IACF;AACA,UAAM,QAAQ,UAAU,QAAQ,UAAU,KAAK;AAC/C,QAAI,MAAO,UAAS,KAAK,GAAG,QAAQ,2BAA2B,MAAM,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,EAAE,KAAK,UAAK,CAAC,EAAE;AAAA,EACzG;AACA,MAAI,OAAO,SAAS,UAAa,CAAC,SAAS,KAAK,OAAO,IAAI,EAAG,UAAS,KAAK,QAAQ,KAAK,UAAU,OAAO,IAAI,CAAC,qBAAqB;AACpI,MAAI,OAAO,eAAe,UAAa,CAAC,OAAO,KAAK,OAAO,UAAU,GAAG;AACtE,aAAS,KAAK,eAAe,KAAK,UAAU,OAAO,UAAU,CAAC,0BAA0B;AAAA,EAC1F;AACA,aAAW,CAAC,MAAM,MAAM,KAAK,OAAO,QAAQ,OAAO,SAAS,CAAC,CAAC,GAAG;AAC/D,QAAI,CAAC,OAAO,KAAK,MAAM,EAAG,UAAS,KAAK,SAAS,IAAI,IAAI,KAAK,UAAU,MAAM,CAAC,6BAA6B;AAAA,EAC9G;AACA,SAAO;AACT;AAQA,SAAS,UAAU,QAAgC,UAAoB,OAA0C;AAC/G,QAAM,QAAQ,CAAC,SAAoC;AACjD,UAAM,QAAQ,OAAO,IAAI;AACzB,QAAI,UAAU,OAAW,QAAO,WAAW,KAAK,EAAE,OAAO,CAAC,MAAM,MAAM,IAAI,CAAC,CAAC;AAC5E,WAAO,SAAS,YAAY,IAAI,KAAK,CAAC;AAAA,EACxC;AACA,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,OAAiB,CAAC;AACxB,QAAM,QAAQ,CAAC,SAAuC;AACpD,UAAM,KAAK,KAAK,QAAQ,IAAI;AAC5B,QAAI,MAAM,EAAG,QAAO,CAAC,GAAG,KAAK,MAAM,EAAE,GAAG,IAAI;AAC5C,QAAI,KAAK,IAAI,IAAI,EAAG,QAAO;AAC3B,SAAK,KAAK,IAAI;AACd,eAAW,QAAQ,MAAM,IAAI,GAAG;AAC9B,YAAM,QAAQ,MAAM,IAAI;AACxB,UAAI,MAAO,QAAO;AAAA,IACpB;AACA,SAAK,IAAI;AACT,SAAK,IAAI,IAAI;AACb,WAAO;AAAA,EACT;AACA,aAAW,QAAQ,OAAO;AACxB,UAAM,QAAQ,MAAM,IAAI;AACxB,QAAI,MAAO,QAAO;AAAA,EACpB;AACA,SAAO;AACT;AASO,SAAS,cAAc,OAAe,QAAqB,UAAoB,SAAS,gBAAwB;AACrH,QAAM,WAAW,KAAK,KAAK,KAAK,IAAI,CAAC,IAAI,CAAC,SAAS,KAAK,UAAU,KAAK,CAAC,0BAA0B;AAClG,WAAS,KAAK,GAAG,cAAc,QAAQ,QAAQ,CAAC;AAChD,MAAI,SAAS,SAAS,EAAG,OAAM,IAAI,aAAa,QAAQ;AAExD,QAAM,KAAK,gBAAgB,KAAK;AAChC,QAAM,QAAQ,CAAC,GAAG,SAAS,UAAU,GAAG,SAAS,OAAO;AACxD,QAAM,QAAQ,CAAC,WAAqB,aAA2B;AAC7D,UAAM,SAAS,OAAO,OAAO,QAAQ;AACrC,UAAM,OAAO,MAAM,OAAO,CAAC,MAAM,KAAK,MAAM,EAAE,IAAI,CAAC,MAAM,OAAO,CAAC,KAAK,OAAO,CAAC,CAAC;AAAA,CAAK;AACpF,WAAO,GAAG,UAAU,KAAK,KAAK,CAAC;AAAA,cAAmB,QAAQ;AAAA,EAAM,KAAK,KAAK,EAAE,CAAC;AAAA;AAAA,EAC/E;AACA,QAAM,SAAmB,CAAC;AAC1B,MAAI,OAAO,SAAS,OAAW,QAAO,KAAK,wBAAwB,OAAO,IAAI;AAAA,CAAO;AACrF,MAAI,OAAO,eAAe,OAAW,QAAO,KAAK,qBAAqB,OAAO,UAAU;AAAA,CAAK;AAC5F,aAAW,QAAQ,YAAY;AAC7B,UAAM,SAAS,OAAO,QAAQ,IAAI;AAClC,QAAI,WAAW,OAAW,QAAO,KAAK,kBAAkB,IAAI,MAAM,MAAM;AAAA,CAAM;AAAA,EAChF;AAEA,SAAO;AAAA;AAAA,IAEL,yCAAyC,OAAO,WAAW,MAAM,MAAM,CAAC;AAAA;AAAA,IACxE,MAAM,CAAC,IAAI,GAAG,EAAE,UAAU,GAAG,EAAE,SAAS,GAAG,OAAO;AAAA,IAClD,MAAM,CAAC,GAAG,EAAE,SAAS,GAAG,EAAE,QAAQ,GAAG,MAAM;AAAA,IAC3C,GAAI,OAAO,SAAS,IAAI,CAAC,GAAG,EAAE;AAAA,EAAO,OAAO,KAAK,EAAE,CAAC;AAAA,CAAK,IAAI,CAAC;AAAA,EAChE,EAAE,KAAK,IAAI;AACb;","names":["table"]}
|