@sohanemon/utils 5.0.5 → 5.0.7

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,50 @@
1
+ import * as React from "react";
2
+ /**
3
+ * Props for the HtmlInjector component
4
+ */
5
+ type HtmlInjectorProps = Omit<React.ComponentProps<"div">, "dangerouslySetInnerHTML"> & {
6
+ /** The HTML content to inject and render */
7
+ html: string;
8
+ /**
9
+ * Whether to sanitize the HTML content by removing potentially dangerous elements and attributes
10
+ * @default false
11
+ */
12
+ sanitize?: boolean;
13
+ /**
14
+ * Whether to execute script tags found in the HTML content
15
+ * @default true
16
+ */
17
+ executeScripts?: boolean;
18
+ };
19
+ /**
20
+ * A robust component for safely injecting and rendering HTML content with optional script execution.
21
+ *
22
+ * This component provides a safe way to render dynamic HTML content with the following features:
23
+ * - Optional HTML sanitization to remove dangerous elements and attributes
24
+ * - Controlled script execution with proper cleanup
25
+ * - Memory leak prevention by tracking and removing injected scripts
26
+ * - Error handling for malformed HTML and script execution failures
27
+ *
28
+ * @example
29
+ * ```tsx
30
+ * // Basic HTML injection
31
+ * <HtmlInjector html="<p>Hello <strong>World</strong></p>" />
32
+ *
33
+ * // With sanitization enabled
34
+ * <HtmlInjector
35
+ * html="<p>Safe content</p><script>alert('removed')</script>"
36
+ * sanitize={true}
37
+ * />
38
+ *
39
+ * // Disable script execution
40
+ * <HtmlInjector
41
+ * html="<p>Content</p><script>console.log('not executed')</script>"
42
+ * executeScripts={false}
43
+ * />
44
+ * ```
45
+ *
46
+ * @param props - The component props
47
+ * @returns A div element containing the injected HTML content, or null if no HTML is provided
48
+ */
49
+ export declare function HtmlInjector({ className, html, sanitize, executeScripts, ...props }: HtmlInjectorProps): import("react/jsx-runtime").JSX.Element;
50
+ export {};
@@ -0,0 +1,108 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "../functions";
5
+ /**
6
+ * A robust component for safely injecting and rendering HTML content with optional script execution.
7
+ *
8
+ * This component provides a safe way to render dynamic HTML content with the following features:
9
+ * - Optional HTML sanitization to remove dangerous elements and attributes
10
+ * - Controlled script execution with proper cleanup
11
+ * - Memory leak prevention by tracking and removing injected scripts
12
+ * - Error handling for malformed HTML and script execution failures
13
+ *
14
+ * @example
15
+ * ```tsx
16
+ * // Basic HTML injection
17
+ * <HtmlInjector html="<p>Hello <strong>World</strong></p>" />
18
+ *
19
+ * // With sanitization enabled
20
+ * <HtmlInjector
21
+ * html="<p>Safe content</p><script>alert('removed')</script>"
22
+ * sanitize={true}
23
+ * />
24
+ *
25
+ * // Disable script execution
26
+ * <HtmlInjector
27
+ * html="<p>Content</p><script>console.log('not executed')</script>"
28
+ * executeScripts={false}
29
+ * />
30
+ * ```
31
+ *
32
+ * @param props - The component props
33
+ * @returns A div element containing the injected HTML content, or null if no HTML is provided
34
+ */
35
+ export function HtmlInjector({ className, html, sanitize = false, executeScripts = true, ...props }) {
36
+ const injectedScriptsRef = React.useRef([]);
37
+ const containerRef = React.useRef(null);
38
+ React.useEffect(() => {
39
+ // NOTE: Cleanup previously injected scripts
40
+ injectedScriptsRef.current.forEach((script) => {
41
+ if (script.parentNode) {
42
+ script.parentNode.removeChild(script);
43
+ }
44
+ });
45
+ injectedScriptsRef.current = [];
46
+ if (!executeScripts || !html)
47
+ return;
48
+ try {
49
+ const tempContainer = document.createElement("div");
50
+ tempContainer.innerHTML = html;
51
+ const scripts = tempContainer.querySelectorAll("script");
52
+ scripts.forEach((oldScript) => {
53
+ const newScript = document.createElement("script");
54
+ // HACK: Copy text content
55
+ if (oldScript.textContent) {
56
+ newScript.textContent = oldScript.textContent;
57
+ }
58
+ // HACK: Copy all attributes (src, type, async, defer, etc.)
59
+ Array.from(oldScript.attributes).forEach((attr) => {
60
+ newScript.setAttribute(attr.name, attr.value);
61
+ });
62
+ newScript.onerror = (error) => {
63
+ console.error("Script injection error:", error);
64
+ };
65
+ document.body.appendChild(newScript);
66
+ injectedScriptsRef.current.push(newScript);
67
+ });
68
+ }
69
+ catch (error) {
70
+ console.error("HTML injection error:", error);
71
+ }
72
+ }, [html, executeScripts]);
73
+ React.useEffect(() => {
74
+ return () => {
75
+ injectedScriptsRef.current.forEach((script) => {
76
+ if (script.parentNode) {
77
+ script.parentNode.removeChild(script);
78
+ }
79
+ });
80
+ };
81
+ }, []);
82
+ const processedHtml = React.useMemo(() => {
83
+ if (!html)
84
+ return "";
85
+ if (sanitize) {
86
+ // Basic sanitization - remove potentially dangerous elements
87
+ const container = document.createElement("div");
88
+ container.innerHTML = html;
89
+ // Remove script tags if sanitize is enabled
90
+ container.querySelectorAll("script").forEach((script) => script.remove());
91
+ // Remove potentially dangerous attributes
92
+ const dangerousAttrs = ["onclick", "onload", "onerror", "onmouseover"];
93
+ container.querySelectorAll("*").forEach((el) => {
94
+ dangerousAttrs.forEach((attr) => {
95
+ if (el.hasAttribute(attr)) {
96
+ el.removeAttribute(attr);
97
+ }
98
+ });
99
+ });
100
+ return container.innerHTML;
101
+ }
102
+ return html;
103
+ }, [html, sanitize]);
104
+ if (!html) {
105
+ return null;
106
+ }
107
+ return (_jsx("div", { ref: containerRef, className: cn(className), dangerouslySetInnerHTML: { __html: processedHtml }, ...props }));
108
+ }
@@ -1,3 +1,4 @@
1
1
  export { Icon as Iconify } from '@iconify/react';
2
2
  export { MediaWrapper } from './media-wrapper';
3
+ export { HtmlInjector } from './html-injector';
3
4
  export { ResponsiveIndicator, ResponsiveIndicator as TailwindIndicator, } from './responsive-indicator';
@@ -1,4 +1,5 @@
1
1
  'use client';
2
2
  export { Icon as Iconify } from '@iconify/react';
3
3
  export { MediaWrapper } from './media-wrapper';
4
+ export { HtmlInjector } from './html-injector';
4
5
  export { ResponsiveIndicator, ResponsiveIndicator as TailwindIndicator, } from './responsive-indicator';
@@ -33,7 +33,7 @@ export declare function useDebounce<T>(state: T, delay?: number): T;
33
33
  /**
34
34
  * Hook to handle effects with layout synchronization in the browser.
35
35
  */
36
- export declare const useIsomorphicEffect: typeof React.useLayoutEffect;
36
+ export declare const useIsomorphicEffect: typeof React.useEffect;
37
37
  /**
38
38
  * Hook to invoke a callback after a specified timeout.
39
39
  * @param callback - The callback function to invoke.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sohanemon/utils",
3
- "version": "5.0.5",
3
+ "version": "5.0.7",
4
4
  "author": "Sohan Emon <sohanemon@outlook.com>",
5
5
  "description": "",
6
6
  "type": "module",