@streamscloud/kit 0.76.4 → 0.77.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 (30) hide show
  1. package/dist/ui/form-field/cmp.form-field-validatable.svelte +3 -3
  2. package/dist/ui/form-field/cmp.form-field-validatable.svelte.d.ts +4 -4
  3. package/dist/ui/page-layout/cmp.page-layout.svelte +11 -9
  4. package/dist/ui/page-layout/cmp.page-layout.svelte.d.ts +3 -3
  5. package/dist/ui/page-layout/types.d.ts +2 -2
  6. package/dist/ui/table/_table-styles.scss +37 -8
  7. package/dist/ui/table/cmp.table-draggable.svelte +63 -23
  8. package/dist/ui/table/cmp.table-draggable.svelte.d.ts +10 -2
  9. package/dist/ui/table/cmp.table.svelte +50 -19
  10. package/dist/ui/table/cmp.table.svelte.d.ts +10 -2
  11. package/dist/ui/table/table-cells/table-checkbox-cell.svelte +0 -1
  12. package/dist/ui/table/table-cells/table-move-row-cell.svelte +0 -2
  13. package/dist/ui/table/table-cells/table-text-cell.svelte +0 -1
  14. package/dist/ui/table/table-column-models.svelte.d.ts +5 -0
  15. package/dist/ui/table/table-column-models.svelte.js +6 -0
  16. package/dist/ui/table/table-headers/table-header-sortable.svelte +1 -4
  17. package/dist/ui/table/table-headers/table-header.svelte +1 -1
  18. package/dist/ui/table/table-view.svelte.d.ts +2 -2
  19. package/dist/ui/table/table-view.svelte.js +11 -4
  20. package/dist/ui/table/types.d.ts +17 -0
  21. package/dist/ui/validatable/cmp.validatable.svelte +3 -4
  22. package/dist/ui/validatable/cmp.validatable.svelte.d.ts +6 -6
  23. package/dist/ui/validatable/cmp.validation-error.svelte +4 -4
  24. package/dist/ui/validatable/cmp.validation-error.svelte.d.ts +7 -6
  25. package/dist/ui/validatable/field-frame.svelte +25 -15
  26. package/dist/ui/validatable/field-frame.svelte.d.ts +8 -8
  27. package/dist/ui/validatable/index.d.ts +1 -0
  28. package/dist/ui/validatable/types.d.ts +7 -0
  29. package/dist/ui/validatable/types.js +1 -0
  30. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  <script lang="ts" generics="T extends Record<string, unknown>, K extends keyof T & string">import { Validatable } from '../validatable';
2
2
  import { default as FormField } from './cmp.form-field.svelte';
3
- let { handler, name, label, hint, required = false, validateOn, debounceMs, disableTouchedTracking, reserveErrorSpace = false, on, children } = $props();
3
+ let { handler, name, label, hint, required = false, validateOn, debounceMs, disableTouchedTracking, errorPlacement, on, children } = $props();
4
4
  </script>
5
5
 
6
6
  <FormField label={label} hint={hint} required={required}>
@@ -10,7 +10,7 @@ let { handler, name, label, hint, required = false, validateOn, debounceMs, disa
10
10
  validateOn={validateOn}
11
11
  debounceMs={debounceMs}
12
12
  disableTouchedTracking={disableTouchedTracking}
13
- reserveErrorSpace={reserveErrorSpace}
13
+ errorPlacement={errorPlacement}
14
14
  on={on}
15
15
  children={children} />
16
16
  </FormField>
@@ -41,5 +41,5 @@ Identical visual output to:
41
41
  </FormField>
42
42
  ```
43
43
 
44
- Both `FormField`'s label/required and `Validatable`'s no-jump error slot apply unchanged — this cmp adds no styling, just collapses the wiring.
44
+ Both `FormField`'s label/required and `Validatable`'s error slot (`errorPlacement`) apply unchanged — this cmp adds no styling, just collapses the wiring.
45
45
  -->
@@ -1,5 +1,5 @@
1
1
  import type { FormValidationHandler } from '../../core/validation';
2
- import { type FieldBinding, type ValidateOnEvent } from '../validatable';
2
+ import { type ErrorPlacement, type FieldBinding, type ValidateOnEvent } from '../validatable';
3
3
  import type { Snippet } from 'svelte';
4
4
  declare function $$render<T extends Record<string, unknown>, K extends keyof T & string>(): {
5
5
  props: {
@@ -19,8 +19,8 @@ declare function $$render<T extends Record<string, unknown>, K extends keyof T &
19
19
  debounceMs?: number;
20
20
  /** Show errors before the field has been touched. Forwarded to `<Validatable>`. @default false */
21
21
  disableTouchedTracking?: boolean;
22
- /** Reserve space below the field so a toggling error doesn't shift layout. Forwarded to `<Validatable>` (whose own default is `true`). @default false */
23
- reserveErrorSpace?: boolean;
22
+ /** Where the error message sits. Forwarded to `<Validatable>`. @default 'overlay' */
23
+ errorPlacement?: ErrorPlacement;
24
24
  /** Extra consumer callbacks. Forwarded to `<Validatable>`. */
25
25
  on?: {
26
26
  input?: (value: T[K]) => void;
@@ -74,7 +74,7 @@ interface $$IsomorphicComponent {
74
74
  * </FormField>
75
75
  * ```
76
76
  *
77
- * Both `FormField`'s label/required and `Validatable`'s no-jump error slot apply unchanged — this cmp adds no styling, just collapses the wiring.
77
+ * Both `FormField`'s label/required and `Validatable`'s error slot (`errorPlacement`) apply unchanged — this cmp adds no styling, just collapses the wiring.
78
78
  */
79
79
  declare const Cmp: $$IsomorphicComponent;
80
80
  type Cmp<T extends Record<string, unknown>, K extends keyof T & string> = InstanceType<typeof Cmp<T, K>>;
@@ -34,12 +34,14 @@ const { timeout: loadingTimeout, blocking: loadingBlocking } = $derived(resolveP
34
34
  on={{ click: navigationToggle.onClick }} />
35
35
  {/if}
36
36
  {#if editorControls}
37
- <LayoutControlButton
38
- icon={IconWindowMultiple}
39
- label={localization.togglePanels}
40
- active={editorControls.panelsToggle.active}
41
- flip
42
- on={{ click: editorControls.panelsToggle.onClick }} />
37
+ {#if editorControls.panelsToggle}
38
+ <LayoutControlButton
39
+ icon={IconWindowMultiple}
40
+ label={localization.togglePanels}
41
+ active={editorControls.panelsToggle.active}
42
+ flip
43
+ on={{ click: editorControls.panelsToggle.onClick }} />
44
+ {/if}
43
45
  <LayoutControlButton
44
46
  icon={IconDismiss}
45
47
  label={localization.close}
@@ -63,9 +65,9 @@ const { timeout: loadingTimeout, blocking: loadingBlocking } = $derived(resolveP
63
65
  PageLayout — top-level window chrome. Renders a fixed-height header (title + actions + an optional
64
66
  group of chrome buttons) above a flex-column body that typically contains a `PagePanels` row.
65
67
 
66
- `navigationToggle` surfaces the app-navigation button as a bare glyph; `editorControls` is the fixed
67
- editor pair — panels toggle + close, both required so an editor never ships half the preset, drawn as
68
- outlined squares. Every entry takes `{ active?, onClick }`, and the group renders in the order
68
+ `navigationToggle` surfaces the app-navigation button as a bare glyph; `editorControls` is the editor
69
+ pair — panels toggle + close, drawn as outlined squares. `close` is required; omit `panelsToggle` for a
70
+ close-only state with no panels to fold (e.g. a "not found" editor). Every entry takes `{ active?, onClick }`, and the group renders in the order
69
71
  navigation → panels → close. Set `scrollable` to let the whole page scroll as one document with
70
72
  sticky panel headers.
71
73
 
@@ -18,9 +18,9 @@ type Props = {
18
18
  * PageLayout — top-level window chrome. Renders a fixed-height header (title + actions + an optional
19
19
  * group of chrome buttons) above a flex-column body that typically contains a `PagePanels` row.
20
20
  *
21
- * `navigationToggle` surfaces the app-navigation button as a bare glyph; `editorControls` is the fixed
22
- * editor pair — panels toggle + close, both required so an editor never ships half the preset, drawn as
23
- * outlined squares. Every entry takes `{ active?, onClick }`, and the group renders in the order
21
+ * `navigationToggle` surfaces the app-navigation button as a bare glyph; `editorControls` is the editor
22
+ * pair — panels toggle + close, drawn as outlined squares. `close` is required; omit `panelsToggle` for a
23
+ * close-only state with no panels to fold (e.g. a "not found" editor). Every entry takes `{ active?, onClick }`, and the group renders in the order
24
24
  * navigation → panels → close. Set `scrollable` to let the whole page scroll as one document with
25
25
  * sticky panel headers.
26
26
  *
@@ -25,9 +25,9 @@ export type PageLayoutAction = {
25
25
  active?: boolean;
26
26
  onClick: () => void;
27
27
  };
28
- /** Editor chrome — a fixed pair, so an editor never ships half of it. */
28
+ /** Editor chrome — panels toggle + close. Omit `panelsToggle` for a close-only state with no panels to fold (e.g. a "not found" editor). */
29
29
  export type EditorControls = {
30
- panelsToggle: PageLayoutAction;
30
+ panelsToggle?: PageLayoutAction;
31
31
  close: PageLayoutAction;
32
32
  };
33
33
  /**
@@ -1,5 +1,6 @@
1
1
  @use 'styles/mixins';
2
2
  @use 'styles/functions';
3
+ @use 'styles/transitions';
3
4
 
4
5
  @mixin base {
5
6
  .alternative-view {
@@ -16,11 +17,16 @@
16
17
  .table-container {
17
18
  --_table--row--height: var(--sc-kit--table--row--height, var(--_table--size-row-height));
18
19
  --_table--cell--font-size: var(--sc-kit--table--cell--font-size, var(--_table--size-cell-font-size));
19
- --_table--cell--horizontal-padding: var(--sc-kit--table--cell--horizontal-padding, var(--_table--size-cell-padding));
20
+ --_table--cell--padding-inline: var(--sc-kit--table--cell--padding-inline, var(--_table--size-cell-padding));
21
+ --_table--cell--padding-block: var(--sc-kit--table--cell--padding-block, 0);
22
+ --_table--cell--vertical-align: var(--sc-kit--table--cell--vertical-align, middle);
20
23
  --_table--head--height: var(--sc-kit--table--head--height, var(--_table--size-head-height, var(--_table--row--height)));
21
24
 
22
25
  --_table--head--background-color: var(--sc-kit--table--head--background-color, var(--sc-kit--color--bg--field-alt));
23
26
  --_table--head--color: var(--sc-kit--table--head--color, var(--sc-kit--color--text--secondary));
27
+ --_table--head--font-weight: var(--sc-kit--table--head--font-weight, var(--sc-kit--font-weight--semibold));
28
+ --_table--head--letter-spacing: var(--sc-kit--table--head--letter-spacing, normal);
29
+ --_table--head--text-transform: var(--sc-kit--table--head--text-transform, none);
24
30
  --_table--border-color: var(--sc-kit--table--border-color, var(--sc-kit--color--border));
25
31
  --_table--row--color: var(--sc-kit--table--row--color, transparent);
26
32
  --_table--row--alternative-color: var(--sc-kit--table--row--alternative-color, var(--sc-kit--color--bg--hover));
@@ -69,25 +75,31 @@
69
75
  border-collapse: separate;
70
76
  border-spacing: 0;
71
77
 
78
+ &--fixed {
79
+ table-layout: fixed;
80
+ }
81
+
72
82
  &__head {
73
83
  height: var(--_table--head--height);
74
84
  max-height: var(--_table--head--height);
75
85
  background: var(--_table--head--background-color);
76
86
  color: var(--_table--head--color);
77
87
  font-size: var(--sc-kit--table--head--font-size, var(--sc-kit--font-size--sm));
78
- font-weight: var(--sc-kit--font-weight--semibold);
88
+ letter-spacing: var(--_table--head--letter-spacing);
89
+ text-transform: var(--_table--head--text-transform);
79
90
  }
80
91
 
81
92
  &__th {
82
- padding: 0 var(--_table--cell--horizontal-padding);
93
+ padding: 0 var(--_table--cell--padding-inline);
94
+ // On th, not the head: the UA stylesheet's `th { font-weight: bold }` beats an inherited weight.
95
+ font-weight: var(--_table--head--font-weight);
83
96
  text-align: left;
84
97
  vertical-align: middle;
85
98
  border-bottom: 1px solid var(--_table--border-color);
86
99
  @include mixins.no-select;
87
100
 
88
101
  &--centered {
89
- text-align: center;
90
- --_table-header--justify-content: center;
102
+ --_--table-header--justify-content: center;
91
103
  }
92
104
  }
93
105
 
@@ -103,7 +115,15 @@
103
115
  &:hover,
104
116
  &--highlighted {
105
117
  background: var(--_table--row--alternative-color);
106
- --_table--move-row-cell-opacity: 1;
118
+ }
119
+
120
+ &:hover,
121
+ &:focus-within {
122
+ --_table--row-reveal--opacity: 1;
123
+ }
124
+
125
+ @media (any-hover: none) {
126
+ --_table--row-reveal--opacity: 1;
107
127
  }
108
128
 
109
129
  &--selected {
@@ -120,11 +140,11 @@
120
140
  }
121
141
 
122
142
  &__td {
123
- padding: 0 var(--_table--cell--horizontal-padding);
143
+ padding: 0;
124
144
  border-bottom: 1px solid var(--_table--border-color);
125
145
  font-size: var(--_table--cell--font-size);
126
146
  color: var(--sc-kit--color--text--primary);
127
- vertical-align: middle;
147
+ vertical-align: var(--_table--cell--vertical-align);
128
148
 
129
149
  &--centered {
130
150
  text-align: center;
@@ -139,6 +159,15 @@
139
159
  }
140
160
  }
141
161
 
162
+ &__cell {
163
+ padding: var(--_table--cell--padding-block) var(--_table--cell--padding-inline);
164
+
165
+ &--reveal-on-row-hover {
166
+ opacity: var(--_table--row-reveal--opacity, 0);
167
+ @include transitions.transition(opacity);
168
+ }
169
+ }
170
+
142
171
  &__placeholder {
143
172
  display: flex;
144
173
  align-items: center;
@@ -3,7 +3,7 @@ import { EmptyState } from '../empty-state';
3
3
  import { Icon } from '../icon';
4
4
  import { TableCell, TableHideableCell } from './table-cells';
5
5
  import { TableLocalization } from './table-localization';
6
- import { columnWidthStyle, createTableView } from './table-view.svelte';
6
+ import { columnCellStyle, createTableView } from './table-view.svelte';
7
7
  import IconReorderDotsHorizontal from '@fluentui/svg-icons/icons/re_order_dots_horizontal_20_regular.svg?raw';
8
8
  import { flip } from 'svelte/animate';
9
9
  import { dndzone } from 'svelte-dnd-action';
@@ -44,10 +44,12 @@ const transformDraggedElement = (draggedRow) => {
44
44
  }
45
45
  // Clone is reparented to <body>, losing the .table-container --_table--* vars; cells collapse without this.
46
46
  const cellStyle = window.getComputedStyle(referenceColumns[0]);
47
+ // Read off the table, not a cell: the drag-handle cell zeroes its own padding and per-column cellPadding overrides live on their td.
48
+ const tableStyle = window.getComputedStyle(tableRef);
47
49
  draggedRow.style.setProperty('--_table--cell--font-size', cellStyle.fontSize);
48
- draggedRow.style.setProperty('--_table--cell--horizontal-padding', cellStyle.paddingInlineStart);
49
- // Without a value the var resolves to currentColor (dark) on the reparented clone — drop the row border while dragging.
50
- draggedRow.style.setProperty('--_table--border-color', 'transparent');
50
+ draggedRow.style.setProperty('--_table--cell--padding-inline', tableStyle.getPropertyValue('--_table--cell--padding-inline'));
51
+ draggedRow.style.setProperty('--_table--cell--padding-block', tableStyle.getPropertyValue('--_table--cell--padding-block'));
52
+ draggedRow.style.setProperty('--_table--cell--vertical-align', cellStyle.verticalAlign);
51
53
  };
52
54
  </script>
53
55
 
@@ -59,9 +61,7 @@ const transformDraggedElement = (draggedRow) => {
59
61
  {/if}
60
62
  {/snippet}
61
63
 
62
- <div
63
- class="table-container table-container--{model.styles?.size ?? 'md'} table-container--{model.styles?.variant ?? 'plain'}"
64
- class:table-container--zebra={model.styles?.zebra === true}>
64
+ <div class="table-container table-container--{model.styles?.size ?? 'md'} table-container--{model.styles?.variant ?? 'plain'}">
65
65
  {#if alternativeView && showAlternativeView}
66
66
  {#if view.showNoItemsPlaceholder}
67
67
  <div class="alternative-view__placeholder">{@render noItemsContent()}</div>
@@ -69,13 +69,17 @@ const transformDraggedElement = (draggedRow) => {
69
69
  {@render alternativeView()}
70
70
  {/if}
71
71
  {:else}
72
- <table class="table" class:table--sticky-header={model.styles?.stickyHeader === true} bind:this={tableRef}>
72
+ <table
73
+ class="table"
74
+ class:table--sticky-header={model.styles?.stickyHeader === true}
75
+ class:table--fixed={model.styles?.layout === 'fixed'}
76
+ bind:this={tableRef}>
73
77
  <thead class="table__head">
74
78
  <tr>
75
79
  <th class="table__th table__drag-handle">&nbsp;</th>
76
80
  {#each model.columns as column (column)}
77
81
  <TableHideableCell column={column}>
78
- <th class="table__th" class:table__th--centered={column.centered} style={columnWidthStyle(column)} title={column.title}>
82
+ <th class="table__th" class:table__th--centered={column.centered} style={columnCellStyle(column)} title={column.title}>
79
83
  <DynamicComponent model={column.headerView} />
80
84
  </th>
81
85
  </TableHideableCell>
@@ -117,7 +121,9 @@ const transformDraggedElement = (draggedRow) => {
117
121
  class:table__td--flush={flushBottom}
118
122
  onmousedown={startDrag}
119
123
  ontouchstart={startDrag}>
120
- <Icon src={IconReorderDotsHorizontal} />
124
+ <div class="table__cell">
125
+ <Icon src={IconReorderDotsHorizontal} />
126
+ </div>
121
127
  </td>
122
128
  {#each model.columns as column (column)}
123
129
  <TableHideableCell column={column}>
@@ -126,9 +132,11 @@ const transformDraggedElement = (draggedRow) => {
126
132
  class:table__td--centered={column.centered}
127
133
  class:table__td--clickable={!!column.onClick}
128
134
  class:table__td--flush={flushBottom}
129
- style={columnWidthStyle(column)}
135
+ style={columnCellStyle(column)}
130
136
  onclick={() => column.onClick && column.onClick(item)}>
131
- <TableCell model={model} item={item} column={column} itemIndex={itemIndex} on={{ itemsReordered: on?.itemsReordered }} />
137
+ <div class="table__cell" class:table__cell--reveal-on-row-hover={column.revealOnRowHover}>
138
+ <TableCell model={model} item={item} column={column} itemIndex={itemIndex} on={{ itemsReordered: on?.itemsReordered }} />
139
+ </div>
132
140
  </td>
133
141
  </TableHideableCell>
134
142
  {/each}
@@ -154,15 +162,23 @@ preserve the current selection. For non-reorderable tables use `Table` (it ships
154
162
  | `zebra` | `boolean` | Alternates row backgrounds with `--sc-kit--color--bg--field-alt` |
155
163
  | `stickyHeader` | `boolean` | Sticks `thead` to the scrolling ancestor |
156
164
  | `actionsPopoverTopDirection` | `boolean` | Forces the per-row actions popover above instead of below |
165
+ | `layout` | `'auto' \| 'fixed'` | `'fixed'` honours `cssWidth` exactly; percent columns share what fixed ones leave, `minCssWidth` is ignored (default `'auto'`) |
157
166
 
158
167
  ### CSS Custom Properties
159
168
  | Property | Description | Default |
160
169
  |---|---|---|
161
- | `--sc-kit--table--row--height` | Row height | per size (32 / 50 px) |
162
- | `--sc-kit--table--cell--horizontal-padding` | Cell horizontal padding | per size (8 / 12 px) |
170
+ | `--sc-kit--table--row--height` | Row height | per size (36 / 44 px) |
171
+ | `--sc-kit--table--cell--padding-inline` | Cell inline padding | per size (`--space--3` / `--space--4`) |
163
172
  | `--sc-kit--table--cell--font-size` | Cell font size | per size (`--font-size--sm` / `--md`) |
173
+ | `--sc-kit--table--cell--padding-block` | Cell block padding | `0` |
174
+ | `--sc-kit--table--cell--vertical-align` | Cell content alignment — `top` keeps inputs aligned when one cell grows | `middle` |
175
+ | `--sc-kit--table--head--height` | Header row height | per size (32 / 40 px) |
164
176
  | `--sc-kit--table--head--background-color` | Header background | `--sc-kit--color--bg--field-alt` |
165
177
  | `--sc-kit--table--head--color` | Header text color | `--sc-kit--color--text--secondary` |
178
+ | `--sc-kit--table--head--font-size` | Header font size | `--sc-kit--font-size--sm` |
179
+ | `--sc-kit--table--head--font-weight` | Header font weight | `--sc-kit--font-weight--semibold` |
180
+ | `--sc-kit--table--head--letter-spacing` | Header letter spacing | `normal` |
181
+ | `--sc-kit--table--head--text-transform` | Header text transform | `none` |
166
182
  | `--sc-kit--table--border-color` | Border color between rows | `--sc-kit--color--border` |
167
183
  | `--sc-kit--table--row--color` | Row background | `transparent` |
168
184
  | `--sc-kit--table--row--alternative-color` | Hover / highlighted row background | `--sc-kit--color--bg--hover` |
@@ -185,10 +201,15 @@ preserve the current selection. For non-reorderable tables use `Table` (it ships
185
201
  .table-container {
186
202
  --_table--row--height: var(--sc-kit--table--row--height, var(--_table--size-row-height));
187
203
  --_table--cell--font-size: var(--sc-kit--table--cell--font-size, var(--_table--size-cell-font-size));
188
- --_table--cell--horizontal-padding: var(--sc-kit--table--cell--horizontal-padding, var(--_table--size-cell-padding));
204
+ --_table--cell--padding-inline: var(--sc-kit--table--cell--padding-inline, var(--_table--size-cell-padding));
205
+ --_table--cell--padding-block: var(--sc-kit--table--cell--padding-block, 0);
206
+ --_table--cell--vertical-align: var(--sc-kit--table--cell--vertical-align, middle);
189
207
  --_table--head--height: var(--sc-kit--table--head--height, var(--_table--size-head-height, var(--_table--row--height)));
190
208
  --_table--head--background-color: var(--sc-kit--table--head--background-color, var(--sc-kit--color--bg--field-alt));
191
209
  --_table--head--color: var(--sc-kit--table--head--color, var(--sc-kit--color--text--secondary));
210
+ --_table--head--font-weight: var(--sc-kit--table--head--font-weight, var(--sc-kit--font-weight--semibold));
211
+ --_table--head--letter-spacing: var(--sc-kit--table--head--letter-spacing, normal);
212
+ --_table--head--text-transform: var(--sc-kit--table--head--text-transform, none);
192
213
  --_table--border-color: var(--sc-kit--table--border-color, var(--sc-kit--color--border));
193
214
  --_table--row--color: var(--sc-kit--table--row--color, transparent);
194
215
  --_table--row--alternative-color: var(--sc-kit--table--row--alternative-color, var(--sc-kit--color--bg--hover));
@@ -249,16 +270,21 @@ preserve the current selection. For non-reorderable tables use `Table` (it ships
249
270
  border-collapse: separate;
250
271
  border-spacing: 0;
251
272
  }
273
+ .table--fixed {
274
+ table-layout: fixed;
275
+ }
252
276
  .table__head {
253
277
  height: var(--_table--head--height);
254
278
  max-height: var(--_table--head--height);
255
279
  background: var(--_table--head--background-color);
256
280
  color: var(--_table--head--color);
257
281
  font-size: var(--sc-kit--table--head--font-size, var(--sc-kit--font-size--sm));
258
- font-weight: var(--sc-kit--font-weight--semibold);
282
+ letter-spacing: var(--_table--head--letter-spacing);
283
+ text-transform: var(--_table--head--text-transform);
259
284
  }
260
285
  .table__th {
261
- padding: 0 var(--_table--cell--horizontal-padding);
286
+ padding: 0 var(--_table--cell--padding-inline);
287
+ font-weight: var(--_table--head--font-weight);
262
288
  text-align: left;
263
289
  vertical-align: middle;
264
290
  border-bottom: 1px solid var(--_table--border-color);
@@ -269,8 +295,7 @@ preserve the current selection. For non-reorderable tables use `Table` (it ships
269
295
  user-select: text;
270
296
  }
271
297
  .table__th--centered {
272
- text-align: center;
273
- --_table-header--justify-content: center;
298
+ --_--table-header--justify-content: center;
274
299
  }
275
300
  .table__tr {
276
301
  height: var(--_table--row--height);
@@ -282,7 +307,14 @@ preserve the current selection. For non-reorderable tables use `Table` (it ships
282
307
  }
283
308
  .table__tr:hover, .table__tr--highlighted {
284
309
  background: var(--_table--row--alternative-color);
285
- --_table--move-row-cell-opacity: 1;
310
+ }
311
+ .table__tr:hover, .table__tr:focus-within {
312
+ --_table--row-reveal--opacity: 1;
313
+ }
314
+ @media (any-hover: none) {
315
+ .table__tr {
316
+ --_table--row-reveal--opacity: 1;
317
+ }
286
318
  }
287
319
  .table__tr--selected {
288
320
  background: var(--_table--selected--color);
@@ -294,11 +326,11 @@ preserve the current selection. For non-reorderable tables use `Table` (it ships
294
326
  background: var(--_table--row--color) !important;
295
327
  }
296
328
  .table__td {
297
- padding: 0 var(--_table--cell--horizontal-padding);
329
+ padding: 0;
298
330
  border-bottom: 1px solid var(--_table--border-color);
299
331
  font-size: var(--_table--cell--font-size);
300
332
  color: var(--sc-kit--color--text--primary);
301
- vertical-align: middle;
333
+ vertical-align: var(--_table--cell--vertical-align);
302
334
  }
303
335
  .table__td--centered {
304
336
  text-align: center;
@@ -309,6 +341,13 @@ preserve the current selection. For non-reorderable tables use `Table` (it ships
309
341
  .table__td--flush {
310
342
  border-bottom: none;
311
343
  }
344
+ .table__cell {
345
+ padding: var(--_table--cell--padding-block) var(--_table--cell--padding-inline);
346
+ }
347
+ .table__cell--reveal-on-row-hover {
348
+ opacity: var(--_table--row-reveal--opacity, 0);
349
+ transition: opacity var(--sc-kit--duration--base) var(--sc-kit--ease--default);
350
+ }
312
351
  .table__placeholder {
313
352
  display: flex;
314
353
  align-items: center;
@@ -323,7 +362,8 @@ preserve the current selection. For non-reorderable tables use `Table` (it ships
323
362
  }
324
363
 
325
364
  .table__drag-handle {
326
- width: 2.25rem;
365
+ --_table--cell--padding-inline: 0;
366
+ width: 3.25rem;
327
367
  cursor: grab;
328
368
  text-align: center;
329
369
  --sc-kit--icon--size: 1.25rem;
@@ -58,15 +58,23 @@ interface $$IsomorphicComponent {
58
58
  * | `zebra` | `boolean` | Alternates row backgrounds with `--sc-kit--color--bg--field-alt` |
59
59
  * | `stickyHeader` | `boolean` | Sticks `thead` to the scrolling ancestor |
60
60
  * | `actionsPopoverTopDirection` | `boolean` | Forces the per-row actions popover above instead of below |
61
+ * | `layout` | `'auto' \| 'fixed'` | `'fixed'` honours `cssWidth` exactly; percent columns share what fixed ones leave, `minCssWidth` is ignored (default `'auto'`) |
61
62
  *
62
63
  * ### CSS Custom Properties
63
64
  * | Property | Description | Default |
64
65
  * |---|---|---|
65
- * | `--sc-kit--table--row--height` | Row height | per size (32 / 50 px) |
66
- * | `--sc-kit--table--cell--horizontal-padding` | Cell horizontal padding | per size (8 / 12 px) |
66
+ * | `--sc-kit--table--row--height` | Row height | per size (36 / 44 px) |
67
+ * | `--sc-kit--table--cell--padding-inline` | Cell inline padding | per size (`--space--3` / `--space--4`) |
67
68
  * | `--sc-kit--table--cell--font-size` | Cell font size | per size (`--font-size--sm` / `--md`) |
69
+ * | `--sc-kit--table--cell--padding-block` | Cell block padding | `0` |
70
+ * | `--sc-kit--table--cell--vertical-align` | Cell content alignment — `top` keeps inputs aligned when one cell grows | `middle` |
71
+ * | `--sc-kit--table--head--height` | Header row height | per size (32 / 40 px) |
68
72
  * | `--sc-kit--table--head--background-color` | Header background | `--sc-kit--color--bg--field-alt` |
69
73
  * | `--sc-kit--table--head--color` | Header text color | `--sc-kit--color--text--secondary` |
74
+ * | `--sc-kit--table--head--font-size` | Header font size | `--sc-kit--font-size--sm` |
75
+ * | `--sc-kit--table--head--font-weight` | Header font weight | `--sc-kit--font-weight--semibold` |
76
+ * | `--sc-kit--table--head--letter-spacing` | Header letter spacing | `normal` |
77
+ * | `--sc-kit--table--head--text-transform` | Header text transform | `none` |
70
78
  * | `--sc-kit--table--border-color` | Border color between rows | `--sc-kit--color--border` |
71
79
  * | `--sc-kit--table--row--color` | Row background | `transparent` |
72
80
  * | `--sc-kit--table--row--alternative-color` | Hover / highlighted row background | `--sc-kit--color--bg--hover` |
@@ -2,7 +2,7 @@
2
2
  import { EmptyState } from '../empty-state';
3
3
  import { TableCell, TableHideableCell } from './table-cells';
4
4
  import { TableLocalization } from './table-localization';
5
- import { columnWidthStyle, createTableView } from './table-view.svelte';
5
+ import { columnCellStyle, createTableView } from './table-view.svelte';
6
6
  let { model, showAlternativeView = false, noItemsPlaceholderEnabled, noItemsPlaceholderDelay, grouping, on, alternativeView, customNoItemsPlaceholder } = $props();
7
7
  const localization = new TableLocalization();
8
8
  const view = createTableView(() => model, () => on, () => noItemsPlaceholderEnabled, () => noItemsPlaceholderDelay);
@@ -45,9 +45,7 @@ const visibleColspan = $derived(model.columns.filter((column) => !column.hidden)
45
45
  {/if}
46
46
  {/snippet}
47
47
 
48
- <div
49
- class="table-container table-container--{model.styles?.size ?? 'md'} table-container--{model.styles?.variant ?? 'plain'}"
50
- class:table-container--zebra={model.styles?.zebra === true}>
48
+ <div class="table-container table-container--{model.styles?.size ?? 'md'} table-container--{model.styles?.variant ?? 'plain'}">
51
49
  {#if alternativeView && showAlternativeView}
52
50
  {#if view.showNoItemsPlaceholder}
53
51
  <div class="alternative-view__placeholder">{@render noItemsContent()}</div>
@@ -55,12 +53,12 @@ const visibleColspan = $derived(model.columns.filter((column) => !column.hidden)
55
53
  {@render alternativeView()}
56
54
  {/if}
57
55
  {:else}
58
- <table class="table" class:table--sticky-header={model.styles?.stickyHeader === true}>
56
+ <table class="table" class:table--sticky-header={model.styles?.stickyHeader === true} class:table--fixed={model.styles?.layout === 'fixed'}>
59
57
  <thead class="table__head">
60
58
  <tr>
61
59
  {#each model.columns as column (column)}
62
60
  <TableHideableCell column={column}>
63
- <th class="table__th" class:table__th--centered={column.centered} style={columnWidthStyle(column)} title={column.title}>
61
+ <th class="table__th" class:table__th--centered={column.centered} style={columnCellStyle(column)} title={column.title}>
64
62
  <DynamicComponent model={column.headerView} />
65
63
  </th>
66
64
  </TableHideableCell>
@@ -106,9 +104,11 @@ const visibleColspan = $derived(model.columns.filter((column) => !column.hidden)
106
104
  class:table__td--centered={column.centered}
107
105
  class:table__td--clickable={!!column.onClick}
108
106
  class:table__td--flush={flushBottom}
109
- style={columnWidthStyle(column)}
107
+ style={columnCellStyle(column)}
110
108
  onclick={() => column.onClick && column.onClick(item)}>
111
- <TableCell model={model} item={item} column={column} itemIndex={itemIndex} on={{ itemsReordered: on?.itemsReordered }} />
109
+ <div class="table__cell" class:table__cell--reveal-on-row-hover={column.revealOnRowHover}>
110
+ <TableCell model={model} item={item} column={column} itemIndex={itemIndex} on={{ itemsReordered: on?.itemsReordered }} />
111
+ </div>
112
112
  </td>
113
113
  </TableHideableCell>
114
114
  {/each}
@@ -135,15 +135,23 @@ variant ships no dnd code).
135
135
  | `zebra` | `boolean` | Alternates row backgrounds with `--sc-kit--color--bg--field-alt` |
136
136
  | `stickyHeader` | `boolean` | Sticks `thead` to the scrolling ancestor |
137
137
  | `actionsPopoverTopDirection` | `boolean` | Forces the per-row actions popover above instead of below |
138
+ | `layout` | `'auto' \| 'fixed'` | `'fixed'` honours `cssWidth` exactly; percent columns share what fixed ones leave, `minCssWidth` is ignored (default `'auto'`) |
138
139
 
139
140
  ### CSS Custom Properties
140
141
  | Property | Description | Default |
141
142
  |---|---|---|
142
- | `--sc-kit--table--row--height` | Row height | per size (32 / 50 px) |
143
- | `--sc-kit--table--cell--horizontal-padding` | Cell horizontal padding | per size (8 / 12 px) |
143
+ | `--sc-kit--table--row--height` | Row height | per size (36 / 44 px) |
144
+ | `--sc-kit--table--cell--padding-inline` | Cell inline padding | per size (`--space--3` / `--space--4`) |
144
145
  | `--sc-kit--table--cell--font-size` | Cell font size | per size (`--font-size--sm` / `--md`) |
146
+ | `--sc-kit--table--cell--padding-block` | Cell block padding | `0` |
147
+ | `--sc-kit--table--cell--vertical-align` | Cell content alignment — `top` keeps inputs aligned when one cell grows | `middle` |
148
+ | `--sc-kit--table--head--height` | Header row height | per size (32 / 40 px) |
145
149
  | `--sc-kit--table--head--background-color` | Header background | `--sc-kit--color--bg--field-alt` |
146
150
  | `--sc-kit--table--head--color` | Header text color | `--sc-kit--color--text--secondary` |
151
+ | `--sc-kit--table--head--font-size` | Header font size | `--sc-kit--font-size--sm` |
152
+ | `--sc-kit--table--head--font-weight` | Header font weight | `--sc-kit--font-weight--semibold` |
153
+ | `--sc-kit--table--head--letter-spacing` | Header letter spacing | `normal` |
154
+ | `--sc-kit--table--head--text-transform` | Header text transform | `none` |
147
155
  | `--sc-kit--table--border-color` | Border color between rows | `--sc-kit--color--border` |
148
156
  | `--sc-kit--table--row--color` | Row background | `transparent` |
149
157
  | `--sc-kit--table--row--alternative-color` | Hover / highlighted row background | `--sc-kit--color--bg--hover` |
@@ -166,10 +174,15 @@ variant ships no dnd code).
166
174
  .table-container {
167
175
  --_table--row--height: var(--sc-kit--table--row--height, var(--_table--size-row-height));
168
176
  --_table--cell--font-size: var(--sc-kit--table--cell--font-size, var(--_table--size-cell-font-size));
169
- --_table--cell--horizontal-padding: var(--sc-kit--table--cell--horizontal-padding, var(--_table--size-cell-padding));
177
+ --_table--cell--padding-inline: var(--sc-kit--table--cell--padding-inline, var(--_table--size-cell-padding));
178
+ --_table--cell--padding-block: var(--sc-kit--table--cell--padding-block, 0);
179
+ --_table--cell--vertical-align: var(--sc-kit--table--cell--vertical-align, middle);
170
180
  --_table--head--height: var(--sc-kit--table--head--height, var(--_table--size-head-height, var(--_table--row--height)));
171
181
  --_table--head--background-color: var(--sc-kit--table--head--background-color, var(--sc-kit--color--bg--field-alt));
172
182
  --_table--head--color: var(--sc-kit--table--head--color, var(--sc-kit--color--text--secondary));
183
+ --_table--head--font-weight: var(--sc-kit--table--head--font-weight, var(--sc-kit--font-weight--semibold));
184
+ --_table--head--letter-spacing: var(--sc-kit--table--head--letter-spacing, normal);
185
+ --_table--head--text-transform: var(--sc-kit--table--head--text-transform, none);
173
186
  --_table--border-color: var(--sc-kit--table--border-color, var(--sc-kit--color--border));
174
187
  --_table--row--color: var(--sc-kit--table--row--color, transparent);
175
188
  --_table--row--alternative-color: var(--sc-kit--table--row--alternative-color, var(--sc-kit--color--bg--hover));
@@ -230,16 +243,21 @@ variant ships no dnd code).
230
243
  border-collapse: separate;
231
244
  border-spacing: 0;
232
245
  }
246
+ .table--fixed {
247
+ table-layout: fixed;
248
+ }
233
249
  .table__head {
234
250
  height: var(--_table--head--height);
235
251
  max-height: var(--_table--head--height);
236
252
  background: var(--_table--head--background-color);
237
253
  color: var(--_table--head--color);
238
254
  font-size: var(--sc-kit--table--head--font-size, var(--sc-kit--font-size--sm));
239
- font-weight: var(--sc-kit--font-weight--semibold);
255
+ letter-spacing: var(--_table--head--letter-spacing);
256
+ text-transform: var(--_table--head--text-transform);
240
257
  }
241
258
  .table__th {
242
- padding: 0 var(--_table--cell--horizontal-padding);
259
+ padding: 0 var(--_table--cell--padding-inline);
260
+ font-weight: var(--_table--head--font-weight);
243
261
  text-align: left;
244
262
  vertical-align: middle;
245
263
  border-bottom: 1px solid var(--_table--border-color);
@@ -250,8 +268,7 @@ variant ships no dnd code).
250
268
  user-select: text;
251
269
  }
252
270
  .table__th--centered {
253
- text-align: center;
254
- --_table-header--justify-content: center;
271
+ --_--table-header--justify-content: center;
255
272
  }
256
273
  .table__tr {
257
274
  height: var(--_table--row--height);
@@ -263,7 +280,14 @@ variant ships no dnd code).
263
280
  }
264
281
  .table__tr:hover, .table__tr--highlighted {
265
282
  background: var(--_table--row--alternative-color);
266
- --_table--move-row-cell-opacity: 1;
283
+ }
284
+ .table__tr:hover, .table__tr:focus-within {
285
+ --_table--row-reveal--opacity: 1;
286
+ }
287
+ @media (any-hover: none) {
288
+ .table__tr {
289
+ --_table--row-reveal--opacity: 1;
290
+ }
267
291
  }
268
292
  .table__tr--selected {
269
293
  background: var(--_table--selected--color);
@@ -275,11 +299,11 @@ variant ships no dnd code).
275
299
  background: var(--_table--row--color) !important;
276
300
  }
277
301
  .table__td {
278
- padding: 0 var(--_table--cell--horizontal-padding);
302
+ padding: 0;
279
303
  border-bottom: 1px solid var(--_table--border-color);
280
304
  font-size: var(--_table--cell--font-size);
281
305
  color: var(--sc-kit--color--text--primary);
282
- vertical-align: middle;
306
+ vertical-align: var(--_table--cell--vertical-align);
283
307
  }
284
308
  .table__td--centered {
285
309
  text-align: center;
@@ -290,6 +314,13 @@ variant ships no dnd code).
290
314
  .table__td--flush {
291
315
  border-bottom: none;
292
316
  }
317
+ .table__cell {
318
+ padding: var(--_table--cell--padding-block) var(--_table--cell--padding-inline);
319
+ }
320
+ .table__cell--reveal-on-row-hover {
321
+ opacity: var(--_table--row-reveal--opacity, 0);
322
+ transition: opacity var(--sc-kit--duration--base) var(--sc-kit--ease--default);
323
+ }
293
324
  .table__placeholder {
294
325
  display: flex;
295
326
  align-items: center;
@@ -305,7 +336,7 @@ variant ships no dnd code).
305
336
 
306
337
  .table__group-header {
307
338
  padding-block: var(--sc-kit--space--6) var(--sc-kit--space--2);
308
- padding-inline: var(--_table--cell--horizontal-padding);
339
+ padding-inline: var(--_table--cell--padding-inline);
309
340
  font-size: var(--sc-kit--font-size--sm);
310
341
  font-weight: var(--sc-kit--font-weight--semibold);
311
342
  color: var(--sc-kit--color--text--secondary);
@@ -70,15 +70,23 @@ interface $$IsomorphicComponent {
70
70
  * | `zebra` | `boolean` | Alternates row backgrounds with `--sc-kit--color--bg--field-alt` |
71
71
  * | `stickyHeader` | `boolean` | Sticks `thead` to the scrolling ancestor |
72
72
  * | `actionsPopoverTopDirection` | `boolean` | Forces the per-row actions popover above instead of below |
73
+ * | `layout` | `'auto' \| 'fixed'` | `'fixed'` honours `cssWidth` exactly; percent columns share what fixed ones leave, `minCssWidth` is ignored (default `'auto'`) |
73
74
  *
74
75
  * ### CSS Custom Properties
75
76
  * | Property | Description | Default |
76
77
  * |---|---|---|
77
- * | `--sc-kit--table--row--height` | Row height | per size (32 / 50 px) |
78
- * | `--sc-kit--table--cell--horizontal-padding` | Cell horizontal padding | per size (8 / 12 px) |
78
+ * | `--sc-kit--table--row--height` | Row height | per size (36 / 44 px) |
79
+ * | `--sc-kit--table--cell--padding-inline` | Cell inline padding | per size (`--space--3` / `--space--4`) |
79
80
  * | `--sc-kit--table--cell--font-size` | Cell font size | per size (`--font-size--sm` / `--md`) |
81
+ * | `--sc-kit--table--cell--padding-block` | Cell block padding | `0` |
82
+ * | `--sc-kit--table--cell--vertical-align` | Cell content alignment — `top` keeps inputs aligned when one cell grows | `middle` |
83
+ * | `--sc-kit--table--head--height` | Header row height | per size (32 / 40 px) |
80
84
  * | `--sc-kit--table--head--background-color` | Header background | `--sc-kit--color--bg--field-alt` |
81
85
  * | `--sc-kit--table--head--color` | Header text color | `--sc-kit--color--text--secondary` |
86
+ * | `--sc-kit--table--head--font-size` | Header font size | `--sc-kit--font-size--sm` |
87
+ * | `--sc-kit--table--head--font-weight` | Header font weight | `--sc-kit--font-weight--semibold` |
88
+ * | `--sc-kit--table--head--letter-spacing` | Header letter spacing | `normal` |
89
+ * | `--sc-kit--table--head--text-transform` | Header text transform | `none` |
82
90
  * | `--sc-kit--table--border-color` | Border color between rows | `--sc-kit--color--border` |
83
91
  * | `--sc-kit--table--row--color` | Row background | `transparent` |
84
92
  * | `--sc-kit--table--row--alternative-color` | Hover / highlighted row background | `--sc-kit--color--bg--hover` |
@@ -20,5 +20,4 @@ const toggle = () => {
20
20
  display: flex;
21
21
  align-items: center;
22
22
  justify-content: center;
23
- font-size: var(--_table--cell--font-size);
24
23
  }</style>
@@ -21,8 +21,6 @@ const onDown = () => on?.down?.();
21
21
  gap: 0.125rem;
22
22
  justify-content: space-evenly;
23
23
  --sc-kit--icon--size: 1rem;
24
- opacity: var(--_table--move-row-cell-opacity, 0);
25
- transition: opacity var(--sc-kit--duration--base) var(--sc-kit--ease--default);
26
24
  }
27
25
  .move-row-cell button:disabled {
28
26
  --sc-kit--icon--color: var(--sc-kit--color--text--muted);
@@ -44,7 +44,6 @@ Text cell for table rows. Supports plain text, date, date-time, and HTML formats
44
44
  --_table-text-cell--clickable--color: var(--sc-kit--table-text-cell--clickable--color, var(--sc-kit--color--accent));
45
45
  display: flex;
46
46
  align-content: center;
47
- font-size: var(--_table--cell--font-size);
48
47
  }
49
48
  .table-text-cell__single-line-no-trim {
50
49
  white-space: nowrap;
@@ -24,6 +24,11 @@ export declare class ResolvedColumn<T extends WithId> {
24
24
  get id(): keyof T;
25
25
  get title(): string;
26
26
  get centered(): boolean;
27
+ get revealOnRowHover(): boolean;
28
+ get cellPadding(): {
29
+ inline?: string;
30
+ block?: string;
31
+ };
27
32
  get cssWidth(): string;
28
33
  get minCssWidth(): string;
29
34
  get hideable(): boolean;
@@ -31,6 +31,12 @@ export class ResolvedColumn {
31
31
  get centered() {
32
32
  return this.def.centered ?? false;
33
33
  }
34
+ get revealOnRowHover() {
35
+ return this.def.revealOnRowHover ?? this.def.type === 'move-row';
36
+ }
37
+ get cellPadding() {
38
+ return this.def.cellPadding ?? {};
39
+ }
34
40
  get cssWidth() {
35
41
  return this.def.cssWidth ?? '';
36
42
  }
@@ -18,7 +18,7 @@ const changeSort = (direction) => {
18
18
  </script>
19
19
 
20
20
  <TableHeader column={column}>
21
- <div class="th-sortable" class:th-sortable--centered={column.centered}>
21
+ <div class="th-sortable">
22
22
  {@render children?.()}
23
23
  <span>&nbsp;</span>
24
24
  <Popover position="bottom-start">
@@ -72,9 +72,6 @@ const changeSort = (direction) => {
72
72
  align-items: center;
73
73
  justify-content: flex-start;
74
74
  }
75
- .th-sortable--centered {
76
- justify-content: center;
77
- }
78
75
  .th-sortable__title, .th-sortable__marker {
79
76
  color: inherit;
80
77
  display: inline-block;
@@ -13,6 +13,6 @@ export {};
13
13
  <style>.table-header {
14
14
  display: flex;
15
15
  align-items: center;
16
- justify-content: var(--_table-header--justify-content, flex-start);
16
+ justify-content: var(--_--table-header--justify-content, flex-start);
17
17
  white-space: nowrap;
18
18
  }</style>
@@ -6,8 +6,8 @@ type SelectionCallbacks<T> = {
6
6
  itemsSelected?: (e: T[]) => void;
7
7
  itemsDeselected?: (e: T[]) => void;
8
8
  };
9
- /** Inline width declarations for a column's `th` / `td`: the preferred width plus, when set, the floor the browser must honour. */
10
- export declare const columnWidthStyle: <T extends WithId>(column: ResolvedColumn<T>) => string;
9
+ /** Inline declarations for a column's `th` / `td`: the preferred width, the floor the browser must honour, and the per-column padding override. */
10
+ export declare const columnCellStyle: <T extends WithId>(column: ResolvedColumn<T>) => string;
11
11
  /** Shared presentation wiring for the table views: selection-event subscriptions + the no-items placeholder decision (on/off gate + anti-flicker delay). */
12
12
  export declare const createTableView: <T extends WithId>(model: () => TableModel<T>, on: () => SelectionCallbacks<T> | undefined, noItemsPlaceholderEnabled?: () => boolean | undefined, noItemsPlaceholderDelay?: () => number | undefined) => {
13
13
  readonly showNoItemsPlaceholder: boolean;
@@ -1,8 +1,15 @@
1
1
  import { untrack } from 'svelte';
2
- /** Inline width declarations for a column's `th` / `td`: the preferred width plus, when set, the floor the browser must honour. */
3
- export const columnWidthStyle = (column) => {
4
- const width = column.cssWidth ? `width: ${column.cssWidth};` : 'width: auto;';
5
- return column.minCssWidth ? `${width} min-width: ${column.minCssWidth};` : width;
2
+ /** Inline declarations for a column's `th` / `td`: the preferred width, the floor the browser must honour, and the per-column padding override. */
3
+ export const columnCellStyle = (column) => {
4
+ const { inline, block } = column.cellPadding;
5
+ return [
6
+ column.cssWidth ? `width: ${column.cssWidth};` : 'width: auto;',
7
+ column.minCssWidth ? `min-width: ${column.minCssWidth};` : '',
8
+ inline ? `--_table--cell--padding-inline: ${inline};` : '',
9
+ block ? `--_table--cell--padding-block: ${block};` : ''
10
+ ]
11
+ .filter(Boolean)
12
+ .join(' ');
6
13
  };
7
14
  /** Shared presentation wiring for the table views: selection-event subscriptions + the no-items placeholder decision (on/off gate + anti-flicker delay). */
8
15
  export const createTableView = (model, on, noItemsPlaceholderEnabled = () => undefined, noItemsPlaceholderDelay = () => undefined) => {
@@ -19,6 +19,16 @@ interface ITableColumnBase {
19
19
  /** Floor the column will not shrink below, taken out of columns whose `cssWidth` is merely a suggestion. Use it on the column that must keep space. */
20
20
  minCssWidth?: string;
21
21
  centered?: boolean;
22
+ /** Hide the cell until its row is hovered or holds focus. Always visible on touch-only devices. @default true for `move-row`, false otherwise */
23
+ revealOnRowHover?: boolean;
24
+ /**
25
+ * Overrides the table-wide cell padding for this column. `inline` applies to the header and body cells, `block` to body cells. Under the kit's
26
+ * border-box sizing `cssWidth` includes this padding.
27
+ */
28
+ cellPadding?: {
29
+ inline?: string;
30
+ block?: string;
31
+ };
22
32
  }
23
33
  export interface ITableColumn<T> extends ITableColumnBase {
24
34
  id: keyof T;
@@ -122,6 +132,13 @@ export type TableStyles = {
122
132
  zebra?: boolean;
123
133
  /** Visual chrome around the table. `'plain'` blends with surrounding layout; `'card'` adds a bordered, rounded panel. @default 'plain' */
124
134
  variant?: 'plain' | 'card';
135
+ /**
136
+ * Column width algorithm. `'auto'` treats `cssWidth` as a hint and lets content redistribute space. `'fixed'` honours `cssWidth` exactly and wraps or
137
+ * clips content instead of widening the column. Under `'fixed'`, percent columns summing to 100% share what the fixed columns leave in that ratio;
138
+ * `minCssWidth` does not apply — floor the whole table with a `min-width` wrapper inside a horizontally scrolling container instead.
139
+ * @default 'auto'
140
+ */
141
+ layout?: 'auto' | 'fixed';
125
142
  };
126
143
  export type TableOrderByState<TOrder> = {
127
144
  orderBy: TOrder;
@@ -3,7 +3,7 @@
3
3
 
4
4
  <script lang="ts" generics="T extends Record<string, unknown>, K extends keyof T & string">import { Utils } from '../../core/utils';
5
5
  import { default as FieldFrame } from './field-frame.svelte';
6
- let { handler, name, validateOn = ['input', 'change', 'blur'], debounceMs = 0, disableTouchedTracking = false, reserveErrorSpace = false, on, children } = $props();
6
+ let { handler, name, validateOn = ['input', 'change', 'blur'], debounceMs = 0, disableTouchedTracking = false, errorPlacement = 'overlay', on, children } = $props();
7
7
  // True between a value change and the next completed validation. Hides the stale error in the
8
8
  // UI without mutating handler.errors — the model keeps the last known result, the view just
9
9
  // doesn't trust it until the validator has caught up.
@@ -64,7 +64,7 @@ const field = $derived({
64
64
  });
65
65
  </script>
66
66
 
67
- <FieldFrame error={showErrors ? handler.errors[name] : null} reserveErrorSpace={reserveErrorSpace}>
67
+ <FieldFrame error={showErrors ? handler.errors[name] : null} errorPlacement={errorPlacement}>
68
68
  {@render children(field, pendingValidation)}
69
69
  </FieldFrame>
70
70
 
@@ -72,8 +72,7 @@ const field = $derived({
72
72
  @component
73
73
  Binds a form field to any kit input via a render-prop snippet. The base inputs stay unchanged;
74
74
  `Validatable` is the only place that knows about `FormValidationHandler`. Wraps children in an
75
- internal `FieldFrame` that reserves space for the error message so toggling doesn't shift
76
- layout; `reserveErrorSpace={false}` drops the reserve — the message overlays content below.
75
+ internal `FieldFrame` that renders the error message below the field, placed per `errorPlacement`.
77
76
 
78
77
  ### Usage
79
78
  ```svelte
@@ -10,6 +10,7 @@ export type FieldBinding<V> = {
10
10
  };
11
11
  };
12
12
  import type { FormValidationHandler } from '../../core/validation';
13
+ import type { ErrorPlacement } from './types';
13
14
  import type { Snippet } from 'svelte';
14
15
  declare function $$render<T extends Record<string, unknown>, K extends keyof T & string>(): {
15
16
  props: {
@@ -31,11 +32,11 @@ declare function $$render<T extends Record<string, unknown>, K extends keyof T &
31
32
  /** Show errors even when the field has not been touched. @default false */
32
33
  disableTouchedTracking?: boolean;
33
34
  /**
34
- * Reserve space below the field for the error message. Turn off when a sibling must align to
35
- * the field's real bottom edge — the message then overlays whatever sits below the field.
36
- * @default false
35
+ * Where the error message sits: `'overlay'` covers whatever is below the field, `'reserved'` keeps a
36
+ * permanent strip so toggling never shifts layout, `'inline'` grows the field only while an error is shown.
37
+ * @default 'overlay'
37
38
  */
38
- reserveErrorSpace?: boolean;
39
+ errorPlacement?: ErrorPlacement;
39
40
  /**
40
41
  * Extra consumer callbacks. Run AFTER the validation wiring; they cannot override it.
41
42
  * The `children` snippet always receives fully-wired `field.on.*`.
@@ -73,8 +74,7 @@ interface $$IsomorphicComponent {
73
74
  /**
74
75
  * Binds a form field to any kit input via a render-prop snippet. The base inputs stay unchanged;
75
76
  * `Validatable` is the only place that knows about `FormValidationHandler`. Wraps children in an
76
- * internal `FieldFrame` that reserves space for the error message so toggling doesn't shift
77
- * layout; `reserveErrorSpace={false}` drops the reserve — the message overlays content below.
77
+ * internal `FieldFrame` that renders the error message below the field, placed per `errorPlacement`.
78
78
  *
79
79
  * ### Usage
80
80
  * ```svelte
@@ -1,9 +1,9 @@
1
1
  <script lang="ts" generics="T extends Record<string, unknown>">import { default as FieldFrame } from './field-frame.svelte';
2
- let { name, validationResult, reserveErrorSpace = false, children } = $props();
2
+ let { name, validationResult, errorPlacement = 'overlay', children } = $props();
3
3
  const errorText = $derived(validationResult?.errors[name] ?? null);
4
4
  </script>
5
5
 
6
- <FieldFrame error={errorText} reserveErrorSpace={reserveErrorSpace}>
6
+ <FieldFrame error={errorText} errorPlacement={errorPlacement}>
7
7
  {@render children()}
8
8
  </FieldFrame>
9
9
 
@@ -11,6 +11,6 @@ const errorText = $derived(validationResult?.errors[name] ?? null);
11
11
  @component
12
12
  Wraps a form field and displays validation errors from a `FormValidateResult` object. Stateless
13
13
  sibling of `Validatable` for cases where the consumer already has the validation result in hand
14
- (e.g. server-validated forms) and does not need a `FormValidationHandler`. Pass
15
- `reserveErrorSpace={false}` to overlay the message instead of reserving layout space.
14
+ (e.g. server-validated forms) and does not need a `FormValidationHandler`. `errorPlacement` picks
15
+ where the message sits — same contract as `Validatable`.
16
16
  -->
@@ -1,4 +1,5 @@
1
1
  import type { FormValidateResult } from '../../core/validation';
2
+ import type { ErrorPlacement } from './types';
2
3
  import type { Snippet } from 'svelte';
3
4
  declare function $$render<T extends Record<string, unknown>>(): {
4
5
  props: {
@@ -7,11 +8,11 @@ declare function $$render<T extends Record<string, unknown>>(): {
7
8
  /** Validation result object containing error messages */
8
9
  validationResult?: FormValidateResult<T>;
9
10
  /**
10
- * Reserve space below the field for the error message. Turn off when a sibling must align to
11
- * the field's real bottom edge — the message then overlays whatever sits below the field.
12
- * @default false
11
+ * Where the error message sits: `'overlay'` covers whatever is below the field, `'reserved'` keeps a
12
+ * permanent strip so toggling never shifts layout, `'inline'` grows the field only while an error is shown.
13
+ * @default 'overlay'
13
14
  */
14
- reserveErrorSpace?: boolean;
15
+ errorPlacement?: ErrorPlacement;
15
16
  children: Snippet;
16
17
  };
17
18
  exports: {};
@@ -36,8 +37,8 @@ interface $$IsomorphicComponent {
36
37
  /**
37
38
  * Wraps a form field and displays validation errors from a `FormValidateResult` object. Stateless
38
39
  * sibling of `Validatable` for cases where the consumer already has the validation result in hand
39
- * (e.g. server-validated forms) and does not need a `FormValidationHandler`. Pass
40
- * `reserveErrorSpace={false}` to overlay the message instead of reserving layout space.
40
+ * (e.g. server-validated forms) and does not need a `FormValidationHandler`. `errorPlacement` picks
41
+ * where the message sits — same contract as `Validatable`.
41
42
  */
42
43
  declare const Cmp: $$IsomorphicComponent;
43
44
  type Cmp<T extends Record<string, unknown>> = InstanceType<typeof Cmp<T>>;
@@ -1,40 +1,50 @@
1
- <script lang="ts">let { error, reserveErrorSpace = false, children } = $props();
1
+ <script lang="ts">let { error, errorPlacement = 'overlay', children } = $props();
2
2
  export {};
3
3
  </script>
4
4
 
5
- <div class="field-frame" class:field-frame--errored={!!error} class:field-frame--no-reserve={!reserveErrorSpace}>
5
+ <div class="field-frame" class:field-frame--reserved={errorPlacement === 'reserved'}>
6
6
  {@render children()}
7
7
  {#if error}
8
- <small class="field-frame__message">{error}</small>
8
+ <small class="field-frame__message field-frame__message--{errorPlacement}">{error}</small>
9
9
  {/if}
10
10
  </div>
11
11
 
12
12
  <!--
13
13
  @component
14
- Internal helper — wraps a form field and renders its error message in an absolutely-positioned slot
15
- below, with reserved space so the layout doesn't shift when the error toggles. With
16
- `reserveErrorSpace={false}` the reserve is dropped — the message stays absolutely positioned and
17
- overlays whatever sits below the field. Used internally by `Validatable` / `ValidationError` (and
18
- any other field-shaped kit consumer that needs the same visual contract).
14
+ Internal helper — wraps a form field and renders its error message below it, placed per `errorPlacement`: overlaying
15
+ the content below, inside a permanently reserved strip, or in normal flow so the field grows only while the error is
16
+ shown. Used internally by `Validatable` / `ValidationError` (and any other field-shaped kit consumer that needs the
17
+ same visual contract).
19
18
 
20
19
  Not exported from `index.ts` — for kit-internal use only.
21
20
  -->
22
21
 
23
22
  <style>.field-frame {
24
23
  position: relative;
25
- margin-bottom: 1.1em;
26
24
  display: block;
27
25
  }
28
- .field-frame--no-reserve {
29
- margin-bottom: 0;
26
+ .field-frame--reserved {
27
+ padding-block-end: 1.1rem;
30
28
  }
31
29
  .field-frame__message {
32
- position: absolute;
33
- top: calc(100% + 0.1em);
34
- left: 0;
35
- right: 0;
36
30
  display: block;
37
31
  color: var(--sc-kit--color--danger);
38
32
  font-size: 0.6875rem;
33
+ line-height: 1.5;
39
34
  font-weight: 400;
35
+ }
36
+ .field-frame__message--overlay {
37
+ position: absolute;
38
+ top: calc(100% + 0.0688rem);
39
+ left: 0;
40
+ right: 0;
41
+ }
42
+ .field-frame__message--reserved {
43
+ position: absolute;
44
+ bottom: 0;
45
+ left: 0;
46
+ right: 0;
47
+ }
48
+ .field-frame__message--inline {
49
+ margin-top: 0.0688rem;
40
50
  }</style>
@@ -1,17 +1,17 @@
1
+ import type { ErrorPlacement } from './types';
1
2
  import type { Snippet } from 'svelte';
2
3
  type Props = {
3
- /** Error message to display below the field. Falsy hides the message and the `--errored` flag. */
4
+ /** Error message to display below the field. Falsy hides the message. */
4
5
  error?: string | null | undefined;
5
- /** Reserve space below the field for the error message. Off drops the reserve — the message overlays content below. @default false */
6
- reserveErrorSpace?: boolean;
6
+ /** Where the error message sits relative to the field. @default 'overlay' */
7
+ errorPlacement?: ErrorPlacement;
7
8
  children: Snippet;
8
9
  };
9
10
  /**
10
- * Internal helper — wraps a form field and renders its error message in an absolutely-positioned slot
11
- * below, with reserved space so the layout doesn't shift when the error toggles. With
12
- * `reserveErrorSpace={false}` the reserve is dropped — the message stays absolutely positioned and
13
- * overlays whatever sits below the field. Used internally by `Validatable` / `ValidationError` (and
14
- * any other field-shaped kit consumer that needs the same visual contract).
11
+ * Internal helper — wraps a form field and renders its error message below it, placed per `errorPlacement`: overlaying
12
+ * the content below, inside a permanently reserved strip, or in normal flow so the field grows only while the error is
13
+ * shown. Used internally by `Validatable` / `ValidationError` (and any other field-shaped kit consumer that needs the
14
+ * same visual contract).
15
15
  *
16
16
  * Not exported from `index.ts` — for kit-internal use only.
17
17
  */
@@ -1,3 +1,4 @@
1
1
  export { default as Validatable } from './cmp.validatable.svelte';
2
2
  export type { FieldBinding, ValidateOnEvent } from './cmp.validatable.svelte';
3
3
  export { default as ValidationError } from './cmp.validation-error.svelte';
4
+ export type { ErrorPlacement } from './types';
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Where a field's validation message sits.
3
+ * - `'overlay'` — absolutely positioned below the field; no space reserved, the message covers whatever sits below.
4
+ * - `'reserved'` — absolutely positioned inside a permanently reserved strip, so toggling the error never shifts layout.
5
+ * - `'inline'` — in normal flow, only while an error is shown: the field grows on error and shrinks back when it clears.
6
+ */
7
+ export type ErrorPlacement = 'overlay' | 'reserved' | 'inline';
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamscloud/kit",
3
- "version": "0.76.4",
3
+ "version": "0.77.0",
4
4
  "author": "StreamsCloud",
5
5
  "repository": {
6
6
  "type": "git",