@svelte-lean/table 0.2.0 → 0.3.2

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @svelte-lean/table
2
2
 
3
+ ## 0.3.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Grid layout: content drawn straight into a cell (a badge, a chip, a link) keeps its own width instead of stretching across the cell; the selection and expand columns center their control. A click on a disabled `data-action` control reports no `onAction`.
8
+
9
+ ## 0.3.1
10
+
11
+ ### Patch Changes
12
+
13
+ - The selection count moves out of the toolbar: with a pager it ends the pager's status line (`Page 1 of 15 · 144 rows · 3 selected`), without one it stays in the toolbar; either way it shows only while rows are selected, never as "0 selected". A table with selection and nothing else in its toolbar no longer draws an empty band above its header row.
14
+
15
+ ## 0.3.0
16
+
17
+ ### Minor Changes
18
+
19
+ - `pageSizeSelect="custom"` draws the page-size control with the Select primitive of `@svelte-lean/primitives`: a trigger button and a listbox popup drawn from the design tokens in every engine, around the same native `<select>`, which keeps the value and works without scripts. The application imports `@svelte-lean/primitives/select/register` and `@svelte-lean/styles/select.css`. `native` (a plain `<select>`) stays the default.
20
+
21
+ The ids that tie the control's label, trigger and listbox come from `$props.id()`, so the peer dependency is now `svelte ^5.20.0`.
22
+
3
23
  ## 0.2.0
4
24
 
5
25
  ### Minor Changes
package/dist/style.css CHANGED
@@ -700,6 +700,13 @@
700
700
  gap: 0.5rem;
701
701
  white-space: nowrap;
702
702
  }
703
+ /* The Select primitive as the page-size control (`pageSizeSelect="custom"`): as tall as the
704
+ pager's buttons, as wide as its longest option. */
705
+ :where(.slean-table-page-select) {
706
+ --slean-control-height-sm: var(--slean-table-control-height);
707
+ min-inline-size: 4.75rem;
708
+ font-size: inherit;
709
+ }
703
710
  :where(.slean-table-pager, .slean-table-pages) {
704
711
  display: flex;
705
712
  align-items: center;
@@ -851,12 +858,36 @@
851
858
  align-items: center;
852
859
  min-width: 0;
853
860
  }
861
+ /* The renderer's own boxes in a cell fill it; content drawn straight into a cell (a badge, a
862
+ chip, a link in a plain grid cell, a control) keeps its own width. */
854
863
  :where(
855
864
  .slean-table[data-layout='grid'] > .slean-table-scroll > table > * > tr > :is(th, td) > *
865
+ ) {
866
+ flex: 0 1 auto;
867
+ }
868
+ :where(
869
+ .slean-table[data-layout='grid']
870
+ > .slean-table-scroll
871
+ > table
872
+ > *
873
+ > tr
874
+ > :is(th, td)
875
+ > :is(.slean-table-cell, .slean-table-heading, .slean-table-editor)
856
876
  ) {
857
877
  flex: 1 1 auto;
858
878
  min-width: 0;
859
879
  }
880
+ /* The selection and expand columns center their one control. */
881
+ :where(
882
+ .slean-table[data-layout='grid']
883
+ > .slean-table-scroll
884
+ > table
885
+ > *
886
+ > tr
887
+ > :is(.slean-table-selection, .slean-table-expander)
888
+ ) {
889
+ justify-content: center;
890
+ }
860
891
  :where(
861
892
  .slean-table[data-layout='grid'] > .slean-table-scroll > table > * > tr > .slean-table-full
862
893
  ) {
@@ -119,6 +119,7 @@
119
119
  progressive = false,
120
120
  pagination = true,
121
121
  pageSizeOptions = [10, 20, 50, 100],
122
+ pageSizeSelect = 'native',
122
123
  placeholder = '',
123
124
  rowHoverable = true,
124
125
  dir,
@@ -136,6 +137,15 @@
136
137
  onCellRangeChange,
137
138
  class: className = ''
138
139
  }: TableProps<T> = $props();
140
+ /** Ids of this instance's own relationships (the page-size label and listbox); SSR-stable. */
141
+ const uid = $props.id();
142
+ /**
143
+ * The Select primitive's root marker, spread rather than written: @svelte-lean/vite registers
144
+ * the behavior of every marker it reads in markup, and a marker here would put the select
145
+ * behavior in every application that renders a table. The application that sets
146
+ * `pageSizeSelect="custom"` imports `@svelte-lean/primitives/select/register` itself.
147
+ */
148
+ const SELECT_ROOT = { 'data-slean': 'select' } as const;
139
149
  /**
140
150
  * One engine. Props are handed over as getters so the engine follows every prop change.
141
151
  * `getSubRows` and `searchText` must stay `undefined` when absent (grouping rejects tree input;
@@ -855,6 +865,8 @@
855
865
  }
856
866
  /** The page status: a manual table without `rowCount` does not know its total. */
857
867
  const rowsKnown = $derived(!manual || rowCount !== undefined);
868
+ /** Without a pager the toolbar carries the selection count, and only while something is chosen. */
869
+ const selectedInToolbar = $derived(Boolean(selection) && !pagination && s.selection.length > 0);
858
870
  const headerRowCount = $derived(showHeader ? table.headers.length + (filters ? 1 : 0) : 0);
859
871
  /** A manual page is part of a known total: `aria-rowcount` and `aria-rowindex` say where. */
860
872
  const ariaRowCount = $derived(
@@ -1053,7 +1065,10 @@
1053
1065
  const target = e.target as HTMLElement;
1054
1066
  const action = target.closest<HTMLElement>('[data-action]');
1055
1067
  if (action && cell.contains(action)) {
1056
- onAction?.(action.dataset.action ?? '', row, action, index);
1068
+ // A disabled control reports nothing (a click can still be dispatched on one).
1069
+ if (!action.matches(':disabled, [aria-disabled="true"]')) {
1070
+ onAction?.(action.dataset.action ?? '', row, action, index);
1071
+ }
1057
1072
  return;
1058
1073
  }
1059
1074
  const control = target.closest(NOT_ROW_CLICK);
@@ -1465,7 +1480,11 @@
1465
1480
  <!-- The toolbar exists only when something goes in it: an empty flex row would still paint its
1466
1481
  padding as a blank band above the header row (Svelte keeps a text node between the blocks,
1467
1482
  so `:empty` cannot hide it). -->
1468
- {#if searchable || toolbar || selection || columnControls}<div class="slean-table-toolbar">
1483
+ <!-- The selection count sits in the pager's status line when there is a pager, and in the
1484
+ toolbar only without one; either way it shows while something is selected, never "0". -->
1485
+ {#if searchable || toolbar || selectedInToolbar || columnControls}<div
1486
+ class="slean-table-toolbar"
1487
+ >
1469
1488
  {#if searchable}<input
1470
1489
  class="slean-table-search slean-table-input"
1471
1490
  type="search"
@@ -1475,7 +1494,7 @@
1475
1494
  oninput={(e) => delayed('search', e.currentTarget.value, (v) => table.setSearch(v))}
1476
1495
  />{/if}
1477
1496
  {#if toolbar}{@render toolbar(model)}{/if}
1478
- {#if selection}<span class="slean-table-selected" aria-live="polite"
1497
+ {#if selectedInToolbar}<span class="slean-table-selected" aria-live="polite"
1479
1498
  >{m.selected(s.selection.length)}</span
1480
1499
  >{/if}
1481
1500
  {#if columnControls}<details class="slean-table-columns">
@@ -1716,17 +1735,48 @@
1716
1735
  </div>
1717
1736
  {#if pagination}<div class="slean-table-pagination">
1718
1737
  <span class="slean-table-page-status" aria-live="polite"
1719
- >{m.page(model.pageIndex + 1, model.pageCount, rowsKnown ? model.total : undefined)}</span
1720
- ><label class="slean-table-page-size"
1721
- >{m.pageSize}<select
1722
- class="slean-table-input"
1723
- value={s.pageSize}
1724
- onchange={(e) => table.setPageSize(Number(e.currentTarget.value))}
1725
- >{#each pageSizes as size (size)}<option value={size}
1726
- >{size === 0 ? m.allRows : size}</option
1727
- >{/each}</select
1728
- ></label
1729
- ><span class="slean-table-pager"
1738
+ >{m.page(
1739
+ model.pageIndex + 1,
1740
+ model.pageCount,
1741
+ rowsKnown ? model.total : undefined
1742
+ )}{#if selection && s.selection.length > 0}
1743
+ · <span class="slean-table-selected">{m.selected(s.selection.length)}</span>{/if}</span
1744
+ >{#if pageSizeSelect === 'custom'}<span class="slean-table-page-size"
1745
+ ><span id="{uid}-page-size">{m.pageSize}</span>
1746
+ <div class="slean-table-page-select" {...SELECT_ROOT} data-size="sm">
1747
+ <select
1748
+ data-slean-part="native"
1749
+ aria-labelledby="{uid}-page-size"
1750
+ value={s.pageSize}
1751
+ onchange={(e) => table.setPageSize(Number(e.currentTarget.value))}
1752
+ >{#each pageSizes as size (size)}<option value={size}
1753
+ >{size === 0 ? m.allRows : size}</option
1754
+ >{/each}</select
1755
+ ><button
1756
+ type="button"
1757
+ role="combobox"
1758
+ aria-haspopup="listbox"
1759
+ aria-expanded="false"
1760
+ aria-controls="{uid}-page-sizes"
1761
+ aria-labelledby="{uid}-page-size"
1762
+ data-slean-part="trigger"
1763
+ ><span data-slean-part="value">{s.pageSize === 0 ? m.allRows : s.pageSize}</span
1764
+ ></button
1765
+ ><span data-slean-part="toggle" aria-hidden="true"></span>
1766
+ <div popover="manual" data-slean-part="popup">
1767
+ <ul id="{uid}-page-sizes" role="listbox" aria-labelledby="{uid}-page-size"></ul>
1768
+ </div>
1769
+ </div></span
1770
+ >{:else}<label class="slean-table-page-size"
1771
+ >{m.pageSize}<select
1772
+ class="slean-table-input"
1773
+ value={s.pageSize}
1774
+ onchange={(e) => table.setPageSize(Number(e.currentTarget.value))}
1775
+ >{#each pageSizes as size (size)}<option value={size}
1776
+ >{size === 0 ? m.allRows : size}</option
1777
+ >{/each}</select
1778
+ ></label
1779
+ >{/if}<span class="slean-table-pager"
1730
1780
  ><button
1731
1781
  type="button"
1732
1782
  class="slean-table-button"
package/dist/types.d.ts CHANGED
@@ -299,6 +299,14 @@ export interface TableProps<T> {
299
299
  pagination?: boolean;
300
300
  /** Page sizes offered by the pager; the current size is always included. */
301
301
  pageSizeOptions?: number[];
302
+ /**
303
+ * The page-size control. `native` (default): a native `<select>`. `custom`: the Select primitive
304
+ * of `@svelte-lean/primitives` (a trigger button and a listbox popup drawn the same in every
305
+ * engine), around the same native `<select>`, which keeps the value and works without scripts.
306
+ * `custom` needs the select behavior (`import '@svelte-lean/primitives/select/register'`) and
307
+ * its styles (`@svelte-lean/styles/select.css`) in the application.
308
+ */
309
+ pageSizeSelect?: 'native' | 'custom';
302
310
  /** Text of an empty value (`null`, `undefined`, `''`, or `format` returning `null`/`''`). */
303
311
  placeholder?: string;
304
312
  rowHoverable?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svelte-lean/table",
3
- "version": "0.2.0",
3
+ "version": "0.3.2",
4
4
  "description": "A Svelte-native table and data-grid engine that ships only the features you use.",
5
5
  "license": "MIT",
6
6
  "author": "Ömer Say",
@@ -58,7 +58,7 @@
58
58
  "access": "public"
59
59
  },
60
60
  "peerDependencies": {
61
- "svelte": "^5.0.0"
61
+ "svelte": "^5.20.0"
62
62
  },
63
63
  "scripts": {
64
64
  "build": "yarn run -T svelte-kit sync && yarn run -T svelte-package && node scripts/types.mjs && yarn run -T publint && yarn run -T attw --pack . --profile esm-only --exclude-entrypoints ./style.css --format table-flipped --no-emoji",