@umplabs/truncated-value 0.1.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/LICENSE +21 -0
- package/README.md +178 -0
- package/dist/react.cjs +89 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +54 -0
- package/dist/react.d.ts +54 -0
- package/dist/react.js +87 -0
- package/dist/react.js.map +1 -0
- package/dist/truncated-value.iife.global.js +83 -0
- package/dist/truncated-value.iife.global.js.map +1 -0
- package/dist/web-component.cjs +116 -0
- package/dist/web-component.cjs.map +1 -0
- package/dist/web-component.d.cts +44 -0
- package/dist/web-component.d.ts +44 -0
- package/dist/web-component.js +116 -0
- package/dist/web-component.js.map +1 -0
- package/package.json +131 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 UMP Labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# truncated-value
|
|
2
|
+
|
|
3
|
+
Truncate long strings safely, without destroying searchability or verifiability.
|
|
4
|
+
|
|
5
|
+
## The Problem: Address Poisoning
|
|
6
|
+
|
|
7
|
+
Almost all apps, wallets and explorers display addresses truncated to their first and last few characters (e.g. `0xd8dA...6045`). [Address poisoning scams](https://support.metamask.io/stay-safe/protect-yourself/wallet-and-hardware/address-poisoning-scams) exploit this: attackers generate addresses matching the visible prefix and suffix, seed them into transaction histories, and wait for users to copy the wrong one.
|
|
8
|
+
|
|
9
|
+
**Truncation hides the characters that matter most for verification.**
|
|
10
|
+
|
|
11
|
+
## How This Helps
|
|
12
|
+
|
|
13
|
+
`@umplabs/truncated-value` keeps the **full value in the DOM at all times**. The middle portion is visually collapsed with CSS, but never removed.
|
|
14
|
+
|
|
15
|
+
- **Browser find (Ctrl+F) matches the complete address**, even while truncated
|
|
16
|
+
- **Optional expansion**: Enable `expandable` to let users click/focus to inspect every character before copying
|
|
17
|
+
|
|
18
|
+
Poisoned addresses become significantly harder to miss.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install @umplabs/truncated-value
|
|
24
|
+
# or
|
|
25
|
+
pnpm add @umplabs/truncated-value
|
|
26
|
+
# or
|
|
27
|
+
yarn add @umplabs/truncated-value
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
### React
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
import { TruncatedValue } from "@umplabs/truncated-value/react";
|
|
36
|
+
|
|
37
|
+
function App() {
|
|
38
|
+
return (
|
|
39
|
+
<TruncatedValue
|
|
40
|
+
value="0x1234567890abcdef1234567890abcdef12345678"
|
|
41
|
+
startLength={6}
|
|
42
|
+
endLength={4}
|
|
43
|
+
expandable
|
|
44
|
+
/>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Web Component (Vue, Svelte, or any framework)
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
// Import once to register the custom element
|
|
53
|
+
import "@umplabs/truncated-value/web-component";
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```html
|
|
57
|
+
<truncated-value
|
|
58
|
+
value="0x1234567890abcdef1234567890abcdef12345678"
|
|
59
|
+
start-length="6"
|
|
60
|
+
end-length="4"
|
|
61
|
+
expandable
|
|
62
|
+
></truncated-value>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### CDN (No bundler required)
|
|
66
|
+
|
|
67
|
+
```html
|
|
68
|
+
<script type="module" src="https://unpkg.com/@umplabs/truncated-value"></script>
|
|
69
|
+
|
|
70
|
+
<truncated-value
|
|
71
|
+
value="0x1234567890abcdef1234567890abcdef12345678"
|
|
72
|
+
expandable
|
|
73
|
+
></truncated-value>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Examples
|
|
77
|
+
|
|
78
|
+
### Ethereum Address
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
<TruncatedValue value="0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" expandable />
|
|
82
|
+
// Displays: 0xd8dA...6045
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Transaction Hash
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
<TruncatedValue
|
|
89
|
+
value="0xf7b9d38a3c5e4e7b9d38a3c5e4e7b9d38a3c5e4e7b9d38a3c5e4e7b9d38a3c5e4e"
|
|
90
|
+
startLength={10}
|
|
91
|
+
endLength={8}
|
|
92
|
+
expandable
|
|
93
|
+
/>
|
|
94
|
+
// Displays: 0xf7b9d38a...38a3c5e4e
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### IPFS CID
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
<TruncatedValue
|
|
101
|
+
value="QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG"
|
|
102
|
+
startLength={6}
|
|
103
|
+
endLength={6}
|
|
104
|
+
expandable
|
|
105
|
+
/>
|
|
106
|
+
// Displays: QmYwAP...nPbdG
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Custom Styling
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
<TruncatedValue
|
|
113
|
+
value="0x1234567890abcdef"
|
|
114
|
+
expandable
|
|
115
|
+
className="my-custom-class"
|
|
116
|
+
/>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Features
|
|
120
|
+
|
|
121
|
+
- **Smart truncation**: Shows start and end characters, visually collapses the middle
|
|
122
|
+
- **Interactive expansion** (optional): Click or focus to reveal and verify the full value
|
|
123
|
+
- **Fully searchable**: Browser find (Ctrl+F) works on the complete text, even when truncated
|
|
124
|
+
- **Multi-framework**: React component + Web Component (Vue, Svelte, vanilla HTML)
|
|
125
|
+
- **Accessible**: Keyboard navigation and ARIA attributes
|
|
126
|
+
- **Tiny footprint**: Tree-shakable, minimal dependencies
|
|
127
|
+
|
|
128
|
+
## Unopinionated by Design
|
|
129
|
+
|
|
130
|
+
This component ships with **no visual opinions** — no colors, fonts, sizes, or spacing baked in. The only built-in CSS handles the truncation mechanics (collapsing the middle text and cursor hints). Everything else — how it looks, how it fits your brand — is up to you. Style it with plain CSS, theme tokens, or your design system as you would any other inline element.
|
|
131
|
+
|
|
132
|
+
## API
|
|
133
|
+
|
|
134
|
+
### React Component Props
|
|
135
|
+
|
|
136
|
+
| Prop | Type | Default | Description |
|
|
137
|
+
| ------------- | --------- | ------- | ---------------------------------------------------- |
|
|
138
|
+
| `value` | `string` | `""` | The string value to display and potentially truncate |
|
|
139
|
+
| `startLength` | `number` | `6` | Number of characters to show at the start |
|
|
140
|
+
| `endLength` | `number` | `4` | Number of characters to show at the end |
|
|
141
|
+
| `expandable` | `boolean` | `false` | Whether the component expands on focus/click |
|
|
142
|
+
| `className` | `string` | - | Additional CSS class name |
|
|
143
|
+
|
|
144
|
+
### Web Component Attributes
|
|
145
|
+
|
|
146
|
+
| Attribute | Type | Default | Description |
|
|
147
|
+
| -------------- | --------- | ------- | -------------------------------------------- |
|
|
148
|
+
| `value` | `string` | `""` | The string value to display |
|
|
149
|
+
| `start-length` | `number` | `6` | Characters to show at the start |
|
|
150
|
+
| `end-length` | `number` | `4` | Characters to show at the end |
|
|
151
|
+
| `expandable` | `boolean` | `false` | Whether the component expands on focus/click |
|
|
152
|
+
|
|
153
|
+
### Shadow DOM Parts (Web Component)
|
|
154
|
+
|
|
155
|
+
Style internal elements using `::part()`:
|
|
156
|
+
|
|
157
|
+
```css
|
|
158
|
+
truncated-value::part(container) {
|
|
159
|
+
font-weight: bold;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
truncated-value::part(start),
|
|
163
|
+
truncated-value::part(end) {
|
|
164
|
+
color: #888;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
truncated-value::part(middle) {
|
|
168
|
+
color: #666;
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Browser Support
|
|
173
|
+
|
|
174
|
+
Modern browsers (Chrome, Firefox, Safari, Edge).
|
|
175
|
+
|
|
176
|
+
## License
|
|
177
|
+
|
|
178
|
+
MIT
|
package/dist/react.cjs
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
4
|
+
|
|
5
|
+
// src/core/truncate.ts
|
|
6
|
+
function truncate(options) {
|
|
7
|
+
const { value, startLength = 6, endLength = 4 } = options;
|
|
8
|
+
if (!value) {
|
|
9
|
+
return {
|
|
10
|
+
start: "",
|
|
11
|
+
middle: "",
|
|
12
|
+
end: "",
|
|
13
|
+
isTruncated: false
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
const isTruncated = startLength + endLength < value.length;
|
|
17
|
+
const excess = Math.max(0, startLength + endLength - value.length);
|
|
18
|
+
const adjustedStartLength = startLength - (startLength > endLength ? Math.ceil : Math.floor)(excess / 2);
|
|
19
|
+
const adjustedEndLength = endLength - (startLength > endLength ? Math.floor : Math.ceil)(excess / 2);
|
|
20
|
+
const safeStartLength = Math.max(
|
|
21
|
+
0,
|
|
22
|
+
Math.min(adjustedStartLength, value.length)
|
|
23
|
+
);
|
|
24
|
+
const safeEndLength = Math.max(
|
|
25
|
+
0,
|
|
26
|
+
Math.min(adjustedEndLength, value.length - safeStartLength)
|
|
27
|
+
);
|
|
28
|
+
const start = value.slice(0, safeStartLength);
|
|
29
|
+
const end = safeEndLength > 0 ? value.slice(-safeEndLength) : "";
|
|
30
|
+
const middle = value.slice(
|
|
31
|
+
safeStartLength,
|
|
32
|
+
safeEndLength > 0 ? -safeEndLength : value.length
|
|
33
|
+
);
|
|
34
|
+
return {
|
|
35
|
+
start,
|
|
36
|
+
middle,
|
|
37
|
+
end,
|
|
38
|
+
isTruncated
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/core/styles.ts
|
|
43
|
+
var styles = `
|
|
44
|
+
[tabindex].truncated-value { --isTruncated: 0; }
|
|
45
|
+
[tabindex].truncated-value:not(:is(:active, :focus-within, [data-is-truncated="false"])) { --isTruncated: 1; cursor: zoom-in; }
|
|
46
|
+
[tabindex="-1"].truncated-value:not([data-is-truncated="false"]) { --isTruncated: 1 !important; cursor: default !important; }
|
|
47
|
+
[tabindex].truncated-value > .tv-middle > span:not(:empty) { font-size: calc((1 - var(--isTruncated)) * 1em); }
|
|
48
|
+
[tabindex].truncated-value > .tv-middle > span:empty { font-size: calc(var(--isTruncated) * 1em); pointer-events: none; }
|
|
49
|
+
[tabindex].truncated-value > .tv-middle > span:empty::after { content: "..."; }
|
|
50
|
+
`;
|
|
51
|
+
var TruncatedValue = ({
|
|
52
|
+
value = "",
|
|
53
|
+
startLength = 6,
|
|
54
|
+
endLength = 4,
|
|
55
|
+
expandable = false,
|
|
56
|
+
className,
|
|
57
|
+
style
|
|
58
|
+
}) => {
|
|
59
|
+
const { start, middle, end, isTruncated } = truncate({
|
|
60
|
+
value,
|
|
61
|
+
startLength,
|
|
62
|
+
endLength
|
|
63
|
+
});
|
|
64
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
65
|
+
/* @__PURE__ */ jsxRuntime.jsx("style", { children: styles }),
|
|
66
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
67
|
+
"span",
|
|
68
|
+
{
|
|
69
|
+
"data-is-truncated": isTruncated,
|
|
70
|
+
tabIndex: expandable ? 0 : -1,
|
|
71
|
+
className: `truncated-value${className ? ` ${className}` : ""}`,
|
|
72
|
+
style,
|
|
73
|
+
children: [
|
|
74
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: start }),
|
|
75
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "tv-middle", children: [
|
|
76
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: middle.slice(0, middle.length / 2) }),
|
|
77
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true" }),
|
|
78
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: middle.slice(middle.length / 2) })
|
|
79
|
+
] }),
|
|
80
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: end })
|
|
81
|
+
]
|
|
82
|
+
}
|
|
83
|
+
)
|
|
84
|
+
] });
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
exports.TruncatedValue = TruncatedValue;
|
|
88
|
+
//# sourceMappingURL=react.cjs.map
|
|
89
|
+
//# sourceMappingURL=react.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/truncate.ts","../src/core/styles.ts","../src/TruncatedValue.tsx"],"names":["jsxs","Fragment","jsx"],"mappings":"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","file":"react.cjs","sourcesContent":["import type { TruncateOptions, TruncateResult } from \"./types\";\n\n/**\n * Truncates a string value intelligently, preserving the start and end portions.\n *\n * The function handles edge cases like:\n * - Strings shorter than the requested lengths (adjusts proportionally)\n * - Zero or very large length values\n * - Empty strings\n *\n * @example\n * ```ts\n * const result = truncate({\n * value: \"0x1234567890abcdef1234567890abcdef12345678\",\n * startLength: 6,\n * endLength: 4\n * });\n * // result.start = \"0x1234\"\n * // result.middle = \"567890abcdef1234567890abcdef1234\"\n * // result.end = \"5678\"\n * // result.isTruncated = true\n * ```\n */\nexport function truncate(options: TruncateOptions): TruncateResult {\n const { value, startLength = 6, endLength = 4 } = options;\n\n // Handle empty or undefined values\n if (!value) {\n return {\n start: \"\",\n middle: \"\",\n end: \"\",\n isTruncated: false,\n };\n }\n\n // Determine if truncation is needed\n const isTruncated = startLength + endLength < value.length;\n\n // Calculate excess characters when string is shorter than requested lengths\n const excess = Math.max(0, startLength + endLength - value.length);\n\n // Adjust lengths proportionally when string is too short\n const adjustedStartLength =\n startLength -\n (startLength > endLength ? Math.ceil : Math.floor)(excess / 2);\n const adjustedEndLength =\n endLength - (startLength > endLength ? Math.floor : Math.ceil)(excess / 2);\n\n // Ensure we don't get negative lengths\n const safeStartLength = Math.max(\n 0,\n Math.min(adjustedStartLength, value.length)\n );\n const safeEndLength = Math.max(\n 0,\n Math.min(adjustedEndLength, value.length - safeStartLength)\n );\n\n // Split the value into start, middle, and end segments\n const start = value.slice(0, safeStartLength);\n const end = safeEndLength > 0 ? value.slice(-safeEndLength) : \"\";\n const middle = value.slice(\n safeStartLength,\n safeEndLength > 0 ? -safeEndLength : value.length\n );\n\n return {\n start,\n middle,\n end,\n isTruncated,\n };\n}\n","/**\n * CSS styles for the TruncatedValue component.\n *\n * How it works:\n * - `--isTruncated` CSS variable (0=expanded, 1=truncated) represents state\n * - Visual default is truncated; expands on :active or :focus-within\n * - Short values (data-is-truncated=\"false\") stay expanded\n * - Non-expandable (tabindex=\"-1\") stays truncated unless value is short\n * - Middle text: font-size set to 0 when truncated (hidden but searchable)\n * - Ellipsis: empty span with ::after content, font-size set inverse\n */\nexport const styles = `\n[tabindex].truncated-value { --isTruncated: 0; }\n[tabindex].truncated-value:not(:is(:active, :focus-within, [data-is-truncated=\"false\"])) { --isTruncated: 1; cursor: zoom-in; }\n[tabindex=\"-1\"].truncated-value:not([data-is-truncated=\"false\"]) { --isTruncated: 1 !important; cursor: default !important; }\n[tabindex].truncated-value > .tv-middle > span:not(:empty) { font-size: calc((1 - var(--isTruncated)) * 1em); }\n[tabindex].truncated-value > .tv-middle > span:empty { font-size: calc(var(--isTruncated) * 1em); pointer-events: none; }\n[tabindex].truncated-value > .tv-middle > span:empty::after { content: \"...\"; }\n`;\n","import * as React from \"react\";\nimport { truncate, styles, type TruncatedValueBaseProps } from \"./core\";\n\n/**\n * Props for the TruncatedValue React component\n */\nexport interface TruncatedValueProps extends TruncatedValueBaseProps {\n /** Additional CSS class name applied to the interactive element */\n className?: string;\n /** Inline styles applied to the component */\n style?: React.CSSProperties;\n}\n\n/**\n * A component that intelligently truncates long string values, displaying the start and end\n * while hiding the middle portion. Features interactive focus states that reveal the full value.\n * The complete text remains searchable via browser find functionality even when truncated.\n *\n * Commonly used for displaying addresses, hashes, or other long identifiers where the beginning\n * and end are most important for identification.\n *\n * @example\n * ```tsx\n * <TruncatedValue\n * value=\"0x1234567890abcdef1234567890abcdef12345678\"\n * startLength={8}\n * endLength={6}\n * expandable\n * />\n * // Displays: 0x123456...345678 (expandable on focus)\n *\n * <TruncatedValue\n * value=\"0x1234567890abcdef1234567890abcdef12345678\"\n * expandable={false}\n * />\n * // Displays: 0x1234...5678 (always truncated)\n * // Full text remains searchable with Ctrl+F/Cmd+F\n * ```\n */\nexport const TruncatedValue: React.FC<TruncatedValueProps> = ({\n value = \"\",\n startLength = 6,\n endLength = 4,\n expandable = false,\n className,\n style,\n}) => {\n const { start, middle, end, isTruncated } = truncate({\n value,\n startLength,\n endLength,\n });\n\n return (\n <>\n <style>{styles}</style>\n <span\n data-is-truncated={isTruncated}\n tabIndex={expandable ? 0 : -1}\n className={`truncated-value${className ? ` ${className}` : \"\"}`}\n style={style}\n >\n <span>{start}</span>\n <span className=\"tv-middle\">\n <span>{middle.slice(0, middle.length / 2)}</span>\n <span aria-hidden=\"true\"></span>\n <span>{middle.slice(middle.length / 2)}</span>\n </span>\n <span>{end}</span>\n </span>\n </>\n );\n};\n"]}
|
package/dist/react.d.cts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Props shared between React and Web Component implementations
|
|
5
|
+
*/
|
|
6
|
+
interface TruncatedValueBaseProps {
|
|
7
|
+
/** The string value to display and potentially truncate */
|
|
8
|
+
value?: string;
|
|
9
|
+
/** Number of characters to show at the start when truncated (default: 6) */
|
|
10
|
+
startLength?: number;
|
|
11
|
+
/** Number of characters to show at the end when truncated (default: 4) */
|
|
12
|
+
endLength?: number;
|
|
13
|
+
/** Whether the component should expand on focus/click (default: false) */
|
|
14
|
+
expandable?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Props for the TruncatedValue React component
|
|
19
|
+
*/
|
|
20
|
+
interface TruncatedValueProps extends TruncatedValueBaseProps {
|
|
21
|
+
/** Additional CSS class name applied to the interactive element */
|
|
22
|
+
className?: string;
|
|
23
|
+
/** Inline styles applied to the component */
|
|
24
|
+
style?: React.CSSProperties;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A component that intelligently truncates long string values, displaying the start and end
|
|
28
|
+
* while hiding the middle portion. Features interactive focus states that reveal the full value.
|
|
29
|
+
* The complete text remains searchable via browser find functionality even when truncated.
|
|
30
|
+
*
|
|
31
|
+
* Commonly used for displaying addresses, hashes, or other long identifiers where the beginning
|
|
32
|
+
* and end are most important for identification.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```tsx
|
|
36
|
+
* <TruncatedValue
|
|
37
|
+
* value="0x1234567890abcdef1234567890abcdef12345678"
|
|
38
|
+
* startLength={8}
|
|
39
|
+
* endLength={6}
|
|
40
|
+
* expandable
|
|
41
|
+
* />
|
|
42
|
+
* // Displays: 0x123456...345678 (expandable on focus)
|
|
43
|
+
*
|
|
44
|
+
* <TruncatedValue
|
|
45
|
+
* value="0x1234567890abcdef1234567890abcdef12345678"
|
|
46
|
+
* expandable={false}
|
|
47
|
+
* />
|
|
48
|
+
* // Displays: 0x1234...5678 (always truncated)
|
|
49
|
+
* // Full text remains searchable with Ctrl+F/Cmd+F
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
declare const TruncatedValue: React.FC<TruncatedValueProps>;
|
|
53
|
+
|
|
54
|
+
export { TruncatedValue, type TruncatedValueProps };
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Props shared between React and Web Component implementations
|
|
5
|
+
*/
|
|
6
|
+
interface TruncatedValueBaseProps {
|
|
7
|
+
/** The string value to display and potentially truncate */
|
|
8
|
+
value?: string;
|
|
9
|
+
/** Number of characters to show at the start when truncated (default: 6) */
|
|
10
|
+
startLength?: number;
|
|
11
|
+
/** Number of characters to show at the end when truncated (default: 4) */
|
|
12
|
+
endLength?: number;
|
|
13
|
+
/** Whether the component should expand on focus/click (default: false) */
|
|
14
|
+
expandable?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Props for the TruncatedValue React component
|
|
19
|
+
*/
|
|
20
|
+
interface TruncatedValueProps extends TruncatedValueBaseProps {
|
|
21
|
+
/** Additional CSS class name applied to the interactive element */
|
|
22
|
+
className?: string;
|
|
23
|
+
/** Inline styles applied to the component */
|
|
24
|
+
style?: React.CSSProperties;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A component that intelligently truncates long string values, displaying the start and end
|
|
28
|
+
* while hiding the middle portion. Features interactive focus states that reveal the full value.
|
|
29
|
+
* The complete text remains searchable via browser find functionality even when truncated.
|
|
30
|
+
*
|
|
31
|
+
* Commonly used for displaying addresses, hashes, or other long identifiers where the beginning
|
|
32
|
+
* and end are most important for identification.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```tsx
|
|
36
|
+
* <TruncatedValue
|
|
37
|
+
* value="0x1234567890abcdef1234567890abcdef12345678"
|
|
38
|
+
* startLength={8}
|
|
39
|
+
* endLength={6}
|
|
40
|
+
* expandable
|
|
41
|
+
* />
|
|
42
|
+
* // Displays: 0x123456...345678 (expandable on focus)
|
|
43
|
+
*
|
|
44
|
+
* <TruncatedValue
|
|
45
|
+
* value="0x1234567890abcdef1234567890abcdef12345678"
|
|
46
|
+
* expandable={false}
|
|
47
|
+
* />
|
|
48
|
+
* // Displays: 0x1234...5678 (always truncated)
|
|
49
|
+
* // Full text remains searchable with Ctrl+F/Cmd+F
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
declare const TruncatedValue: React.FC<TruncatedValueProps>;
|
|
53
|
+
|
|
54
|
+
export { TruncatedValue, type TruncatedValueProps };
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
// src/core/truncate.ts
|
|
4
|
+
function truncate(options) {
|
|
5
|
+
const { value, startLength = 6, endLength = 4 } = options;
|
|
6
|
+
if (!value) {
|
|
7
|
+
return {
|
|
8
|
+
start: "",
|
|
9
|
+
middle: "",
|
|
10
|
+
end: "",
|
|
11
|
+
isTruncated: false
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
const isTruncated = startLength + endLength < value.length;
|
|
15
|
+
const excess = Math.max(0, startLength + endLength - value.length);
|
|
16
|
+
const adjustedStartLength = startLength - (startLength > endLength ? Math.ceil : Math.floor)(excess / 2);
|
|
17
|
+
const adjustedEndLength = endLength - (startLength > endLength ? Math.floor : Math.ceil)(excess / 2);
|
|
18
|
+
const safeStartLength = Math.max(
|
|
19
|
+
0,
|
|
20
|
+
Math.min(adjustedStartLength, value.length)
|
|
21
|
+
);
|
|
22
|
+
const safeEndLength = Math.max(
|
|
23
|
+
0,
|
|
24
|
+
Math.min(adjustedEndLength, value.length - safeStartLength)
|
|
25
|
+
);
|
|
26
|
+
const start = value.slice(0, safeStartLength);
|
|
27
|
+
const end = safeEndLength > 0 ? value.slice(-safeEndLength) : "";
|
|
28
|
+
const middle = value.slice(
|
|
29
|
+
safeStartLength,
|
|
30
|
+
safeEndLength > 0 ? -safeEndLength : value.length
|
|
31
|
+
);
|
|
32
|
+
return {
|
|
33
|
+
start,
|
|
34
|
+
middle,
|
|
35
|
+
end,
|
|
36
|
+
isTruncated
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// src/core/styles.ts
|
|
41
|
+
var styles = `
|
|
42
|
+
[tabindex].truncated-value { --isTruncated: 0; }
|
|
43
|
+
[tabindex].truncated-value:not(:is(:active, :focus-within, [data-is-truncated="false"])) { --isTruncated: 1; cursor: zoom-in; }
|
|
44
|
+
[tabindex="-1"].truncated-value:not([data-is-truncated="false"]) { --isTruncated: 1 !important; cursor: default !important; }
|
|
45
|
+
[tabindex].truncated-value > .tv-middle > span:not(:empty) { font-size: calc((1 - var(--isTruncated)) * 1em); }
|
|
46
|
+
[tabindex].truncated-value > .tv-middle > span:empty { font-size: calc(var(--isTruncated) * 1em); pointer-events: none; }
|
|
47
|
+
[tabindex].truncated-value > .tv-middle > span:empty::after { content: "..."; }
|
|
48
|
+
`;
|
|
49
|
+
var TruncatedValue = ({
|
|
50
|
+
value = "",
|
|
51
|
+
startLength = 6,
|
|
52
|
+
endLength = 4,
|
|
53
|
+
expandable = false,
|
|
54
|
+
className,
|
|
55
|
+
style
|
|
56
|
+
}) => {
|
|
57
|
+
const { start, middle, end, isTruncated } = truncate({
|
|
58
|
+
value,
|
|
59
|
+
startLength,
|
|
60
|
+
endLength
|
|
61
|
+
});
|
|
62
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
63
|
+
/* @__PURE__ */ jsx("style", { children: styles }),
|
|
64
|
+
/* @__PURE__ */ jsxs(
|
|
65
|
+
"span",
|
|
66
|
+
{
|
|
67
|
+
"data-is-truncated": isTruncated,
|
|
68
|
+
tabIndex: expandable ? 0 : -1,
|
|
69
|
+
className: `truncated-value${className ? ` ${className}` : ""}`,
|
|
70
|
+
style,
|
|
71
|
+
children: [
|
|
72
|
+
/* @__PURE__ */ jsx("span", { children: start }),
|
|
73
|
+
/* @__PURE__ */ jsxs("span", { className: "tv-middle", children: [
|
|
74
|
+
/* @__PURE__ */ jsx("span", { children: middle.slice(0, middle.length / 2) }),
|
|
75
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true" }),
|
|
76
|
+
/* @__PURE__ */ jsx("span", { children: middle.slice(middle.length / 2) })
|
|
77
|
+
] }),
|
|
78
|
+
/* @__PURE__ */ jsx("span", { children: end })
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
)
|
|
82
|
+
] });
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export { TruncatedValue };
|
|
86
|
+
//# sourceMappingURL=react.js.map
|
|
87
|
+
//# sourceMappingURL=react.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/truncate.ts","../src/core/styles.ts","../src/TruncatedValue.tsx"],"names":[],"mappings":"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","file":"react.js","sourcesContent":["import type { TruncateOptions, TruncateResult } from \"./types\";\n\n/**\n * Truncates a string value intelligently, preserving the start and end portions.\n *\n * The function handles edge cases like:\n * - Strings shorter than the requested lengths (adjusts proportionally)\n * - Zero or very large length values\n * - Empty strings\n *\n * @example\n * ```ts\n * const result = truncate({\n * value: \"0x1234567890abcdef1234567890abcdef12345678\",\n * startLength: 6,\n * endLength: 4\n * });\n * // result.start = \"0x1234\"\n * // result.middle = \"567890abcdef1234567890abcdef1234\"\n * // result.end = \"5678\"\n * // result.isTruncated = true\n * ```\n */\nexport function truncate(options: TruncateOptions): TruncateResult {\n const { value, startLength = 6, endLength = 4 } = options;\n\n // Handle empty or undefined values\n if (!value) {\n return {\n start: \"\",\n middle: \"\",\n end: \"\",\n isTruncated: false,\n };\n }\n\n // Determine if truncation is needed\n const isTruncated = startLength + endLength < value.length;\n\n // Calculate excess characters when string is shorter than requested lengths\n const excess = Math.max(0, startLength + endLength - value.length);\n\n // Adjust lengths proportionally when string is too short\n const adjustedStartLength =\n startLength -\n (startLength > endLength ? Math.ceil : Math.floor)(excess / 2);\n const adjustedEndLength =\n endLength - (startLength > endLength ? Math.floor : Math.ceil)(excess / 2);\n\n // Ensure we don't get negative lengths\n const safeStartLength = Math.max(\n 0,\n Math.min(adjustedStartLength, value.length)\n );\n const safeEndLength = Math.max(\n 0,\n Math.min(adjustedEndLength, value.length - safeStartLength)\n );\n\n // Split the value into start, middle, and end segments\n const start = value.slice(0, safeStartLength);\n const end = safeEndLength > 0 ? value.slice(-safeEndLength) : \"\";\n const middle = value.slice(\n safeStartLength,\n safeEndLength > 0 ? -safeEndLength : value.length\n );\n\n return {\n start,\n middle,\n end,\n isTruncated,\n };\n}\n","/**\n * CSS styles for the TruncatedValue component.\n *\n * How it works:\n * - `--isTruncated` CSS variable (0=expanded, 1=truncated) represents state\n * - Visual default is truncated; expands on :active or :focus-within\n * - Short values (data-is-truncated=\"false\") stay expanded\n * - Non-expandable (tabindex=\"-1\") stays truncated unless value is short\n * - Middle text: font-size set to 0 when truncated (hidden but searchable)\n * - Ellipsis: empty span with ::after content, font-size set inverse\n */\nexport const styles = `\n[tabindex].truncated-value { --isTruncated: 0; }\n[tabindex].truncated-value:not(:is(:active, :focus-within, [data-is-truncated=\"false\"])) { --isTruncated: 1; cursor: zoom-in; }\n[tabindex=\"-1\"].truncated-value:not([data-is-truncated=\"false\"]) { --isTruncated: 1 !important; cursor: default !important; }\n[tabindex].truncated-value > .tv-middle > span:not(:empty) { font-size: calc((1 - var(--isTruncated)) * 1em); }\n[tabindex].truncated-value > .tv-middle > span:empty { font-size: calc(var(--isTruncated) * 1em); pointer-events: none; }\n[tabindex].truncated-value > .tv-middle > span:empty::after { content: \"...\"; }\n`;\n","import * as React from \"react\";\nimport { truncate, styles, type TruncatedValueBaseProps } from \"./core\";\n\n/**\n * Props for the TruncatedValue React component\n */\nexport interface TruncatedValueProps extends TruncatedValueBaseProps {\n /** Additional CSS class name applied to the interactive element */\n className?: string;\n /** Inline styles applied to the component */\n style?: React.CSSProperties;\n}\n\n/**\n * A component that intelligently truncates long string values, displaying the start and end\n * while hiding the middle portion. Features interactive focus states that reveal the full value.\n * The complete text remains searchable via browser find functionality even when truncated.\n *\n * Commonly used for displaying addresses, hashes, or other long identifiers where the beginning\n * and end are most important for identification.\n *\n * @example\n * ```tsx\n * <TruncatedValue\n * value=\"0x1234567890abcdef1234567890abcdef12345678\"\n * startLength={8}\n * endLength={6}\n * expandable\n * />\n * // Displays: 0x123456...345678 (expandable on focus)\n *\n * <TruncatedValue\n * value=\"0x1234567890abcdef1234567890abcdef12345678\"\n * expandable={false}\n * />\n * // Displays: 0x1234...5678 (always truncated)\n * // Full text remains searchable with Ctrl+F/Cmd+F\n * ```\n */\nexport const TruncatedValue: React.FC<TruncatedValueProps> = ({\n value = \"\",\n startLength = 6,\n endLength = 4,\n expandable = false,\n className,\n style,\n}) => {\n const { start, middle, end, isTruncated } = truncate({\n value,\n startLength,\n endLength,\n });\n\n return (\n <>\n <style>{styles}</style>\n <span\n data-is-truncated={isTruncated}\n tabIndex={expandable ? 0 : -1}\n className={`truncated-value${className ? ` ${className}` : \"\"}`}\n style={style}\n >\n <span>{start}</span>\n <span className=\"tv-middle\">\n <span>{middle.slice(0, middle.length / 2)}</span>\n <span aria-hidden=\"true\"></span>\n <span>{middle.slice(middle.length / 2)}</span>\n </span>\n <span>{end}</span>\n </span>\n </>\n );\n};\n"]}
|