@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.
@@ -0,0 +1,116 @@
1
+ 'use strict';
2
+
3
+ var lit = require('lit');
4
+ var decorators_js = require('lit/decorators.js');
5
+
6
+ var __defProp = Object.defineProperty;
7
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
8
+ var __decorateClass = (decorators, target, key, kind) => {
9
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
10
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
11
+ if (decorator = decorators[i])
12
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
13
+ if (kind && result) __defProp(target, key, result);
14
+ return result;
15
+ };
16
+
17
+ // src/core/truncate.ts
18
+ function truncate(options) {
19
+ const { value, startLength = 6, endLength = 4 } = options;
20
+ if (!value) {
21
+ return {
22
+ start: "",
23
+ middle: "",
24
+ end: "",
25
+ isTruncated: false
26
+ };
27
+ }
28
+ const isTruncated = startLength + endLength < value.length;
29
+ const excess = Math.max(0, startLength + endLength - value.length);
30
+ const adjustedStartLength = startLength - (startLength > endLength ? Math.ceil : Math.floor)(excess / 2);
31
+ const adjustedEndLength = endLength - (startLength > endLength ? Math.floor : Math.ceil)(excess / 2);
32
+ const safeStartLength = Math.max(
33
+ 0,
34
+ Math.min(adjustedStartLength, value.length)
35
+ );
36
+ const safeEndLength = Math.max(
37
+ 0,
38
+ Math.min(adjustedEndLength, value.length - safeStartLength)
39
+ );
40
+ const start = value.slice(0, safeStartLength);
41
+ const end = safeEndLength > 0 ? value.slice(-safeEndLength) : "";
42
+ const middle = value.slice(
43
+ safeStartLength,
44
+ safeEndLength > 0 ? -safeEndLength : value.length
45
+ );
46
+ return {
47
+ start,
48
+ middle,
49
+ end,
50
+ isTruncated
51
+ };
52
+ }
53
+
54
+ // src/core/styles.ts
55
+ var styles = `
56
+ [tabindex].truncated-value { --isTruncated: 0; }
57
+ [tabindex].truncated-value:not(:is(:active, :focus-within, [data-is-truncated="false"])) { --isTruncated: 1; cursor: zoom-in; }
58
+ [tabindex="-1"].truncated-value:not([data-is-truncated="false"]) { --isTruncated: 1 !important; cursor: default !important; }
59
+ [tabindex].truncated-value > .tv-middle > span:not(:empty) { font-size: calc((1 - var(--isTruncated)) * 1em); }
60
+ [tabindex].truncated-value > .tv-middle > span:empty { font-size: calc(var(--isTruncated) * 1em); pointer-events: none; }
61
+ [tabindex].truncated-value > .tv-middle > span:empty::after { content: "..."; }
62
+ `;
63
+
64
+ // src/web-component.ts
65
+ exports.TruncatedValueElement = class TruncatedValueElement extends lit.LitElement {
66
+ constructor() {
67
+ super(...arguments);
68
+ this.value = "";
69
+ this.startLength = 6;
70
+ this.endLength = 4;
71
+ this.expandable = false;
72
+ }
73
+ render() {
74
+ const { start, middle, end, isTruncated } = truncate({
75
+ value: this.value,
76
+ startLength: this.startLength,
77
+ endLength: this.endLength
78
+ });
79
+ const middleFirstHalf = middle.slice(0, middle.length / 2);
80
+ const middleSecondHalf = middle.slice(middle.length / 2);
81
+ return lit.html`<span
82
+ part="container"
83
+ data-is-truncated="${isTruncated}"
84
+ tabindex="${this.expandable ? 0 : -1}"
85
+ class="truncated-value"
86
+ ><span part="start">${start}</span
87
+ ><span part="middle" class="tv-middle"
88
+ ><span>${middleFirstHalf}</span><span aria-hidden="true"></span
89
+ ><span>${middleSecondHalf}</span></span
90
+ ><span part="end">${end}</span></span
91
+ >`;
92
+ }
93
+ };
94
+ exports.TruncatedValueElement.styles = lit.css`
95
+ :host {
96
+ display: inline-block;
97
+ }
98
+ ${lit.unsafeCSS(styles)}
99
+ `;
100
+ __decorateClass([
101
+ decorators_js.property({ type: String })
102
+ ], exports.TruncatedValueElement.prototype, "value", 2);
103
+ __decorateClass([
104
+ decorators_js.property({ type: Number, attribute: "start-length" })
105
+ ], exports.TruncatedValueElement.prototype, "startLength", 2);
106
+ __decorateClass([
107
+ decorators_js.property({ type: Number, attribute: "end-length" })
108
+ ], exports.TruncatedValueElement.prototype, "endLength", 2);
109
+ __decorateClass([
110
+ decorators_js.property({ type: Boolean })
111
+ ], exports.TruncatedValueElement.prototype, "expandable", 2);
112
+ exports.TruncatedValueElement = __decorateClass([
113
+ decorators_js.customElement("truncated-value")
114
+ ], exports.TruncatedValueElement);
115
+ //# sourceMappingURL=web-component.cjs.map
116
+ //# sourceMappingURL=web-component.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/truncate.ts","../src/core/styles.ts","../src/web-component.ts"],"names":["TruncatedValueElement","LitElement","html","css","unsafeCSS","property","customElement"],"mappings":"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","file":"web-component.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 { LitElement, html, css, unsafeCSS } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { truncate, styles } from \"./core\";\n\n/**\n * A web component that intelligently truncates long string values.\n *\n * @element truncated-value\n *\n * @attr {string} value - The string value to display and potentially truncate\n * @attr {number} start-length - Number of characters to show at the start (default: 6)\n * @attr {number} end-length - Number of characters to show at the end (default: 4)\n * @attr {boolean} expandable - Whether the component should expand on focus/click\n *\n * @csspart container - The main container element\n * @csspart start - The start portion text\n * @csspart middle - The middle portion container\n * @csspart end - The end portion text\n *\n *\n * @example\n * ```html\n * <truncated-value\n * value=\"0x1234567890abcdef1234567890abcdef12345678\"\n * start-length=\"8\"\n * end-length=\"6\"\n * expandable\n * ></truncated-value>\n * ```\n */\n@customElement(\"truncated-value\")\nexport class TruncatedValueElement extends LitElement {\n static override styles = css`\n :host {\n display: inline-block;\n }\n ${unsafeCSS(styles)}\n `;\n\n @property({ type: String })\n value = \"\";\n\n @property({ type: Number, attribute: \"start-length\" })\n startLength = 6;\n\n @property({ type: Number, attribute: \"end-length\" })\n endLength = 4;\n\n @property({ type: Boolean })\n expandable = false;\n\n override render() {\n const { start, middle, end, isTruncated } = truncate({\n value: this.value,\n startLength: this.startLength,\n endLength: this.endLength,\n });\n\n const middleFirstHalf = middle.slice(0, middle.length / 2);\n const middleSecondHalf = middle.slice(middle.length / 2);\n\n return html`<span\n part=\"container\"\n data-is-truncated=\"${isTruncated}\"\n tabindex=\"${this.expandable ? 0 : -1}\"\n class=\"truncated-value\"\n ><span part=\"start\">${start}</span\n ><span part=\"middle\" class=\"tv-middle\"\n ><span>${middleFirstHalf}</span><span aria-hidden=\"true\"></span\n ><span>${middleSecondHalf}</span></span\n ><span part=\"end\">${end}</span></span\n >`;\n }\n}\n\n// Declare for type checking\ndeclare global {\n interface HTMLElementTagNameMap {\n \"truncated-value\": TruncatedValueElement;\n }\n}\n"]}
@@ -0,0 +1,44 @@
1
+ import * as lit from 'lit';
2
+ import { LitElement } from 'lit';
3
+
4
+ /**
5
+ * A web component that intelligently truncates long string values.
6
+ *
7
+ * @element truncated-value
8
+ *
9
+ * @attr {string} value - The string value to display and potentially truncate
10
+ * @attr {number} start-length - Number of characters to show at the start (default: 6)
11
+ * @attr {number} end-length - Number of characters to show at the end (default: 4)
12
+ * @attr {boolean} expandable - Whether the component should expand on focus/click
13
+ *
14
+ * @csspart container - The main container element
15
+ * @csspart start - The start portion text
16
+ * @csspart middle - The middle portion container
17
+ * @csspart end - The end portion text
18
+ *
19
+ *
20
+ * @example
21
+ * ```html
22
+ * <truncated-value
23
+ * value="0x1234567890abcdef1234567890abcdef12345678"
24
+ * start-length="8"
25
+ * end-length="6"
26
+ * expandable
27
+ * ></truncated-value>
28
+ * ```
29
+ */
30
+ declare class TruncatedValueElement extends LitElement {
31
+ static styles: lit.CSSResult;
32
+ value: string;
33
+ startLength: number;
34
+ endLength: number;
35
+ expandable: boolean;
36
+ render(): lit.TemplateResult<1>;
37
+ }
38
+ declare global {
39
+ interface HTMLElementTagNameMap {
40
+ "truncated-value": TruncatedValueElement;
41
+ }
42
+ }
43
+
44
+ export { TruncatedValueElement };
@@ -0,0 +1,44 @@
1
+ import * as lit from 'lit';
2
+ import { LitElement } from 'lit';
3
+
4
+ /**
5
+ * A web component that intelligently truncates long string values.
6
+ *
7
+ * @element truncated-value
8
+ *
9
+ * @attr {string} value - The string value to display and potentially truncate
10
+ * @attr {number} start-length - Number of characters to show at the start (default: 6)
11
+ * @attr {number} end-length - Number of characters to show at the end (default: 4)
12
+ * @attr {boolean} expandable - Whether the component should expand on focus/click
13
+ *
14
+ * @csspart container - The main container element
15
+ * @csspart start - The start portion text
16
+ * @csspart middle - The middle portion container
17
+ * @csspart end - The end portion text
18
+ *
19
+ *
20
+ * @example
21
+ * ```html
22
+ * <truncated-value
23
+ * value="0x1234567890abcdef1234567890abcdef12345678"
24
+ * start-length="8"
25
+ * end-length="6"
26
+ * expandable
27
+ * ></truncated-value>
28
+ * ```
29
+ */
30
+ declare class TruncatedValueElement extends LitElement {
31
+ static styles: lit.CSSResult;
32
+ value: string;
33
+ startLength: number;
34
+ endLength: number;
35
+ expandable: boolean;
36
+ render(): lit.TemplateResult<1>;
37
+ }
38
+ declare global {
39
+ interface HTMLElementTagNameMap {
40
+ "truncated-value": TruncatedValueElement;
41
+ }
42
+ }
43
+
44
+ export { TruncatedValueElement };
@@ -0,0 +1,116 @@
1
+ import { unsafeCSS, css, LitElement, html } from 'lit';
2
+ import { property, customElement } from 'lit/decorators.js';
3
+
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __decorateClass = (decorators, target, key, kind) => {
7
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
8
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
9
+ if (decorator = decorators[i])
10
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
11
+ if (kind && result) __defProp(target, key, result);
12
+ return result;
13
+ };
14
+
15
+ // src/core/truncate.ts
16
+ function truncate(options) {
17
+ const { value, startLength = 6, endLength = 4 } = options;
18
+ if (!value) {
19
+ return {
20
+ start: "",
21
+ middle: "",
22
+ end: "",
23
+ isTruncated: false
24
+ };
25
+ }
26
+ const isTruncated = startLength + endLength < value.length;
27
+ const excess = Math.max(0, startLength + endLength - value.length);
28
+ const adjustedStartLength = startLength - (startLength > endLength ? Math.ceil : Math.floor)(excess / 2);
29
+ const adjustedEndLength = endLength - (startLength > endLength ? Math.floor : Math.ceil)(excess / 2);
30
+ const safeStartLength = Math.max(
31
+ 0,
32
+ Math.min(adjustedStartLength, value.length)
33
+ );
34
+ const safeEndLength = Math.max(
35
+ 0,
36
+ Math.min(adjustedEndLength, value.length - safeStartLength)
37
+ );
38
+ const start = value.slice(0, safeStartLength);
39
+ const end = safeEndLength > 0 ? value.slice(-safeEndLength) : "";
40
+ const middle = value.slice(
41
+ safeStartLength,
42
+ safeEndLength > 0 ? -safeEndLength : value.length
43
+ );
44
+ return {
45
+ start,
46
+ middle,
47
+ end,
48
+ isTruncated
49
+ };
50
+ }
51
+
52
+ // src/core/styles.ts
53
+ var styles = `
54
+ [tabindex].truncated-value { --isTruncated: 0; }
55
+ [tabindex].truncated-value:not(:is(:active, :focus-within, [data-is-truncated="false"])) { --isTruncated: 1; cursor: zoom-in; }
56
+ [tabindex="-1"].truncated-value:not([data-is-truncated="false"]) { --isTruncated: 1 !important; cursor: default !important; }
57
+ [tabindex].truncated-value > .tv-middle > span:not(:empty) { font-size: calc((1 - var(--isTruncated)) * 1em); }
58
+ [tabindex].truncated-value > .tv-middle > span:empty { font-size: calc(var(--isTruncated) * 1em); pointer-events: none; }
59
+ [tabindex].truncated-value > .tv-middle > span:empty::after { content: "..."; }
60
+ `;
61
+
62
+ // src/web-component.ts
63
+ var TruncatedValueElement = class extends LitElement {
64
+ constructor() {
65
+ super(...arguments);
66
+ this.value = "";
67
+ this.startLength = 6;
68
+ this.endLength = 4;
69
+ this.expandable = false;
70
+ }
71
+ render() {
72
+ const { start, middle, end, isTruncated } = truncate({
73
+ value: this.value,
74
+ startLength: this.startLength,
75
+ endLength: this.endLength
76
+ });
77
+ const middleFirstHalf = middle.slice(0, middle.length / 2);
78
+ const middleSecondHalf = middle.slice(middle.length / 2);
79
+ return html`<span
80
+ part="container"
81
+ data-is-truncated="${isTruncated}"
82
+ tabindex="${this.expandable ? 0 : -1}"
83
+ class="truncated-value"
84
+ ><span part="start">${start}</span
85
+ ><span part="middle" class="tv-middle"
86
+ ><span>${middleFirstHalf}</span><span aria-hidden="true"></span
87
+ ><span>${middleSecondHalf}</span></span
88
+ ><span part="end">${end}</span></span
89
+ >`;
90
+ }
91
+ };
92
+ TruncatedValueElement.styles = css`
93
+ :host {
94
+ display: inline-block;
95
+ }
96
+ ${unsafeCSS(styles)}
97
+ `;
98
+ __decorateClass([
99
+ property({ type: String })
100
+ ], TruncatedValueElement.prototype, "value", 2);
101
+ __decorateClass([
102
+ property({ type: Number, attribute: "start-length" })
103
+ ], TruncatedValueElement.prototype, "startLength", 2);
104
+ __decorateClass([
105
+ property({ type: Number, attribute: "end-length" })
106
+ ], TruncatedValueElement.prototype, "endLength", 2);
107
+ __decorateClass([
108
+ property({ type: Boolean })
109
+ ], TruncatedValueElement.prototype, "expandable", 2);
110
+ TruncatedValueElement = __decorateClass([
111
+ customElement("truncated-value")
112
+ ], TruncatedValueElement);
113
+
114
+ export { TruncatedValueElement };
115
+ //# sourceMappingURL=web-component.js.map
116
+ //# sourceMappingURL=web-component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/truncate.ts","../src/core/styles.ts","../src/web-component.ts"],"names":[],"mappings":"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","file":"web-component.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 { LitElement, html, css, unsafeCSS } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { truncate, styles } from \"./core\";\n\n/**\n * A web component that intelligently truncates long string values.\n *\n * @element truncated-value\n *\n * @attr {string} value - The string value to display and potentially truncate\n * @attr {number} start-length - Number of characters to show at the start (default: 6)\n * @attr {number} end-length - Number of characters to show at the end (default: 4)\n * @attr {boolean} expandable - Whether the component should expand on focus/click\n *\n * @csspart container - The main container element\n * @csspart start - The start portion text\n * @csspart middle - The middle portion container\n * @csspart end - The end portion text\n *\n *\n * @example\n * ```html\n * <truncated-value\n * value=\"0x1234567890abcdef1234567890abcdef12345678\"\n * start-length=\"8\"\n * end-length=\"6\"\n * expandable\n * ></truncated-value>\n * ```\n */\n@customElement(\"truncated-value\")\nexport class TruncatedValueElement extends LitElement {\n static override styles = css`\n :host {\n display: inline-block;\n }\n ${unsafeCSS(styles)}\n `;\n\n @property({ type: String })\n value = \"\";\n\n @property({ type: Number, attribute: \"start-length\" })\n startLength = 6;\n\n @property({ type: Number, attribute: \"end-length\" })\n endLength = 4;\n\n @property({ type: Boolean })\n expandable = false;\n\n override render() {\n const { start, middle, end, isTruncated } = truncate({\n value: this.value,\n startLength: this.startLength,\n endLength: this.endLength,\n });\n\n const middleFirstHalf = middle.slice(0, middle.length / 2);\n const middleSecondHalf = middle.slice(middle.length / 2);\n\n return html`<span\n part=\"container\"\n data-is-truncated=\"${isTruncated}\"\n tabindex=\"${this.expandable ? 0 : -1}\"\n class=\"truncated-value\"\n ><span part=\"start\">${start}</span\n ><span part=\"middle\" class=\"tv-middle\"\n ><span>${middleFirstHalf}</span><span aria-hidden=\"true\"></span\n ><span>${middleSecondHalf}</span></span\n ><span part=\"end\">${end}</span></span\n >`;\n }\n}\n\n// Declare for type checking\ndeclare global {\n interface HTMLElementTagNameMap {\n \"truncated-value\": TruncatedValueElement;\n }\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,131 @@
1
+ {
2
+ "name": "@umplabs/truncated-value",
3
+ "version": "0.1.0",
4
+ "description": "A component that intelligently truncates long string values with interactive expansion and full searchability",
5
+ "keywords": [
6
+ "truncate",
7
+ "ellipsis",
8
+ "address",
9
+ "ethereum",
10
+ "web3",
11
+ "react",
12
+ "web-component",
13
+ "lit"
14
+ ],
15
+ "license": "MIT",
16
+ "author": "UMP Labs",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/umpeth/truncated-value.git"
20
+ },
21
+ "bugs": {
22
+ "url": "https://github.com/umpeth/truncated-value/issues"
23
+ },
24
+ "homepage": "https://github.com/umpeth/truncated-value#readme",
25
+ "type": "module",
26
+ "exports": {
27
+ "./react": {
28
+ "import": {
29
+ "types": "./dist/react.d.ts",
30
+ "default": "./dist/react.js"
31
+ },
32
+ "require": {
33
+ "types": "./dist/react.d.cts",
34
+ "default": "./dist/react.cjs"
35
+ }
36
+ },
37
+ "./web-component": {
38
+ "import": {
39
+ "types": "./dist/web-component.d.ts",
40
+ "default": "./dist/web-component.js"
41
+ },
42
+ "require": {
43
+ "types": "./dist/web-component.d.cts",
44
+ "default": "./dist/web-component.cjs"
45
+ }
46
+ }
47
+ },
48
+ "files": [
49
+ "dist",
50
+ "README.md",
51
+ "LICENSE"
52
+ ],
53
+ "sideEffects": [
54
+ "./dist/web-component.js",
55
+ "./dist/web-component.cjs"
56
+ ],
57
+ "peerDependencies": {
58
+ "lit": "^3.2.0",
59
+ "react": ">=17",
60
+ "react-dom": ">=17"
61
+ },
62
+ "peerDependenciesMeta": {
63
+ "lit": {
64
+ "optional": true
65
+ },
66
+ "react": {
67
+ "optional": true
68
+ },
69
+ "react-dom": {
70
+ "optional": true
71
+ }
72
+ },
73
+ "devDependencies": {
74
+ "@eslint/js": "^9.39.2",
75
+ "@playwright/test": "^1.58.1",
76
+ "@size-limit/file": "^12.0.0",
77
+ "@storybook/react-vite": "^10.2.2",
78
+ "@testing-library/dom": "^10.4.0",
79
+ "@testing-library/jest-dom": "^6.9.1",
80
+ "@testing-library/react": "^16.0.0",
81
+ "@types/node": "^25.0.0",
82
+ "@types/react": "^19.0.0",
83
+ "@types/react-dom": "^19.0.0",
84
+ "@typescript-eslint/eslint-plugin": "^8.0.0",
85
+ "@typescript-eslint/parser": "^8.0.0",
86
+ "@vitest/coverage-v8": "^4.0.0",
87
+ "eslint": "^9.0.0",
88
+ "eslint-plugin-react": "^7.35.0",
89
+ "eslint-plugin-react-hooks": "^7.0.0",
90
+ "happy-dom": "^20.4.0",
91
+ "husky": "^9.1.7",
92
+ "lint-staged": "^16.2.7",
93
+ "prettier": "^3.8.1",
94
+ "react": "^19.0.0",
95
+ "react-dom": "^19.0.0",
96
+ "serve": "^14.2.5",
97
+ "size-limit": "^12.0.0",
98
+ "storybook": "^10.2.2",
99
+ "tsup": "^8.3.0",
100
+ "typescript": "^5.6.0",
101
+ "typescript-eslint": "^8.54.0",
102
+ "vitest": "^4.0.0"
103
+ },
104
+ "engines": {
105
+ "node": ">=18"
106
+ },
107
+ "lint-staged": {
108
+ "*.{js,jsx,ts,tsx}": [
109
+ "eslint --fix",
110
+ "prettier --write"
111
+ ],
112
+ "!(*.{js,jsx,ts,tsx})": [
113
+ "prettier --write --ignore-unknown"
114
+ ]
115
+ },
116
+ "scripts": {
117
+ "build": "tsup",
118
+ "dev": "tsup --watch",
119
+ "test": "vitest run",
120
+ "test:watch": "vitest",
121
+ "test:coverage": "vitest run --coverage",
122
+ "test:e2e": "playwright test",
123
+ "test:e2e:ui": "playwright test --ui",
124
+ "lint": "eslint src tests",
125
+ "typecheck": "tsc --noEmit",
126
+ "format": "prettier --write .",
127
+ "storybook": "storybook dev -p 6006",
128
+ "build-storybook": "storybook build",
129
+ "size": "size-limit"
130
+ }
131
+ }