@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.
Files changed (208) hide show
  1. package/README.md +180 -42
  2. package/dist/components/Alert.svelte +119 -0
  3. package/dist/components/Alert.svelte.d.ts +29 -0
  4. package/dist/components/Badge/Badge.svelte +142 -69
  5. package/dist/components/Badge/Badge.svelte.d.ts +26 -6
  6. package/dist/components/Badge/index.d.ts +1 -1
  7. package/dist/components/Badge/index.js +1 -1
  8. package/dist/components/Banner.svelte +133 -0
  9. package/dist/components/Banner.svelte.d.ts +31 -0
  10. package/dist/components/Button.svelte +240 -0
  11. package/dist/components/Button.svelte.d.ts +33 -0
  12. package/dist/components/ButtonDropdown.svelte +145 -0
  13. package/dist/components/ButtonDropdown.svelte.d.ts +31 -0
  14. package/dist/components/Calendar.svelte +259 -0
  15. package/dist/components/Calendar.svelte.d.ts +27 -0
  16. package/dist/components/Card.svelte +78 -0
  17. package/dist/components/Card.svelte.d.ts +17 -0
  18. package/dist/components/CardBody.svelte +41 -0
  19. package/dist/components/CardBody.svelte.d.ts +17 -0
  20. package/dist/components/CardFooter.svelte +49 -0
  21. package/dist/components/CardFooter.svelte.d.ts +16 -0
  22. package/dist/components/CardHeader.svelte +49 -0
  23. package/dist/components/CardHeader.svelte.d.ts +16 -0
  24. package/dist/components/Checkbox.svelte +131 -0
  25. package/dist/components/Checkbox.svelte.d.ts +16 -0
  26. package/dist/components/DataTable.svelte +518 -0
  27. package/dist/components/DataTable.svelte.d.ts +74 -0
  28. package/dist/components/DatePicker.svelte +312 -0
  29. package/dist/components/DatePicker.svelte.d.ts +32 -0
  30. package/dist/components/DateTimeInput.svelte +93 -0
  31. package/dist/components/DateTimeInput.svelte.d.ts +20 -0
  32. package/dist/components/Dialog.svelte +132 -0
  33. package/dist/components/Dialog.svelte.d.ts +24 -0
  34. package/dist/components/{EmailInput/EmailInput.svelte → EmailInput.svelte} +17 -8
  35. package/dist/components/EmailInput.svelte.d.ts +21 -0
  36. package/dist/components/Input.svelte +369 -0
  37. package/dist/components/Input.svelte.d.ts +35 -0
  38. package/dist/components/{List/List.svelte → List.svelte} +194 -133
  39. package/dist/components/{List/List.svelte.d.ts → List.svelte.d.ts} +9 -1
  40. package/dist/components/Menu.svelte +117 -0
  41. package/dist/components/Menu.svelte.d.ts +20 -0
  42. package/dist/components/NotesEditor.svelte +127 -0
  43. package/dist/components/NotesEditor.svelte.d.ts +17 -0
  44. package/dist/components/{NumberInput/NumberInput.svelte → NumberInput.svelte} +146 -105
  45. package/dist/components/{NumberInput/NumberInput.svelte.d.ts → NumberInput.svelte.d.ts} +11 -4
  46. package/dist/components/PasswordInput.svelte +52 -0
  47. package/dist/components/PasswordInput.svelte.d.ts +20 -0
  48. package/dist/components/Popup.svelte +140 -0
  49. package/dist/components/Popup.svelte.d.ts +31 -0
  50. package/dist/components/Query.svelte +284 -0
  51. package/dist/components/Query.svelte.d.ts +15 -0
  52. package/dist/components/{Rating/Rating.svelte → Rating.svelte} +19 -10
  53. package/dist/components/Rating.svelte.d.ts +13 -0
  54. package/dist/components/{SearchInput/SearchInput.svelte → SearchInput.svelte} +19 -8
  55. package/dist/components/{SearchInput/SearchInput.svelte.d.ts → SearchInput.svelte.d.ts} +9 -2
  56. package/dist/components/{Select/Select.svelte → Select.svelte} +48 -76
  57. package/dist/components/{Select/Select.svelte.d.ts → Select.svelte.d.ts} +11 -1
  58. package/dist/components/SideBarMenu/SideBarMenu.svelte +724 -0
  59. package/dist/components/SideBarMenu/SideBarMenu.svelte.d.ts +109 -0
  60. package/dist/components/SideBarMenu/index.d.ts +2 -0
  61. package/dist/components/SideBarMenu/index.js +1 -0
  62. package/dist/components/TabStrip/TabStrip.svelte +384 -0
  63. package/dist/components/TabStrip/TabStrip.svelte.d.ts +50 -0
  64. package/dist/components/TabStrip/index.d.ts +3 -0
  65. package/dist/components/TabStrip/index.js +2 -0
  66. package/dist/components/{TextArea/TextArea.svelte → TextArea.svelte} +143 -112
  67. package/dist/components/TextArea.svelte.d.ts +38 -0
  68. package/dist/components/ThemeSelector.svelte +81 -0
  69. package/dist/components/ThemeSelector.svelte.d.ts +10 -0
  70. package/dist/components/TimePicker.svelte +148 -0
  71. package/dist/components/TimePicker.svelte.d.ts +28 -0
  72. package/dist/components/TimeRangeInput.svelte +203 -0
  73. package/dist/components/TimeRangeInput.svelte.d.ts +29 -0
  74. package/dist/components/TimeSpinner.svelte +202 -0
  75. package/dist/components/TimeSpinner.svelte.d.ts +26 -0
  76. package/dist/components/Toast/Toaster.svelte +51 -0
  77. package/dist/components/Toast/Toaster.svelte.d.ts +12 -0
  78. package/dist/components/Toast/toast.svelte.d.ts +29 -0
  79. package/dist/components/Toast/toast.svelte.js +27 -0
  80. package/dist/components/Toggle.svelte +93 -0
  81. package/dist/components/Toggle.svelte.d.ts +15 -0
  82. package/dist/data/dataset.d.ts +42 -0
  83. package/dist/data/dataset.js +3 -0
  84. package/dist/data/index.d.ts +3 -0
  85. package/dist/data/index.js +3 -0
  86. package/dist/data/query/ast.d.ts +62 -0
  87. package/dist/data/query/ast.js +12 -0
  88. package/dist/data/query/index.d.ts +3 -0
  89. package/dist/data/query/index.js +3 -0
  90. package/dist/data/query/lexer.d.ts +33 -0
  91. package/dist/data/query/lexer.js +225 -0
  92. package/dist/data/query/parser.d.ts +11 -0
  93. package/dist/data/query/parser.js +252 -0
  94. package/dist/data/table/grid.svelte.d.ts +57 -0
  95. package/dist/data/table/grid.svelte.js +139 -0
  96. package/dist/data/table/index.d.ts +2 -0
  97. package/dist/data/table/index.js +2 -0
  98. package/dist/data/table/types.d.ts +79 -0
  99. package/dist/index.d.ts +48 -22
  100. package/dist/index.js +45 -21
  101. package/dist/positioning/anchored.d.ts +61 -0
  102. package/dist/positioning/anchored.js +122 -0
  103. package/dist/positioning/tooltip.d.ts +41 -0
  104. package/dist/positioning/tooltip.js +147 -0
  105. package/dist/theme.css +205 -0
  106. package/dist/types/breakpoints.d.ts +24 -0
  107. package/dist/types/breakpoints.js +13 -0
  108. package/dist/types/responsive.d.ts +32 -0
  109. package/dist/types/responsive.js +54 -0
  110. package/dist/types/sizes.d.ts +10 -3
  111. package/dist/types/time.d.ts +19 -0
  112. package/dist/types/time.js +10 -0
  113. package/dist/types/variants.d.ts +8 -1
  114. package/dist/types/variants.js +16 -1
  115. package/package.json +93 -63
  116. package/dist/components/Button/Button.svelte +0 -172
  117. package/dist/components/Button/Button.svelte.d.ts +0 -32
  118. package/dist/components/Button/index.d.ts +0 -1
  119. package/dist/components/Button/index.js +0 -1
  120. package/dist/components/Dialog/Dialog.svelte +0 -101
  121. package/dist/components/Dialog/Dialog.svelte.d.ts +0 -18
  122. package/dist/components/Dialog/index.d.ts +0 -1
  123. package/dist/components/Dialog/index.js +0 -1
  124. package/dist/components/ElementManager/ElementManager.svelte +0 -397
  125. package/dist/components/ElementManager/ElementManager.svelte.d.ts +0 -43
  126. package/dist/components/ElementManager/index.d.ts +0 -1
  127. package/dist/components/ElementManager/index.js +0 -1
  128. package/dist/components/EmailInput/EmailInput.svelte.d.ts +0 -14
  129. package/dist/components/EmailInput/index.d.ts +0 -1
  130. package/dist/components/EmailInput/index.js +0 -1
  131. package/dist/components/Icon/Icon.svelte +0 -74
  132. package/dist/components/Icon/Icon.svelte.d.ts +0 -13
  133. package/dist/components/Icon/index.d.ts +0 -1
  134. package/dist/components/Icon/index.js +0 -1
  135. package/dist/components/Input/Input.svelte +0 -268
  136. package/dist/components/Input/Input.svelte.d.ts +0 -18
  137. package/dist/components/Input/index.d.ts +0 -1
  138. package/dist/components/Input/index.js +0 -1
  139. package/dist/components/List/index.d.ts +0 -1
  140. package/dist/components/List/index.js +0 -1
  141. package/dist/components/ListItem/ListItem.svelte +0 -175
  142. package/dist/components/ListItem/ListItem.svelte.d.ts +0 -24
  143. package/dist/components/ListItem/index.d.ts +0 -2
  144. package/dist/components/ListItem/index.js +0 -2
  145. package/dist/components/ListView/ListView.svelte +0 -463
  146. package/dist/components/ListView/ListView.svelte.d.ts +0 -37
  147. package/dist/components/ListView/index.d.ts +0 -2
  148. package/dist/components/ListView/index.js +0 -2
  149. package/dist/components/NodeGraph/BaseNode.d.ts +0 -57
  150. package/dist/components/NodeGraph/BaseNode.js +0 -30
  151. package/dist/components/NodeGraph/Edge.svelte +0 -60
  152. package/dist/components/NodeGraph/Edge.svelte.d.ts +0 -16
  153. package/dist/components/NodeGraph/ExpressionEvaluator.d.ts +0 -82
  154. package/dist/components/NodeGraph/ExpressionEvaluator.js +0 -152
  155. package/dist/components/NodeGraph/Node.svelte +0 -100
  156. package/dist/components/NodeGraph/Node.svelte.d.ts +0 -10
  157. package/dist/components/NodeGraph/NodeGraph.svelte +0 -52
  158. package/dist/components/NodeGraph/NodeGraph.svelte.d.ts +0 -14
  159. package/dist/components/NodeGraph/NodeInstance.svelte.d.ts +0 -80
  160. package/dist/components/NodeGraph/NodeInstance.svelte.js +0 -241
  161. package/dist/components/NodeGraph/NodePort.svelte +0 -75
  162. package/dist/components/NodeGraph/NodePort.svelte.d.ts +0 -11
  163. package/dist/components/NodeGraph/decorators.d.ts +0 -81
  164. package/dist/components/NodeGraph/decorators.js +0 -148
  165. package/dist/components/NodeGraph/index.d.ts +0 -9
  166. package/dist/components/NodeGraph/index.js +0 -9
  167. package/dist/components/NodeGraph/types.d.ts +0 -94
  168. package/dist/components/NodeGraph/types.js +0 -33
  169. package/dist/components/NotesEditor/NotesEditor.svelte +0 -203
  170. package/dist/components/NotesEditor/NotesEditor.svelte.d.ts +0 -9
  171. package/dist/components/NotesEditor/index.d.ts +0 -1
  172. package/dist/components/NotesEditor/index.js +0 -1
  173. package/dist/components/NumberInput/index.d.ts +0 -1
  174. package/dist/components/NumberInput/index.js +0 -1
  175. package/dist/components/PasswordInput/PasswordInput.svelte +0 -41
  176. package/dist/components/PasswordInput/PasswordInput.svelte.d.ts +0 -13
  177. package/dist/components/PasswordInput/index.d.ts +0 -1
  178. package/dist/components/PasswordInput/index.js +0 -1
  179. package/dist/components/Popup/index.d.ts +0 -1
  180. package/dist/components/Popup/index.js +0 -1
  181. package/dist/components/Rating/Rating.svelte.d.ts +0 -7
  182. package/dist/components/Rating/index.d.ts +0 -1
  183. package/dist/components/Rating/index.js +0 -1
  184. package/dist/components/ScrollView/ScrollView.svelte +0 -285
  185. package/dist/components/ScrollView/ScrollView.svelte.d.ts +0 -19
  186. package/dist/components/ScrollView/index.d.ts +0 -1
  187. package/dist/components/ScrollView/index.js +0 -1
  188. package/dist/components/SearchInput/index.d.ts +0 -1
  189. package/dist/components/SearchInput/index.js +0 -1
  190. package/dist/components/Select/index.d.ts +0 -1
  191. package/dist/components/Select/index.js +0 -1
  192. package/dist/components/TextArea/TextArea.svelte.d.ts +0 -19
  193. package/dist/components/TextArea/index.d.ts +0 -1
  194. package/dist/components/TextArea/index.js +0 -1
  195. package/dist/components/ThemeSelector/ThemeSelector.svelte +0 -64
  196. package/dist/components/ThemeSelector/ThemeSelector.svelte.d.ts +0 -3
  197. package/dist/components/ThemeSelector/index.d.ts +0 -1
  198. package/dist/components/ThemeSelector/index.js +0 -1
  199. package/dist/components/TooltipHandler/TooltipHandler.svelte +0 -593
  200. package/dist/components/TooltipHandler/TooltipHandler.svelte.d.ts +0 -10
  201. package/dist/components/TooltipHandler/index.d.ts +0 -1
  202. package/dist/components/TooltipHandler/index.js +0 -1
  203. package/dist/styles/sizes.d.ts +0 -78
  204. package/dist/styles/sizes.js +0 -120
  205. package/dist/styles/variants.d.ts +0 -106
  206. package/dist/styles/variants.js +0 -194
  207. package/dist/types/ManagerTypes.d.ts +0 -42
  208. /package/dist/{types/ManagerTypes.js → data/table/types.js} +0 -0
package/README.md CHANGED
@@ -1,80 +1,218 @@
1
- # Svelte UI Component Library
1
+ # svelte-ui
2
2
 
3
- A collection of reusable Svelte 5 UI components with Tailwind CSS styling and FontAwesome icon support.
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
- See JUNIE.md for how Junie (our autonomous programmer) handles repository rules files like CLAUDE.md and .cursorrules.
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
- - **Button** - Flexible button/anchor with variants and icon support
10
- - **Dialog** - Modal dialog with backdrop handling
11
- - **Input** - Enhanced text input with icons and validation
12
- - **Select** - Custom dropdown with keyboard navigation
13
- - **Rating** - Interactive star rating component
14
- - **NotesEditor** - Auto-sizing textarea component
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
- ```bash
19
- # For local development
20
- npm install ../path/to/svelte-ui/dist
34
+ Published to the public npm registry:
21
35
 
22
- # Or build from source
23
- cd svelte-ui
24
- npm install
25
- npm run package
36
+ ```bash
37
+ npm install @spaethtech/svelte-ui
26
38
  ```
27
39
 
28
- ## Prerequisites
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
- Your project must have these peer dependencies installed:
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.0.0 tailwindcss@^4.0.0
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
- ### 1. Tailwind Configuration
64
+ **1. Vite — add the icon resolver** (`vite.config.ts`):
41
65
 
42
- Add the component paths to your `tailwind.config.js`:
66
+ ```ts
67
+ import Icons from "unplugin-icons/vite";
43
68
 
44
- ```js
45
- export default {
46
- content: [
47
- './src/**/*.{html,js,svelte,ts}',
48
- './node_modules/svelte-ui/**/*.{html,js,svelte,ts}'
49
- ],
50
- // ... rest of config
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
- ### 2. Import Components
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, Dialog, Input, Select, Rating } from 'svelte-ui';
59
- import { faSearch, faSave } from '@fortawesome/free-solid-svg-icons';
109
+ import { Button, tooltip } from "svelte-ui";
60
110
  </script>
61
111
 
62
- <Button text="Click me" variant="primary" />
63
- <Input bind:value={searchQuery} icon={faSearch} placeholder="Search..." />
64
- <Select options={myOptions} bind:value={selected} />
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 # Start demo server
71
- npm run package # Build library
72
- npm run check # TypeScript validation
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
- ## Demo
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
- Run `npm run dev` to see all components in action at http://localhost:5173
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
- import Icon from "../Icon/Icon.svelte";
3
- import type { IconDefinition } from "@fortawesome/free-solid-svg-icons";
4
- import type { Variant } from "../../types/variants.js";
5
- import type { Size } from "../../types/sizes.js";
6
- import {
7
- getVariantClasses,
8
- getVariantStyles,
9
- } from "../../styles/variants.js";
10
- import {
11
- badgeSizeClasses,
12
- componentHeights,
13
- textSizes,
14
- } from "../../styles/sizes.js";
15
- interface Props {
16
- icon?: IconDefinition;
17
- text?: string;
18
- variant?: Variant;
19
- size?: Size;
20
- iconPosition?: "left" | "right";
21
- rounded?: boolean;
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
- let {
25
- icon,
26
- text = "",
27
- variant = "primary",
28
- size = "md",
29
- iconPosition = "left",
30
- rounded = true,
31
- }: Props = $props();
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
- // Get text and icon adjustments from variant styles
34
- const textAdjustment = $derived(getVariantStyles(variant).textAdjustment);
35
- const iconAdjustment = $derived(getVariantStyles(variant).iconAdjustment);
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
- // Icon-only badges should be square (w=h)
38
- const isIconOnly = $derived(icon && !text);
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
- // Map sizes to explicit widths (matching heights)
41
- const squareWidths = {
42
- sm: "w-7", // matches h-7
43
- md: "w-8", // matches h-8
44
- lg: "w-9", // matches h-9
45
- } as const;
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
- const sizeClasses = $derived(
48
- isIconOnly
49
- ? `${componentHeights[size]} ${textSizes[size]} ${squareWidths[size]}`
50
- : badgeSizeClasses[size],
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
- // Ghost variant uses outline (doesn't affect layout) instead of border
54
- // Other variants don't need borders since they have solid backgrounds
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 text || icon}
58
- <span
59
- class="inline-flex items-center justify-center gap-2 select-none {sizeClasses} {getVariantClasses(
60
- variant,
61
- {
62
- includeBorder: true,
63
- includeIcon: false,
64
- includeHover: false,
65
- },
66
- )} {rounded ? 'rounded-full' : 'rounded'}"
67
- >
68
- <span class="flex items-center justify-center gap-2">
69
- {#if iconPosition === "left" && icon}
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}