@dorsk/tsumikit 0.68.0 → 0.69.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.
package/README.md CHANGED
@@ -334,7 +334,8 @@ bindable, `onselect`/`onclose(reason)` report picks; `findTrigger`/`applyTrigger
334
334
  are exported for trigger-char detection and token replacement).
335
335
 
336
336
  **Organisms:** ConversationPanel / ConversationFeed (chat column + sticky
337
- feed, see below), DataTable (generic `<T>`, typed columns + cell snippets;
337
+ feed, see below), DataTable (generic `<T>`, typed columns + cell snippets; `headerSnippets[key]`
338
+ replaces a header cell (a select-all checkbox);
338
339
  `layout="fixed"` makes column widths authoritative, `Column.truncate` /
339
340
  `nowrap` / `hideBelow="sm|md|lg"` (container-query on the table's own box),
340
341
  `hideHeader` clips the header but keeps it for assistive tech, `rowTone(row)`
@@ -346,7 +347,9 @@ for a hover/focus-revealed trailing cell (always visible on touch; under
346
347
  hooks on head/row/cell; `responsive="stack"` turns rows into cards below
347
348
  `stackBelow` (48rem, measured on the table's own box) using `Column.role`
348
349
  `title | detail | meta | actions | hidden` — detail cells get a `data-label`
349
- prefix, the `<table>` stays a table for assistive tech), FilterSearchBar /
350
+ prefix, `Column.stackLabel: false` drops it so metas join on one `·` line
351
+ and details share lines, `--dt-meta-font` restyles meta values, the `<table>`
352
+ stays a table for assistive tech), FilterSearchBar /
350
353
  FilterInput (`size="sm"` compact bar on `--control-height-compact`,
351
354
  `shape="pill"`, `surface`, `hotkey="/"` focuses the input from anywhere
352
355
  outside an editable element, `showHotkey` renders the `<kbd>` hint, `grow`;
@@ -3,8 +3,10 @@
3
3
  value: string;
4
4
  /** Visible text. Optional in `icon` mode where the icon carries the meaning. */
5
5
  label?: string;
6
- /** Optional count badge rendered after the label (filter-pill use). */
7
- count?: number;
6
+ /** Optional count badge rendered after the label (filter-pill use). Numbers
7
+ * get thousands separators (`11178` → `11,178`, fixed en-US so SSR and
8
+ * hydration agree); strings render verbatim (`'99+'`, another locale). */
9
+ count?: number | string;
8
10
  /** Icon glyph — required for icon-toggle mode, optional alongside a label. */
9
11
  icon?: import('../atoms/Icon.svelte').IconName;
10
12
  /** Greyed out, not selectable, skipped by keyboard navigation. */
@@ -23,6 +25,7 @@
23
25
  // Space/Enter (or click) activate. `value` is bindable.
24
26
  import type { Snippet } from 'svelte';
25
27
  import Icon from '../atoms/Icon.svelte';
28
+ import { formatSegmentCount } from './segmented-control-count.js';
26
29
  import { nextEnabledSegment } from './segmented-control-keyboard.js';
27
30
 
28
31
  let {
@@ -143,7 +146,7 @@
143
146
  {:else}
144
147
  {#if o.icon}<Icon name={o.icon} />{/if}
145
148
  {#if o.label}<span class="seg-label">{o.label}</span>{/if}
146
- {#if o.count !== undefined}<span class="seg-count">{o.count}</span>{/if}
149
+ {#if o.count !== undefined}<span class="seg-count">{formatSegmentCount(o.count)}</span>{/if}
147
150
  {/if}
148
151
  </button>
149
152
  {/each}
@@ -2,8 +2,10 @@ export interface SegmentOption {
2
2
  value: string;
3
3
  /** Visible text. Optional in `icon` mode where the icon carries the meaning. */
4
4
  label?: string;
5
- /** Optional count badge rendered after the label (filter-pill use). */
6
- count?: number;
5
+ /** Optional count badge rendered after the label (filter-pill use). Numbers
6
+ * get thousands separators (`11178` → `11,178`, fixed en-US so SSR and
7
+ * hydration agree); strings render verbatim (`'99+'`, another locale). */
8
+ count?: number | string;
7
9
  /** Icon glyph — required for icon-toggle mode, optional alongside a label. */
8
10
  icon?: import('../atoms/Icon.svelte').IconName;
9
11
  /** Greyed out, not selectable, skipped by keyboard navigation. */
@@ -0,0 +1,2 @@
1
+ /** @param {number | string} count */
2
+ export function formatSegmentCount(count: number | string): string;
@@ -0,0 +1,6 @@
1
+ const countFormat = new Intl.NumberFormat('en-US');
2
+
3
+ /** @param {number | string} count */
4
+ export function formatSegmentCount(count) {
5
+ return typeof count === 'number' ? countFormat.format(count) : count;
6
+ }
@@ -21,6 +21,9 @@
21
21
  /** Placement inside the stacked card (`responsive="stack"` only). Columns
22
22
  * without a role render as `label: value` detail lines. */
23
23
  role?: 'title' | 'detail' | 'meta' | 'actions' | 'hidden';
24
+ /** `false` drops the stacked label: meta cells join on one `·` line and
25
+ * detail cells share lines (`responsive="stack"` only, default true). */
26
+ stackLabel?: boolean;
24
27
  }
25
28
  </script>
26
29
 
@@ -40,6 +43,7 @@
40
43
  onrowclick,
41
44
  onsort,
42
45
  cellSnippets = {},
46
+ headerSnippets = {},
43
47
  empty = 'No data.',
44
48
  stickyHeader = false,
45
49
  stickyOffset,
@@ -69,6 +73,8 @@
69
73
  * table only reflects the indicator and emits; it does not reorder rows. */
70
74
  onsort?: (key: string, dir: 'asc' | 'desc') => void;
71
75
  cellSnippets?: Record<string, Snippet<[T]>>;
76
+ /** Replaces a column's header content (e.g. a select-all checkbox). */
77
+ headerSnippets?: Record<string, Snippet<[Column<T>]>>;
72
78
  empty?: string | Snippet;
73
79
  stickyHeader?: boolean;
74
80
  /** Distance from the scroll container's top for the sticky header, e.g.
@@ -210,7 +216,9 @@
210
216
  : 'none'
211
217
  : undefined}
212
218
  >
213
- {#if col.sortable}
219
+ {#if headerSnippets[col.key]}
220
+ {@render headerSnippets[col.key](col)}
221
+ {:else if col.sortable}
214
222
  <button type="button" class="dt-sort" onclick={() => toggleSort(col)}>
215
223
  <span>{col.label}</span>
216
224
  <span class="dt-arrow" class:active={sortKey === col.key}>
@@ -264,6 +272,7 @@
264
272
  data-hide-below={col.hideBelow}
265
273
  data-role={responsive === 'stack' ? (col.role ?? 'detail') : undefined}
266
274
  data-label={responsive === 'stack' ? col.label : undefined}
275
+ data-stack-label={responsive === 'stack' && col.stackLabel === false ? 'false' : undefined}
267
276
  class:truncate={col.truncate}
268
277
  class:nowrap={col.nowrap}
269
278
  style:text-align={stacked ? undefined : (col.align ?? 'left')}
@@ -528,12 +537,27 @@
528
537
  [data-stacked] td[data-role='meta'] {
529
538
  flex: none;
530
539
  order: 1;
531
- font-family: var(--font-mono);
540
+ font-family: var(--dt-meta-font, var(--font-mono));
532
541
  font-size: var(--fs-xs);
533
542
  color: var(--text-muted);
534
543
  }
535
544
  [data-stacked] td[data-role='meta']::before {
536
545
  content: attr(data-label) ' ';
546
+ font-family: var(--font-sans);
547
+ color: var(--text-faint);
548
+ }
549
+ [data-stacked] td[data-role='detail'][data-stack-label='false'] {
550
+ flex: 0 1 auto;
551
+ }
552
+ [data-stacked] td[data-stack-label='false']::before {
553
+ content: none;
554
+ }
555
+ [data-stacked] td[data-role='meta'] ~ td[data-role='meta'][data-stack-label='false'] {
556
+ margin-inline-start: calc(var(--sp-1) - var(--sp-3));
557
+ }
558
+ [data-stacked] td[data-role='meta'] ~ td[data-role='meta'][data-stack-label='false']::before {
559
+ content: '·';
560
+ margin-inline-end: var(--sp-1);
537
561
  color: var(--text-faint);
538
562
  }
539
563
  [data-stacked] td[data-role='actions'],
@@ -19,6 +19,9 @@ export interface Column<T> {
19
19
  /** Placement inside the stacked card (`responsive="stack"` only). Columns
20
20
  * without a role render as `label: value` detail lines. */
21
21
  role?: 'title' | 'detail' | 'meta' | 'actions' | 'hidden';
22
+ /** `false` drops the stacked label: meta cells join on one `·` line and
23
+ * detail cells share lines (`responsive="stack"` only, default true). */
24
+ stackLabel?: boolean;
22
25
  }
23
26
  import type { Snippet } from 'svelte';
24
27
  declare function $$render<T>(): {
@@ -32,6 +35,8 @@ declare function $$render<T>(): {
32
35
  * table only reflects the indicator and emits; it does not reorder rows. */
33
36
  onsort?: (key: string, dir: "asc" | "desc") => void;
34
37
  cellSnippets?: Record<string, Snippet<[T]>>;
38
+ /** Replaces a column's header content (e.g. a select-all checkbox). */
39
+ headerSnippets?: Record<string, Snippet<[Column<T>]>>;
35
40
  empty?: string | Snippet;
36
41
  stickyHeader?: boolean;
37
42
  /** Distance from the scroll container's top for the sticky header, e.g.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dorsk/tsumikit",
3
- "version": "0.68.0",
3
+ "version": "0.69.0",
4
4
  "description": "Minimal, dependency-free Svelte 5 + pure-CSS UI kit. Token-driven atoms, molecules & layouts with theming out of the box.",
5
5
  "type": "module",
6
6
  "license": "MIT",