@iit/precision-ui 0.8.35 → 0.8.37

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.
@@ -1,7 +1,8 @@
1
- declare const SafeHtmlRenderer: ({ html, className, withoutContentClass, }: {
1
+ declare const SafeHtmlRenderer: ({ html, className, withoutContentClass, truncateLength, }: {
2
2
  html: string;
3
3
  className?: string;
4
4
  withoutContentClass?: boolean;
5
+ truncateLength?: number;
5
6
  }) => import("react/jsx-dev-runtime").JSX.Element;
6
7
  export default SafeHtmlRenderer;
7
8
  //# sourceMappingURL=SafeHtml.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SafeHtml.d.ts","sourceRoot":"","sources":["../../src/components/SafeHtml.tsx"],"names":[],"mappings":"AAkKA,QAAA,MAAM,gBAAgB,8CAInB;IACD,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,mBAAmB,CAAC,EAAE,OAAO,CAAA;CAC9B,gDAgBA,CAAA;AAED,eAAe,gBAAgB,CAAA"}
1
+ {"version":3,"file":"SafeHtml.d.ts","sourceRoot":"","sources":["../../src/components/SafeHtml.tsx"],"names":[],"mappings":"AA+LA,QAAA,MAAM,gBAAgB,8DAKnB;IACD,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB,gDAuBA,CAAA;AAED,eAAe,gBAAgB,CAAA"}
@@ -1,25 +1,25 @@
1
- import u from "react";
1
+ import g from "react";
2
2
  const h = (t) => {
3
- const a = /* @__PURE__ */ new Set(["br", "img", "input", "hr", "meta", "link"]), c = /<\/?([a-z]+)(\s[^>]*)?>/gi, o = [];
4
- let l;
5
- for (; (l = c.exec(t)) !== null; ) {
6
- const s = l[0], e = l[1].toLowerCase();
3
+ const e = /* @__PURE__ */ new Set(["br", "img", "input", "hr", "meta", "link"]), o = /<\/?([a-z]+)(\s[^>]*)?>/gi, a = [];
4
+ let r;
5
+ for (; (r = o.exec(t)) !== null; ) {
6
+ const s = r[0], n = r[1].toLowerCase();
7
7
  if (s === "</br>")
8
8
  return !1;
9
9
  if (s.startsWith("</")) {
10
- if (a.has(e) || o.length === 0 || o.pop() !== e)
10
+ if (e.has(n) || a.length === 0 || a.pop() !== n)
11
11
  return !1;
12
12
  } else
13
- !a.has(e) && !s.endsWith("/>") && o.push(e);
13
+ !e.has(n) && !s.endsWith("/>") && a.push(n);
14
14
  }
15
- return o.length === 0;
16
- }, m = (t) => {
17
- const a = /<[a-z]+\s+([^>]+)>/gi, c = t.matchAll(a);
18
- for (const [s, e] of c) {
19
- const r = e.match(/\w+\s*=\s*(['"])(.*?)\1|\w+(?!=)/g) || [];
20
- for (const n of r)
21
- if (n.includes("=")) {
22
- const [g, ...f] = n.split("="), i = f.join("=");
15
+ return a.length === 0;
16
+ }, p = (t) => {
17
+ const e = /<[a-z]+\s+([^>]+)>/gi, o = t.matchAll(e);
18
+ for (const [s, n] of o) {
19
+ const c = n.match(/\w+\s*=\s*(['"])(.*?)\1|\w+(?!=)/g) || [];
20
+ for (const l of c)
21
+ if (l.includes("=")) {
22
+ const [f, ...u] = l.split("="), i = u.join("=");
23
23
  if (!(i.startsWith('"') && i.endsWith('"') || i.startsWith("'") && i.endsWith("'")))
24
24
  return !1;
25
25
  const d = i[0];
@@ -27,51 +27,67 @@ const h = (t) => {
27
27
  return !1;
28
28
  }
29
29
  }
30
- const o = /<[^>]*?['"][^'"]*>/g, l = t.match(o) || [];
31
- for (const s of l) {
32
- let e = !1, r = "";
33
- for (let n = 0; n < s.length; n++)
34
- (s[n] === "'" || s[n] === '"') && (e ? s[n] === r && (e = !1) : (e = !0, r = s[n]));
35
- if (e)
30
+ const a = /<[^>]*?['"][^'"]*>/g, r = t.match(a) || [];
31
+ for (const s of r) {
32
+ let n = !1, c = "";
33
+ for (let l = 0; l < s.length; l++)
34
+ (s[l] === "'" || s[l] === '"') && (n ? s[l] === c && (n = !1) : (n = !0, c = s[l]));
35
+ if (n)
36
36
  return !1;
37
37
  }
38
38
  return !0;
39
- }, v = (t) => {
40
- const a = [...t.matchAll(/<([a-z]+)(\s[^>]*)\/>/gi)];
41
- for (const [c, o, l] of a) {
42
- const s = /(\w+)=["']([^"']*)["']/g, e = [...l.matchAll(s)];
43
- for (const [g, f, i] of e)
44
- if (!f || !i)
39
+ }, m = (t) => {
40
+ const e = [...t.matchAll(/<([a-z]+)(\s[^>]*)\/>/gi)];
41
+ for (const [o, a, r] of e)
42
+ if (r) {
43
+ const s = /(\w+)=["']([^"']*)["']/g, n = [...r.matchAll(s)];
44
+ for (const [f, u, i] of n)
45
+ if (!u || !i)
46
+ return !1;
47
+ const c = /(\w+)=["'][^"']*$/g;
48
+ if ([...r.matchAll(c)].length > 0)
45
49
  return !1;
46
- const r = /(\w+)=["'][^"']*$/g;
47
- if ([...l.matchAll(r)].length > 0)
48
- return !1;
49
- }
50
+ }
50
51
  return !0;
51
- }, p = (t) => {
52
- const a = /style=["']([^"']*)["']/gi, c = [...t.matchAll(a)];
53
- for (const [o, l] of c) {
54
- const s = l.split(";");
55
- for (const e of s)
56
- if (e.trim()) {
57
- const [r, n] = e.split(":");
58
- if (!r || !n)
52
+ }, y = (t) => {
53
+ const e = /style=["']([^"']*)["']/gi, o = [...t.matchAll(e)];
54
+ for (const [a, r] of o) {
55
+ const s = r.split(";");
56
+ for (const n of s)
57
+ if (n.trim()) {
58
+ const [c, l] = n.split(":").map((f) => f.trim());
59
+ if (!c || !l)
59
60
  return !1;
60
61
  }
61
62
  }
62
63
  return !0;
63
- }, T = (t) => h(t) && m(t) && v(t) && p(t), y = ({
64
+ }, T = (t) => h(t) && p(t) && m(t) && y(t), v = (t) => {
65
+ const e = {
66
+ nbsp: " ",
67
+ amp: "&",
68
+ lt: "<",
69
+ gt: ">",
70
+ quot: '"'
71
+ }, o = (a) => Object.prototype.hasOwnProperty.call(e, a);
72
+ return t.replace(/<[^>]*>/g, "").replace(/&([a-z]+);/g, (a, r) => o(r) ? e[r] : "").replace(/\s+/g, " ").trim();
73
+ }, C = (t, e) => t.length <= e ? t : t.slice(0, e).trim() + "...", A = ({
64
74
  html: t,
65
- className: a,
66
- withoutContentClass: c
67
- }) => T(t) ? /* @__PURE__ */ u.createElement(
68
- "div",
69
- {
70
- className: [!c && "content", a].filter(Boolean).join(" "),
71
- dangerouslySetInnerHTML: { __html: t }
72
- }
73
- ) : (console.error("Invalid HTML content:", t), /* @__PURE__ */ u.createElement("div", { style: { color: "red" } }, "Invalid HTML content")), R = y;
75
+ className: e,
76
+ withoutContentClass: o,
77
+ truncateLength: a
78
+ }) => {
79
+ if (!T(t))
80
+ return console.error("Invalid HTML content:", t), /* @__PURE__ */ g.createElement("div", { style: { color: "red" } }, "Некорректное HTML содержимое");
81
+ const s = a ? C(v(t), a) : t;
82
+ return /* @__PURE__ */ g.createElement(
83
+ "div",
84
+ {
85
+ className: [!o && "content", e].filter(Boolean).join(" "),
86
+ dangerouslySetInnerHTML: { __html: s }
87
+ }
88
+ );
89
+ }, w = A;
74
90
  export {
75
- R as default
91
+ w as default
76
92
  };
77
93
  //# sourceMappingURL=index.es19.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.es19.js","sources":["../src/components/SafeHtml.tsx"],"sourcesContent":["import React from 'react'\r\n\r\n// Check if tags are correctly opened and closed\r\nconst validateTags = (htmlString: string) => {\r\n // List of known self-closing tags\r\n const selfClosingTags = new Set(['br', 'img', 'input', 'hr', 'meta', 'link'])\r\n\r\n const tagPattern = /<\\/?([a-z]+)(\\s[^>]*)?>/gi\r\n const tagStack: string[] = []\r\n let match\r\n\r\n while ((match = tagPattern.exec(htmlString)) !== null) {\r\n const fullTag = match[0]\r\n const tagName = match[1].toLowerCase()\r\n\r\n // Check for incorrect </br> usage\r\n if (fullTag === '</br>') {\r\n return false // </br> is invalid HTML\r\n }\r\n\r\n if (fullTag.startsWith('</')) {\r\n // Closing tag\r\n if (selfClosingTags.has(tagName)) {\r\n return false // Self-closing tags shouldn't have closing tags\r\n }\r\n if (tagStack.length === 0) return false\r\n const lastTag = tagStack.pop()\r\n if (lastTag !== tagName) return false\r\n } else {\r\n // Opening tag\r\n if (!selfClosingTags.has(tagName) && !fullTag.endsWith('/>')) {\r\n tagStack.push(tagName)\r\n }\r\n }\r\n }\r\n\r\n return tagStack.length === 0\r\n}\r\n\r\nconst validateAttributes = (htmlString: string) => {\r\n const attributeRegex = /<[a-z]+\\s+([^>]+)>/gi\r\n const matches = htmlString.matchAll(attributeRegex)\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\r\n for (const [fullMatch, attributes] of matches) {\r\n // Split attributes by space, but keep quoted values together\r\n const attrs = attributes.match(/\\w+\\s*=\\s*(['\"])(.*?)\\1|\\w+(?!=)/g) || []\r\n\r\n for (const attr of attrs) {\r\n // Check if attribute has quotes\r\n if (attr.includes('=')) {\r\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\r\n const [name, ...rest] = attr.split('=')\r\n const value = rest.join('=') // Rejoin in case value contains =\r\n\r\n // Check for proper quote matching\r\n if (\r\n !(\r\n (value.startsWith('\"') && value.endsWith('\"')) ||\r\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\r\n )\r\n ) {\r\n return false\r\n }\r\n\r\n // Extract the quote character used\r\n const quoteChar = value[0]\r\n // Check if there are any unescaped quotes of the same type inside the value\r\n const valueContent = value.slice(1, -1)\r\n if (valueContent.includes(quoteChar)) {\r\n return false\r\n }\r\n }\r\n }\r\n }\r\n\r\n // Additional check for unclosed quotes before >\r\n const unclosedQuoteCheck = /<[^>]*?['\"][^'\"]*>/g\r\n const potentiallyUnclosedTags = htmlString.match(unclosedQuoteCheck) || []\r\n for (const tag of potentiallyUnclosedTags) {\r\n let inQuote = false\r\n let quoteChar = ''\r\n for (let i = 0; i < tag.length; i++) {\r\n if (tag[i] === \"'\" || tag[i] === '\"') {\r\n if (!inQuote) {\r\n inQuote = true\r\n quoteChar = tag[i]\r\n } else if (tag[i] === quoteChar) {\r\n inQuote = false\r\n }\r\n }\r\n }\r\n if (inQuote) {\r\n return false\r\n }\r\n }\r\n\r\n return true\r\n}\r\n\r\n// Check if self-closing tags are valid\r\nconst validateSelfClosingTags = (htmlString: string) => {\r\n const selfClosingTags = [...htmlString.matchAll(/<([a-z]+)(\\s[^>]*)\\/>/gi)]\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\r\n for (const [_, tag, attributes] of selfClosingTags) {\r\n const attrRegex = /(\\w+)=[\"']([^\"']*)[\"']/g\r\n const attrs = [...attributes.matchAll(attrRegex)]\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\r\n for (const [_, attrName, attrValue] of attrs) {\r\n if (!attrName || !attrValue) {\r\n return false // Invalid attribute format\r\n }\r\n }\r\n\r\n // Check for unclosed attributes\r\n const unclosedAttrRegex = /(\\w+)=[\"'][^\"']*$/g\r\n const unclosedAttrs = [...attributes.matchAll(unclosedAttrRegex)]\r\n\r\n if (unclosedAttrs.length > 0) {\r\n return false // Unclosed attribute in self-closing tag\r\n }\r\n }\r\n\r\n return true // Self-closing tags are valid\r\n}\r\n\r\n// Check for inline styles and ensure they follow a valid format\r\nconst validateInlineStyles = (htmlString: string) => {\r\n const styleRegex = /style=[\"']([^\"']*)[\"']/gi\r\n const styles = [...htmlString.matchAll(styleRegex)]\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\r\n for (const [_, styleContent] of styles) {\r\n // A basic check for properly formatted style declarations (e.g., \"property: value;\")\r\n const styleRules = styleContent.split(';')\r\n for (const rule of styleRules) {\r\n if (rule.trim()) {\r\n const [property, value] = rule.split(':')\r\n if (!property || !value) {\r\n return false // Invalid style declaration\r\n }\r\n }\r\n }\r\n }\r\n\r\n return true // Inline styles are valid\r\n}\r\n\r\n// Main function that calls the smaller subfunctions\r\nconst validateHtmlBasic = (htmlString: string) => {\r\n return (\r\n validateTags(htmlString) &&\r\n validateAttributes(htmlString) &&\r\n validateSelfClosingTags(htmlString) &&\r\n validateInlineStyles(htmlString)\r\n )\r\n}\r\n\r\n////\r\n\r\nconst SafeHtmlRenderer = ({\r\n html,\r\n className,\r\n withoutContentClass,\r\n}: {\r\n html: string\r\n className?: string\r\n withoutContentClass?: boolean\r\n}) => {\r\n const isValid = validateHtmlBasic(html)\r\n // console.log(\"isValidHTML\", isValid);\r\n // TODO: add send error to TG\r\n\r\n if (!isValid) {\r\n console.error('Invalid HTML content:', html)\r\n return <div style={{ color: 'red' }}>Invalid HTML content</div>\r\n }\r\n\r\n return (\r\n <div\r\n className={[!withoutContentClass && 'content', className].filter(Boolean).join(' ')}\r\n dangerouslySetInnerHTML={{ __html: html }}\r\n />\r\n )\r\n}\r\n\r\nexport default SafeHtmlRenderer\r\n"],"names":["validateTags","htmlString","selfClosingTags","tagPattern","tagStack","match","fullTag","tagName","validateAttributes","attributeRegex","matches","fullMatch","attributes","attrs","attr","name","rest","value","quoteChar","unclosedQuoteCheck","potentiallyUnclosedTags","tag","inQuote","i","validateSelfClosingTags","_","attrRegex","attrName","attrValue","unclosedAttrRegex","validateInlineStyles","styleRegex","styles","styleContent","styleRules","rule","property","validateHtmlBasic","SafeHtmlRenderer","html","className","withoutContentClass","React","SafeHtmlRenderer$1"],"mappings":"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"}
1
+ {"version":3,"file":"index.es19.js","sources":["../src/components/SafeHtml.tsx"],"sourcesContent":["import React from 'react'\r\n\r\n// Check if tags are correctly opened and closed\r\nconst validateTags = (htmlString: string) => {\r\n // List of known self-closing tags\r\n const selfClosingTags = new Set(['br', 'img', 'input', 'hr', 'meta', 'link'])\r\n\r\n const tagPattern = /<\\/?([a-z]+)(\\s[^>]*)?>/gi\r\n const tagStack: string[] = []\r\n let match\r\n\r\n while ((match = tagPattern.exec(htmlString)) !== null) {\r\n const fullTag = match[0]\r\n const tagName = match[1].toLowerCase()\r\n\r\n // Check for incorrect </br> usage\r\n if (fullTag === '</br>') {\r\n return false // </br> is invalid HTML\r\n }\r\n\r\n if (fullTag.startsWith('</')) {\r\n // Closing tag\r\n if (selfClosingTags.has(tagName)) {\r\n return false // Self-closing tags shouldn't have closing tags\r\n }\r\n if (tagStack.length === 0) return false\r\n const lastTag = tagStack.pop()\r\n if (lastTag !== tagName) return false\r\n } else {\r\n // Opening tag\r\n if (!selfClosingTags.has(tagName) && !fullTag.endsWith('/>')) {\r\n tagStack.push(tagName)\r\n }\r\n }\r\n }\r\n\r\n return tagStack.length === 0\r\n}\r\n\r\nconst validateAttributes = (htmlString: string) => {\r\n const attributeRegex = /<[a-z]+\\s+([^>]+)>/gi\r\n const matches = htmlString.matchAll(attributeRegex)\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\r\n for (const [fullMatch, attributes] of matches) {\r\n // Split attributes by space, but keep quoted values together\r\n const attrs = attributes.match(/\\w+\\s*=\\s*(['\"])(.*?)\\1|\\w+(?!=)/g) || []\r\n\r\n for (const attr of attrs) {\r\n // Check if attribute has quotes\r\n if (attr.includes('=')) {\r\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\r\n const [name, ...rest] = attr.split('=')\r\n const value = rest.join('=') // Rejoin in case value contains =\r\n\r\n // Check for proper quote matching\r\n if (\r\n !(\r\n (value.startsWith('\"') && value.endsWith('\"')) ||\r\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\r\n )\r\n ) {\r\n return false\r\n }\r\n\r\n // Extract the quote character used\r\n const quoteChar = value[0]\r\n // Check if there are any unescaped quotes of the same type inside the value\r\n const valueContent = value.slice(1, -1)\r\n if (valueContent.includes(quoteChar)) {\r\n return false\r\n }\r\n }\r\n }\r\n }\r\n\r\n // Additional check for unclosed quotes before >\r\n const unclosedQuoteCheck = /<[^>]*?['\"][^'\"]*>/g\r\n const potentiallyUnclosedTags = htmlString.match(unclosedQuoteCheck) || []\r\n for (const tag of potentiallyUnclosedTags) {\r\n let inQuote = false\r\n let quoteChar = ''\r\n for (let i = 0; i < tag.length; i++) {\r\n if (tag[i] === \"'\" || tag[i] === '\"') {\r\n if (!inQuote) {\r\n inQuote = true\r\n quoteChar = tag[i]\r\n } else if (tag[i] === quoteChar) {\r\n inQuote = false\r\n }\r\n }\r\n }\r\n if (inQuote) {\r\n return false\r\n }\r\n }\r\n\r\n return true\r\n}\r\n\r\n// Check if self-closing tags are valid\r\nconst validateSelfClosingTags = (htmlString: string) => {\r\n const selfClosingTags = [...htmlString.matchAll(/<([a-z]+)(\\s[^>]*)\\/>/gi)]\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\r\n for (const [_, tag, attributes] of selfClosingTags) {\r\n if (attributes) {\r\n const attrRegex = /(\\w+)=[\"']([^\"']*)[\"']/g\r\n const attrs = [...attributes.matchAll(attrRegex)]\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\r\n for (const [__, attrName, attrValue] of attrs) {\r\n if (!attrName || !attrValue) {\r\n return false // Invalid attribute format\r\n }\r\n }\r\n // Check for unclosed attributes\r\n const unclosedAttrRegex = /(\\w+)=[\"'][^\"']*$/g\r\n const unclosedAttrs = [...attributes.matchAll(unclosedAttrRegex)]\r\n if (unclosedAttrs.length > 0) {\r\n return false // Unclosed attribute in self-closing tag\r\n }\r\n }\r\n }\r\n\r\n return true // Self-closing tags are valid\r\n}\r\n\r\n// Check for inline styles and ensure they follow a valid format\r\nconst validateInlineStyles = (htmlString: string) => {\r\n const styleRegex = /style=[\"']([^\"']*)[\"']/gi\r\n const styles = [...htmlString.matchAll(styleRegex)]\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\r\n for (const [_, styleContent] of styles) {\r\n // A basic check for properly formatted style declarations (e.g., \"property: value;\")\r\n const styleRules = styleContent.split(';')\r\n for (const rule of styleRules) {\r\n if (rule.trim()) {\r\n const [property, value] = rule.split(':').map((s) => s.trim())\r\n if (!property || !value) {\r\n return false // Invalid style declaration\r\n }\r\n }\r\n }\r\n }\r\n\r\n return true // Inline styles are valid\r\n}\r\n\r\n// Main function that calls the smaller subfunctions\r\nconst validateHtmlBasic = (htmlString: string) => {\r\n return (\r\n validateTags(htmlString) &&\r\n validateAttributes(htmlString) &&\r\n validateSelfClosingTags(htmlString) &&\r\n validateInlineStyles(htmlString)\r\n )\r\n}\r\n\r\n// Utility to extract plain text from HTML string\r\nconst htmlToText = (html: string): string => {\r\n const entityMap = {\r\n nbsp: ' ',\r\n amp: '&',\r\n lt: '<',\r\n gt: '>',\r\n quot: '\"',\r\n } as const // `as const` makes TypeScript infer literal types and enables key inference\r\n\r\n type EntityKey = keyof typeof entityMap\r\n\r\n const isValidEntity = (name: string): name is EntityKey => {\r\n return Object.prototype.hasOwnProperty.call(entityMap, name)\r\n }\r\n\r\n return html\r\n .replace(/<[^>]*>/g, '') // Strip HTML\r\n .replace(/&([a-z]+);/g, (_, name: string) => {\r\n return isValidEntity(name) ? entityMap[name] : ''\r\n })\r\n .replace(/\\s+/g, ' ') // Normalize whitespace\r\n .trim()\r\n}\r\n\r\n// Utility to truncate text safely\r\nconst truncateText = (text: string, maxLength: number): string => {\r\n if (text.length <= maxLength) return text\r\n return text.slice(0, maxLength).trim() + '...'\r\n}\r\n\r\nconst SafeHtmlRenderer = ({\r\n html,\r\n className,\r\n withoutContentClass,\r\n truncateLength,\r\n}: {\r\n html: string\r\n className?: string\r\n withoutContentClass?: boolean\r\n truncateLength?: number\r\n}) => {\r\n const isValid = validateHtmlBasic(html)\r\n // console.log(\"isValidHTML\", isValid);\r\n // TODO: add send error to TG\r\n\r\n if (!isValid) {\r\n console.error('Invalid HTML content:', html)\r\n return <div style={{ color: 'red' }}>Некорректное HTML содержимое</div>\r\n }\r\n\r\n // If truncateLength is provided, extract text, truncate, and render as plain HTML-safe text\r\n const processedHtml = truncateLength\r\n ? truncateText(htmlToText(html), truncateLength)\r\n : html\r\n\r\n return (\r\n <div\r\n className={[!withoutContentClass && 'content', className]\r\n .filter(Boolean)\r\n .join(' ')}\r\n dangerouslySetInnerHTML={{ __html: processedHtml }}\r\n />\r\n )\r\n}\r\n\r\nexport default SafeHtmlRenderer\r\n"],"names":["validateTags","htmlString","selfClosingTags","tagPattern","tagStack","match","fullTag","tagName","validateAttributes","attributeRegex","matches","fullMatch","attributes","attrs","attr","name","rest","value","quoteChar","unclosedQuoteCheck","potentiallyUnclosedTags","tag","inQuote","i","validateSelfClosingTags","_","attrRegex","__","attrName","attrValue","unclosedAttrRegex","validateInlineStyles","styleRegex","styles","styleContent","styleRules","rule","property","s","validateHtmlBasic","htmlToText","html","entityMap","isValidEntity","truncateText","text","maxLength","SafeHtmlRenderer","className","withoutContentClass","truncateLength","processedHtml","React","SafeHtmlRenderer$1"],"mappings":"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"}
package/dist/styles.css CHANGED
@@ -1020,6 +1020,11 @@ body{
1020
1020
  .overflow-x-hidden{
1021
1021
  overflow-x: hidden;
1022
1022
  }
1023
+ .truncate{
1024
+ overflow: hidden;
1025
+ text-overflow: ellipsis;
1026
+ white-space: nowrap;
1027
+ }
1023
1028
  .whitespace-nowrap{
1024
1029
  white-space: nowrap;
1025
1030
  }
package/package.json CHANGED
@@ -13,7 +13,7 @@
13
13
  "type": "git",
14
14
  "url": "git+https://github.com/wowxoxo/precision-ui.git"
15
15
  },
16
- "version": "0.8.35",
16
+ "version": "0.8.37",
17
17
  "type": "module",
18
18
  "module": "./dist/index.es.js",
19
19
  "types": "./dist/index.d.ts",