contain-css-svelte 1.5.0 → 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.
@@ -0,0 +1,147 @@
1
+ <script lang="ts">import { elementProps } from "../util";
2
+ import { COLOR_VARS, RADIUS_VARS, TYPOGRAPHY_VARS, } from "../styleProps";
3
+ const GROUPED_TOGGLE_VARS = [
4
+ ...COLOR_VARS,
5
+ ...RADIUS_VARS,
6
+ ...TYPOGRAPHY_VARS,
7
+ ];
8
+ let { children, class: className, role = "group", ...restProps } = $props();
9
+ const el = $derived(elementProps(restProps, "grouped-toggle", [
10
+ ...GROUPED_TOGGLE_VARS,
11
+ "padding",
12
+ "width",
13
+ "height",
14
+ ]));
15
+ </script>
16
+
17
+ <div class={["grouped-toggle", className]} {role} {...el}>
18
+ {@render children?.()}
19
+ </div>
20
+
21
+ <style>/* Per-level heading (h1-h6) box/typography properties, resolved to PRIVATE,
22
+ inheriting custom properties (--_h1-color, --_h2-font-size, etc) rather than
23
+ assigned directly to a matched h1-h6 selector. Nothing in this file matches
24
+ an actual heading element anymore -- see typography-context.css (vars/), which
25
+ is the single, global, :where()-scoped rule that actually reads these vars
26
+ and applies them to real elements. That split is what lets a component
27
+ nested arbitrarily deep (through <article>, <Tile>, whatever) still get the
28
+ nearest ancestor's heading styling: these are ordinary inherited custom
29
+ properties, so DOM proximity decides which component's values apply, not
30
+ selector specificity or stylesheet order. See AGENTS-INTERNAL.md. */
31
+ /* All six heading levels' context vars in one call -- what every
32
+ "typography container" component (Container, Card, Tile, TextLayout, ...)
33
+ includes on itself once, alongside the shared --_heading-* vars below. */
34
+ /* Shared prose-measure group: p, blockquote, dl, ul, ol. li/dt/dd don't get
35
+ their own vars -- they're never wider than their ul/ol/dl parent's own
36
+ max-width box, so they're already bounded for free by ordinary block
37
+ layout, not by anything selector-based reaching them individually. */
38
+ /* p specifically also gets the fuller typography-props-bare treatment
39
+ (paragraph-prefixed), which -- same as in the original selector-based
40
+ version -- is declared/consumed AFTER the shared prose vars above, so it
41
+ wins for whichever properties both groups set. */
42
+ /* The first paragraph in a typography context (p:first-of-type, or a p right
43
+ after a heading) gets its own, slightly different treatment -- larger lede
44
+ text, no top margin. Structural matching (:first-of-type, `h1 + p`) still
45
+ has to happen where the real element is, in the global consumer rule; only
46
+ the VALUES move to context vars here. */
47
+ /* Text reset for an overlay's root element: a tooltip, popover menu or dialog
48
+ that sits in the DOM wherever its trigger was placed.
49
+
50
+ The top layer changes how an element paints and where it is positioned, not
51
+ what it inherits. A popover declared inside a `white-space: nowrap` table
52
+ cell is nowrap too, and one inside a bold, uppercase, centred header is
53
+ bold, uppercase and centred -- styles that belong to the trigger's context,
54
+ not to the overlay. typography-props() doesn't stop that: its chains end in
55
+ `unset`, which for an inherited property means "take the parent's value".
56
+
57
+ So this re-declares every inherited text property with a real default at
58
+ the end of the same variable chain. The vars still win (--tooltip-*, then
59
+ the category, then the global), and custom properties still inherit --
60
+ that is how theming reaches an overlay -- but when nothing is set, the
61
+ overlay gets its own defaults instead of its host's.
62
+
63
+ INCLUDE IT AFTER typography-props() / typography-container-props() in the
64
+ same rule: it has to beat their `unset` fallbacks by source order.
65
+
66
+ Deliberately left inheriting:
67
+ - direction, writing-mode: the document's script direction is the
68
+ overlay's too.
69
+ - visibility: a hidden subtree should not be able to show an overlay.
70
+ - font-family: every theme defines --font-family at :root, and there is no
71
+ neutral value to fall back to that would beat "the page's font". */
72
+ /* Logical, not top/bottom/left/right: margin-block/margin-inline already take
73
+ one value (both sides equal) or two (start end), so there is no separate
74
+ "which side" prop to remember and no 4-value shorthand order to get wrong.
75
+ Defaults to 0 on purpose -- unlike padding-props above, a margin that
76
+ appeared by default on every consumer would be a silent layout change the
77
+ day this mixin got added to a new component. Callers opt in with an actual
78
+ value. (Container does not use this mixin: it wants --gap/auto as its
79
+ defaults, which do not fit the "0 unless asked" contract here.) */
80
+ /* The width at or below which a Sidebar swaps its grab-bar rail for the sheet
81
+ affordance -- a floating button that opens a panel over the content.
82
+
83
+ Page has to agree with Sidebar about this, because Page is what floats the
84
+ aside out of flow and reserves the strip of padding the sheet button sits
85
+ in. They used to disagree: Sidebar switched at 512px and Page at 600px,
86
+ which left a band between 513 and 600 where Page had already lifted the
87
+ aside out of the layout while the Sidebar inside it was still showing a
88
+ rail -- a grab bar floating on top of the content, which is the affordance
89
+ we deliberately avoid everywhere else.
90
+
91
+ One definition, used by both, so they cannot drift again. */
92
+ /* The "small" size tier: a component whose container is this wide or less
93
+ is on a phone, or in a pane about that narrow, and sizes itself for it.
94
+
95
+ The convention, shared by every component that has a size worth changing
96
+ there (Card started it; Tile, GridLayout, Bar and Container follow):
97
+
98
+ --{component}-{property}-small
99
+
100
+ applies inside a small container. What wins there, in order:
101
+ 1. the `-small` variable, if you set it;
102
+ 2. otherwise the regular variable, if you set it -- a size you chose on
103
+ purpose is not overridden behind your back;
104
+ 3. otherwise the component's own small default, which for most
105
+ properties is simply its regular default.
106
+
107
+ Container queries can't read a custom property, so the breakpoint itself
108
+ is fixed here rather than themeable. 600px matches Card and SplitPane,
109
+ which already switched there. (The Sidebar's rail-to-sheet switch,
110
+ above, is a separate, layout-chrome decision and keeps its own value.) */
111
+ /* Styles for the small tier: the nearest container is narrow, OR the
112
+ viewport is.
113
+
114
+ The container query is the real answer -- a Tile in a 400px pane on a
115
+ desktop is small. But a container query only matches inside an ancestor
116
+ that *is* a container, and a component dropped straight into a page (no
117
+ Page, no Container around it) has none, so on a phone it would never
118
+ find out. The viewport query covers that: a narrow viewport means every
119
+ container in it is narrow too, so the two never disagree in the
120
+ direction that matters.
121
+
122
+ An element never queries itself, so on a component that is its own
123
+ container (Tile, Bar) this asks about the space it was given. */
124
+ /* Warning: because we define a fallback
125
+ media query, the media query can override the container
126
+ if we stack a bunch of these in a row and aren't thoughtful about the order.
127
+ Put min-width queries *after* max-width queries so that smaller
128
+ container queries don't get their styles overridden by large media queries.
129
+ */
130
+ /* Convenience groupings */
131
+ .grouped-toggle {
132
+ display: inline-flex;
133
+ align-items: stretch;
134
+ /* A grid or flex parent stretches a block-level item to fill its track
135
+ by default (DemoWithCode's own layout does exactly this) -- `inline-
136
+ flex` alone doesn't protect against that, since being a grid/flex
137
+ *item* is a separate question from this element's own `display`.
138
+ Without this, the row ends up wider than its children, which leaves
139
+ dead space after the last segment and strands the rounded corner out
140
+ past it, with the last segment itself still square. A segmented
141
+ control is sized to its content, like TabBar/a button group, not
142
+ full-bleed by default. */
143
+ width: fit-content;
144
+ border-radius: var(--grouped-toggle-border-radius, var(--border-radius, var(--border-radius, 4px)));
145
+ overflow: hidden;
146
+ border: var(--grouped-toggle-border, var(--border, 1px solid var(--border-color, rgba(127, 127, 127, 0.4))));
147
+ }</style>
@@ -0,0 +1,9 @@
1
+ import type { Snippet } from "svelte";
2
+ import type { HTMLAttributes } from "svelte/elements";
3
+ import type { BaseStyleProps, ContainProps } from "../types";
4
+ import { type StyleProps } from "../styleProps";
5
+ declare const GroupedToggle: import("svelte").Component<ContainProps<HTMLAttributes<HTMLDivElement>, {
6
+ children?: Snippet;
7
+ }, BaseStyleProps & StyleProps<readonly ["bg", "fg", "borderRadius", "fontFamily", "fontSize", "fontWeight", "lineHeight", "letterSpacing", "textAlign", "textTransform", "textDecoration", "fontVariant"]>>, {}, "">;
8
+ type GroupedToggle = ReturnType<typeof GroupedToggle>;
9
+ export default GroupedToggle;
@@ -0,0 +1,452 @@
1
+ <script lang="ts">import { elementProps } from "../util";
2
+ import { COLOR_VARS, TYPOGRAPHY_VARS, } from "../styleProps";
3
+ import Tooltip from "../overlays/Tooltip.svelte";
4
+ /**
5
+ * One segment of a `GroupedToggle` row -- an independent boolean, not a
6
+ * mutually-exclusive choice. Several segments in the same row can be
7
+ * `checked` at once (that's the whole point: a thing can be filtered *and*
8
+ * grouped by at the same time), so this is built on the same semantics as
9
+ * `Checkbox`, not `RadioButton` -- it renders a real `aria-pressed` toggle
10
+ * button rather than a radio input, since there's no single form value
11
+ * being submitted here, just live UI state (the same reasoning a text
12
+ * editor's Bold/Italic/Underline toggle buttons use). For a mutually-
13
+ * exclusive row with the same look, use `GroupedRadioItem` instead -- the
14
+ * keyboard contract genuinely differs (roving-tabindex arrow-key
15
+ * navigation vs. each segment being its own Tab stop), so it's a separate
16
+ * component rather than a mode flag on this one.
17
+ */
18
+ const GROUPED_TOGGLE_ITEM_VARS = [...COLOR_VARS, ...TYPOGRAPHY_VARS];
19
+ let { checked = $bindable(false), disabled = false, disabledReason = "", children, class: className, onclick, ...restProps } = $props();
20
+ const el = $derived(elementProps(restProps, "grouped-toggle-item", [
21
+ ...GROUPED_TOGGLE_ITEM_VARS,
22
+ "padding",
23
+ "width",
24
+ "height",
25
+ "checkedBg",
26
+ "checkedFg",
27
+ ]));
28
+ const showTooltip = $derived(disabled && disabledReason.length > 0);
29
+ /**
30
+ * A native `disabled` button can never receive focus, which would make a
31
+ * `disabledReason` tooltip -- documented above as shown "on hover/focus" --
32
+ * unreachable for keyboard users, exactly the audience an explained-disabled
33
+ * segment is for. So when there's a reason to show, this stays a real Tab
34
+ * stop with `aria-disabled` instead of `disabled`, and `handleClick` does
35
+ * the disabled-guarding the native attribute would otherwise have done.
36
+ * Plain `disabled` (no reason) keeps the native attribute -- nothing to
37
+ * read on focus, so there's no reason to pull it out of the Tab order.
38
+ */
39
+ const useAriaDisabled = $derived(showTooltip);
40
+ function handleClick(event) {
41
+ if (disabled)
42
+ return;
43
+ checked = !checked;
44
+ onclick?.(event);
45
+ }
46
+ </script>
47
+
48
+ {#snippet toggleButton()}
49
+ <button
50
+ type="button"
51
+ class={["grouped-toggle-item", className]}
52
+ class:checked
53
+ disabled={disabled && !useAriaDisabled}
54
+ aria-disabled={useAriaDisabled ? "true" : undefined}
55
+ aria-pressed={checked}
56
+ onclick={handleClick}
57
+ {...el}
58
+ >
59
+ {@render children?.()}
60
+ </button>
61
+ {/snippet}
62
+
63
+ {#if showTooltip}
64
+ <Tooltip tooltipText={disabledReason}>
65
+ {@render toggleButton()}
66
+ </Tooltip>
67
+ {:else}
68
+ {@render toggleButton()}
69
+ {/if}
70
+
71
+ <style>/* Per-level heading (h1-h6) box/typography properties, resolved to PRIVATE,
72
+ inheriting custom properties (--_h1-color, --_h2-font-size, etc) rather than
73
+ assigned directly to a matched h1-h6 selector. Nothing in this file matches
74
+ an actual heading element anymore -- see typography-context.css (vars/), which
75
+ is the single, global, :where()-scoped rule that actually reads these vars
76
+ and applies them to real elements. That split is what lets a component
77
+ nested arbitrarily deep (through <article>, <Tile>, whatever) still get the
78
+ nearest ancestor's heading styling: these are ordinary inherited custom
79
+ properties, so DOM proximity decides which component's values apply, not
80
+ selector specificity or stylesheet order. See AGENTS-INTERNAL.md. */
81
+ /* All six heading levels' context vars in one call -- what every
82
+ "typography container" component (Container, Card, Tile, TextLayout, ...)
83
+ includes on itself once, alongside the shared --_heading-* vars below. */
84
+ /* Shared prose-measure group: p, blockquote, dl, ul, ol. li/dt/dd don't get
85
+ their own vars -- they're never wider than their ul/ol/dl parent's own
86
+ max-width box, so they're already bounded for free by ordinary block
87
+ layout, not by anything selector-based reaching them individually. */
88
+ /* p specifically also gets the fuller typography-props-bare treatment
89
+ (paragraph-prefixed), which -- same as in the original selector-based
90
+ version -- is declared/consumed AFTER the shared prose vars above, so it
91
+ wins for whichever properties both groups set. */
92
+ /* The first paragraph in a typography context (p:first-of-type, or a p right
93
+ after a heading) gets its own, slightly different treatment -- larger lede
94
+ text, no top margin. Structural matching (:first-of-type, `h1 + p`) still
95
+ has to happen where the real element is, in the global consumer rule; only
96
+ the VALUES move to context vars here. */
97
+ /* Text reset for an overlay's root element: a tooltip, popover menu or dialog
98
+ that sits in the DOM wherever its trigger was placed.
99
+
100
+ The top layer changes how an element paints and where it is positioned, not
101
+ what it inherits. A popover declared inside a `white-space: nowrap` table
102
+ cell is nowrap too, and one inside a bold, uppercase, centred header is
103
+ bold, uppercase and centred -- styles that belong to the trigger's context,
104
+ not to the overlay. typography-props() doesn't stop that: its chains end in
105
+ `unset`, which for an inherited property means "take the parent's value".
106
+
107
+ So this re-declares every inherited text property with a real default at
108
+ the end of the same variable chain. The vars still win (--tooltip-*, then
109
+ the category, then the global), and custom properties still inherit --
110
+ that is how theming reaches an overlay -- but when nothing is set, the
111
+ overlay gets its own defaults instead of its host's.
112
+
113
+ INCLUDE IT AFTER typography-props() / typography-container-props() in the
114
+ same rule: it has to beat their `unset` fallbacks by source order.
115
+
116
+ Deliberately left inheriting:
117
+ - direction, writing-mode: the document's script direction is the
118
+ overlay's too.
119
+ - visibility: a hidden subtree should not be able to show an overlay.
120
+ - font-family: every theme defines --font-family at :root, and there is no
121
+ neutral value to fall back to that would beat "the page's font". */
122
+ /* Logical, not top/bottom/left/right: margin-block/margin-inline already take
123
+ one value (both sides equal) or two (start end), so there is no separate
124
+ "which side" prop to remember and no 4-value shorthand order to get wrong.
125
+ Defaults to 0 on purpose -- unlike padding-props above, a margin that
126
+ appeared by default on every consumer would be a silent layout change the
127
+ day this mixin got added to a new component. Callers opt in with an actual
128
+ value. (Container does not use this mixin: it wants --gap/auto as its
129
+ defaults, which do not fit the "0 unless asked" contract here.) */
130
+ /* The width at or below which a Sidebar swaps its grab-bar rail for the sheet
131
+ affordance -- a floating button that opens a panel over the content.
132
+
133
+ Page has to agree with Sidebar about this, because Page is what floats the
134
+ aside out of flow and reserves the strip of padding the sheet button sits
135
+ in. They used to disagree: Sidebar switched at 512px and Page at 600px,
136
+ which left a band between 513 and 600 where Page had already lifted the
137
+ aside out of the layout while the Sidebar inside it was still showing a
138
+ rail -- a grab bar floating on top of the content, which is the affordance
139
+ we deliberately avoid everywhere else.
140
+
141
+ One definition, used by both, so they cannot drift again. */
142
+ /* The "small" size tier: a component whose container is this wide or less
143
+ is on a phone, or in a pane about that narrow, and sizes itself for it.
144
+
145
+ The convention, shared by every component that has a size worth changing
146
+ there (Card started it; Tile, GridLayout, Bar and Container follow):
147
+
148
+ --{component}-{property}-small
149
+
150
+ applies inside a small container. What wins there, in order:
151
+ 1. the `-small` variable, if you set it;
152
+ 2. otherwise the regular variable, if you set it -- a size you chose on
153
+ purpose is not overridden behind your back;
154
+ 3. otherwise the component's own small default, which for most
155
+ properties is simply its regular default.
156
+
157
+ Container queries can't read a custom property, so the breakpoint itself
158
+ is fixed here rather than themeable. 600px matches Card and SplitPane,
159
+ which already switched there. (The Sidebar's rail-to-sheet switch,
160
+ above, is a separate, layout-chrome decision and keeps its own value.) */
161
+ /* Styles for the small tier: the nearest container is narrow, OR the
162
+ viewport is.
163
+
164
+ The container query is the real answer -- a Tile in a 400px pane on a
165
+ desktop is small. But a container query only matches inside an ancestor
166
+ that *is* a container, and a component dropped straight into a page (no
167
+ Page, no Container around it) has none, so on a phone it would never
168
+ find out. The viewport query covers that: a narrow viewport means every
169
+ container in it is narrow too, so the two never disagree in the
170
+ direction that matters.
171
+
172
+ An element never queries itself, so on a component that is its own
173
+ container (Tile, Bar) this asks about the space it was given. */
174
+ /* Warning: because we define a fallback
175
+ media query, the media query can override the container
176
+ if we stack a bunch of these in a row and aren't thoughtful about the order.
177
+ Put min-width queries *after* max-width queries so that smaller
178
+ container queries don't get their styles overridden by large media queries.
179
+ */
180
+ /* Convenience groupings */
181
+ .grouped-toggle-item {
182
+ --link-bg: var(--grouped-toggle-item-link-bg, var(--control-link-bg, var(--secondary-link-bg, inherit)));
183
+ --link-fg: var(--grouped-toggle-item-link-fg, var(--control-link-fg, var(--secondary-link-fg, inherit)));
184
+ --_bg-mix-color: var(--grouped-toggle-item-bg-mix-color, var(--control-bg-mix-color, var(--secondary-bg-mix-color, var(--bg-mix-color, transparent))));
185
+ --_bg-mix-amount: var(--grouped-toggle-item-bg-mix-amount, var(--control-bg-mix-amount, var(--secondary-bg-mix-amount, var(--bg-mix-amount, 0%))));
186
+ --_fg-mix-color: var(--grouped-toggle-item-fg-mix-color, var(--control-fg-mix-color, var(--secondary-fg-mix-color, var(--fg-mix-color, transparent))));
187
+ --_fg-mix-amount: var(--grouped-toggle-item-fg-mix-amount, var(--control-fg-mix-amount, var(--secondary-fg-mix-amount, var(--fg-mix-amount, 0%))));
188
+ --_bg-base: var(--grouped-toggle-item-bg, var(--control-bg, var(--secondary-bg, var(--bg, unset))));
189
+ --_fg-base: var(--grouped-toggle-item-fg, var(--control-fg, var(--secondary-fg, var(--fg, unset))));
190
+ --_background-color: color-mix(
191
+ in srgb,
192
+ var(--_bg-base),
193
+ var(--_bg-mix-color) var(--_bg-mix-amount)
194
+ );
195
+ --_color: color-mix(
196
+ in srgb,
197
+ var(--_fg-base),
198
+ var(--_fg-mix-color) var(--_fg-mix-amount)
199
+ );
200
+ --muted-color: var(--muted-color-override, var(--_background-color));
201
+ --primary-text-fg: var(
202
+ --primary-text-fg-override,
203
+ color-mix(
204
+ in srgb,
205
+ var(--_color),
206
+ var(--primary-color)
207
+ var(--primary-text-amount, var(--text-amount, 50%))
208
+ )
209
+ );
210
+ --secondary-text-fg: var(
211
+ --secondary-text-fg-override,
212
+ color-mix(
213
+ in srgb,
214
+ var(--_color),
215
+ var(--secondary-color)
216
+ var(--secondary-text-amount, var(--text-amount, 50%))
217
+ )
218
+ );
219
+ --info-text-fg: var(
220
+ --info-text-fg-override,
221
+ color-mix(
222
+ in srgb,
223
+ var(--_color),
224
+ var(--info-color)
225
+ var(--info-text-amount, var(--text-amount, 50%))
226
+ )
227
+ );
228
+ --success-text-fg: var(
229
+ --success-text-fg-override,
230
+ color-mix(
231
+ in srgb,
232
+ var(--_color),
233
+ var(--success-color)
234
+ var(--success-text-amount, var(--text-amount, 50%))
235
+ )
236
+ );
237
+ --warning-text-fg: var(
238
+ --warning-text-fg-override,
239
+ color-mix(
240
+ in srgb,
241
+ var(--_color),
242
+ var(--warning-color)
243
+ var(--warning-text-amount, var(--text-amount, 50%))
244
+ )
245
+ );
246
+ --danger-text-fg: var(
247
+ --danger-text-fg-override,
248
+ color-mix(
249
+ in srgb,
250
+ var(--_color),
251
+ var(--danger-color)
252
+ var(--danger-text-amount, var(--text-amount, 50%))
253
+ )
254
+ );
255
+ --muted-text-fg: var(
256
+ --muted-text-fg-override,
257
+ color-mix(
258
+ in srgb,
259
+ var(--_color),
260
+ var(--muted-color)
261
+ var(--muted-text-amount, var(--text-amount, 50%))
262
+ )
263
+ );
264
+ background-color: var(--_background-color);
265
+ color: var(--_color);
266
+ font-family: var(--grouped-toggle-item-font-family, var(--control-font-family, inherit));
267
+ text-transform: var(--grouped-toggle-item-text-transform, var(--control-text-transform, inherit));
268
+ text-decoration: var(--grouped-toggle-item-text-decoration, var(--control-text-decoration, inherit));
269
+ --_font-size: var(--grouped-toggle-item-font-size, var(--control-font-size, inherit));
270
+ font-size: var(--_font-size);
271
+ font-weight: var(--grouped-toggle-item-font-weight, var(--control-font-weight, inherit));
272
+ line-height: var(--grouped-toggle-item-line-height, var(--control-line-height, inherit));
273
+ letter-spacing: var(--grouped-toggle-item-letter-spacing, var(--control-letter-spacing, inherit));
274
+ text-indent: var(--grouped-toggle-item-indent, var(--control-indent, inherit));
275
+ font-variant: var(--grouped-toggle-item-font-variant, var(--control-font-variant, inherit));
276
+ text-align: var(--grouped-toggle-item-text-align, var(--control-text-align, inherit));
277
+ cursor: var(--grouped-toggle-item-cursor, var(--clickable-cursor, var(--cursor, pointer)));
278
+ /* `.grouped-toggle` clips overflow (same reason TabBar does for
279
+ TabItem), so the standard outer `focusable()` ring gets cut off,
280
+ worst on the rounded first/last segments -- an inset ring stays
281
+ inside the clip instead. */
282
+ }
283
+
284
+ .grouped-toggle-item:focus-visible {
285
+ outline: var(--focus-color, -webkit-focus-ring-color) auto 1px;
286
+ outline-offset: var(--focus-inset-outline-offset, -3px);
287
+ }
288
+
289
+ .grouped-toggle-item {
290
+ border: none;
291
+ border-inline-start: var(--grouped-toggle-divider, var(--grouped-toggle-item-divider, var(--divider, 1px solid var(--border-color, rgba(127, 127, 127, 0.4)))));
292
+ margin: 0;
293
+ padding: var(--grouped-toggle-item-padding, var(--padding, 0.35em 0.75em));
294
+ min-width: 2em;
295
+ /* `auto` basis, not `0%`: a basis of 0 would force every segment to the
296
+ same final width regardless of label length, even in the ordinary
297
+ case where the row is exactly as wide as its content and there's
298
+ nothing extra to distribute. `auto` only starts sharing out space
299
+ once the row is wider than its segments' own content -- a consumer
300
+ overriding the width on GroupedToggle -- so "Light" and "Earthtones"
301
+ keep their natural proportions unless something is actually stretching
302
+ the row. */
303
+ flex: 1 1 auto;
304
+ transition: filter var(--grouped-toggle-item-transition, var(--clickable-transition, var(--control-transition, var(--120ms ease-transition, var(--transition, 300ms))))), background-color var(--grouped-toggle-item-transition, var(--clickable-transition, var(--control-transition, var(--120ms ease-transition, var(--transition, 300ms))))), border-color var(--grouped-toggle-item-transition, var(--clickable-transition, var(--control-transition, var(--120ms ease-transition, var(--transition, 300ms))))), box-shadow var(--grouped-toggle-item-transition, var(--clickable-transition, var(--control-transition, var(--120ms ease-transition, var(--transition, 300ms))))), transform var(--grouped-toggle-item-transition, var(--clickable-transition, var(--control-transition, var(--120ms ease-transition, var(--transition, 300ms)))));
305
+ }
306
+
307
+ /* A disabled segment with a `disabledReason` wraps in <Tooltip>, which
308
+ inserts not one but TWO boxless (display: contents) layers between this
309
+ button and GroupedToggle in the DOM -- `.tooltip-wrapper` and, inside
310
+ it, `.tooltip-target` -- both invisible to layout, neither invisible to
311
+ :first-child/:last-child, which match the real DOM tree regardless of
312
+ display. Every branch below requires `.grouped-toggle` itself as the
313
+ ancestor whose first/last-ness is actually being tested (via
314
+ `:global(.grouped-toggle) >`), not just bare `:first-child`/
315
+ `:last-child` on the button -- a wrapped item is always the only child
316
+ of its own tooltip-target, so an unscoped selector would match it
317
+ regardless of its true row position (e.g. a disabled *last* item would
318
+ wrongly lose its divider, thinking it was first). */
319
+ :global(.grouped-toggle) > .grouped-toggle-item:first-child,
320
+ :global(.grouped-toggle) > :global(.tooltip-wrapper):first-child > :global(.tooltip-target) > .grouped-toggle-item {
321
+ border-inline-start: none;
322
+ }
323
+
324
+ /* Rounded on the item itself, not just relied on via the parent's
325
+ `overflow: hidden` clip -- that clip only rounds whatever happens to sit
326
+ flush against the parent's true edge, and a parent that ends up wider
327
+ than its content (a grid/flex ancestor stretching it, before the
328
+ `width: fit-content` fix on GroupedToggle) left the last segment
329
+ stranded short of that edge, still square. Explicit corner radii here
330
+ hold regardless of how wide the parent ends up.
331
+ Same --grouped-toggle-border-radius GroupedToggle itself reads: custom
332
+ properties inherit, so a value set on the wrapper (default or
333
+ overridden via its `borderRadius` prop) resolves identically here. */
334
+ :global(.grouped-toggle) > .grouped-toggle-item:first-child,
335
+ :global(.grouped-toggle) > :global(.tooltip-wrapper):first-child > :global(.tooltip-target) > .grouped-toggle-item {
336
+ border-start-start-radius: var(--grouped-toggle-border-radius, var(--border-radius, var(--border-radius, 4px)));
337
+ border-end-start-radius: var(--grouped-toggle-border-radius, var(--border-radius, var(--border-radius, 4px)));
338
+ }
339
+
340
+ :global(.grouped-toggle) > .grouped-toggle-item:last-child,
341
+ :global(.grouped-toggle) > :global(.tooltip-wrapper):last-child > :global(.tooltip-target) > .grouped-toggle-item {
342
+ border-start-end-radius: var(--grouped-toggle-border-radius, var(--border-radius, var(--border-radius, 4px)));
343
+ border-end-end-radius: var(--grouped-toggle-border-radius, var(--border-radius, var(--border-radius, 4px)));
344
+ }
345
+
346
+ .grouped-toggle-item:hover:not(:disabled):not([aria-disabled=true]) {
347
+ filter: var(--grouped-toggle-item-hover-filter, var(--clickable-hover-filter, var(--hover-filter, brightness(1.05))));
348
+ transform: var(--grouped-toggle-item-hover-transform, var(--clickable-hover-transform, var(--hover-transform, none)));
349
+ color: var(--_color);
350
+ background-color: color-mix(in oklab, var(--_background-color) var(--hover-base-color-percentage, 90%), var(--hover-color-mix, var(--_color, white)) calc(100% - var(--hover-base-color-percentage, 90%)));
351
+ box-shadow: var(--grouped-toggle-item-hover-box-shadow, var(--clickable-hover-box-shadow, var(--hover-box-shadow, var(--_box-shadow, none))));
352
+ }
353
+
354
+ .grouped-toggle-item:active:not(:disabled):not([aria-disabled=true]) {
355
+ filter: var(--grouped-toggle-item-active-filter, var(--clickable-active-filter, var(--active-filter, brightness(0.9))));
356
+ transform: var(--grouped-toggle-item-active-transform, var(--clickable-active-transform, var(--active-transform, none)));
357
+ background-color: color-mix(in oklab, var(--_background-color) var(--active-base-color-percentage, 90%), var(--active-color-mix, black) calc(100% - var(--active-base-color-percentage, 90%)));
358
+ box-shadow: var(--grouped-toggle-item-active-box-shadow, var(--clickable-active-box-shadow, var(--active-box-shadow, var(--_box-shadow, none))));
359
+ }
360
+
361
+ .grouped-toggle-item.checked {
362
+ --link-bg: var(--grouped-toggle-item-checked-link-bg, var(--toggle-on-link-bg, var(--primary-link-bg, var(--grouped-toggle-item-link-bg, inherit))));
363
+ --link-fg: var(--grouped-toggle-item-checked-link-fg, var(--toggle-on-link-fg, var(--primary-link-fg, var(--grouped-toggle-item-link-fg, inherit))));
364
+ --_bg-mix-color: var(--grouped-toggle-item-checked-bg-mix-color, var(--toggle-on-bg-mix-color, var(--primary-bg-mix-color, var(--grouped-toggle-item-bg-mix-color, var(--bg-mix-color, transparent)))));
365
+ --_bg-mix-amount: var(--grouped-toggle-item-checked-bg-mix-amount, var(--toggle-on-bg-mix-amount, var(--primary-bg-mix-amount, var(--grouped-toggle-item-bg-mix-amount, var(--bg-mix-amount, 0%)))));
366
+ --_fg-mix-color: var(--grouped-toggle-item-checked-fg-mix-color, var(--toggle-on-fg-mix-color, var(--primary-fg-mix-color, var(--grouped-toggle-item-fg-mix-color, var(--fg-mix-color, transparent)))));
367
+ --_fg-mix-amount: var(--grouped-toggle-item-checked-fg-mix-amount, var(--toggle-on-fg-mix-amount, var(--primary-fg-mix-amount, var(--grouped-toggle-item-fg-mix-amount, var(--fg-mix-amount, 0%)))));
368
+ --_bg-base: var(--grouped-toggle-item-checked-bg, var(--toggle-on-bg, var(--primary-bg, var(--grouped-toggle-item-bg, var(--bg, unset)))));
369
+ --_fg-base: var(--grouped-toggle-item-checked-fg, var(--toggle-on-fg, var(--primary-fg, var(--grouped-toggle-item-fg, var(--fg, unset)))));
370
+ --_background-color: color-mix(
371
+ in srgb,
372
+ var(--_bg-base),
373
+ var(--_bg-mix-color) var(--_bg-mix-amount)
374
+ );
375
+ --_color: color-mix(
376
+ in srgb,
377
+ var(--_fg-base),
378
+ var(--_fg-mix-color) var(--_fg-mix-amount)
379
+ );
380
+ --muted-color: var(--muted-color-override, var(--_background-color));
381
+ --primary-text-fg: var(
382
+ --primary-text-fg-override,
383
+ color-mix(
384
+ in srgb,
385
+ var(--_color),
386
+ var(--primary-color)
387
+ var(--primary-text-amount, var(--text-amount, 50%))
388
+ )
389
+ );
390
+ --secondary-text-fg: var(
391
+ --secondary-text-fg-override,
392
+ color-mix(
393
+ in srgb,
394
+ var(--_color),
395
+ var(--secondary-color)
396
+ var(--secondary-text-amount, var(--text-amount, 50%))
397
+ )
398
+ );
399
+ --info-text-fg: var(
400
+ --info-text-fg-override,
401
+ color-mix(
402
+ in srgb,
403
+ var(--_color),
404
+ var(--info-color)
405
+ var(--info-text-amount, var(--text-amount, 50%))
406
+ )
407
+ );
408
+ --success-text-fg: var(
409
+ --success-text-fg-override,
410
+ color-mix(
411
+ in srgb,
412
+ var(--_color),
413
+ var(--success-color)
414
+ var(--success-text-amount, var(--text-amount, 50%))
415
+ )
416
+ );
417
+ --warning-text-fg: var(
418
+ --warning-text-fg-override,
419
+ color-mix(
420
+ in srgb,
421
+ var(--_color),
422
+ var(--warning-color)
423
+ var(--warning-text-amount, var(--text-amount, 50%))
424
+ )
425
+ );
426
+ --danger-text-fg: var(
427
+ --danger-text-fg-override,
428
+ color-mix(
429
+ in srgb,
430
+ var(--_color),
431
+ var(--danger-color)
432
+ var(--danger-text-amount, var(--text-amount, 50%))
433
+ )
434
+ );
435
+ --muted-text-fg: var(
436
+ --muted-text-fg-override,
437
+ color-mix(
438
+ in srgb,
439
+ var(--_color),
440
+ var(--muted-color)
441
+ var(--muted-text-amount, var(--text-amount, 50%))
442
+ )
443
+ );
444
+ background-color: var(--_background-color);
445
+ color: var(--_color);
446
+ }
447
+
448
+ .grouped-toggle-item:disabled,
449
+ .grouped-toggle-item[aria-disabled=true] {
450
+ cursor: not-allowed;
451
+ opacity: var(--grouped-toggle-item-disabled-opacity, 0.5);
452
+ }</style>
@@ -0,0 +1,31 @@
1
+ import type { Snippet } from "svelte";
2
+ import type { HTMLButtonAttributes } from "svelte/elements";
3
+ import type { BaseStyleProps, ContainProps } from "../types";
4
+ import { type StyleProps } from "../styleProps";
5
+ declare const GroupedToggleItem: import("svelte").Component<ContainProps<HTMLButtonAttributes, {
6
+ /** Two-way bindable, like `Toggle`/`Checkbox` -- unlike `TabItem`'s
7
+ * `active`, this segment's checked-ness is its own independent fact,
8
+ * not something computed by comparing against sibling state, so
9
+ * `bind:checked` is the natural fit rather than a manual
10
+ * `checked={} onclick={}` pair. A consumer can still pass `onclick`
11
+ * for a side effect (analytics, closing a menu, ...); it fires after
12
+ * the internal toggle, same order `bind:checked` + `onchange`
13
+ * compose on a native input. */
14
+ checked?: boolean;
15
+ disabled?: boolean;
16
+ /** Shown in a tooltip on hover/focus when disabled, instead of the
17
+ * segment just silently doing nothing -- e.g. "This can't be grouped
18
+ * by; it has no performance bands." A disabled segment with no reason
19
+ * given just stays inert, same as a native disabled control. */
20
+ disabledReason?: string;
21
+ children?: Snippet;
22
+ }, BaseStyleProps & StyleProps<readonly ["bg", "fg", "fontFamily", "fontSize", "fontWeight", "lineHeight", "letterSpacing", "textAlign", "textTransform", "textDecoration", "fontVariant"]> & {
23
+ /** Override just the checked-state fill/text colour, independent of
24
+ * the unchecked styling above -- this is what lets three instances
25
+ * in the same row (e.g. Filter / Group / Show) each have their own
26
+ * checked colour while sharing one unchecked appearance. */
27
+ checkedBg?: string | null;
28
+ checkedFg?: string | null;
29
+ }, "aria-pressed" | "type">, {}, "checked">;
30
+ type GroupedToggleItem = ReturnType<typeof GroupedToggleItem>;
31
+ export default GroupedToggleItem;