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
@@ -0,0 +1,551 @@
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 single-select `GroupedToggle` row -- the exclusive-
6
+ * choice sibling of `GroupedToggleItem`. Visually identical (same CSS
7
+ * classes), but a different behavioural contract: exactly one segment in
8
+ * the row is ever checked, and that's the ARIA "radiogroup" pattern, not a
9
+ * row of independent buttons -- which means real roving-tabindex keyboard
10
+ * support (Left/Right moves *and changes* the selection; only the checked
11
+ * segment is a normal Tab stop), not just different styling. That's the
12
+ * reason this is its own component rather than a mode flag on
13
+ * `GroupedToggleItem`: the keyboard contract is genuinely different, and a
14
+ * flag that silently swaps it would be easy to get wrong.
15
+ *
16
+ * Wrap these in `<GroupedToggle role="radiogroup">` -- `GroupedToggle`
17
+ * itself carries no selection logic (same as `TabBar`), so the `role`
18
+ * override is what tells assistive tech this row is exclusive-choice.
19
+ */
20
+ const GROUPED_RADIO_ITEM_VARS = [...COLOR_VARS, ...TYPOGRAPHY_VARS];
21
+ let { checked = false, value = undefined, group = $bindable(undefined), disabled = false, disabledReason = "", children, class: className, onclick, ...restProps } = $props();
22
+ const useGroup = $derived(group !== undefined && value !== undefined);
23
+ const isChecked = $derived(useGroup ? group === value : checked);
24
+ const showTooltip = $derived(disabled && disabledReason.length > 0);
25
+ /** See GroupedToggleItem's identical flag: a native `disabled` button can
26
+ * never be focused, which would make a `disabledReason` tooltip --
27
+ * documented above as shown "on hover/focus" -- unreachable by keyboard.
28
+ * `aria-disabled` keeps it a real Tab stop instead; `handleClick` and
29
+ * `handleKeydown` do the disabled-guarding the native attribute would
30
+ * otherwise have done. */
31
+ const useAriaDisabled = $derived(showTooltip);
32
+ function handleClick(event) {
33
+ if (disabled)
34
+ return;
35
+ if (useGroup)
36
+ group = value;
37
+ onclick?.(event);
38
+ }
39
+ const el = $derived(elementProps(restProps, "grouped-toggle-item", [
40
+ ...GROUPED_RADIO_ITEM_VARS,
41
+ "padding",
42
+ "width",
43
+ "height",
44
+ "checkedBg",
45
+ "checkedFg",
46
+ ]));
47
+ let ref = $state();
48
+ /**
49
+ * Roving tabindex + arrow-key selection, per the WAI-ARIA radiogroup
50
+ * pattern: the group is one Tab stop (only the checked segment has
51
+ * tabindex 0), and arrow keys both move focus *and* pick the new segment --
52
+ * unlike a checkbox row, where arrow keys do nothing special and every
53
+ * segment is its own Tab stop.
54
+ *
55
+ * This walks DOM siblings rather than coordinating through Svelte state:
56
+ * `GroupedToggle` carries no selection state, so there's nothing here to
57
+ * subscribe to -- finding the next `[role="radio"]` sibling and clicking it
58
+ * reuses the exact same `onclick` the consumer already wires for pointer
59
+ * selection, rather than inventing a second code path for keyboard
60
+ * selection.
61
+ */
62
+ function handleKeydown(event) {
63
+ const forward = event.key === "ArrowRight" || event.key === "ArrowDown";
64
+ const backward = event.key === "ArrowLeft" || event.key === "ArrowUp";
65
+ const home = event.key === "Home";
66
+ const end = event.key === "End";
67
+ if (!forward && !backward && !home && !end)
68
+ return;
69
+ if (!ref)
70
+ return;
71
+ event.preventDefault();
72
+ const groupEl = ref.closest('[role="radiogroup"]');
73
+ if (!groupEl)
74
+ return;
75
+ // Only truly-native-disabled segments (no `disabledReason`, so nothing
76
+ // to explain) are excluded from arrow-key travel -- an `aria-disabled`
77
+ // segment stays reachable so its tooltip is actually discoverable by
78
+ // keyboard, same as GroupedToggleItem. `handleClick`'s `if (disabled)
79
+ // return` already stops arrow-navigating onto one from selecting it.
80
+ const radios = Array.from(groupEl.querySelectorAll('[role="radio"]:not(:disabled)'));
81
+ if (radios.length === 0)
82
+ return;
83
+ let nextIndex;
84
+ if (home) {
85
+ nextIndex = 0;
86
+ }
87
+ else if (end) {
88
+ nextIndex = radios.length - 1;
89
+ }
90
+ else {
91
+ const index = radios.indexOf(ref);
92
+ if (index === -1)
93
+ return;
94
+ nextIndex = forward
95
+ ? (index + 1) % radios.length
96
+ : (index - 1 + radios.length) % radios.length;
97
+ }
98
+ radios[nextIndex].focus();
99
+ radios[nextIndex].click();
100
+ }
101
+ /**
102
+ * Which segment owns the group's single Tab stop. Normally that's whichever
103
+ * enabled segment is checked -- but if the checked value is disabled (or
104
+ * nothing is checked yet), roving tabindex has to fall back to SOME enabled
105
+ * segment, or the whole group becomes unreachable by Tab. Per the WAI-ARIA
106
+ * radiogroup pattern, that fallback is the first enabled segment.
107
+ *
108
+ * This can't be a pure `$derived` of this item's own props: "is any enabled
109
+ * sibling checked" isn't something this component has a reactive reference
110
+ * to (`GroupedToggle` carries no shared selection state, same reasoning as
111
+ * `handleKeydown` above), so it's recomputed from the live DOM whenever
112
+ * this item's own checked/disabled state -- or, via the shared `group`
113
+ * bindable, a sibling's -- changes.
114
+ */
115
+ let isTabStop = $state(false);
116
+ $effect(() => {
117
+ isChecked;
118
+ disabled;
119
+ group;
120
+ if (!ref)
121
+ return;
122
+ const groupEl = ref.closest('[role="radiogroup"]');
123
+ if (!groupEl) {
124
+ isTabStop = isChecked && !disabled;
125
+ return;
126
+ }
127
+ const radios = Array.from(groupEl.querySelectorAll('[role="radio"]'));
128
+ const enabled = radios.filter((radio) => !radio.disabled && radio.getAttribute("aria-disabled") !== "true");
129
+ const anEnabledSiblingIsChecked = enabled.some((radio) => radio.getAttribute("aria-checked") === "true");
130
+ isTabStop = anEnabledSiblingIsChecked
131
+ ? isChecked && !disabled
132
+ : enabled[0] === ref;
133
+ });
134
+ </script>
135
+
136
+ {#snippet radioButton()}
137
+ <button
138
+ bind:this={ref}
139
+ type="button"
140
+ class={["grouped-toggle-item", className]}
141
+ class:checked={isChecked}
142
+ disabled={disabled && !useAriaDisabled}
143
+ aria-disabled={useAriaDisabled ? "true" : undefined}
144
+ role="radio"
145
+ aria-checked={isChecked}
146
+ tabindex={isTabStop ? 0 : -1}
147
+ onclick={handleClick}
148
+ onkeydown={handleKeydown}
149
+ {...el}
150
+ >
151
+ {@render children?.()}
152
+ </button>
153
+ {/snippet}
154
+
155
+ {#if showTooltip}
156
+ <Tooltip tooltipText={disabledReason}>
157
+ {@render radioButton()}
158
+ </Tooltip>
159
+ {:else}
160
+ {@render radioButton()}
161
+ {/if}
162
+
163
+ <style>/* Per-level heading (h1-h6) box/typography properties, resolved to PRIVATE,
164
+ inheriting custom properties (--_h1-color, --_h2-font-size, etc) rather than
165
+ assigned directly to a matched h1-h6 selector. Nothing in this file matches
166
+ an actual heading element anymore -- see typography-context.css (vars/), which
167
+ is the single, global, :where()-scoped rule that actually reads these vars
168
+ and applies them to real elements. That split is what lets a component
169
+ nested arbitrarily deep (through <article>, <Tile>, whatever) still get the
170
+ nearest ancestor's heading styling: these are ordinary inherited custom
171
+ properties, so DOM proximity decides which component's values apply, not
172
+ selector specificity or stylesheet order. See AGENTS-INTERNAL.md. */
173
+ /* All six heading levels' context vars in one call -- what every
174
+ "typography container" component (Container, Card, Tile, TextLayout, ...)
175
+ includes on itself once, alongside the shared --_heading-* vars below. */
176
+ /* Shared prose-measure group: p, blockquote, dl, ul, ol. li/dt/dd don't get
177
+ their own vars -- they're never wider than their ul/ol/dl parent's own
178
+ max-width box, so they're already bounded for free by ordinary block
179
+ layout, not by anything selector-based reaching them individually. */
180
+ /* p specifically also gets the fuller typography-props-bare treatment
181
+ (paragraph-prefixed), which -- same as in the original selector-based
182
+ version -- is declared/consumed AFTER the shared prose vars above, so it
183
+ wins for whichever properties both groups set. */
184
+ /* The first paragraph in a typography context (p:first-of-type, or a p right
185
+ after a heading) gets its own, slightly different treatment -- larger lede
186
+ text, no top margin. Structural matching (:first-of-type, `h1 + p`) still
187
+ has to happen where the real element is, in the global consumer rule; only
188
+ the VALUES move to context vars here. */
189
+ /* Text reset for an overlay's root element: a tooltip, popover menu or dialog
190
+ that sits in the DOM wherever its trigger was placed.
191
+
192
+ The top layer changes how an element paints and where it is positioned, not
193
+ what it inherits. A popover declared inside a `white-space: nowrap` table
194
+ cell is nowrap too, and one inside a bold, uppercase, centred header is
195
+ bold, uppercase and centred -- styles that belong to the trigger's context,
196
+ not to the overlay. typography-props() doesn't stop that: its chains end in
197
+ `unset`, which for an inherited property means "take the parent's value".
198
+
199
+ So this re-declares every inherited text property with a real default at
200
+ the end of the same variable chain. The vars still win (--tooltip-*, then
201
+ the category, then the global), and custom properties still inherit --
202
+ that is how theming reaches an overlay -- but when nothing is set, the
203
+ overlay gets its own defaults instead of its host's.
204
+
205
+ INCLUDE IT AFTER typography-props() / typography-container-props() in the
206
+ same rule: it has to beat their `unset` fallbacks by source order.
207
+
208
+ Deliberately left inheriting:
209
+ - direction, writing-mode: the document's script direction is the
210
+ overlay's too.
211
+ - visibility: a hidden subtree should not be able to show an overlay.
212
+ - font-family: every theme defines --font-family at :root, and there is no
213
+ neutral value to fall back to that would beat "the page's font". */
214
+ /* Logical, not top/bottom/left/right: margin-block/margin-inline already take
215
+ one value (both sides equal) or two (start end), so there is no separate
216
+ "which side" prop to remember and no 4-value shorthand order to get wrong.
217
+ Defaults to 0 on purpose -- unlike padding-props above, a margin that
218
+ appeared by default on every consumer would be a silent layout change the
219
+ day this mixin got added to a new component. Callers opt in with an actual
220
+ value. (Container does not use this mixin: it wants --gap/auto as its
221
+ defaults, which do not fit the "0 unless asked" contract here.) */
222
+ /* The width at or below which a Sidebar swaps its grab-bar rail for the sheet
223
+ affordance -- a floating button that opens a panel over the content.
224
+
225
+ Page has to agree with Sidebar about this, because Page is what floats the
226
+ aside out of flow and reserves the strip of padding the sheet button sits
227
+ in. They used to disagree: Sidebar switched at 512px and Page at 600px,
228
+ which left a band between 513 and 600 where Page had already lifted the
229
+ aside out of the layout while the Sidebar inside it was still showing a
230
+ rail -- a grab bar floating on top of the content, which is the affordance
231
+ we deliberately avoid everywhere else.
232
+
233
+ One definition, used by both, so they cannot drift again. */
234
+ /* The "small" size tier: a component whose container is this wide or less
235
+ is on a phone, or in a pane about that narrow, and sizes itself for it.
236
+
237
+ The convention, shared by every component that has a size worth changing
238
+ there (Card started it; Tile, GridLayout, Bar and Container follow):
239
+
240
+ --{component}-{property}-small
241
+
242
+ applies inside a small container. What wins there, in order:
243
+ 1. the `-small` variable, if you set it;
244
+ 2. otherwise the regular variable, if you set it -- a size you chose on
245
+ purpose is not overridden behind your back;
246
+ 3. otherwise the component's own small default, which for most
247
+ properties is simply its regular default.
248
+
249
+ Container queries can't read a custom property, so the breakpoint itself
250
+ is fixed here rather than themeable. 600px matches Card and SplitPane,
251
+ which already switched there. (The Sidebar's rail-to-sheet switch,
252
+ above, is a separate, layout-chrome decision and keeps its own value.) */
253
+ /* Styles for the small tier: the nearest container is narrow, OR the
254
+ viewport is.
255
+
256
+ The container query is the real answer -- a Tile in a 400px pane on a
257
+ desktop is small. But a container query only matches inside an ancestor
258
+ that *is* a container, and a component dropped straight into a page (no
259
+ Page, no Container around it) has none, so on a phone it would never
260
+ find out. The viewport query covers that: a narrow viewport means every
261
+ container in it is narrow too, so the two never disagree in the
262
+ direction that matters.
263
+
264
+ An element never queries itself, so on a component that is its own
265
+ container (Tile, Bar) this asks about the space it was given. */
266
+ /* Warning: because we define a fallback
267
+ media query, the media query can override the container
268
+ if we stack a bunch of these in a row and aren't thoughtful about the order.
269
+ Put min-width queries *after* max-width queries so that smaller
270
+ container queries don't get their styles overridden by large media queries.
271
+ */
272
+ /* Convenience groupings */
273
+ /* Same visual language as GroupedToggleItem -- these sit in the same
274
+ GroupedToggle row and should be indistinguishable by eye; only the
275
+ role/keyboard contract differs. Duplicated rather than shared via a
276
+ common class, matching this library's existing pattern of each
277
+ component owning its own scoped styles (see Checkbox vs Toggle). */
278
+ .grouped-toggle-item {
279
+ --link-bg: var(--grouped-toggle-item-link-bg, var(--control-link-bg, var(--secondary-link-bg, inherit)));
280
+ --link-fg: var(--grouped-toggle-item-link-fg, var(--control-link-fg, var(--secondary-link-fg, inherit)));
281
+ --_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))));
282
+ --_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%))));
283
+ --_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))));
284
+ --_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%))));
285
+ --_bg-base: var(--grouped-toggle-item-bg, var(--control-bg, var(--secondary-bg, var(--bg, unset))));
286
+ --_fg-base: var(--grouped-toggle-item-fg, var(--control-fg, var(--secondary-fg, var(--fg, unset))));
287
+ --_background-color: color-mix(
288
+ in srgb,
289
+ var(--_bg-base),
290
+ var(--_bg-mix-color) var(--_bg-mix-amount)
291
+ );
292
+ --_color: color-mix(
293
+ in srgb,
294
+ var(--_fg-base),
295
+ var(--_fg-mix-color) var(--_fg-mix-amount)
296
+ );
297
+ --muted-color: var(--muted-color-override, var(--_background-color));
298
+ --primary-text-fg: var(
299
+ --primary-text-fg-override,
300
+ color-mix(
301
+ in srgb,
302
+ var(--_color),
303
+ var(--primary-color)
304
+ var(--primary-text-amount, var(--text-amount, 50%))
305
+ )
306
+ );
307
+ --secondary-text-fg: var(
308
+ --secondary-text-fg-override,
309
+ color-mix(
310
+ in srgb,
311
+ var(--_color),
312
+ var(--secondary-color)
313
+ var(--secondary-text-amount, var(--text-amount, 50%))
314
+ )
315
+ );
316
+ --info-text-fg: var(
317
+ --info-text-fg-override,
318
+ color-mix(
319
+ in srgb,
320
+ var(--_color),
321
+ var(--info-color)
322
+ var(--info-text-amount, var(--text-amount, 50%))
323
+ )
324
+ );
325
+ --success-text-fg: var(
326
+ --success-text-fg-override,
327
+ color-mix(
328
+ in srgb,
329
+ var(--_color),
330
+ var(--success-color)
331
+ var(--success-text-amount, var(--text-amount, 50%))
332
+ )
333
+ );
334
+ --warning-text-fg: var(
335
+ --warning-text-fg-override,
336
+ color-mix(
337
+ in srgb,
338
+ var(--_color),
339
+ var(--warning-color)
340
+ var(--warning-text-amount, var(--text-amount, 50%))
341
+ )
342
+ );
343
+ --danger-text-fg: var(
344
+ --danger-text-fg-override,
345
+ color-mix(
346
+ in srgb,
347
+ var(--_color),
348
+ var(--danger-color)
349
+ var(--danger-text-amount, var(--text-amount, 50%))
350
+ )
351
+ );
352
+ --muted-text-fg: var(
353
+ --muted-text-fg-override,
354
+ color-mix(
355
+ in srgb,
356
+ var(--_color),
357
+ var(--muted-color)
358
+ var(--muted-text-amount, var(--text-amount, 50%))
359
+ )
360
+ );
361
+ background-color: var(--_background-color);
362
+ color: var(--_color);
363
+ font-family: var(--grouped-toggle-item-font-family, var(--control-font-family, inherit));
364
+ text-transform: var(--grouped-toggle-item-text-transform, var(--control-text-transform, inherit));
365
+ text-decoration: var(--grouped-toggle-item-text-decoration, var(--control-text-decoration, inherit));
366
+ --_font-size: var(--grouped-toggle-item-font-size, var(--control-font-size, inherit));
367
+ font-size: var(--_font-size);
368
+ font-weight: var(--grouped-toggle-item-font-weight, var(--control-font-weight, inherit));
369
+ line-height: var(--grouped-toggle-item-line-height, var(--control-line-height, inherit));
370
+ letter-spacing: var(--grouped-toggle-item-letter-spacing, var(--control-letter-spacing, inherit));
371
+ text-indent: var(--grouped-toggle-item-indent, var(--control-indent, inherit));
372
+ font-variant: var(--grouped-toggle-item-font-variant, var(--control-font-variant, inherit));
373
+ text-align: var(--grouped-toggle-item-text-align, var(--control-text-align, inherit));
374
+ cursor: var(--grouped-toggle-item-cursor, var(--clickable-cursor, var(--cursor, pointer)));
375
+ /* `.grouped-toggle` clips overflow (same reason TabBar does for
376
+ TabItem), so the standard outer `focusable()` ring gets cut off,
377
+ worst on the rounded first/last segments -- an inset ring stays
378
+ inside the clip instead. */
379
+ }
380
+
381
+ .grouped-toggle-item:focus-visible {
382
+ outline: var(--focus-color, -webkit-focus-ring-color) auto 1px;
383
+ outline-offset: var(--focus-inset-outline-offset, -3px);
384
+ }
385
+
386
+ .grouped-toggle-item {
387
+ border: none;
388
+ 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)))));
389
+ margin: 0;
390
+ padding: var(--grouped-toggle-item-padding, var(--padding, 0.35em 0.75em));
391
+ min-width: 2em;
392
+ /* `auto` basis, not `0%` -- see GroupedToggleItem's identical rule: only
393
+ shares out extra space once the row is wider than its segments' own
394
+ content, so labels of different lengths keep their natural
395
+ proportions in the ordinary (unstretched) case. */
396
+ flex: 1 1 auto;
397
+ 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)))));
398
+ }
399
+
400
+ /* A disabled segment with a `disabledReason` wraps in <Tooltip>, which
401
+ inserts not one but TWO boxless (display: contents) layers between this
402
+ button and GroupedToggle in the DOM -- `.tooltip-wrapper` and, inside
403
+ it, `.tooltip-target`. See GroupedToggleItem's identical rule for why
404
+ every branch requires `.grouped-toggle` itself as the ancestor whose
405
+ first/last-ness is actually being tested, drilling through both
406
+ wrapper layers, rather than bare `:first-child`/`:last-child` on the
407
+ button -- a wrapped item is always the only child of its own
408
+ tooltip-target, so an unscoped selector would match it regardless of
409
+ its true row position. */
410
+ :global(.grouped-toggle) > .grouped-toggle-item:first-child,
411
+ :global(.grouped-toggle) > :global(.tooltip-wrapper):first-child > :global(.tooltip-target) > .grouped-toggle-item {
412
+ border-inline-start: none;
413
+ }
414
+
415
+ /* Rounded on the item itself -- see GroupedToggleItem's identical rule for
416
+ why relying solely on the parent's overflow:hidden clip isn't enough. */
417
+ :global(.grouped-toggle) > .grouped-toggle-item:first-child,
418
+ :global(.grouped-toggle) > :global(.tooltip-wrapper):first-child > :global(.tooltip-target) > .grouped-toggle-item {
419
+ border-start-start-radius: var(--grouped-toggle-border-radius, var(--border-radius, var(--border-radius, 4px)));
420
+ border-end-start-radius: var(--grouped-toggle-border-radius, var(--border-radius, var(--border-radius, 4px)));
421
+ }
422
+
423
+ :global(.grouped-toggle) > .grouped-toggle-item:last-child,
424
+ :global(.grouped-toggle) > :global(.tooltip-wrapper):last-child > :global(.tooltip-target) > .grouped-toggle-item {
425
+ border-start-end-radius: var(--grouped-toggle-border-radius, var(--border-radius, var(--border-radius, 4px)));
426
+ border-end-end-radius: var(--grouped-toggle-border-radius, var(--border-radius, var(--border-radius, 4px)));
427
+ }
428
+
429
+ .grouped-toggle-item:hover:not(:disabled):not([aria-disabled=true]) {
430
+ filter: var(--grouped-toggle-item-hover-filter, var(--clickable-hover-filter, var(--hover-filter, brightness(1.05))));
431
+ transform: var(--grouped-toggle-item-hover-transform, var(--clickable-hover-transform, var(--hover-transform, none)));
432
+ color: var(--_color);
433
+ 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%)));
434
+ box-shadow: var(--grouped-toggle-item-hover-box-shadow, var(--clickable-hover-box-shadow, var(--hover-box-shadow, var(--_box-shadow, none))));
435
+ }
436
+
437
+ .grouped-toggle-item:active:not(:disabled):not([aria-disabled=true]) {
438
+ filter: var(--grouped-toggle-item-active-filter, var(--clickable-active-filter, var(--active-filter, brightness(0.9))));
439
+ transform: var(--grouped-toggle-item-active-transform, var(--clickable-active-transform, var(--active-transform, none)));
440
+ 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%)));
441
+ box-shadow: var(--grouped-toggle-item-active-box-shadow, var(--clickable-active-box-shadow, var(--active-box-shadow, var(--_box-shadow, none))));
442
+ }
443
+
444
+ /* Tinted toward the selection accent rather than the plain neutral ring
445
+ GroupedToggleItem uses -- arrow-key roving only ever lands on ONE
446
+ segment at a time here, so the ring doubles as "this is the choice
447
+ you're navigating," not just "this element has focus" the way an
448
+ independent toggle's ring does. `--primary-bg` has to come BEFORE the
449
+ generic `--focus-color` in the fallback chain, not after -- every theme
450
+ in this library sets `--focus-color` globally, so putting it first (as
451
+ `var-with-fallbacks` would, threading one base var through prefixes)
452
+ makes it win outright and this ring would end up identical to
453
+ GroupedToggleItem's. `--grouped-radio-item-focus-color` stays
454
+ overridable like every other color here; it just defaults to the accent
455
+ instead of the generic focus color. */
456
+ .grouped-toggle-item:focus-visible {
457
+ outline-color: var(--grouped-radio-item-focus-color, var(--primary-bg, var(--focus-color, -webkit-focus-ring-color)));
458
+ }
459
+
460
+ .grouped-toggle-item.checked {
461
+ --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))));
462
+ --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))));
463
+ --_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)))));
464
+ --_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%)))));
465
+ --_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)))));
466
+ --_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%)))));
467
+ --_bg-base: var(--grouped-toggle-item-checked-bg, var(--toggle-on-bg, var(--primary-bg, var(--grouped-toggle-item-bg, var(--bg, unset)))));
468
+ --_fg-base: var(--grouped-toggle-item-checked-fg, var(--toggle-on-fg, var(--primary-fg, var(--grouped-toggle-item-fg, var(--fg, unset)))));
469
+ --_background-color: color-mix(
470
+ in srgb,
471
+ var(--_bg-base),
472
+ var(--_bg-mix-color) var(--_bg-mix-amount)
473
+ );
474
+ --_color: color-mix(
475
+ in srgb,
476
+ var(--_fg-base),
477
+ var(--_fg-mix-color) var(--_fg-mix-amount)
478
+ );
479
+ --muted-color: var(--muted-color-override, var(--_background-color));
480
+ --primary-text-fg: var(
481
+ --primary-text-fg-override,
482
+ color-mix(
483
+ in srgb,
484
+ var(--_color),
485
+ var(--primary-color)
486
+ var(--primary-text-amount, var(--text-amount, 50%))
487
+ )
488
+ );
489
+ --secondary-text-fg: var(
490
+ --secondary-text-fg-override,
491
+ color-mix(
492
+ in srgb,
493
+ var(--_color),
494
+ var(--secondary-color)
495
+ var(--secondary-text-amount, var(--text-amount, 50%))
496
+ )
497
+ );
498
+ --info-text-fg: var(
499
+ --info-text-fg-override,
500
+ color-mix(
501
+ in srgb,
502
+ var(--_color),
503
+ var(--info-color)
504
+ var(--info-text-amount, var(--text-amount, 50%))
505
+ )
506
+ );
507
+ --success-text-fg: var(
508
+ --success-text-fg-override,
509
+ color-mix(
510
+ in srgb,
511
+ var(--_color),
512
+ var(--success-color)
513
+ var(--success-text-amount, var(--text-amount, 50%))
514
+ )
515
+ );
516
+ --warning-text-fg: var(
517
+ --warning-text-fg-override,
518
+ color-mix(
519
+ in srgb,
520
+ var(--_color),
521
+ var(--warning-color)
522
+ var(--warning-text-amount, var(--text-amount, 50%))
523
+ )
524
+ );
525
+ --danger-text-fg: var(
526
+ --danger-text-fg-override,
527
+ color-mix(
528
+ in srgb,
529
+ var(--_color),
530
+ var(--danger-color)
531
+ var(--danger-text-amount, var(--text-amount, 50%))
532
+ )
533
+ );
534
+ --muted-text-fg: var(
535
+ --muted-text-fg-override,
536
+ color-mix(
537
+ in srgb,
538
+ var(--_color),
539
+ var(--muted-color)
540
+ var(--muted-text-amount, var(--text-amount, 50%))
541
+ )
542
+ );
543
+ background-color: var(--_background-color);
544
+ color: var(--_color);
545
+ }
546
+
547
+ .grouped-toggle-item:disabled,
548
+ .grouped-toggle-item[aria-disabled=true] {
549
+ cursor: not-allowed;
550
+ opacity: var(--grouped-toggle-item-disabled-opacity, 0.5);
551
+ }</style>
@@ -0,0 +1,36 @@
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 GroupedRadioItem: import("svelte").Component<ContainProps<HTMLButtonAttributes, {
6
+ /**
7
+ * Two ways to drive this, matching `Checkbox`'s own `group`/`value`
8
+ * pair exactly -- just singular instead of an array, which is the
9
+ * whole difference between a radio and a checkbox:
10
+ *
11
+ * 1. **`bind:group` + `value`** (preferred): every item in the row
12
+ * binds to the *same* `group` variable; this one computes its own
13
+ * `checked` as `group === value` and clicking it sets
14
+ * `group = value` directly. One variable for the whole row, no
15
+ * manual comparison to write.
16
+ * 2. **Explicit `checked` + `onclick`**: for when the "selected" fact
17
+ * lives somewhere this component shouldn't own (e.g. it's derived
18
+ * from a prop one level up). Deliberately NOT `bind:checked` the way
19
+ * `GroupedToggleItem` is -- a single radio's checked-ness is
20
+ * relational ("am I the one that matches"), not an independent
21
+ * fact, so bindable here would let two siblings each think they're
22
+ * independently true with nothing enforcing exclusivity. Same
23
+ * reasoning as `TabItem`'s `active`.
24
+ */
25
+ checked?: boolean;
26
+ value?: unknown;
27
+ group?: unknown;
28
+ disabled?: boolean;
29
+ disabledReason?: string;
30
+ children?: Snippet;
31
+ }, BaseStyleProps & StyleProps<readonly ["bg", "fg", "fontFamily", "fontSize", "fontWeight", "lineHeight", "letterSpacing", "textAlign", "textTransform", "textDecoration", "fontVariant"]> & {
32
+ checkedBg?: string | null;
33
+ checkedFg?: string | null;
34
+ }, "tabindex" | "role" | "aria-checked" | "onkeydown" | "type">, {}, "group">;
35
+ type GroupedRadioItem = ReturnType<typeof GroupedRadioItem>;
36
+ export default GroupedRadioItem;