@dorsk/tsumikit 0.2.14 → 0.2.15

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.
@@ -7,8 +7,12 @@
7
7
  */
8
8
  export function autoresize(node, _value) {
9
9
  const resize = () => {
10
+ // A manual drag handle may set `min-height` as a user-chosen floor; grow
11
+ // with content but never collapse below it. Content always wins the lower
12
+ // bound, so dragging shorter than the text can't shrink past it.
13
+ const floor = parseFloat(node.style.minHeight) || 0;
10
14
  node.style.height = 'auto';
11
- node.style.height = `${node.scrollHeight}px`;
15
+ node.style.height = `${Math.max(node.scrollHeight, floor)}px`;
12
16
  };
13
17
  resize();
14
18
  node.addEventListener('input', resize);
@@ -8,8 +8,12 @@
8
8
  //
9
9
  // `resize` replaces the native (un-themeable) resize grip with our own drag
10
10
  // handle on the top or bottom edge, styled like the Modal/AppShell grips: a
11
- // centered pill that's a thicker portion of the border. `autoresize` wins —
12
- // content-driven sizing leaves no manual handle.
11
+ // centered pill that's a thicker portion of the border.
12
+ //
13
+ // With `autoresize`, only a `top` handle is offered and it sets a manual
14
+ // *floor* (min-height) rather than a fixed height: the textarea still grows
15
+ // with content, but dragging up reserves extra space. (A bottom handle makes
16
+ // no sense alongside content-driven sizing, so it's suppressed.)
13
17
  import type { HTMLTextareaAttributes } from 'svelte/elements';
14
18
  import { autoresize as autoresizeAction } from '../../autoresize';
15
19
 
@@ -17,8 +21,9 @@
17
21
  mono?: boolean;
18
22
  autoresize?: boolean;
19
23
  size?: 'sm' | 'md';
20
- /** Manual resize handle edge, or 'none' to disable. Ignored when
21
- * `autoresize` is set. Defaults to a bottom handle. */
24
+ /** Manual resize handle edge, or 'none' to disable. Defaults to a bottom
25
+ * handle. With `autoresize`, only `top` is honored and it drags a
26
+ * min-height floor (the textarea still grows with content). */
22
27
  resize?: 'none' | 'top' | 'bottom';
23
28
  /** Error state: danger border + aria-invalid (also styles if a consumer
24
29
  * sets aria-invalid directly). */
@@ -40,7 +45,9 @@
40
45
  ...rest
41
46
  }: Props = $props();
42
47
 
43
- const showHandle = $derived(!autoresize && resize !== 'none');
48
+ // With autoresize, only the top handle (a min-height floor) is meaningful.
49
+ const handleEdge = $derived(autoresize ? (resize === 'top' ? 'top' : 'none') : resize);
50
+ const showHandle = $derived(handleEdge !== 'none');
44
51
 
45
52
  // --- manual resize drag (mirrors AppShell/Modal: rAF-throttled pointer drag) ---
46
53
  let dragging = $state(false);
@@ -65,8 +72,16 @@
65
72
  rafId = 0;
66
73
  if (!el) return;
67
74
  // Top handle grows upward (drag up = taller), bottom grows downward.
68
- const delta = resize === 'top' ? startY - lastY : lastY - startY;
69
- el.style.height = `${Math.max(0, startH + delta)}px`;
75
+ const delta = handleEdge === 'top' ? startY - lastY : lastY - startY;
76
+ const next = Math.max(0, startH + delta);
77
+ if (autoresize) {
78
+ // Set a min-height floor and let the autoresize action re-measure
79
+ // (content still wins the lower bound). Dispatch input to re-run it.
80
+ el.style.minHeight = `${next}px`;
81
+ el.dispatchEvent(new Event('input'));
82
+ } else {
83
+ el.style.height = `${next}px`;
84
+ }
70
85
  });
71
86
  }
72
87
  function endDrag(e: PointerEvent) {
@@ -109,7 +124,7 @@
109
124
  {/if}
110
125
  {#if showHandle}
111
126
  <div
112
- class="resize-handle resize-{resize}"
127
+ class="resize-handle resize-{handleEdge}"
113
128
  onpointerdown={startDrag}
114
129
  onpointermove={onDrag}
115
130
  onpointerup={endDrag}
@@ -3,8 +3,9 @@ type Props = HTMLTextareaAttributes & {
3
3
  mono?: boolean;
4
4
  autoresize?: boolean;
5
5
  size?: 'sm' | 'md';
6
- /** Manual resize handle edge, or 'none' to disable. Ignored when
7
- * `autoresize` is set. Defaults to a bottom handle. */
6
+ /** Manual resize handle edge, or 'none' to disable. Defaults to a bottom
7
+ * handle. With `autoresize`, only `top` is honored and it drags a
8
+ * min-height floor (the textarea still grows with content). */
8
9
  resize?: 'none' | 'top' | 'bottom';
9
10
  /** Error state: danger border + aria-invalid (also styles if a consumer
10
11
  * sets aria-invalid directly). */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dorsk/tsumikit",
3
- "version": "0.2.14",
3
+ "version": "0.2.15",
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",