@dorsk/tsumikit 0.38.1 → 0.40.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 (145) hide show
  1. package/dist/autoresize.d.ts +2 -0
  2. package/dist/autoresize.js +15 -1
  3. package/dist/components/atoms/Artwork.svelte +19 -18
  4. package/dist/components/atoms/Artwork.svelte.d.ts +3 -2
  5. package/dist/components/atoms/Badge.svelte +60 -26
  6. package/dist/components/atoms/Badge.svelte.d.ts +14 -3
  7. package/dist/components/atoms/Button.svelte +30 -12
  8. package/dist/components/atoms/Button.svelte.d.ts +4 -2
  9. package/dist/components/atoms/Card.svelte +47 -30
  10. package/dist/components/atoms/Card.svelte.d.ts +12 -3
  11. package/dist/components/atoms/Checkbox.svelte +16 -2
  12. package/dist/components/atoms/Checkbox.svelte.d.ts +2 -0
  13. package/dist/components/atoms/Dot.svelte +17 -11
  14. package/dist/components/atoms/Dot.svelte.d.ts +5 -2
  15. package/dist/components/atoms/Gauge.svelte +19 -18
  16. package/dist/components/atoms/Gauge.svelte.d.ts +3 -2
  17. package/dist/components/atoms/Heading.svelte +20 -19
  18. package/dist/components/atoms/Heading.svelte.d.ts +3 -2
  19. package/dist/components/atoms/Icon.svelte +36 -13
  20. package/dist/components/atoms/Icon.svelte.d.ts +20 -2
  21. package/dist/components/atoms/Input.svelte +17 -3
  22. package/dist/components/atoms/Input.svelte.d.ts +2 -1
  23. package/dist/components/atoms/Kbd.svelte +25 -3
  24. package/dist/components/atoms/Kbd.svelte.d.ts +5 -1
  25. package/dist/components/atoms/Link.svelte +16 -12
  26. package/dist/components/atoms/Link.svelte.d.ts +6 -2
  27. package/dist/components/atoms/Progress.svelte +119 -4
  28. package/dist/components/atoms/Progress.svelte.d.ts +28 -2
  29. package/dist/components/atoms/Scrim.svelte +7 -2
  30. package/dist/components/atoms/Scrim.svelte.d.ts +2 -0
  31. package/dist/components/atoms/SegmentedProgress.svelte +14 -2
  32. package/dist/components/atoms/SegmentedProgress.svelte.d.ts +5 -2
  33. package/dist/components/atoms/Select.svelte +18 -6
  34. package/dist/components/atoms/Select.svelte.d.ts +3 -3
  35. package/dist/components/atoms/Skeleton.svelte +12 -11
  36. package/dist/components/atoms/Skeleton.svelte.d.ts +3 -2
  37. package/dist/components/atoms/Slider.svelte +45 -3
  38. package/dist/components/atoms/Slider.svelte.d.ts +7 -0
  39. package/dist/components/atoms/Spinner.svelte +2 -0
  40. package/dist/components/atoms/Spinner.svelte.d.ts +2 -0
  41. package/dist/components/atoms/Switch.svelte +70 -18
  42. package/dist/components/atoms/Switch.svelte.d.ts +5 -1
  43. package/dist/components/atoms/Text.svelte +39 -26
  44. package/dist/components/atoms/Text.svelte.d.ts +7 -3
  45. package/dist/components/atoms/Textarea.svelte +47 -10
  46. package/dist/components/atoms/Textarea.svelte.d.ts +10 -3
  47. package/dist/components/layouts/AppShell.svelte +7 -3
  48. package/dist/components/layouts/AppShell.svelte.d.ts +2 -0
  49. package/dist/components/layouts/AutoGrid.svelte +8 -0
  50. package/dist/components/layouts/AutoGrid.svelte.d.ts +2 -0
  51. package/dist/components/layouts/Cluster.svelte +62 -1
  52. package/dist/components/layouts/Cluster.svelte.d.ts +9 -0
  53. package/dist/components/layouts/NavItem.svelte +22 -1
  54. package/dist/components/layouts/NavItem.svelte.d.ts +5 -0
  55. package/dist/components/layouts/NavSection.svelte +4 -1
  56. package/dist/components/layouts/NavSection.svelte.d.ts +2 -0
  57. package/dist/components/layouts/ResizablePanel.svelte +14 -3
  58. package/dist/components/layouts/ResizablePanel.svelte.d.ts +5 -0
  59. package/dist/components/layouts/Stack.svelte +41 -0
  60. package/dist/components/layouts/Stack.svelte.d.ts +9 -0
  61. package/dist/components/layouts/Toolbar.svelte +122 -0
  62. package/dist/components/layouts/Toolbar.svelte.d.ts +22 -0
  63. package/dist/components/molecules/AttachmentList.svelte +59 -0
  64. package/dist/components/molecules/AttachmentList.svelte.d.ts +13 -0
  65. package/dist/components/molecules/Breadcrumb.svelte +6 -2
  66. package/dist/components/molecules/Breadcrumb.svelte.d.ts +2 -0
  67. package/dist/components/molecules/Callout.svelte +10 -4
  68. package/dist/components/molecules/Callout.svelte.d.ts +1 -1
  69. package/dist/components/molecules/CapBar.svelte +5 -1
  70. package/dist/components/molecules/ChatBubble.svelte +158 -0
  71. package/dist/components/molecules/ChatBubble.svelte.d.ts +26 -0
  72. package/dist/components/molecules/CodeBlock.svelte +36 -3
  73. package/dist/components/molecules/CodeBlock.svelte.d.ts +8 -0
  74. package/dist/components/molecules/Composer.svelte +195 -0
  75. package/dist/components/molecules/Composer.svelte.d.ts +27 -0
  76. package/dist/components/molecules/ConfirmModal.svelte +15 -5
  77. package/dist/components/molecules/ConfirmModal.svelte.d.ts +5 -1
  78. package/dist/components/molecules/Drawer.svelte +9 -6
  79. package/dist/components/molecules/Dropzone.svelte +9 -3
  80. package/dist/components/molecules/Dropzone.svelte.d.ts +2 -0
  81. package/dist/components/molecules/EmptyState.svelte +9 -5
  82. package/dist/components/molecules/EmptyState.svelte.d.ts +3 -1
  83. package/dist/components/molecules/Field.svelte +7 -1
  84. package/dist/components/molecules/Field.svelte.d.ts +2 -0
  85. package/dist/components/molecules/FileButton.svelte +2 -1
  86. package/dist/components/molecules/FileButton.svelte.d.ts +2 -1
  87. package/dist/components/molecules/FilterInput.svelte +13 -3
  88. package/dist/components/molecules/FilterInput.svelte.d.ts +4 -1
  89. package/dist/components/molecules/FontScalePicker.svelte +8 -1
  90. package/dist/components/molecules/IconButton.svelte +32 -7
  91. package/dist/components/molecules/IconButton.svelte.d.ts +9 -1
  92. package/dist/components/molecules/KeyValue.svelte +4 -1
  93. package/dist/components/molecules/KeyValue.svelte.d.ts +2 -1
  94. package/dist/components/molecules/Menu.svelte +50 -5
  95. package/dist/components/molecules/Menu.svelte.d.ts +10 -1
  96. package/dist/components/molecules/Meter.svelte +91 -0
  97. package/dist/components/molecules/Meter.svelte.d.ts +29 -0
  98. package/dist/components/molecules/Metric.svelte +134 -8
  99. package/dist/components/molecules/Metric.svelte.d.ts +20 -2
  100. package/dist/components/molecules/Modal.svelte +26 -12
  101. package/dist/components/molecules/Modal.svelte.d.ts +7 -1
  102. package/dist/components/molecules/Pagination.svelte +25 -1
  103. package/dist/components/molecules/Pagination.svelte.d.ts +9 -2
  104. package/dist/components/molecules/Popover.svelte +16 -5
  105. package/dist/components/molecules/Popover.svelte.d.ts +5 -0
  106. package/dist/components/molecules/Prose.svelte +140 -0
  107. package/dist/components/molecules/Prose.svelte.d.ts +13 -0
  108. package/dist/components/molecules/RadioGroup.svelte +42 -2
  109. package/dist/components/molecules/RadioGroup.svelte.d.ts +4 -1
  110. package/dist/components/molecules/SectionHeader.svelte +5 -2
  111. package/dist/components/molecules/SectionHeader.svelte.d.ts +1 -1
  112. package/dist/components/molecules/SegmentedControl.svelte +36 -1
  113. package/dist/components/molecules/SegmentedControl.svelte.d.ts +11 -1
  114. package/dist/components/molecules/SelectButton.svelte +64 -4
  115. package/dist/components/molecules/SelectButton.svelte.d.ts +18 -1
  116. package/dist/components/molecules/Tabs.svelte +21 -3
  117. package/dist/components/molecules/Tabs.svelte.d.ts +6 -0
  118. package/dist/components/molecules/Timestamp.svelte +13 -6
  119. package/dist/components/molecules/Timestamp.svelte.d.ts +4 -0
  120. package/dist/components/molecules/Toaster.svelte +54 -38
  121. package/dist/components/molecules/Toaster.svelte.d.ts +5 -1
  122. package/dist/components/molecules/Toggle.svelte +2 -1
  123. package/dist/components/molecules/Toggle.svelte.d.ts +2 -1
  124. package/dist/components/molecules/Tooltip.svelte +32 -5
  125. package/dist/components/molecules/Tooltip.svelte.d.ts +9 -0
  126. package/dist/components/molecules/Truncate.svelte +156 -18
  127. package/dist/components/molecules/Truncate.svelte.d.ts +17 -1
  128. package/dist/components/molecules/WorkingDir.svelte +6 -0
  129. package/dist/components/molecules/WorkingDir.svelte.d.ts +2 -0
  130. package/dist/components/organisms/DataTable.svelte +38 -10
  131. package/dist/components/organisms/DataTable.svelte.d.ts +4 -0
  132. package/dist/components/organisms/FilterSearchBar.svelte +9 -2
  133. package/dist/components/organisms/FilterSearchBar.svelte.d.ts +4 -1
  134. package/dist/index.d.ts +10 -2
  135. package/dist/index.js +8 -1
  136. package/dist/size.d.ts +2 -0
  137. package/dist/size.js +1 -0
  138. package/dist/styles/tokens.css +3 -0
  139. package/dist/timestamp.d.ts +2 -2
  140. package/dist/timestamp.js +4 -4
  141. package/dist/tone.d.ts +1 -1
  142. package/dist/tone.js +1 -1
  143. package/dist/truncate.d.ts +13 -1
  144. package/dist/truncate.js +39 -0
  145. package/package.json +5 -3
@@ -1,3 +1,7 @@
1
- declare const Toaster: import("svelte").Component<Record<string, never>, {}, "">;
1
+ type $$ComponentProps = {
2
+ class?: string;
3
+ style?: string;
4
+ };
5
+ declare const Toaster: import("svelte").Component<$$ComponentProps, {}, "">;
2
6
  type Toaster = ReturnType<typeof Toaster>;
3
7
  export default Toaster;
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import type { ControlSize } from '../../size';
2
3
  // Toggle chip — a muted pill/chip that lights up (tinted) when `pressed`.
3
4
  // The "on" tint is driven by the `--toggle-accent` CSS var (default accent),
4
5
  // so callers recolor it per-use (e.g. warm for behavior, role color for
@@ -26,7 +27,7 @@
26
27
  pressed?: boolean;
27
28
  pill?: boolean;
28
29
  struck?: boolean;
29
- size?: 'sm' | 'md';
30
+ size?: ControlSize;
30
31
  // Share a flex row with siblings (flex: 1 1 0).
31
32
  grow?: boolean;
32
33
  // `shrink={false}` keeps the chip at its intrinsic width (flex: none).
@@ -1,10 +1,11 @@
1
+ import type { ControlSize } from '../../size';
1
2
  import type { Snippet } from 'svelte';
2
3
  import type { HTMLButtonAttributes } from 'svelte/elements';
3
4
  type $$ComponentProps = HTMLButtonAttributes & {
4
5
  pressed?: boolean;
5
6
  pill?: boolean;
6
7
  struck?: boolean;
7
- size?: 'sm' | 'md';
8
+ size?: ControlSize;
8
9
  grow?: boolean;
9
10
  shrink?: boolean;
10
11
  children?: Snippet;
@@ -24,7 +24,13 @@
24
24
  placement = 'top',
25
25
  delay = 200,
26
26
  closeDelay = 250,
27
- trigger
27
+ trigger,
28
+ cursor = true,
29
+ inline = false,
30
+ width,
31
+ maxWidth,
32
+ class: klass = '',
33
+ style: styleProp = '',
28
34
  }: {
29
35
  /** Plain-text bubble. Ignored when `content` is provided. */
30
36
  text?: string;
@@ -36,6 +42,15 @@
36
42
  /** Grace period (ms) before closing — lets the pointer travel into the panel. */
37
43
  closeDelay?: number;
38
44
  trigger: Snippet;
45
+ /** `cursor: help` on the trigger wrapper (default). */
46
+ cursor?: boolean;
47
+ /** `flex: none` on the wrapper so a flex row cannot squeeze the trigger. */
48
+ inline?: boolean;
49
+ /** Panel width / max-width (default max 16rem). */
50
+ width?: string;
51
+ maxWidth?: string;
52
+ class?: string;
53
+ style?: string;
39
54
  } = $props();
40
55
 
41
56
  const id = `tip-${Math.random().toString(36).slice(2, 8)}`;
@@ -82,10 +97,14 @@
82
97
  // hover/focus listeners (attached via the DOM, so the wrapper stays a plain,
83
98
  // role-less container — the interactive element is the trigger inside).
84
99
  function tooltip(node: HTMLElement) {
85
- const target = (node.matches(FOCUSABLE) ? node : node.querySelector(FOCUSABLE)) as
100
+ let target = (node.matches(FOCUSABLE) ? node : node.querySelector(FOCUSABLE)) as
86
101
  | HTMLElement
87
102
  | null;
88
- target?.setAttribute('aria-describedby', id);
103
+ if (!target) {
104
+ node.tabIndex = 0;
105
+ target = node;
106
+ }
107
+ target.setAttribute('aria-describedby', id);
89
108
  const onkey = (e: KeyboardEvent) => e.key === 'Escape' && hideNow();
90
109
  node.addEventListener('mouseenter', show);
91
110
  node.addEventListener('mouseleave', scheduleHide);
@@ -94,7 +113,7 @@
94
113
  node.addEventListener('keydown', onkey);
95
114
  return {
96
115
  destroy() {
97
- target?.removeAttribute('aria-describedby');
116
+ target.removeAttribute('aria-describedby');
98
117
  node.removeEventListener('mouseenter', show);
99
118
  node.removeEventListener('mouseleave', scheduleHide);
100
119
  node.removeEventListener('focusin', show);
@@ -125,7 +144,7 @@
125
144
  }
126
145
  </script>
127
146
 
128
- <span class="tip-wrap" data-tsu="Tooltip" bind:this={wrapEl} use:tooltip>
147
+ <span class="tip-wrap {klass}" class:help={cursor} class:inline style={styleProp} data-tsu="Tooltip" bind:this={wrapEl} use:tooltip>
129
148
  {@render trigger()}
130
149
  </span>
131
150
 
@@ -136,6 +155,8 @@
136
155
  popover="manual"
137
156
  class="tip"
138
157
  class:rich={!!content}
158
+ style:width
159
+ style:max-width={maxWidth}
139
160
  use:panel
140
161
  >
141
162
  {#if content}{@render content()}{:else}{text}{/if}
@@ -145,6 +166,12 @@
145
166
  .tip-wrap {
146
167
  display: inline-flex;
147
168
  }
169
+ .tip-wrap.inline {
170
+ flex: none;
171
+ }
172
+ .tip-wrap.help:not(:has(a, button, input, select, textarea)) {
173
+ cursor: help;
174
+ }
148
175
  .tip {
149
176
  position: fixed;
150
177
  margin: 0;
@@ -10,6 +10,15 @@ type $$ComponentProps = {
10
10
  /** Grace period (ms) before closing — lets the pointer travel into the panel. */
11
11
  closeDelay?: number;
12
12
  trigger: Snippet;
13
+ /** `cursor: help` on the trigger wrapper (default). */
14
+ cursor?: boolean;
15
+ /** `flex: none` on the wrapper so a flex row cannot squeeze the trigger. */
16
+ inline?: boolean;
17
+ /** Panel width / max-width (default max 16rem). */
18
+ width?: string;
19
+ maxWidth?: string;
20
+ class?: string;
21
+ style?: string;
13
22
  };
14
23
  declare const Tooltip: import("svelte").Component<$$ComponentProps, {}, "">;
15
24
  type Tooltip = ReturnType<typeof Tooltip>;
@@ -5,10 +5,11 @@
5
5
  // fits, it renders as a bare inline span with no tooltip wiring at all — so
6
6
  // you only pay for the Tooltip when something is actually hidden.
7
7
  //
8
- // For responsive, width-driven end-clipping prefer <Text truncate> (pure
9
- // CSS); reach for this when you need middle/start truncation or a guaranteed
10
- // max character count — e.g. addresses, hashes, long ids.
11
- import { truncate, type TruncateMode } from '../../truncate';
8
+ // `fit` swaps the character budget for a measured one: the host width is
9
+ // observed and the longest candidate that fits is shown (mode `path`
10
+ // abbreviates fish-style). `copyable` copies the full text on click.
11
+ import { copyToClipboard } from '../../clipboard';
12
+ import { pathCandidates, truncate, type TruncateMode } from '../../truncate';
12
13
  import Tooltip from './Tooltip.svelte';
13
14
 
14
15
  let {
@@ -16,34 +17,135 @@
16
17
  max,
17
18
  mode = 'end',
18
19
  ellipsis = '…',
20
+ separator = '/',
21
+ minLeaf = 3,
22
+ keepFirst = 0,
23
+ fit = false,
24
+ mono = false,
25
+ copyable = false,
26
+ copiedLabel = 'Copied',
27
+ oncopy,
28
+ grow = false,
19
29
  tooltip = true,
20
- placement = 'top'
30
+ placement = 'top',
31
+ class: klass = '',
32
+ style: styleProp = '',
21
33
  }: {
22
34
  text: string;
23
- max: number;
35
+ /** Character budget. Ignored when `fit` is set. */
36
+ max?: number;
24
37
  mode?: TruncateMode;
25
38
  ellipsis?: string;
39
+ /** `path` mode options. */
40
+ separator?: string;
41
+ minLeaf?: number;
42
+ keepFirst?: number;
43
+ /** Measure the host width and show the longest candidate that fits. */
44
+ fit?: boolean;
45
+ mono?: boolean;
46
+ /** Click copies the full text; announces via a live region. */
47
+ copyable?: boolean;
48
+ copiedLabel?: string;
49
+ oncopy?: (ok: boolean) => void;
50
+ /** `flex: 1 1 0; min-width: 0` so the host claims a flex row's free space. */
51
+ grow?: boolean;
26
52
  /** Reveal the full text on hover/focus when truncated. Default true. */
27
53
  tooltip?: boolean;
28
54
  placement?: 'top' | 'bottom' | 'left' | 'right';
55
+ class?: string;
56
+ style?: string;
29
57
  } = $props();
30
58
 
31
- const shown = $derived(truncate(text, { max, mode, ellipsis }));
59
+ const pathOpts = $derived({ ellipsis, separator, minLeaf, keepFirst });
60
+
61
+ const candidates = $derived.by(() => {
62
+ if (mode === 'path') return pathCandidates(text, pathOpts);
63
+ const out: string[] = [];
64
+ for (let m = [...text].length; m >= 2; m--) {
65
+ const c = truncate(text, { max: m, mode, ellipsis });
66
+ if (out[out.length - 1] !== c) out.push(c);
67
+ }
68
+ return out;
69
+ });
70
+
71
+ let rail = $state<HTMLSpanElement | null>(null);
72
+ let probe = $state<HTMLSpanElement | null>(null);
73
+ let avail = $state(Number.POSITIVE_INFINITY);
74
+ let chPx = $state(8);
75
+
76
+ $effect(() => {
77
+ if (!fit || !rail) return;
78
+ const el = rail;
79
+ const measure = () => {
80
+ avail = el.clientWidth;
81
+ if (probe) chPx = probe.getBoundingClientRect().width / 10 || chPx;
82
+ };
83
+ measure();
84
+ const ro = new ResizeObserver(measure);
85
+ ro.observe(el);
86
+ return () => ro.disconnect();
87
+ });
88
+
89
+ const shown = $derived.by(() => {
90
+ if (fit) {
91
+ const budget = Math.max(0, avail - 1);
92
+ return candidates.find((c) => [...c].length * chPx <= budget) ?? candidates[candidates.length - 1];
93
+ }
94
+ if (max === undefined) return text;
95
+ return truncate(text, { max, mode, ...pathOpts });
96
+ });
32
97
  const isTruncated = $derived(shown !== text);
98
+
99
+ let status = $state('');
100
+ let timer: ReturnType<typeof setTimeout> | undefined;
101
+ async function copy(e: MouseEvent) {
102
+ e.stopPropagation();
103
+ const ok = await copyToClipboard(text);
104
+ oncopy?.(ok);
105
+ status = ok ? copiedLabel : 'Copy failed';
106
+ clearTimeout(timer);
107
+ timer = setTimeout(() => {
108
+ status = '';
109
+ }, 1500);
110
+ }
33
111
  </script>
34
112
 
35
- {#if isTruncated && tooltip}
36
- <Tooltip text={text} {placement}>
37
- {#snippet trigger()}
38
- <!-- tabindex makes the shortened text reachable by keyboard so the
39
- full-text tooltip (wired by Tooltip onto the first focusable child)
40
- isn't mouse-only; the span is otherwise non-interactive. -->
41
- <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
42
- <span class="trunc" data-tsu="Truncate" tabindex="0">{shown}</span>
43
- {/snippet}
44
- </Tooltip>
113
+ {#snippet inner(focusable: boolean)}
114
+ {#if copyable}
115
+ <button type="button" class="trunc copy {klass}" class:mono style={styleProp} data-tsu="Truncate" title={text} onclick={copy}>
116
+ {shown}
117
+ </button>
118
+ <span class="sr-only" role="status" aria-live="polite">{status}</span>
119
+ {:else if focusable}
120
+ <!-- tabindex makes the shortened text reachable by keyboard so the
121
+ full-text tooltip (wired by Tooltip onto the first focusable child)
122
+ isn't mouse-only; the span is otherwise non-interactive. -->
123
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
124
+ <span class="trunc {klass}" class:mono style={styleProp} data-tsu="Truncate" tabindex="0">{shown}</span>
125
+ {:else}
126
+ <span class="trunc {klass}" class:mono style={styleProp} data-tsu="Truncate">{shown}</span>
127
+ {/if}
128
+ {/snippet}
129
+
130
+ {#snippet body()}
131
+ {#if isTruncated && tooltip}
132
+ <Tooltip {text} {placement}>
133
+ {#snippet trigger()}{@render inner(true)}{/snippet}
134
+ </Tooltip>
135
+ {:else}
136
+ {@render inner(false)}
137
+ {/if}
138
+ {/snippet}
139
+
140
+ {#if fit}
141
+ <span class="rail" class:grow bind:this={rail}>
142
+ {@render body()}
143
+ <span class="probe" bind:this={probe} aria-hidden="true">{'0'.repeat(10)}</span>
144
+ </span>
145
+ {:else if grow}
146
+ <span class="rail grow">{@render body()}</span>
45
147
  {:else}
46
- <span class="trunc" data-tsu="Truncate">{shown}</span>
148
+ {@render body()}
47
149
  {/if}
48
150
 
49
151
  <style>
@@ -51,5 +153,41 @@
51
153
  /* No layout of its own — it shortens the string, not the box. The cursor
52
154
  hint signals there's more to reveal. */
53
155
  cursor: default;
156
+ white-space: nowrap;
157
+ }
158
+ .mono {
159
+ font-family: var(--font-mono);
160
+ }
161
+ .copy {
162
+ border: 0;
163
+ padding: 0;
164
+ background: none;
165
+ color: inherit;
166
+ font: inherit;
167
+ cursor: copy;
168
+ }
169
+ .rail {
170
+ display: inline-flex;
171
+ align-items: center;
172
+ min-width: 0;
173
+ max-width: 100%;
174
+ overflow: hidden;
175
+ }
176
+ .grow {
177
+ flex: 1 1 0;
178
+ }
179
+ .probe {
180
+ position: absolute;
181
+ visibility: hidden;
182
+ white-space: pre;
183
+ pointer-events: none;
184
+ }
185
+ .sr-only {
186
+ position: absolute;
187
+ width: 1px;
188
+ height: 1px;
189
+ overflow: hidden;
190
+ clip: rect(0, 0, 0, 0);
191
+ white-space: nowrap;
54
192
  }
55
193
  </style>
@@ -1,12 +1,28 @@
1
1
  import { type TruncateMode } from '../../truncate';
2
2
  type $$ComponentProps = {
3
3
  text: string;
4
- max: number;
4
+ /** Character budget. Ignored when `fit` is set. */
5
+ max?: number;
5
6
  mode?: TruncateMode;
6
7
  ellipsis?: string;
8
+ /** `path` mode options. */
9
+ separator?: string;
10
+ minLeaf?: number;
11
+ keepFirst?: number;
12
+ /** Measure the host width and show the longest candidate that fits. */
13
+ fit?: boolean;
14
+ mono?: boolean;
15
+ /** Click copies the full text; announces via a live region. */
16
+ copyable?: boolean;
17
+ copiedLabel?: string;
18
+ oncopy?: (ok: boolean) => void;
19
+ /** `flex: 1 1 0; min-width: 0` so the host claims a flex row's free space. */
20
+ grow?: boolean;
7
21
  /** Reveal the full text on hover/focus when truncated. Default true. */
8
22
  tooltip?: boolean;
9
23
  placement?: 'top' | 'bottom' | 'left' | 'right';
24
+ class?: string;
25
+ style?: string;
10
26
  };
11
27
  declare const Truncate: import("svelte").Component<$$ComponentProps, {}, "">;
12
28
  type Truncate = ReturnType<typeof Truncate>;
@@ -13,6 +13,7 @@
13
13
  minLeaf = 18,
14
14
  mono = true,
15
15
  full = false,
16
+ shrink = false,
16
17
  icon = 'folder',
17
18
  title,
18
19
  copy = false,
@@ -27,6 +28,8 @@
27
28
  mono?: boolean;
28
29
  /** Skip the fit algorithm: render the whole path and size to content. */
29
30
  full?: boolean;
31
+ /** Size to the path when it fits; keep truncating when it does not. */
32
+ shrink?: boolean;
30
33
  icon?: IconName;
31
34
  /** Defaults to the full path. */
32
35
  title?: string;
@@ -64,6 +67,8 @@
64
67
  return () => ro.disconnect();
65
68
  });
66
69
 
70
+ const fullWidth = $derived(shrink && !full ? Math.ceil(candidates[0].length * chPx + chrome + 2) : undefined);
71
+
67
72
  const shown = $derived.by(() => {
68
73
  if (full) return normalized;
69
74
  const budget = Math.max(0, avail - chrome - 1);
@@ -87,6 +92,7 @@
87
92
  class="rail {klass}"
88
93
  class:full
89
94
  bind:this={rail}
95
+ style:max-width={fullWidth ? `${fullWidth}px` : undefined}
90
96
  {style}
91
97
  >
92
98
  <Badge
@@ -6,6 +6,8 @@ type $$ComponentProps = {
6
6
  mono?: boolean;
7
7
  /** Skip the fit algorithm: render the whole path and size to content. */
8
8
  full?: boolean;
9
+ /** Size to the path when it fits; keep truncating when it does not. */
10
+ shrink?: boolean;
9
11
  icon?: IconName;
10
12
  /** Defaults to the full path. */
11
13
  title?: string;
@@ -28,8 +28,8 @@
28
28
  // Generic, accessible data table. Columns are typed against the row type; a
29
29
  // column renders row[key] by default, a `get` accessor, or a custom
30
30
  // `cellSnippets[key]` snippet for full control (badges, actions…). Sticky
31
- // header, empty state, optional row click (rows become buttons with the
32
- // right keyboard semantics). Horizontal scroll is contained so it never
31
+ // header, empty state, optional row click (the table becomes a grid whose
32
+ // rows are focusable and Enter/Space-activatable; rows stay rows for AT). Horizontal scroll is contained so it never
33
33
  // breaks the page layout on mobile.
34
34
  import type { Snippet } from 'svelte';
35
35
 
@@ -55,7 +55,10 @@
55
55
  onloadmore,
56
56
  loadMoreLabel = 'Load more',
57
57
  responsive = 'scroll',
58
- stackBelow = '48rem'
58
+ stackBelow = '48rem',
59
+ class: klass = '',
60
+ style: styleProp = '',
61
+ tableClass = '',
59
62
  }: {
60
63
  columns: Column<T>[];
61
64
  rows: T[];
@@ -92,10 +95,25 @@
92
95
  responsive?: 'scroll' | 'stack';
93
96
  /** Width (px/em/rem) under which `stack` mode kicks in. */
94
97
  stackBelow?: string;
98
+ class?: string;
99
+ style?: string;
100
+ /** Class on the inner <table>. */
101
+ tableClass?: string;
95
102
  } = $props();
96
103
 
97
104
  let wrapEl = $state<HTMLDivElement | null>(null);
98
105
  let stacked = $state(false);
106
+ let actionsW = $state(0);
107
+
108
+ function measureActions(node: HTMLElement) {
109
+ const update = () => {
110
+ actionsW = Math.max(actionsW, Math.ceil(node.getBoundingClientRect().width));
111
+ };
112
+ update();
113
+ const observer = new ResizeObserver(update);
114
+ observer.observe(node);
115
+ return { destroy: () => observer.disconnect() };
116
+ }
99
117
 
100
118
  function toPx(length: string, el: HTMLElement): number {
101
119
  const n = Number.parseFloat(length);
@@ -158,7 +176,8 @@
158
176
 
159
177
  <div
160
178
  bind:this={wrapEl}
161
- class="dt-scroll"
179
+ class="dt-scroll {klass}"
180
+ style={styleProp}
162
181
  class:dt-stack={responsive === 'stack'}
163
182
  data-tsu="DataTable"
164
183
  data-size={size}
@@ -166,12 +185,14 @@
166
185
  aria-busy={loading || undefined}
167
186
  >
168
187
  <table
169
- class="dt"
188
+ class="dt {tableClass}"
170
189
  class:sticky={stickyHeader}
171
190
  class:fixed={layout === 'fixed'}
172
191
  class:head-hidden={hideHeader}
173
192
  class:sm={size === 'sm'}
174
193
  style:--dt-sticky-offset={stickyOffset}
194
+ style:--dt-actions-w={actionsW ? `calc(${actionsW}px + 2 * var(--sp-3))` : undefined}
195
+ role={onrowclick ? 'grid' : undefined}
175
196
  >
176
197
  <thead data-part="head">
177
198
  <tr>
@@ -227,7 +248,6 @@
227
248
  class:clickable={!!onrowclick}
228
249
  class:toned={!!tone && tone !== 'neutral'}
229
250
  tabindex={onrowclick ? 0 : undefined}
230
- role={onrowclick ? 'button' : undefined}
231
251
  onclick={onrowclick ? () => onrowclick(row) : undefined}
232
252
  onkeydown={onrowclick
233
253
  ? (e) => {
@@ -257,7 +277,7 @@
257
277
  {/each}
258
278
  {#if rowActions}
259
279
  <td data-part="actions" class="dt-actions">
260
- {@render rowActions(row)}
280
+ <span class="dt-actions-inner" use:measureActions>{@render rowActions(row)}</span>
261
281
  </td>
262
282
  {/if}
263
283
  </tr>
@@ -414,6 +434,12 @@
414
434
  opacity: 0;
415
435
  transition: opacity 0.12s var(--ease);
416
436
  }
437
+ .dt-actions-inner {
438
+ display: inline-flex;
439
+ align-items: center;
440
+ gap: var(--sp-1);
441
+ white-space: nowrap;
442
+ }
417
443
  tr:hover .dt-actions,
418
444
  tr:focus-within .dt-actions {
419
445
  opacity: 1;
@@ -444,7 +470,7 @@
444
470
  [data-stacked] {
445
471
  overflow-x: visible;
446
472
  }
447
- [data-stacked] thead th {
473
+ [data-stacked] thead {
448
474
  position: absolute;
449
475
  width: 1px;
450
476
  height: 1px;
@@ -460,7 +486,8 @@
460
486
  [data-stacked] td {
461
487
  display: block;
462
488
  }
463
- [data-stacked] tr {
489
+ [data-stacked] tbody tr,
490
+ [data-stacked] tfoot tr {
464
491
  display: flex;
465
492
  flex-wrap: wrap;
466
493
  align-items: baseline;
@@ -487,7 +514,8 @@
487
514
  box-shadow: inset 3px 0 0 var(--dt-tone);
488
515
  }
489
516
  [data-stacked] td[data-role='title'] {
490
- flex: 1 1 0;
517
+ flex: 1 1 auto;
518
+ min-width: min(100%, 12rem);
491
519
  order: -2;
492
520
  font-weight: var(--fw-semibold);
493
521
  font-size: var(--fs-md);
@@ -58,6 +58,10 @@ declare function $$render<T>(): {
58
58
  responsive?: "scroll" | "stack";
59
59
  /** Width (px/em/rem) under which `stack` mode kicks in. */
60
60
  stackBelow?: string;
61
+ class?: string;
62
+ style?: string;
63
+ /** Class on the inner <table>. */
64
+ tableClass?: string;
61
65
  };
62
66
  exports: {};
63
67
  bindings: "";
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import type { ControlSize } from '../../size';
2
3
  // ───────────────────────────────────────────────────────────────────────
3
4
  // FilterSearchBar organism — a YouTrack-style structured search bar.
4
5
  //
@@ -31,7 +32,9 @@
31
32
  showHotkey = false,
32
33
  grow = false,
33
34
  onchange,
34
- onsubmit
35
+ onsubmit,
36
+ class: klass = '',
37
+ style: styleProp = '',
35
38
  }: {
36
39
  schema: Schema;
37
40
  /** The raw textual query (two-way bindable). */
@@ -46,7 +49,7 @@
46
49
  */
47
50
  autoQuote?: boolean;
48
51
  /** Forwarded to FilterInput. */
49
- size?: 'sm' | 'md';
52
+ size?: ControlSize;
50
53
  shape?: 'square' | 'pill';
51
54
  surface?: 'base' | 'raised' | 'sunken';
52
55
  hotkey?: string;
@@ -56,6 +59,8 @@
56
59
  onchange?: (query: Query, raw: string) => void;
57
60
  /** Fires on Enter / clear / chip-remove with the raw query string. */
58
61
  onsubmit?: (value: string) => void;
62
+ class?: string;
63
+ style?: string;
59
64
  } = $props();
60
65
 
61
66
  function labelFor(fieldName: string): string {
@@ -76,6 +81,8 @@
76
81
  {grow}
77
82
  {onchange}
78
83
  {onsubmit}
84
+ class={klass}
85
+ style={styleProp}
79
86
  >
80
87
  {#snippet below({ filters: chips, text, remove })}
81
88
  {#if showChips && (chips.length || text)}
@@ -1,3 +1,4 @@
1
+ import type { ControlSize } from '../../size';
1
2
  import type { Query } from '../../query/ast';
2
3
  import { type Schema } from '../../query/schema';
3
4
  type $$ComponentProps = {
@@ -14,7 +15,7 @@ type $$ComponentProps = {
14
15
  */
15
16
  autoQuote?: boolean;
16
17
  /** Forwarded to FilterInput. */
17
- size?: 'sm' | 'md';
18
+ size?: ControlSize;
18
19
  shape?: 'square' | 'pill';
19
20
  surface?: 'base' | 'raised' | 'sunken';
20
21
  hotkey?: string;
@@ -24,6 +25,8 @@ type $$ComponentProps = {
24
25
  onchange?: (query: Query, raw: string) => void;
25
26
  /** Fires on Enter / clear / chip-remove with the raw query string. */
26
27
  onsubmit?: (value: string) => void;
28
+ class?: string;
29
+ style?: string;
27
30
  };
28
31
  declare const FilterSearchBar: import("svelte").Component<$$ComponentProps, {}, "value">;
29
32
  type FilterSearchBar = ReturnType<typeof FilterSearchBar>;