@dorsk/tsumikit 0.13.1 → 0.14.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
@@ -108,6 +108,27 @@ proportionally with no code. That's the recommended path for magnification.
108
108
  text tokens only) for reading-dense apps that want larger body text while
109
109
  keeping chrome compact. It isn't wired into AppShell or any default.
110
110
 
111
+ ### Toolbar control heights
112
+
113
+ `size="sm"` is the shared compact-toolbar contract for `Button`, `Popover`
114
+ triggers, and `SegmentedControl`. Each renders an exact
115
+ `--control-height-compact` outer box, so mixed controls share a vertical center:
116
+
117
+ ```svelte
118
+ <SegmentedControl size="sm" {options} bind:value />
119
+ <Popover size="sm" variant="default" label="More actions">
120
+ {#snippet trigger()}More{/snippet}
121
+ <!-- panel content -->
122
+ </Popover>
123
+ <Button size="sm">Apply</Button>
124
+ ```
125
+
126
+ `Popover` owns that single semantic trigger button; pass `variant`, `tone`,
127
+ `size`, `control`, `block`, and `disabled` directly instead of rendering a
128
+ `Button` inside its `trigger` snippet. Omitting these props preserves the
129
+ original ghost icon-button default. Use `control` on `Button` or `Popover` when
130
+ the roomier shared `--control-height` composer contract is required.
131
+
111
132
  ## Built on the platform
112
133
 
113
134
  Interactive components lean on modern web features rather than reimplementing
@@ -89,7 +89,7 @@
89
89
  justify-content: center;
90
90
  gap: var(--sp-2);
91
91
  padding: var(--sp-2) var(--sp-4);
92
- min-height: 2.5rem;
92
+ min-height: var(--control-height-default);
93
93
  border: 1px solid var(--border-strong);
94
94
  border-radius: var(--r-md);
95
95
  background: var(--surface);
@@ -137,12 +137,13 @@
137
137
  border-color: transparent;
138
138
  }
139
139
  .btn-sm {
140
- min-height: 2rem;
140
+ height: var(--control-height-compact);
141
+ min-height: var(--control-height-compact);
141
142
  padding: var(--sp-1) var(--sp-3);
142
143
  font-size: var(--fs-xs);
143
144
  }
144
145
  .btn-lg {
145
- min-height: 3rem;
146
+ min-height: var(--control-height-large);
146
147
  padding: var(--sp-3) var(--sp-5);
147
148
  font-size: var(--fs-base);
148
149
  }
@@ -1,7 +1,8 @@
1
1
  <script lang="ts">
2
- import type { Snippet } from 'svelte';
2
+ import { onDestroy, type Snippet } from 'svelte';
3
3
  import { browser } from '../../env';
4
4
  import Icon from '../atoms/Icon.svelte';
5
+ import { createFrameBatcher } from './resizable-panel-frame.js';
5
6
  import { parseStoredCollapsed, parseStoredWidth } from './resizable-panel-persistence';
6
7
 
7
8
  let {
@@ -89,6 +90,14 @@
89
90
  if (persist) persistWidth();
90
91
  }
91
92
 
93
+ const pointerWidths = createFrameBatcher<number>(
94
+ (callback) => requestAnimationFrame(callback),
95
+ (handle) => cancelAnimationFrame(handle),
96
+ (nextWidth) => setWidth(nextWidth, false)
97
+ );
98
+
99
+ onDestroy(() => pointerWidths.discard());
100
+
92
101
  function toggle() {
93
102
  collapsed = !collapsed;
94
103
  if (browser && widthKey && persistCollapsed) {
@@ -109,18 +118,22 @@
109
118
 
110
119
  function resize(event: PointerEvent) {
111
120
  if (!resizing) return;
112
- setWidth(widthFromPointer(event.clientX), false);
121
+ pointerWidths.schedule(widthFromPointer(event.clientX));
113
122
  }
114
123
 
115
124
  function finishResize(event: PointerEvent) {
116
125
  if (!resizing) return;
126
+ const finalWidth = Math.round(
127
+ Math.max(boundedMin, Math.min(widthFromPointer(event.clientX), boundedMax))
128
+ );
129
+ pointerWidths.flush(finalWidth);
117
130
  resizing = false;
118
131
  try {
119
132
  (event.currentTarget as HTMLElement).releasePointerCapture(event.pointerId);
120
133
  } catch {
121
134
  // Pointer capture may already have been released by the browser.
122
135
  }
123
- persistWidth();
136
+ if (browser && widthKey) localStorage.setItem(widthKey, String(finalWidth));
124
137
  }
125
138
 
126
139
  function resizeWithKeyboard(event: KeyboardEvent) {
@@ -191,7 +204,6 @@
191
204
  onclick={toggle}
192
205
  >
193
206
  <Icon name={toggleIcon} size={20} />
194
- {#if !collapsed}<span>{toggleLabel}</span>{/if}
195
207
  </button>
196
208
  </aside>
197
209
 
@@ -200,7 +212,6 @@
200
212
 
201
213
  <style>
202
214
  .panel-layout {
203
- --panel-collapsed-width: var(--control-height);
204
215
  position: relative;
205
216
  display: grid;
206
217
  grid-template-columns: var(--panel-current-width) minmax(0, 1fr);
@@ -216,7 +227,7 @@
216
227
  --panel-current-width: var(--panel-width);
217
228
  }
218
229
  .panel-layout.collapsed {
219
- --panel-current-width: var(--panel-collapsed-width);
230
+ --panel-current-width: 0px;
220
231
  }
221
232
  .panel {
222
233
  position: relative;
@@ -250,23 +261,28 @@
250
261
  overflow: auto;
251
262
  }
252
263
  .collapse-control {
264
+ position: absolute;
265
+ right: calc(-1 * var(--control-height));
266
+ bottom: var(--sp-3);
267
+ z-index: 3;
253
268
  display: flex;
254
269
  align-items: center;
255
270
  justify-content: center;
256
- gap: var(--sp-2);
257
- width: 100%;
258
- min-height: var(--control-height);
259
- margin-top: auto;
260
- padding: var(--sp-2) var(--sp-3);
271
+ width: var(--control-height);
272
+ height: var(--control-height);
273
+ padding: 0;
261
274
  color: var(--text-muted);
262
275
  font: inherit;
263
- font-size: var(--fs-sm);
264
- font-weight: var(--fw-medium);
265
276
  background: var(--bg-elevated-2);
266
- border: 0;
267
- border-top: 1px solid var(--border);
277
+ border: 1px solid var(--border);
278
+ border-radius: var(--r-sm);
279
+ box-shadow: var(--shadow-sm);
268
280
  cursor: pointer;
269
281
  }
282
+ .right .collapse-control {
283
+ right: auto;
284
+ left: calc(-1 * var(--control-height));
285
+ }
270
286
  .collapse-control:hover {
271
287
  color: var(--text);
272
288
  background: var(--surface);
@@ -280,7 +296,7 @@
280
296
  position: absolute;
281
297
  top: 0;
282
298
  right: -6px;
283
- bottom: var(--control-height);
299
+ bottom: 0;
284
300
  z-index: 2;
285
301
  width: 12px;
286
302
  padding: 0;
@@ -317,7 +333,7 @@
317
333
  }
318
334
 
319
335
  /* In a narrow host, the expanded panel overlays the main area instead of
320
- squeezing it. The bottom control remains visible as a persistent rail. */
336
+ squeezing it. */
321
337
  @container resizable-panel (max-width: 40rem) {
322
338
  .panel-layout {
323
339
  display: block;
@@ -339,11 +355,6 @@
339
355
  }
340
356
  .main {
341
357
  height: 100%;
342
- padding-left: var(--panel-collapsed-width);
343
- }
344
- .right .main {
345
- padding-right: var(--panel-collapsed-width);
346
- padding-left: 0;
347
358
  }
348
359
  }
349
360
 
@@ -1,4 +1,4 @@
1
- import type { Snippet } from 'svelte';
1
+ import { type Snippet } from 'svelte';
2
2
  type $$ComponentProps = {
3
3
  /** Content shown while the panel is expanded. */
4
4
  panel: Snippet;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Coalesce high-frequency values into one update per animation frame while
3
+ * still allowing pointer release to synchronously apply the final value.
4
+ *
5
+ * @template T
6
+ * @param {(callback: FrameRequestCallback) => number} requestFrame
7
+ * @param {(handle: number) => void} cancelFrame
8
+ * @param {(value: T) => void} apply
9
+ */
10
+ export function createFrameBatcher<T>(requestFrame: (callback: FrameRequestCallback) => number, cancelFrame: (handle: number) => void, apply: (value: T) => void): {
11
+ /** @param {T} value */
12
+ schedule(value: T): void;
13
+ /** @param {T} value */
14
+ flush(value: T): void;
15
+ discard(): void;
16
+ };
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Coalesce high-frequency values into one update per animation frame while
3
+ * still allowing pointer release to synchronously apply the final value.
4
+ *
5
+ * @template T
6
+ * @param {(callback: FrameRequestCallback) => number} requestFrame
7
+ * @param {(handle: number) => void} cancelFrame
8
+ * @param {(value: T) => void} apply
9
+ */
10
+ export function createFrameBatcher(requestFrame, cancelFrame, apply) {
11
+ /** @type {number | undefined} */
12
+ let frame;
13
+ /** @type {T | undefined} */
14
+ let pending;
15
+ let scheduled = false;
16
+ let generation = 0;
17
+
18
+ /** Apply the latest queued value, invalidating any stale frame callback. */
19
+ function applyPending() {
20
+ if (pending === undefined) return;
21
+ const value = pending;
22
+ pending = undefined;
23
+ apply(value);
24
+ }
25
+
26
+ return {
27
+ /** @param {T} value */
28
+ schedule(value) {
29
+ pending = value;
30
+ if (scheduled) return;
31
+
32
+ scheduled = true;
33
+ const token = ++generation;
34
+ frame = requestFrame(() => {
35
+ if (!scheduled || token !== generation) return;
36
+ scheduled = false;
37
+ frame = undefined;
38
+ applyPending();
39
+ });
40
+ },
41
+
42
+ /** @param {T} value */
43
+ flush(value) {
44
+ pending = value;
45
+ if (scheduled) {
46
+ scheduled = false;
47
+ generation += 1;
48
+ if (frame !== undefined) cancelFrame(frame);
49
+ frame = undefined;
50
+ }
51
+ applyPending();
52
+ },
53
+
54
+ discard() {
55
+ pending = undefined;
56
+ if (!scheduled) return;
57
+ scheduled = false;
58
+ generation += 1;
59
+ if (frame !== undefined) cancelFrame(frame);
60
+ frame = undefined;
61
+ },
62
+ };
63
+ }
@@ -12,6 +12,9 @@
12
12
  import { place } from '../../floating';
13
13
 
14
14
  type Placement = 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end';
15
+ type TriggerVariant = 'default' | 'primary' | 'ghost' | 'danger';
16
+ type TriggerTone = 'none' | 'accent' | 'info' | 'warn' | 'danger';
17
+ type TriggerSize = 'sm' | 'md' | 'lg';
15
18
 
16
19
  let {
17
20
  placement = 'bottom-start',
@@ -21,6 +24,12 @@
21
24
  children,
22
25
  triggerClass = '',
23
26
  bare = false,
27
+ variant,
28
+ tone = 'none',
29
+ size,
30
+ control = false,
31
+ block = false,
32
+ disabled = false,
24
33
  onopen,
25
34
  onclose
26
35
  }: {
@@ -38,10 +47,23 @@
38
47
  /** Drop the default ghost-icon chrome so the trigger is an unstyled button
39
48
  * you fully own (pair with `triggerClass`). */
40
49
  bare?: boolean;
50
+ /** Button-compatible trigger chrome. Omitting every chrome prop preserves
51
+ * the original ghost icon-button trigger. */
52
+ variant?: TriggerVariant;
53
+ tone?: TriggerTone;
54
+ size?: TriggerSize;
55
+ /** Use the shared `--control-height` toolbar/composer contract. */
56
+ control?: boolean;
57
+ block?: boolean;
58
+ disabled?: boolean;
41
59
  onopen?: () => void;
42
60
  onclose?: () => void;
43
61
  } = $props();
44
62
 
63
+ const canonicalChrome = $derived(
64
+ variant !== undefined || tone !== 'none' || size !== undefined || control || block
65
+ );
66
+
45
67
  const id = `pop-${Math.random().toString(36).slice(2, 8)}`;
46
68
  let triggerEl = $state<HTMLButtonElement | null>(null);
47
69
  let panelEl = $state<HTMLDivElement | null>(null);
@@ -70,8 +92,21 @@
70
92
  type="button"
71
93
  class="pop-trigger {triggerClass}"
72
94
  class:bare
95
+ class:canonical={canonicalChrome}
96
+ class:trigger-primary={variant === 'primary'}
97
+ class:trigger-ghost={variant === 'ghost'}
98
+ class:trigger-danger={variant === 'danger'}
99
+ class:trigger-sm={size === 'sm'}
100
+ class:trigger-lg={size === 'lg'}
101
+ class:trigger-control={control}
102
+ class:trigger-block={block}
103
+ class:trigger-tone-accent={tone === 'accent'}
104
+ class:trigger-tone-info={tone === 'info'}
105
+ class:trigger-tone-warn={tone === 'warn'}
106
+ class:trigger-tone-danger={tone === 'danger'}
73
107
  popovertarget={id}
74
108
  aria-label={label}
109
+ {disabled}
75
110
  >
76
111
  {@render trigger()}
77
112
  </button>
@@ -107,18 +142,114 @@
107
142
  background 0.12s var(--ease),
108
143
  border-color 0.12s var(--ease);
109
144
  }
110
- .pop-trigger:hover {
145
+ .pop-trigger:hover:not(:disabled) {
146
+ background: var(--bg-elevated-2);
147
+ }
148
+ /* Supplying canonical chrome props opts into the same dimensions, variants
149
+ and state tones as Button while keeping this one native trigger element. */
150
+ .pop-trigger.canonical {
151
+ min-width: 0;
152
+ min-height: var(--control-height-default);
153
+ padding: var(--sp-2) var(--sp-4);
154
+ border-color: var(--border-strong);
155
+ background: var(--surface);
156
+ font-weight: var(--fw-medium);
157
+ font-size: var(--fs-sm);
158
+ line-height: 1;
159
+ user-select: none;
160
+ white-space: nowrap;
161
+ }
162
+ .pop-trigger.canonical:hover:not(:disabled) {
163
+ border-color: var(--accent);
164
+ background: var(--surface);
165
+ }
166
+ .pop-trigger.trigger-primary {
167
+ background: var(--accent);
168
+ border-color: var(--accent);
169
+ color: var(--text-on-accent);
170
+ font-weight: var(--fw-semibold);
171
+ }
172
+ .pop-trigger.trigger-primary:hover:not(:disabled) {
173
+ background: var(--accent);
174
+ border-color: var(--accent);
175
+ filter: brightness(1.08);
176
+ }
177
+ .pop-trigger.trigger-ghost {
178
+ background: transparent;
179
+ border-color: transparent;
180
+ }
181
+ .pop-trigger.trigger-ghost:hover:not(:disabled) {
111
182
  background: var(--bg-elevated-2);
183
+ border-color: transparent;
184
+ }
185
+ .pop-trigger.trigger-danger {
186
+ color: var(--danger);
187
+ border-color: color-mix(in srgb, var(--danger) 50%, var(--border));
188
+ }
189
+ .pop-trigger.trigger-danger:hover:not(:disabled) {
190
+ background: color-mix(in srgb, var(--danger) 14%, transparent);
191
+ border-color: var(--danger);
192
+ }
193
+ .pop-trigger.trigger-sm {
194
+ height: var(--control-height-compact);
195
+ min-height: var(--control-height-compact);
196
+ padding: var(--sp-1) var(--sp-3);
197
+ font-size: var(--fs-xs);
198
+ }
199
+ .pop-trigger.trigger-lg {
200
+ min-height: var(--control-height-large);
201
+ padding: var(--sp-3) var(--sp-5);
202
+ font-size: var(--fs-base);
203
+ }
204
+ .pop-trigger.trigger-control {
205
+ height: var(--control-height);
206
+ min-height: var(--control-height);
207
+ padding: 0 var(--sp-3);
208
+ }
209
+ .pop-trigger.trigger-block {
210
+ width: 100%;
211
+ }
212
+ .pop-trigger.trigger-tone-accent {
213
+ --pop-trigger-tone: var(--accent);
214
+ }
215
+ .pop-trigger.trigger-tone-info {
216
+ --pop-trigger-tone: var(--info);
217
+ }
218
+ .pop-trigger.trigger-tone-warn {
219
+ --pop-trigger-tone: var(--warn);
220
+ }
221
+ .pop-trigger.trigger-tone-danger {
222
+ --pop-trigger-tone: var(--danger);
223
+ }
224
+ .pop-trigger.trigger-tone-accent,
225
+ .pop-trigger.trigger-tone-info,
226
+ .pop-trigger.trigger-tone-warn,
227
+ .pop-trigger.trigger-tone-danger {
228
+ color: var(--pop-trigger-tone);
229
+ border-color: color-mix(in srgb, var(--pop-trigger-tone) 50%, var(--border));
230
+ }
231
+ .pop-trigger.trigger-tone-accent:hover:not(:disabled),
232
+ .pop-trigger.trigger-tone-info:hover:not(:disabled),
233
+ .pop-trigger.trigger-tone-warn:hover:not(:disabled),
234
+ .pop-trigger.trigger-tone-danger:hover:not(:disabled) {
235
+ background: color-mix(in srgb, var(--pop-trigger-tone) 14%, transparent);
236
+ border-color: var(--pop-trigger-tone);
237
+ }
238
+ .pop-trigger:disabled {
239
+ opacity: 0.45;
240
+ cursor: not-allowed;
112
241
  }
113
242
  /* `bare`: strip the chrome down to a plain button the consumer styles. */
114
243
  .pop-trigger.bare {
244
+ height: auto;
115
245
  min-height: 0;
116
246
  min-width: 0;
247
+ width: auto;
117
248
  padding: 0;
118
249
  border: 0;
119
250
  background: none;
120
251
  }
121
- .pop-trigger.bare:hover {
252
+ .pop-trigger.bare:hover:not(:disabled) {
122
253
  background: none;
123
254
  }
124
255
  .pop-panel {
@@ -1,5 +1,8 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  type Placement = 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end';
3
+ type TriggerVariant = 'default' | 'primary' | 'ghost' | 'danger';
4
+ type TriggerTone = 'none' | 'accent' | 'info' | 'warn' | 'danger';
5
+ type TriggerSize = 'sm' | 'md' | 'lg';
3
6
  type $$ComponentProps = {
4
7
  placement?: Placement;
5
8
  gap?: number;
@@ -15,6 +18,15 @@ type $$ComponentProps = {
15
18
  /** Drop the default ghost-icon chrome so the trigger is an unstyled button
16
19
  * you fully own (pair with `triggerClass`). */
17
20
  bare?: boolean;
21
+ /** Button-compatible trigger chrome. Omitting every chrome prop preserves
22
+ * the original ghost icon-button trigger. */
23
+ variant?: TriggerVariant;
24
+ tone?: TriggerTone;
25
+ size?: TriggerSize;
26
+ /** Use the shared `--control-height` toolbar/composer contract. */
27
+ control?: boolean;
28
+ block?: boolean;
29
+ disabled?: boolean;
18
30
  onopen?: () => void;
19
31
  onclose?: () => void;
20
32
  };
@@ -22,6 +22,7 @@
22
22
  // Space/Enter (or click) activate. `value` is bindable.
23
23
  import type { Snippet } from 'svelte';
24
24
  import Icon from '../atoms/Icon.svelte';
25
+ import { nextEnabledSegment } from './segmented-control-keyboard.js';
25
26
 
26
27
  let {
27
28
  options,
@@ -68,25 +69,10 @@
68
69
  );
69
70
  }
70
71
  }
71
- // Step to the next non-disabled option in a direction, wrapping around.
72
- function step(from: number, dir: 1 | -1): number {
73
- const n = options.length;
74
- for (let k = 1; k <= n; k++) {
75
- const j = (((from + dir * k) % n) + n) % n;
76
- if (!options[j].disabled) return j;
77
- }
78
- return from;
79
- }
80
72
  function onkeydown(e: KeyboardEvent) {
81
73
  const i = options.findIndex((o) => o.value === value);
82
- if (i < 0) return;
83
- let next = i;
84
- if (e.key === 'ArrowRight' || e.key === 'ArrowDown') next = step(i, 1);
85
- else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') next = step(i, -1);
86
- else if (e.key === 'Home') next = options.findIndex((o) => !o.disabled);
87
- else if (e.key === 'End')
88
- next = options.length - 1 - [...options].reverse().findIndex((o) => !o.disabled);
89
- else return;
74
+ const next = nextEnabledSegment(options, i, e.key);
75
+ if (next === undefined) return;
90
76
  e.preventDefault();
91
77
  select(options[next].value, true);
92
78
  }
@@ -165,6 +151,15 @@
165
151
  padding: 0.2rem var(--sp-2);
166
152
  font-size: var(--fs-xs);
167
153
  }
154
+ /* Compact toolbar contract: the outer interactive box exactly matches
155
+ Button size="sm" and Popover size="sm". */
156
+ .seg-sm {
157
+ height: var(--control-height-compact);
158
+ min-height: var(--control-height-compact);
159
+ }
160
+ .seg-sm .seg-item {
161
+ height: 100%;
162
+ }
168
163
  .seg-icon .seg-item {
169
164
  gap: 0;
170
165
  padding: 0.35rem;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Resolve a SegmentedControl navigation key to the next selectable option.
3
+ * Arrow navigation wraps and skips disabled options; Home/End jump to the
4
+ * first/last selectable option. Unhandled keys do not change selection.
5
+ *
6
+ * @param {{ disabled?: boolean }[]} options
7
+ * @param {number} current
8
+ * @param {string} key
9
+ * @returns {number | undefined}
10
+ */
11
+ export function nextEnabledSegment(options: {
12
+ disabled?: boolean;
13
+ }[], current: number, key: string): number | undefined;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Resolve a SegmentedControl navigation key to the next selectable option.
3
+ * Arrow navigation wraps and skips disabled options; Home/End jump to the
4
+ * first/last selectable option. Unhandled keys do not change selection.
5
+ *
6
+ * @param {{ disabled?: boolean }[]} options
7
+ * @param {number} current
8
+ * @param {string} key
9
+ * @returns {number | undefined}
10
+ */
11
+ export function nextEnabledSegment(options, current, key) {
12
+ if (current < 0 || current >= options.length) return undefined;
13
+ if (key === 'Home') {
14
+ const first = options.findIndex((option) => !option.disabled);
15
+ return first >= 0 ? first : current;
16
+ }
17
+ if (key === 'End') {
18
+ const fromEnd = [...options].reverse().findIndex((option) => !option.disabled);
19
+ return fromEnd >= 0 ? options.length - 1 - fromEnd : current;
20
+ }
21
+
22
+ let direction;
23
+ if (key === 'ArrowRight' || key === 'ArrowDown') direction = 1;
24
+ else if (key === 'ArrowLeft' || key === 'ArrowUp') direction = -1;
25
+ else return undefined;
26
+
27
+ for (let offset = 1; offset <= options.length; offset += 1) {
28
+ const next =
29
+ (((current + direction * offset) % options.length) + options.length) % options.length;
30
+ if (!options[next].disabled) return next;
31
+ }
32
+ return current;
33
+ }
@@ -161,8 +161,13 @@
161
161
  --shadow-md: 0 6px 20px rgba(0, 0, 0, 0.45);
162
162
  --shadow-lg: 0 12px 40px rgba(0, 0, 0, 0.55);
163
163
 
164
- /* ---- Controls (CCT-250 item 1) — shared height for icon buttons + inputs
165
- so toolbars/composers line up. Button's `control` variant consumes it. */
164
+ /* ---- Controls ----
165
+ `size="sm"` is the compact toolbar contract shared by Button, Popover and
166
+ SegmentedControl. `control` is the roomier uniform-height contract used by
167
+ Button and other composer controls. Both remain rem-based for zoom. */
168
+ --control-height-compact: 2rem;
169
+ --control-height-default: 2.5rem;
170
+ --control-height-large: 3rem;
166
171
  --control-height: 2.75rem;
167
172
 
168
173
  /* ---- Layout ---- */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dorsk/tsumikit",
3
- "version": "0.13.1",
3
+ "version": "0.14.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",
@@ -45,7 +45,7 @@
45
45
  "dev": "vite dev",
46
46
  "build": "vite build",
47
47
  "preview": "vite preview",
48
- "test": "node --test tests/resizable-panel-persistence.test.js",
48
+ "test": "node --test tests/*.test.js",
49
49
  "prepare": "svelte-kit sync && (lefthook install || true)",
50
50
  "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
51
51
  "lint": "biome check .",