midcut 0.0.1 → 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/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # midcut
2
+
3
+ A <1kb React component that accurately middle-truncates monospaced text. Uses CSS `round()` and container queries (no JS measurement).
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm i -S midcut
9
+ pnpm add midcut
10
+ bun add midcut
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```tsx
16
+ import { Midcut } from "midcut";
17
+
18
+ function App() {
19
+ return <Midcut prefix="0x" value="0x1234567890abcdef1234567890abcdef12345678" />;
20
+ }
21
+ ```
22
+
23
+ **Important:** only use with a monospace font.
24
+
25
+ ## Props
26
+
27
+ | Prop | Type | Default | Description |
28
+ | ---------- | ------------------ | --------- | ------------------------------------------------------- |
29
+ | `value` | `string` | `""` | The string to truncate |
30
+ | `prefix` | `string` | `""` | A prefix that is always visible (e.g. `"0x"`) |
31
+ | `min` | `number` | `1` | Minimum characters visible on each side of the ellipsis |
32
+ | `ellipsis` | `string` | `"…"` | The ellipsis character(s) |
33
+ | `align` | `"start" \| "end"` | `"start"` | Alignment of the truncated string within its container |
34
+
35
+ ## How it works
36
+
37
+ 1. The string is split into two halves displayed as inline flex items
38
+ 2. A CSS container query activates truncation when the container is narrower than the full string
39
+ 3. `round(down, ..., 2ch)` snaps the truncatable width to character boundaries so characters are never partially clipped
40
+ 4. The end half uses `justify-content: flex-end` to truncate from the start
41
+
42
+ ## Development
43
+
44
+ ```bash
45
+ bun install
46
+ bun dev
47
+ ```
48
+
49
+ ## License
50
+
51
+ MIT
@@ -0,0 +1,21 @@
1
+ import * as _$react_jsx_runtime0 from "react/jsx-runtime";
2
+
3
+ //#region src/Midcut.d.ts
4
+ declare function Midcut({
5
+ align,
6
+ ellipsis,
7
+ min,
8
+ prefix,
9
+ value
10
+ }: Midcut.Props): string | _$react_jsx_runtime0.JSX.Element;
11
+ declare namespace Midcut {
12
+ interface Props {
13
+ align?: "start" | "end";
14
+ ellipsis?: string;
15
+ min?: number;
16
+ prefix?: string;
17
+ value?: string;
18
+ }
19
+ }
20
+ //#endregion
21
+ export { Midcut };
@@ -0,0 +1,67 @@
1
+ import { useId } from "react";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ //#region src/Midcut.tsx
4
+ function Midcut({ align = "start", ellipsis = "…", min = 1, prefix = "", value = "" }) {
5
+ const id = useId();
6
+ const body = value.slice(value.startsWith(prefix) ? prefix.length : 0);
7
+ if (body.length < 2) return prefix + ellipsis;
8
+ const cutAt = 1 + Math.ceil((body.length - 1) / 2);
9
+ const [start, end] = [body.slice(1, cutAt), body.slice(cutAt, -1)];
10
+ const minWidth = prefix.length + min * 2 + 1;
11
+ return /* @__PURE__ */ jsxs("span", {
12
+ id,
13
+ title: value,
14
+ style: {
15
+ display: "inline-flex",
16
+ justifyContent: align === "end" ? "flex-end" : void 0,
17
+ width: "100%",
18
+ minWidth: `${minWidth}ch`,
19
+ textDecoration: "inherit",
20
+ containerType: "inline-size",
21
+ containerName: id
22
+ },
23
+ children: [
24
+ /* @__PURE__ */ jsx("style", { children: `
25
+ .${id}-ellipsis { display: none }
26
+ @container ${id} (max-width: ${value.length + 1}ch) {
27
+ .${id}-ellipsis { display: flex }
28
+ .${id}-part {
29
+ overflow: hidden;
30
+ width: calc((100cqw - ${prefix.length + 3}ch) / 2);
31
+ }
32
+ }
33
+ ` }),
34
+ /* @__PURE__ */ jsxs("span", {
35
+ style: {
36
+ display: "inline-flex",
37
+ maxWidth: `${value.length - 1}ch`,
38
+ width: "round(down, calc(100% - 1ch), 2ch)",
39
+ whiteSpace: "nowrap"
40
+ },
41
+ children: [
42
+ prefix,
43
+ body.at(0),
44
+ /* @__PURE__ */ jsx("span", {
45
+ className: `${id}-part`,
46
+ children: start
47
+ }),
48
+ /* @__PURE__ */ jsx("span", {
49
+ className: `${id}-ellipsis`,
50
+ children: ellipsis
51
+ }),
52
+ /* @__PURE__ */ jsx("span", {
53
+ className: `${id}-part`,
54
+ style: {
55
+ display: "flex",
56
+ justifyContent: "flex-end"
57
+ },
58
+ children: end
59
+ })
60
+ ]
61
+ }),
62
+ body.at(-1)
63
+ ]
64
+ });
65
+ }
66
+ //#endregion
67
+ export { Midcut };
package/package.json CHANGED
@@ -1,5 +1,56 @@
1
1
  {
2
2
  "name": "midcut",
3
- "version": "0.0.1",
4
- "license": "MIT"
5
- }
3
+ "version": "0.1.0",
4
+ "description": "A <1kb React component that accurately middle-truncates monospaced text",
5
+ "keywords": [
6
+ "css",
7
+ "ellipsis",
8
+ "middle",
9
+ "monospace",
10
+ "react",
11
+ "truncate"
12
+ ],
13
+ "license": "MIT",
14
+ "repository": "tempoxyz/midcut",
15
+ "files": [
16
+ "dist",
17
+ "src/Midcut.tsx"
18
+ ],
19
+ "type": "module",
20
+ "main": "dist/Midcut.mjs",
21
+ "module": "dist/Midcut.mjs",
22
+ "types": "dist/Midcut.d.mts",
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/Midcut.d.mts",
26
+ "import": "./dist/Midcut.mjs"
27
+ }
28
+ },
29
+ "scripts": {
30
+ "dev": "vp dev",
31
+ "build": "vp pack src/Midcut.tsx --dts --tsconfig tsconfig.app.json",
32
+ "build:demo": "vp build --outDir dist/client && vp build --outDir dist/server --ssr entry-server.tsx && bun scripts/prerender.ts",
33
+ "lint": "vp lint .",
34
+ "preview": "vp preview",
35
+ "prepublishOnly": "bun run build",
36
+ "prepare": "vp config"
37
+ },
38
+ "devDependencies": {
39
+ "@types/react": "^19.2.5",
40
+ "@types/react-dom": "^19.2.3",
41
+ "@vitejs/plugin-react": "^5.1.1",
42
+ "react": "^19.2.0",
43
+ "react-dom": "^19.2.0",
44
+ "typescript": "~5.9.3",
45
+ "vite": "npm:@voidzero-dev/vite-plus-core@latest",
46
+ "vite-plus": "latest"
47
+ },
48
+ "peerDependencies": {
49
+ "react": ">=18"
50
+ },
51
+ "overrides": {
52
+ "vite": "npm:@voidzero-dev/vite-plus-core@latest",
53
+ "vitest": "npm:@voidzero-dev/vite-plus-test@latest"
54
+ },
55
+ "packageManager": "bun@1.3.11"
56
+ }
package/src/Midcut.tsx ADDED
@@ -0,0 +1,81 @@
1
+ import { useId } from "react";
2
+
3
+ export function Midcut({
4
+ align = "start",
5
+ ellipsis = "…",
6
+ min = 1,
7
+ prefix = "",
8
+ value = "",
9
+ }: Midcut.Props) {
10
+ const id = useId();
11
+
12
+ const body = value.slice(value.startsWith(prefix) ? prefix.length : 0);
13
+ if (body.length < 2) return prefix + ellipsis;
14
+
15
+ const cutAt = 1 + Math.ceil((body.length - 1) / 2);
16
+ const [start, end] = [body.slice(1, cutAt), body.slice(cutAt, -1)];
17
+
18
+ const minWidth = prefix.length + min * 2 + 1;
19
+
20
+ return (
21
+ <span
22
+ id={id}
23
+ title={value}
24
+ style={{
25
+ display: "inline-flex",
26
+ justifyContent: align === "end" ? "flex-end" : undefined,
27
+ width: "100%",
28
+ minWidth: `${minWidth}ch`,
29
+ textDecoration: "inherit",
30
+ containerType: "inline-size",
31
+ containerName: id,
32
+ }}
33
+ >
34
+ <style>
35
+ {`
36
+ .${id}-ellipsis { display: none }
37
+ @container ${id} (max-width: ${value.length + 1}ch) {
38
+ .${id}-ellipsis { display: flex }
39
+ .${id}-part {
40
+ overflow: hidden;
41
+ width: calc((100cqw - ${prefix.length + 3}ch) / 2);
42
+ }
43
+ }
44
+ `}
45
+ </style>
46
+ <span
47
+ style={{
48
+ display: "inline-flex",
49
+ maxWidth: `${value.length - 1}ch`,
50
+ width: "round(down, calc(100% - 1ch), 2ch)",
51
+ whiteSpace: "nowrap",
52
+ }}
53
+ >
54
+ {prefix}
55
+ {body.at(0)}
56
+ <span className={`${id}-part`}>{start}</span>
57
+ <span className={`${id}-ellipsis`}>{ellipsis}</span>
58
+ <span
59
+ className={`${id}-part`}
60
+ style={{
61
+ display: "flex",
62
+ justifyContent: "flex-end",
63
+ }}
64
+ >
65
+ {end}
66
+ </span>
67
+ </span>
68
+ {body.at(-1)}
69
+ </span>
70
+ );
71
+ }
72
+
73
+ export namespace Midcut {
74
+ export interface Props {
75
+ align?: "start" | "end";
76
+ ellipsis?: string;
77
+ min?: number;
78
+ prefix?: string;
79
+ value?: string;
80
+ }
81
+ }