contain-css-svelte 1.1.20 → 1.2.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 (94) hide show
  1. package/dist/Card.svelte +15 -6
  2. package/dist/Card.svelte.d.ts +4 -5
  3. package/dist/controls/Button.svelte +19 -5
  4. package/dist/controls/Button.svelte.d.ts +4 -4
  5. package/dist/controls/ButtonLink.svelte +17 -14
  6. package/dist/controls/ButtonLink.svelte.d.ts +4 -4
  7. package/dist/controls/Checkbox.svelte +15 -8
  8. package/dist/controls/Checkbox.svelte.d.ts +4 -4
  9. package/dist/controls/CircleButton.svelte +14 -7
  10. package/dist/controls/CircleButton.svelte.d.ts +4 -4
  11. package/dist/controls/Input.svelte +14 -3
  12. package/dist/controls/Input.svelte.d.ts +4 -3
  13. package/dist/controls/Option.svelte +4 -3
  14. package/dist/controls/Option.svelte.d.ts +3 -2
  15. package/dist/controls/RadioButton.svelte +14 -7
  16. package/dist/controls/RadioButton.svelte.d.ts +4 -4
  17. package/dist/controls/Select.svelte +15 -3
  18. package/dist/controls/Select.svelte.d.ts +4 -3
  19. package/dist/controls/Slider.svelte +5 -11
  20. package/dist/controls/Slider.svelte.d.ts +3 -3
  21. package/dist/controls/TabItem.svelte +24 -5
  22. package/dist/controls/TabItem.svelte.d.ts +4 -4
  23. package/dist/controls/Toggle.svelte +21 -5
  24. package/dist/controls/Toggle.svelte.d.ts +4 -4
  25. package/dist/dropdowns/DropdownMenu.svelte +18 -6
  26. package/dist/dropdowns/DropdownMenu.svelte.d.ts +4 -4
  27. package/dist/layout/Accordion.svelte +17 -2
  28. package/dist/layout/Accordion.svelte.d.ts +5 -3
  29. package/dist/layout/Bar.svelte +13 -7
  30. package/dist/layout/Bar.svelte.d.ts +4 -4
  31. package/dist/layout/Column.svelte.d.ts +3 -3
  32. package/dist/layout/ColumnContainer.svelte +12 -9
  33. package/dist/layout/ColumnContainer.svelte.d.ts +4 -4
  34. package/dist/layout/Columns.svelte +4 -3
  35. package/dist/layout/Columns.svelte.d.ts +7 -3
  36. package/dist/layout/Container.svelte +23 -8
  37. package/dist/layout/Container.svelte.d.ts +7 -5
  38. package/dist/layout/DataList.svelte +15 -7
  39. package/dist/layout/DataList.svelte.d.ts +5 -4
  40. package/dist/layout/DataListItem.svelte +14 -8
  41. package/dist/layout/DataListItem.svelte.d.ts +4 -4
  42. package/dist/layout/Fieldset.svelte +16 -8
  43. package/dist/layout/Fieldset.svelte.d.ts +6 -3
  44. package/dist/layout/Form.svelte +17 -8
  45. package/dist/layout/Form.svelte.d.ts +8 -6
  46. package/dist/layout/FormItem.svelte +12 -7
  47. package/dist/layout/FormItem.svelte.d.ts +4 -3
  48. package/dist/layout/FormProvider.svelte +3 -0
  49. package/dist/layout/GridLayout.svelte +14 -9
  50. package/dist/layout/GridLayout.svelte.d.ts +4 -4
  51. package/dist/layout/GridRow.svelte +6 -10
  52. package/dist/layout/GridRow.svelte.d.ts +3 -2
  53. package/dist/layout/Hero.svelte +14 -8
  54. package/dist/layout/Hero.svelte.d.ts +4 -4
  55. package/dist/layout/Inline.svelte +16 -15
  56. package/dist/layout/Inline.svelte.d.ts +4 -4
  57. package/dist/layout/MenuList.svelte +13 -6
  58. package/dist/layout/MenuList.svelte.d.ts +4 -4
  59. package/dist/layout/Page.svelte +14 -8
  60. package/dist/layout/Page.svelte.d.ts +11 -3
  61. package/dist/layout/ResponsiveText.svelte +4 -0
  62. package/dist/layout/Row.svelte.d.ts +3 -3
  63. package/dist/layout/RowContainer.svelte +12 -9
  64. package/dist/layout/RowContainer.svelte.d.ts +4 -4
  65. package/dist/layout/Sidebar.svelte +17 -6
  66. package/dist/layout/Sidebar.svelte.d.ts +12 -4
  67. package/dist/layout/SplitPane.svelte +8 -14
  68. package/dist/layout/SplitPane.svelte.d.ts +16 -2
  69. package/dist/layout/Stack.svelte +16 -15
  70. package/dist/layout/Stack.svelte.d.ts +4 -4
  71. package/dist/layout/TabBar.svelte +12 -2
  72. package/dist/layout/TabBar.svelte.d.ts +17 -10
  73. package/dist/layout/Table.svelte +5 -1
  74. package/dist/layout/Tile.svelte +40 -27
  75. package/dist/layout/Tile.svelte.d.ts +32 -10
  76. package/dist/misc/Code.svelte +2 -0
  77. package/dist/misc/Progress.svelte +25 -8
  78. package/dist/misc/Progress.svelte.d.ts +15 -8
  79. package/dist/misc/Tag.svelte +97 -44
  80. package/dist/misc/Tag.svelte.d.ts +17 -5
  81. package/dist/overlays/Dialog.svelte +18 -5
  82. package/dist/overlays/Dialog.svelte.d.ts +5 -3
  83. package/dist/overlays/Tooltip.svelte +21 -3
  84. package/dist/overlays/Tooltip.svelte.d.ts +5 -3
  85. package/dist/styleProps.d.ts +67 -0
  86. package/dist/styleProps.js +76 -0
  87. package/dist/types.d.ts +40 -1
  88. package/dist/types.js +1 -1
  89. package/dist/typography/Text.svelte +4 -11
  90. package/dist/typography/TextLayout.svelte +4 -7
  91. package/dist/typography/TextLayout.svelte.d.ts +4 -3
  92. package/dist/util.d.ts +60 -3
  93. package/dist/util.js +101 -11
  94. package/package.json +1 -1
@@ -1,9 +1,23 @@
1
1
  import type { Snippet } from "svelte";
2
2
  import type { HTMLAttributes } from "svelte/elements";
3
- type Props = {
3
+ import type { ContainProps } from "../types";
4
+ type Props = ContainProps<HTMLAttributes<HTMLElement>, {
4
5
  left?: Snippet;
5
6
  right?: Snippet;
6
- } & HTMLAttributes<HTMLElement> & Record<string, unknown>;
7
+ /**
8
+ * Accessible name for the drag handle between the panes. Name what the
9
+ * panes hold -- `resizerLabel="Resize the preview"` -- so it is clear
10
+ * what moving the handle does.
11
+ */
12
+ resizerLabel?: string;
13
+ }, {
14
+ bg?: string | null;
15
+ fg?: string | null;
16
+ border?: string | null;
17
+ height?: string | null;
18
+ leftWidth?: string | null;
19
+ rightWidth?: string | null;
20
+ }>;
7
21
  declare const SplitPane: import("svelte").Component<Props, {}, "">;
8
22
  type SplitPane = ReturnType<typeof SplitPane>;
9
23
  export default SplitPane;
@@ -1,32 +1,33 @@
1
- <script lang="ts">import { injectVars } from "../util";
2
- let { fill = false, stretch = false, split = false, center = false, justify = null, align = null, gap = null, children, ...restProps } = $props();
1
+ <script lang="ts">import { elementProps } from "../util";
2
+ import { MARGIN_VARS, PADDING_VARS, RADIUS_VARS, } from "../styleProps";
3
+ let { fill = false, stretch = false, split = false, center = false, justify = null, align = null, gap = null, children, class: className, ...restProps } = $props();
3
4
  const resolvedAlign = $derived(align ?? (center ? "center" : null));
4
- const inlineStyle = $derived(restProps.style);
5
- const elementProps = $derived.by(() => {
6
- const { style: _, ...rest } = restProps;
7
- return rest;
8
- });
9
- const style = $derived(injectVars({ justify, align: resolvedAlign, gap, ...elementProps }, "stack", [
5
+ /* The shorthands stack's own CSS backs, one group per mixin it
6
+ includes. The Props type is derived from this same array, so what the
7
+ component accepts and what it emits cannot drift apart. */
8
+ const STACK_VARS = [
9
+ ...PADDING_VARS,
10
+ ...RADIUS_VARS,
11
+ ...MARGIN_VARS,
12
+ ];
13
+ const el = $derived(elementProps({ justify, align: resolvedAlign, gap, ...restProps }, "stack", [
14
+ ...STACK_VARS,
10
15
  "bg",
11
16
  "fg",
12
- "padding",
13
17
  "width",
14
18
  "height",
15
19
  "gap",
16
20
  "justify",
17
21
  "align",
18
- "marginBlock",
19
- "marginInline",
20
- ]) + (inlineStyle ?? ""));
22
+ ]));
21
23
  </script>
22
24
 
23
25
  <div
24
- class="stack"
26
+ class={["stack", className]}
25
27
  class:fill
26
28
  class:stretch
27
29
  class:split
28
- {style}
29
- {...elementProps}
30
+ {...el}
30
31
  >
31
32
  {@render children?.()}
32
33
  </div>
@@ -1,7 +1,8 @@
1
1
  import type { Snippet } from "svelte";
2
2
  import type { HTMLAttributes } from "svelte/elements";
3
- import type { BaseStyleProps, MarginStyleProps } from "../types";
4
- type Props = {
3
+ import type { BaseStyleProps, ContainProps, MarginStyleProps } from "../types";
4
+ import { type StyleProps } from "../styleProps";
5
+ declare const Stack: import("svelte").Component<ContainProps<HTMLAttributes<HTMLDivElement>, {
5
6
  fill?: boolean;
6
7
  stretch?: boolean;
7
8
  split?: boolean;
@@ -10,7 +11,6 @@ type Props = {
10
11
  align?: string | null;
11
12
  gap?: string | null;
12
13
  children?: Snippet;
13
- } & BaseStyleProps & MarginStyleProps & HTMLAttributes<HTMLDivElement>;
14
- declare const Stack: import("svelte").Component<Props, {}, "">;
14
+ }, BaseStyleProps & MarginStyleProps & StyleProps<readonly ["padding", "borderRadius", "marginBlock", "marginInline"]>>, {}, "">;
15
15
  type Stack = ReturnType<typeof Stack>;
16
16
  export default Stack;
@@ -1,10 +1,20 @@
1
1
  <script lang="ts">import Bar from "./Bar.svelte";
2
2
  import TabItem from "../controls/TabItem.svelte";
3
- let { sticky = false, active = $bindable(null), items = [], onchange, children, } = $props();
3
+ import { elementProps } from "../util";
4
+ import { COLOR_VARS, TYPOGRAPHY_VARS, } from "../styleProps";
5
+ let { sticky = false, active = $bindable(null), items = [], onchange, children, class: className, ...restProps } = $props();
6
+ /* The shorthands this component's own CSS backs, one group per mixin
7
+ it includes. The Props type is derived from this same array, so what
8
+ the component accepts and what it emits cannot drift apart. */
9
+ const TAB_BAR_VARS = [
10
+ ...COLOR_VARS,
11
+ ...TYPOGRAPHY_VARS,
12
+ ];
13
+ const el = $derived(elementProps(restProps, "tab-bar", TAB_BAR_VARS));
4
14
  let lastActive = $state(active);
5
15
  </script>
6
16
 
7
- <div class="tabs" class:sticky>
17
+ <div class={["tabs", className]} class:sticky {...el}>
8
18
  <Bar
9
19
  padding="0"
10
20
  --button-height="var(--tab-bar-height, 3em)"
@@ -1,14 +1,21 @@
1
- type Item = {
2
- label: string;
3
- value: string;
4
- };
5
- interface Props {
1
+ import type { HTMLAttributes } from "svelte/elements";
2
+ import type { ContainProps } from "../types";
3
+ import { type StyleProps } from "../styleProps";
4
+ declare const TabBar: import("svelte").Component<ContainProps<HTMLAttributes<HTMLDivElement>, {
6
5
  sticky?: boolean;
7
- active?: string | Item | null;
8
- items?: (string | Item)[];
9
- onchange?: (value: string | Item | null) => void;
6
+ active?: string | {
7
+ label: string;
8
+ value: string;
9
+ } | null;
10
+ items?: (string | {
11
+ label: string;
12
+ value: string;
13
+ })[];
14
+ onchange?: (value: string | {
15
+ label: string;
16
+ value: string;
17
+ } | null) => void;
10
18
  children?: import("svelte").Snippet;
11
- }
12
- declare const TabBar: import("svelte").Component<Props, {}, "active">;
19
+ }, StyleProps<readonly ["bg", "fg", "fontFamily", "fontSize", "fontWeight", "lineHeight", "letterSpacing", "textAlign", "textTransform", "textDecoration", "fontVariant"]>>, {}, "active">;
13
20
  type TabBar = ReturnType<typeof TabBar>;
14
21
  export default TabBar;
@@ -1,4 +1,8 @@
1
- <script lang="ts">let { sticky = false, column_widths = null, thead, tbody, children, } = $props();
1
+ <script lang="ts">/* No attribute pass-through, deliberately: the sticky variant renders the
2
+ visible tables AND a visually-hidden clone of them used to measure column
3
+ widths. Spreading a caller's `id` would emit it twice, which is invalid.
4
+ Set attributes on the <table> markup you pass in as `thead`/`tbody`. */
5
+ let { sticky = false, column_widths = null, thead, tbody, children, } = $props();
2
6
  // svelte-ignore state_referenced_locally
3
7
  let columns = $state(column_widths || []);
4
8
  /* Code for syncing column widths for scrolling table solution */
@@ -1,4 +1,15 @@
1
- <script lang="ts">import { injectVars } from "../util";
1
+ <script lang="ts">import { splitProps } from "../util";
2
+ import { COLOR_VARS, PADDING_VARS, RADIUS_VARS, MARGIN_VARS, TYPOGRAPHY_CONTAINER_VARS, } from "../styleProps";
3
+ /* The shorthands this component's own CSS backs, one group per mixin
4
+ it includes. The Props type is derived from this same array, so what
5
+ the component accepts and what it emits cannot drift apart. */
6
+ const TILE_VARS = [
7
+ ...COLOR_VARS,
8
+ ...PADDING_VARS,
9
+ ...RADIUS_VARS,
10
+ ...MARGIN_VARS,
11
+ ...TYPOGRAPHY_CONTAINER_VARS,
12
+ ];
2
13
  let { checked = $bindable(false), ...props } = $props();
3
14
  /* Props to CSS variables, the way Stack, Card, Tag and the rest do it, so a
4
15
  Tile can be aimed with `<Tile center>` rather than only a raw variable. */
@@ -16,43 +27,45 @@ const styleProps = $derived.by(() => {
16
27
  align: align ?? (center ? "center" : null),
17
28
  };
18
29
  });
19
- const style = $derived(injectVars(styleProps, "tile", [
20
- "bg",
21
- "fg",
22
- "padding",
23
- "width",
24
- "height",
25
- "justify",
26
- "align",
27
- "marginBlock",
28
- "marginInline",
29
- ]) + (props.style ?? ""));
30
- /** Everything that is ours rather than the element's. */
31
- function stripOwnProps(value) {
32
- const { selectable: _selectable, interactive: _interactive, children: _children, justify: _justify, align: _align, center: _center, bg: _bg, fg: _fg, padding: _padding, width: _width, height: _height, marginBlock: _marginBlock, marginInline: _marginInline, style: _style, ...rest } = value;
33
- return rest;
34
- }
35
- function getSelectableInputProps(value) {
36
- return stripOwnProps(value);
37
- }
38
- function getInteractiveButtonProps(value) {
39
- return stripOwnProps(value);
40
- }
30
+ /* splitProps consumes the style props and `--*`, and keeps `style` out of
31
+ the attributes; the props that are Tile's own rather than the element's
32
+ are peeled off here first. In the selectable case the variables land on
33
+ the label that `.tile` styles while the attributes go to the checkbox,
34
+ which is what a caller's `aria-*` and `data-*` are describing. */
35
+ const el = $derived.by(() => {
36
+ const { selectable: _selectable, interactive: _interactive, children: _children, center: _center, class: _class, ...rest } = props;
37
+ return splitProps({ ...rest, ...styleProps }, "tile", [
38
+ ...TILE_VARS,
39
+ "width",
40
+ "height",
41
+ "justify",
42
+ "align",
43
+ ]);
44
+ });
45
+ const className = $derived(props.class);
41
46
  </script>
42
47
 
43
48
  {#if props.selectable}
44
- <label class="tile" {style}>
49
+ <label class={["tile", className]} style={el.style}>
45
50
  <div class="checkbox">
46
- <input type="checkbox" bind:checked {...getSelectableInputProps(props)} />
51
+ <input
52
+ type="checkbox"
53
+ bind:checked
54
+ {...el.attrs as HTMLInputAttributes}
55
+ />
47
56
  </div>
48
57
  {@render props.children?.()}
49
58
  </label>
50
59
  {:else if props.interactive}
51
- <button class="tile" {style} {...getInteractiveButtonProps(props)}>
60
+ <button
61
+ class={["tile", className]}
62
+ style={el.style}
63
+ {...el.attrs as HTMLButtonAttributes}
64
+ >
52
65
  {@render props.children?.()}
53
66
  </button>
54
67
  {:else}
55
- <div class="tile" {style} {...stripOwnProps(props as RenderProps)}>
68
+ <div class={["tile", className]} style={el.style} {...el.attrs}>
56
69
  {@render props.children?.()}
57
70
  </div>
58
71
  {/if}
@@ -1,7 +1,8 @@
1
1
  import type { Snippet } from "svelte";
2
2
  import type { HTMLAttributes, HTMLButtonAttributes, HTMLInputAttributes } from "svelte/elements";
3
+ import { type StyleProps } from "../styleProps";
3
4
  import type { BaseStyleProps, MarginStyleProps } from "../types";
4
- type BaseProps = BaseStyleProps & MarginStyleProps & {
5
+ declare const Tile: import("svelte").Component<(BaseStyleProps & MarginStyleProps & StyleProps<readonly ["bg", "fg", "padding", "borderRadius", "marginBlock", "marginInline", "fontFamily", "fontSize", "fontWeight", "lineHeight", "letterSpacing", "textAlign", "textTransform", "textDecoration", "fontVariant", "lineWidth"]> & {
5
6
  /** Distribution along the tile's own axis, which is vertical. */
6
7
  justify?: string | null;
7
8
  /** Cross-axis alignment, which is horizontal. Centred already. */
@@ -14,23 +15,44 @@ type BaseProps = BaseStyleProps & MarginStyleProps & {
14
15
  */
15
16
  center?: boolean;
16
17
  children?: Snippet;
17
- };
18
- type StaticTileProps = BaseProps & HTMLAttributes<HTMLDivElement> & {
18
+ } & HTMLAttributes<HTMLDivElement> & {
19
19
  interactive?: false;
20
20
  selectable?: false;
21
21
  checked?: never;
22
- };
23
- type InteractiveTileProps = BaseProps & HTMLButtonAttributes & {
22
+ }) | (BaseStyleProps & MarginStyleProps & StyleProps<readonly ["bg", "fg", "padding", "borderRadius", "marginBlock", "marginInline", "fontFamily", "fontSize", "fontWeight", "lineHeight", "letterSpacing", "textAlign", "textTransform", "textDecoration", "fontVariant", "lineWidth"]> & {
23
+ /** Distribution along the tile's own axis, which is vertical. */
24
+ justify?: string | null;
25
+ /** Cross-axis alignment, which is horizontal. Centred already. */
26
+ align?: string | null;
27
+ /**
28
+ * Shorthand for centring the tile's contents. Sets both axes, unlike
29
+ * `Stack`'s `center`, because a Tile already centres horizontally -- setting
30
+ * only the cross axis here would be a no-op and `<Tile center>` would look
31
+ * broken.
32
+ */
33
+ center?: boolean;
34
+ children?: Snippet;
35
+ } & HTMLButtonAttributes & {
24
36
  interactive: true;
25
37
  selectable?: false;
26
38
  checked?: never;
27
- };
28
- type SelectableTileProps = BaseProps & HTMLInputAttributes & {
39
+ }) | (BaseStyleProps & MarginStyleProps & StyleProps<readonly ["bg", "fg", "padding", "borderRadius", "marginBlock", "marginInline", "fontFamily", "fontSize", "fontWeight", "lineHeight", "letterSpacing", "textAlign", "textTransform", "textDecoration", "fontVariant", "lineWidth"]> & {
40
+ /** Distribution along the tile's own axis, which is vertical. */
41
+ justify?: string | null;
42
+ /** Cross-axis alignment, which is horizontal. Centred already. */
43
+ align?: string | null;
44
+ /**
45
+ * Shorthand for centring the tile's contents. Sets both axes, unlike
46
+ * `Stack`'s `center`, because a Tile already centres horizontally -- setting
47
+ * only the cross axis here would be a no-op and `<Tile center>` would look
48
+ * broken.
49
+ */
50
+ center?: boolean;
51
+ children?: Snippet;
52
+ } & HTMLInputAttributes & {
29
53
  selectable: true;
30
54
  interactive?: false;
31
55
  checked?: boolean;
32
- };
33
- type Props = StaticTileProps | InteractiveTileProps | SelectableTileProps;
34
- declare const Tile: import("svelte").Component<Props, {}, "checked">;
56
+ }), {}, "checked">;
35
57
  type Tile = ReturnType<typeof Tile>;
36
58
  export default Tile;
@@ -1,4 +1,6 @@
1
1
  <script lang="ts">const browser = typeof window !== "undefined";
2
+ /* No attribute pass-through, deliberately: this renders CodeInner (loaded
3
+ lazily in the browser) and no element of its own. */
2
4
  let { code, inline = false, language = "html" } = $props();
3
5
  let component = $state(null);
4
6
  if (browser) {
@@ -1,8 +1,20 @@
1
- <script lang="ts">import { injectVars } from "../util";
2
- // migrate props via $props() so we can pick up extra CSS var props in restProps
3
- let { state = "inprogress", value = 0, max = 100, bg = null, fg = null, padding = null, width = null, height = null, children, ...restProps } = $props();
4
- const cssKeys = ["bg", "fg", "padding", "width", "height"];
5
- const style = $derived(injectVars(restProps, "progress", cssKeys));
1
+ <script lang="ts">import { elementProps } from "../util";
2
+ import { COLOR_VARS, PADDING_VARS, RADIUS_VARS, TYPOGRAPHY_CONTAINER_VARS, } from "../styleProps";
3
+ let { state = "inprogress", value = 0, max = 100, bg = null, fg = null, padding = null, width = null, height = null, children, progressLabel = "Progress", class: className, ...restProps } = $props();
4
+ /* The shorthands this component's own CSS backs, one group per mixin
5
+ it includes. The Props type is derived from this same array, so what
6
+ the component accepts and what it emits cannot drift apart. */
7
+ const PROGRESS_VARS = [
8
+ ...COLOR_VARS,
9
+ ...PADDING_VARS,
10
+ ...RADIUS_VARS,
11
+ ...TYPOGRAPHY_CONTAINER_VARS,
12
+ ];
13
+ const cssKeys = [...PROGRESS_VARS, "width", "height"];
14
+ /* Every style prop is destructured above, so they are handed back
15
+ explicitly. Passing `restProps` alone -- which is what this did before --
16
+ meant <Progress bg="red"> emitted no --progress-bg at all. */
17
+ const el = $derived(elementProps({ bg, fg, padding, width, height, ...restProps }, "progress", cssKeys));
6
18
  // Compute filled fraction (0..1). 'indeterminate' treats as 1 for the visual bar,
7
19
  // but the native <progress> should omit the value attribute for indeterminate.
8
20
  let fillFraction = $derived(value === "indeterminate"
@@ -19,11 +31,16 @@ let halfFull = $derived(fillFraction > 0.5);
19
31
  1) Hidden native <progress> for a11y.
20
32
  If 'indeterminate', omit the value attribute entirely.
21
33
  -->
22
- <div class="progress-container" data-state={state} {style}>
34
+ <div class={["progress-container", className]} data-state={state} {...el}>
23
35
  {#if value === "indeterminate"}
24
- <progress aria-label="Progress" {max} hidden></progress>
36
+ <progress aria-label={progressLabel} {max} hidden></progress>
25
37
  {:else}
26
- <progress aria-label="Progress" {max} value={Number(value)} hidden></progress>
38
+ <progress
39
+ aria-label={progressLabel}
40
+ {max}
41
+ value={Number(value)}
42
+ hidden
43
+ ></progress>
27
44
  {/if}
28
45
 
29
46
  <!-- 2) Custom track with diagonal stripes (scrolling) when animateTrack=true -->
@@ -1,15 +1,22 @@
1
+ import { type StyleProps } from "../styleProps";
1
2
  import type { Snippet } from "svelte";
2
- type $$ComponentProps = {
3
+ import type { HTMLAttributes } from "svelte/elements";
4
+ import type { BaseStyleProps, ContainProps } from "../types";
5
+ declare const Progress: import("svelte").Component<ContainProps<HTMLAttributes<HTMLDivElement>, {
3
6
  state?: "uninitiated" | "inprogress" | "complete";
4
7
  value?: number | "indeterminate";
5
8
  max?: number;
6
- bg?: string | null;
7
- fg?: string | null;
8
- padding?: string | null;
9
- width?: string | null;
10
- height?: string | null;
11
9
  children?: Snippet;
12
- } & Record<string, unknown>;
13
- declare const Progress: import("svelte").Component<$$ComponentProps, {}, "">;
10
+ /**
11
+ * Accessible name for the native <progress> that carries this bar's
12
+ * semantics. Say what is progressing -- `progressLabel="Upload"` -- since
13
+ * "Progress" tells a screen-reader user nothing they did not already know
14
+ * from the role.
15
+ *
16
+ * Named `progressLabel`, not `label`, because `label` is a real HTML
17
+ * attribute; a style or a11y prop must never shadow one.
18
+ */
19
+ progressLabel?: string;
20
+ }, BaseStyleProps & StyleProps<readonly ["bg", "fg", "padding", "borderRadius", "fontFamily", "fontSize", "fontWeight", "lineHeight", "letterSpacing", "textAlign", "textTransform", "textDecoration", "fontVariant", "lineWidth"]>>, {}, "">;
14
21
  type Progress = ReturnType<typeof Progress>;
15
22
  export default Progress;
@@ -1,32 +1,33 @@
1
- <script lang="ts">import { injectVars } from "../util";
2
- let { children, primary, warning, danger, success, info, onclose = null, ...restProps } = $props();
3
- let style = $derived(injectVars(restProps, "tag", [
4
- "bg",
5
- "fg",
6
- "padding",
7
- "borderRadius",
8
- "fontSize",
9
- ]));
1
+ <script lang="ts">import { elementProps } from "../util";
2
+ import { COLOR_VARS, PADDING_VARS, RADIUS_VARS, TYPOGRAPHY_VARS, } from "../styleProps";
3
+ let { children, primary, warning, danger, success, info, onclose = null, closeLabel = "Remove", class: className, ...restProps } = $props();
4
+ /* The shorthands tag's own CSS backs, one group per mixin it
5
+ includes. The Props type is derived from this same array, so what the
6
+ component accepts and what it emits cannot drift apart. */
7
+ const TAG_VARS = [
8
+ ...COLOR_VARS,
9
+ ...PADDING_VARS,
10
+ ...RADIUS_VARS,
11
+ ...TYPOGRAPHY_VARS,
12
+ ];
13
+ const el = $derived(elementProps(restProps, "tag", TAG_VARS));
10
14
  </script>
11
15
 
12
16
  <span
13
- {style}
14
- class="tag"
17
+ class={["tag", className]}
15
18
  class:closable={!!onclose}
16
- {...restProps}
17
19
  class:primary
18
20
  class:warning
19
21
  class:danger
20
22
  class:success
21
23
  class:info
24
+ {...el}
22
25
  >
23
- {@render children?.()}
24
- {#if onclose}
25
- &nbsp;
26
- <button class="close-button" aria-label="Close tag" onclick={onclose}>
27
- &times;
28
- </button>
29
- {/if}
26
+ {@render children?.()}{#if onclose}<button
27
+ class="close-button"
28
+ aria-label={closeLabel}
29
+ onclick={onclose}><span aria-hidden="true">&times;</span></button
30
+ >{/if}
30
31
  </span>
31
32
 
32
33
  <style>/* Logical, not top/bottom/left/right: margin-block/margin-inline already take
@@ -161,48 +162,100 @@ let style = $derived(injectVars(restProps, "tag", [
161
162
  margin-inline-end: var(--space);
162
163
  }
163
164
 
165
+ /* A closable tag lays its label and button out in flow rather than absolutely
166
+ positioning the button over the top-right corner. The old approach needed a
167
+ literal &nbsp; in the markup to stop the button covering the last character,
168
+ which still overlapped once a tag wrapped to two lines and put the button's
169
+ hit area outside the padding. As a flex item the button reserves its own
170
+ space at any length, and the gap scales with the tag's font-size. */
164
171
  .tag.closable {
165
- position: relative;
172
+ display: inline-flex;
173
+ align-items: center;
174
+ gap: var(--tag-close-button-gap, 0.35em);
166
175
  }
167
176
 
168
- .tag button {
177
+ .tag .close-button {
169
178
  background: transparent;
170
179
  color: inherit;
171
180
  box-shadow: none;
172
181
  border: none;
173
- position: absolute;
174
- top: 0;
175
- right: 0;
182
+ padding: 0;
183
+ margin: 0;
184
+ /* Square, so the focus ring and pointer target read as a button rather
185
+ than tracking the width of the glyph. */
186
+ inline-size: var(--tag-close-button-size, 1.25em);
187
+ block-size: var(--tag-close-button-size, 1.25em);
188
+ position: relative;
189
+ display: inline-flex;
190
+ align-items: center;
191
+ justify-content: center;
192
+ border-radius: var(--tag-close-button-border-radius, 50%);
193
+ font-size: inherit;
194
+ line-height: 1;
195
+ flex: none;
176
196
  cursor: var(--tag-close-button-cursor, var(--button-cursor, var(--cursor, pointer)));
177
197
  transition: filter var(--tag-close-button-transition, var(--button-transition, var(--transition, 300ms))), background-color var(--tag-close-button-transition, var(--button-transition, var(--transition, 300ms))), border-color var(--tag-close-button-transition, var(--button-transition, var(--transition, 300ms))), box-shadow var(--tag-close-button-transition, var(--button-transition, var(--transition, 300ms))), transform var(--tag-close-button-transition, var(--button-transition, var(--transition, 300ms)));
178
198
  }
179
199
 
180
- .tag button:hover {
181
- filter: var(--tag-close-button-hover-filter, var(--button-hover-filter, var(--hover-filter, brightness(1.05))));
182
- transform: var(--tag-close-button-hover-transform, var(--button-hover-transform, var(--hover-transform, none)));
183
- background-color: color-mix(in oklch, var(--_background-color) var(--hover-base-color-percentage, 90%), var(--hover-color-mix, white) calc(100% - var(--hover-base-color-percentage, 90%)));
184
- box-shadow: var(--tag-close-button-hover-box-shadow, var(--button-hover-box-shadow, var(--hover-box-shadow, var(--_box-shadow, none))));
200
+ /* The affordance is a scrim mixed from the tag's own FOREGROUND, not from
201
+ `clickable()`'s fill.
202
+
203
+ clickable() derives its hover fill from --_background-color, which this
204
+ button inherits from the tag. On a red tag that produced 90% red + 10%
205
+ white -- a muddy circle a shade off the tag it sits on, reading as a
206
+ rendering artifact rather than a control. Mixing from currentColor
207
+ instead inverts correctly against any tag colour: a light scrim on a
208
+ saturated tag, a dark one on the default pale tag, with no per-variant
209
+ rules. */
210
+ .tag .close-button::before {
211
+ content: "";
212
+ position: absolute;
213
+ inset: 0;
214
+ border-radius: inherit;
215
+ background-color: transparent;
216
+ transition: background-color var(--tag-close-button-transition, var(--transition, 150ms));
217
+ }
218
+
219
+ .tag .close-button:hover::before {
220
+ background-color: var(--tag-close-button-hover-bg, color-mix(in oklab, transparent, currentColor 18%));
221
+ }
222
+
223
+ .tag .close-button:active::before {
224
+ background-color: var(--tag-close-button-active-bg, color-mix(in oklab, transparent, currentColor 30%));
185
225
  }
186
226
 
187
- .tag button:active {
188
- filter: var(--tag-close-button-active-filter, var(--button-active-filter, var(--active-filter, brightness(0.9))));
189
- transform: var(--tag-close-button-active-transform, var(--button-active-transform, var(--active-transform, none)));
190
- background-color: color-mix(in oklch, var(--_background-color) var(--active-base-color-percentage, 90%), var(--active-color-mix, black) calc(100% - var(--active-base-color-percentage, 90%)));
191
- box-shadow: var(--tag-close-button-active-box-shadow, var(--button-active-box-shadow, var(--active-box-shadow, var(--_box-shadow, none))));
227
+ /* WCAG 2.5.8 wants a 24x24 CSS px target. The glyph is sized in `em` so it
228
+ stays proportional to a tag that is deliberately tiny (0.75rem), which
229
+ left a 15x15 target. This expands the POINTER target without changing
230
+ the visual size or the tag's height. */
231
+ .tag .close-button::after {
232
+ content: "";
233
+ position: absolute;
234
+ top: 50%;
235
+ left: 50%;
236
+ translate: -50% -50%;
237
+ inline-size: max(100%, var(--tag-close-button-target-size, 24px));
238
+ block-size: max(100%, var(--tag-close-button-target-size, 24px));
192
239
  }
193
240
 
194
- .tag button:disabled {
195
- cursor: var(--tag-close-button-disabled-cursor, var(--button-disabled-cursor, var(--disabled-cursor, not-allowed)));
196
- transform: var(--tag-close-button-disabled-transform, var(--button-disabled-transform, var(--disabled-transform, none)));
197
- filter: var(--tag-close-button-disabled-filter, var(--button-disabled-filter, var(--disabled-filter, grayscale(0.5))));
198
- background-color: color-mix(in oklch, var(--_background-color) var(--disabled-base-color-percentage, 50%), var(--disabled-color-mix, gray) calc(100% - var(--disabled-base-color-percentage, 50%)));
199
- box-shadow: var(--tag-close-button-disabled-box-shadow, var(--button-disabled-box-shadow, var(--disabled-box-shadow, var(--_box-shadow, none))));
241
+ /* focus-ring()'s 3px spread shadow plus its 2px offset is sized for a button
242
+ standing on a page; on a 15px control inside a 26px tag it ballooned well
243
+ outside the tag.
244
+
245
+ The room available is exactly the tag's padding-block (0.2em, ~2.4px at
246
+ the default tag font-size) -- the button is as tall as the tag's line box,
247
+ so there is nothing else to grow into. Hence a 2px ring at no offset: it
248
+ reads clearly, hugs the circle, and clears the tag's edge. The scrim stays
249
+ lit under focus so keyboard focus reads the same as pointer hover (the
250
+ `focusable-as-hover` idea, with this component's own fill). */
251
+ .tag .close-button:focus-visible {
252
+ outline: var(--tag-close-button-focus-color, var(--focus-color, currentColor)) solid var(--tag-close-button-focus-width, 2px);
253
+ outline-offset: var(--tag-close-button-focus-offset, 0px);
254
+ box-shadow: none;
200
255
  }
201
256
 
202
- .tag button:focus-visible {
203
- outline: var(--focus-color, -webkit-focus-ring-color) auto 1px;
204
- outline-offset: var(--focus-outline-offset, 2px);
205
- box-shadow: var(--focus-ring-box-shadow, 0 0 0 3px var(--focus-shadow-color, rgba(100, 150, 250, 0.5)));
257
+ .tag .close-button:focus-visible::before {
258
+ background-color: var(--tag-close-button-hover-bg, color-mix(in oklab, transparent, currentColor 18%));
206
259
  }
207
260
 
208
261
  .tag:first-child {
@@ -1,14 +1,26 @@
1
+ import type { Snippet } from "svelte";
1
2
  import type { HTMLAttributes } from "svelte/elements";
2
- import type { TagStyleProps } from "../types";
3
- type Props = {
4
- children?: import("svelte").Snippet;
3
+ import type { ContainProps, TagStyleProps } from "../types";
4
+ import { type StyleProps } from "../styleProps";
5
+ declare const Tag: import("svelte").Component<ContainProps<HTMLAttributes<HTMLSpanElement>, {
6
+ children?: Snippet;
5
7
  primary?: boolean;
6
8
  warning?: boolean;
7
9
  danger?: boolean;
8
10
  success?: boolean;
9
11
  info?: boolean;
10
12
  onclose?: (() => void) | null;
11
- } & TagStyleProps & HTMLAttributes<HTMLSpanElement>;
12
- declare const Tag: import("svelte").Component<Props, {}, "">;
13
+ /**
14
+ * Accessible name for the close button.
15
+ *
16
+ * The default describes the *widget* ("close a tag"), which is rarely
17
+ * what the action means to a user. A tag is a filter chip in one app and
18
+ * a removable label in another, so say what pressing it does here:
19
+ * `closeLabel="Remove Fiction"`, `closeLabel="Stop searching by author"`.
20
+ * Including the tag's own text is what makes a list of close buttons
21
+ * distinguishable to a screen reader.
22
+ */
23
+ closeLabel?: string;
24
+ }, TagStyleProps & StyleProps<readonly ["bg", "fg", "padding", "borderRadius", "fontFamily", "fontSize", "fontWeight", "lineHeight", "letterSpacing", "textAlign", "textTransform", "textDecoration", "fontVariant"]>>, {}, "">;
13
25
  type Tag = ReturnType<typeof Tag>;
14
26
  export default Tag;