@smart-cloud/ai-kit-ui 1.0.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,18 @@
1
+ import jseslint from "@eslint/js";
2
+ import { defineConfig } from 'eslint/config';
3
+ import globals from "globals";
4
+ import tseslint from 'typescript-eslint';
5
+
6
+ export default defineConfig(
7
+ { ignores: ['**/build/**', '**/dist/**', '**/webpack.config.cjs'] },
8
+ jseslint.configs.recommended,
9
+ ...tseslint.configs.recommended,
10
+ {
11
+ files: ["**/*.tsx", "**/*.ts"],
12
+ languageOptions: {
13
+ parser: tseslint.parser,
14
+ ecmaVersion: 2020,
15
+ globals: globals.browser,
16
+ },
17
+ }
18
+ );
package/package.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "@smart-cloud/ai-kit-ui",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "main": "./dist/index.cjs",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "license": "ISC",
9
+ "scripts": {
10
+ "build": "tsup --minify",
11
+ "lint": "eslint 'src/**/*.{ts,tsx}' --report-unused-disable-directives --max-warnings 0",
12
+ "test": "echo \"Error: no test specified\" && exit 1",
13
+ "publish": "WPSUITE_PREMIUM=true tsup --minify && npm publish --access=public"
14
+ },
15
+ "publishConfig": {
16
+ "registry": "https://registry.npmjs.org/",
17
+ "scope": "@smart-cloud/ai-kit-ui"
18
+ },
19
+ "dependencies": {
20
+ "@smart-cloud/ai-kit-core": "^1.0.0",
21
+ "@smart-cloud/wpsuite-core": "^2.0.5",
22
+ "@tabler/icons-react": "^3.36.1",
23
+ "react-markdown": "^10.1.0",
24
+ "rehype-sanitize": "^6.0.0",
25
+ "rehype-stringify": "^10.0.1",
26
+ "remark-gfm": "^4.0.1",
27
+ "remark-parse": "^11.0.0",
28
+ "remark-rehype": "^11.1.2",
29
+ "unified": "^11.0.5"
30
+ },
31
+ "peerDependencies": {
32
+ "@emotion/cache": "^11.14.0",
33
+ "@emotion/react": "^11.14.0",
34
+ "@mantine/core": "^8.3.12",
35
+ "@mantine/hooks": "^8.3.12",
36
+ "@mantine/modals": "^8.3.12",
37
+ "@wordpress/data": "^10.37.0",
38
+ "aws-amplify": "^6.15.9",
39
+ "react": "^18.3.1",
40
+ "react-dom": "^18.3.1"
41
+ },
42
+ "devDependencies": {
43
+ "@emotion/cache": "^11.14.0",
44
+ "@emotion/react": "^11.14.0",
45
+ "@eslint/js": "^9.39.2",
46
+ "@mantine/core": "^8.3.12",
47
+ "@mantine/hooks": "^8.3.12",
48
+ "@mantine/modals": "^8.3.12",
49
+ "@types/dom-chromium-ai": "^0.0.13",
50
+ "@types/jquery": "^3.5.33",
51
+ "@types/react": "^18.3.23",
52
+ "@types/react-dom": "^18.3.7",
53
+ "@typescript-eslint/eslint-plugin": "^8.52.0",
54
+ "@typescript-eslint/parser": "^8.52.0",
55
+ "@wordpress/data": "^10.37.0",
56
+ "ajv": "^8.17.1",
57
+ "aws-amplify": "^6.15.9",
58
+ "eslint": "^9.39.2",
59
+ "globals": "^17.0.0",
60
+ "jquery": "^3.7.1",
61
+ "react": "^18.3.1",
62
+ "react-dom": "^18.3.1",
63
+ "tsup": "^8.5.1",
64
+ "typescript": "^5.9.3",
65
+ "typescript-eslint": "^8.52.0"
66
+ },
67
+ "exports": {
68
+ ".": {
69
+ "types": "./dist/index.d.ts",
70
+ "import": "./dist/index.js",
71
+ "require": "./dist/index.cjs"
72
+ },
73
+ "./styles.css": "./dist/ai-kit-ui.css"
74
+ }
75
+ }
@@ -0,0 +1,266 @@
1
+ import createCache from "@emotion/cache";
2
+ import { CacheProvider } from "@emotion/react";
3
+ import { useLayoutEffect, useMemo, useRef, useState } from "react";
4
+ import { createPortal } from "react-dom";
5
+
6
+ export type ShadowBoundaryMode = "local" | "overlay";
7
+
8
+ export type ShadowBoundaryProps = {
9
+ /** Stylesheets to inject into the shadow root (as <link rel="stylesheet">). */
10
+ stylesheets: string[];
11
+
12
+ /** Optional class name applied to the host element. */
13
+ className?: string;
14
+
15
+ /** Optional raw CSS text injected into the shadow root (as <style>). */
16
+ styleText?: string;
17
+
18
+ /** ID of the element inside the shadow root used as the portal target. */
19
+ rootElementId: string;
20
+
21
+ /**
22
+ * Where to create the shadow root:
23
+ * - "local": attach shadow to this component's host element (keeps layout positioning).
24
+ * - "overlay": attach shadow to a singleton element in top (or self) document (always on top).
25
+ */
26
+ mode?: ShadowBoundaryMode;
27
+
28
+ /**
29
+ * For mode="overlay": host id in the top (or self) document.
30
+ * Same id everywhere => singleton overlay host.
31
+ */
32
+ overlayRootId?: string;
33
+
34
+ children: (api: {
35
+ /** Portal target element inside the shadow root. */
36
+ rootElement: HTMLDivElement;
37
+ /** Shadow root instance. */
38
+ shadowRoot: ShadowRoot;
39
+ }) => React.ReactNode;
40
+ };
41
+
42
+ function getTopDocumentSafe(): Document {
43
+ try {
44
+ return window.top?.document ?? window.document;
45
+ } catch {
46
+ return window.document;
47
+ }
48
+ }
49
+
50
+ function ensureStylesheets(
51
+ doc: Document,
52
+ container: HTMLElement,
53
+ shadow: ShadowRoot,
54
+ hrefs: string[],
55
+ ) {
56
+ for (const href of hrefs) {
57
+ const id = `ai-kit-style-${btoa(href).replace(/=+$/g, "")}`;
58
+ if (shadow.getElementById(id)) continue;
59
+ const link = doc.createElement("link");
60
+ link.id = id;
61
+ link.rel = "stylesheet";
62
+ link.href = href;
63
+ container.appendChild(link);
64
+ }
65
+ }
66
+
67
+ const REGISTRY_ID = "ai-kit-property-registry";
68
+
69
+ function installAiKitPropertyRegistry() {
70
+ const doc = getTopDocumentSafe();
71
+
72
+ // simple dedupe
73
+ if (doc.getElementById(REGISTRY_ID)) return;
74
+
75
+ const style = doc.createElement("style");
76
+ style.id = REGISTRY_ID;
77
+
78
+ // ONLY @property registrations (no global resets!)
79
+ style.textContent = `
80
+ @property --ai-kit-border-angle {
81
+ syntax: "<angle>";
82
+ inherits: true;
83
+ initial-value: 0deg;
84
+ }
85
+ `;
86
+
87
+ doc.head.appendChild(style);
88
+ }
89
+
90
+ // tiny stable hash to detect styleText changes without forcing huge deps churn
91
+ function hashStringDjb2(str: string): string {
92
+ let hash = 5381;
93
+ for (let i = 0; i < str.length; i++) {
94
+ hash = ((hash << 5) + hash) ^ str.charCodeAt(i);
95
+ }
96
+ // unsigned + base36
97
+ return (hash >>> 0).toString(36);
98
+ }
99
+
100
+ const STYLE_TEXT_ID = "ai-kit-style-text";
101
+
102
+ export function ShadowBoundary({
103
+ stylesheets,
104
+ className,
105
+ styleText,
106
+ children,
107
+ rootElementId,
108
+ mode = "local",
109
+ overlayRootId = "ai-kit-overlay-root",
110
+ }: ShadowBoundaryProps) {
111
+ const hostRef = useRef<HTMLDivElement | null>(null);
112
+
113
+ const [shadowRoot, setShadowRoot] = useState<ShadowRoot | null>(null);
114
+ const [portalTarget, setPortalTarget] = useState<HTMLDivElement | null>(null);
115
+
116
+ // Combine built-in + external stylesheets; stable key so callers don't need to memoize arrays.
117
+ const stylesKey = useMemo(() => {
118
+ const all = [...stylesheets];
119
+ return all.join("|");
120
+ }, [stylesheets]);
121
+
122
+ const styleTextHash = useMemo(() => {
123
+ return styleText ? hashStringDjb2(styleText) : "";
124
+ }, [styleText]);
125
+
126
+ useLayoutEffect(() => {
127
+ if (!hostRef.current) return;
128
+
129
+ const doc =
130
+ mode === "overlay" ? getTopDocumentSafe() : hostRef.current.ownerDocument;
131
+
132
+ // 1) Decide overlay host vs local host
133
+ let host: HTMLElement;
134
+ if (mode === "overlay") {
135
+ let overlayHost = doc.getElementById(
136
+ overlayRootId,
137
+ ) as HTMLDivElement | null;
138
+ if (!overlayHost) {
139
+ overlayHost = doc.createElement("div");
140
+ overlayHost.id = overlayRootId;
141
+
142
+ // Do not affect layout; allow overlays to sit above everything.
143
+ overlayHost.style.position = "fixed";
144
+ overlayHost.style.inset = "0";
145
+ overlayHost.style.width = "0";
146
+ overlayHost.style.height = "0";
147
+ overlayHost.style.zIndex = "2147483647"; // max z-index
148
+ overlayHost.style.pointerEvents = "none";
149
+
150
+ doc.body.appendChild(overlayHost);
151
+ }
152
+ host = overlayHost;
153
+ } else {
154
+ host = hostRef.current;
155
+ }
156
+
157
+ // 2) Ensure shadow root
158
+ const shadow = host.shadowRoot ?? host.attachShadow({ mode: "open" });
159
+
160
+ // 3) Ensure portal target div
161
+ let rootEl = shadow.querySelector(
162
+ `#${CSS.escape(rootElementId)}`,
163
+ ) as HTMLDivElement | null;
164
+ if (!rootEl) {
165
+ rootEl = doc.createElement("div");
166
+ rootEl.id = rootElementId;
167
+ rootEl.style.margin = "0";
168
+
169
+ // Overlay host itself has pointerEvents:none; allow content to receive events.
170
+ if (mode === "overlay") {
171
+ rootEl.style.pointerEvents = "auto";
172
+ }
173
+ shadow.appendChild(rootEl);
174
+ }
175
+
176
+ const readScheme = () => rootEl.getAttribute("data-mantine-color-scheme");
177
+ const readVariation = () => rootEl.getAttribute("data-ai-kit-variation");
178
+
179
+ const applyScheme = () => {
180
+ host.setAttribute("data-ai-kit-variation", readVariation() || "default");
181
+ host.setAttribute("data-mantine-color-scheme", readScheme() || "auto");
182
+ if (className) {
183
+ host.className = className;
184
+ }
185
+ host.style.setProperty("outline", "none");
186
+ host.style.setProperty("box-shadow", "none");
187
+ host.style.setProperty("--mantine-color-body", "transparent");
188
+ };
189
+
190
+ applyScheme();
191
+
192
+ // Kövesd, ha a host dokumentumban változik a séma
193
+ const mo = new MutationObserver(applyScheme);
194
+ mo.observe(rootEl, {
195
+ attributes: true,
196
+ attributeFilter: ["data-mantine-color-scheme"],
197
+ });
198
+
199
+ const mq = window.matchMedia?.("(prefers-color-scheme: dark)");
200
+ const onMq = () => applyScheme();
201
+ mq?.addEventListener?.("change", onMq);
202
+
203
+ installAiKitPropertyRegistry();
204
+
205
+ // 5) Inject styles into shadow body (dedup per shadow root)
206
+ ensureStylesheets(
207
+ doc,
208
+ rootEl,
209
+ shadow,
210
+ stylesKey ? stylesKey.split("|") : [],
211
+ );
212
+
213
+ // 6) Optional: inject raw style text into the shadow root
214
+ const existingStyle = shadow.getElementById(
215
+ STYLE_TEXT_ID,
216
+ ) as HTMLStyleElement | null;
217
+
218
+ if (styleText) {
219
+ if (!existingStyle) {
220
+ const s = doc.createElement("style");
221
+ s.id = STYLE_TEXT_ID;
222
+ s.setAttribute("data-hash", styleTextHash);
223
+ s.textContent = styleText;
224
+ rootEl.appendChild(s);
225
+ } else {
226
+ const prevHash = existingStyle.getAttribute("data-hash") || "";
227
+ if (prevHash !== styleTextHash) {
228
+ existingStyle.setAttribute("data-hash", styleTextHash);
229
+ existingStyle.textContent = styleText;
230
+ }
231
+ }
232
+ } else if (existingStyle) {
233
+ existingStyle.remove();
234
+ }
235
+
236
+ setShadowRoot(shadow);
237
+ setPortalTarget(rootEl);
238
+
239
+ return () => {
240
+ mo.disconnect();
241
+ mq?.removeEventListener?.("change", onMq);
242
+ };
243
+ }, [mode, overlayRootId, rootElementId, stylesKey, styleText, styleTextHash]);
244
+
245
+ const emotionCache = useMemo(() => {
246
+ if (!portalTarget) return null;
247
+ // IMPORTANT: container must be an HTMLElement inside the shadow tree.
248
+ return createCache({
249
+ key: mode === "overlay" ? "ai-kit-ov" : "ai-kit-local",
250
+ container: portalTarget,
251
+ });
252
+ }, [portalTarget, mode]);
253
+
254
+ return (
255
+ <div ref={hostRef}>
256
+ {portalTarget && shadowRoot && emotionCache
257
+ ? createPortal(
258
+ <CacheProvider value={emotionCache}>
259
+ {children({ rootElement: portalTarget, shadowRoot })}
260
+ </CacheProvider>,
261
+ portalTarget,
262
+ )
263
+ : null}
264
+ </div>
265
+ );
266
+ }