contain-css-svelte 1.4.3 → 1.5.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 (49) 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/Input.svelte +32 -0
  7. package/dist/controls/RadioButton.svelte +36 -1
  8. package/dist/controls/Select.svelte +35 -0
  9. package/dist/controls/Slider.svelte +32 -0
  10. package/dist/controls/TabItem.svelte +36 -0
  11. package/dist/controls/Toggle.svelte +32 -0
  12. package/dist/dropdowns/DropdownMenu.svelte +53 -1
  13. package/dist/layout/Accordion.svelte +32 -0
  14. package/dist/layout/Bar.svelte +50 -0
  15. package/dist/layout/ColumnContainer.svelte +32 -0
  16. package/dist/layout/Columns.svelte +32 -0
  17. package/dist/layout/Container.svelte +48 -2
  18. package/dist/layout/DataList.svelte +33 -1
  19. package/dist/layout/DataList.svelte.d.ts +7 -0
  20. package/dist/layout/DataListItem.svelte +56 -2
  21. package/dist/layout/Fieldset.svelte +32 -0
  22. package/dist/layout/Form.svelte +32 -0
  23. package/dist/layout/FormItem.svelte +46 -0
  24. package/dist/layout/GridLayout.svelte +59 -0
  25. package/dist/layout/Hero.svelte +42 -0
  26. package/dist/layout/Inline.svelte +37 -1
  27. package/dist/layout/MenuList.svelte +32 -0
  28. package/dist/layout/Page.svelte +137 -10
  29. package/dist/layout/Page.svelte.d.ts +17 -0
  30. package/dist/layout/RowContainer.svelte +32 -0
  31. package/dist/layout/Sidebar.svelte +104 -56
  32. package/dist/layout/SidebarContainer.svelte +32 -0
  33. package/dist/layout/SplitPane.svelte +32 -0
  34. package/dist/layout/Stack.svelte +32 -0
  35. package/dist/layout/TabBar.svelte +127 -8
  36. package/dist/layout/TabBar.svelte.d.ts +16 -0
  37. package/dist/layout/Table.svelte +60 -9
  38. package/dist/layout/Tile.svelte +89 -6
  39. package/dist/misc/CodeInner.svelte +32 -0
  40. package/dist/misc/Progress.svelte +32 -0
  41. package/dist/misc/Tag.svelte +32 -0
  42. package/dist/overlays/Dialog.svelte +32 -0
  43. package/dist/overlays/Tooltip.svelte +32 -0
  44. package/dist/sass/_responsive.scss +43 -0
  45. package/dist/typography/TextLayout.svelte +47 -0
  46. package/dist/vars/defaults.css +4 -1
  47. package/dist/vars/layout.css +4 -1
  48. package/dist/vars/responsive-typography.css +4 -1
  49. package/package.json +1 -1
@@ -103,6 +103,38 @@ const el = $derived(elementProps({ justify, align, gap, wrap, ...restProps }, "i
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.
@@ -135,7 +167,11 @@ const el = $derived(elementProps({ justify, align, gap, wrap, ...restProps }, "i
135
167
  margin-inline: var(--_margin-inline);
136
168
  display: flex;
137
169
  flex-direction: row;
138
- flex-wrap: var(--inline-wrap, nowrap);
170
+ /* Wraps by default, like Bar: a row of tags or choices that can't wrap
171
+ just pushes past its container the moment the container is narrow.
172
+ Pass wrap="nowrap" (or --inline-wrap) for a row that must stay on one
173
+ line -- and then give its children room to shrink. */
174
+ flex-wrap: var(--inline-wrap, wrap);
139
175
  gap: var(--inline-gap, var(--gap));
140
176
  justify-content: var(--inline-justify, flex-start);
141
177
  align-items: var(--inline-align, center);
@@ -97,6 +97,38 @@ const el = $derived(elementProps(restProps, "menu", [
97
97
  we deliberately avoid everywhere else.
98
98
 
99
99
  One definition, used by both, so they cannot drift again. */
100
+ /* The "small" size tier: a component whose container is this wide or less
101
+ is on a phone, or in a pane about that narrow, and sizes itself for it.
102
+
103
+ The convention, shared by every component that has a size worth changing
104
+ there (Card started it; Tile, GridLayout, Bar and Container follow):
105
+
106
+ --{component}-{property}-small
107
+
108
+ applies inside a small container. What wins there, in order:
109
+ 1. the `-small` variable, if you set it;
110
+ 2. otherwise the regular variable, if you set it -- a size you chose on
111
+ purpose is not overridden behind your back;
112
+ 3. otherwise the component's own small default, which for most
113
+ properties is simply its regular default.
114
+
115
+ Container queries can't read a custom property, so the breakpoint itself
116
+ is fixed here rather than themeable. 600px matches Card and SplitPane,
117
+ which already switched there. (The Sidebar's rail-to-sheet switch,
118
+ above, is a separate, layout-chrome decision and keeps its own value.) */
119
+ /* Styles for the small tier: the nearest container is narrow, OR the
120
+ viewport is.
121
+
122
+ The container query is the real answer -- a Tile in a 400px pane on a
123
+ desktop is small. But a container query only matches inside an ancestor
124
+ that *is* a container, and a component dropped straight into a page (no
125
+ Page, no Container around it) has none, so on a phone it would never
126
+ find out. The viewport query covers that: a narrow viewport means every
127
+ container in it is narrow too, so the two never disagree in the
128
+ direction that matters.
129
+
130
+ An element never queries itself, so on a component that is its own
131
+ container (Tile, Bar) this asks about the space it was given. */
100
132
  /* Warning: because we define a fallback
101
133
  media query, the media query can override the container
102
134
  if we stack a bunch of these in a row and aren't thoughtful about the order.
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">import { elementProps } from "../util";
2
2
  import { COLOR_VARS, TYPOGRAPHY_VARS, } from "../styleProps";
3
3
  import { onDestroy, onMount } from "svelte";
4
- const { right = false, sticky = false, hideSidebar = false, hideHeader = false, hideFooter = false, onStickyChange = () => { }, header, footer, sidebar, children, class: className, ...restProps } = $props();
4
+ const { right = false, sticky = false, hideSidebar = false, hideHeader = false, hideFooter = false, compactSidebar, onStickyChange = () => { }, header, footer, sidebar, children, class: className, ...restProps } = $props();
5
5
  /* The shorthands page's own CSS backs, one group per mixin it
6
6
  includes. The Props type is derived from this same array, so what the
7
7
  component accepts and what it emits cannot drift apart. */
@@ -18,9 +18,17 @@ const el = $derived(elementProps(restProps, "page", [
18
18
  const hasSidebar = $derived(Boolean(sidebar) && !hideSidebar);
19
19
  const hasHeader = $derived(Boolean(header) && !hideHeader);
20
20
  const hasFooter = $derived(Boolean(footer) && !hideFooter);
21
+ const sheetSpace = $derived(compactSidebar === "header" || compactSidebar === undefined
22
+ ? hasHeader
23
+ ? "header"
24
+ : "gutter"
25
+ : compactSidebar);
21
26
  // Start "unfrozen" so i.e. scrolling on reload
22
27
  // or hash link works properly
23
28
  let freeze = $state(false);
29
+ /* Measured so the compact sheet button can be centred in the header row
30
+ (see the compact block in the styles). */
31
+ let headerHeight = $state(0);
24
32
  let pageElement;
25
33
  function handleScroll() {
26
34
  if (sticky) {
@@ -70,14 +78,18 @@ onMount(() => {
70
78
  class:hasHeader
71
79
  class:hasSidebar
72
80
  class:hasFooter
81
+ class:sheet-header={sheetSpace === "header"}
82
+ class:sheet-gutter={sheetSpace === "gutter"}
83
+ class:sheet-symmetric={sheetSpace === "symmetric"}
84
+ class:sheet-overlap={sheetSpace === "overlap"}
73
85
  bind:this={pageElement}
74
86
  {...el}
75
87
  >
76
- <header>
88
+ <header bind:clientHeight={headerHeight}>
77
89
  {#if hasHeader}{@render header?.()}{/if}
78
90
  </header>
79
91
  <div class="side-by-side">
80
- <div class="aside">
92
+ <div class="aside" style:--_page-header-height="{headerHeight}px">
81
93
  {#if hasSidebar}{@render sidebar?.()}{/if}
82
94
  </div>
83
95
  <div class="content">
@@ -160,6 +172,38 @@ onMount(() => {
160
172
  we deliberately avoid everywhere else.
161
173
 
162
174
  One definition, used by both, so they cannot drift again. */
175
+ /* The "small" size tier: a component whose container is this wide or less
176
+ is on a phone, or in a pane about that narrow, and sizes itself for it.
177
+
178
+ The convention, shared by every component that has a size worth changing
179
+ there (Card started it; Tile, GridLayout, Bar and Container follow):
180
+
181
+ --{component}-{property}-small
182
+
183
+ applies inside a small container. What wins there, in order:
184
+ 1. the `-small` variable, if you set it;
185
+ 2. otherwise the regular variable, if you set it -- a size you chose on
186
+ purpose is not overridden behind your back;
187
+ 3. otherwise the component's own small default, which for most
188
+ properties is simply its regular default.
189
+
190
+ Container queries can't read a custom property, so the breakpoint itself
191
+ is fixed here rather than themeable. 600px matches Card and SplitPane,
192
+ which already switched there. (The Sidebar's rail-to-sheet switch,
193
+ above, is a separate, layout-chrome decision and keeps its own value.) */
194
+ /* Styles for the small tier: the nearest container is narrow, OR the
195
+ viewport is.
196
+
197
+ The container query is the real answer -- a Tile in a 400px pane on a
198
+ desktop is small. But a container query only matches inside an ancestor
199
+ that *is* a container, and a component dropped straight into a page (no
200
+ Page, no Container around it) has none, so on a phone it would never
201
+ find out. The viewport query covers that: a narrow viewport means every
202
+ container in it is narrow too, so the two never disagree in the
203
+ direction that matters.
204
+
205
+ An element never queries itself, so on a component that is its own
206
+ container (Tile, Bar) this asks about the space it was given. */
163
207
  /* Warning: because we define a fallback
164
208
  media query, the media query can override the container
165
209
  if we stack a bunch of these in a row and aren't thoughtful about the order.
@@ -305,6 +349,12 @@ footer {
305
349
  justify-content: stretch;
306
350
  }
307
351
 
352
+ .page {
353
+ /* Containing block for the compact sidebar, which spans the whole page
354
+ (header included) once it has a header to sit in. */
355
+ position: relative;
356
+ }
357
+
308
358
  .page.sticky {
309
359
  position: sticky;
310
360
  top: 0;
@@ -315,6 +365,22 @@ footer {
315
365
  }
316
366
 
317
367
  .content {
368
+ /* The compact sidebar's reserved strip: at least the content's own
369
+ padding, at least a sheet button plus a gap. Declared here, where
370
+ --_padding (contentPadding) is set, so it resolves against it. */
371
+ --_page-gutter: max(
372
+ var(--_padding, var(--padding, 0px)),
373
+ var(
374
+ --sidebar-compact-side-padding,
375
+ calc(
376
+ max(
377
+ var(--sidebar-icon-width, 0.65rem),
378
+ var(--sidebar-icon-height, 1rem),
379
+ var(--icon-size, 32px)
380
+ ) + var(--gap, 8px)
381
+ )
382
+ )
383
+ );
318
384
  overflow-y: auto;
319
385
  scrollbar-width: thin;
320
386
  scrollbar-color: transparent transparent;
@@ -486,25 +552,86 @@ footer {
486
552
  .side-by-side {
487
553
  gap: 0;
488
554
  }
555
+ .page > div > .content {
556
+ min-width: 0;
557
+ }
558
+ /* The sidebar becomes a floating sheet, summoned by a button. The aside
559
+ holding it is a transparent strip laid over the content; only the
560
+ Sidebar's button and open sheet catch clicks, never the strip. What
561
+ room the content makes for the button is the `compactSidebar` mode,
562
+ below. */
489
563
  .hasSidebar > .side-by-side > .aside {
490
564
  position: absolute;
491
565
  inset-block: 0;
492
566
  inset-inline-start: 0;
493
567
  z-index: 3;
494
568
  display: flex;
495
- }
496
- .hasSidebar > .side-by-side > .content {
497
- padding-inline-start: max(var(--_padding, var(--padding, 0px)), var(--sidebar-compact-side-padding, calc(max(var(--sidebar-icon-width, 0.65rem), var(--sidebar-icon-height, 1rem), var(--icon-size, 32px)) + var(--gap, 8px))));
569
+ pointer-events: none;
498
570
  }
499
571
  .hasSidebar.right > .side-by-side > .aside {
500
572
  inset-inline-start: auto;
501
573
  inset-inline-end: 0;
502
574
  }
503
- .hasSidebar.right > .side-by-side > .content {
575
+ /* "gutter": a button-wide strip down the sidebar's side of the content. */
576
+ .hasSidebar.sheet-gutter:not(.right) > .side-by-side > .content {
577
+ padding-inline-start: var(--_page-gutter);
578
+ }
579
+ .hasSidebar.sheet-gutter.right > .side-by-side > .content {
504
580
  padding-inline-start: var(--_padding, var(--padding, 0px));
505
- padding-inline-end: max(var(--_padding, var(--padding, 0px)), var(--sidebar-compact-side-padding, calc(max(var(--sidebar-icon-width, 0.65rem), var(--sidebar-icon-height, 1rem), var(--icon-size, 32px)) + var(--gap, 8px))));
581
+ padding-inline-end: var(--_page-gutter);
506
582
  }
507
- .page > div > .content {
508
- min-width: 0;
583
+ /* "symmetric": the same strip on both sides, so the column stays
584
+ centred. */
585
+ .hasSidebar.sheet-symmetric > .side-by-side > .content {
586
+ padding-inline: var(--_page-gutter);
587
+ }
588
+ /* "overlap" and "header": the content keeps its ordinary, even padding.
589
+ Under "overlap" the button simply floats over the content's corner. */
590
+ .hasSidebar.sheet-overlap > .side-by-side > .content,
591
+ .hasSidebar.sheet-header > .side-by-side > .content {
592
+ padding-inline: var(--_padding, var(--padding, 0px));
593
+ }
594
+ /* "header": the button sits where a menu button is on every phone.
595
+ - The aside spans the whole page, header included, instead of just
596
+ the row beside the content, so the button can sit in the header row
597
+ and the open sheet covers the page top to bottom, like any drawer.
598
+ - The button is centred in the header's measured height.
599
+ - The header's content steps aside by the button's width. */
600
+ .hasSidebar.sheet-header > .side-by-side {
601
+ position: static;
602
+ }
603
+ .hasSidebar.sheet-header > .side-by-side > .aside {
604
+ --sidebar-sheet-button-top: calc(
605
+ (var(--_page-header-height, 0px) - var(--_page-sheet-button-size)) / 2
606
+ );
509
607
  }
608
+ /* It's the header's own content (a Bar, usually) that makes room, not
609
+ the header box: padding the header would leave a strip of page colour
610
+ beside a coloured bar. Children passed CSS variables arrive wrapped in
611
+ a display:contents <svelte-css-wrapper>, hence the second selector. */
612
+ .hasSidebar.sheet-header:not(.right) > header > :global(*:not(svelte-css-wrapper)),
613
+ .hasSidebar.sheet-header:not(.right) > header > :global(svelte-css-wrapper > *) {
614
+ padding-inline-start: var(--_page-header-inset);
615
+ }
616
+ .hasSidebar.sheet-header.right > header > :global(*:not(svelte-css-wrapper)),
617
+ .hasSidebar.sheet-header.right > header > :global(svelte-css-wrapper > *) {
618
+ padding-inline-end: var(--_page-header-inset);
619
+ }
620
+ }
621
+ .page {
622
+ /* Same size the Sidebar gives its sheet button. */
623
+ --_page-sheet-button-size: max(
624
+ var(--sidebar-icon-height, 1rem),
625
+ var(--icon-size, 32px)
626
+ );
627
+ --_page-header-inset: var(
628
+ --page-header-sidebar-inset,
629
+ calc(
630
+ max(
631
+ var(--sidebar-icon-width, 0.65rem),
632
+ var(--sidebar-icon-height, 1rem),
633
+ var(--icon-size, 32px)
634
+ ) + 2 * var(--gap, 8px)
635
+ )
636
+ );
510
637
  }</style>
@@ -8,6 +8,23 @@ declare const Page: import("svelte").Component<ContainProps<HTMLAttributes<HTMLE
8
8
  hideSidebar?: boolean;
9
9
  hideHeader?: boolean;
10
10
  hideFooter?: boolean;
11
+ /**
12
+ * Where the sidebar's menu button goes, and what room is made for it,
13
+ * once the page is narrow enough that the sidebar becomes a sheet:
14
+ *
15
+ * - `"header"` (default when there is a header): the button sits in
16
+ * the header row and the header's content steps aside for it. The
17
+ * page content keeps its ordinary, even padding.
18
+ * - `"gutter"` (default without a header): a button-wide strip is
19
+ * reserved down the sidebar's side of the content.
20
+ * - `"symmetric"`: the same strip on both sides, so the column stays
21
+ * centred at the cost of width on both.
22
+ * - `"overlap"`: no room is made; the button floats over the top
23
+ * corner of the content. Fine when that corner is empty.
24
+ *
25
+ * `"header"` with no header showing falls back to `"gutter"`.
26
+ */
27
+ compactSidebar?: "header" | "gutter" | "symmetric" | "overlap";
11
28
  onStickyChange?: (stuck: boolean) => void;
12
29
  header?: Snippet;
13
30
  footer?: Snippet;
@@ -92,6 +92,38 @@ const el = $derived(elementProps(restProps, "row-container", ROW_CONTAINER_VARS)
92
92
  we deliberately avoid everywhere else.
93
93
 
94
94
  One definition, used by both, so they cannot drift again. */
95
+ /* The "small" size tier: a component whose container is this wide or less
96
+ is on a phone, or in a pane about that narrow, and sizes itself for it.
97
+
98
+ The convention, shared by every component that has a size worth changing
99
+ there (Card started it; Tile, GridLayout, Bar and Container follow):
100
+
101
+ --{component}-{property}-small
102
+
103
+ applies inside a small container. What wins there, in order:
104
+ 1. the `-small` variable, if you set it;
105
+ 2. otherwise the regular variable, if you set it -- a size you chose on
106
+ purpose is not overridden behind your back;
107
+ 3. otherwise the component's own small default, which for most
108
+ properties is simply its regular default.
109
+
110
+ Container queries can't read a custom property, so the breakpoint itself
111
+ is fixed here rather than themeable. 600px matches Card and SplitPane,
112
+ which already switched there. (The Sidebar's rail-to-sheet switch,
113
+ above, is a separate, layout-chrome decision and keeps its own value.) */
114
+ /* Styles for the small tier: the nearest container is narrow, OR the
115
+ viewport is.
116
+
117
+ The container query is the real answer -- a Tile in a 400px pane on a
118
+ desktop is small. But a container query only matches inside an ancestor
119
+ that *is* a container, and a component dropped straight into a page (no
120
+ Page, no Container around it) has none, so on a phone it would never
121
+ find out. The viewport query covers that: a narrow viewport means every
122
+ container in it is narrow too, so the two never disagree in the
123
+ direction that matters.
124
+
125
+ An element never queries itself, so on a component that is its own
126
+ container (Tile, Bar) this asks about the space it was given. */
95
127
  /* Warning: because we define a fallback
96
128
  media query, the media query can override the container
97
129
  if we stack a bunch of these in a row and aren't thoughtful about the order.