contain-css-svelte 1.1.19 → 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 +22 -8
  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 +27 -15
  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,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;
@@ -1,8 +1,21 @@
1
1
  <script lang="ts">import CircleButton from "../controls/CircleButton.svelte";
2
- import { copyCSSVariables, injectVars } from "../util";
3
- let { open = true, modal = true, dismissible = false, children, onClose, onclose: oncloseProp = null, ...restProps } = $props();
2
+ import { copyCSSVariables, splitProps } from "../util";
3
+ import { COLOR_VARS, PADDING_VARS, RADIUS_VARS, TYPOGRAPHY_CONTAINER_VARS, } from "../styleProps";
4
+ let { open = true, modal = true, dismissible = false, children, onClose, onclose: oncloseProp = null, class: className, ...restProps } = $props();
4
5
  let onclose = $derived(oncloseProp || onClose);
5
- const style = $derived(injectVars(restProps, "dialog", []));
6
+ /* The <section> is the variable carrier that copyCSSVariables reads through,
7
+ so it takes the style while the <dialog> takes the attributes -- a
8
+ caller's `aria-*` and `data-*` belong on the dialog itself. */
9
+ /* The shorthands this component's own CSS backs, one group per mixin
10
+ it includes. The Props type is derived from this same array, so what
11
+ the component accepts and what it emits cannot drift apart. */
12
+ const DIALOG_VARS = [
13
+ ...COLOR_VARS,
14
+ ...PADDING_VARS,
15
+ ...RADIUS_VARS,
16
+ ...TYPOGRAPHY_CONTAINER_VARS,
17
+ ];
18
+ const el = $derived(splitProps(restProps, "dialog", DIALOG_VARS));
6
19
  // Handle backdrop click to close modal (click outside behavior)
7
20
  // Clicks on the dialog element itself (the backdrop) close the dialog
8
21
  function handleBackdropClick(event) {
@@ -52,9 +65,9 @@ let dialogElement;
52
65
  let ref;
53
66
  </script>
54
67
 
55
- <section {style}>
68
+ <section style={el.style}>
56
69
  <div class="variable-placeholder" bind:this={ref}></div>
57
- <dialog bind:this={dialogElement} {onclose} {...restProps}>
70
+ <dialog bind:this={dialogElement} {onclose} class={className} {...el.attrs}>
58
71
  <div class="close-bar">
59
72
  <div class="close-button">
60
73
  <CircleButton onclick={onclose}>&times;</CircleButton>
@@ -1,12 +1,14 @@
1
+ import { type StyleProps } from "../styleProps";
1
2
  import type { Snippet } from "svelte";
2
- type PropsType = {
3
+ import type { HTMLDialogAttributes } from "svelte/elements";
4
+ import type { ContainProps } from "../types";
5
+ declare const Dialog: import("svelte").Component<ContainProps<HTMLDialogAttributes, {
3
6
  open?: boolean;
4
7
  modal?: boolean;
5
8
  dismissible?: boolean;
6
9
  children?: Snippet;
7
10
  onclose?: (() => void) | null;
8
11
  onClose?: (() => void) | null;
9
- } & Record<string, unknown>;
10
- declare const Dialog: import("svelte").Component<PropsType, {}, "">;
12
+ }, StyleProps<readonly ["bg", "fg", "padding", "borderRadius", "fontFamily", "fontSize", "fontWeight", "lineHeight", "letterSpacing", "textAlign", "textTransform", "textDecoration", "fontVariant", "lineWidth"]>>, {}, "">;
11
13
  type Dialog = ReturnType<typeof Dialog>;
12
14
  export default Dialog;
@@ -1,8 +1,24 @@
1
1
  <script lang="ts">import { onDestroy, tick } from "svelte";
2
+ import { elementProps } from "../util";
3
+ import { COLOR_VARS, PADDING_VARS, RADIUS_VARS, TYPOGRAPHY_CONTAINER_VARS, } from "../styleProps";
2
4
  let tooltipDiv = $state();
3
5
  let targetDiv = $state();
4
6
  let tooltipMeasurementDiv = $state();
5
- let { tooltipText = "", vertical = "bottom", horizontal = "right", children, tooltip, block = false, } = $props();
7
+ let { tooltipText = "", vertical = "bottom", horizontal = "right", children, tooltip, block = false, class: className, ...restProps } = $props();
8
+ /* The wrapper is the element a caller is pointing at -- the tooltip itself
9
+ is a popover the component owns and positions. */
10
+ /* The shorthands this component's own CSS backs, one group per mixin
11
+ it includes. The Props type is derived from this same array, so what
12
+ the component accepts and what it emits cannot drift apart. */
13
+ const TOOLTIP_VARS = [
14
+ ...COLOR_VARS,
15
+ ...PADDING_VARS,
16
+ ...RADIUS_VARS,
17
+ ...TYPOGRAPHY_CONTAINER_VARS,
18
+ ];
19
+ /* The variables land on the wrapper and reach the popover by inheritance:
20
+ the tooltip is a descendant of it, and custom properties inherit. */
21
+ const el = $derived(elementProps(restProps, "tooltip", TOOLTIP_VARS));
6
22
  // svelte-ignore state_referenced_locally
7
23
  let renderedVertical = $state(vertical);
8
24
  // svelte-ignore state_referenced_locally
@@ -206,11 +222,12 @@ async function showPopover() {
206
222
  {#if block}
207
223
  <!-- svelte-ignore a11y_no_static_element_interactions -->
208
224
  <div
209
- class="tooltip-wrapper"
225
+ class={["tooltip-wrapper", className]}
210
226
  onmouseenter={() => showPopover()}
211
227
  onmouseleave={() => hidePopover()}
212
228
  onfocusin={() => showPopover()}
213
229
  onfocusout={() => hidePopover()}
230
+ {...el}
214
231
  >
215
232
  <div class="tooltip-target" bind:this={targetDiv}>
216
233
  {@render children?.()}
@@ -241,11 +258,12 @@ async function showPopover() {
241
258
  {:else}
242
259
  <!-- svelte-ignore a11y_no_static_element_interactions -->
243
260
  <span
244
- class="tooltip-wrapper"
261
+ class={["tooltip-wrapper", className]}
245
262
  onmouseenter={() => showPopover()}
246
263
  onmouseleave={() => hidePopover()}
247
264
  onfocusin={() => showPopover()}
248
265
  onfocusout={() => hidePopover()}
266
+ {...el}
249
267
  >
250
268
  <span class="tooltip-target" bind:this={targetDiv}>
251
269
  {@render children?.()}
@@ -1,11 +1,13 @@
1
- interface Props {
1
+ import type { HTMLAttributes } from "svelte/elements";
2
+ import type { ContainProps } from "../types";
3
+ import { type StyleProps } from "../styleProps";
4
+ declare const Tooltip: import("svelte").Component<ContainProps<HTMLAttributes<HTMLElement>, {
2
5
  tooltipText?: string;
3
6
  vertical?: string;
4
7
  horizontal?: string;
5
8
  children?: import("svelte").Snippet;
6
9
  tooltip?: import("svelte").Snippet;
7
10
  block?: boolean;
8
- }
9
- declare const Tooltip: import("svelte").Component<Props, {}, "">;
11
+ }, StyleProps<readonly ["bg", "fg", "padding", "borderRadius", "fontFamily", "fontSize", "fontWeight", "lineHeight", "letterSpacing", "textAlign", "textTransform", "textDecoration", "fontVariant", "lineWidth"]>>, {}, "">;
10
12
  type Tooltip = ReturnType<typeof Tooltip>;
11
13
  export default Tooltip;
@@ -0,0 +1,67 @@
1
+ /**
2
+ * The style-prop shorthands, grouped to mirror the SCSS mixins that make them
3
+ * readable.
4
+ *
5
+ * The problem this solves: which shorthands a component accepted used to be a
6
+ * hand-maintained list in each component, drifting from the mixins its CSS
7
+ * actually included. Tag exposed `fontSize` while the other 23 components
8
+ * whose CSS includes a typography mixin exposed none of it -- `--button-font-
9
+ * weight` resolved perfectly well, there was just no prop for it. In the other
10
+ * direction, FormItem injected three variables no stylesheet read at all.
11
+ *
12
+ * So each group below names one mixin, and a component's prop list is the
13
+ * union of the groups for the mixins its own CSS includes. "Which shorthands
14
+ * does this component take" stops being a decision and becomes a consequence.
15
+ *
16
+ * The prop types are derived from these same arrays via `StyleProps<>`, so the
17
+ * type and the runtime list cannot disagree -- adding a name to the array is
18
+ * what adds the prop.
19
+ *
20
+ * A shorthand is only safe when the element the component renders has no HTML
21
+ * attribute of that name: `ContainProps` resolves the clash by dropping the
22
+ * attribute, which is right for a deprecated or meaningless one and wrong for
23
+ * `<progress max>` or `<option label>`. That is why the size group is applied
24
+ * per component rather than being folded into the others, and why Progress's
25
+ * accessible-name prop is `progressLabel`.
26
+ */
27
+ /** Every style prop takes a CSS value, or null/undefined to leave it unset. */
28
+ export type StyleProps<T extends readonly string[]> = {
29
+ [K in T[number]]?: string | null;
30
+ };
31
+ /** `@include color-props(<prefix>, ...)` */
32
+ export declare const COLOR_VARS: readonly ["bg", "fg"];
33
+ /** `@include padding-props(...)`, and the box-props family that includes it */
34
+ export declare const PADDING_VARS: readonly ["padding"];
35
+ /** The border-radius the box-props family sets alongside padding */
36
+ export declare const RADIUS_VARS: readonly ["borderRadius"];
37
+ /** `@include margin-props(<prefix>, ...)` */
38
+ export declare const MARGIN_VARS: readonly ["marginBlock", "marginInline"];
39
+ /** `@include gap-props(<prefix>, ...)` */
40
+ export declare const GAP_VARS: readonly ["gap"];
41
+ /**
42
+ * `@include typography-props(...)` and its `-bare` / `-bold` variants.
43
+ *
44
+ * `fontSize` was the only one of these ever exposed as a prop, and only on
45
+ * Tag. The rest resolved in CSS the whole time.
46
+ */
47
+ export declare const TYPOGRAPHY_VARS: readonly ["fontFamily", "fontSize", "fontWeight", "lineHeight", "letterSpacing", "textAlign", "textTransform", "textDecoration", "fontVariant"];
48
+ /**
49
+ * `@include typography-container-props(...)`, which is typography-props plus
50
+ * the readable measure it imposes on prose children.
51
+ */
52
+ export declare const TYPOGRAPHY_CONTAINER_VARS: readonly ["fontFamily", "fontSize", "fontWeight", "lineHeight", "letterSpacing", "textAlign", "textTransform", "textDecoration", "fontVariant", "lineWidth"];
53
+ /**
54
+ * `@include box-size-props(<prefix>, ...)`.
55
+ *
56
+ * Deliberately NOT part of any other group: `width` and `height` are real HTML
57
+ * attributes on `<input>`, `<img>`, `<canvas>` and `<iframe>`, so a component
58
+ * rendering one of those must not shadow them. Applied per component.
59
+ */
60
+ export declare const SIZE_VARS: readonly ["width", "minWidth", "maxWidth", "height", "minHeight", "maxHeight"];
61
+ export type ColorStyleProps = StyleProps<typeof COLOR_VARS>;
62
+ export type PaddingStyleProps = StyleProps<typeof PADDING_VARS>;
63
+ export type RadiusStyleProps = StyleProps<typeof RADIUS_VARS>;
64
+ export type GapStyleProps = StyleProps<typeof GAP_VARS>;
65
+ export type TypographyStyleProps = StyleProps<typeof TYPOGRAPHY_VARS>;
66
+ export type TypographyContainerStyleProps = StyleProps<typeof TYPOGRAPHY_CONTAINER_VARS>;
67
+ export type SizeStyleProps = StyleProps<typeof SIZE_VARS>;
@@ -0,0 +1,76 @@
1
+ /**
2
+ * The style-prop shorthands, grouped to mirror the SCSS mixins that make them
3
+ * readable.
4
+ *
5
+ * The problem this solves: which shorthands a component accepted used to be a
6
+ * hand-maintained list in each component, drifting from the mixins its CSS
7
+ * actually included. Tag exposed `fontSize` while the other 23 components
8
+ * whose CSS includes a typography mixin exposed none of it -- `--button-font-
9
+ * weight` resolved perfectly well, there was just no prop for it. In the other
10
+ * direction, FormItem injected three variables no stylesheet read at all.
11
+ *
12
+ * So each group below names one mixin, and a component's prop list is the
13
+ * union of the groups for the mixins its own CSS includes. "Which shorthands
14
+ * does this component take" stops being a decision and becomes a consequence.
15
+ *
16
+ * The prop types are derived from these same arrays via `StyleProps<>`, so the
17
+ * type and the runtime list cannot disagree -- adding a name to the array is
18
+ * what adds the prop.
19
+ *
20
+ * A shorthand is only safe when the element the component renders has no HTML
21
+ * attribute of that name: `ContainProps` resolves the clash by dropping the
22
+ * attribute, which is right for a deprecated or meaningless one and wrong for
23
+ * `<progress max>` or `<option label>`. That is why the size group is applied
24
+ * per component rather than being folded into the others, and why Progress's
25
+ * accessible-name prop is `progressLabel`.
26
+ */
27
+ /** `@include color-props(<prefix>, ...)` */
28
+ export const COLOR_VARS = ["bg", "fg"];
29
+ /** `@include padding-props(...)`, and the box-props family that includes it */
30
+ export const PADDING_VARS = ["padding"];
31
+ /** The border-radius the box-props family sets alongside padding */
32
+ export const RADIUS_VARS = ["borderRadius"];
33
+ /** `@include margin-props(<prefix>, ...)` */
34
+ export const MARGIN_VARS = ["marginBlock", "marginInline"];
35
+ /** `@include gap-props(<prefix>, ...)` */
36
+ export const GAP_VARS = ["gap"];
37
+ /**
38
+ * `@include typography-props(...)` and its `-bare` / `-bold` variants.
39
+ *
40
+ * `fontSize` was the only one of these ever exposed as a prop, and only on
41
+ * Tag. The rest resolved in CSS the whole time.
42
+ */
43
+ export const TYPOGRAPHY_VARS = [
44
+ "fontFamily",
45
+ "fontSize",
46
+ "fontWeight",
47
+ "lineHeight",
48
+ "letterSpacing",
49
+ "textAlign",
50
+ "textTransform",
51
+ "textDecoration",
52
+ "fontVariant",
53
+ ];
54
+ /**
55
+ * `@include typography-container-props(...)`, which is typography-props plus
56
+ * the readable measure it imposes on prose children.
57
+ */
58
+ export const TYPOGRAPHY_CONTAINER_VARS = [
59
+ ...TYPOGRAPHY_VARS,
60
+ "lineWidth",
61
+ ];
62
+ /**
63
+ * `@include box-size-props(<prefix>, ...)`.
64
+ *
65
+ * Deliberately NOT part of any other group: `width` and `height` are real HTML
66
+ * attributes on `<input>`, `<img>`, `<canvas>` and `<iframe>`, so a component
67
+ * rendering one of those must not shadow them. Applied per component.
68
+ */
69
+ export const SIZE_VARS = [
70
+ "width",
71
+ "minWidth",
72
+ "maxWidth",
73
+ "height",
74
+ "minHeight",
75
+ "maxHeight",
76
+ ];
package/dist/types.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Common CSS variable props that can be passed to components.
3
- * These are converted to CSS custom properties via injectVars().
3
+ * These are converted to CSS custom properties via elementProps().
4
4
  */
5
5
  /** Base style props supported by most components */
6
6
  export interface BaseStyleProps {
@@ -88,3 +88,42 @@ export interface DropdownMenuStyleProps extends BaseStyleProps {
88
88
  triggerFg?: string | null;
89
89
  triggerPadding?: string | null;
90
90
  }
91
+ /**
92
+ * Props that let a caller set CSS custom properties directly on a component.
93
+ *
94
+ * NOTE: writing `<Tag --tag-bg="red">` in a template does NOT go through this
95
+ * type -- Svelte intercepts `--*` attributes on components itself and renders
96
+ * `<svelte-css-wrapper style="display: contents; --tag-bg: red;">` around the
97
+ * component. This type covers the remaining path, an object spread
98
+ * (`<Tag {...{"--tag-bg": "red"}}>`), which Svelte cannot detect statically and
99
+ * therefore passes through as an ordinary prop.
100
+ */
101
+ export type CssVarProps = {
102
+ [key: `--${string}`]: string | number | null | undefined;
103
+ };
104
+ /**
105
+ * The standard prop type for a Contain CSS component.
106
+ *
107
+ * Composes the three things every component's props are made of, and -- the
108
+ * reason this exists rather than a bare `&` chain -- removes the element
109
+ * attributes that the style props and the component's own props shadow.
110
+ * `width`, `height` and `align` are real HTML attributes, so intersecting
111
+ * `BaseStyleProps` with `HTMLInputAttributes` the old way produced
112
+ * `(string | null) & (string | number | undefined)` for `width`: a type neither
113
+ * side meant, which silently accepted and rejected the wrong values.
114
+ *
115
+ * @typeParam Attributes - the element's attribute type, e.g. `HTMLButtonAttributes`
116
+ * @typeParam Own - the component's own props (snippets, variant booleans, ...)
117
+ * @typeParam Style - the component's style props, e.g. `BaseStyleProps`
118
+ * @typeParam Owned - attributes the component controls and a caller must not
119
+ * set, e.g. `"type"` on Slider's range input. Spreading rest props last means
120
+ * a caller's value would otherwise win over the component's own.
121
+ *
122
+ * @example
123
+ * type Props = ContainProps<
124
+ * HTMLButtonAttributes,
125
+ * { primary?: boolean; children?: Snippet },
126
+ * BaseStyleProps
127
+ * >;
128
+ */
129
+ export type ContainProps<Attributes, Own = {}, Style = {}, Owned extends keyof Attributes | string = never> = Own & Style & Omit<Attributes, keyof Own | keyof Style | Owned> & CssVarProps;
package/dist/types.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
2
  * Common CSS variable props that can be passed to components.
3
- * These are converted to CSS custom properties via injectVars().
3
+ * These are converted to CSS custom properties via elementProps().
4
4
  */
5
5
  export {};
@@ -1,5 +1,5 @@
1
- <script lang="ts">import { injectVars } from "../util";
2
- let { children, primary = false, secondary = false, info = false, success = false, warning = false, danger = false, muted = false, bold = false, italic = false, color, amount, style: inlineStyle, ...restProps } = $props();
1
+ <script lang="ts">import { elementProps } from "../util";
2
+ let { children, primary = false, secondary = false, info = false, success = false, warning = false, danger = false, muted = false, bold = false, italic = false, color, amount, class: className, ...restProps } = $props();
3
3
  // Choose one tone deterministically; explicit color takes precedence.
4
4
  const tone = $derived(danger
5
5
  ? "danger"
@@ -30,17 +30,10 @@ const foreground = $derived.by(() => {
30
30
  return mixed;
31
31
  return `var(--${tone}-text-fg, ${mixed})`;
32
32
  });
33
- const elementProps = $derived(Object.fromEntries(Object.entries(restProps).filter(([key]) => !key.startsWith("--"))));
34
- const style = $derived(injectVars(restProps, "text", []) + (inlineStyle ?? ""));
33
+ const el = $derived(elementProps(restProps, "text"));
35
34
  </script>
36
35
 
37
- <span
38
- {...elementProps}
39
- {style}
40
- style:color={foreground}
41
- class:bold
42
- class:italic
43
- >
36
+ <span class={className} class:bold class:italic style:color={foreground} {...el}>
44
37
  {@render children?.()}
45
38
  </span>
46
39
 
@@ -1,12 +1,9 @@
1
- <script lang="ts">import { injectVars } from "../util";
2
- let { id = "", marginBlock = null, marginInline = null, children } = $props();
3
- const style = $derived(injectVars({ marginBlock, marginInline }, "text-layout", [
4
- "marginBlock",
5
- "marginInline",
6
- ]));
1
+ <script lang="ts">import { elementProps } from "../util";
2
+ let { id = "", children, class: className, ...restProps } = $props();
3
+ const el = $derived(elementProps(restProps, "text-layout", ["marginBlock", "marginInline"]));
7
4
  </script>
8
5
 
9
- <div {id} {style}>
6
+ <div {id} class={className} {...el}>
10
7
  {@render children?.()}
11
8
  </div>
12
9
 
@@ -1,8 +1,9 @@
1
- import type { MarginStyleProps } from "../types";
2
- interface Props extends MarginStyleProps {
1
+ import type { HTMLAttributes } from "svelte/elements";
2
+ import type { ContainProps, MarginStyleProps } from "../types";
3
+ type Props = ContainProps<HTMLAttributes<HTMLDivElement>, {
3
4
  id?: string;
4
5
  children?: import("svelte").Snippet;
5
- }
6
+ }, MarginStyleProps>;
6
7
  declare const TextLayout: import("svelte").Component<Props, {}, "">;
7
8
  type TextLayout = ReturnType<typeof TextLayout>;
8
9
  export default TextLayout;