@dorsk/tsumikit 0.2.14 → 0.2.16
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/dist/autoresize.js +5 -1
- package/dist/components/atoms/Textarea.svelte +23 -8
- package/dist/components/atoms/Textarea.svelte.d.ts +3 -2
- package/dist/components/molecules/FileButton.svelte +12 -8
- package/dist/components/molecules/FileButton.svelte.d.ts +6 -3
- package/dist/components/molecules/IconButton.svelte +21 -3
- package/dist/components/molecules/IconButton.svelte.d.ts +4 -1
- package/package.json +1 -1
package/dist/autoresize.js
CHANGED
|
@@ -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.
|
|
12
|
-
//
|
|
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.
|
|
21
|
-
* `autoresize` is
|
|
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
|
-
|
|
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 =
|
|
69
|
-
|
|
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-{
|
|
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.
|
|
7
|
-
* `autoresize` is
|
|
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). */
|
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
// File-picker button. A real <input type="file"> visually hidden inside a
|
|
3
3
|
// <label> styled as a button — so it's keyboard-focusable and works with zero
|
|
4
4
|
// JS to open the dialog. Emits the chosen files via `onfiles`. Dependency-free.
|
|
5
|
-
import Icon
|
|
5
|
+
import Icon from '../atoms/Icon.svelte';
|
|
6
6
|
import type { IconName } from '../atoms/Icon.svelte';
|
|
7
7
|
|
|
8
8
|
let {
|
|
9
9
|
onfiles,
|
|
10
10
|
label = 'Choose file',
|
|
11
|
-
icon
|
|
11
|
+
icon,
|
|
12
|
+
emoji = '📎',
|
|
12
13
|
iconOnly = false,
|
|
13
14
|
accept,
|
|
14
15
|
multiple = false,
|
|
@@ -19,9 +20,12 @@
|
|
|
19
20
|
}: {
|
|
20
21
|
onfiles: (files: File[]) => void;
|
|
21
22
|
label?: string;
|
|
22
|
-
/** A registered glyph name (rendered as SVG)
|
|
23
|
-
*
|
|
24
|
-
icon?: IconName
|
|
23
|
+
/** A registered glyph name (rendered as SVG). Use `emoji` for an
|
|
24
|
+
* off-registry glyph. */
|
|
25
|
+
icon?: IconName;
|
|
26
|
+
/** Off-registry glyph such as an emoji, rendered as text as-is. Defaults to
|
|
27
|
+
* the 📎 paperclip emoji; ignored when `icon` is set. */
|
|
28
|
+
emoji?: string;
|
|
25
29
|
/** Hide the label visually, showing only the icon. The label is kept for
|
|
26
30
|
* assistive tech (and used as the accessible name). */
|
|
27
31
|
iconOnly?: boolean;
|
|
@@ -61,10 +65,10 @@
|
|
|
61
65
|
{disabled}
|
|
62
66
|
onchange={onchange}
|
|
63
67
|
/>
|
|
64
|
-
{#if
|
|
68
|
+
{#if icon}
|
|
65
69
|
<Icon name={icon} />
|
|
66
|
-
{:else}
|
|
67
|
-
<span class="emoji" aria-hidden="true">{
|
|
70
|
+
{:else if emoji}
|
|
71
|
+
<span class="emoji" aria-hidden="true">{emoji}</span>
|
|
68
72
|
{/if}
|
|
69
73
|
<span class:sr-only={iconOnly}>{label}</span>
|
|
70
74
|
</label>
|
|
@@ -2,9 +2,12 @@ import type { IconName } from '../atoms/Icon.svelte';
|
|
|
2
2
|
type $$ComponentProps = {
|
|
3
3
|
onfiles: (files: File[]) => void;
|
|
4
4
|
label?: string;
|
|
5
|
-
/** A registered glyph name (rendered as SVG)
|
|
6
|
-
*
|
|
7
|
-
icon?: IconName
|
|
5
|
+
/** A registered glyph name (rendered as SVG). Use `emoji` for an
|
|
6
|
+
* off-registry glyph. */
|
|
7
|
+
icon?: IconName;
|
|
8
|
+
/** Off-registry glyph such as an emoji, rendered as text as-is. Defaults to
|
|
9
|
+
* the 📎 paperclip emoji; ignored when `icon` is set. */
|
|
10
|
+
emoji?: string;
|
|
8
11
|
/** Hide the label visually, showing only the icon. The label is kept for
|
|
9
12
|
* assistive tech (and used as the accessible name). */
|
|
10
13
|
iconOnly?: boolean;
|
|
@@ -6,8 +6,11 @@
|
|
|
6
6
|
import type { IconName } from '../atoms/Icon.svelte';
|
|
7
7
|
|
|
8
8
|
type IconButtonProps = HTMLButtonAttributes & {
|
|
9
|
-
/** Named glyph from the registry
|
|
9
|
+
/** Named glyph from the registry (rendered as SVG). */
|
|
10
10
|
icon?: IconName;
|
|
11
|
+
/** Off-registry glyph such as an emoji, rendered as text as-is. Use instead
|
|
12
|
+
* of `icon` when the glyph isn't in the registry. */
|
|
13
|
+
emoji?: string;
|
|
11
14
|
/** Raw SVG markup (24×24 viewBox) — overrides `icon`. Pass a
|
|
12
15
|
* lucide-svelte component's contents to render any off-registry icon. */
|
|
13
16
|
children?: Snippet;
|
|
@@ -32,6 +35,7 @@
|
|
|
32
35
|
|
|
33
36
|
let {
|
|
34
37
|
icon,
|
|
38
|
+
emoji,
|
|
35
39
|
children,
|
|
36
40
|
label,
|
|
37
41
|
title = label,
|
|
@@ -50,7 +54,7 @@
|
|
|
50
54
|
</script>
|
|
51
55
|
|
|
52
56
|
<!-- Composition: the icon-only button is a Button (canonical control styling)
|
|
53
|
-
in its icon variant, wrapping an Icon. -->
|
|
57
|
+
in its icon variant, wrapping an Icon (or a text emoji glyph). -->
|
|
54
58
|
<Button
|
|
55
59
|
{...rest}
|
|
56
60
|
{variant}
|
|
@@ -68,7 +72,21 @@
|
|
|
68
72
|
>
|
|
69
73
|
{#if children}
|
|
70
74
|
<Icon {size}>{@render children()}</Icon>
|
|
71
|
-
{:else}
|
|
75
|
+
{:else if emoji}
|
|
76
|
+
<span class="emoji" style="font-size: {size * 1.35}px" aria-hidden="true">{emoji}</span>
|
|
77
|
+
{:else if icon}
|
|
72
78
|
<Icon name={icon} {size} />
|
|
73
79
|
{/if}
|
|
74
80
|
</Button>
|
|
81
|
+
|
|
82
|
+
<style>
|
|
83
|
+
/* Off-registry glyph (emoji) rendered as text rather than an SVG. Sized off the
|
|
84
|
+
`size` prop (×1.35, since an emoji reads small next to an SVG glyph of the
|
|
85
|
+
same px) and centered so it shares the button's tap target. */
|
|
86
|
+
.emoji {
|
|
87
|
+
display: inline-flex;
|
|
88
|
+
align-items: center;
|
|
89
|
+
justify-content: center;
|
|
90
|
+
line-height: 1;
|
|
91
|
+
}
|
|
92
|
+
</style>
|
|
@@ -2,8 +2,11 @@ import type { Snippet } from 'svelte';
|
|
|
2
2
|
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
3
3
|
import type { IconName } from '../atoms/Icon.svelte';
|
|
4
4
|
type IconButtonProps = HTMLButtonAttributes & {
|
|
5
|
-
/** Named glyph from the registry
|
|
5
|
+
/** Named glyph from the registry (rendered as SVG). */
|
|
6
6
|
icon?: IconName;
|
|
7
|
+
/** Off-registry glyph such as an emoji, rendered as text as-is. Use instead
|
|
8
|
+
* of `icon` when the glyph isn't in the registry. */
|
|
9
|
+
emoji?: string;
|
|
7
10
|
/** Raw SVG markup (24×24 viewBox) — overrides `icon`. Pass a
|
|
8
11
|
* lucide-svelte component's contents to render any off-registry icon. */
|
|
9
12
|
children?: Snippet;
|
package/package.json
CHANGED