@underverse-ui/underverse 1.0.203 → 1.0.204

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.
Files changed (39) hide show
  1. package/README.md +8 -2
  2. package/api-reference.json +1 -1
  3. package/dist/EmojiPicker-UN6N7YVB.js +14 -0
  4. package/dist/EmojiPicker-UN6N7YVB.js.map +1 -0
  5. package/dist/chunk-4TYW5K4J.js +769 -0
  6. package/dist/chunk-4TYW5K4J.js.map +1 -0
  7. package/dist/chunk-CC3QO2YM.js +14506 -0
  8. package/dist/chunk-CC3QO2YM.js.map +1 -0
  9. package/dist/chunk-CKKLFVOY.js +206 -0
  10. package/dist/chunk-CKKLFVOY.js.map +1 -0
  11. package/dist/chunk-GSBRTFP2.js +2464 -0
  12. package/dist/chunk-GSBRTFP2.js.map +1 -0
  13. package/dist/chunk-KFTYWTJR.js +4481 -0
  14. package/dist/chunk-KFTYWTJR.js.map +1 -0
  15. package/dist/chunk-NSBPE2FW.js +17 -0
  16. package/dist/chunk-NSBPE2FW.js.map +1 -0
  17. package/dist/chunk-QVQTWZ24.js +199 -0
  18. package/dist/chunk-QVQTWZ24.js.map +1 -0
  19. package/dist/chunk-ZUAIBY2Z.js +74 -0
  20. package/dist/chunk-ZUAIBY2Z.js.map +1 -0
  21. package/dist/emojis-I5AMZ3EE.js +8 -0
  22. package/dist/emojis-I5AMZ3EE.js.map +1 -0
  23. package/dist/index.cjs +37014 -35837
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.d.cts +3 -134
  26. package/dist/index.d.ts +3 -134
  27. package/dist/index.js +2801 -25581
  28. package/dist/index.js.map +1 -1
  29. package/dist/lowlight-runtime-Y43Y7YTW.js +11 -0
  30. package/dist/lowlight-runtime-Y43Y7YTW.js.map +1 -0
  31. package/dist/menu-bar-6U5S4PS7.js +947 -0
  32. package/dist/menu-bar-6U5S4PS7.js.map +1 -0
  33. package/dist/ueditor.cjs +23503 -0
  34. package/dist/ueditor.cjs.map +1 -0
  35. package/dist/ueditor.d.cts +136 -0
  36. package/dist/ueditor.d.ts +136 -0
  37. package/dist/ueditor.js +20 -0
  38. package/dist/ueditor.js.map +1 -0
  39. package/package.json +54 -46
@@ -0,0 +1,17 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
+ }) : x)(function(x) {
5
+ if (typeof require !== "undefined") return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
7
+ });
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+
13
+ export {
14
+ __require,
15
+ __export
16
+ };
17
+ //# sourceMappingURL=chunk-NSBPE2FW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,199 @@
1
+ import {
2
+ getPanelBorderRadiusClass,
3
+ useUnderverseUIConfig
4
+ } from "./chunk-KFTYWTJR.js";
5
+ import {
6
+ cn,
7
+ useGlobalI18n
8
+ } from "./chunk-GSBRTFP2.js";
9
+
10
+ // src/components/Modal.tsx
11
+ import * as React from "react";
12
+ import { createPortal } from "react-dom";
13
+ import { X } from "lucide-react";
14
+ import { jsx, jsxs } from "react/jsx-runtime";
15
+ var sizeStyles = {
16
+ sm: "max-w-md",
17
+ md: "max-w-2xl",
18
+ lg: "max-w-4xl",
19
+ xl: "max-w-6xl",
20
+ full: "max-w-full"
21
+ };
22
+ var Modal = ({
23
+ isOpen,
24
+ onClose,
25
+ children,
26
+ title,
27
+ description,
28
+ className,
29
+ contentClassName,
30
+ overlayClassName,
31
+ showCloseButton = true,
32
+ closeOnOverlayClick = true,
33
+ closeOnEsc = true,
34
+ size = "md",
35
+ noPadding = false,
36
+ fullWidth = false,
37
+ width,
38
+ height,
39
+ borderMode
40
+ }) => {
41
+ const gi18n = useGlobalI18n();
42
+ const globalConfig = useUnderverseUIConfig();
43
+ const resolvedBorderMode = borderMode ?? globalConfig.borderMode;
44
+ const [isMounted, setIsMounted] = React.useState(false);
45
+ const [isVisible, setIsVisible] = React.useState(false);
46
+ const [isAnimating, setIsAnimating] = React.useState(true);
47
+ const mouseDownTarget = React.useRef(null);
48
+ const modalContentRef = React.useRef(null);
49
+ React.useEffect(() => {
50
+ setIsMounted(true);
51
+ return () => setIsMounted(false);
52
+ }, []);
53
+ const animationRef = React.useRef(false);
54
+ React.useEffect(() => {
55
+ if (isOpen) {
56
+ if (animationRef.current) return;
57
+ animationRef.current = true;
58
+ setIsVisible(true);
59
+ setIsAnimating(true);
60
+ requestAnimationFrame(() => {
61
+ setIsAnimating(false);
62
+ });
63
+ } else {
64
+ animationRef.current = false;
65
+ if (isVisible) {
66
+ setIsAnimating(true);
67
+ const hideTimer = setTimeout(() => {
68
+ setIsVisible(false);
69
+ }, 200);
70
+ return () => clearTimeout(hideTimer);
71
+ }
72
+ }
73
+ }, [isOpen, isVisible]);
74
+ React.useEffect(() => {
75
+ if (!isOpen || !closeOnEsc) return;
76
+ const handleEscape = (event) => {
77
+ if (event.key === "Escape") {
78
+ onClose();
79
+ }
80
+ };
81
+ document.addEventListener("keydown", handleEscape);
82
+ return () => document.removeEventListener("keydown", handleEscape);
83
+ }, [isOpen, closeOnEsc, onClose]);
84
+ React.useEffect(() => {
85
+ if (isOpen) {
86
+ document.body.style.overflow = "hidden";
87
+ } else {
88
+ document.body.style.overflow = "unset";
89
+ }
90
+ return () => {
91
+ document.body.style.overflow = "unset";
92
+ };
93
+ }, [isOpen]);
94
+ const isInsidePortal = (element) => {
95
+ if (!element) return false;
96
+ let current = element;
97
+ while (current) {
98
+ if (current.hasAttribute?.("data-datepicker") || current.hasAttribute?.("data-popover") || current.hasAttribute?.("data-dropdown") || current.hasAttribute?.("data-dropdown-menu") || current.hasAttribute?.("data-radix-popper-content-wrapper") || current.hasAttribute?.("data-radix-portal") || current.hasAttribute?.("data-tippy-root") || current.getAttribute?.("role") === "listbox" || current.getAttribute?.("role") === "dialog" || current.classList?.contains("datepicker-portal") || current.classList?.contains("popover-portal") || current.classList?.contains("tippy-box") || current.classList?.contains("tippy-content")) {
99
+ return true;
100
+ }
101
+ current = current.parentElement;
102
+ }
103
+ return false;
104
+ };
105
+ const handleOverlayMouseDown = (event) => {
106
+ mouseDownTarget.current = event.target;
107
+ };
108
+ const handleOverlayMouseUp = (event) => {
109
+ const modalContent2 = modalContentRef.current;
110
+ const mouseDownTarget_ = mouseDownTarget.current;
111
+ const mouseUpTarget = event.target;
112
+ if (isInsidePortal(mouseDownTarget_) || isInsidePortal(mouseUpTarget)) {
113
+ mouseDownTarget.current = null;
114
+ return;
115
+ }
116
+ const mouseDownOutside = modalContent2 && !modalContent2.contains(mouseDownTarget_);
117
+ const mouseUpOutside = modalContent2 && !modalContent2.contains(mouseUpTarget);
118
+ if (closeOnOverlayClick && mouseDownOutside && mouseUpOutside) {
119
+ onClose();
120
+ }
121
+ mouseDownTarget.current = null;
122
+ };
123
+ if (!isMounted || !isOpen && !isVisible) {
124
+ return null;
125
+ }
126
+ const maxWidthClass = width ? "max-w-none" : fullWidth ? "max-w-full" : sizeStyles[size];
127
+ const modalContent = /* @__PURE__ */ jsxs(
128
+ "div",
129
+ {
130
+ className: cn("fixed inset-0 z-[9999] flex items-center justify-center p-4 md:p-6", overlayClassName),
131
+ style: { overscrollBehavior: "contain" },
132
+ onMouseDown: handleOverlayMouseDown,
133
+ onMouseUp: handleOverlayMouseUp,
134
+ children: [
135
+ /* @__PURE__ */ jsx(
136
+ "div",
137
+ {
138
+ className: "absolute inset-0 bg-background/80 backdrop-blur-sm transition-opacity duration-200 ease-out",
139
+ style: {
140
+ opacity: isOpen && !isAnimating ? 1 : 0
141
+ }
142
+ }
143
+ ),
144
+ /* @__PURE__ */ jsxs(
145
+ "div",
146
+ {
147
+ ref: modalContentRef,
148
+ className: cn(
149
+ "relative w-full border border-border/40 bg-card text-card-foreground shadow-xl",
150
+ resolvedBorderMode ? getPanelBorderRadiusClass(resolvedBorderMode) : "rounded-2xl md:rounded-3xl",
151
+ "transition-all duration-200 ease-out",
152
+ maxWidthClass,
153
+ fullWidth && "mx-0",
154
+ className
155
+ ),
156
+ style: {
157
+ opacity: isOpen && !isAnimating ? 1 : 0,
158
+ transform: isOpen && !isAnimating ? "scale(1)" : "scale(0.9)",
159
+ // Thêm dòng này để tạo hiệu ứng nảy
160
+ transition: "all 300ms cubic-bezier(0.34, 1.76, 0.64, 1)",
161
+ width,
162
+ height
163
+ },
164
+ onClick: (e) => e.stopPropagation(),
165
+ children: [
166
+ (title || description || showCloseButton) && /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between p-6 pb-0", children: [
167
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
168
+ title && /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold leading-none tracking-tight", children: title }),
169
+ description && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: description })
170
+ ] }),
171
+ showCloseButton && /* @__PURE__ */ jsx(
172
+ "button",
173
+ {
174
+ onClick: onClose,
175
+ "aria-label": gi18n.closeModal ?? "Close modal",
176
+ className: cn(
177
+ "rounded-lg opacity-70 ring-offset-background transition-opacity",
178
+ "hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
179
+ "disabled:pointer-events-none "
180
+ ),
181
+ children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4 cursor-pointer", "aria-hidden": "true" })
182
+ }
183
+ )
184
+ ] }),
185
+ /* @__PURE__ */ jsx("div", { className: cn("p-6", noPadding && "p-0", contentClassName), children })
186
+ ]
187
+ }
188
+ )
189
+ ]
190
+ }
191
+ );
192
+ return typeof window !== "undefined" ? createPortal(modalContent, document.body) : null;
193
+ };
194
+ var Modal_default = Modal;
195
+
196
+ export {
197
+ Modal_default
198
+ };
199
+ //# sourceMappingURL=chunk-QVQTWZ24.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/Modal.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { cn } from \"../utils/cn\";\nimport { X } from \"lucide-react\";\nimport { useGlobalI18n } from \"../contexts/GlobalI18nContext\";\nimport { getBorderRadiusClass, getPanelBorderRadiusClass, type BorderMode } from \"../utils/radius\";\nimport { useUnderverseUIConfig } from \"../contexts/UnderverseConfigContext\";\n\n/** Public props for the `Modal` component. */\ninterface ModalProps {\n isOpen: boolean;\n onClose: () => void;\n children: React.ReactNode;\n title?: string;\n description?: string;\n className?: string;\n contentClassName?: string;\n overlayClassName?: string;\n showCloseButton?: boolean;\n closeOnOverlayClick?: boolean;\n closeOnEsc?: boolean;\n size?: \"sm\" | \"md\" | \"lg\" | \"xl\" | \"full\";\n noPadding?: boolean;\n fullWidth?: boolean;\n width?: string | number;\n height?: string | number;\n borderMode?: BorderMode;\n}\n\nconst sizeStyles = {\n sm: \"max-w-md\",\n md: \"max-w-2xl\",\n lg: \"max-w-4xl\",\n xl: \"max-w-6xl\",\n full: \"max-w-full\",\n};\n\nconst Modal: React.FC<ModalProps> = ({\n isOpen,\n onClose,\n children,\n title,\n description,\n className,\n contentClassName,\n overlayClassName,\n showCloseButton = true,\n closeOnOverlayClick = true,\n closeOnEsc = true,\n size = \"md\",\n noPadding = false,\n fullWidth = false,\n width,\n height,\n borderMode,\n}) => {\n const gi18n = useGlobalI18n();\n const globalConfig = useUnderverseUIConfig();\n const resolvedBorderMode = borderMode ?? globalConfig.borderMode;\n const [isMounted, setIsMounted] = React.useState(false);\n const [isVisible, setIsVisible] = React.useState(false);\n const [isAnimating, setIsAnimating] = React.useState(true);\n // Track if mousedown started outside modal content\n const mouseDownTarget = React.useRef<EventTarget | null>(null);\n const modalContentRef = React.useRef<HTMLDivElement>(null);\n\n React.useEffect(() => {\n setIsMounted(true);\n return () => setIsMounted(false);\n }, []);\n\n // Ref to prevent double animation trigger in React StrictMode\n const animationRef = React.useRef(false);\n\n // Animation handling\n React.useEffect(() => {\n if (isOpen) {\n if (animationRef.current) return; // Prevent double animation in StrictMode\n animationRef.current = true;\n setIsVisible(true);\n setIsAnimating(true);\n // Start animation on next frame\n requestAnimationFrame(() => {\n setIsAnimating(false);\n });\n } else {\n animationRef.current = false;\n if (isVisible) {\n setIsAnimating(true);\n // Hide after animation completes\n const hideTimer = setTimeout(() => {\n setIsVisible(false);\n }, 200);\n return () => clearTimeout(hideTimer);\n }\n }\n }, [isOpen, isVisible]);\n\n // Handle escape key\n React.useEffect(() => {\n if (!isOpen || !closeOnEsc) return;\n\n const handleEscape = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") {\n onClose();\n }\n };\n\n document.addEventListener(\"keydown\", handleEscape);\n return () => document.removeEventListener(\"keydown\", handleEscape);\n }, [isOpen, closeOnEsc, onClose]);\n\n // Prevent body scroll when modal is open\n React.useEffect(() => {\n if (isOpen) {\n document.body.style.overflow = \"hidden\";\n } else {\n document.body.style.overflow = \"unset\";\n }\n\n return () => {\n document.body.style.overflow = \"unset\";\n };\n }, [isOpen]);\n\n // Check if an element is inside a portal (datepicker, popover, dropdown, etc.)\n const isInsidePortal = (element: Node | null): boolean => {\n if (!element) return false;\n let current = element as HTMLElement | null;\n while (current) {\n // Check for common portal indicators (data attributes, z-index patterns)\n if (\n current.hasAttribute?.(\"data-datepicker\") ||\n current.hasAttribute?.(\"data-popover\") ||\n current.hasAttribute?.(\"data-dropdown\") ||\n current.hasAttribute?.(\"data-dropdown-menu\") ||\n current.hasAttribute?.(\"data-radix-popper-content-wrapper\") ||\n current.hasAttribute?.(\"data-radix-portal\") ||\n current.hasAttribute?.(\"data-tippy-root\") ||\n current.getAttribute?.(\"role\") === \"listbox\" ||\n current.getAttribute?.(\"role\") === \"dialog\" ||\n current.classList?.contains(\"datepicker-portal\") ||\n current.classList?.contains(\"popover-portal\") ||\n current.classList?.contains(\"tippy-box\") ||\n current.classList?.contains(\"tippy-content\")\n ) {\n return true;\n }\n current = current.parentElement;\n }\n return false;\n };\n\n const handleOverlayMouseDown = (event: React.MouseEvent) => {\n // Store the mousedown target\n mouseDownTarget.current = event.target;\n };\n\n const handleOverlayMouseUp = (event: React.MouseEvent) => {\n // Check if both mousedown and mouseup occurred outside modal content\n const modalContent = modalContentRef.current;\n const mouseDownTarget_ = mouseDownTarget.current as Node;\n const mouseUpTarget = event.target as Node;\n\n // Don't close if clicking inside a portal element (datepicker, popover, etc.)\n if (isInsidePortal(mouseDownTarget_) || isInsidePortal(mouseUpTarget)) {\n mouseDownTarget.current = null;\n return;\n }\n\n const mouseDownOutside = modalContent && !modalContent.contains(mouseDownTarget_);\n const mouseUpOutside = modalContent && !modalContent.contains(mouseUpTarget);\n\n if (closeOnOverlayClick && mouseDownOutside && mouseUpOutside) {\n onClose();\n }\n mouseDownTarget.current = null;\n };\n\n if (!isMounted || (!isOpen && !isVisible)) {\n return null;\n }\n\n const maxWidthClass = width ? \"max-w-none\" : fullWidth ? \"max-w-full\" : sizeStyles[size];\n\n const modalContent = (\n <div\n className={cn(\"fixed inset-0 z-[9999] flex items-center justify-center p-4 md:p-6\", overlayClassName)}\n style={{ overscrollBehavior: \"contain\" }}\n onMouseDown={handleOverlayMouseDown}\n onMouseUp={handleOverlayMouseUp}\n >\n {/* Overlay */}\n <div\n className=\"absolute inset-0 bg-background/80 backdrop-blur-sm transition-opacity duration-200 ease-out\"\n style={{\n opacity: isOpen && !isAnimating ? 1 : 0,\n }}\n />\n\n {/* Modal */}\n <div\n ref={modalContentRef}\n className={cn(\n \"relative w-full border border-border/40 bg-card text-card-foreground shadow-xl\",\n resolvedBorderMode ? getPanelBorderRadiusClass(resolvedBorderMode) : \"rounded-2xl md:rounded-3xl\",\n \"transition-all duration-200 ease-out\",\n maxWidthClass,\n fullWidth && \"mx-0\",\n className,\n )}\n style={{\n opacity: isOpen && !isAnimating ? 1 : 0,\n transform: isOpen && !isAnimating ? \"scale(1)\" : \"scale(0.9)\",\n // Thêm dòng này để tạo hiệu ứng nảy\n transition: \"all 300ms cubic-bezier(0.34, 1.76, 0.64, 1)\",\n width,\n height,\n }}\n onClick={(e) => e.stopPropagation()}\n >\n {/* Header */}\n {(title || description || showCloseButton) && (\n <div className=\"flex items-start justify-between p-6 pb-0\">\n <div className=\"space-y-1.5\">\n {title && <h2 className=\"text-lg font-semibold leading-none tracking-tight\">{title}</h2>}\n {description && <p className=\"text-sm text-muted-foreground\">{description}</p>}\n </div>\n {showCloseButton && (\n <button\n onClick={onClose}\n aria-label={gi18n.closeModal ?? \"Close modal\"}\n className={cn(\n \"rounded-lg opacity-70 ring-offset-background transition-opacity\",\n \"hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2\",\n \"disabled:pointer-events-none \",\n )}\n >\n <X className=\"h-4 w-4 cursor-pointer\" aria-hidden=\"true\" />\n </button>\n )}\n </div>\n )}\n\n {/* Content */}\n <div className={cn(\"p-6\", noPadding && \"p-0\", contentClassName)}>{children}</div>\n </div>\n </div>\n );\n\n return typeof window !== \"undefined\" ? createPortal(modalContent, document.body) : null;\n};\n\nexport default Modal;\n"],"mappings":"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","names":["modalContent"]}
@@ -0,0 +1,74 @@
1
+ import {
2
+ cn
3
+ } from "./chunk-GSBRTFP2.js";
4
+
5
+ // src/components/emoji-ui.tsx
6
+ import React from "react";
7
+ import { jsx } from "react/jsx-runtime";
8
+ function formatEmojiName(name) {
9
+ return name.replaceAll("_", " ");
10
+ }
11
+ function formatEmojiCountLabel(template, shown, total) {
12
+ return template.replace("{shown}", String(shown)).replace("{total}", String(total));
13
+ }
14
+ function getEmojiUnifiedCode(emoji) {
15
+ const codePoints = [];
16
+ for (let i = 0; i < emoji.length; ) {
17
+ const codePoint = emoji.codePointAt(i);
18
+ if (codePoint === void 0) break;
19
+ let hex = codePoint.toString(16).toLowerCase();
20
+ if (hex.length < 4) {
21
+ hex = hex.padStart(4, "0");
22
+ }
23
+ codePoints.push(hex);
24
+ i += codePoint > 65535 ? 2 : 1;
25
+ }
26
+ return codePoints.join("-");
27
+ }
28
+ var globalEmojiBaseUrl = "/emojis";
29
+ function setEmojiBaseUrl(url) {
30
+ globalEmojiBaseUrl = url.endsWith("/") ? url.slice(0, -1) : url;
31
+ }
32
+ function getEmojiImageUrl(unified) {
33
+ return `${globalEmojiBaseUrl}/${unified}.png`;
34
+ }
35
+ var EmojiGridButton = ({ emoji, name, onClick, className, active = false }) => {
36
+ const [hasError, setHasError] = React.useState(false);
37
+ const unified = React.useMemo(() => getEmojiUnifiedCode(emoji), [emoji]);
38
+ return /* @__PURE__ */ jsx(
39
+ "button",
40
+ {
41
+ type: "button",
42
+ "aria-label": formatEmojiName(name),
43
+ title: formatEmojiName(name),
44
+ onClick,
45
+ className: cn(
46
+ "group flex h-9 w-9 items-center justify-center rounded-xl transition-all duration-300",
47
+ "bg-transparent hover:bg-primary/10 border border-transparent hover:border-primary/20",
48
+ "focus:outline-none focus:ring-2 focus:ring-primary/20",
49
+ "scale-100 hover:scale-110 active:scale-95",
50
+ active ? "bg-primary/15 border-primary/30 ring-2 ring-primary/20" : "",
51
+ className
52
+ ),
53
+ children: hasError ? /* @__PURE__ */ jsx("span", { className: "text-2xl transition-transform duration-300 group-hover:rotate-6", children: emoji }) : /* @__PURE__ */ jsx(
54
+ "img",
55
+ {
56
+ src: getEmojiImageUrl(unified),
57
+ alt: emoji,
58
+ onError: () => setHasError(true),
59
+ className: "h-6.5 w-6.5 object-contain transition-all duration-300 group-hover:rotate-6 group-hover:drop-shadow-[0_2px_5px_rgba(0,0,0,0.15)]",
60
+ loading: "lazy"
61
+ }
62
+ )
63
+ }
64
+ );
65
+ };
66
+
67
+ export {
68
+ formatEmojiCountLabel,
69
+ getEmojiUnifiedCode,
70
+ setEmojiBaseUrl,
71
+ getEmojiImageUrl,
72
+ EmojiGridButton
73
+ };
74
+ //# sourceMappingURL=chunk-ZUAIBY2Z.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/emoji-ui.tsx"],"sourcesContent":["\"use client\";\n\nimport React from \"react\";\nimport { cn } from \"../utils/cn\";\n\nexport function formatEmojiName(name: string) {\n return name.replaceAll(\"_\", \" \");\n}\n\nexport function formatEmojiCountLabel(template: string, shown: number, total: number) {\n return template\n .replace(\"{shown}\", String(shown))\n .replace(\"{total}\", String(total));\n}\n\n/**\n * Converts a Unicode emoji string into a lowercase hyphenated hex code point format\n * used by emoji CDNs (e.g. \"😀\" -> \"1f600\", \"#️⃣\" -> \"0023-fe0f-20e3\").\n */\nexport function getEmojiUnifiedCode(emoji: string): string {\n const codePoints = [];\n for (let i = 0; i < emoji.length; ) {\n const codePoint = emoji.codePointAt(i);\n if (codePoint === undefined) break;\n let hex = codePoint.toString(16).toLowerCase();\n if (hex.length < 4) {\n hex = hex.padStart(4, \"0\");\n }\n codePoints.push(hex);\n i += codePoint > 0xffff ? 2 : 1;\n }\n return codePoints.join(\"-\");\n}\n\nlet globalEmojiBaseUrl = \"/emojis\";\n\n/**\n * Configure a custom base URL for loading emoji image assets (e.g. `/emojis` for local offline storage).\n * If not set, defaults to jsDelivr CDN.\n */\nexport function setEmojiBaseUrl(url: string) {\n globalEmojiBaseUrl = url.endsWith(\"/\") ? url.slice(0, -1) : url;\n}\n\n/**\n * Resolves the final image URL for a given unified emoji code.\n */\nexport function getEmojiImageUrl(unified: string): string {\n return `${globalEmojiBaseUrl}/${unified}.png`;\n}\n\nexport const EmojiGridButton: React.FC<{\n emoji: string;\n name: string;\n onClick: () => void;\n className?: string;\n active?: boolean;\n}> = ({ emoji, name, onClick, className, active = false }) => {\n const [hasError, setHasError] = React.useState(false);\n const unified = React.useMemo(() => getEmojiUnifiedCode(emoji), [emoji]);\n\n return (\n <button\n type=\"button\"\n aria-label={formatEmojiName(name)}\n title={formatEmojiName(name)}\n onClick={onClick}\n className={cn(\n \"group flex h-9 w-9 items-center justify-center rounded-xl transition-all duration-300\",\n \"bg-transparent hover:bg-primary/10 border border-transparent hover:border-primary/20\",\n \"focus:outline-none focus:ring-2 focus:ring-primary/20\",\n \"scale-100 hover:scale-110 active:scale-95\",\n active ? \"bg-primary/15 border-primary/30 ring-2 ring-primary/20\" : \"\",\n className,\n )}\n >\n {hasError ? (\n <span className=\"text-2xl transition-transform duration-300 group-hover:rotate-6\">{emoji}</span>\n ) : (\n <img\n src={getEmojiImageUrl(unified)}\n alt={emoji}\n onError={() => setHasError(true)}\n className=\"h-6.5 w-6.5 object-contain transition-all duration-300 group-hover:rotate-6 group-hover:drop-shadow-[0_2px_5px_rgba(0,0,0,0.15)]\"\n loading=\"lazy\"\n />\n )}\n </button>\n );\n};\n"],"mappings":";;;;;AAEA,OAAO,WAAW;AA2EV;AAxED,SAAS,gBAAgB,MAAc;AAC5C,SAAO,KAAK,WAAW,KAAK,GAAG;AACjC;AAEO,SAAS,sBAAsB,UAAkB,OAAe,OAAe;AACpF,SAAO,SACJ,QAAQ,WAAW,OAAO,KAAK,CAAC,EAChC,QAAQ,WAAW,OAAO,KAAK,CAAC;AACrC;AAMO,SAAS,oBAAoB,OAAuB;AACzD,QAAM,aAAa,CAAC;AACpB,WAAS,IAAI,GAAG,IAAI,MAAM,UAAU;AAClC,UAAM,YAAY,MAAM,YAAY,CAAC;AACrC,QAAI,cAAc,OAAW;AAC7B,QAAI,MAAM,UAAU,SAAS,EAAE,EAAE,YAAY;AAC7C,QAAI,IAAI,SAAS,GAAG;AAClB,YAAM,IAAI,SAAS,GAAG,GAAG;AAAA,IAC3B;AACA,eAAW,KAAK,GAAG;AACnB,SAAK,YAAY,QAAS,IAAI;AAAA,EAChC;AACA,SAAO,WAAW,KAAK,GAAG;AAC5B;AAEA,IAAI,qBAAqB;AAMlB,SAAS,gBAAgB,KAAa;AAC3C,uBAAqB,IAAI,SAAS,GAAG,IAAI,IAAI,MAAM,GAAG,EAAE,IAAI;AAC9D;AAKO,SAAS,iBAAiB,SAAyB;AACxD,SAAO,GAAG,kBAAkB,IAAI,OAAO;AACzC;AAEO,IAAM,kBAMR,CAAC,EAAE,OAAO,MAAM,SAAS,WAAW,SAAS,MAAM,MAAM;AAC5D,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAS,KAAK;AACpD,QAAM,UAAU,MAAM,QAAQ,MAAM,oBAAoB,KAAK,GAAG,CAAC,KAAK,CAAC;AAEvE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,cAAY,gBAAgB,IAAI;AAAA,MAChC,OAAO,gBAAgB,IAAI;AAAA,MAC3B;AAAA,MACA,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,SAAS,2DAA2D;AAAA,QACpE;AAAA,MACF;AAAA,MAEC,qBACC,oBAAC,UAAK,WAAU,mEAAmE,iBAAM,IAEzF;AAAA,QAAC;AAAA;AAAA,UACC,KAAK,iBAAiB,OAAO;AAAA,UAC7B,KAAK;AAAA,UACL,SAAS,MAAM,YAAY,IAAI;AAAA,UAC/B,WAAU;AAAA,UACV,SAAQ;AAAA;AAAA,MACV;AAAA;AAAA,EAEJ;AAEJ;","names":[]}
@@ -0,0 +1,8 @@
1
+ import {
2
+ EMOJI_LIST
3
+ } from "./chunk-4TYW5K4J.js";
4
+ import "./chunk-NSBPE2FW.js";
5
+ export {
6
+ EMOJI_LIST
7
+ };
8
+ //# sourceMappingURL=emojis-I5AMZ3EE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}