contain-css-svelte 1.1.8 → 1.1.10
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/LICENSE +21 -0
- package/README.md +110 -30
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/layout/GridLayout.svelte +100 -5
- package/dist/layout/GridRow.svelte +23 -0
- package/dist/layout/GridRow.svelte.d.ts +24 -0
- package/dist/layout/Tile.svelte +35 -9
- package/dist/layout/Tile.svelte.d.ts +13 -1
- package/dist/overlays/Tooltip.svelte +44 -14
- package/dist/review/ReviewAppPage.svelte +4 -3
- package/dist/review/ReviewRootIndex.svelte +3 -2
- package/dist/review/ReviewThemeAppIndex.svelte +3 -2
- package/dist/review/ReviewThemeIndex.svelte +4 -3
- package/dist/review/ReviewThemeSwitcher.svelte +3 -2
- package/package.json +2 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Tom Hinkle
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,58 +1,138 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ContainCSS for Svelte
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**A small Svelte component library that gets out of your way.** Every component is
|
|
4
|
+
styled through CSS custom properties, sized by container queries, and built on
|
|
5
|
+
native HTML elements — so you can theme it from the outside, drop it into any
|
|
6
|
+
layout, and keep the accessibility you get for free from `<dialog>`, `<details>`,
|
|
7
|
+
and `popover`.
|
|
4
8
|
|
|
5
|
-
|
|
9
|
+
### 📖 [Browse the docs & live demos →](https://thinkle.github.io/svelte-contain-css/)
|
|
6
10
|
|
|
7
|
-
|
|
11
|
+
[](https://www.npmjs.com/package/contain-css-svelte)
|
|
8
12
|
|
|
9
|
-
|
|
13
|
+
---
|
|
10
14
|
|
|
11
|
-
|
|
12
|
-
# create a new project in the current directory
|
|
13
|
-
npm create svelte@latest
|
|
15
|
+
## Which version do I want?
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
| | Branch | npm version | Svelte | Docs |
|
|
18
|
+
| --- | --- | --- | --- | --- |
|
|
19
|
+
| **👉 Start here** | [`svelte5`](https://github.com/thinkle/svelte-contain-css/tree/svelte5) | `1.x` | Svelte 5 | [svelte5 docs](https://thinkle.github.io/svelte-contain-css/svelte5/) |
|
|
20
|
+
| Legacy | [`main`](https://github.com/thinkle/svelte-contain-css/tree/main) | `0.0.x` | Svelte 3 & 4 | [legacy docs](https://thinkle.github.io/svelte-contain-css/legacy/) |
|
|
18
21
|
|
|
19
|
-
|
|
22
|
+
**`svelte5` is the active development branch** and where all new work lands.
|
|
23
|
+
`main` is frozen for existing Svelte 4 and earlier projects; it gets fixes
|
|
24
|
+
backported when they're worth it, but no new features. If you're starting
|
|
25
|
+
something new, use `svelte5` / `1.x`.
|
|
20
26
|
|
|
21
|
-
|
|
27
|
+
## Install
|
|
22
28
|
|
|
23
29
|
```bash
|
|
24
|
-
npm
|
|
30
|
+
npm install contain-css-svelte
|
|
31
|
+
```
|
|
25
32
|
|
|
26
|
-
|
|
27
|
-
|
|
33
|
+
Import the default variables once, at the root of your app:
|
|
34
|
+
|
|
35
|
+
```svelte
|
|
36
|
+
<!-- +layout.svelte or App.svelte -->
|
|
37
|
+
<script>
|
|
38
|
+
import "contain-css-svelte/vars/defaults.css";
|
|
39
|
+
</script>
|
|
28
40
|
```
|
|
29
41
|
|
|
30
|
-
|
|
42
|
+
Then use components anywhere:
|
|
43
|
+
|
|
44
|
+
```svelte
|
|
45
|
+
<script>
|
|
46
|
+
import { Container, Button, Card } from "contain-css-svelte";
|
|
47
|
+
</script>
|
|
31
48
|
|
|
32
|
-
|
|
49
|
+
<Container>
|
|
50
|
+
<Card>
|
|
51
|
+
<h2>Hello</h2>
|
|
52
|
+
<Button primary>Do the thing</Button>
|
|
53
|
+
</Card>
|
|
54
|
+
</Container>
|
|
55
|
+
```
|
|
33
56
|
|
|
34
|
-
|
|
57
|
+
## The idea
|
|
35
58
|
|
|
36
|
-
|
|
37
|
-
|
|
59
|
+
**1. Style props are CSS variables, not inline styles.**
|
|
60
|
+
|
|
61
|
+
```svelte
|
|
62
|
+
<Button bg="green" fg="white" height="64px">Wow</Button>
|
|
63
|
+
<!-- becomes style="--button-bg: green; --button-fg: white; --button-height: 64px" -->
|
|
38
64
|
```
|
|
39
65
|
|
|
40
|
-
|
|
66
|
+
Because they're variables rather than hard-coded styles, the cascade still
|
|
67
|
+
works — which means you can theme a whole subtree from above:
|
|
41
68
|
|
|
42
|
-
```
|
|
43
|
-
|
|
69
|
+
```svelte
|
|
70
|
+
<div style="--primary-bg: darkblue; --primary-fg: white; --border-radius: 0">
|
|
71
|
+
<Button primary>Themed</Button>
|
|
72
|
+
<Button primary>Also themed</Button>
|
|
73
|
+
</div>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
...or in plain CSS, with no build step and no wrapper components:
|
|
77
|
+
|
|
78
|
+
```css
|
|
79
|
+
.my-app {
|
|
80
|
+
--bg: #1a1a1a;
|
|
81
|
+
--fg: #fff;
|
|
82
|
+
--primary-bg: #4a90d9;
|
|
83
|
+
--font-family: "Inter", sans-serif;
|
|
84
|
+
}
|
|
44
85
|
```
|
|
45
86
|
|
|
46
|
-
|
|
87
|
+
**2. Components respond to their container, not the viewport.** A `Card` in a
|
|
88
|
+
narrow sidebar lays itself out like a narrow card, whether or not the window is
|
|
89
|
+
wide. No breakpoint bookkeeping.
|
|
90
|
+
|
|
91
|
+
**3. Native elements underneath.** `Dialog` is a real `<dialog>`, `Accordion` is
|
|
92
|
+
`<details>`, `Tooltip` uses the `popover` API. Focus trapping, escape-to-close,
|
|
93
|
+
and screen-reader semantics come from the platform.
|
|
94
|
+
|
|
95
|
+
## What's in the box
|
|
96
|
+
|
|
97
|
+
- **Controls** — `Button`, `ButtonLink`, `MiniButton`, `Input`, `Select`,
|
|
98
|
+
`Checkbox`, `RadioButton`, `Toggle`, `Slider`
|
|
99
|
+
- **Layout** — `Container`, `Page`, `Row`/`Column`/`Columns`, `Stack`, `Inline`,
|
|
100
|
+
`GridLayout`, `SplitPane`, `Sidebar`, `Bar`, `TabBar`, `Table`, `Tile`,
|
|
101
|
+
`DataList`, `Hero`, `Accordion`, `Form`/`FormItem`/`Fieldset`
|
|
102
|
+
- **Overlays** — `Dialog`, `Tooltip`, `DropdownMenu`
|
|
103
|
+
- **Misc** — `Card`, `Code`, `Progress`, `Tag`, `TextLayout`, `ResponsiveText`
|
|
47
104
|
|
|
48
|
-
|
|
105
|
+
Plus a set of ready-made themes (`light`, `dark`, `material`, `bootstrap`,
|
|
106
|
+
`retro`, `purple`, `forest`, `canyon`, and several typography-only themes) under
|
|
107
|
+
`contain-css-svelte/themes/`.
|
|
49
108
|
|
|
50
|
-
|
|
109
|
+
Every component and every CSS variable is documented with a live, editable demo
|
|
110
|
+
in [the docs site](https://thinkle.github.io/svelte-contain-css/svelte5/).
|
|
51
111
|
|
|
52
|
-
|
|
112
|
+
## Using this with an AI assistant
|
|
53
113
|
|
|
54
|
-
|
|
114
|
+
[`AGENTS-EXTERNAL.md`](AGENTS-EXTERNAL.md) is a complete reference written for
|
|
115
|
+
coding agents — component list, prop-to-variable conventions, and the full CSS
|
|
116
|
+
variable table. Point your assistant at it (or drop it in your project) and it
|
|
117
|
+
will write idiomatic ContainCSS code.
|
|
118
|
+
|
|
119
|
+
## Developing the library
|
|
55
120
|
|
|
56
121
|
```bash
|
|
57
|
-
npm
|
|
122
|
+
npm install
|
|
123
|
+
npm run dev # docs site + live demos at localhost:5173
|
|
124
|
+
npm run check # svelte-check
|
|
125
|
+
npm run test:e2e # playwright
|
|
126
|
+
npm run package # build dist/
|
|
58
127
|
```
|
|
128
|
+
|
|
129
|
+
The docs site lives in `src/routes`; the library itself is `src/lib`. Pushing to
|
|
130
|
+
`svelte5` or `main` rebuilds and publishes both doc versions to GitHub Pages via
|
|
131
|
+
[`.github/workflows/deploy-docs.yml`](.github/workflows/deploy-docs.yml).
|
|
132
|
+
|
|
133
|
+
See [AGENTS-INTERNAL.md](AGENTS-INTERNAL.md) for library-development conventions
|
|
134
|
+
and [BACKPORT.md](BACKPORT.md) for moving fixes to the legacy branch.
|
|
135
|
+
|
|
136
|
+
## License
|
|
137
|
+
|
|
138
|
+
MIT © [Tom Hinkle](https://tomhinkle.net)
|
package/dist/index.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ import Form from "./layout/Form.svelte";
|
|
|
23
23
|
import FormItem from "./layout/FormItem.svelte";
|
|
24
24
|
import FormProvider from "./layout/FormProvider.svelte";
|
|
25
25
|
import GridLayout from "./layout/GridLayout.svelte";
|
|
26
|
+
import GridRow from "./layout/GridRow.svelte";
|
|
26
27
|
import Hero from "./layout/Hero.svelte";
|
|
27
28
|
import Inline from "./layout/Inline.svelte";
|
|
28
29
|
import MenuList from "./layout/MenuList.svelte";
|
|
@@ -48,7 +49,7 @@ export { Tag };
|
|
|
48
49
|
export { Hero };
|
|
49
50
|
export { TabItem };
|
|
50
51
|
export { Button, ButtonLink, Checkbox, Input, Slider, Toggle, RadioButton, MiniButton, Select, Option, };
|
|
51
|
-
export { Bar, ColumnContainer, Container, GridLayout, Inline, MenuList, Page, ResponsiveText, RowContainer, Stack, TabBar, };
|
|
52
|
+
export { Bar, ColumnContainer, Container, GridLayout, GridRow, Inline, MenuList, Page, ResponsiveText, RowContainer, Stack, TabBar, };
|
|
52
53
|
export { TextLayout, Code, Accordion };
|
|
53
54
|
export { Card, Tile, Form, FormItem, FormProvider, Fieldset };
|
|
54
55
|
export { DataList, DataListItem };
|
package/dist/index.js
CHANGED
|
@@ -24,6 +24,7 @@ import Form from "./layout/Form.svelte";
|
|
|
24
24
|
import FormItem from "./layout/FormItem.svelte";
|
|
25
25
|
import FormProvider from "./layout/FormProvider.svelte";
|
|
26
26
|
import GridLayout from "./layout/GridLayout.svelte";
|
|
27
|
+
import GridRow from "./layout/GridRow.svelte";
|
|
27
28
|
import Hero from "./layout/Hero.svelte";
|
|
28
29
|
import Inline from "./layout/Inline.svelte";
|
|
29
30
|
import MenuList from "./layout/MenuList.svelte";
|
|
@@ -48,7 +49,7 @@ export { Tag };
|
|
|
48
49
|
export { Hero };
|
|
49
50
|
export { TabItem };
|
|
50
51
|
export { Button, ButtonLink, Checkbox, Input, Slider, Toggle, RadioButton, MiniButton, Select, Option, };
|
|
51
|
-
export { Bar, ColumnContainer, Container, GridLayout, Inline, MenuList, Page, ResponsiveText, RowContainer, Stack, TabBar, };
|
|
52
|
+
export { Bar, ColumnContainer, Container, GridLayout, GridRow, Inline, MenuList, Page, ResponsiveText, RowContainer, Stack, TabBar, };
|
|
52
53
|
export { TextLayout, Code, Accordion };
|
|
53
54
|
export { Card, Tile, Form, FormItem, FormProvider, Fieldset };
|
|
54
55
|
export { DataList, DataListItem };
|
|
@@ -11,7 +11,8 @@ export {};
|
|
|
11
11
|
{@render children?.()}
|
|
12
12
|
</div>
|
|
13
13
|
|
|
14
|
-
<style
|
|
14
|
+
<style>@charset "UTF-8";
|
|
15
|
+
/* Warning: because we define a fallback
|
|
15
16
|
media query, the media query can override the container
|
|
16
17
|
if we stack a bunch of these in a row and aren't thoughtful about the order.
|
|
17
18
|
Put min-width queries *after* max-width queries so that smaller
|
|
@@ -20,10 +21,58 @@ export {};
|
|
|
20
21
|
/* Convenience groupings */
|
|
21
22
|
.grid-layout {
|
|
22
23
|
display: grid;
|
|
23
|
-
|
|
24
|
+
/* Tracks sized to the item, not `1fr`.
|
|
25
|
+
|
|
26
|
+
`1fr` splits the leftover width between the tracks, which turns it into
|
|
27
|
+
gutter *between* the items while the edges of the run stay tight -- three
|
|
28
|
+
cards in a 900px container end up sprung apart and pushed left. That is
|
|
29
|
+
rarely what anyone means by declaring an item width: they mean "lay these
|
|
30
|
+
out at this size", and the leftover belongs at the edges.
|
|
31
|
+
|
|
32
|
+
Sizing the track to the item gives exactly the `gap` that was asked for
|
|
33
|
+
between items, and hands the remainder to `justify-content: center` below,
|
|
34
|
+
which centres the whole run. That declaration has been here all along and
|
|
35
|
+
could never fire, because `1fr` tracks always consume the full width.
|
|
36
|
+
|
|
37
|
+
`min(100%, …)` keeps a track from outgrowing a narrow screen.
|
|
38
|
+
|
|
39
|
+
`auto-fit`, not `auto-fill`, and the difference is the whole ballgame for
|
|
40
|
+
centring. Both lay down as many tracks as fit the container; `auto-fill`
|
|
41
|
+
keeps the empty ones at full width, so three cards in a wide container
|
|
42
|
+
leave `justify-content` no slack to work with and pack against the left.
|
|
43
|
+
`auto-fit` collapses the tracks nothing landed in, so the run shrinks to
|
|
44
|
+
the items and centres. Three 200px items in a 1400px container: 72px left
|
|
45
|
+
and 696px right under `auto-fill`, 384px either side under `auto-fit`.
|
|
46
|
+
|
|
47
|
+
One caveat worth knowing, because it is not a bug we can fix here: an item
|
|
48
|
+
spanning `1 / -1` -- a `GridRow`, a heading -- occupies every track, so
|
|
49
|
+
there is nothing left for `auto-fit` to collapse and the run goes back to
|
|
50
|
+
full width and left-packed. A grid with full-width rows in it is a
|
|
51
|
+
full-width grid. Headings kept outside the grid, one grid per section,
|
|
52
|
+
keep the centring. */
|
|
53
|
+
grid-template-columns: repeat(auto-fit, min(100%, var(--grid-layout-item-width, var(--item-width, 250px))));
|
|
24
54
|
gap: var(--grid-layout-gap, var(--gap, 8px));
|
|
25
|
-
|
|
26
|
-
|
|
55
|
+
/* Where the run of tracks sits in the grid box. Both axes as longhands,
|
|
56
|
+
because `place-content` is a shorthand and was declared after
|
|
57
|
+
`justify-content` here -- so it quietly overwrote it and
|
|
58
|
+
`--grid-justify-content` never did anything at all. Only
|
|
59
|
+
`--grid-place-content` worked, and it moves both axes at once.
|
|
60
|
+
|
|
61
|
+
Named `--grid-layout-*` like everything else, with the old spellings kept
|
|
62
|
+
as fallbacks so anything already setting them keeps working -- and
|
|
63
|
+
`--grid-justify-content` starts working for the first time. */
|
|
64
|
+
justify-content: var(--grid-layout-justify-content, var(--justify-content, var(--grid-justify-content, var(--grid-place-content, center))));
|
|
65
|
+
align-content: var(--grid-layout-align-content, var(--align-content, var(--grid-place-content, center)));
|
|
66
|
+
/* Where an item sits *inside* its track -- a separate question from where
|
|
67
|
+
the tracks sit in the grid, which is `justify-content` above.
|
|
68
|
+
|
|
69
|
+
Mostly moot now that a track is sized to the item: there is usually no
|
|
70
|
+
room inside a track to sit anywhere. It still matters when a child is
|
|
71
|
+
narrower than the track it was handed, which happens whenever a component
|
|
72
|
+
carries its own width -- a `Tile` with a `--tile-width` smaller than the
|
|
73
|
+
grid's `--item-width`. The default stays `stretch` so anything that can
|
|
74
|
+
use its whole track still gets it. */
|
|
75
|
+
justify-items: var(--grid-layout-justify-items, var(--justify-items, stretch));
|
|
27
76
|
box-sizing: border-box;
|
|
28
77
|
--_padding: var(--grid-layout-padding, var(--padding, 4px));
|
|
29
78
|
padding: var(--_padding);
|
|
@@ -38,10 +87,56 @@ export {};
|
|
|
38
87
|
border-radius: var(--grid-layout-border-radius, var(--border-radius, none));
|
|
39
88
|
}
|
|
40
89
|
|
|
90
|
+
/* Escape hatch for the things in a grid that are not items in the run: a
|
|
91
|
+
section heading, or a note explaining why the grid is empty. Both want the
|
|
92
|
+
whole row rather than one item-width column with tiles beside them.
|
|
93
|
+
|
|
94
|
+
Declared here so it travels with the component, and matched as a descendant
|
|
95
|
+
rather than a child on purpose -- a component handed a CSS custom property
|
|
96
|
+
is wrapped in a `<svelte-css-wrapper>`, so it is a grandchild of the grid
|
|
97
|
+
even though it is still the grid item. `grid-column` is inert on anything
|
|
98
|
+
that is not a grid item, so the looser match costs nothing. */
|
|
99
|
+
.grid-layout :global(.grid-full-row) {
|
|
100
|
+
grid-column: 1/-1;
|
|
101
|
+
/* Spanning is only half of it. How an item fills the tracks it spans is
|
|
102
|
+
`justify-items` on the grid, which a tile grid sets to `center` -- and a
|
|
103
|
+
centred block with no width of its own shrinks to fit its content, so the
|
|
104
|
+
row it spans renders as a short centred blob rather than a full-width
|
|
105
|
+
heading. Saying `stretch` here makes the class mean the same thing in
|
|
106
|
+
every grid, whatever the container prefers for ordinary items.
|
|
107
|
+
|
|
108
|
+
A consumer spanning their own item -- `grid-column: span 2` -- owns this
|
|
109
|
+
decision themselves and will want `justify-self` alongside it. */
|
|
110
|
+
justify-self: var(--grid-row-justify, var(--justify, stretch));
|
|
111
|
+
/* And the typography has to be let go of too, which is the part that took a
|
|
112
|
+
while to see. A heading inside a typographic container gets a readable
|
|
113
|
+
measure and auto side margins -- `max-width: var(--line-width)`, 42rem by
|
|
114
|
+
default, with `margin-inline: auto` -- so it sits as a centred column of
|
|
115
|
+
text. Sensible for prose, wrong for a band that is supposed to run the
|
|
116
|
+
width of a grid.
|
|
117
|
+
|
|
118
|
+
Worse, an auto inline margin makes `justify-self: stretch` do nothing at
|
|
119
|
+
all: the spec only stretches a box whose inline margins are both
|
|
120
|
+
non-auto, so the heading fell back to shrink-to-fit. "Sections" measured
|
|
121
|
+
74px in a 1064px row, and computed style reports the *used* margin as
|
|
122
|
+
`0px`, so it does not look like margins are the culprit until you set them
|
|
123
|
+
and watch the width jump. */
|
|
124
|
+
max-width: none;
|
|
125
|
+
margin-inline: 0;
|
|
126
|
+
}
|
|
127
|
+
|
|
41
128
|
.card-grid {
|
|
42
129
|
--item-width: var(--card-width, 400px);
|
|
43
130
|
}
|
|
44
131
|
|
|
45
132
|
.tile-grid {
|
|
46
|
-
|
|
133
|
+
/* Has to be the width a Tile actually defaults to, not a round number near
|
|
134
|
+
it. Tile says `calc(var(--space-lg) * 24)`, which is 192px; this said
|
|
135
|
+
200px, so every tile sat 4px inside its own track and a full-width row
|
|
136
|
+
did not line up with the tiles under it. */
|
|
137
|
+
--item-width: var(--tile-width, calc(var(--space-lg) * 24));
|
|
138
|
+
/* The track now matches `--item-width`, so this only bites when a Tile is
|
|
139
|
+
narrower than the track it was handed -- a `--tile-width` set smaller than
|
|
140
|
+
the grid's item width, say. */
|
|
141
|
+
--justify-items: center;
|
|
47
142
|
}</style>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script lang="ts">import { injectVars } from "../util";
|
|
2
|
+
let { justify = null, center = false, children, ...restProps } = $props();
|
|
3
|
+
const resolvedJustify = $derived(justify ?? (center ? "center" : null));
|
|
4
|
+
const inlineStyle = $derived(restProps.style);
|
|
5
|
+
const elementProps = $derived.by(() => {
|
|
6
|
+
const { style: _, ...rest } = restProps;
|
|
7
|
+
return rest;
|
|
8
|
+
});
|
|
9
|
+
const style = $derived(injectVars({ justify: resolvedJustify, ...elementProps }, "grid-row", ["justify"]) +
|
|
10
|
+
(inlineStyle ?? ""));
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<div class="grid-full-row grid-row" {...elementProps} {style}>
|
|
14
|
+
{@render children?.()}
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<style>.grid-row {
|
|
18
|
+
/* Spanning, stretching and the margin reset all live on `.grid-full-row`,
|
|
19
|
+
in GridLayout. What this adds is releasing the measure for the content:
|
|
20
|
+
without it a heading in here is clamped to `--line-width` and centred by
|
|
21
|
+
its own auto margins, so the band spans and the words inside it do not. */
|
|
22
|
+
--line-width: none;
|
|
23
|
+
}</style>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
3
|
+
/**
|
|
4
|
+
* A full-width band inside a GridLayout: a section heading, an explanation, a
|
|
5
|
+
* note saying why the grid is empty. Anything that belongs *in* the run of
|
|
6
|
+
* items without being one of them.
|
|
7
|
+
*
|
|
8
|
+
* Carries `.grid-full-row`, which is the same thing as a class for when you
|
|
9
|
+
* already have an element and would rather not nest a div inside it. The
|
|
10
|
+
* difference is `--line-width`: a heading or paragraph placed inside this
|
|
11
|
+
* component is meant to run the width of the band, so the readable measure
|
|
12
|
+
* that typographic containers impose is lifted for its contents too. Put the
|
|
13
|
+
* class on your own heading instead and the heading keeps that measure.
|
|
14
|
+
*/
|
|
15
|
+
type Props = {
|
|
16
|
+
/** How the row sits in the span it covers: start, center, end, stretch. */
|
|
17
|
+
justify?: string | null;
|
|
18
|
+
/** Shorthand for `justify="center"`, as `Stack` takes `center`. */
|
|
19
|
+
center?: boolean;
|
|
20
|
+
children?: Snippet;
|
|
21
|
+
} & HTMLAttributes<HTMLDivElement>;
|
|
22
|
+
declare const GridRow: import("svelte").Component<Props, {}, "">;
|
|
23
|
+
type GridRow = ReturnType<typeof GridRow>;
|
|
24
|
+
export default GridRow;
|
package/dist/layout/Tile.svelte
CHANGED
|
@@ -1,28 +1,54 @@
|
|
|
1
|
-
<script lang="ts">
|
|
1
|
+
<script lang="ts">import { injectVars } from "../util";
|
|
2
|
+
let { checked = $bindable(false), ...props } = $props();
|
|
3
|
+
/* Props to CSS variables, the way Stack, Card, Tag and the rest do it, so a
|
|
4
|
+
Tile can be aimed with `<Tile center>` rather than only a raw variable. */
|
|
5
|
+
const styleProps = $derived.by(() => {
|
|
6
|
+
const { justify = null, align = null, center = false, bg = null, fg = null, padding = null, width = null, height = null, } = props;
|
|
7
|
+
return {
|
|
8
|
+
bg,
|
|
9
|
+
fg,
|
|
10
|
+
padding,
|
|
11
|
+
width,
|
|
12
|
+
height,
|
|
13
|
+
justify: justify ?? (center ? "center" : null),
|
|
14
|
+
align: align ?? (center ? "center" : null),
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
const style = $derived(injectVars(styleProps, "tile", [
|
|
18
|
+
"bg",
|
|
19
|
+
"fg",
|
|
20
|
+
"padding",
|
|
21
|
+
"width",
|
|
22
|
+
"height",
|
|
23
|
+
"justify",
|
|
24
|
+
"align",
|
|
25
|
+
]) + (props.style ?? ""));
|
|
26
|
+
/** Everything that is ours rather than the element's. */
|
|
27
|
+
function stripOwnProps(value) {
|
|
28
|
+
const { selectable: _selectable, interactive: _interactive, children: _children, justify: _justify, align: _align, center: _center, bg: _bg, fg: _fg, padding: _padding, width: _width, height: _height, style: _style, ...rest } = value;
|
|
29
|
+
return rest;
|
|
30
|
+
}
|
|
2
31
|
function getSelectableInputProps(value) {
|
|
3
|
-
|
|
4
|
-
return inputProps;
|
|
32
|
+
return stripOwnProps(value);
|
|
5
33
|
}
|
|
6
34
|
function getInteractiveButtonProps(value) {
|
|
7
|
-
|
|
8
|
-
return buttonProps;
|
|
35
|
+
return stripOwnProps(value);
|
|
9
36
|
}
|
|
10
|
-
export {};
|
|
11
37
|
</script>
|
|
12
38
|
|
|
13
39
|
{#if props.selectable}
|
|
14
|
-
<label class="tile">
|
|
40
|
+
<label class="tile" {style}>
|
|
15
41
|
<div class="checkbox">
|
|
16
42
|
<input type="checkbox" bind:checked {...getSelectableInputProps(props)} />
|
|
17
43
|
</div>
|
|
18
44
|
{@render props.children?.()}
|
|
19
45
|
</label>
|
|
20
46
|
{:else if props.interactive}
|
|
21
|
-
<button class="tile" {...getInteractiveButtonProps(props)}>
|
|
47
|
+
<button class="tile" {style} {...getInteractiveButtonProps(props)}>
|
|
22
48
|
{@render props.children?.()}
|
|
23
49
|
</button>
|
|
24
50
|
{:else}
|
|
25
|
-
<div class="tile">
|
|
51
|
+
<div class="tile" {style} {...stripOwnProps(props as RenderProps)}>
|
|
26
52
|
{@render props.children?.()}
|
|
27
53
|
</div>
|
|
28
54
|
{/if}
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import type { Snippet } from "svelte";
|
|
2
2
|
import type { HTMLAttributes, HTMLButtonAttributes, HTMLInputAttributes } from "svelte/elements";
|
|
3
|
-
type
|
|
3
|
+
import type { BaseStyleProps } from "../types";
|
|
4
|
+
type BaseProps = BaseStyleProps & {
|
|
5
|
+
/** Distribution along the tile's own axis, which is vertical. */
|
|
6
|
+
justify?: string | null;
|
|
7
|
+
/** Cross-axis alignment, which is horizontal. Centred already. */
|
|
8
|
+
align?: string | null;
|
|
9
|
+
/**
|
|
10
|
+
* Shorthand for centring the tile's contents. Sets both axes, unlike
|
|
11
|
+
* `Stack`'s `center`, because a Tile already centres horizontally -- setting
|
|
12
|
+
* only the cross axis here would be a no-op and `<Tile center>` would look
|
|
13
|
+
* broken.
|
|
14
|
+
*/
|
|
15
|
+
center?: boolean;
|
|
4
16
|
children?: Snippet;
|
|
5
17
|
};
|
|
6
18
|
type StaticTileProps = BaseProps & HTMLAttributes<HTMLDivElement> & {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<script lang="ts">
|
|
1
|
+
<script lang="ts">import { tick } from "svelte";
|
|
2
2
|
let tooltipDiv = $state();
|
|
3
3
|
let targetDiv = $state();
|
|
4
4
|
let tooltipMeasurementDiv = $state();
|
|
@@ -7,7 +7,29 @@ let { tooltipText = "", vertical = "bottom", horizontal = "right", children, too
|
|
|
7
7
|
let renderedVertical = $state(vertical);
|
|
8
8
|
// svelte-ignore state_referenced_locally
|
|
9
9
|
let renderedHorizontal = $state(horizontal);
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Tooltip content mounts on first show, not on mount. A page with many
|
|
12
|
+
* tooltips (e.g. a grid of cells each carrying a rich tooltip snippet)
|
|
13
|
+
* would otherwise build every tooltip twice — popover + measurement copy —
|
|
14
|
+
* before the user hovers anything. Once shown, content stays mounted.
|
|
15
|
+
*/
|
|
16
|
+
let hasRendered = $state(false);
|
|
17
|
+
/** Guards against the pointer/focus leaving while content mounts. */
|
|
18
|
+
let wantsShow = false;
|
|
19
|
+
function hidePopover() {
|
|
20
|
+
wantsShow = false;
|
|
21
|
+
tooltipDiv?.togglePopover(false);
|
|
22
|
+
}
|
|
23
|
+
async function showPopover() {
|
|
24
|
+
wantsShow = true;
|
|
25
|
+
if (!hasRendered) {
|
|
26
|
+
hasRendered = true;
|
|
27
|
+
// Wait for the content to exist before measuring it — positioning below
|
|
28
|
+
// reads the measurement element's height/width to decide flip direction.
|
|
29
|
+
await tick();
|
|
30
|
+
if (!wantsShow)
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
11
33
|
// Get the position of the target element
|
|
12
34
|
// with respect to our screen
|
|
13
35
|
if (!targetDiv?.children[0])
|
|
@@ -75,9 +97,9 @@ function showPopover() {
|
|
|
75
97
|
<div
|
|
76
98
|
class="tooltip-wrapper"
|
|
77
99
|
onmouseenter={() => showPopover()}
|
|
78
|
-
onmouseleave={() => (
|
|
100
|
+
onmouseleave={() => hidePopover()}
|
|
79
101
|
onfocusin={() => showPopover()}
|
|
80
|
-
onfocusout={() => (
|
|
102
|
+
onfocusout={() => hidePopover()}
|
|
81
103
|
bind:this={targetDiv}
|
|
82
104
|
>
|
|
83
105
|
{@render children?.()}
|
|
@@ -90,13 +112,17 @@ function showPopover() {
|
|
|
90
112
|
class:left={renderedHorizontal === "left"}
|
|
91
113
|
class:right={renderedHorizontal === "right"}
|
|
92
114
|
>
|
|
93
|
-
{#if
|
|
94
|
-
{
|
|
115
|
+
{#if hasRendered}
|
|
116
|
+
{#if tooltip}{@render tooltip()}{:else}
|
|
117
|
+
{tooltipText}
|
|
118
|
+
{/if}
|
|
95
119
|
{/if}
|
|
96
120
|
</div>
|
|
97
121
|
<div class="tooltip invisible measure" bind:this={tooltipMeasurementDiv}>
|
|
98
|
-
{#if
|
|
99
|
-
{
|
|
122
|
+
{#if hasRendered}
|
|
123
|
+
{#if tooltip}{@render tooltip()}{:else}
|
|
124
|
+
{tooltipText}
|
|
125
|
+
{/if}
|
|
100
126
|
{/if}
|
|
101
127
|
</div>
|
|
102
128
|
</div>
|
|
@@ -105,9 +131,9 @@ function showPopover() {
|
|
|
105
131
|
<span
|
|
106
132
|
class="tooltip-wrapper"
|
|
107
133
|
onmouseenter={() => showPopover()}
|
|
108
|
-
onmouseleave={() => (
|
|
134
|
+
onmouseleave={() => hidePopover()}
|
|
109
135
|
onfocusin={() => showPopover()}
|
|
110
|
-
onfocusout={() => (
|
|
136
|
+
onfocusout={() => hidePopover()}
|
|
111
137
|
bind:this={targetDiv}
|
|
112
138
|
>
|
|
113
139
|
{@render children?.()}
|
|
@@ -120,13 +146,17 @@ function showPopover() {
|
|
|
120
146
|
class:left={renderedHorizontal === "left"}
|
|
121
147
|
class:right={renderedHorizontal === "right"}
|
|
122
148
|
>
|
|
123
|
-
{#if
|
|
124
|
-
{
|
|
149
|
+
{#if hasRendered}
|
|
150
|
+
{#if tooltip}{@render tooltip()}{:else}
|
|
151
|
+
{tooltipText}
|
|
152
|
+
{/if}
|
|
125
153
|
{/if}
|
|
126
154
|
</span>
|
|
127
155
|
<span class="tooltip invisible measure" bind:this={tooltipMeasurementDiv}>
|
|
128
|
-
{#if
|
|
129
|
-
{
|
|
156
|
+
{#if hasRendered}
|
|
157
|
+
{#if tooltip}{@render tooltip()}{:else}
|
|
158
|
+
{tooltipText}
|
|
159
|
+
{/if}
|
|
130
160
|
{/if}
|
|
131
161
|
</span>
|
|
132
162
|
</span>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
<script lang="ts">import
|
|
1
|
+
<script lang="ts">import { base } from "$app/paths";
|
|
2
|
+
import ReviewThemeSwitcher from "./ReviewThemeSwitcher.svelte";
|
|
2
3
|
import { reviewAppScenarios } from "./scenarios";
|
|
3
4
|
let { data } = $props();
|
|
4
5
|
const scenario = $derived(reviewAppScenarios[data.scenario]);
|
|
@@ -10,8 +11,8 @@ const scenario = $derived(reviewAppScenarios[data.scenario]);
|
|
|
10
11
|
|
|
11
12
|
<div class="review-page-header">
|
|
12
13
|
<div class="review-page-links">
|
|
13
|
-
<a href={
|
|
14
|
-
<a href={
|
|
14
|
+
<a href={`${base}/review/${data.theme}/`}>{data.themeLabel} routes</a>
|
|
15
|
+
<a href={`${base}/review/${data.theme}/app/`}>App scenarios</a>
|
|
15
16
|
</div>
|
|
16
17
|
|
|
17
18
|
<ReviewThemeSwitcher theme={data.theme} />
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
<script lang="ts">import
|
|
1
|
+
<script lang="ts">import { base } from "$app/paths";
|
|
2
|
+
import ReviewIndexShell from "./ReviewIndexShell.svelte";
|
|
2
3
|
import { reviewThemeLabels } from "./themes";
|
|
3
4
|
</script>
|
|
4
5
|
|
|
@@ -13,7 +14,7 @@ import { reviewThemeLabels } from "./themes";
|
|
|
13
14
|
<ul>
|
|
14
15
|
{#each Object.entries(reviewThemeLabels) as [theme, label]}
|
|
15
16
|
<li>
|
|
16
|
-
<a href={
|
|
17
|
+
<a href={`${base}/review/${theme}/`}>{label}</a>
|
|
17
18
|
</li>
|
|
18
19
|
{/each}
|
|
19
20
|
</ul>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
<script lang="ts">import
|
|
1
|
+
<script lang="ts">import { base } from "$app/paths";
|
|
2
|
+
import ReviewIndexShell from "./ReviewIndexShell.svelte";
|
|
2
3
|
import ReviewThemeSwitcher from "./ReviewThemeSwitcher.svelte";
|
|
3
4
|
import { reviewAppScenarios } from "./scenarios";
|
|
4
5
|
let { theme, themeLabel } = $props();
|
|
@@ -17,7 +18,7 @@ let { theme, themeLabel } = $props();
|
|
|
17
18
|
<ul>
|
|
18
19
|
{#each Object.entries(reviewAppScenarios) as [scenario, entry]}
|
|
19
20
|
<li>
|
|
20
|
-
<a href={
|
|
21
|
+
<a href={`${base}/review/${theme}/app/${scenario}`}>
|
|
21
22
|
{entry.title}
|
|
22
23
|
</a>
|
|
23
24
|
</li>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
<script lang="ts">import
|
|
1
|
+
<script lang="ts">import { base } from "$app/paths";
|
|
2
|
+
import ReviewIndexShell from "./ReviewIndexShell.svelte";
|
|
2
3
|
import ReviewThemeSwitcher from "./ReviewThemeSwitcher.svelte";
|
|
3
4
|
import { reviewAppScenarios } from "./scenarios";
|
|
4
5
|
let { theme, themeLabel } = $props();
|
|
@@ -18,7 +19,7 @@ let { theme, themeLabel } = $props();
|
|
|
18
19
|
<h2>Groups</h2>
|
|
19
20
|
<ul>
|
|
20
21
|
<li>
|
|
21
|
-
<a href={
|
|
22
|
+
<a href={`${base}/review/${theme}/app/`}>App scenarios</a>
|
|
22
23
|
</li>
|
|
23
24
|
</ul>
|
|
24
25
|
</section>
|
|
@@ -28,7 +29,7 @@ let { theme, themeLabel } = $props();
|
|
|
28
29
|
<ul>
|
|
29
30
|
{#each Object.entries(reviewAppScenarios) as [scenario, entry]}
|
|
30
31
|
<li>
|
|
31
|
-
<a href={
|
|
32
|
+
<a href={`${base}/review/${theme}/app/${scenario}`}>
|
|
32
33
|
{entry.title}
|
|
33
34
|
</a>
|
|
34
35
|
</li>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
<script lang="ts">import {
|
|
1
|
+
<script lang="ts">import { base } from "$app/paths";
|
|
2
|
+
import { page } from "$app/state";
|
|
2
3
|
import { reviewThemeLabels, reviewThemeNames, } from "./themes";
|
|
3
4
|
let { theme } = $props();
|
|
4
5
|
const themeLinks = $derived.by(() => {
|
|
@@ -16,7 +17,7 @@ const themeLinks = $derived.by(() => {
|
|
|
16
17
|
</script>
|
|
17
18
|
|
|
18
19
|
<nav class="review-theme-switcher" aria-label="Review theme switcher">
|
|
19
|
-
<a class="utility-link" href=
|
|
20
|
+
<a class="utility-link" href={`${base}/review/`}>All Themes</a>
|
|
20
21
|
|
|
21
22
|
{#each themeLinks as item}
|
|
22
23
|
<a
|
package/package.json
CHANGED