contain-css-svelte 1.4.3 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/Card.svelte +57 -3
  2. package/dist/controls/Button.svelte +32 -0
  3. package/dist/controls/ButtonLink.svelte +32 -0
  4. package/dist/controls/Checkbox.svelte +36 -0
  5. package/dist/controls/CircleButton.svelte +32 -0
  6. package/dist/controls/GroupedRadioItem.svelte +551 -0
  7. package/dist/controls/GroupedRadioItem.svelte.d.ts +36 -0
  8. package/dist/controls/GroupedToggle.svelte +147 -0
  9. package/dist/controls/GroupedToggle.svelte.d.ts +9 -0
  10. package/dist/controls/GroupedToggleItem.svelte +452 -0
  11. package/dist/controls/GroupedToggleItem.svelte.d.ts +31 -0
  12. package/dist/controls/Input.svelte +32 -0
  13. package/dist/controls/RadioButton.svelte +36 -1
  14. package/dist/controls/Select.svelte +80 -2
  15. package/dist/controls/Slider.svelte +32 -0
  16. package/dist/controls/TabItem.svelte +36 -0
  17. package/dist/controls/Toggle.svelte +32 -0
  18. package/dist/dropdowns/DropdownMenu.svelte +53 -1
  19. package/dist/index.d.ts +4 -0
  20. package/dist/index.js +4 -0
  21. package/dist/layout/Accordion.svelte +32 -0
  22. package/dist/layout/Bar.svelte +50 -0
  23. package/dist/layout/ColumnContainer.svelte +32 -0
  24. package/dist/layout/Columns.svelte +32 -0
  25. package/dist/layout/Container.svelte +48 -2
  26. package/dist/layout/DataList.svelte +33 -1
  27. package/dist/layout/DataList.svelte.d.ts +7 -0
  28. package/dist/layout/DataListItem.svelte +56 -2
  29. package/dist/layout/Fieldset.svelte +32 -0
  30. package/dist/layout/Form.svelte +32 -0
  31. package/dist/layout/FormItem.svelte +46 -0
  32. package/dist/layout/GridLayout.svelte +59 -0
  33. package/dist/layout/Hero.svelte +42 -0
  34. package/dist/layout/Inline.svelte +37 -1
  35. package/dist/layout/MenuList.svelte +32 -0
  36. package/dist/layout/Page.svelte +137 -10
  37. package/dist/layout/Page.svelte.d.ts +17 -0
  38. package/dist/layout/RowContainer.svelte +32 -0
  39. package/dist/layout/Sidebar.svelte +104 -56
  40. package/dist/layout/SidebarContainer.svelte +32 -0
  41. package/dist/layout/SplitPane.svelte +32 -0
  42. package/dist/layout/Stack.svelte +32 -0
  43. package/dist/layout/TabBar.svelte +127 -8
  44. package/dist/layout/TabBar.svelte.d.ts +16 -0
  45. package/dist/layout/Table.svelte +60 -9
  46. package/dist/layout/Tile.svelte +89 -6
  47. package/dist/misc/CodeInner.svelte +32 -0
  48. package/dist/misc/Progress.svelte +32 -0
  49. package/dist/misc/Tag.svelte +32 -0
  50. package/dist/overlays/Dialog.svelte +32 -0
  51. package/dist/overlays/Tooltip.svelte +32 -0
  52. package/dist/sass/_responsive.scss +43 -0
  53. package/dist/typography/TextLayout.svelte +47 -0
  54. package/dist/vars/defaults.css +4 -1
  55. package/dist/vars/layout.css +4 -1
  56. package/dist/vars/responsive-typography.css +4 -1
  57. package/package.json +1 -1
@@ -103,6 +103,38 @@ const el = $derived(elementProps({ justify, align: resolvedAlign, gap, ...restPr
103
103
  we deliberately avoid everywhere else.
104
104
 
105
105
  One definition, used by both, so they cannot drift again. */
106
+ /* The "small" size tier: a component whose container is this wide or less
107
+ is on a phone, or in a pane about that narrow, and sizes itself for it.
108
+
109
+ The convention, shared by every component that has a size worth changing
110
+ there (Card started it; Tile, GridLayout, Bar and Container follow):
111
+
112
+ --{component}-{property}-small
113
+
114
+ applies inside a small container. What wins there, in order:
115
+ 1. the `-small` variable, if you set it;
116
+ 2. otherwise the regular variable, if you set it -- a size you chose on
117
+ purpose is not overridden behind your back;
118
+ 3. otherwise the component's own small default, which for most
119
+ properties is simply its regular default.
120
+
121
+ Container queries can't read a custom property, so the breakpoint itself
122
+ is fixed here rather than themeable. 600px matches Card and SplitPane,
123
+ which already switched there. (The Sidebar's rail-to-sheet switch,
124
+ above, is a separate, layout-chrome decision and keeps its own value.) */
125
+ /* Styles for the small tier: the nearest container is narrow, OR the
126
+ viewport is.
127
+
128
+ The container query is the real answer -- a Tile in a 400px pane on a
129
+ desktop is small. But a container query only matches inside an ancestor
130
+ that *is* a container, and a component dropped straight into a page (no
131
+ Page, no Container around it) has none, so on a phone it would never
132
+ find out. The viewport query covers that: a narrow viewport means every
133
+ container in it is narrow too, so the two never disagree in the
134
+ direction that matters.
135
+
136
+ An element never queries itself, so on a component that is its own
137
+ container (Tile, Bar) this asks about the space it was given. */
106
138
  /* Warning: because we define a fallback
107
139
  media query, the media query can override the container
108
140
  if we stack a bunch of these in a row and aren't thoughtful about the order.
@@ -2,7 +2,7 @@
2
2
  import TabItem from "../controls/TabItem.svelte";
3
3
  import { elementProps } from "../util";
4
4
  import { COLOR_VARS, TYPOGRAPHY_VARS, } from "../styleProps";
5
- let { sticky = false, active = $bindable(null), items = [], onchange, children, class: className, ...restProps } = $props();
5
+ let { sticky = false, wrap = false, active = $bindable(null), items = [], onchange, children, actions, class: className, ...restProps } = $props();
6
6
  /* The shorthands this component's own CSS backs, one group per mixin
7
7
  it includes. The Props type is derived from this same array, so what
8
8
  the component accepts and what it emits cannot drift apart. */
@@ -14,7 +14,7 @@ const el = $derived(elementProps(restProps, "tab-bar", TAB_BAR_VARS));
14
14
  let lastActive = $state(active);
15
15
  </script>
16
16
 
17
- <div class={["tabs", className]} class:sticky {...el}>
17
+ {#snippet strip()}
18
18
  <Bar
19
19
  padding="0"
20
20
  --button-height="var(--tab-bar-height, 3em)"
@@ -23,11 +23,15 @@ let lastActive = $state(active);
23
23
  --bar-fg="var(--tab-bar-fg, var(--bar-fg))"
24
24
  --bar-bg="var(--tab-bar-bg, var(--bar-bg))"
25
25
  --bar-align="var(--tab-bar-align, flex-end)"
26
- --bar-overflow="hidden"
26
+ --bar-overflow="var(--tab-bar-overflow, auto hidden)"
27
+ --bar-wrap="var(--tab-bar-wrap, nowrap)"
27
28
  --bar-border-left="var(--tab-bar-border-left, none)"
28
29
  --bar-border-right="var(--tab-bar-border-right, none)"
29
30
  --bar-border-top="var(--tab-bar-border-top, none)"
30
- --bar-border-bottom="var(--tab-bar-border-bottom, var(--border-width) var(--border-style) var(--border-color))"
31
+ --bar-border-bottom={actions
32
+ ? "none"
33
+ : "var(--tab-bar-border-bottom, var(--border-width) var(--border-style) var(--border-color))"}
34
+ --bar-margin-block={actions ? "0" : undefined}
31
35
  >
32
36
  {#each items as item}
33
37
  {@const value = typeof item === "string" ? item : item.value}
@@ -44,6 +48,23 @@ let lastActive = $state(active);
44
48
  {/each}
45
49
  {@render children?.()}
46
50
  </Bar>
51
+ {/snippet}
52
+
53
+ <div
54
+ class={["tabs", className]}
55
+ class:sticky
56
+ class:wrap-tabs={wrap}
57
+ class:has-actions={!!actions}
58
+ {...el}
59
+ >
60
+ {#if actions}
61
+ <!-- The strip and the actions share the row; the underline moves to
62
+ the whole row so it runs under both. -->
63
+ <div class="tab-strip">{@render strip()}</div>
64
+ <div class="tab-actions">{@render actions()}</div>
65
+ {:else}
66
+ {@render strip()}
67
+ {/if}
47
68
  </div>
48
69
 
49
70
  <style>/* Per-level heading (h1-h6) box/typography properties, resolved to PRIVATE,
@@ -117,6 +138,38 @@ let lastActive = $state(active);
117
138
  we deliberately avoid everywhere else.
118
139
 
119
140
  One definition, used by both, so they cannot drift again. */
141
+ /* The "small" size tier: a component whose container is this wide or less
142
+ is on a phone, or in a pane about that narrow, and sizes itself for it.
143
+
144
+ The convention, shared by every component that has a size worth changing
145
+ there (Card started it; Tile, GridLayout, Bar and Container follow):
146
+
147
+ --{component}-{property}-small
148
+
149
+ applies inside a small container. What wins there, in order:
150
+ 1. the `-small` variable, if you set it;
151
+ 2. otherwise the regular variable, if you set it -- a size you chose on
152
+ purpose is not overridden behind your back;
153
+ 3. otherwise the component's own small default, which for most
154
+ properties is simply its regular default.
155
+
156
+ Container queries can't read a custom property, so the breakpoint itself
157
+ is fixed here rather than themeable. 600px matches Card and SplitPane,
158
+ which already switched there. (The Sidebar's rail-to-sheet switch,
159
+ above, is a separate, layout-chrome decision and keeps its own value.) */
160
+ /* Styles for the small tier: the nearest container is narrow, OR the
161
+ viewport is.
162
+
163
+ The container query is the real answer -- a Tile in a 400px pane on a
164
+ desktop is small. But a container query only matches inside an ancestor
165
+ that *is* a container, and a component dropped straight into a page (no
166
+ Page, no Container around it) has none, so on a phone it would never
167
+ find out. The viewport query covers that: a narrow viewport means every
168
+ container in it is narrow too, so the two never disagree in the
169
+ direction that matters.
170
+
171
+ An element never queries itself, so on a component that is its own
172
+ container (Tile, Bar) this asks about the space it was given. */
120
173
  /* Warning: because we define a fallback
121
174
  media query, the media query can override the container
122
175
  if we stack a bunch of these in a row and aren't thoughtful about the order.
@@ -125,7 +178,7 @@ let lastActive = $state(active);
125
178
  */
126
179
  /* Convenience groupings */
127
180
  div > :global(.bar),
128
- div > :global(div > .bar) {
181
+ div > :global(svelte-css-wrapper > .bar) {
129
182
  --link-bg: var(--tab-bar-link-bg, var(--bar-link-bg, var(--surface-link-bg, inherit)));
130
183
  --link-fg: var(--tab-bar-link-fg, var(--bar-link-fg, var(--surface-link-fg, inherit)));
131
184
  --_bg-mix-color: var(--tab-bar-bg-mix-color, var(--bar-bg-mix-color, var(--surface-bg-mix-color, var(--bg-mix-color, transparent))));
@@ -224,12 +277,78 @@ div > :global(div > .bar) {
224
277
  align-items: var(--tab-bar-align, var(--align, flex-end));
225
278
  border-bottom: var(--tab-bar-border-bottom, var(--border-width) var(--border-style) var(--border-color));
226
279
  border-left: var(--tab-bar-border-left, none);
227
- border-right: var(--tab-bar-border-left, none);
228
- border-top: var(--tab-bar-border-left, none);
280
+ border-right: var(--tab-bar-border-right, none);
281
+ border-top: var(--tab-bar-border-top, none);
229
282
  gap: var(--tab-bar-gap, var(--space-md));
230
- overflow: hidden;
283
+ /* Tabs stay one strip. When they don't all fit -- four tabs on a phone --
284
+ the strip scrolls sideways rather than dropping the last tab onto a
285
+ second row, which reads as a broken tab set (the second row floats
286
+ under the active tab's baseline). `wrap` (or --tab-bar-wrap: wrap)
287
+ brings wrapping back. */
288
+ overflow-x: auto;
289
+ overflow-y: hidden;
290
+ scrollbar-width: thin;
291
+ }
292
+
293
+ /* The scroll also goes in through --bar-overflow on the Bar itself (x
294
+ scrolls, y clipped), so it holds even where a consumer's own CSS beats
295
+ the rule above. */
296
+ .wrap-tabs {
297
+ --tab-bar-wrap: wrap;
298
+ }
299
+
300
+ .has-actions {
301
+ display: flex;
302
+ align-items: flex-end;
303
+ gap: var(--tab-bar-gap, var(--gap, var(--space-md, 8px)));
304
+ border-bottom: var(--tab-bar-border-bottom, var(--border-width) var(--border-style) var(--border-color));
305
+ margin-block: var(--tab-bar-margin-block, 0 1em);
306
+ }
307
+
308
+ /* The strip takes the room and gives way first: min-width 0 lets it
309
+ shrink below its tabs' width, at which point it scrolls (above). */
310
+ .tab-strip {
311
+ flex: 1 1 auto;
312
+ min-width: 0;
313
+ }
314
+
315
+ /* The row carries the underline; the strip's own (from the rule above)
316
+ would double it and stop short of the actions. */
317
+ .tab-strip > :global(svelte-css-wrapper > .bar),
318
+ .tab-strip > :global(.bar) {
319
+ border-bottom: none;
320
+ }
321
+
322
+ .tab-actions {
323
+ flex: 0 1 auto;
324
+ display: flex;
325
+ flex-wrap: wrap;
326
+ justify-content: flex-end;
327
+ align-items: center;
328
+ gap: var(--tab-bar-actions-gap, var(--gap, var(--space-md, 8px)));
329
+ padding-block-end: var(--tab-bar-actions-padding, var(--padding, var(--space-sm, 4px)));
231
330
  }
232
331
 
332
+ /* Small tier: actions get their own row, above the tabs, so the tabs stay
333
+ attached to the panel they switch. */
334
+ @media (max-width: 600px) {
335
+ .has-actions {
336
+ flex-direction: column-reverse;
337
+ align-items: stretch;
338
+ }
339
+ .tab-actions {
340
+ justify-content: flex-start;
341
+ }
342
+ }
343
+ @container (max-width: 600px) {
344
+ .has-actions {
345
+ flex-direction: column-reverse;
346
+ align-items: stretch;
347
+ }
348
+ .tab-actions {
349
+ justify-content: flex-start;
350
+ }
351
+ }
233
352
  .sticky {
234
353
  position: sticky;
235
354
  top: calc(-1 * var(--surface-padding, var(--block-padding, var(--padding, 8px))));
@@ -3,6 +3,14 @@ import type { ContainProps } from "../types";
3
3
  import { type StyleProps } from "../styleProps";
4
4
  declare const TabBar: import("svelte").Component<ContainProps<HTMLAttributes<HTMLDivElement>, {
5
5
  sticky?: boolean;
6
+ /**
7
+ * Let tabs that don't fit wrap onto another row. By default the tab
8
+ * strip stays one row and scrolls sideways when it runs out of room,
9
+ * which keeps the active tab attached to its panel; wrapping suits a
10
+ * long set of short, filter-like tabs where seeing them all at once
11
+ * matters more. Same as setting --tab-bar-wrap: wrap.
12
+ */
13
+ wrap?: boolean;
6
14
  active?: string | {
7
15
  label: string;
8
16
  value: string;
@@ -16,6 +24,14 @@ declare const TabBar: import("svelte").Component<ContainProps<HTMLAttributes<HTM
16
24
  value: string;
17
25
  } | null) => void;
18
26
  children?: import("svelte").Snippet;
27
+ /**
28
+ * Controls that belong to the tab row but aren't tabs -- a Refresh
29
+ * button, a "New..." action. They sit at the end of the row when there
30
+ * is room, and on their own row above the tabs in the small tier (see
31
+ * the -small variables), so they never squeeze the tabs or stack into
32
+ * a tall column beside them on a phone.
33
+ */
34
+ actions?: import("svelte").Snippet;
19
35
  }, StyleProps<readonly ["bg", "fg", "fontFamily", "fontSize", "fontWeight", "lineHeight", "letterSpacing", "textAlign", "textTransform", "textDecoration", "fontVariant"]>>, {}, "active">;
20
36
  type TabBar = ReturnType<typeof TabBar>;
21
37
  export default TabBar;
@@ -308,15 +308,22 @@ function observeColumns() {
308
308
  </div>
309
309
  {/if}
310
310
  {:else}
311
- <table>
312
- {#if thead}
313
- {@render thead?.()}
314
- {/if}
315
- {#if tbody}
316
- {@render tbody?.()}
317
- {/if}
318
- {@render children?.()}
319
- </table>
311
+ <!-- A table can't shrink below its content, so on a narrow screen it is
312
+ wider than its container. It scrolls sideways in its own strip rather
313
+ than widening (or, under an overflow-clipping ancestor, being cut off
314
+ by) everything around it. Not done for the sticky variants: a scroll
315
+ box would become the sticky header's scroll container. -->
316
+ <div class="table-scroll">
317
+ <table>
318
+ {#if thead}
319
+ {@render thead?.()}
320
+ {/if}
321
+ {#if tbody}
322
+ {@render tbody?.()}
323
+ {/if}
324
+ {@render children?.()}
325
+ </table>
326
+ </div>
320
327
  {/if}
321
328
 
322
329
  <style>/* Per-level heading (h1-h6) box/typography properties, resolved to PRIVATE,
@@ -390,6 +397,38 @@ function observeColumns() {
390
397
  we deliberately avoid everywhere else.
391
398
 
392
399
  One definition, used by both, so they cannot drift again. */
400
+ /* The "small" size tier: a component whose container is this wide or less
401
+ is on a phone, or in a pane about that narrow, and sizes itself for it.
402
+
403
+ The convention, shared by every component that has a size worth changing
404
+ there (Card started it; Tile, GridLayout, Bar and Container follow):
405
+
406
+ --{component}-{property}-small
407
+
408
+ applies inside a small container. What wins there, in order:
409
+ 1. the `-small` variable, if you set it;
410
+ 2. otherwise the regular variable, if you set it -- a size you chose on
411
+ purpose is not overridden behind your back;
412
+ 3. otherwise the component's own small default, which for most
413
+ properties is simply its regular default.
414
+
415
+ Container queries can't read a custom property, so the breakpoint itself
416
+ is fixed here rather than themeable. 600px matches Card and SplitPane,
417
+ which already switched there. (The Sidebar's rail-to-sheet switch,
418
+ above, is a separate, layout-chrome decision and keeps its own value.) */
419
+ /* Styles for the small tier: the nearest container is narrow, OR the
420
+ viewport is.
421
+
422
+ The container query is the real answer -- a Tile in a 400px pane on a
423
+ desktop is small. But a container query only matches inside an ancestor
424
+ that *is* a container, and a component dropped straight into a page (no
425
+ Page, no Container around it) has none, so on a phone it would never
426
+ find out. The viewport query covers that: a narrow viewport means every
427
+ container in it is narrow too, so the two never disagree in the
428
+ direction that matters.
429
+
430
+ An element never queries itself, so on a component that is its own
431
+ container (Tile, Bar) this asks about the space it was given. */
393
432
  /* Warning: because we define a fallback
394
433
  media query, the media query can override the container
395
434
  if we stack a bunch of these in a row and aren't thoughtful about the order.
@@ -836,6 +875,18 @@ table :global(tr > th:first-child:has(~ td)) {
836
875
  border-right: var(--table-first-row-bottom-border, var(--table-thick-border, 3px) solid var(--table-first-row-border-color, var(--secondary-bg))) !important;
837
876
  }
838
877
 
878
+ .table-scroll {
879
+ max-width: 100%;
880
+ overflow-x: auto;
881
+ /* Room for the table's own box-shadow, which the scroll box would
882
+ otherwise clip. */
883
+ padding: var(--table-scroll-padding, 0 0 4px);
884
+ /* A visible scrollbar, not the hover-revealed one the other scroll areas
885
+ use: a phone has no hover, and the bar is the only sign that the table
886
+ goes on past the edge. */
887
+ scrollbar-width: thin;
888
+ }
889
+
839
890
  .table-container {
840
891
  position: sticky;
841
892
  top: 0;
@@ -142,6 +142,38 @@ const className = $derived(props.class);
142
142
  we deliberately avoid everywhere else.
143
143
 
144
144
  One definition, used by both, so they cannot drift again. */
145
+ /* The "small" size tier: a component whose container is this wide or less
146
+ is on a phone, or in a pane about that narrow, and sizes itself for it.
147
+
148
+ The convention, shared by every component that has a size worth changing
149
+ there (Card started it; Tile, GridLayout, Bar and Container follow):
150
+
151
+ --{component}-{property}-small
152
+
153
+ applies inside a small container. What wins there, in order:
154
+ 1. the `-small` variable, if you set it;
155
+ 2. otherwise the regular variable, if you set it -- a size you chose on
156
+ purpose is not overridden behind your back;
157
+ 3. otherwise the component's own small default, which for most
158
+ properties is simply its regular default.
159
+
160
+ Container queries can't read a custom property, so the breakpoint itself
161
+ is fixed here rather than themeable. 600px matches Card and SplitPane,
162
+ which already switched there. (The Sidebar's rail-to-sheet switch,
163
+ above, is a separate, layout-chrome decision and keeps its own value.) */
164
+ /* Styles for the small tier: the nearest container is narrow, OR the
165
+ viewport is.
166
+
167
+ The container query is the real answer -- a Tile in a 400px pane on a
168
+ desktop is small. But a container query only matches inside an ancestor
169
+ that *is* a container, and a component dropped straight into a page (no
170
+ Page, no Container around it) has none, so on a phone it would never
171
+ find out. The viewport query covers that: a narrow viewport means every
172
+ container in it is narrow too, so the two never disagree in the
173
+ direction that matters.
174
+
175
+ An element never queries itself, so on a component that is its own
176
+ container (Tile, Bar) this asks about the space it was given. */
145
177
  /* Warning: because we define a fallback
146
178
  media query, the media query can override the container
147
179
  if we stack a bunch of these in a row and aren't thoughtful about the order.
@@ -451,15 +483,22 @@ const className = $derived(props.class);
451
483
  --_box-shadow:
452
484
  var(--tile-shadow-distance, var(--surface-shadow-distance, var(--shadow-distance, var(--space)))) var(--tile-shadow-distance, var(--surface-shadow-distance, var(--shadow-distance, var(--space)))) var(--tile-shadow-blur, var(--surface-shadow-blur, var(--shadow-blur, var(--space)))) var(--tile-shadow-color, var(--surface-shadow-color, var(--shadow-color, rgba(127, 127, 127, 0.4))));
453
485
  box-shadow: var(--_box-shadow);
454
- width: var(--tile-width, calc(var(--space-lg) * 24));
455
- /* Override typography max-width */
456
- max-width: var(--tile-width, calc(var(--space-lg) * 24));
486
+ /* --_tile-width is resolved in the sizing block below. max-width also
487
+ overrides the typography mixin's prose measure. */
488
+ width: var(--_tile-width);
489
+ max-width: var(--_tile-width);
457
490
  display: inline-flex;
458
491
  vertical-align: top;
459
492
  flex-direction: column;
460
493
  justify-content: var(--tile-justify, var(--justify, flex-start));
461
494
  align-items: var(--tile-align, var(--align, center));
462
495
  container-type: inline-size;
496
+ /* A Tile is a fixed-size object. Inline-size containment makes its
497
+ min-content width zero, so as a flex item in any row that runs out of
498
+ room (a RowContainer lane, a nowrap Inline) it would be squeezed to a
499
+ sliver with its text spilling out. Hold the size; let the row wrap or
500
+ scroll instead. */
501
+ flex-shrink: 0;
463
502
  }
464
503
 
465
504
  button.tile,
@@ -610,12 +649,56 @@ label.tile:has(:global(input:focus-visible)) {
610
649
  box-shadow: var(--focus-ring-box-shadow, 0 0 0 3px var(--focus-shadow-color, rgba(100, 150, 250, 0.5)));
611
650
  }
612
651
 
613
- /* Sizing code */
652
+ /* Sizing.
653
+
654
+ One width, declared once. There used to be two -- `--space-lg * 24`
655
+ (192px) above and `200px` here -- and the narrower max-width quietly won,
656
+ so tiles rendered 192px wide but 267px tall (200 * 4/3).
657
+
658
+ The 3:4 shape is a *minimum* height rather than a fixed one, so a tile
659
+ with more content than its shape holds grows to fit instead of spilling
660
+ out. Set --tile-height to pin it (it sets the minimum too, or the shape
661
+ would win over a shorter pinned height); --tile-aspect-ratio to change
662
+ the shape.
663
+
664
+ Not the `aspect-ratio` property: a percentage --tile-width (a tile
665
+ filling a ColumnContainer rail) would then be 4/3 of the rail tall. As a
666
+ calc, a percentage height against an auto-height parent resolves to
667
+ nothing and the tile is as tall as its content, which is what those
668
+ layouts have always relied on. */
614
669
  .tile {
615
- width: var(--tile-width, 200px);
616
- height: var(--tile-height, calc(var(--tile-width, 200px) * 1.333));
670
+ --_tile-width: var(--tile-width, calc(var(--space-lg, 8px) * 24));
671
+ --_tile-min-height: var(
672
+ --tile-height,
673
+ calc(var(--_tile-width) / (var(--tile-aspect-ratio, 3 / 4)))
674
+ );
675
+ height: var(--tile-height, auto);
676
+ min-height: var(--_tile-min-height);
617
677
  }
618
678
 
679
+ /* Small tier (see $small-max): two tiles side by side on a phone instead of
680
+ one 192px tile alone in a 360px row. 160px * 2 + a gap fits a 328px
681
+ content box, i.e. a 360px phone with 16px gutters. */
682
+ @media (max-width: 600px) {
683
+ .tile {
684
+ --_tile-width: var(
685
+ --tile-width-small,
686
+ var(--tile-width, calc(var(--space-lg, 8px) * 20))
687
+ );
688
+ height: var(--tile-height-small, var(--tile-height, auto));
689
+ min-height: var(--tile-height-small, var(--_tile-min-height));
690
+ }
691
+ }
692
+ @container (max-width: 600px) {
693
+ .tile {
694
+ --_tile-width: var(
695
+ --tile-width-small,
696
+ var(--tile-width, calc(var(--space-lg, 8px) * 20))
697
+ );
698
+ height: var(--tile-height-small, var(--tile-height, auto));
699
+ min-height: var(--tile-height-small, var(--_tile-min-height));
700
+ }
701
+ }
619
702
  /* Checkbox code */
620
703
  .tile {
621
704
  position: relative;
@@ -87,6 +87,38 @@ let highlightedCode = $derived.by(() => {
87
87
  we deliberately avoid everywhere else.
88
88
 
89
89
  One definition, used by both, so they cannot drift again. */
90
+ /* The "small" size tier: a component whose container is this wide or less
91
+ is on a phone, or in a pane about that narrow, and sizes itself for it.
92
+
93
+ The convention, shared by every component that has a size worth changing
94
+ there (Card started it; Tile, GridLayout, Bar and Container follow):
95
+
96
+ --{component}-{property}-small
97
+
98
+ applies inside a small container. What wins there, in order:
99
+ 1. the `-small` variable, if you set it;
100
+ 2. otherwise the regular variable, if you set it -- a size you chose on
101
+ purpose is not overridden behind your back;
102
+ 3. otherwise the component's own small default, which for most
103
+ properties is simply its regular default.
104
+
105
+ Container queries can't read a custom property, so the breakpoint itself
106
+ is fixed here rather than themeable. 600px matches Card and SplitPane,
107
+ which already switched there. (The Sidebar's rail-to-sheet switch,
108
+ above, is a separate, layout-chrome decision and keeps its own value.) */
109
+ /* Styles for the small tier: the nearest container is narrow, OR the
110
+ viewport is.
111
+
112
+ The container query is the real answer -- a Tile in a 400px pane on a
113
+ desktop is small. But a container query only matches inside an ancestor
114
+ that *is* a container, and a component dropped straight into a page (no
115
+ Page, no Container around it) has none, so on a phone it would never
116
+ find out. The viewport query covers that: a narrow viewport means every
117
+ container in it is narrow too, so the two never disagree in the
118
+ direction that matters.
119
+
120
+ An element never queries itself, so on a component that is its own
121
+ container (Tile, Bar) this asks about the space it was given. */
90
122
  /* Warning: because we define a fallback
91
123
  media query, the media query can override the container
92
124
  if we stack a bunch of these in a row and aren't thoughtful about the order.
@@ -134,6 +134,38 @@ let halfFull = $derived(fillFraction > 0.5);
134
134
  we deliberately avoid everywhere else.
135
135
 
136
136
  One definition, used by both, so they cannot drift again. */
137
+ /* The "small" size tier: a component whose container is this wide or less
138
+ is on a phone, or in a pane about that narrow, and sizes itself for it.
139
+
140
+ The convention, shared by every component that has a size worth changing
141
+ there (Card started it; Tile, GridLayout, Bar and Container follow):
142
+
143
+ --{component}-{property}-small
144
+
145
+ applies inside a small container. What wins there, in order:
146
+ 1. the `-small` variable, if you set it;
147
+ 2. otherwise the regular variable, if you set it -- a size you chose on
148
+ purpose is not overridden behind your back;
149
+ 3. otherwise the component's own small default, which for most
150
+ properties is simply its regular default.
151
+
152
+ Container queries can't read a custom property, so the breakpoint itself
153
+ is fixed here rather than themeable. 600px matches Card and SplitPane,
154
+ which already switched there. (The Sidebar's rail-to-sheet switch,
155
+ above, is a separate, layout-chrome decision and keeps its own value.) */
156
+ /* Styles for the small tier: the nearest container is narrow, OR the
157
+ viewport is.
158
+
159
+ The container query is the real answer -- a Tile in a 400px pane on a
160
+ desktop is small. But a container query only matches inside an ancestor
161
+ that *is* a container, and a component dropped straight into a page (no
162
+ Page, no Container around it) has none, so on a phone it would never
163
+ find out. The viewport query covers that: a narrow viewport means every
164
+ container in it is narrow too, so the two never disagree in the
165
+ direction that matters.
166
+
167
+ An element never queries itself, so on a component that is its own
168
+ container (Tile, Bar) this asks about the space it was given. */
137
169
  /* Warning: because we define a fallback
138
170
  media query, the media query can override the container
139
171
  if we stack a bunch of these in a row and aren't thoughtful about the order.
@@ -101,6 +101,38 @@ const el = $derived(elementProps(restProps, "tag", TAG_VARS));
101
101
  we deliberately avoid everywhere else.
102
102
 
103
103
  One definition, used by both, so they cannot drift again. */
104
+ /* The "small" size tier: a component whose container is this wide or less
105
+ is on a phone, or in a pane about that narrow, and sizes itself for it.
106
+
107
+ The convention, shared by every component that has a size worth changing
108
+ there (Card started it; Tile, GridLayout, Bar and Container follow):
109
+
110
+ --{component}-{property}-small
111
+
112
+ applies inside a small container. What wins there, in order:
113
+ 1. the `-small` variable, if you set it;
114
+ 2. otherwise the regular variable, if you set it -- a size you chose on
115
+ purpose is not overridden behind your back;
116
+ 3. otherwise the component's own small default, which for most
117
+ properties is simply its regular default.
118
+
119
+ Container queries can't read a custom property, so the breakpoint itself
120
+ is fixed here rather than themeable. 600px matches Card and SplitPane,
121
+ which already switched there. (The Sidebar's rail-to-sheet switch,
122
+ above, is a separate, layout-chrome decision and keeps its own value.) */
123
+ /* Styles for the small tier: the nearest container is narrow, OR the
124
+ viewport is.
125
+
126
+ The container query is the real answer -- a Tile in a 400px pane on a
127
+ desktop is small. But a container query only matches inside an ancestor
128
+ that *is* a container, and a component dropped straight into a page (no
129
+ Page, no Container around it) has none, so on a phone it would never
130
+ find out. The viewport query covers that: a narrow viewport means every
131
+ container in it is narrow too, so the two never disagree in the
132
+ direction that matters.
133
+
134
+ An element never queries itself, so on a component that is its own
135
+ container (Tile, Bar) this asks about the space it was given. */
104
136
  /* Warning: because we define a fallback
105
137
  media query, the media query can override the container
106
138
  if we stack a bunch of these in a row and aren't thoughtful about the order.
@@ -148,6 +148,38 @@ let ref;
148
148
  we deliberately avoid everywhere else.
149
149
 
150
150
  One definition, used by both, so they cannot drift again. */
151
+ /* The "small" size tier: a component whose container is this wide or less
152
+ is on a phone, or in a pane about that narrow, and sizes itself for it.
153
+
154
+ The convention, shared by every component that has a size worth changing
155
+ there (Card started it; Tile, GridLayout, Bar and Container follow):
156
+
157
+ --{component}-{property}-small
158
+
159
+ applies inside a small container. What wins there, in order:
160
+ 1. the `-small` variable, if you set it;
161
+ 2. otherwise the regular variable, if you set it -- a size you chose on
162
+ purpose is not overridden behind your back;
163
+ 3. otherwise the component's own small default, which for most
164
+ properties is simply its regular default.
165
+
166
+ Container queries can't read a custom property, so the breakpoint itself
167
+ is fixed here rather than themeable. 600px matches Card and SplitPane,
168
+ which already switched there. (The Sidebar's rail-to-sheet switch,
169
+ above, is a separate, layout-chrome decision and keeps its own value.) */
170
+ /* Styles for the small tier: the nearest container is narrow, OR the
171
+ viewport is.
172
+
173
+ The container query is the real answer -- a Tile in a 400px pane on a
174
+ desktop is small. But a container query only matches inside an ancestor
175
+ that *is* a container, and a component dropped straight into a page (no
176
+ Page, no Container around it) has none, so on a phone it would never
177
+ find out. The viewport query covers that: a narrow viewport means every
178
+ container in it is narrow too, so the two never disagree in the
179
+ direction that matters.
180
+
181
+ An element never queries itself, so on a component that is its own
182
+ container (Tile, Bar) this asks about the space it was given. */
151
183
  /* Warning: because we define a fallback
152
184
  media query, the media query can override the container
153
185
  if we stack a bunch of these in a row and aren't thoughtful about the order.
@@ -368,6 +368,38 @@ async function showPopover() {
368
368
  we deliberately avoid everywhere else.
369
369
 
370
370
  One definition, used by both, so they cannot drift again. */
371
+ /* The "small" size tier: a component whose container is this wide or less
372
+ is on a phone, or in a pane about that narrow, and sizes itself for it.
373
+
374
+ The convention, shared by every component that has a size worth changing
375
+ there (Card started it; Tile, GridLayout, Bar and Container follow):
376
+
377
+ --{component}-{property}-small
378
+
379
+ applies inside a small container. What wins there, in order:
380
+ 1. the `-small` variable, if you set it;
381
+ 2. otherwise the regular variable, if you set it -- a size you chose on
382
+ purpose is not overridden behind your back;
383
+ 3. otherwise the component's own small default, which for most
384
+ properties is simply its regular default.
385
+
386
+ Container queries can't read a custom property, so the breakpoint itself
387
+ is fixed here rather than themeable. 600px matches Card and SplitPane,
388
+ which already switched there. (The Sidebar's rail-to-sheet switch,
389
+ above, is a separate, layout-chrome decision and keeps its own value.) */
390
+ /* Styles for the small tier: the nearest container is narrow, OR the
391
+ viewport is.
392
+
393
+ The container query is the real answer -- a Tile in a 400px pane on a
394
+ desktop is small. But a container query only matches inside an ancestor
395
+ that *is* a container, and a component dropped straight into a page (no
396
+ Page, no Container around it) has none, so on a phone it would never
397
+ find out. The viewport query covers that: a narrow viewport means every
398
+ container in it is narrow too, so the two never disagree in the
399
+ direction that matters.
400
+
401
+ An element never queries itself, so on a component that is its own
402
+ container (Tile, Bar) this asks about the space it was given. */
371
403
  /* Warning: because we define a fallback
372
404
  media query, the media query can override the container
373
405
  if we stack a bunch of these in a row and aren't thoughtful about the order.