@dorsk/tsumikit 0.73.0 → 0.73.1

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
@@ -1033,6 +1033,10 @@ half the gap sideways, which stays inside the group's padding, so neighbours
1033
1033
  never overlap. `Input size="sm"` cannot host a pseudo-element and grows to 40px
1034
1034
  instead, as should any control that clips its own overflow:
1035
1035
  `min-height: var(--touch-target-compact)`.
1036
+ `Checkbox`, `Button variant="link"` and the `DataTable` sort button take the same
1037
+ slab; inside a DataTable it is clipped at the table's top edge. A `SegmentedControl`
1038
+ wider than its container switches to the `scroll` layout until it fits again
1039
+ (`wrap` wraps instead), and `block` segments ellipsise their labels.
1036
1040
 
1037
1041
  `--control-height-*` and `--box-*` are **required tokens**: a consumer that
1038
1042
  forks `variables.css` must keep them (Button `control`, Select `sm`, every `box`)
@@ -561,7 +561,8 @@
561
561
  overflow counts towards the scrollable overflow of every scroll ancestor, and
562
562
  in a row that half lies under the next sibling anyway. Growing further towards
563
563
  the start would let each slab cover the previous sibling's centre. Text
564
- buttons at size sm take the same shape sized by --touch-slab. */
564
+ buttons at size sm and link buttons take the same shape sized by
565
+ --touch-slab. */
565
566
  @media (pointer: coarse) {
566
567
  .btn-icon:not(.hit-compact),
567
568
  .btn-icon-inline:not(.hit-compact),
@@ -593,6 +594,16 @@
593
594
  inset-inline-start: var(--touch-slab);
594
595
  inset-inline-end: 0;
595
596
  }
597
+ .btn-link:not(.hit-compact) {
598
+ position: relative;
599
+ }
600
+ .btn-link:not(.hit-compact)::before {
601
+ content: '';
602
+ position: absolute;
603
+ inset-block: var(--touch-slab);
604
+ inset-inline-start: var(--touch-slab);
605
+ inset-inline-end: 0;
606
+ }
596
607
  }
597
608
 
598
609
  /* Two-state (toggle) buttons — e.g. an IconButton with `pressed`. Reacts to
@@ -141,6 +141,18 @@
141
141
  .checkbox:has(input:disabled) {
142
142
  cursor: not-allowed;
143
143
  }
144
+ @media (pointer: coarse) {
145
+ .checkbox {
146
+ position: relative;
147
+ }
148
+ .checkbox::before {
149
+ content: '';
150
+ position: absolute;
151
+ inset-block: var(--touch-slab);
152
+ inset-inline-start: var(--touch-slab);
153
+ inset-inline-end: 0;
154
+ }
155
+ }
144
156
  .sr-only {
145
157
  position: absolute;
146
158
  width: 1px;
@@ -207,7 +207,9 @@
207
207
  .tabbar :global(.tabbar-badge) {
208
208
  position: absolute;
209
209
  top: -0.4rem;
210
- left: calc(100% - 0.5rem);
210
+ left: calc(100% - 2px);
211
+ max-width: none;
212
+ padding-inline: 0.3rem;
211
213
  }
212
214
  @media (prefers-reduced-motion: reduce) {
213
215
  .tabbar-item {
@@ -24,6 +24,7 @@
24
24
  // browser-free keyboard model matches Tabs: ←/→ and Home/End move selection,
25
25
  // Space/Enter (or click) activate. `value` is bindable.
26
26
  import type { Snippet } from 'svelte';
27
+ import type { Attachment } from 'svelte/attachments';
27
28
  import Icon from '../atoms/Icon.svelte';
28
29
  import { formatSegmentCount } from './segmented-control-count.js';
29
30
  import { nextEnabledSegment } from './segmented-control-keyboard.js';
@@ -95,6 +96,26 @@
95
96
  );
96
97
  const segId = (val: string) => `${baseId}-${val}`;
97
98
 
99
+ // A pill wider than its container falls back to the `scroll` layout; the
100
+ // class is dropped again once the row fits so the touch slab stays unclipped.
101
+ let overflowing = $state(false);
102
+ const watchOverflow: Attachment<HTMLElement> = (node) => {
103
+ if (wrap || typeof ResizeObserver === 'undefined') {
104
+ overflowing = false;
105
+ return;
106
+ }
107
+ const check = () => {
108
+ overflowing = node.scrollWidth - node.clientWidth > 1;
109
+ };
110
+ const ro = new ResizeObserver(check);
111
+ ro.observe(node);
112
+ check();
113
+ return () => {
114
+ ro.disconnect();
115
+ overflowing = false;
116
+ };
117
+ };
118
+
98
119
  function select(val: string, focus = false) {
99
120
  if (options.find((o) => o.value === val)?.disabled) return;
100
121
  value = val;
@@ -127,8 +148,10 @@
127
148
  class:collapse-container={collapseLabels === 'container'}
128
149
  class:seg-scroll={scroll}
129
150
  class:seg-block={block}
151
+ class:seg-overflow={overflowing}
130
152
  data-tsu="SegmentedControl"
131
153
  {onkeydown}
154
+ {@attach watchOverflow}
132
155
  >
133
156
  {#each options as o (o.value)}
134
157
  {@const selected = value === o.value}
@@ -165,6 +188,8 @@
165
188
  background: var(--bg-elevated-2);
166
189
  border: 1px solid var(--border);
167
190
  border-radius: var(--r-pill);
191
+ max-width: 100%;
192
+ min-width: 0;
168
193
  }
169
194
  .seg-icon {
170
195
  border-radius: var(--r-md);
@@ -193,6 +218,19 @@
193
218
  .seg-scroll .seg-item {
194
219
  flex: none;
195
220
  }
221
+ .seg-overflow {
222
+ flex-wrap: nowrap;
223
+ overflow-x: auto;
224
+ overflow-y: hidden;
225
+ scrollbar-width: none;
226
+ -webkit-overflow-scrolling: touch;
227
+ }
228
+ .seg-overflow::-webkit-scrollbar {
229
+ display: none;
230
+ }
231
+ .seg-overflow .seg-item {
232
+ flex: none;
233
+ }
196
234
  .seg-block {
197
235
  display: flex;
198
236
  width: 100%;
@@ -200,6 +238,12 @@
200
238
  .seg-block .seg-item {
201
239
  flex: 1 1 0;
202
240
  justify-content: center;
241
+ min-width: 0;
242
+ }
243
+ .seg-block .seg-label {
244
+ min-width: 0;
245
+ overflow: hidden;
246
+ text-overflow: ellipsis;
203
247
  }
204
248
 
205
249
  .seg-item {
@@ -18,6 +18,10 @@
18
18
  * a drag that is more vertical than horizontal is left to the scroller.
19
19
  * The same actions are reachable as visually hidden buttons for keyboard
20
20
  * and screen-reader users.
21
+ *
22
+ * On touch the browser implicitly captures the pointer on the element under
23
+ * the finger; taking capture on the row makes that child fire a bubbling
24
+ * `lostpointercapture`, which must not be read as a cancel.
21
25
  */
22
26
  import type { Snippet } from 'svelte';
23
27
  import type { HTMLAttributes } from 'svelte/elements';
@@ -162,8 +166,8 @@
162
166
  e.preventDefault();
163
167
  }
164
168
 
165
- function onlostpointercapture() {
166
- if (pointerId === null) return;
169
+ function onlostpointercapture(e: PointerEvent) {
170
+ if (pointerId === null || e.pointerId !== pointerId || e.target !== e.currentTarget) return;
167
171
  pointerId = null;
168
172
  locked = null;
169
173
  dragging = false;
@@ -13,6 +13,10 @@ export interface SwipeAction {
13
13
  * a drag that is more vertical than horizontal is left to the scroller.
14
14
  * The same actions are reachable as visually hidden buttons for keyboard
15
15
  * and screen-reader users.
16
+ *
17
+ * On touch the browser implicitly captures the pointer on the element under
18
+ * the finger; taking capture on the row makes that child fire a bubbling
19
+ * `lostpointercapture`, which must not be read as a cancel.
16
20
  */
17
21
  import type { Snippet } from 'svelte';
18
22
  import type { HTMLAttributes } from 'svelte/elements';
@@ -503,6 +503,18 @@
503
503
  .dt-actions {
504
504
  opacity: 1;
505
505
  }
506
+ /* Sort slab: inline-end stays at 0 so it never reaches the next header
507
+ cell, and the inline-start reach stays inside the cell's own padding. */
508
+ .dt-sort {
509
+ position: relative;
510
+ }
511
+ .dt-sort::before {
512
+ content: '';
513
+ position: absolute;
514
+ inset-block: var(--touch-slab);
515
+ inset-inline-start: var(--touch-slab);
516
+ inset-inline-end: 0;
517
+ }
506
518
  }
507
519
  @container (max-width: 30rem) {
508
520
  [data-hide-below='sm'] {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dorsk/tsumikit",
3
- "version": "0.73.0",
3
+ "version": "0.73.1",
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",