@dorsk/tsumikit 0.2.1 → 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.
@@ -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;
@@ -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.1",
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",