@spaethtech/svelte-ui 0.1.10 → 0.3.1-dev.3.e16df24
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 +180 -42
- package/dist/components/Alert.svelte +119 -0
- package/dist/components/Alert.svelte.d.ts +29 -0
- package/dist/components/Badge/Badge.svelte +142 -69
- package/dist/components/Badge/Badge.svelte.d.ts +26 -6
- package/dist/components/Badge/index.d.ts +1 -1
- package/dist/components/Badge/index.js +1 -1
- package/dist/components/Banner.svelte +133 -0
- package/dist/components/Banner.svelte.d.ts +31 -0
- package/dist/components/Button.svelte +240 -0
- package/dist/components/Button.svelte.d.ts +33 -0
- package/dist/components/ButtonDropdown.svelte +145 -0
- package/dist/components/ButtonDropdown.svelte.d.ts +31 -0
- package/dist/components/Calendar.svelte +259 -0
- package/dist/components/Calendar.svelte.d.ts +27 -0
- package/dist/components/Card.svelte +78 -0
- package/dist/components/Card.svelte.d.ts +17 -0
- package/dist/components/CardBody.svelte +41 -0
- package/dist/components/CardBody.svelte.d.ts +17 -0
- package/dist/components/CardFooter.svelte +49 -0
- package/dist/components/CardFooter.svelte.d.ts +16 -0
- package/dist/components/CardHeader.svelte +49 -0
- package/dist/components/CardHeader.svelte.d.ts +16 -0
- package/dist/components/Checkbox.svelte +131 -0
- package/dist/components/Checkbox.svelte.d.ts +16 -0
- package/dist/components/DataTable.svelte +518 -0
- package/dist/components/DataTable.svelte.d.ts +74 -0
- package/dist/components/DatePicker.svelte +312 -0
- package/dist/components/DatePicker.svelte.d.ts +32 -0
- package/dist/components/DateTimeInput.svelte +93 -0
- package/dist/components/DateTimeInput.svelte.d.ts +20 -0
- package/dist/components/Dialog.svelte +132 -0
- package/dist/components/Dialog.svelte.d.ts +24 -0
- package/dist/components/{EmailInput/EmailInput.svelte → EmailInput.svelte} +17 -8
- package/dist/components/EmailInput.svelte.d.ts +21 -0
- package/dist/components/Input.svelte +369 -0
- package/dist/components/Input.svelte.d.ts +35 -0
- package/dist/components/{List/List.svelte → List.svelte} +194 -133
- package/dist/components/{List/List.svelte.d.ts → List.svelte.d.ts} +9 -1
- package/dist/components/Menu.svelte +117 -0
- package/dist/components/Menu.svelte.d.ts +20 -0
- package/dist/components/NotesEditor.svelte +127 -0
- package/dist/components/NotesEditor.svelte.d.ts +17 -0
- package/dist/components/{NumberInput/NumberInput.svelte → NumberInput.svelte} +146 -105
- package/dist/components/{NumberInput/NumberInput.svelte.d.ts → NumberInput.svelte.d.ts} +11 -4
- package/dist/components/PasswordInput.svelte +52 -0
- package/dist/components/PasswordInput.svelte.d.ts +20 -0
- package/dist/components/Popup.svelte +140 -0
- package/dist/components/Popup.svelte.d.ts +31 -0
- package/dist/components/Query.svelte +284 -0
- package/dist/components/Query.svelte.d.ts +15 -0
- package/dist/components/{Rating/Rating.svelte → Rating.svelte} +19 -10
- package/dist/components/Rating.svelte.d.ts +13 -0
- package/dist/components/{SearchInput/SearchInput.svelte → SearchInput.svelte} +19 -8
- package/dist/components/{SearchInput/SearchInput.svelte.d.ts → SearchInput.svelte.d.ts} +9 -2
- package/dist/components/{Select/Select.svelte → Select.svelte} +48 -76
- package/dist/components/{Select/Select.svelte.d.ts → Select.svelte.d.ts} +11 -1
- package/dist/components/SideBarMenu/SideBarMenu.svelte +724 -0
- package/dist/components/SideBarMenu/SideBarMenu.svelte.d.ts +109 -0
- package/dist/components/SideBarMenu/index.d.ts +2 -0
- package/dist/components/SideBarMenu/index.js +1 -0
- package/dist/components/TabStrip/TabStrip.svelte +384 -0
- package/dist/components/TabStrip/TabStrip.svelte.d.ts +50 -0
- package/dist/components/TabStrip/index.d.ts +3 -0
- package/dist/components/TabStrip/index.js +2 -0
- package/dist/components/{TextArea/TextArea.svelte → TextArea.svelte} +143 -112
- package/dist/components/TextArea.svelte.d.ts +38 -0
- package/dist/components/ThemeSelector.svelte +81 -0
- package/dist/components/ThemeSelector.svelte.d.ts +10 -0
- package/dist/components/TimePicker.svelte +148 -0
- package/dist/components/TimePicker.svelte.d.ts +28 -0
- package/dist/components/TimeRangeInput.svelte +203 -0
- package/dist/components/TimeRangeInput.svelte.d.ts +29 -0
- package/dist/components/TimeSpinner.svelte +202 -0
- package/dist/components/TimeSpinner.svelte.d.ts +26 -0
- package/dist/components/Toast/Toaster.svelte +51 -0
- package/dist/components/Toast/Toaster.svelte.d.ts +12 -0
- package/dist/components/Toast/toast.svelte.d.ts +29 -0
- package/dist/components/Toast/toast.svelte.js +27 -0
- package/dist/components/Toggle.svelte +93 -0
- package/dist/components/Toggle.svelte.d.ts +15 -0
- package/dist/data/dataset.d.ts +42 -0
- package/dist/data/dataset.js +3 -0
- package/dist/data/index.d.ts +3 -0
- package/dist/data/index.js +3 -0
- package/dist/data/query/ast.d.ts +62 -0
- package/dist/data/query/ast.js +12 -0
- package/dist/data/query/index.d.ts +3 -0
- package/dist/data/query/index.js +3 -0
- package/dist/data/query/lexer.d.ts +33 -0
- package/dist/data/query/lexer.js +225 -0
- package/dist/data/query/parser.d.ts +11 -0
- package/dist/data/query/parser.js +252 -0
- package/dist/data/table/grid.svelte.d.ts +57 -0
- package/dist/data/table/grid.svelte.js +139 -0
- package/dist/data/table/index.d.ts +2 -0
- package/dist/data/table/index.js +2 -0
- package/dist/data/table/types.d.ts +79 -0
- package/dist/index.d.ts +48 -22
- package/dist/index.js +45 -21
- package/dist/positioning/anchored.d.ts +61 -0
- package/dist/positioning/anchored.js +122 -0
- package/dist/positioning/tooltip.d.ts +41 -0
- package/dist/positioning/tooltip.js +147 -0
- package/dist/theme.css +205 -0
- package/dist/types/breakpoints.d.ts +24 -0
- package/dist/types/breakpoints.js +13 -0
- package/dist/types/responsive.d.ts +32 -0
- package/dist/types/responsive.js +54 -0
- package/dist/types/sizes.d.ts +10 -3
- package/dist/types/time.d.ts +19 -0
- package/dist/types/time.js +10 -0
- package/dist/types/variants.d.ts +8 -1
- package/dist/types/variants.js +16 -1
- package/package.json +93 -63
- package/dist/components/Button/Button.svelte +0 -172
- package/dist/components/Button/Button.svelte.d.ts +0 -32
- package/dist/components/Button/index.d.ts +0 -1
- package/dist/components/Button/index.js +0 -1
- package/dist/components/Dialog/Dialog.svelte +0 -101
- package/dist/components/Dialog/Dialog.svelte.d.ts +0 -18
- package/dist/components/Dialog/index.d.ts +0 -1
- package/dist/components/Dialog/index.js +0 -1
- package/dist/components/ElementManager/ElementManager.svelte +0 -397
- package/dist/components/ElementManager/ElementManager.svelte.d.ts +0 -43
- package/dist/components/ElementManager/index.d.ts +0 -1
- package/dist/components/ElementManager/index.js +0 -1
- package/dist/components/EmailInput/EmailInput.svelte.d.ts +0 -14
- package/dist/components/EmailInput/index.d.ts +0 -1
- package/dist/components/EmailInput/index.js +0 -1
- package/dist/components/Icon/Icon.svelte +0 -74
- package/dist/components/Icon/Icon.svelte.d.ts +0 -13
- package/dist/components/Icon/index.d.ts +0 -1
- package/dist/components/Icon/index.js +0 -1
- package/dist/components/Input/Input.svelte +0 -268
- package/dist/components/Input/Input.svelte.d.ts +0 -18
- package/dist/components/Input/index.d.ts +0 -1
- package/dist/components/Input/index.js +0 -1
- package/dist/components/List/index.d.ts +0 -1
- package/dist/components/List/index.js +0 -1
- package/dist/components/ListItem/ListItem.svelte +0 -175
- package/dist/components/ListItem/ListItem.svelte.d.ts +0 -24
- package/dist/components/ListItem/index.d.ts +0 -2
- package/dist/components/ListItem/index.js +0 -2
- package/dist/components/ListView/ListView.svelte +0 -463
- package/dist/components/ListView/ListView.svelte.d.ts +0 -37
- package/dist/components/ListView/index.d.ts +0 -2
- package/dist/components/ListView/index.js +0 -2
- package/dist/components/NodeGraph/BaseNode.d.ts +0 -57
- package/dist/components/NodeGraph/BaseNode.js +0 -30
- package/dist/components/NodeGraph/Edge.svelte +0 -60
- package/dist/components/NodeGraph/Edge.svelte.d.ts +0 -16
- package/dist/components/NodeGraph/ExpressionEvaluator.d.ts +0 -82
- package/dist/components/NodeGraph/ExpressionEvaluator.js +0 -152
- package/dist/components/NodeGraph/Node.svelte +0 -100
- package/dist/components/NodeGraph/Node.svelte.d.ts +0 -10
- package/dist/components/NodeGraph/NodeGraph.svelte +0 -52
- package/dist/components/NodeGraph/NodeGraph.svelte.d.ts +0 -14
- package/dist/components/NodeGraph/NodeInstance.svelte.d.ts +0 -80
- package/dist/components/NodeGraph/NodeInstance.svelte.js +0 -241
- package/dist/components/NodeGraph/NodePort.svelte +0 -75
- package/dist/components/NodeGraph/NodePort.svelte.d.ts +0 -11
- package/dist/components/NodeGraph/decorators.d.ts +0 -81
- package/dist/components/NodeGraph/decorators.js +0 -148
- package/dist/components/NodeGraph/index.d.ts +0 -9
- package/dist/components/NodeGraph/index.js +0 -9
- package/dist/components/NodeGraph/types.d.ts +0 -94
- package/dist/components/NodeGraph/types.js +0 -33
- package/dist/components/NotesEditor/NotesEditor.svelte +0 -203
- package/dist/components/NotesEditor/NotesEditor.svelte.d.ts +0 -9
- package/dist/components/NotesEditor/index.d.ts +0 -1
- package/dist/components/NotesEditor/index.js +0 -1
- package/dist/components/NumberInput/index.d.ts +0 -1
- package/dist/components/NumberInput/index.js +0 -1
- package/dist/components/PasswordInput/PasswordInput.svelte +0 -41
- package/dist/components/PasswordInput/PasswordInput.svelte.d.ts +0 -13
- package/dist/components/PasswordInput/index.d.ts +0 -1
- package/dist/components/PasswordInput/index.js +0 -1
- package/dist/components/Popup/index.d.ts +0 -1
- package/dist/components/Popup/index.js +0 -1
- package/dist/components/Rating/Rating.svelte.d.ts +0 -7
- package/dist/components/Rating/index.d.ts +0 -1
- package/dist/components/Rating/index.js +0 -1
- package/dist/components/ScrollView/ScrollView.svelte +0 -285
- package/dist/components/ScrollView/ScrollView.svelte.d.ts +0 -19
- package/dist/components/ScrollView/index.d.ts +0 -1
- package/dist/components/ScrollView/index.js +0 -1
- package/dist/components/SearchInput/index.d.ts +0 -1
- package/dist/components/SearchInput/index.js +0 -1
- package/dist/components/Select/index.d.ts +0 -1
- package/dist/components/Select/index.js +0 -1
- package/dist/components/TextArea/TextArea.svelte.d.ts +0 -19
- package/dist/components/TextArea/index.d.ts +0 -1
- package/dist/components/TextArea/index.js +0 -1
- package/dist/components/ThemeSelector/ThemeSelector.svelte +0 -64
- package/dist/components/ThemeSelector/ThemeSelector.svelte.d.ts +0 -3
- package/dist/components/ThemeSelector/index.d.ts +0 -1
- package/dist/components/ThemeSelector/index.js +0 -1
- package/dist/components/TooltipHandler/TooltipHandler.svelte +0 -593
- package/dist/components/TooltipHandler/TooltipHandler.svelte.d.ts +0 -10
- package/dist/components/TooltipHandler/index.d.ts +0 -1
- package/dist/components/TooltipHandler/index.js +0 -1
- package/dist/styles/sizes.d.ts +0 -78
- package/dist/styles/sizes.js +0 -120
- package/dist/styles/variants.d.ts +0 -106
- package/dist/styles/variants.js +0 -194
- package/dist/types/ManagerTypes.d.ts +0 -42
- /package/dist/{types/ManagerTypes.js → data/table/types.js} +0 -0
package/README.md
CHANGED
|
@@ -1,80 +1,218 @@
|
|
|
1
|
-
#
|
|
1
|
+
# svelte-ui
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Reusable **Svelte 5** (runes) UI components, styled with **Tailwind CSS v4** and a small set of
|
|
4
|
+
shared design tokens. Icons are **MDI** via [`unplugin-icons`](https://github.com/unplugin/unplugin-icons) —
|
|
5
|
+
there's no per-component icon dependency; you render icons into snippets.
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
Three entry points:
|
|
8
|
+
|
|
9
|
+
- **`svelte-ui`** — the components.
|
|
10
|
+
- **`svelte-ui/data`** — the headless data layer (query language parser/AST + `createGrid`) that
|
|
11
|
+
powers `DataTable`/`Query`.
|
|
12
|
+
- **`svelte-ui/theme.css`** — the theme (color + sizing tokens, light + dark).
|
|
6
13
|
|
|
7
14
|
## Components
|
|
8
15
|
|
|
9
|
-
- **
|
|
10
|
-
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
13
|
-
- **
|
|
14
|
-
- **
|
|
16
|
+
- **Form** — `Button`, `Input`, `Select`, `Checkbox`, `TextArea`, `Rating`, plus the specialized
|
|
17
|
+
inputs `SearchInput` / `EmailInput` / `PasswordInput` / `NumberInput`.
|
|
18
|
+
- **Data** — `DataTable`, `Query` (on the `svelte-ui/data` headless layer).
|
|
19
|
+
- **Overlays** — `Dialog`, `Popup`, `Menu`, and the `use:tooltip` action.
|
|
20
|
+
- **Display / theme** — `Badge`, `NotesEditor`, `ThemeSelector`.
|
|
21
|
+
- **Positioning** — the `anchored` engine (Svelte action + `computePlacement`) shared by
|
|
22
|
+
`Popup` / `Menu` / tooltips.
|
|
23
|
+
|
|
24
|
+
### Cross-cutting axes
|
|
25
|
+
|
|
26
|
+
Most components share two props that behave the same everywhere:
|
|
27
|
+
|
|
28
|
+
- **`variant`** — color/style: `primary` `secondary` `danger` `info` `success` `warning` `ghost`
|
|
29
|
+
`plain` (`plain` = borderless/transparent, for dense icon controls).
|
|
30
|
+
- **`size`** — `sm` (24px) · `md` (32px, **default**) · `lg` (40px).
|
|
15
31
|
|
|
16
32
|
## Installation
|
|
17
33
|
|
|
18
|
-
|
|
19
|
-
# For local development
|
|
20
|
-
npm install ../path/to/svelte-ui/dist
|
|
34
|
+
Published to the public npm registry:
|
|
21
35
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
npm install
|
|
25
|
-
npm run package
|
|
36
|
+
```bash
|
|
37
|
+
npm install @spaethtech/svelte-ui
|
|
26
38
|
```
|
|
27
39
|
|
|
28
|
-
|
|
40
|
+
Consumers pin a caret range and pick up releases with `npm update`. Note this package is **pre-1.0**,
|
|
41
|
+
so `^0.2.0` allows `0.2.x` (patch fixes) but **not** `0.3.0` — breaking changes bump the MINOR (see
|
|
42
|
+
`CHANGELOG.md`), so a new minor is adopted deliberately by bumping the range, never silently.
|
|
29
43
|
|
|
30
|
-
|
|
44
|
+
During active co-development you can still link a local checkout with a `file:` dependency instead:
|
|
45
|
+
|
|
46
|
+
```jsonc
|
|
47
|
+
// package.json
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@spaethtech/svelte-ui": "file:../svelte-ui"
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Peer dependencies
|
|
31
54
|
|
|
32
55
|
```bash
|
|
33
|
-
npm install svelte@^5
|
|
34
|
-
npm install @fortawesome/fontawesome-svg-core@^6.7.0
|
|
35
|
-
npm install @fortawesome/free-solid-svg-icons@^6.7.0
|
|
56
|
+
npm install svelte@^5 tailwindcss@^4 unplugin-icons@^23 @iconify-json/mdi@^1
|
|
36
57
|
```
|
|
37
58
|
|
|
59
|
+
`unplugin-icons` + `@iconify-json/mdi` are **required**: the packaged components ship bare
|
|
60
|
+
`~icons/mdi/*` imports that the consumer resolves at build time.
|
|
61
|
+
|
|
38
62
|
## Setup
|
|
39
63
|
|
|
40
|
-
|
|
64
|
+
**1. Vite — add the icon resolver** (`vite.config.ts`):
|
|
41
65
|
|
|
42
|
-
|
|
66
|
+
```ts
|
|
67
|
+
import Icons from "unplugin-icons/vite";
|
|
43
68
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
69
|
+
export default defineConfig({
|
|
70
|
+
plugins: [/* sveltekit(), tailwindcss(), */ Icons({ compiler: "svelte" })],
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**2. CSS — import Tailwind + the theme, and scan the dist** (`app.css`, Tailwind v4):
|
|
75
|
+
|
|
76
|
+
```css
|
|
77
|
+
@import "tailwindcss";
|
|
78
|
+
@import "svelte-ui/theme.css";
|
|
79
|
+
|
|
80
|
+
/* Generate the utility classes the components use (path is relative to this file). */
|
|
81
|
+
@source "../node_modules/svelte-ui/dist";
|
|
52
82
|
```
|
|
53
83
|
|
|
54
|
-
|
|
84
|
+
**3. Use components** — render icons into snippets with `~icons/mdi/*`:
|
|
85
|
+
|
|
86
|
+
```svelte
|
|
87
|
+
<script>
|
|
88
|
+
import { Button, Input, Select } from "svelte-ui";
|
|
89
|
+
import IconSearch from "~icons/mdi/magnify";
|
|
90
|
+
</script>
|
|
91
|
+
|
|
92
|
+
<Button text="Save" variant="primary" size="md" />
|
|
93
|
+
|
|
94
|
+
<Input placeholder="Search…">
|
|
95
|
+
{#snippet icon()}<IconSearch />{/snippet}
|
|
96
|
+
</Input>
|
|
97
|
+
|
|
98
|
+
<Select bind:value={selected} options={myOptions} />
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Tooltips
|
|
102
|
+
|
|
103
|
+
Themed tooltips come from the `use:tooltip` action (built on `anchored`) — no global handler.
|
|
104
|
+
`Button` drives it from its `title`, so native `title` tooltips are replaced wherever a Button is
|
|
105
|
+
used; apply the action directly on any other element:
|
|
55
106
|
|
|
56
107
|
```svelte
|
|
57
108
|
<script>
|
|
58
|
-
import { Button,
|
|
59
|
-
import { faSearch, faSave } from '@fortawesome/free-solid-svg-icons';
|
|
109
|
+
import { Button, tooltip } from "svelte-ui";
|
|
60
110
|
</script>
|
|
61
111
|
|
|
62
|
-
<Button
|
|
63
|
-
|
|
64
|
-
<
|
|
112
|
+
<Button title="Delete" variant="danger" />
|
|
113
|
+
<!-- title → themed tooltip + aria-label backstop -->
|
|
114
|
+
<span use:tooltip={{ text: "Any element", side: "top" }}>hover me</span>
|
|
65
115
|
```
|
|
66
116
|
|
|
117
|
+
## Theme
|
|
118
|
+
|
|
119
|
+
`svelte-ui/theme.css` is the single source of truth — semantic color tokens
|
|
120
|
+
(`--ui-color-primary` / `--ui-color-secondary` / `--ui-color-text` / `--ui-color-background` / …) and sizing
|
|
121
|
+
tokens (`--ui-height*`, `--ui-border`), **light + dark only**. Dark follows the system preference;
|
|
122
|
+
force either with the `.theme-light` / `.theme-dark` classes on `<html>` (see `ThemeSelector`).
|
|
123
|
+
|
|
124
|
+
## Composition standard
|
|
125
|
+
|
|
126
|
+
Compound components are built **only** from the library's own core components — never raw
|
|
127
|
+
`<button>` / `<select>` / `<input>`. e.g. `DataTable`'s controls + pagination are
|
|
128
|
+
`<Button variant="plain" size="sm">` and its per-page is `<Select size="sm">`; `Query` is built on
|
|
129
|
+
`<Input>`. See `CLAUDE.md` for the full standard and the shared `variant`/`size` axes.
|
|
130
|
+
|
|
67
131
|
## Development
|
|
68
132
|
|
|
69
133
|
```bash
|
|
70
|
-
npm run dev
|
|
71
|
-
npm run package
|
|
72
|
-
npm run check
|
|
134
|
+
npm run dev # demo server (http://localhost:5173)
|
|
135
|
+
npm run package # build the distributable (svelte-package + publint)
|
|
136
|
+
npm run check # svelte-check
|
|
137
|
+
npm run test # Playwright
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
The demo (every component) runs at `http://localhost:5173`, served in dev behind
|
|
141
|
+
https://svelte-ui.spaethtech.dev. Component reference and examples live in `docs/`.
|
|
142
|
+
|
|
143
|
+
## Releasing
|
|
144
|
+
|
|
145
|
+
Releases are published to npm by CI (`.github/workflows/publish.yml`) when a `v*` tag is pushed —
|
|
146
|
+
authenticated via npm **OIDC trusted publishing** (no `NPM_TOKEN` secret). Build provenance is off
|
|
147
|
+
because npm only supports it for **public** source repos (this repo is private); if it ever goes
|
|
148
|
+
public, re-add `"provenance": true` to `publishConfig`.
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
# 1. Roll CHANGELOG [Unreleased] into the new version heading, commit it.
|
|
152
|
+
# 2. Bump + tag (choose one). Pre-1.0: breaking changes are a MINOR bump.
|
|
153
|
+
npm version minor # or: patch | major → edits package.json, creates the vX.Y.Z tag
|
|
154
|
+
# 3. Push the commit and the tag; the tag push triggers the publish workflow.
|
|
155
|
+
git push && git push --tags
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
CI verifies the tag matches `package.json`, type-checks, then runs `npm publish` (the `prepack`
|
|
159
|
+
script builds `dist/` fresh — never commit `dist/`). Versions are **immutable**: a number can't be
|
|
160
|
+
republished, so don't tag until the CHANGELOG/version are right.
|
|
161
|
+
|
|
162
|
+
### Dev / canary builds
|
|
163
|
+
|
|
164
|
+
Every push to `main` also publishes a prerelease under the **`dev`** dist-tag (same workflow, no git
|
|
165
|
+
tag needed) — version `<next patch>-dev.<run>.<sha>`. It never touches `latest`, and caret ranges
|
|
166
|
+
(`^0.3.0`) skip prereleases, so it's invisible unless a consumer opts in:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
npm install @spaethtech/svelte-ui@dev # newest main build; re-run / npm update to refresh
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Use it to test unreleased work in a consumer before cutting a real release. (Publishing on every push
|
|
173
|
+
does accumulate prerelease versions on the registry — that's expected.)
|
|
174
|
+
|
|
175
|
+
**One-time setup (already done once, listed for reference):** on npmjs.com → the package → Settings →
|
|
176
|
+
add a **Trusted Publisher** for GitHub Actions (org `spaethtech`, repo `svelte-ui`, workflow
|
|
177
|
+
`publish.yml`, "Allow npm publish"). Both the release and dev paths live in that one workflow file, so
|
|
178
|
+
the single trusted-publisher entry covers both.
|
|
179
|
+
|
|
180
|
+
## Known issues
|
|
181
|
+
|
|
182
|
+
### White / outline-less mouse cursor on Windows (`text`, `grab`, `grabbing`, `crosshair`)
|
|
183
|
+
|
|
184
|
+
On some Windows machines — most often with **AMD GPUs** (including AMD APU-presented hybrid
|
|
185
|
+
laptops) — the `text` (I-beam), `grab`/`grabbing`, and `crosshair` cursors render as a **white,
|
|
186
|
+
outline-less mask** that's nearly invisible on light backgrounds, until _any_ repaint (data
|
|
187
|
+
loading, a theme toggle, scrolling) momentarily clears it. The default arrow and pointer/hand
|
|
188
|
+
cursors are unaffected, because the OS draws those natively while Chromium supplies its own bitmap
|
|
189
|
+
for the affected ones.
|
|
190
|
+
|
|
191
|
+
**This is NOT a bug in svelte-ui — or in any web app/framework.** It's a known **Chromium +
|
|
192
|
+
Windows** defect in the GPU **Multiplane Overlay (MPO)** present path: the legacy monochrome cursor
|
|
193
|
+
mask is composited incorrectly on the overlay plane. It reproduces across unrelated sites, in both
|
|
194
|
+
Chrome and Edge, and is independent of `color-scheme`, hydration, or anything in CSS. Tracked
|
|
195
|
+
upstream:
|
|
196
|
+
|
|
197
|
+
- https://issues.chromium.org/issues/40239916
|
|
198
|
+
- https://issues.chromium.org/issues/339818333
|
|
199
|
+
|
|
200
|
+
**Per-machine fix — disable MPO** (keeps hardware acceleration on; only the overlay path changes):
|
|
201
|
+
|
|
202
|
+
```bat
|
|
203
|
+
reg add "HKLM\SOFTWARE\Microsoft\Windows\Dwm" /v OverlayTestMode /t REG_DWORD /d 5 /f
|
|
73
204
|
```
|
|
74
205
|
|
|
75
|
-
|
|
206
|
+
Reboot, then confirm in `chrome://gpu` that multiple-plane overlays are no longer active. Revert
|
|
207
|
+
with `reg delete "HKLM\SOFTWARE\Microsoft\Windows\Dwm" /v OverlayTestMode /f` + reboot. The only
|
|
208
|
+
tradeoff is slightly higher power during video playback. Alternatives: disable "Use graphics
|
|
209
|
+
acceleration" in the browser (broader hammer), set the Windows Text Select pointer to an ARGB
|
|
210
|
+
cursor such as `beam_r.cur` (fixes the I-beam only), or update the GPU driver.
|
|
76
211
|
|
|
77
|
-
|
|
212
|
+
**Do not work around this in component code.** A custom CSS `cursor:` would override every healthy
|
|
213
|
+
user's correct native cursor (size, DPI, accessibility, OS shape) just to patch a client-side GPU
|
|
214
|
+
bug — and you can't feature-detect the broken state to apply it conditionally. The fix belongs on
|
|
215
|
+
the affected client, not in the library.
|
|
78
216
|
|
|
79
217
|
## License
|
|
80
218
|
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import type { Size } from "../types/sizes.js";
|
|
4
|
+
import { responsiveClasses, type Responsive } from "../types/responsive.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A status banner — themed via the semantic colour tokens, so it tracks light/dark + per-tenant
|
|
8
|
+
* accent. Honours the shared {@link Variant} + {@link Size} axes (the status subset). Replaces
|
|
9
|
+
* hand-rolled `[color:var(--ui-color-error)]` divs. Renders nothing when empty (drop-in for `{#if err}`).
|
|
10
|
+
*/
|
|
11
|
+
type AlertVariant =
|
|
12
|
+
"neutral" | "primary" | "secondary" | "success" | "info" | "warning" | "danger" | "ghost";
|
|
13
|
+
interface Props {
|
|
14
|
+
variant?: AlertVariant;
|
|
15
|
+
size?: Responsive<Size>;
|
|
16
|
+
text?: string;
|
|
17
|
+
/** Faint tinted background in addition to the bordered + coloured text. */
|
|
18
|
+
filled?: boolean;
|
|
19
|
+
/** Drop the visible frame while keeping the 1px box: border colour = the alert's own background
|
|
20
|
+
* (the `filled` tint, or transparent when not filled). Defaults `false` — an Alert reads as a
|
|
21
|
+
* bordered status frame. */
|
|
22
|
+
borderless?: boolean;
|
|
23
|
+
/** Optional icon — a Snippet (icon-set-agnostic), like Button/Input. Inherits the alert colour. */
|
|
24
|
+
icon?: Snippet;
|
|
25
|
+
/** Which side the icon sits on. Defaults to leading (left). */
|
|
26
|
+
iconPosition?: "left" | "right";
|
|
27
|
+
class?: string;
|
|
28
|
+
children?: Snippet;
|
|
29
|
+
}
|
|
30
|
+
let {
|
|
31
|
+
variant = "info",
|
|
32
|
+
size = "md",
|
|
33
|
+
text = "",
|
|
34
|
+
filled = false,
|
|
35
|
+
borderless = false,
|
|
36
|
+
icon,
|
|
37
|
+
iconPosition = "left",
|
|
38
|
+
class: cls = "",
|
|
39
|
+
children,
|
|
40
|
+
}: Props = $props();
|
|
41
|
+
|
|
42
|
+
// Variant → semantic token (danger maps to the renamed --ui-color-error).
|
|
43
|
+
const tokenFor: Record<AlertVariant, string> = {
|
|
44
|
+
neutral: "--ui-color-text",
|
|
45
|
+
primary: "--ui-color-primary",
|
|
46
|
+
secondary: "--ui-color-secondary",
|
|
47
|
+
success: "--ui-color-success",
|
|
48
|
+
info: "--ui-color-info",
|
|
49
|
+
warning: "--ui-color-warning",
|
|
50
|
+
danger: "--ui-color-error",
|
|
51
|
+
ghost: "--ui-color-text",
|
|
52
|
+
};
|
|
53
|
+
const sizeClass: Record<Size, string> = {
|
|
54
|
+
sm: "px-2 py-1 text-xs gap-1.5",
|
|
55
|
+
md: "px-3 py-2 text-sm gap-2",
|
|
56
|
+
lg: "px-4 py-3 text-base gap-2",
|
|
57
|
+
};
|
|
58
|
+
// Inline-icon svg size per tier (14 / 16 / 20px), matching Button/Input.
|
|
59
|
+
const iconSizeClass: Record<Size, string> = {
|
|
60
|
+
sm: "[&_svg]:w-3.5 [&_svg]:h-3.5",
|
|
61
|
+
md: "[&_svg]:w-4 [&_svg]:h-4",
|
|
62
|
+
lg: "[&_svg]:w-5 [&_svg]:h-5",
|
|
63
|
+
};
|
|
64
|
+
const token = $derived(tokenFor[variant]);
|
|
65
|
+
// Same tint scale as Badge/Card — border / text / (optional) fill all from the shared strengths.
|
|
66
|
+
// The background is the `filled` tint or transparent; `borderless` paints the border that same
|
|
67
|
+
// colour so the frame disappears without altering the 1px box.
|
|
68
|
+
// `ghost` is the quiet, near-transparent alert (mirrors Badge/Button ghost): no fill, a faint
|
|
69
|
+
// text-tint border, muted text — regardless of `filled`. Every other variant uses the shared tint scale.
|
|
70
|
+
const isGhost = $derived(variant === "ghost");
|
|
71
|
+
const bgColor = $derived(
|
|
72
|
+
filled ? `color-mix(in srgb, var(${token}) var(--ui-tint-soft), transparent)` : "transparent",
|
|
73
|
+
);
|
|
74
|
+
const standardBorder = $derived(
|
|
75
|
+
isGhost
|
|
76
|
+
? "color-mix(in srgb, var(--ui-color-text) 20%, transparent)"
|
|
77
|
+
: `color-mix(in srgb, var(${token}) var(--ui-tint-border), transparent)`,
|
|
78
|
+
);
|
|
79
|
+
const textColor = $derived(
|
|
80
|
+
isGhost
|
|
81
|
+
? "color-mix(in srgb, var(--ui-color-text) 65%, transparent)"
|
|
82
|
+
: `color-mix(in srgb, var(${token}) var(--ui-tint-strong), var(--ui-color-text))`,
|
|
83
|
+
);
|
|
84
|
+
const style = $derived(
|
|
85
|
+
`border-color: ${borderless ? bgColor : standardBorder};` +
|
|
86
|
+
` color: ${textColor};` +
|
|
87
|
+
(filled ? ` background-color: ${bgColor};` : ""),
|
|
88
|
+
);
|
|
89
|
+
const has = $derived(!!children || !!text);
|
|
90
|
+
</script>
|
|
91
|
+
|
|
92
|
+
{#if has}
|
|
93
|
+
<div
|
|
94
|
+
role="alert"
|
|
95
|
+
class="flex items-center [border-radius:var(--ui-border-radius)] border {responsiveClasses(
|
|
96
|
+
size,
|
|
97
|
+
sizeClass,
|
|
98
|
+
)} {cls}"
|
|
99
|
+
{style}
|
|
100
|
+
>
|
|
101
|
+
{#if icon && iconPosition === "left"}
|
|
102
|
+
<span
|
|
103
|
+
class="inline-flex shrink-0 [&_svg]:fill-current {responsiveClasses(size, iconSizeClass)}"
|
|
104
|
+
>{@render icon()}</span
|
|
105
|
+
>
|
|
106
|
+
{/if}
|
|
107
|
+
<span class="min-w-0"
|
|
108
|
+
>{#if children}{@render children()}{:else}{text}{/if}</span
|
|
109
|
+
>
|
|
110
|
+
{#if icon && iconPosition === "right"}
|
|
111
|
+
<span
|
|
112
|
+
class="ml-auto inline-flex shrink-0 [&_svg]:fill-current {responsiveClasses(
|
|
113
|
+
size,
|
|
114
|
+
iconSizeClass,
|
|
115
|
+
)}">{@render icon()}</span
|
|
116
|
+
>
|
|
117
|
+
{/if}
|
|
118
|
+
</div>
|
|
119
|
+
{/if}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
import type { Size } from "../types/sizes.js";
|
|
3
|
+
import { type Responsive } from "../types/responsive.js";
|
|
4
|
+
/**
|
|
5
|
+
* A status banner — themed via the semantic colour tokens, so it tracks light/dark + per-tenant
|
|
6
|
+
* accent. Honours the shared {@link Variant} + {@link Size} axes (the status subset). Replaces
|
|
7
|
+
* hand-rolled `[color:var(--ui-color-error)]` divs. Renders nothing when empty (drop-in for `{#if err}`).
|
|
8
|
+
*/
|
|
9
|
+
type AlertVariant = "neutral" | "primary" | "secondary" | "success" | "info" | "warning" | "danger" | "ghost";
|
|
10
|
+
interface Props {
|
|
11
|
+
variant?: AlertVariant;
|
|
12
|
+
size?: Responsive<Size>;
|
|
13
|
+
text?: string;
|
|
14
|
+
/** Faint tinted background in addition to the bordered + coloured text. */
|
|
15
|
+
filled?: boolean;
|
|
16
|
+
/** Drop the visible frame while keeping the 1px box: border colour = the alert's own background
|
|
17
|
+
* (the `filled` tint, or transparent when not filled). Defaults `false` — an Alert reads as a
|
|
18
|
+
* bordered status frame. */
|
|
19
|
+
borderless?: boolean;
|
|
20
|
+
/** Optional icon — a Snippet (icon-set-agnostic), like Button/Input. Inherits the alert colour. */
|
|
21
|
+
icon?: Snippet;
|
|
22
|
+
/** Which side the icon sits on. Defaults to leading (left). */
|
|
23
|
+
iconPosition?: "left" | "right";
|
|
24
|
+
class?: string;
|
|
25
|
+
children?: Snippet;
|
|
26
|
+
}
|
|
27
|
+
declare const Alert: import("svelte").Component<Props, {}, "">;
|
|
28
|
+
type Alert = ReturnType<typeof Alert>;
|
|
29
|
+
export default Alert;
|
|
@@ -1,80 +1,153 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import Close from "~icons/mdi/close";
|
|
4
|
+
import type { Size } from "../../types/sizes.js";
|
|
5
|
+
import { responsiveClasses, type Responsive } from "../../types/responsive.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A compact status chip — themed via the semantic colour tokens like
|
|
9
|
+
* Alert. Honours the shared {@link Variant} + {@link Size} axes;
|
|
10
|
+
* chips scale in lockstep with the surrounding text.
|
|
11
|
+
*
|
|
12
|
+
* Variant → semantic token. `default` uses `--ui-color-secondary`
|
|
13
|
+
* (the neutral in every theme). The chip fills with a soft tint
|
|
14
|
+
* of the token and borders with the token at full strength; the
|
|
15
|
+
* text stays high-contrast against the background.
|
|
16
|
+
*/
|
|
17
|
+
type BadgeVariant =
|
|
18
|
+
| "neutral"
|
|
19
|
+
| "default"
|
|
20
|
+
| "primary"
|
|
21
|
+
| "secondary"
|
|
22
|
+
| "success"
|
|
23
|
+
| "info"
|
|
24
|
+
| "warning"
|
|
25
|
+
| "danger"
|
|
26
|
+
| "ghost";
|
|
23
27
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
interface Props {
|
|
29
|
+
text?: string;
|
|
30
|
+
variant?: BadgeVariant;
|
|
31
|
+
size?: Responsive<Size>;
|
|
32
|
+
dismissible?: boolean;
|
|
33
|
+
rounded?: boolean;
|
|
34
|
+
/** Hide the border WITHOUT changing the box: the border stays 1px (so height/width are
|
|
35
|
+
* identical to a bordered chip) but its colour is set to the chip's own background, so the
|
|
36
|
+
* visible ring disappears. Set `borderless={false}` for the classic ringed chip — the ring is
|
|
37
|
+
* `--ui-tint-border` (30%) over the `--ui-tint-soft` (12%) fill. Defaults `true` (fill only). */
|
|
38
|
+
borderless?: boolean;
|
|
39
|
+
/** When set, the badge renders as an `<a href>` (clickable, `cursor-pointer`) instead of a
|
|
40
|
+
* static `<span>`. Mirrors Button's `href`. */
|
|
41
|
+
href?: string;
|
|
42
|
+
ondismiss?: () => void;
|
|
43
|
+
children?: Snippet;
|
|
44
|
+
}
|
|
32
45
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
46
|
+
let {
|
|
47
|
+
text = "",
|
|
48
|
+
variant = "default",
|
|
49
|
+
size = "md",
|
|
50
|
+
dismissible = false,
|
|
51
|
+
rounded = true,
|
|
52
|
+
borderless = true,
|
|
53
|
+
href,
|
|
54
|
+
ondismiss,
|
|
55
|
+
children,
|
|
56
|
+
}: Props = $props();
|
|
36
57
|
|
|
37
|
-
|
|
38
|
-
|
|
58
|
+
// Every variant maps to a library-wide colour token — no Tailwind
|
|
59
|
+
// palette here, no hardcoded hex. `default` uses --ui-color-secondary
|
|
60
|
+
// (the neutral) so themes control it via the shared bridge.
|
|
61
|
+
const tokenFor: Record<BadgeVariant, string> = {
|
|
62
|
+
neutral: "--ui-color-text",
|
|
63
|
+
default: "--ui-color-secondary",
|
|
64
|
+
primary: "--ui-color-primary",
|
|
65
|
+
secondary: "--ui-color-secondary",
|
|
66
|
+
success: "--ui-color-success",
|
|
67
|
+
info: "--ui-color-info",
|
|
68
|
+
warning: "--ui-color-warning",
|
|
69
|
+
danger: "--ui-color-error",
|
|
70
|
+
ghost: "--ui-color-text",
|
|
71
|
+
};
|
|
39
72
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
73
|
+
// Sizing follows the Record<Size, string> pattern (Card, Alert,
|
|
74
|
+
// Checkbox). No explicit height — padding + line-height decide
|
|
75
|
+
// the chip's vertical footprint so it aligns to inherited text.
|
|
76
|
+
const sizeClass: Record<Size, string> = {
|
|
77
|
+
sm: "text-xs px-1.5 py-0.5",
|
|
78
|
+
md: "text-sm px-2 py-0.5",
|
|
79
|
+
lg: "text-base px-2.5 py-1",
|
|
80
|
+
};
|
|
81
|
+
const iconSizeClass: Record<Size, string> = {
|
|
82
|
+
sm: "w-3 h-3",
|
|
83
|
+
md: "w-3.5 h-3.5",
|
|
84
|
+
lg: "w-4 h-4",
|
|
85
|
+
};
|
|
46
86
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
87
|
+
const token = $derived(tokenFor[variant]);
|
|
88
|
+
// `ghost` is the neutral, near-transparent chip (mirrors Button's ghost): no fill, faint text-tint
|
|
89
|
+
// ring, muted text. Every other variant fills with a soft tint of its token and rings with the
|
|
90
|
+
// standard tint-border. The fill + ring are computed separately so `borderless` can collapse the
|
|
91
|
+
// ring onto the fill (keeping the 1px border — the box is unchanged — but hiding it).
|
|
92
|
+
const bgColor = $derived(
|
|
93
|
+
variant === "ghost"
|
|
94
|
+
? "transparent"
|
|
95
|
+
: `color-mix(in srgb, var(${token}) var(--ui-tint-soft), transparent)`,
|
|
96
|
+
);
|
|
97
|
+
const standardBorder = $derived(
|
|
98
|
+
variant === "ghost"
|
|
99
|
+
? "color-mix(in srgb, var(--ui-color-text) 20%, transparent)"
|
|
100
|
+
: `color-mix(in srgb, var(${token}) var(--ui-tint-border), transparent)`,
|
|
101
|
+
);
|
|
102
|
+
const textColor = $derived(
|
|
103
|
+
variant === "ghost"
|
|
104
|
+
? "color-mix(in srgb, var(--ui-color-text) 65%, transparent)"
|
|
105
|
+
: `color-mix(in srgb, var(${token}) var(--ui-tint-strong), var(--ui-color-text))`,
|
|
106
|
+
);
|
|
107
|
+
const style = $derived(
|
|
108
|
+
`background-color: ${bgColor};` +
|
|
109
|
+
` border-color: ${borderless ? bgColor : standardBorder};` +
|
|
110
|
+
` color: ${textColor};`,
|
|
111
|
+
);
|
|
52
112
|
|
|
53
|
-
|
|
54
|
-
|
|
113
|
+
function handleDismiss() {
|
|
114
|
+
ondismiss?.();
|
|
115
|
+
}
|
|
116
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
117
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
118
|
+
event.preventDefault();
|
|
119
|
+
handleDismiss();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const hasContent = $derived(!!children || !!text);
|
|
55
124
|
</script>
|
|
56
125
|
|
|
57
|
-
{#if
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
<span class={iconAdjustment}><Icon {icon} /></span>
|
|
71
|
-
{/if}
|
|
72
|
-
{#if text}
|
|
73
|
-
<span class={textAdjustment}>{text}</span>
|
|
74
|
-
{/if}
|
|
75
|
-
{#if iconPosition === "right" && icon}
|
|
76
|
-
<span class={iconAdjustment}><Icon {icon} /></span>
|
|
77
|
-
{/if}
|
|
78
|
-
</span>
|
|
126
|
+
{#if hasContent}
|
|
127
|
+
<svelte:element
|
|
128
|
+
this={href ? "a" : "span"}
|
|
129
|
+
{href}
|
|
130
|
+
class="inline-flex items-center gap-1 border no-underline select-none {href
|
|
131
|
+
? 'cursor-pointer hover:brightness-95'
|
|
132
|
+
: 'cursor-default'} {responsiveClasses(size, sizeClass)} {rounded
|
|
133
|
+
? 'rounded-full'
|
|
134
|
+
: 'rounded'} transition-[filter,background-color,border-color] duration-150"
|
|
135
|
+
{style}
|
|
136
|
+
>
|
|
137
|
+
<span class="truncate">
|
|
138
|
+
{#if children}{@render children()}{:else}{text}{/if}
|
|
79
139
|
</span>
|
|
140
|
+
|
|
141
|
+
{#if dismissible}
|
|
142
|
+
<button
|
|
143
|
+
type="button"
|
|
144
|
+
onclick={handleDismiss}
|
|
145
|
+
onkeydown={handleKeydown}
|
|
146
|
+
aria-label="Dismiss"
|
|
147
|
+
class="inline-flex items-center justify-center rounded-full hover:[background-color:color-mix(in_srgb,currentColor_15%,transparent)] focus:outline-none transition-colors duration-150"
|
|
148
|
+
>
|
|
149
|
+
<Close class={responsiveClasses(size, iconSizeClass)} />
|
|
150
|
+
</button>
|
|
151
|
+
{/if}
|
|
152
|
+
</svelte:element>
|
|
80
153
|
{/if}
|