vzcode 1.15.0 → 1.17.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.
Files changed (44) hide show
  1. package/dist/assets/index-BQgRA0SD.js +181 -0
  2. package/dist/assets/index-CSSpCKM0.js +447 -0
  3. package/dist/assets/index-zzK6rRiK.css +1 -0
  4. package/dist/assets/worker-8af7bTHW.js +293 -0
  5. package/dist/index.html +2 -2
  6. package/package.json +40 -34
  7. package/src/client/AIAssist/AIAssistWidget/index.tsx +10 -6
  8. package/src/client/App/index.tsx +52 -20
  9. package/src/client/App/usePending.ts +50 -0
  10. package/src/client/App/useShareDB.ts +40 -1
  11. package/src/client/CodeEditor/Copilot/index.ts +28 -0
  12. package/src/client/CodeEditor/InteractiveWidgets/colorPicker.ts +129 -33
  13. package/src/client/CodeEditor/InteractiveWidgets/colorsInTextPlugin.ts +36 -1
  14. package/src/client/CodeEditor/getOrCreateEditor.ts +43 -6
  15. package/src/client/CodeEditor/index.tsx +7 -4
  16. package/src/client/CodeEditor/json1PresenceDisplay.ts +5 -5
  17. package/src/client/CodeEditor/rainbowBrackets.ts +14 -2
  18. package/src/client/CodeEditor/style.scss +106 -1
  19. package/src/client/Icons/MicSVG.tsx +25 -0
  20. package/src/client/RunCodeWidget/index.tsx +20 -3
  21. package/src/client/TabList/index.tsx +8 -11
  22. package/src/client/VZCodeContext.tsx +66 -24
  23. package/src/client/VZKeyboardShortcutsDoc.tsx +8 -8
  24. package/src/client/VZLeft.tsx +4 -2
  25. package/src/client/VZMiddle.tsx +86 -27
  26. package/src/client/VZResizer/index.tsx +3 -3
  27. package/src/client/VZSettings.tsx +54 -3
  28. package/src/client/VZSidebar/FileListing.tsx +20 -6
  29. package/src/client/VZSidebar/Listing.tsx +2 -2
  30. package/src/client/VZSidebar/VoiceChatModal.tsx +121 -0
  31. package/src/client/VZSidebar/index.tsx +80 -15
  32. package/src/client/VZSidebar/styles.scss +6 -2
  33. package/src/client/presenceColor.ts +8 -0
  34. package/src/client/useTypeScript/worker/constants.ts +6 -0
  35. package/src/server/generateAIResponse.js +79 -13
  36. package/src/server/handleAICopilot.js +77 -0
  37. package/src/server/index.js +23 -8
  38. package/src/server/livekit.js +24 -0
  39. package/src/server/setupEnv.js +1 -1
  40. package/src/types.ts +1 -0
  41. package/dist/assets/index-Bm65AzMP.js +0 -425
  42. package/dist/assets/index-DbFdL1A1.js +0 -159
  43. package/dist/assets/index-Yz-ZHc0O.css +0 -1
  44. package/dist/assets/worker-CJWrLBN2.js +0 -286
package/dist/index.html CHANGED
@@ -20,8 +20,8 @@
20
20
  href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap"
21
21
  rel="stylesheet"
22
22
  />
23
- <script type="module" crossorigin src="/assets/index-DbFdL1A1.js"></script>
24
- <link rel="stylesheet" crossorigin href="/assets/index-Yz-ZHc0O.css">
23
+ <script type="module" crossorigin src="/assets/index-BQgRA0SD.js"></script>
24
+ <link rel="stylesheet" crossorigin href="/assets/index-zzK6rRiK.css">
25
25
  </head>
26
26
  <body>
27
27
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vzcode",
3
- "version": "1.15.0",
3
+ "version": "1.17.0",
4
4
  "description": "Multiplayer code editor system",
5
5
  "main": "src/index.ts",
6
6
  "type": "module",
@@ -103,68 +103,74 @@
103
103
  },
104
104
  "homepage": "https://github.com/vizhub-core/vzcode#readme",
105
105
  "dependencies": {
106
- "@codemirror/autocomplete": "^6.17.0",
107
- "@codemirror/lang-css": "^6.2.1",
106
+ "@codemirror/autocomplete": "^6.18.3",
107
+ "@codemirror/lang-css": "^6.3.1",
108
108
  "@codemirror/lang-html": "^6.4.9",
109
109
  "@codemirror/lang-javascript": "^6.2.2",
110
110
  "@codemirror/lang-json": "^6.0.1",
111
- "@codemirror/lang-markdown": "^6.2.5",
111
+ "@codemirror/lang-markdown": "^6.3.1",
112
112
  "@codemirror/state": "^6.4.1",
113
113
  "@codemirror/theme-one-dark": "^6.1.2",
114
- "@codemirror/view": "^6.29.1",
115
- "@lezer/highlight": "^1.2.0",
114
+ "@codemirror/view": "^6.35.0",
115
+ "@lezer/highlight": "^1.2.1",
116
+ "@livekit/components-react": "^2.6.9",
117
+ "@livekit/components-styles": "^1.1.4",
116
118
  "@replit/codemirror-indentation-markers": "^6.5.3",
117
119
  "@replit/codemirror-interact": "^6.3.1",
118
120
  "@replit/codemirror-lang-svelte": "^6.0.0",
119
121
  "@replit/codemirror-vscode-keymap": "^6.0.2",
120
122
  "@teamwork/websocket-json-stream": "^2.0.0",
121
123
  "@typescript/vfs": "^1.6.0",
122
- "@uiw/codemirror-theme-abcdef": "^4.23.0",
123
- "@uiw/codemirror-theme-dracula": "^4.23.0",
124
- "@uiw/codemirror-theme-eclipse": "^4.23.0",
125
- "@uiw/codemirror-theme-github": "^4.23.0",
126
- "@uiw/codemirror-theme-material": "^4.23.0",
127
- "@uiw/codemirror-theme-nord": "^4.23.0",
128
- "@uiw/codemirror-theme-okaidia": "^4.23.0",
129
- "@uiw/codemirror-theme-xcode": "^4.23.0",
130
- "@uiw/codemirror-themes": "^4.23.0",
131
- "body-parser": "^1.20.2",
124
+ "@uiw/codemirror-theme-abcdef": "^4.23.6",
125
+ "@uiw/codemirror-theme-dracula": "^4.23.6",
126
+ "@uiw/codemirror-theme-eclipse": "^4.23.6",
127
+ "@uiw/codemirror-theme-github": "^4.23.6",
128
+ "@uiw/codemirror-theme-material": "^4.23.6",
129
+ "@uiw/codemirror-theme-nord": "^4.23.6",
130
+ "@uiw/codemirror-theme-okaidia": "^4.23.6",
131
+ "@uiw/codemirror-theme-xcode": "^4.23.6",
132
+ "@uiw/codemirror-themes": "^4.23.6",
133
+ "body-parser": "^1.20.3",
132
134
  "codemirror": "^6.0.1",
135
+ "codemirror-copilot": "^0.0.7",
133
136
  "codemirror-ot": "^4.4.0",
134
137
  "color-hash": "^2.0.2",
135
138
  "d3-array": "^3.2.4",
136
139
  "diff-match-patch": "^1.0.5",
137
140
  "dotenv": "^16.4.5",
138
- "express": "^4.19.2",
139
- "ignore": "^5.3.1",
141
+ "express": "^4.21.1",
142
+ "ignore": "^6.0.2",
140
143
  "json0-ot-diff": "^1.1.2",
144
+ "livekit-client": "^2.7.1",
145
+ "livekit-server-sdk": "^2.9.2",
141
146
  "ngrok": "^4.3.3",
142
147
  "open": "^10.1.0",
143
- "openai": "^4.53.2",
144
- "prettier-plugin-svelte": "^3.2.6",
148
+ "openai": "^4.73.1",
149
+ "prettier-plugin-svelte": "^3.3.2",
145
150
  "react": "^18.3.1",
146
- "react-bootstrap": "^2.10.4",
151
+ "react-bootstrap": "^2.10.6",
147
152
  "react-dom": "^18.3.1",
148
- "react-router-dom": "^6.26.0",
149
- "sharedb": "^5.0.3",
153
+ "react-router-dom": "^7.0.1",
154
+ "sharedb": "^5.1.1",
150
155
  "sharedb-client-browser": "^5.0.3",
156
+ "uuid": "^11.0.3",
151
157
  "vizhub-ui": "^3.23.0",
152
158
  "ws": "^8.18.0"
153
159
  },
154
160
  "devDependencies": {
155
- "@types/react": "^18.3.3",
156
- "@types/react-dom": "^18.3.0",
157
- "@vitejs/plugin-react": "^4.3.1",
158
- "concurrently": "^8.2.2",
161
+ "@types/react": "^18.3.12",
162
+ "@types/react-dom": "^18.3.1",
163
+ "@vitejs/plugin-react": "^4.3.4",
164
+ "concurrently": "^9.1.0",
159
165
  "cross-env": "^7.0.3",
160
- "prettier": "^3.3.3",
161
- "sass": "^1.77.8",
162
- "typescript": "^5.5.4",
163
- "vite": "^5.3.5",
164
- "vitest": "^2.0.5"
166
+ "prettier": "^3.4.1",
167
+ "sass": "^1.81.0",
168
+ "typescript": "^5.7.2",
169
+ "vite": "^6.0.2",
170
+ "vitest": "^2.1.8"
165
171
  },
166
172
  "optionalDependencies": {
167
- "@rollup/rollup-darwin-arm64": "^4.19.2",
168
- "@rollup/rollup-win32-x64-msvc": "^4.19.2"
173
+ "@rollup/rollup-darwin-arm64": "^4.28.0",
174
+ "@rollup/rollup-win32-x64-msvc": "^4.28.0"
169
175
  }
170
176
  }
@@ -1,5 +1,6 @@
1
1
  import { useCallback, useContext, useState } from 'react';
2
2
  import { OverlayTrigger, Tooltip } from '../../bootstrap';
3
+ import { FileId, PaneId, TabState } from '../../../types';
3
4
  import { VZCodeContext } from '../../VZCodeContext';
4
5
  import {
5
6
  RequestId,
@@ -7,8 +8,8 @@ import {
7
8
  } from '../../generateRequestId';
8
9
  import { SparklesSVG } from '../../Icons';
9
10
  import { startAIAssist } from '../startAIAssist';
10
- import { Spinner } from '../Spinner';
11
11
  import { editorCacheKey } from '../../useEditorCache';
12
+ import { Spinner } from '../Spinner';
12
13
  import './style.scss';
13
14
 
14
15
  const enableStopGeneration = false;
@@ -18,17 +19,20 @@ export const AIAssistWidget = ({
18
19
  aiAssistOptions,
19
20
  aiAssistTooltipText = 'Start AI Assist',
20
21
  aiAssistClickOverride,
22
+ activeFileId,
23
+ tabList,
24
+ paneId,
21
25
  }: {
22
26
  aiAssistEndpoint: string;
23
27
  aiAssistOptions: { [key: string]: string };
24
28
  aiAssistTooltipText?: string;
25
29
  aiAssistClickOverride?: () => void;
30
+ activeFileId: FileId;
31
+ tabList: Array<TabState>;
32
+ paneId: PaneId;
26
33
  }) => {
27
34
  const {
28
35
  shareDBDoc,
29
- activeFileId,
30
- activePaneId,
31
- tabList,
32
36
  editorCache,
33
37
  runPrettierRef,
34
38
  runCodeRef,
@@ -52,7 +56,7 @@ export const AIAssistWidget = ({
52
56
  // Wait for generation to finish.
53
57
  await startAIAssist({
54
58
  view: editorCache.get(
55
- editorCacheKey(activeFileId, activePaneId),
59
+ editorCacheKey(activeFileId, paneId),
56
60
  ).editor,
57
61
  shareDBDoc,
58
62
  fileId: activeFileId,
@@ -96,7 +100,7 @@ export const AIAssistWidget = ({
96
100
  : currentAIStreamId,
97
101
  );
98
102
  }
99
- }, [activeFileId, activePaneId, aiStreamId]);
103
+ }, [activeFileId, paneId, aiStreamId]);
100
104
 
101
105
  const showWidget = enableStopGeneration
102
106
  ? true
@@ -1,26 +1,31 @@
1
- import { useContext } from 'react';
1
+ import { useContext, useState } from 'react';
2
2
  import ShareDBClient from 'sharedb-client-browser/dist/sharedb-client-umd.cjs';
3
3
  import { json1Presence } from '../../ot';
4
4
  import { Username } from '../../types';
5
5
  // @ts-ignore
6
6
  import PrettierWorker from '../usePrettier/worker?worker';
7
7
  // @ts-ignore
8
- import TypeScriptWorker from '../useTypeScript/worker?worker';
8
+ import {
9
+ LiveKitRoom,
10
+ RoomAudioRenderer,
11
+ } from '@livekit/components-react';
12
+ import { v4 } from 'uuid';
9
13
  import { SplitPaneResizeProvider } from '../SplitPaneResizeContext';
10
- import { VZResizer } from '../VZResizer';
11
14
  import {
12
- useInitialUsername,
13
- usePersistUsername,
14
- } from '../usernameLocalStorage';
15
- import { useShareDB } from './useShareDB';
15
+ VZCodeContext,
16
+ VZCodeProvider,
17
+ } from '../VZCodeContext';
16
18
  import { VZLeft } from '../VZLeft';
17
19
  import { VZMiddle } from '../VZMiddle';
20
+ import { VZResizer } from '../VZResizer';
18
21
  import { VZRight } from '../VZRight';
22
+ import TypeScriptWorker from '../useTypeScript/worker?worker';
19
23
  import {
20
- VZCodeContext,
21
- VZCodeProvider,
22
- } from '../VZCodeContext';
24
+ useInitialUsername,
25
+ usePersistUsername,
26
+ } from '../usernameLocalStorage';
23
27
  import './style.scss';
28
+ import { useShareDB } from './useShareDB';
24
29
 
25
30
  // Instantiate the Prettier worker.
26
31
  const prettierWorker = new PrettierWorker();
@@ -62,13 +67,24 @@ function App() {
62
67
  docPresence,
63
68
  submitOperation,
64
69
  connected,
70
+ pending,
65
71
  } = useShareDB({
66
72
  connection,
67
73
  });
68
74
 
75
+ //state for the voice channel
76
+ const [liveKitToken, setLiveKitToken] =
77
+ useState(undefined);
78
+ const [liveKitRoomName, setLiveKitRoomName] =
79
+ useState(v4()); //default room name will be an arbitrary uuid
80
+ const [liveKitConnection, setLiveKitConnection] =
81
+ useState(false);
69
82
  // Get the initial username from localStorage.
70
83
  const initialUsername: Username = useInitialUsername();
71
84
 
85
+ //@ts-ignore
86
+ const serverUrl = import.meta.env.VITE_LIVEKIT_URL; //Vite only parses VITE_ prefixed environment variables
87
+
72
88
  // Feature flag for enabling the right-side panel.
73
89
  // Useful for debugging dual split pane functionality.
74
90
  // We may want to add this as an actual VZCode feature,
@@ -89,17 +105,33 @@ function App() {
89
105
  typeScriptWorker={typeScriptWorker}
90
106
  initialUsername={initialUsername}
91
107
  connected={connected}
108
+ pending={pending}
109
+ liveKitRoomName={liveKitRoomName}
110
+ setLiveKitRoom={setLiveKitRoomName}
111
+ liveKitToken={liveKitToken}
112
+ setLiveKitToken={setLiveKitToken}
113
+ liveKitConnection={liveKitConnection}
114
+ setLiveKitConnection={setLiveKitConnection}
92
115
  >
93
- <div className="app">
94
- <VZLeft />
95
- <VZMiddle allowGlobals={true} />
96
- {enableRightPanel ? <VZRight /> : null}
97
- <VZResizer side="left" />
98
- {enableRightPanel ? (
99
- <VZResizer side="right" />
100
- ) : null}
101
- </div>
102
- <PersistUsername />
116
+ <LiveKitRoom
117
+ audio={true}
118
+ token={liveKitToken}
119
+ serverUrl={serverUrl}
120
+ connect={liveKitConnection}
121
+ style={{ height: '100%' }}
122
+ >
123
+ <div className="app">
124
+ <VZLeft />
125
+ <VZMiddle allowGlobals={true} />
126
+ {enableRightPanel ? <VZRight /> : null}
127
+ <VZResizer side="left" />
128
+ {enableRightPanel ? (
129
+ <VZResizer side="right" />
130
+ ) : null}
131
+ </div>
132
+ <PersistUsername />
133
+ <RoomAudioRenderer />
134
+ </LiveKitRoom>
103
135
  </VZCodeProvider>
104
136
  </SplitPaneResizeProvider>
105
137
  );
@@ -0,0 +1,50 @@
1
+ import {
2
+ useState,
3
+ useEffect,
4
+ useCallback,
5
+ useRef,
6
+ } from 'react';
7
+ import { ShareDBDoc, VZCodeContent } from '../../types';
8
+
9
+ // import { runDelay } from '../constants';
10
+ // TODO understand what this part is for
11
+ const runDelay = 1000;
12
+
13
+ export const usePending = (
14
+ shareDBDoc: ShareDBDoc<VZCodeContent>,
15
+ ) => {
16
+ const lastOpTimerRef = useRef<NodeJS.Timeout>();
17
+
18
+ // TODO rename this to isPending,
19
+ // and also rename connected to isConnected.
20
+ const [pending, setPending] = useState<boolean>(false);
21
+
22
+ const resetOnNoPending = useCallback(() => {
23
+ if (!shareDBDoc) return;
24
+
25
+ // This happens AFTER the changes have been
26
+ // acknowledged by the server.
27
+ shareDBDoc.whenNothingPending(() => {
28
+ setPending(false);
29
+ });
30
+ }, [shareDBDoc, setPending]);
31
+
32
+ useEffect(() => {
33
+ if (!shareDBDoc) return;
34
+
35
+ // This happens when you type, but BEFORE
36
+ // the changes are sent to the server.
37
+ shareDBDoc.on('before op', () => {
38
+ setPending(true);
39
+
40
+ clearTimeout(lastOpTimerRef.current);
41
+
42
+ lastOpTimerRef.current = setTimeout(() => {
43
+ // should be triggered only once, cause upcoming ops unschedule previous resetOnNoPending
44
+ resetOnNoPending();
45
+ }, runDelay);
46
+ });
47
+ }, [shareDBDoc, resetOnNoPending]);
48
+
49
+ return pending;
50
+ };
@@ -1,7 +1,8 @@
1
- import { useEffect, useState } from 'react';
1
+ import { useEffect, useState, useRef } from 'react';
2
2
  import { randomId } from '../../randomId';
3
3
  import { ShareDBDoc, VZCodeContent } from '../../types';
4
4
  import { useSubmitOperation } from '../useSubmitOperation';
5
+ import { usePending } from './usePending';
5
6
 
6
7
  // Set up the connection to ShareDB.
7
8
  export const useShareDB = ({
@@ -30,6 +31,14 @@ export const useShareDB = ({
30
31
 
31
32
  const [connected, setConnected] =
32
33
  useState<boolean>(false);
34
+
35
+ // const [isSaving, setIsSaving] = useState<boolean>(false);
36
+ // const [isSaved, setIsSaved] = useState<boolean>(false);
37
+
38
+ // const debounceTimeoutRef = useRef<ReturnType<
39
+ // typeof setTimeout
40
+ // > | null>(null);
41
+
33
42
  useEffect(() => {
34
43
  // Listen for connection state changes
35
44
  connection.on('connected', () => {
@@ -69,7 +78,28 @@ export const useShareDB = ({
69
78
  // This is not required, because no part of the app outside
70
79
  // of the CodeMirror integration uses those file contents.
71
80
  // The Sidebar only needs to know file names, not contents.
81
+ // Start saving process when there’s an operation.
82
+ // setIsSaving(true);
83
+ // setIsSaved(false);
84
+
72
85
  setContent(shareDBDoc.data);
86
+
87
+ // // Simulate a saving process and switch status to "saved"
88
+ // if (debounceTimeoutRef.current) {
89
+ // clearTimeout(debounceTimeoutRef.current);
90
+ // }
91
+
92
+ // // Set up a debounce timer to switch from "saving" to "saved" after 2 seconds of inactivity.
93
+ // debounceTimeoutRef.current = setTimeout(() => {
94
+ // setIsSaving(false);
95
+ // setIsSaved(true); // Only set isSaved to true after 2 seconds of inactivity
96
+
97
+ // const resetSavedTimeout = setTimeout(() => {
98
+ // setIsSaved(false);
99
+ // }, 1000); // 1 second delay after `isSaved` becomes true.
100
+
101
+ // return () => clearTimeout(resetSavedTimeout);
102
+ // }, 1800); // 1.8-second delay for detecting inactivity
73
103
  });
74
104
 
75
105
  // Set up presence.
@@ -101,11 +131,19 @@ export const useShareDB = ({
101
131
  // TODO unsubscribe from presence
102
132
  // TODO unsubscribe from doc
103
133
  return () => {
134
+ // if (debounceTimeoutRef.current)
135
+ // clearTimeout(debounceTimeoutRef.current);
104
136
  // shareDBDoc.destroy();
105
137
  // docPresence.destroy();
106
138
  };
107
139
  }, []);
108
140
 
141
+ // True for "Saving..."
142
+ // False for "Saved"
143
+ const pending = usePending(shareDBDoc);
144
+
145
+ console.log('pending ', pending);
146
+
109
147
  // A convenience function for mutating the ShareDB document
110
148
  // based submitting OT ops generated by diffing the JSON.
111
149
  const submitOperation =
@@ -118,5 +156,6 @@ export const useShareDB = ({
118
156
  docPresence,
119
157
  submitOperation,
120
158
  connected,
159
+ pending,
121
160
  };
122
161
  };
@@ -0,0 +1,28 @@
1
+ import { inlineCopilot } from 'codemirror-copilot';
2
+
3
+ const defaultAICopilotEndpoint = '/ai-copilot';
4
+
5
+ export const copilot = ({
6
+ aiCopilotEndpoint = defaultAICopilotEndpoint,
7
+ }: {
8
+ aiCopilotEndpoint: string;
9
+ }) => {
10
+ return inlineCopilot(async (prefix, suffix) => {
11
+ const response = await fetch(aiCopilotEndpoint, {
12
+ method: 'POST',
13
+ headers: { 'Content-Type': 'application/json' },
14
+ body: JSON.stringify({ prefix, suffix }),
15
+ });
16
+
17
+ const responseJson = await response.json();
18
+
19
+ if (responseJson.error) {
20
+ console.error(
21
+ '[copilot] error from server:',
22
+ responseJson.error,
23
+ );
24
+ }
25
+
26
+ return responseJson.text;
27
+ });
28
+ };
@@ -8,39 +8,135 @@ export const colorPickerRegex = /#[0-9A-Fa-f]{6}/g;
8
8
  // Works without quotes to support CSS.
9
9
  export const colorPicker = (
10
10
  onInteract?: () => void,
11
- ): InteractRule => ({
12
- regexp: colorPickerRegex,
13
- cursor: 'pointer',
14
- onClick(
15
- text: string,
16
- setText: (newText: string) => void,
17
- ) {
18
- const startingColor: string = text;
19
-
20
- const sel: HTMLInputElement =
21
- document.createElement('input');
22
- sel.type = 'color';
23
- sel.value = startingColor.toLowerCase();
24
-
25
- // `valueIsUpper` maintains the style of the user's code.
26
- // It keeps the case of a-f the same case as the original.
27
- const valueIsUpper: boolean =
28
- startingColor.toUpperCase() === startingColor;
29
-
30
- const updateHex = (e: Event) => {
31
- const el: HTMLInputElement =
32
- e.target as HTMLInputElement;
33
- if (el.value) {
34
- setText(
35
- valueIsUpper ? el.value.toUpperCase() : el.value,
36
- );
37
- }
38
- if (onInteract) onInteract();
39
- };
40
- sel.addEventListener('input', updateHex);
41
- sel.click();
42
- },
43
- });
11
+ ): InteractRule => {
12
+ // Create the color picker element when the page loads
13
+ const sel: HTMLInputElement = document.createElement('input');
14
+ sel.type = 'color';
15
+ sel.style.position = 'fixed';
16
+ sel.style.left = '-9999px'; // Initially off-screen
17
+ sel.style.top = '-9999px';
18
+ document.body.appendChild(sel);
19
+
20
+ // Create a close button element to hide the color picker
21
+ const closeButton: HTMLButtonElement = document.createElement('button');
22
+ closeButton.innerText = 'X';
23
+ closeButton.style.position = 'absolute';
24
+ closeButton.style.width = '20px';
25
+ closeButton.style.height = '20px';
26
+ closeButton.style.borderRadius = '50%';
27
+ closeButton.style.border = 'none';
28
+ closeButton.style.backgroundColor = '#ff4d4f';
29
+ closeButton.style.color = 'white';
30
+ closeButton.style.fontSize = '12px';
31
+ closeButton.style.display = 'none'; // Initially hidden
32
+ closeButton.style.cursor = 'pointer';
33
+ closeButton.style.top = '-10px'; // Adjust position relative to color picker
34
+ closeButton.style.right = '-10px';
35
+ sel.parentElement?.appendChild(closeButton); // Add button to the same container
36
+
37
+ // Function to hide the color picker and close button
38
+ const hideColorPicker = () => {
39
+ sel.style.left = '-9999px';
40
+ sel.style.top = '-9999px';
41
+ closeButton.style.display = 'none';
42
+ };
43
+
44
+ // Add event listener to the close button to hide color picker
45
+ closeButton.addEventListener('click', (event) => {
46
+ event.stopPropagation(); // Prevent click event from bubbling to document
47
+ hideColorPicker();
48
+ });
49
+
50
+ // Add event listener for clicks outside the color picker
51
+ document.addEventListener('click', (event: MouseEvent) => {
52
+ if (event.target !== sel && event.target !== closeButton) {
53
+ hideColorPicker();
54
+ }
55
+ });
56
+
57
+ // Function to set the text and color
58
+ const setText = (newText: string) => {
59
+ console.log('Applying color to the element:', newText);
60
+
61
+ // Assuming you're applying the color to a circle element with id 'colorCircle'
62
+ const colorCircle = document.getElementById('colorCircle');
63
+ if (colorCircle) {
64
+ colorCircle.style.backgroundColor = newText;
65
+ console.log(`Color applied to colorCircle: ${newText}`);
66
+ } else {
67
+ console.log('colorCircle element not found');
68
+ }
69
+
70
+ // If there is any other text element (like an input or div for displaying hex color), update it too.
71
+ const hexDisplay = document.getElementById('hexDisplay');
72
+ if (hexDisplay) {
73
+ hexDisplay.textContent = newText;
74
+ console.log(`Hex value applied to hexDisplay: ${newText}`);
75
+ } else {
76
+ console.log('hexDisplay element not found');
77
+ }
78
+ };
79
+
80
+ return {
81
+ regexp: colorPickerRegex,
82
+ cursor: 'pointer',
83
+
84
+ onClick(
85
+ text: string,
86
+ setText: (newText: string) => void,
87
+ event: MouseEvent
88
+ ) {
89
+ const startingColor: string = text;
90
+
91
+ // Set the initial value of the color picker
92
+ console.log('Initial color:', startingColor);
93
+ sel.value = startingColor.toLowerCase();
94
+ console.log('Color picker initial value set to:', sel.value);
95
+
96
+ // Position the color picker near the cursor
97
+ const cursorX = event.clientX;
98
+ const cursorY = event.clientY;
99
+ sel.style.left = cursorX + 'px';
100
+ sel.style.top = cursorY + 'px';
101
+
102
+ // Show and position the close button
103
+ closeButton.style.display = 'block';
104
+ closeButton.style.left = `${cursorX + sel.offsetWidth - 15}px`; // Adjust to place in corner
105
+ closeButton.style.top = `${cursorY - 15}px`;
106
+
107
+ // Update the color selection and setText on change
108
+ const updateHex = (e: Event) => {
109
+ const el: HTMLInputElement = e.target as HTMLInputElement;
110
+ console.log('Hex color value selected:', el.value);
111
+
112
+ if (el.value) {
113
+ setText(el.value.toUpperCase()); // Use uppercase for consistency
114
+ }
115
+ if (onInteract) {
116
+ console.log('onInteract called');
117
+ onInteract();
118
+ }
119
+
120
+ // Update color display directly (for example, changing the background color of an element)
121
+ const colorDisplay = document.getElementById('colorDisplay');
122
+ if (colorDisplay) {
123
+ colorDisplay.style.backgroundColor = el.value;
124
+ console.log(`colorDisplay updated to: ${el.value}`);
125
+ } else {
126
+ console.log('colorDisplay element not found');
127
+ }
128
+ };
129
+
130
+ // Add input event listener to handle color selection
131
+ sel.addEventListener('input', updateHex);
132
+
133
+ // Trigger click event on the color picker to display it
134
+ sel.click();
135
+ },
136
+ };
137
+ };
138
+
139
+
44
140
 
45
141
  // TODO get this working
46
142
  // rgb color picker