@sohanemon/utils 6.2.8 → 6.3.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/dist/components.d.ts +84 -0
- package/dist/components.js +1 -0
- package/dist/hooks-hkNH7WgA.js +1 -0
- package/dist/hooks.d.ts +309 -0
- package/dist/hooks.js +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +665 -0
- package/dist/index.d.ts +665 -2
- package/dist/index.js +1 -2
- package/package.json +49 -32
- package/dist/components/html-injector.d.ts +0 -50
- package/dist/components/html-injector.js +0 -108
- package/dist/components/index.d.ts +0 -5
- package/dist/components/index.js +0 -7
- package/dist/components/media-wrapper.d.ts +0 -10
- package/dist/components/media-wrapper.js +0 -14
- package/dist/components/responsive-indicator.d.ts +0 -2
- package/dist/components/responsive-indicator.js +0 -68
- package/dist/components/scrollable-marker.d.ts +0 -1
- package/dist/components/scrollable-marker.js +0 -56
- package/dist/functions/cookie.d.ts +0 -6
- package/dist/functions/cookie.js +0 -22
- package/dist/functions/deepmerge.d.ts +0 -59
- package/dist/functions/deepmerge.js +0 -116
- package/dist/functions/hydrate.d.ts +0 -15
- package/dist/functions/hydrate.js +0 -31
- package/dist/functions/index.d.ts +0 -8
- package/dist/functions/index.js +0 -8
- package/dist/functions/object.d.ts +0 -93
- package/dist/functions/object.js +0 -67
- package/dist/functions/poll.d.ts +0 -38
- package/dist/functions/poll.js +0 -69
- package/dist/functions/schedule.d.ts +0 -12
- package/dist/functions/schedule.js +0 -29
- package/dist/functions/shield.d.ts +0 -18
- package/dist/functions/shield.js +0 -15
- package/dist/functions/utils.d.ts +0 -243
- package/dist/functions/utils.js +0 -439
- package/dist/hooks/action.d.ts +0 -20
- package/dist/hooks/action.js +0 -84
- package/dist/hooks/async.d.ts +0 -30
- package/dist/hooks/async.js +0 -82
- package/dist/hooks/index.d.ts +0 -192
- package/dist/hooks/index.js +0 -533
- package/dist/hooks/schedule.d.ts +0 -36
- package/dist/hooks/schedule.js +0 -68
- package/dist/types/gates.d.ts +0 -133
- package/dist/types/gates.js +0 -1
- package/dist/types/guards.d.ts +0 -6
- package/dist/types/guards.js +0 -29
- package/dist/types/index.d.ts +0 -3
- package/dist/types/index.js +0 -3
- package/dist/types/utilities.d.ts +0 -62
- package/dist/types/utilities.js +0 -1
package/package.json
CHANGED
|
@@ -1,52 +1,48 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sohanemon/utils",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.3.0",
|
|
4
4
|
"author": "Sohan Emon <sohanemon@outlook.com>",
|
|
5
5
|
"description": "",
|
|
6
6
|
"type": "module",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
7
10
|
"source": "./src/index.ts",
|
|
8
|
-
"
|
|
9
|
-
"
|
|
11
|
+
"types": "./dist/index.d.cts",
|
|
12
|
+
"main": "./dist/index.cjs",
|
|
13
|
+
"module": "./dist/index.js",
|
|
10
14
|
"exports": {
|
|
11
|
-
".":
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"./
|
|
16
|
-
"./types": "./dist/types/index.js"
|
|
15
|
+
".": {
|
|
16
|
+
"import": "./dist/index.js",
|
|
17
|
+
"require": "./dist/index.cjs"
|
|
18
|
+
},
|
|
19
|
+
"./package.json": "./package.json"
|
|
17
20
|
},
|
|
18
21
|
"typesVersions": {
|
|
19
22
|
"*": {
|
|
20
|
-
"core": [
|
|
21
|
-
"dist/index.d.ts"
|
|
22
|
-
],
|
|
23
|
-
"functions": [
|
|
24
|
-
"dist/functions/index.d.ts"
|
|
25
|
-
],
|
|
26
|
-
"types": [
|
|
27
|
-
"dist/types/index.d.ts"
|
|
28
|
-
],
|
|
29
23
|
"hooks": [
|
|
30
|
-
"dist/hooks
|
|
24
|
+
"dist/hooks.d.ts"
|
|
31
25
|
],
|
|
32
26
|
"components": [
|
|
33
|
-
"dist/components
|
|
27
|
+
"dist/components.d.ts"
|
|
34
28
|
]
|
|
35
29
|
}
|
|
36
30
|
},
|
|
37
|
-
"files": [
|
|
38
|
-
"dist",
|
|
39
|
-
"README.md"
|
|
40
|
-
],
|
|
41
31
|
"scripts": {
|
|
42
|
-
"
|
|
43
|
-
"
|
|
32
|
+
"build": "tsdown",
|
|
33
|
+
"dev": "tsdown --watch",
|
|
44
34
|
"test": "vitest",
|
|
45
35
|
"test:run": "vitest run",
|
|
46
36
|
"test:list": "vitest list",
|
|
47
37
|
"test:log": "vitest run --reporter verbose",
|
|
48
38
|
"test:ui": "vitest --ui",
|
|
49
|
-
"
|
|
39
|
+
"check": "biome check .",
|
|
40
|
+
"fix": "biome check --diagnostic-level=error --write .",
|
|
41
|
+
"lint": "biome lint .",
|
|
42
|
+
"lint:fix": "biome lint --write --unsafe .",
|
|
43
|
+
"format": "biome format .",
|
|
44
|
+
"format:write": "biome format . --write",
|
|
45
|
+
"typecheck": "tsgo --noEmit",
|
|
50
46
|
"release": "tsgo --noEmit && npm run build && npm publish"
|
|
51
47
|
},
|
|
52
48
|
"keywords": [
|
|
@@ -54,19 +50,40 @@
|
|
|
54
50
|
"cn"
|
|
55
51
|
],
|
|
56
52
|
"license": "ISC",
|
|
57
|
-
"homepage": "https://
|
|
58
|
-
"
|
|
53
|
+
"homepage": "https://sohanjs.web.app/utils",
|
|
54
|
+
"repository": {
|
|
55
|
+
"type": "git",
|
|
56
|
+
"url": "git+https://github.com/sohanemon/utils.git"
|
|
57
|
+
},
|
|
58
|
+
"bugs": {
|
|
59
|
+
"url": "https://github.com/sohanemon/utils/issues"
|
|
60
|
+
},
|
|
59
61
|
"devDependencies": {
|
|
60
|
-
"@
|
|
61
|
-
"@
|
|
62
|
+
"@biomejs/biome": "2.3.8",
|
|
63
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
64
|
+
"@testing-library/react": "^16.3.0",
|
|
65
|
+
"@tsconfig/strictest": "^2.0.8",
|
|
66
|
+
"@types/node": "^24.10.1",
|
|
67
|
+
"@types/react": "^19.2.5",
|
|
68
|
+
"@types/react-dom": "^19.2.3",
|
|
69
|
+
"@vitejs/plugin-react": "^5.1.1",
|
|
62
70
|
"@vitest/ui": "^4.0.14",
|
|
71
|
+
"tsdown": "^0.16.4",
|
|
63
72
|
"typescript": "^5.9.3",
|
|
73
|
+
"vite": "npm:rolldown-vite@^7.2.5",
|
|
64
74
|
"vitest": "^4.0.14"
|
|
65
75
|
},
|
|
66
76
|
"dependencies": {
|
|
67
77
|
"@iconify/react": "^6.0.2",
|
|
68
78
|
"clsx": "^2.1.1",
|
|
69
|
-
"
|
|
79
|
+
"jsdom": "^27.2.0",
|
|
70
80
|
"tailwind-merge": "^3.3.1"
|
|
81
|
+
},
|
|
82
|
+
"peerDependencies": {
|
|
83
|
+
"react": "^19.2.0",
|
|
84
|
+
"react-dom": "^19.2.0"
|
|
85
|
+
},
|
|
86
|
+
"publishConfig": {
|
|
87
|
+
"access": "public"
|
|
71
88
|
}
|
|
72
89
|
}
|
|
@@ -1,50 +0,0 @@
|
|
|
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 {};
|
|
@@ -1,108 +0,0 @@
|
|
|
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,5 +0,0 @@
|
|
|
1
|
-
export { Icon as Iconify } from '@iconify/react';
|
|
2
|
-
export { HtmlInjector } from './html-injector';
|
|
3
|
-
export { MediaWrapper } from './media-wrapper';
|
|
4
|
-
export { ResponsiveIndicator, ResponsiveIndicator as TailwindIndicator, } from './responsive-indicator';
|
|
5
|
-
export { ScrollableMarker } from './scrollable-marker';
|
package/dist/components/index.js
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
//NOTE: It's currently unsupported to use "export *" in a client boundary
|
|
3
|
-
export { Icon as Iconify } from '@iconify/react';
|
|
4
|
-
export { HtmlInjector } from './html-injector';
|
|
5
|
-
export { MediaWrapper } from './media-wrapper';
|
|
6
|
-
export { ResponsiveIndicator, ResponsiveIndicator as TailwindIndicator, } from './responsive-indicator';
|
|
7
|
-
export { ScrollableMarker } from './scrollable-marker';
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
type BreakPoints = 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'max-sm' | 'max-md' | 'max-lg' | 'max-xl' | 'max-2xl';
|
|
3
|
-
type MediaWrapperProps = React.ComponentProps<'div'> & {
|
|
4
|
-
breakpoint: BreakPoints;
|
|
5
|
-
as?: React.ElementType;
|
|
6
|
-
fallback?: React.ElementType;
|
|
7
|
-
classNameFallback?: string;
|
|
8
|
-
};
|
|
9
|
-
export declare function MediaWrapper({ breakpoint, as, fallback, classNameFallback, className: classNameOriginal, ...props }: MediaWrapperProps): import("react/jsx-runtime").JSX.Element;
|
|
10
|
-
export {};
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import * as React from 'react';
|
|
4
|
-
import { useMediaQuery } from '../hooks';
|
|
5
|
-
export function MediaWrapper({ breakpoint, as = 'div', fallback = React.Fragment, classNameFallback, className: classNameOriginal, ...props }) {
|
|
6
|
-
const overMedia = useMediaQuery(breakpoint.split('-').pop());
|
|
7
|
-
const isMax = breakpoint.startsWith('max');
|
|
8
|
-
const useFallback = overMedia === isMax;
|
|
9
|
-
// Conditionally determining which component to render,
|
|
10
|
-
// and what className should be passed to it.
|
|
11
|
-
const Wrapper = useFallback ? fallback : as;
|
|
12
|
-
const className = useFallback ? classNameFallback : classNameOriginal;
|
|
13
|
-
return _jsx(Wrapper, { className: className, ...props });
|
|
14
|
-
}
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import * as React from 'react';
|
|
4
|
-
import { isSSR } from '../functions';
|
|
5
|
-
export const ResponsiveIndicator = () => {
|
|
6
|
-
const [viewportWidth, setViewportWidth] = React.useState(isSSR ? 0 : window.innerWidth);
|
|
7
|
-
const [position, setPosition] = React.useState(0); // State to manage button position
|
|
8
|
-
React.useEffect(() => {
|
|
9
|
-
const handleResize = () => {
|
|
10
|
-
setViewportWidth(window.innerWidth);
|
|
11
|
-
};
|
|
12
|
-
window.addEventListener('resize', handleResize);
|
|
13
|
-
return () => {
|
|
14
|
-
window.removeEventListener('resize', handleResize);
|
|
15
|
-
};
|
|
16
|
-
}, []);
|
|
17
|
-
// Function to handle button click
|
|
18
|
-
const handleClick = () => {
|
|
19
|
-
setPosition((prevPosition) => (prevPosition + 1) % 4); // Cycle through positions
|
|
20
|
-
};
|
|
21
|
-
let text = '';
|
|
22
|
-
if (viewportWidth < 640) {
|
|
23
|
-
text = 'xs';
|
|
24
|
-
}
|
|
25
|
-
else if (viewportWidth >= 640 && viewportWidth < 768) {
|
|
26
|
-
text = 'sm';
|
|
27
|
-
}
|
|
28
|
-
else if (viewportWidth >= 768 && viewportWidth < 1024) {
|
|
29
|
-
text = 'md';
|
|
30
|
-
}
|
|
31
|
-
else if (viewportWidth >= 1024 && viewportWidth < 1280) {
|
|
32
|
-
text = 'lg';
|
|
33
|
-
}
|
|
34
|
-
else if (viewportWidth >= 1280 && viewportWidth < 1536) {
|
|
35
|
-
text = 'xl';
|
|
36
|
-
}
|
|
37
|
-
else {
|
|
38
|
-
text = '2xl';
|
|
39
|
-
}
|
|
40
|
-
// Define positions
|
|
41
|
-
const positions = [
|
|
42
|
-
{ bottom: '2rem', left: '2rem' }, // Bottom left
|
|
43
|
-
{ bottom: '2rem', right: '2rem' }, // Bottom right
|
|
44
|
-
{ top: '2rem', right: '2rem' }, // Top right
|
|
45
|
-
{ top: '2rem', left: '2rem' }, // Top left
|
|
46
|
-
];
|
|
47
|
-
const buttonStyle = {
|
|
48
|
-
position: 'fixed',
|
|
49
|
-
zIndex: 50,
|
|
50
|
-
display: 'grid',
|
|
51
|
-
height: '2.5rem',
|
|
52
|
-
width: '2.5rem',
|
|
53
|
-
borderRadius: '50%',
|
|
54
|
-
placeContent: 'center',
|
|
55
|
-
backgroundColor: '#2d3748',
|
|
56
|
-
fontFamily: 'Courier New, Courier, monospace',
|
|
57
|
-
fontSize: '1rem',
|
|
58
|
-
color: '#ffffff',
|
|
59
|
-
border: '2px solid #4a5568',
|
|
60
|
-
boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
|
|
61
|
-
padding: '0.5rem',
|
|
62
|
-
transition: 'all 0.2s ease-in-out',
|
|
63
|
-
...positions[position], // Apply the current position
|
|
64
|
-
};
|
|
65
|
-
if (process.env.NODE_ENV === 'production')
|
|
66
|
-
return null;
|
|
67
|
-
return (_jsx("button", { type: "button", style: buttonStyle, onClick: handleClick, children: text }));
|
|
68
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function ScrollableMarker(): any;
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { useScheduledEffect } from '../hooks/schedule';
|
|
3
|
-
export function ScrollableMarker() {
|
|
4
|
-
useScheduledEffect(() => {
|
|
5
|
-
const root = document.body;
|
|
6
|
-
if (!root)
|
|
7
|
-
return;
|
|
8
|
-
const isScrollable = (el) => {
|
|
9
|
-
const style = getComputedStyle(el);
|
|
10
|
-
if (style.overflow === 'hidden' &&
|
|
11
|
-
style.overflowY === 'hidden' &&
|
|
12
|
-
style.overflowX === 'hidden')
|
|
13
|
-
return false;
|
|
14
|
-
const canScrollY = (style.overflowY === 'auto' || style.overflowY === 'scroll') &&
|
|
15
|
-
el.scrollHeight > el.clientHeight;
|
|
16
|
-
const canScrollX = (style.overflowX === 'auto' || style.overflowX === 'scroll') &&
|
|
17
|
-
el.scrollWidth > el.clientWidth;
|
|
18
|
-
return canScrollY || canScrollX;
|
|
19
|
-
};
|
|
20
|
-
const markScrollable = (el) => {
|
|
21
|
-
if (isScrollable(el))
|
|
22
|
-
el.dataset.scrollable = 'true';
|
|
23
|
-
else
|
|
24
|
-
delete el.dataset.scrollable;
|
|
25
|
-
};
|
|
26
|
-
const scanTree = (node) => {
|
|
27
|
-
markScrollable(node);
|
|
28
|
-
for (let i = 0; i < node.children.length; i++) {
|
|
29
|
-
const child = node.children[i];
|
|
30
|
-
scanTree(child);
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
requestIdleCallback(() => scanTree(root));
|
|
34
|
-
const observer = new MutationObserver((mutations) => {
|
|
35
|
-
for (const m of mutations) {
|
|
36
|
-
if (m.type === 'childList') {
|
|
37
|
-
m.addedNodes.forEach((n) => {
|
|
38
|
-
if (n instanceof HTMLElement)
|
|
39
|
-
scanTree(n);
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
else if (m.type === 'attributes' && m.target instanceof HTMLElement) {
|
|
43
|
-
markScrollable(m.target);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
observer.observe(root, {
|
|
48
|
-
subtree: true,
|
|
49
|
-
childList: true,
|
|
50
|
-
attributes: true,
|
|
51
|
-
attributeFilter: ['style', 'class'],
|
|
52
|
-
});
|
|
53
|
-
return () => observer.disconnect();
|
|
54
|
-
}, []);
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
export declare const setClientSideCookie: (name: string, value: string, days?: number, path?: string) => void;
|
|
2
|
-
export declare const deleteClientSideCookie: (name: string, path?: string) => void;
|
|
3
|
-
export declare const hasClientSideCookie: (name: string) => boolean;
|
|
4
|
-
export declare const getClientSideCookie: (name: string) => {
|
|
5
|
-
value: string;
|
|
6
|
-
};
|
package/dist/functions/cookie.js
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
export const setClientSideCookie = (name, value, days, path = '/') => {
|
|
2
|
-
let expires = '';
|
|
3
|
-
if (days) {
|
|
4
|
-
const date = new Date();
|
|
5
|
-
date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
|
|
6
|
-
expires = `; expires=${date.toUTCString()}`;
|
|
7
|
-
}
|
|
8
|
-
document.cookie = `${name}=${value || ''}${expires}; path=${path}`;
|
|
9
|
-
};
|
|
10
|
-
export const deleteClientSideCookie = (name, path = '/') => {
|
|
11
|
-
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=${path}`;
|
|
12
|
-
};
|
|
13
|
-
export const hasClientSideCookie = (name) => {
|
|
14
|
-
return document.cookie.split('; ').some((row) => row.startsWith(`${name}=`));
|
|
15
|
-
};
|
|
16
|
-
export const getClientSideCookie = (name) => {
|
|
17
|
-
const cookieValue = document.cookie
|
|
18
|
-
.split('; ')
|
|
19
|
-
.find((row) => row.startsWith(`${name}=`))
|
|
20
|
-
?.split('=')[1];
|
|
21
|
-
return { value: cookieValue };
|
|
22
|
-
};
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
type TAllKeys<T> = T extends any ? keyof T : never;
|
|
2
|
-
type TIndexValue<T, K extends PropertyKey, D = never> = T extends any ? K extends keyof T ? T[K] : D : never;
|
|
3
|
-
type TPartialKeys<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>> extends infer O ? {
|
|
4
|
-
[P in keyof O]: O[P];
|
|
5
|
-
} : never;
|
|
6
|
-
type TFunction = (...a: any[]) => any;
|
|
7
|
-
type TPrimitives = string | number | boolean | bigint | symbol | Date | TFunction;
|
|
8
|
-
type TMerged<T> = [T] extends [Array<any>] ? {
|
|
9
|
-
[K in keyof T]: TMerged<T[K]>;
|
|
10
|
-
} : [T] extends [TPrimitives] ? T : [T] extends [object] ? TPartialKeys<{
|
|
11
|
-
[K in TAllKeys<T>]: TMerged<TIndexValue<T, K>>;
|
|
12
|
-
}, never> : T;
|
|
13
|
-
/**
|
|
14
|
-
* Deeply merges multiple objects, with later sources taking precedence.
|
|
15
|
-
* Handles nested objects, arrays, and special object types with circular reference detection.
|
|
16
|
-
*
|
|
17
|
-
* Features:
|
|
18
|
-
* - Deep merging of nested objects
|
|
19
|
-
* - Configurable array merging strategies
|
|
20
|
-
* - Circular reference detection and handling
|
|
21
|
-
* - Support for symbols and special objects (Date, RegExp, etc.)
|
|
22
|
-
* - Type-safe with improved generics
|
|
23
|
-
* - Optional cloning to avoid mutation
|
|
24
|
-
* - Custom merge functions for specific keys
|
|
25
|
-
*
|
|
26
|
-
* @template T - The target object type
|
|
27
|
-
* @param target - The target object to merge into
|
|
28
|
-
* @param sources - Source objects to merge from (can have additional properties)
|
|
29
|
-
* @param options - Configuration options
|
|
30
|
-
* @param options.arrayMerge - How to merge arrays: 'replace' (default), 'concat', or 'merge'
|
|
31
|
-
* @param options.clone - Whether to clone the target (default: true)
|
|
32
|
-
* @param options.customMerge - Custom merge function for specific keys
|
|
33
|
-
* @param options.maxDepth - Maximum recursion depth (default: 100)
|
|
34
|
-
* @returns The merged object with proper typing
|
|
35
|
-
*
|
|
36
|
-
* @example
|
|
37
|
-
* // Basic merge
|
|
38
|
-
* deepmerge({ a: 1 }, { b: 2 }) // { a: 1, b: 2 }
|
|
39
|
-
*
|
|
40
|
-
* @example
|
|
41
|
-
* // Nested merge
|
|
42
|
-
* deepmerge({ a: { x: 1 } }, { a: { y: 2 } }) // { a: { x: 1, y: 2 } }
|
|
43
|
-
*
|
|
44
|
-
* @example
|
|
45
|
-
* // Array concat
|
|
46
|
-
* deepmerge({ arr: [1] }, { arr: [2] }, { arrayMerge: 'concat' }) // { arr: [1, 2] }
|
|
47
|
-
*
|
|
48
|
-
* @example
|
|
49
|
-
* // Sources with extra properties
|
|
50
|
-
* deepmerge({ a: 1 }, { b: 2, c: 3 }) // { a: 1, b: 2, c: 3 }
|
|
51
|
-
*/
|
|
52
|
-
export declare function deepmerge<T extends Record<string, any>, S extends Record<string, any>[]>(target: T, ...sources: S): TMerged<T | S[number]>;
|
|
53
|
-
export declare function deepmerge<T extends Record<string, any>, S extends Record<string, any>[]>(target: T, sources: S, options?: {
|
|
54
|
-
arrayMerge?: 'replace' | 'concat' | 'merge' | ((target: any[], source: any[]) => any[]);
|
|
55
|
-
clone?: boolean;
|
|
56
|
-
customMerge?: (key: string | symbol, targetValue: any, sourceValue: any) => any;
|
|
57
|
-
maxDepth?: number;
|
|
58
|
-
}): TMerged<T | S[number]>;
|
|
59
|
-
export {};
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import { isPlainObject } from '../types';
|
|
2
|
-
export function deepmerge(target, ...args) {
|
|
3
|
-
let sources;
|
|
4
|
-
let options = {};
|
|
5
|
-
// Check if last arg is options object
|
|
6
|
-
const lastArg = args[args.length - 1];
|
|
7
|
-
if (lastArg &&
|
|
8
|
-
typeof lastArg === 'object' &&
|
|
9
|
-
!Array.isArray(lastArg) &&
|
|
10
|
-
(lastArg.arrayMerge !== undefined ||
|
|
11
|
-
lastArg.clone !== undefined ||
|
|
12
|
-
lastArg.customMerge !== undefined ||
|
|
13
|
-
lastArg.maxDepth !== undefined)) {
|
|
14
|
-
options = { ...options, ...lastArg };
|
|
15
|
-
sources = args.slice(0, -1);
|
|
16
|
-
}
|
|
17
|
-
else {
|
|
18
|
-
sources = args;
|
|
19
|
-
}
|
|
20
|
-
const { arrayMerge = 'replace', clone = true, customMerge, maxDepth = 100, } = options;
|
|
21
|
-
const visited = new WeakMap();
|
|
22
|
-
return mergeObjects(target, sources, 0);
|
|
23
|
-
function mergeObjects(target, sources, depth) {
|
|
24
|
-
if (depth >= maxDepth) {
|
|
25
|
-
console.warn(`[deepmerge] Maximum depth ${maxDepth} exceeded. Returning target as-is.`);
|
|
26
|
-
return target;
|
|
27
|
-
}
|
|
28
|
-
if (!isPlainObject(target) && !Array.isArray(target)) {
|
|
29
|
-
// For primitives or special objects, return the last source or target
|
|
30
|
-
for (const source of sources) {
|
|
31
|
-
if (source !== undefined)
|
|
32
|
-
return source;
|
|
33
|
-
}
|
|
34
|
-
return target;
|
|
35
|
-
}
|
|
36
|
-
let result = clone
|
|
37
|
-
? Array.isArray(target)
|
|
38
|
-
? [...target]
|
|
39
|
-
: { ...target }
|
|
40
|
-
: target;
|
|
41
|
-
for (const source of sources) {
|
|
42
|
-
if (source == null)
|
|
43
|
-
continue;
|
|
44
|
-
if (visited.has(source)) {
|
|
45
|
-
// Circular reference, skip
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
48
|
-
visited.set(source, result);
|
|
49
|
-
if (Array.isArray(result) && Array.isArray(source)) {
|
|
50
|
-
result = mergeArrays(result, source, arrayMerge);
|
|
51
|
-
}
|
|
52
|
-
else if (isPlainObject(result) && isPlainObject(source)) {
|
|
53
|
-
const keys = new Set([
|
|
54
|
-
...Object.keys(result),
|
|
55
|
-
...Object.keys(source),
|
|
56
|
-
...Object.getOwnPropertySymbols(result),
|
|
57
|
-
...Object.getOwnPropertySymbols(source),
|
|
58
|
-
]);
|
|
59
|
-
for (const key of keys) {
|
|
60
|
-
const targetValue = result[key];
|
|
61
|
-
const sourceValue = source[key];
|
|
62
|
-
if (customMerge &&
|
|
63
|
-
customMerge(key, targetValue, sourceValue) !== undefined) {
|
|
64
|
-
result[key] = customMerge(key, targetValue, sourceValue);
|
|
65
|
-
}
|
|
66
|
-
else if (isPlainObject(targetValue) && isPlainObject(sourceValue)) {
|
|
67
|
-
result[key] = mergeObjects(targetValue, [sourceValue], depth + 1);
|
|
68
|
-
}
|
|
69
|
-
else if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
|
|
70
|
-
result[key] = mergeArrays(targetValue, sourceValue, arrayMerge);
|
|
71
|
-
}
|
|
72
|
-
else if (sourceValue !== undefined) {
|
|
73
|
-
result[key] = sourceValue;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
else {
|
|
78
|
-
// If types don't match, source takes precedence
|
|
79
|
-
result = source;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return result;
|
|
83
|
-
}
|
|
84
|
-
function mergeArrays(target, source, strategy) {
|
|
85
|
-
if (typeof strategy === 'function') {
|
|
86
|
-
return strategy(target, source);
|
|
87
|
-
}
|
|
88
|
-
switch (strategy) {
|
|
89
|
-
case 'concat':
|
|
90
|
-
return [...target, ...source];
|
|
91
|
-
case 'merge':
|
|
92
|
-
const maxLength = Math.max(target.length, source.length);
|
|
93
|
-
const merged = [];
|
|
94
|
-
for (let i = 0; i < maxLength; i++) {
|
|
95
|
-
if (i < target.length && i < source.length) {
|
|
96
|
-
if (isPlainObject(target[i]) && isPlainObject(source[i])) {
|
|
97
|
-
merged[i] = mergeObjects(target[i], [source[i]], 0);
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
merged[i] = source[i];
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
else if (i < target.length) {
|
|
104
|
-
merged[i] = target[i];
|
|
105
|
-
}
|
|
106
|
-
else {
|
|
107
|
-
merged[i] = source[i];
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
return merged;
|
|
111
|
-
case 'replace':
|
|
112
|
-
default:
|
|
113
|
-
return [...source];
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|