@dorsk/tsumikit 0.2.0 → 0.2.2
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/components/atoms/Text.svelte +7 -0
- package/dist/components/layouts/AutoGrid.svelte +32 -2
- package/dist/components/layouts/AutoGrid.svelte.d.ts +1 -0
- package/dist/components/molecules/Truncate.svelte +55 -0
- package/dist/components/molecules/Truncate.svelte.d.ts +13 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/truncate.d.ts +15 -0
- package/dist/truncate.js +44 -0
- package/package.json +2 -1
|
@@ -118,7 +118,14 @@
|
|
|
118
118
|
.fs-2xl {
|
|
119
119
|
font-size: var(--fs-2xl);
|
|
120
120
|
}
|
|
121
|
+
/* `text-overflow: ellipsis` is a no-op on an inline box, so a truncated bare
|
|
122
|
+
<Text> (default as="span") would overrun its container instead of clipping.
|
|
123
|
+
inline-block gives it a block formatting context so the ellipsis applies,
|
|
124
|
+
while max-width keeps it from overflowing the parent; on block elements
|
|
125
|
+
(as="p"/"div") these are harmless. */
|
|
121
126
|
.truncate {
|
|
127
|
+
display: inline-block;
|
|
128
|
+
max-width: 100%;
|
|
122
129
|
overflow: hidden;
|
|
123
130
|
text-overflow: ellipsis;
|
|
124
131
|
white-space: nowrap;
|
|
@@ -4,13 +4,16 @@
|
|
|
4
4
|
// adapts to whatever space it's given (including inside a narrow column). This
|
|
5
5
|
// is the "columns just adapt to available space" layout from the AppShell
|
|
6
6
|
// demo, as a named component. `min` is the minimum column width, `gap` the
|
|
7
|
-
// gutter.
|
|
7
|
+
// gutter. `maxCols` optionally caps how many columns the grid will ever show:
|
|
8
|
+
// it raises the effective per-column minimum to the width N columns would
|
|
9
|
+
// need, so auto-fit can never pack more than N across.
|
|
8
10
|
import type { Snippet } from 'svelte';
|
|
9
11
|
|
|
10
12
|
let {
|
|
11
13
|
as = 'div',
|
|
12
14
|
min = '14rem',
|
|
13
15
|
gap = 'var(--sp-4)',
|
|
16
|
+
maxCols,
|
|
14
17
|
class: klass = '',
|
|
15
18
|
children,
|
|
16
19
|
...rest
|
|
@@ -18,13 +21,26 @@
|
|
|
18
21
|
as?: 'div' | 'section' | 'ul' | 'ol';
|
|
19
22
|
min?: string;
|
|
20
23
|
gap?: string;
|
|
24
|
+
maxCols?: number;
|
|
21
25
|
class?: string;
|
|
22
26
|
children?: Snippet;
|
|
23
27
|
[key: string]: unknown;
|
|
24
28
|
} = $props();
|
|
29
|
+
|
|
30
|
+
let style = $derived(
|
|
31
|
+
maxCols != null
|
|
32
|
+
? `--ag-min: ${min}; --ag-gap: ${gap}; --ag-cols: ${maxCols}; gap: ${gap}`
|
|
33
|
+
: `--ag-min: ${min}; gap: ${gap}`
|
|
34
|
+
);
|
|
25
35
|
</script>
|
|
26
36
|
|
|
27
|
-
<svelte:element
|
|
37
|
+
<svelte:element
|
|
38
|
+
this={as}
|
|
39
|
+
class="autogrid-c {klass}"
|
|
40
|
+
class:capped={maxCols != null}
|
|
41
|
+
{style}
|
|
42
|
+
{...rest}
|
|
43
|
+
>
|
|
28
44
|
{@render children?.()}
|
|
29
45
|
</svelte:element>
|
|
30
46
|
|
|
@@ -33,4 +49,18 @@
|
|
|
33
49
|
display: grid;
|
|
34
50
|
grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--ag-min)), 1fr));
|
|
35
51
|
}
|
|
52
|
+
|
|
53
|
+
/* Cap at N columns: the per-column floor becomes the larger of `min` and the
|
|
54
|
+
width each column gets when N share the row (minus the N-1 gaps), so
|
|
55
|
+
auto-fit stops adding tracks once N fit. `min(100%, …)` keeps it from
|
|
56
|
+
overflowing when the container is narrower than a single `min` track. */
|
|
57
|
+
.autogrid-c.capped {
|
|
58
|
+
grid-template-columns: repeat(
|
|
59
|
+
auto-fit,
|
|
60
|
+
minmax(
|
|
61
|
+
min(100%, max(var(--ag-min), (100% - (var(--ag-cols) - 1) * var(--ag-gap)) / var(--ag-cols))),
|
|
62
|
+
1fr
|
|
63
|
+
)
|
|
64
|
+
);
|
|
65
|
+
}
|
|
36
66
|
</style>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// Character-count truncation with a hover/focus reveal of the full text.
|
|
3
|
+
// Thin wrapper over the `truncate()` helper (the truncation logic lives there
|
|
4
|
+
// and stays unit-testable) plus Tooltip (the reveal). When the text already
|
|
5
|
+
// fits, it renders as a bare inline span with no tooltip wiring at all — so
|
|
6
|
+
// you only pay for the Tooltip when something is actually hidden.
|
|
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';
|
|
12
|
+
import Tooltip from './Tooltip.svelte';
|
|
13
|
+
|
|
14
|
+
let {
|
|
15
|
+
text,
|
|
16
|
+
max,
|
|
17
|
+
mode = 'end',
|
|
18
|
+
ellipsis = '…',
|
|
19
|
+
tooltip = true,
|
|
20
|
+
placement = 'top'
|
|
21
|
+
}: {
|
|
22
|
+
text: string;
|
|
23
|
+
max: number;
|
|
24
|
+
mode?: TruncateMode;
|
|
25
|
+
ellipsis?: string;
|
|
26
|
+
/** Reveal the full text on hover/focus when truncated. Default true. */
|
|
27
|
+
tooltip?: boolean;
|
|
28
|
+
placement?: 'top' | 'bottom' | 'left' | 'right';
|
|
29
|
+
} = $props();
|
|
30
|
+
|
|
31
|
+
const shown = $derived(truncate(text, { max, mode, ellipsis }));
|
|
32
|
+
const isTruncated = $derived(shown !== text);
|
|
33
|
+
</script>
|
|
34
|
+
|
|
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" tabindex="0">{shown}</span>
|
|
43
|
+
{/snippet}
|
|
44
|
+
</Tooltip>
|
|
45
|
+
{:else}
|
|
46
|
+
<span class="trunc">{shown}</span>
|
|
47
|
+
{/if}
|
|
48
|
+
|
|
49
|
+
<style>
|
|
50
|
+
.trunc {
|
|
51
|
+
/* No layout of its own — it shortens the string, not the box. The cursor
|
|
52
|
+
hint signals there's more to reveal. */
|
|
53
|
+
cursor: default;
|
|
54
|
+
}
|
|
55
|
+
</style>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type TruncateMode } from '../../truncate';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
text: string;
|
|
4
|
+
max: number;
|
|
5
|
+
mode?: TruncateMode;
|
|
6
|
+
ellipsis?: string;
|
|
7
|
+
/** Reveal the full text on hover/focus when truncated. Default true. */
|
|
8
|
+
tooltip?: boolean;
|
|
9
|
+
placement?: 'top' | 'bottom' | 'left' | 'right';
|
|
10
|
+
};
|
|
11
|
+
declare const Truncate: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
12
|
+
type Truncate = ReturnType<typeof Truncate>;
|
|
13
|
+
export default Truncate;
|
package/dist/index.d.ts
CHANGED
|
@@ -41,7 +41,9 @@ export { default as ThemePicker } from './components/molecules/ThemePicker.svelt
|
|
|
41
41
|
export { default as Toaster } from './components/molecules/Toaster.svelte';
|
|
42
42
|
export { default as Toggle } from './components/molecules/Toggle.svelte';
|
|
43
43
|
export { default as Tooltip } from './components/molecules/Tooltip.svelte';
|
|
44
|
+
export { default as Truncate } from './components/molecules/Truncate.svelte';
|
|
44
45
|
export { type Column, default as DataTable } from './components/organisms/DataTable.svelte';
|
|
45
46
|
export { fontScale, SCALE_LEVELS, type ScaleLevel } from './stores/fontscale.svelte';
|
|
46
47
|
export { type Mode, THEMES, theme } from './stores/theme.svelte';
|
|
47
48
|
export { type Toast, type ToastTone, toasts } from './stores/toast.svelte';
|
|
49
|
+
export { type TruncateMode, type TruncateOptions, truncate } from './truncate';
|
package/dist/index.js
CHANGED
|
@@ -49,9 +49,11 @@ export { default as ThemePicker } from './components/molecules/ThemePicker.svelt
|
|
|
49
49
|
export { default as Toaster } from './components/molecules/Toaster.svelte';
|
|
50
50
|
export { default as Toggle } from './components/molecules/Toggle.svelte';
|
|
51
51
|
export { default as Tooltip } from './components/molecules/Tooltip.svelte';
|
|
52
|
+
export { default as Truncate } from './components/molecules/Truncate.svelte';
|
|
52
53
|
// ---- organisms ----
|
|
53
54
|
export { default as DataTable } from './components/organisms/DataTable.svelte';
|
|
54
55
|
export { fontScale, SCALE_LEVELS } from './stores/fontscale.svelte';
|
|
55
56
|
// ---- stores / actions ----
|
|
56
57
|
export { THEMES, theme } from './stores/theme.svelte';
|
|
57
58
|
export { toasts } from './stores/toast.svelte';
|
|
59
|
+
export { truncate } from './truncate';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type TruncateMode = 'end' | 'middle' | 'start';
|
|
2
|
+
export interface TruncateOptions {
|
|
3
|
+
/** Maximum number of characters in the result, ellipsis included. */
|
|
4
|
+
max: number;
|
|
5
|
+
/** Where the ellipsis goes. Default 'end'. */
|
|
6
|
+
mode?: TruncateMode;
|
|
7
|
+
/** Ellipsis string. Default '…' (one char). */
|
|
8
|
+
ellipsis?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Shorten `value` to at most `max` characters (counting the ellipsis), or
|
|
12
|
+
* return it unchanged when it already fits. Returns the original string when
|
|
13
|
+
* `max` is too small to show any content alongside the ellipsis.
|
|
14
|
+
*/
|
|
15
|
+
export declare function truncate(value: string, options: TruncateOptions): string;
|
package/dist/truncate.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Character-count truncation — a pure string helper, the counterpart to the
|
|
2
|
+
// CSS single-line ellipsis on <Text truncate>. CSS can clip to the available
|
|
3
|
+
// width but can't truncate in the *middle* (e.g. `0x1234…cdef` for hashes) or
|
|
4
|
+
// guarantee a minimum number of visible chars; this does, working on the string
|
|
5
|
+
// itself. It is layout-agnostic: it does not react to container resize or font
|
|
6
|
+
// scaling — reach for <Text truncate> when you want responsive end-clipping,
|
|
7
|
+
// and this when you need deterministic, content-aware shortening.
|
|
8
|
+
//
|
|
9
|
+
// Counting is by code point (spread into an array) so astral characters and
|
|
10
|
+
// most emoji are not split mid-surrogate. It is not grapheme-cluster aware, so
|
|
11
|
+
// combining marks / ZWJ sequences can still be cut; that trade-off keeps it
|
|
12
|
+
// dependency-free.
|
|
13
|
+
/**
|
|
14
|
+
* Shorten `value` to at most `max` characters (counting the ellipsis), or
|
|
15
|
+
* return it unchanged when it already fits. Returns the original string when
|
|
16
|
+
* `max` is too small to show any content alongside the ellipsis.
|
|
17
|
+
*/
|
|
18
|
+
export function truncate(value, options) {
|
|
19
|
+
const { max, mode = 'end', ellipsis = '…' } = options;
|
|
20
|
+
const chars = [...value];
|
|
21
|
+
if (chars.length <= max)
|
|
22
|
+
return value;
|
|
23
|
+
const ell = [...ellipsis];
|
|
24
|
+
const budget = max - ell.length; // chars of real content we can keep
|
|
25
|
+
// Not enough room for the ellipsis plus any content: don't produce a string
|
|
26
|
+
// that's all-ellipsis (misleading) — hand back the original and let CSS /
|
|
27
|
+
// the caller deal with overflow.
|
|
28
|
+
if (budget <= 0)
|
|
29
|
+
return value;
|
|
30
|
+
if (mode === 'start') {
|
|
31
|
+
return ellipsis + chars.slice(chars.length - budget).join('');
|
|
32
|
+
}
|
|
33
|
+
if (mode === 'middle') {
|
|
34
|
+
// Bias the extra char to the front so the start (often the more
|
|
35
|
+
// identifying part) keeps one more character on odd budgets.
|
|
36
|
+
const head = Math.ceil(budget / 2);
|
|
37
|
+
const tail = budget - head;
|
|
38
|
+
const start = chars.slice(0, head).join('');
|
|
39
|
+
const end = tail > 0 ? chars.slice(chars.length - tail).join('') : '';
|
|
40
|
+
return start + ellipsis + end;
|
|
41
|
+
}
|
|
42
|
+
// 'end'
|
|
43
|
+
return chars.slice(0, budget).join('') + ellipsis;
|
|
44
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dorsk/tsumikit",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
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",
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
"lint": "biome check .",
|
|
51
51
|
"format": "biome check --write .",
|
|
52
52
|
"package": "svelte-kit sync && svelte-package && publint",
|
|
53
|
+
"package:watch": "svelte-kit sync && svelte-package --watch",
|
|
53
54
|
"prepublishOnly": "npm run package"
|
|
54
55
|
},
|
|
55
56
|
"peerDependencies": {
|