@agent-native/core 0.86.0 → 0.86.1

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 (183) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +9 -0
  3. package/corpus/core/package.json +2 -1
  4. package/corpus/core/src/client/AgentPanel.tsx +10 -0
  5. package/corpus/core/src/client/AssistantChat.tsx +91 -27
  6. package/corpus/core/src/client/ErrorBoundary.tsx +67 -12
  7. package/corpus/core/src/client/ErrorReportActions.tsx +69 -0
  8. package/corpus/core/src/client/FeedbackButton.tsx +8 -4
  9. package/corpus/core/src/client/db-admin/DbAdminPage.tsx +41 -13
  10. package/corpus/core/src/client/db-admin/SqlEditor.tsx +10 -3
  11. package/corpus/core/src/client/db-admin/TableEditor.tsx +12 -5
  12. package/corpus/core/src/client/db-admin/useAgentSync.ts +10 -3
  13. package/corpus/core/src/client/db-admin/useDbAdmin.ts +65 -16
  14. package/corpus/core/src/client/error-reporting.ts +100 -0
  15. package/corpus/core/src/client/index.ts +11 -0
  16. package/corpus/core/src/client/org/RequireActiveOrg.tsx +12 -0
  17. package/corpus/core/src/client/visual-style-controls.tsx +615 -2
  18. package/corpus/core/src/db-admin/index.ts +26 -0
  19. package/corpus/core/src/db-admin/operations.ts +87 -49
  20. package/corpus/core/src/localization/default-messages.ts +4 -0
  21. package/corpus/core/src/server/builder-design-systems.ts +328 -9
  22. package/corpus/core/src/server/index.ts +15 -0
  23. package/corpus/core/src/templates/default/app/root.tsx +47 -0
  24. package/corpus/templates/analytics/AGENTS.md +6 -4
  25. package/corpus/templates/analytics/actions/delete-db-admin-connection.ts +19 -0
  26. package/corpus/templates/analytics/actions/list-db-admin-connections.ts +18 -0
  27. package/corpus/templates/analytics/actions/navigate.ts +17 -3
  28. package/corpus/templates/analytics/actions/save-db-admin-connection.ts +33 -0
  29. package/corpus/templates/analytics/actions/view-screen.ts +11 -3
  30. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +11 -2
  31. package/corpus/templates/analytics/app/i18n/zh-TW.ts +23 -2
  32. package/corpus/templates/analytics/app/i18n-data.ts +230 -20
  33. package/corpus/templates/analytics/app/pages/Agents.tsx +398 -4
  34. package/corpus/templates/analytics/changelog/2026-07-03-connect-other-app-databases-from-analytics.md +6 -0
  35. package/corpus/templates/analytics/server/db/schema.ts +27 -0
  36. package/corpus/templates/analytics/server/lib/db-admin-connections.ts +458 -0
  37. package/corpus/templates/analytics/server/plugins/db.ts +35 -0
  38. package/corpus/templates/analytics/server/routes/[base]/_agent-native/analytics-db-admin/[...path].ts +1 -0
  39. package/corpus/templates/analytics/server/routes/_agent-native/analytics-db-admin/[...path].ts +160 -0
  40. package/corpus/templates/content/app/root.tsx +9 -0
  41. package/corpus/templates/content/changelog/2026-07-03-error-screens-now-include-a-feedback-button-with-debug-info.md +6 -0
  42. package/corpus/templates/design/AGENTS.md +13 -7
  43. package/corpus/templates/design/actions/get-design-system.ts +21 -0
  44. package/corpus/templates/design/actions/index-design-system-with-builder.ts +14 -37
  45. package/corpus/templates/design/app/components/design/code-workbench/CodeWorkbench.tsx +3 -3
  46. package/corpus/templates/design/app/components/design/code-workbench/SideBar.tsx +70 -9
  47. package/corpus/templates/design/app/components/design/code-workbench/editor/EditorTabs.tsx +11 -2
  48. package/corpus/templates/design/app/components/design/code-workbench/editor/MonacoHost.tsx +1 -1
  49. package/corpus/templates/design/app/components/design/code-workbench/editor/StatusBar.tsx +1 -1
  50. package/corpus/templates/design/app/components/design/code-workbench/explorer/ExplorerView.tsx +8 -2
  51. package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +7 -3
  52. package/corpus/templates/design/app/components/design/code-workbench/store.tsx +48 -0
  53. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +6 -0
  54. package/corpus/templates/design/app/i18n/zh-TW.ts +15 -14
  55. package/corpus/templates/design/app/i18n-data.ts +154 -142
  56. package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +32 -14
  57. package/corpus/templates/design/changelog/2026-07-03-design-system-setup-now-indexes-figma-code-and-design-md-sou.md +6 -0
  58. package/corpus/templates/design/changelog/2026-07-03-the-code-editor-is-easier-to-use-with-top-sidebar-tabs-clear.md +6 -0
  59. package/corpus/templates/design/server/lib/builder-design-system-proxy.ts +16 -106
  60. package/corpus/templates/mail/app/root.tsx +60 -1
  61. package/corpus/templates/mail/changelog/2026-07-03-error-screens-now-include-a-feedback-button-with-debug-info.md +6 -0
  62. package/corpus/templates/plan/app/i18n/ar-SA.ts +3 -0
  63. package/corpus/templates/plan/app/i18n/de-DE.ts +4 -0
  64. package/corpus/templates/plan/app/i18n/en-US.ts +4 -0
  65. package/corpus/templates/plan/app/i18n/es-ES.ts +4 -0
  66. package/corpus/templates/plan/app/i18n/fr-FR.ts +4 -0
  67. package/corpus/templates/plan/app/i18n/hi-IN.ts +3 -0
  68. package/corpus/templates/plan/app/i18n/ja-JP.ts +4 -0
  69. package/corpus/templates/plan/app/i18n/ko-KR.ts +4 -0
  70. package/corpus/templates/plan/app/i18n/pt-BR.ts +4 -0
  71. package/corpus/templates/plan/app/i18n/zh-CN.ts +3 -0
  72. package/corpus/templates/plan/app/i18n/zh-TW.ts +3 -0
  73. package/corpus/templates/plan/app/pages/PlansPage.tsx +74 -3
  74. package/corpus/templates/plan/changelog/2026-07-03-error-screens-now-include-a-feedback-button-with-debug-info.md +6 -0
  75. package/corpus/templates/slides/AGENTS.md +7 -3
  76. package/corpus/templates/slides/actions/get-design-system.ts +21 -0
  77. package/corpus/templates/slides/actions/import-file.ts +15 -7
  78. package/corpus/templates/slides/actions/index-design-system-with-builder.ts +14 -37
  79. package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +32 -8
  80. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +2 -2
  81. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +2 -4
  82. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +163 -154
  83. package/corpus/templates/slides/app/components/editor/SlideStyleInspector.tsx +19 -15
  84. package/corpus/templates/slides/app/components/editor/SpeakerNotesPanel.tsx +56 -6
  85. package/corpus/templates/slides/app/components/layout/Layout.tsx +6 -1
  86. package/corpus/templates/slides/app/i18n/ar-SA.ts +8 -7
  87. package/corpus/templates/slides/app/i18n/de-DE.ts +7 -8
  88. package/corpus/templates/slides/app/i18n/en-US.ts +8 -7
  89. package/corpus/templates/slides/app/i18n/es-ES.ts +7 -8
  90. package/corpus/templates/slides/app/i18n/fr-FR.ts +7 -8
  91. package/corpus/templates/slides/app/i18n/hi-IN.ts +8 -7
  92. package/corpus/templates/slides/app/i18n/ja-JP.ts +7 -8
  93. package/corpus/templates/slides/app/i18n/ko-KR.ts +7 -7
  94. package/corpus/templates/slides/app/i18n/pt-BR.ts +7 -8
  95. package/corpus/templates/slides/app/i18n/zh-CN.ts +8 -7
  96. package/corpus/templates/slides/app/i18n/zh-TW.ts +8 -7
  97. package/corpus/templates/slides/app/lib/canvas-zoom.ts +16 -1
  98. package/corpus/templates/slides/app/pages/DeckEditor.tsx +3 -3
  99. package/corpus/templates/slides/changelog/2026-07-03-design-system-setup-now-indexes-figma-code-and-design-md-sou.md +6 -0
  100. package/corpus/templates/slides/changelog/2026-07-03-slide-editing-now-keeps-thumbnails-speaker-notes-styling-and.md +6 -0
  101. package/corpus/templates/slides/server/lib/builder-design-system-proxy.ts +16 -104
  102. package/dist/client/AgentPanel.d.ts.map +1 -1
  103. package/dist/client/AgentPanel.js +2 -1
  104. package/dist/client/AgentPanel.js.map +1 -1
  105. package/dist/client/AssistantChat.d.ts.map +1 -1
  106. package/dist/client/AssistantChat.js +77 -25
  107. package/dist/client/AssistantChat.js.map +1 -1
  108. package/dist/client/ErrorBoundary.d.ts.map +1 -1
  109. package/dist/client/ErrorBoundary.js +37 -2
  110. package/dist/client/ErrorBoundary.js.map +1 -1
  111. package/dist/client/ErrorReportActions.d.ts +13 -0
  112. package/dist/client/ErrorReportActions.d.ts.map +1 -0
  113. package/dist/client/ErrorReportActions.js +14 -0
  114. package/dist/client/ErrorReportActions.js.map +1 -0
  115. package/dist/client/FeedbackButton.d.ts +3 -1
  116. package/dist/client/FeedbackButton.d.ts.map +1 -1
  117. package/dist/client/FeedbackButton.js +6 -5
  118. package/dist/client/FeedbackButton.js.map +1 -1
  119. package/dist/client/db-admin/DbAdminPage.d.ts +9 -1
  120. package/dist/client/db-admin/DbAdminPage.d.ts.map +1 -1
  121. package/dist/client/db-admin/DbAdminPage.js +17 -13
  122. package/dist/client/db-admin/DbAdminPage.js.map +1 -1
  123. package/dist/client/db-admin/SqlEditor.d.ts +3 -1
  124. package/dist/client/db-admin/SqlEditor.d.ts.map +1 -1
  125. package/dist/client/db-admin/SqlEditor.js +3 -3
  126. package/dist/client/db-admin/SqlEditor.js.map +1 -1
  127. package/dist/client/db-admin/TableEditor.d.ts +3 -1
  128. package/dist/client/db-admin/TableEditor.d.ts.map +1 -1
  129. package/dist/client/db-admin/TableEditor.js +6 -6
  130. package/dist/client/db-admin/TableEditor.js.map +1 -1
  131. package/dist/client/db-admin/useAgentSync.d.ts +3 -2
  132. package/dist/client/db-admin/useAgentSync.d.ts.map +1 -1
  133. package/dist/client/db-admin/useAgentSync.js +8 -4
  134. package/dist/client/db-admin/useAgentSync.js.map +1 -1
  135. package/dist/client/db-admin/useDbAdmin.d.ts +11 -7
  136. package/dist/client/db-admin/useDbAdmin.d.ts.map +1 -1
  137. package/dist/client/db-admin/useDbAdmin.js +45 -20
  138. package/dist/client/db-admin/useDbAdmin.js.map +1 -1
  139. package/dist/client/error-reporting.d.ts +19 -0
  140. package/dist/client/error-reporting.d.ts.map +1 -0
  141. package/dist/client/error-reporting.js +71 -0
  142. package/dist/client/error-reporting.js.map +1 -0
  143. package/dist/client/index.d.ts +3 -1
  144. package/dist/client/index.d.ts.map +1 -1
  145. package/dist/client/index.js +3 -1
  146. package/dist/client/index.js.map +1 -1
  147. package/dist/client/org/RequireActiveOrg.d.ts.map +1 -1
  148. package/dist/client/org/RequireActiveOrg.js +2 -1
  149. package/dist/client/org/RequireActiveOrg.js.map +1 -1
  150. package/dist/client/visual-style-controls.d.ts +11 -0
  151. package/dist/client/visual-style-controls.d.ts.map +1 -1
  152. package/dist/client/visual-style-controls.js +350 -1
  153. package/dist/client/visual-style-controls.js.map +1 -1
  154. package/dist/collab/routes.d.ts +1 -1
  155. package/dist/collab/struct-routes.d.ts +1 -1
  156. package/dist/db-admin/index.d.ts +3 -0
  157. package/dist/db-admin/index.d.ts.map +1 -0
  158. package/dist/db-admin/index.js +2 -0
  159. package/dist/db-admin/index.js.map +1 -0
  160. package/dist/db-admin/operations.d.ts +26 -5
  161. package/dist/db-admin/operations.d.ts.map +1 -1
  162. package/dist/db-admin/operations.js +66 -55
  163. package/dist/db-admin/operations.js.map +1 -1
  164. package/dist/localization/default-messages.d.ts +3 -0
  165. package/dist/localization/default-messages.d.ts.map +1 -1
  166. package/dist/localization/default-messages.js +3 -0
  167. package/dist/localization/default-messages.js.map +1 -1
  168. package/dist/notifications/routes.d.ts +1 -1
  169. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  170. package/dist/resources/handlers.d.ts +1 -1
  171. package/dist/secrets/routes.d.ts +9 -9
  172. package/dist/server/builder-design-systems.d.ts +61 -0
  173. package/dist/server/builder-design-systems.d.ts.map +1 -1
  174. package/dist/server/builder-design-systems.js +219 -14
  175. package/dist/server/builder-design-systems.js.map +1 -1
  176. package/dist/server/index.d.ts +1 -1
  177. package/dist/server/index.d.ts.map +1 -1
  178. package/dist/server/index.js +1 -1
  179. package/dist/server/index.js.map +1 -1
  180. package/dist/server/transcribe-voice.d.ts +1 -1
  181. package/dist/templates/default/app/root.tsx +47 -0
  182. package/package.json +2 -1
  183. package/src/templates/default/app/root.tsx +47 -0
@@ -1,3 +1,4 @@
1
+ import { IconColorPicker } from "@tabler/icons-react";
1
2
  import {
2
3
  useEffect,
3
4
  useId,
@@ -8,6 +9,11 @@ import {
8
9
  type ReactNode,
9
10
  } from "react";
10
11
 
12
+ import {
13
+ Popover,
14
+ PopoverContent,
15
+ PopoverTrigger,
16
+ } from "./components/ui/popover.js";
11
17
  import { cn } from "./utils.js";
12
18
 
13
19
  export type VisualControlValue = string | number | boolean;
@@ -50,6 +56,181 @@ function parseDraftNumber(value: string, fallback: number) {
50
56
  return Number.isFinite(next) ? next : fallback;
51
57
  }
52
58
 
59
+ interface RgbaColor {
60
+ r: number;
61
+ g: number;
62
+ b: number;
63
+ a: number;
64
+ }
65
+
66
+ interface HsvaColor {
67
+ h: number;
68
+ s: number;
69
+ v: number;
70
+ a: number;
71
+ }
72
+
73
+ const CHECKERBOARD_IMAGE =
74
+ "linear-gradient(45deg, #d4d4d4 25%, transparent 25%), linear-gradient(-45deg, #d4d4d4 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #d4d4d4 75%), linear-gradient(-45deg, transparent 75%, #d4d4d4 75%)";
75
+ const FALLBACK_RGBA: RgbaColor = { r: 0, g: 0, b: 0, a: 1 };
76
+
77
+ function normalizeRgba(color: RgbaColor): RgbaColor {
78
+ return {
79
+ r: clamp(color.r, 0, 255),
80
+ g: clamp(color.g, 0, 255),
81
+ b: clamp(color.b, 0, 255),
82
+ a: clampFloat(color.a, 0, 1),
83
+ };
84
+ }
85
+
86
+ function normalizeHexColor(value: string): string | null {
87
+ const trimmed = value.trim();
88
+ if (/^#[0-9a-f]{6}$/i.test(trimmed)) return trimmed.toUpperCase();
89
+ if (/^[0-9a-f]{6}$/i.test(trimmed)) return `#${trimmed}`.toUpperCase();
90
+ if (/^#[0-9a-f]{3}$/i.test(trimmed)) {
91
+ return `#${trimmed
92
+ .slice(1)
93
+ .split("")
94
+ .map((char) => char + char)
95
+ .join("")}`.toUpperCase();
96
+ }
97
+ if (/^[0-9a-f]{3}$/i.test(trimmed)) {
98
+ return `#${trimmed
99
+ .split("")
100
+ .map((char) => char + char)
101
+ .join("")}`.toUpperCase();
102
+ }
103
+ return null;
104
+ }
105
+
106
+ function parseCssColor(value: string): RgbaColor | null {
107
+ const trimmed = value.trim();
108
+ if (!trimmed) return null;
109
+ const normalizedHex = normalizeHexColor(trimmed);
110
+ if (normalizedHex) {
111
+ return normalizeRgba({
112
+ r: Number.parseInt(normalizedHex.slice(1, 3), 16),
113
+ g: Number.parseInt(normalizedHex.slice(3, 5), 16),
114
+ b: Number.parseInt(normalizedHex.slice(5, 7), 16),
115
+ a: 1,
116
+ });
117
+ }
118
+ const rgb = trimmed.match(
119
+ /^rgba?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)(?:\s*,\s*([0-9.]+%?))?/i,
120
+ );
121
+ if (rgb) {
122
+ return normalizeRgba({
123
+ r: Number(rgb[1]),
124
+ g: Number(rgb[2]),
125
+ b: Number(rgb[3]),
126
+ a: parseAlpha(rgb[4]),
127
+ });
128
+ }
129
+ const lower = trimmed.toLowerCase();
130
+ if (lower === "transparent" || lower === "rgba(0, 0, 0, 0)") {
131
+ return { r: 0, g: 0, b: 0, a: 0 };
132
+ }
133
+ return null;
134
+ }
135
+
136
+ function channelToHex(value: number): string {
137
+ return clamp(value, 0, 255).toString(16).padStart(2, "0");
138
+ }
139
+
140
+ function rgbaToHex(color: RgbaColor): string {
141
+ const normalized = normalizeRgba(color);
142
+ return `#${channelToHex(normalized.r)}${channelToHex(normalized.g)}${channelToHex(normalized.b)}`.toUpperCase();
143
+ }
144
+
145
+ function rgbaToCss(color: RgbaColor): string {
146
+ const normalized = normalizeRgba(color);
147
+ if (normalized.a >= 1) return rgbaToHex(normalized);
148
+ return `rgba(${normalized.r}, ${normalized.g}, ${normalized.b}, ${trimNumber(normalized.a)})`;
149
+ }
150
+
151
+ function parseAlpha(value: string | undefined): number {
152
+ if (!value) return 1;
153
+ if (value.endsWith("%")) return Number(value.slice(0, -1)) / 100;
154
+ return Number(value);
155
+ }
156
+
157
+ function trimNumber(value: number) {
158
+ return Number(value.toFixed(2)).toString();
159
+ }
160
+
161
+ function rgbaToHsv(color: RgbaColor): HsvaColor {
162
+ const normalized = normalizeRgba(color);
163
+ const r = normalized.r / 255;
164
+ const g = normalized.g / 255;
165
+ const b = normalized.b / 255;
166
+ const max = Math.max(r, g, b);
167
+ const min = Math.min(r, g, b);
168
+ const delta = max - min;
169
+
170
+ let h = 0;
171
+ if (delta !== 0) {
172
+ if (max === r) h = ((g - b) / delta) % 6;
173
+ else if (max === g) h = (b - r) / delta + 2;
174
+ else h = (r - g) / delta + 4;
175
+ h *= 60;
176
+ if (h < 0) h += 360;
177
+ }
178
+
179
+ return {
180
+ h: Math.round(h),
181
+ s: max === 0 ? 0 : Math.round((delta / max) * 100),
182
+ v: Math.round(max * 100),
183
+ a: normalized.a,
184
+ };
185
+ }
186
+
187
+ function hsvToRgba(color: HsvaColor): RgbaColor {
188
+ const h = ((color.h % 360) + 360) % 360;
189
+ const s = clampFloat(color.s, 0, 100) / 100;
190
+ const v = clampFloat(color.v, 0, 100) / 100;
191
+ const chroma = v * s;
192
+ const x = chroma * (1 - Math.abs(((h / 60) % 2) - 1));
193
+ const m = v - chroma;
194
+
195
+ let r = 0;
196
+ let g = 0;
197
+ let b = 0;
198
+ if (h < 60) [r, g, b] = [chroma, x, 0];
199
+ else if (h < 120) [r, g, b] = [x, chroma, 0];
200
+ else if (h < 180) [r, g, b] = [0, chroma, x];
201
+ else if (h < 240) [r, g, b] = [0, x, chroma];
202
+ else if (h < 300) [r, g, b] = [x, 0, chroma];
203
+ else [r, g, b] = [chroma, 0, x];
204
+
205
+ return normalizeRgba({
206
+ r: Math.round((r + m) * 255),
207
+ g: Math.round((g + m) * 255),
208
+ b: Math.round((b + m) * 255),
209
+ a: clampFloat(color.a, 0, 1),
210
+ });
211
+ }
212
+
213
+ function clamp(value: number, min: number, max: number): number {
214
+ if (!Number.isFinite(value)) return min;
215
+ return Math.max(min, Math.min(max, Math.round(value)));
216
+ }
217
+
218
+ function clampFloat(value: number, min: number, max: number): number {
219
+ if (!Number.isFinite(value)) return min;
220
+ return Math.max(min, Math.min(max, value));
221
+ }
222
+
223
+ function swatchBackground(value: string) {
224
+ if (value === "transparent" || value === "rgba(0, 0, 0, 0)") {
225
+ return {
226
+ background: CHECKERBOARD_IMAGE,
227
+ backgroundPosition: "0 0, 0 4px, 4px -4px, -4px 0",
228
+ backgroundSize: "8px 8px",
229
+ };
230
+ }
231
+ return { background: value };
232
+ }
233
+
53
234
  export function VisualInspectorPanel({
54
235
  title,
55
236
  subtitle,
@@ -175,6 +356,435 @@ export function VisualSwatchControl({
175
356
  );
176
357
  }
177
358
 
359
+ export function VisualColorPicker({
360
+ label,
361
+ value,
362
+ onChange,
363
+ documentColors = [],
364
+ allowTransparent = false,
365
+ hexLabel = "Hex",
366
+ documentColorsLabel = "Document colors",
367
+ transparentLabel = "Transparent",
368
+ className,
369
+ }: {
370
+ label: string;
371
+ value: string;
372
+ onChange: (value: string) => void;
373
+ documentColors?: string[];
374
+ allowTransparent?: boolean;
375
+ hexLabel?: string;
376
+ documentColorsLabel?: string;
377
+ transparentLabel?: string;
378
+ className?: string;
379
+ }) {
380
+ const [open, setOpen] = useState(false);
381
+ const color = parseCssColor(value) ?? FALLBACK_RGBA;
382
+ const colorHex = rgbaToHex(color);
383
+ const [draft, setDraft] = useState(colorHex.replace(/^#/, ""));
384
+ const [picking, setPicking] = useState(false);
385
+ const hasEyeDropper = typeof window !== "undefined" && "EyeDropper" in window;
386
+
387
+ useEffect(() => {
388
+ if (!open) setDraft(colorHex.replace(/^#/, ""));
389
+ }, [colorHex, open]);
390
+
391
+ const commitDraft = (nextDraft = draft) => {
392
+ const normalized = normalizeHexColor(nextDraft);
393
+ if (!normalized) {
394
+ setDraft(colorHex.replace(/^#/, ""));
395
+ return;
396
+ }
397
+ setDraft(normalized.replace(/^#/, ""));
398
+ onChange(normalized);
399
+ };
400
+
401
+ const emitHsv = (nextHsv: HsvaColor) => {
402
+ const nextColor = hsvToRgba({ ...nextHsv, a: color.a });
403
+ const nextValue = rgbaToCss(nextColor);
404
+ setDraft(rgbaToHex(nextColor).replace(/^#/, ""));
405
+ onChange(nextValue);
406
+ };
407
+
408
+ const pickScreenColor = async () => {
409
+ if (!hasEyeDropper || picking) return;
410
+ try {
411
+ setPicking(true);
412
+ const EyeDropperCtor = (
413
+ window as unknown as {
414
+ EyeDropper?: new () => { open: () => Promise<{ sRGBHex: string }> };
415
+ }
416
+ ).EyeDropper;
417
+ if (!EyeDropperCtor) return;
418
+ const result = await new EyeDropperCtor().open();
419
+ if (result?.sRGBHex) {
420
+ const next = normalizeHexColor(result.sRGBHex);
421
+ if (next) {
422
+ setDraft(next.replace(/^#/, ""));
423
+ onChange(next);
424
+ }
425
+ }
426
+ } finally {
427
+ setPicking(false);
428
+ }
429
+ };
430
+
431
+ const uniqueDocumentColors = Array.from(
432
+ new Set(
433
+ documentColors
434
+ .map((color) =>
435
+ color === "transparent" ? color : normalizeHexColor(color),
436
+ )
437
+ .filter(Boolean) as string[],
438
+ ),
439
+ ).slice(0, 16);
440
+ const displayValue =
441
+ value === "transparent" || color.a === 0
442
+ ? transparentLabel
443
+ : colorHex.replace(/^#/, "");
444
+ const hsv = rgbaToHsv(color);
445
+
446
+ return (
447
+ <Popover open={open} onOpenChange={setOpen}>
448
+ <PopoverTrigger asChild>
449
+ <button
450
+ type="button"
451
+ aria-label={label}
452
+ className={cn(
453
+ "flex h-7 w-full cursor-pointer items-center gap-1.5 rounded-md border border-border bg-background/70 px-2 text-[11px] shadow-none transition-colors hover:bg-accent/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
454
+ className,
455
+ )}
456
+ >
457
+ <span
458
+ className="size-4 shrink-0 rounded-[3px] border border-border/70"
459
+ style={swatchBackground(value)}
460
+ />
461
+ <span className="min-w-0 flex-1 truncate text-left font-medium tabular-nums text-foreground">
462
+ {displayValue}
463
+ </span>
464
+ <span
465
+ aria-hidden="true"
466
+ className="size-0 border-x-[4px] border-t-[5px] border-x-transparent border-t-muted-foreground/70"
467
+ />
468
+ </button>
469
+ </PopoverTrigger>
470
+ <PopoverContent
471
+ side="left"
472
+ align="start"
473
+ sideOffset={8}
474
+ className="z-[10000] w-[252px] p-0 text-[11px] shadow-xl"
475
+ onFocusOutside={(event) => event.preventDefault()}
476
+ >
477
+ <div className="rounded-md bg-popover text-popover-foreground">
478
+ <VisualSaturationBrightnessField
479
+ hsv={hsv}
480
+ label={label}
481
+ onChange={emitHsv}
482
+ />
483
+
484
+ <div className="mt-2.5 px-3">
485
+ <div className="grid grid-cols-[1.5rem_1fr] items-center gap-x-2">
486
+ <div className="flex items-center justify-center">
487
+ <button
488
+ type="button"
489
+ aria-label={label}
490
+ disabled={!hasEyeDropper || picking}
491
+ onClick={() => void pickScreenColor()}
492
+ className={cn(
493
+ "flex size-6 cursor-pointer items-center justify-center rounded-sm text-muted-foreground transition-colors hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
494
+ (!hasEyeDropper || picking) &&
495
+ "pointer-events-none opacity-40",
496
+ picking &&
497
+ "bg-primary/10 text-primary ring-1 ring-primary/50",
498
+ )}
499
+ >
500
+ <IconColorPicker className="size-4" />
501
+ </button>
502
+ </div>
503
+ <VisualColorTrack
504
+ label={label}
505
+ value={hsv.h}
506
+ min={0}
507
+ max={360}
508
+ backgroundImage="linear-gradient(90deg, #ff0000, #ffff00, #00ff00, #00ffff, #0000ff, #ff00ff, #ff0000)"
509
+ onChange={(nextHue) =>
510
+ emitHsv({ ...hsv, h: nextHue === 360 ? 0 : nextHue })
511
+ }
512
+ />
513
+ <span
514
+ className="mt-2 size-[18px] shrink-0 rounded-[3px] border border-border/60"
515
+ style={swatchBackground(rgbaToCss(color))}
516
+ />
517
+ <VisualColorTrack
518
+ label={transparentLabel}
519
+ value={Math.round(color.a * 100)}
520
+ min={0}
521
+ max={100}
522
+ backgroundImage={`${CHECKERBOARD_IMAGE}, linear-gradient(90deg, rgba(${color.r}, ${color.g}, ${color.b}, 0), rgba(${color.r}, ${color.g}, ${color.b}, 1))`}
523
+ backgroundSize="8px 8px, 8px 8px, 8px 8px, 8px 8px, 100% 100%"
524
+ backgroundPosition="0 0, 0 4px, 4px -4px, -4px 0, 0 0"
525
+ onChange={(nextOpacity) => {
526
+ const nextColor = normalizeRgba({
527
+ ...color,
528
+ a: nextOpacity / 100,
529
+ });
530
+ const nextValue = rgbaToCss(nextColor);
531
+ setDraft(rgbaToHex(nextColor).replace(/^#/, ""));
532
+ onChange(nextValue);
533
+ }}
534
+ />
535
+ </div>
536
+ </div>
537
+
538
+ <div className="mt-2.5 grid grid-cols-[4.5rem_1fr] items-center gap-1 border-b border-border/70 px-3 pb-3">
539
+ <span className="text-[11px] font-medium text-muted-foreground">
540
+ {hexLabel}
541
+ </span>
542
+ <input
543
+ value={draft}
544
+ spellCheck={false}
545
+ onChange={(event) => setDraft(event.currentTarget.value)}
546
+ onFocus={(event) => event.currentTarget.select()}
547
+ onBlur={() => commitDraft()}
548
+ onKeyDown={(event) => {
549
+ if (event.key === "Enter") {
550
+ event.preventDefault();
551
+ commitDraft();
552
+ event.currentTarget.blur();
553
+ }
554
+ if (event.key === "Escape") {
555
+ event.preventDefault();
556
+ setDraft(colorHex.replace(/^#/, ""));
557
+ event.currentTarget.blur();
558
+ }
559
+ }}
560
+ className="h-6 min-w-0 rounded-md border border-input bg-background/70 px-2 text-right text-[11px] uppercase tabular-nums outline-none transition-colors focus:border-ring focus:ring-1 focus:ring-ring"
561
+ />
562
+ </div>
563
+ {(allowTransparent || uniqueDocumentColors.length > 0) && (
564
+ <div className="px-3 py-2.5">
565
+ <div className="mb-2 flex h-5 items-center justify-between text-[11px] text-muted-foreground">
566
+ <span>{documentColorsLabel}</span>
567
+ </div>
568
+ <div className="grid grid-cols-8 gap-1">
569
+ {allowTransparent && (
570
+ <button
571
+ type="button"
572
+ aria-label={transparentLabel}
573
+ aria-pressed={value === "transparent"}
574
+ className={cn(
575
+ "size-5 cursor-pointer rounded-sm border transition-transform hover:scale-110 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
576
+ value === "transparent" || color.a === 0
577
+ ? "border-primary ring-1 ring-primary"
578
+ : "border-border/60",
579
+ )}
580
+ style={swatchBackground("transparent")}
581
+ onClick={() => {
582
+ setDraft(colorHex.replace(/^#/, ""));
583
+ onChange("transparent");
584
+ }}
585
+ />
586
+ )}
587
+ {uniqueDocumentColors.map((color) => (
588
+ <button
589
+ key={color}
590
+ type="button"
591
+ title={color}
592
+ aria-label={color}
593
+ aria-pressed={colorHex === color}
594
+ className={cn(
595
+ "size-5 cursor-pointer rounded-sm border transition-transform hover:scale-110 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
596
+ colorHex === color
597
+ ? "border-primary ring-1 ring-primary"
598
+ : "border-border/60",
599
+ )}
600
+ style={swatchBackground(color)}
601
+ onClick={() => {
602
+ setDraft(color);
603
+ onChange(color);
604
+ }}
605
+ />
606
+ ))}
607
+ </div>
608
+ </div>
609
+ )}
610
+ </div>
611
+ </PopoverContent>
612
+ </Popover>
613
+ );
614
+ }
615
+
616
+ function VisualSaturationBrightnessField({
617
+ hsv,
618
+ label,
619
+ onChange,
620
+ }: {
621
+ hsv: HsvaColor;
622
+ label: string;
623
+ onChange: (value: HsvaColor) => void;
624
+ }) {
625
+ const fieldRef = useRef<HTMLDivElement>(null);
626
+ const draggingRef = useRef(false);
627
+ const hueColor = rgbaToCss(hsvToRgba({ h: hsv.h, s: 100, v: 100, a: 1 }));
628
+
629
+ const updateFromPointer = (event: PointerEvent<HTMLDivElement>) => {
630
+ const rect = fieldRef.current?.getBoundingClientRect();
631
+ if (!rect) return;
632
+ onChange({
633
+ ...hsv,
634
+ s: clamp(((event.clientX - rect.left) / rect.width) * 100, 0, 100),
635
+ v: clamp(100 - ((event.clientY - rect.top) / rect.height) * 100, 0, 100),
636
+ });
637
+ };
638
+
639
+ const stepWithKeyboard = (event: KeyboardEvent<HTMLDivElement>) => {
640
+ const step = event.shiftKey ? 10 : 1;
641
+ if (event.key === "ArrowRight") {
642
+ event.preventDefault();
643
+ onChange({ ...hsv, s: clamp(hsv.s + step, 0, 100) });
644
+ }
645
+ if (event.key === "ArrowLeft") {
646
+ event.preventDefault();
647
+ onChange({ ...hsv, s: clamp(hsv.s - step, 0, 100) });
648
+ }
649
+ if (event.key === "ArrowUp") {
650
+ event.preventDefault();
651
+ onChange({ ...hsv, v: clamp(hsv.v + step, 0, 100) });
652
+ }
653
+ if (event.key === "ArrowDown") {
654
+ event.preventDefault();
655
+ onChange({ ...hsv, v: clamp(hsv.v - step, 0, 100) });
656
+ }
657
+ };
658
+
659
+ return (
660
+ <div
661
+ ref={fieldRef}
662
+ tabIndex={0}
663
+ aria-label={label}
664
+ onPointerDown={(event) => {
665
+ draggingRef.current = true;
666
+ event.currentTarget.setPointerCapture(event.pointerId);
667
+ updateFromPointer(event);
668
+ }}
669
+ onPointerMove={(event) => {
670
+ if (!draggingRef.current) return;
671
+ updateFromPointer(event);
672
+ }}
673
+ onPointerUp={(event) => {
674
+ draggingRef.current = false;
675
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) {
676
+ event.currentTarget.releasePointerCapture(event.pointerId);
677
+ }
678
+ }}
679
+ onPointerCancel={() => {
680
+ draggingRef.current = false;
681
+ }}
682
+ onKeyDown={stepWithKeyboard}
683
+ className="relative h-40 w-full cursor-crosshair overflow-hidden rounded-t-md outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset active:cursor-grabbing"
684
+ style={{
685
+ backgroundImage: `linear-gradient(to top, #000 0%, transparent 100%), linear-gradient(to right, #fff 0%, ${hueColor} 100%)`,
686
+ }}
687
+ >
688
+ <span
689
+ className="pointer-events-none absolute size-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white shadow-[0_0_0_1px_hsl(var(--foreground)/0.6)]"
690
+ style={{
691
+ left: `${hsv.s}%`,
692
+ top: `${100 - hsv.v}%`,
693
+ }}
694
+ />
695
+ </div>
696
+ );
697
+ }
698
+
699
+ function VisualColorTrack({
700
+ label,
701
+ value,
702
+ min,
703
+ max,
704
+ backgroundImage,
705
+ backgroundSize,
706
+ backgroundPosition,
707
+ onChange,
708
+ }: {
709
+ label: string;
710
+ value: number;
711
+ min: number;
712
+ max: number;
713
+ backgroundImage: string;
714
+ backgroundSize?: string;
715
+ backgroundPosition?: string;
716
+ onChange: (value: number) => void;
717
+ }) {
718
+ const trackRef = useRef<HTMLDivElement>(null);
719
+ const draggingRef = useRef(false);
720
+ const percent = ((value - min) / (max - min)) * 100;
721
+
722
+ const updateFromPointer = (event: PointerEvent<HTMLDivElement>) => {
723
+ const rect = trackRef.current?.getBoundingClientRect();
724
+ if (!rect) return;
725
+ const next = min + ((event.clientX - rect.left) / rect.width) * (max - min);
726
+ onChange(clamp(next, min, max));
727
+ };
728
+
729
+ const stepWithKeyboard = (event: KeyboardEvent<HTMLDivElement>) => {
730
+ const step = event.shiftKey ? 10 : 1;
731
+ if (event.key === "ArrowRight" || event.key === "ArrowUp") {
732
+ event.preventDefault();
733
+ onChange(clamp(value + step, min, max));
734
+ }
735
+ if (event.key === "ArrowLeft" || event.key === "ArrowDown") {
736
+ event.preventDefault();
737
+ onChange(clamp(value - step, min, max));
738
+ }
739
+ if (event.key === "Home") {
740
+ event.preventDefault();
741
+ onChange(min);
742
+ }
743
+ if (event.key === "End") {
744
+ event.preventDefault();
745
+ onChange(max);
746
+ }
747
+ };
748
+
749
+ return (
750
+ <div
751
+ ref={trackRef}
752
+ role="slider"
753
+ tabIndex={0}
754
+ aria-label={label}
755
+ aria-valuemin={min}
756
+ aria-valuemax={max}
757
+ aria-valuenow={Math.round(value)}
758
+ onKeyDown={stepWithKeyboard}
759
+ onPointerDown={(event) => {
760
+ draggingRef.current = true;
761
+ event.currentTarget.setPointerCapture(event.pointerId);
762
+ updateFromPointer(event);
763
+ }}
764
+ onPointerMove={(event) => {
765
+ if (!draggingRef.current) return;
766
+ updateFromPointer(event);
767
+ }}
768
+ onPointerUp={(event) => {
769
+ draggingRef.current = false;
770
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) {
771
+ event.currentTarget.releasePointerCapture(event.pointerId);
772
+ }
773
+ }}
774
+ onPointerCancel={() => {
775
+ draggingRef.current = false;
776
+ }}
777
+ className="relative h-3.5 cursor-pointer rounded-full border border-border/60 outline-none ring-offset-background focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 active:cursor-grabbing"
778
+ style={{ backgroundImage, backgroundSize, backgroundPosition }}
779
+ >
780
+ <span
781
+ className="pointer-events-none absolute top-1/2 size-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white shadow-[0_0_0_1px_hsl(var(--foreground)/0.6)]"
782
+ style={{ left: `${clamp(percent, 0, 100)}%` }}
783
+ />
784
+ </div>
785
+ );
786
+ }
787
+
178
788
  export function VisualSegmentedControl({
179
789
  options,
180
790
  value,
@@ -444,9 +1054,12 @@ export function VisualTweakControl({
444
1054
  <VisualControlRow label={tweak.label} className={className}>
445
1055
  {((tweak.type as string) === "color-swatch" ||
446
1056
  (tweak.type as string) === "color-swatches") && (
447
- <VisualSwatchControl
448
- options={tweak.options ?? []}
1057
+ <VisualColorPicker
1058
+ label={tweak.label}
449
1059
  value={String(value)}
1060
+ documentColors={(tweak.options ?? []).map(
1061
+ (option) => option.color ?? option.value,
1062
+ )}
450
1063
  onChange={onChange}
451
1064
  />
452
1065
  )}
@@ -0,0 +1,26 @@
1
+ export {
2
+ listTables,
3
+ getTableSchema,
4
+ getRows,
5
+ applyMutations,
6
+ runSql,
7
+ DbAdminConfirmRequiredError,
8
+ type DbAdminRuntime,
9
+ } from "./operations.js";
10
+
11
+ export type {
12
+ DbAdminColumn,
13
+ DbAdminDialect,
14
+ DbAdminFilter,
15
+ DbAdminFilterOp,
16
+ DbAdminForeignKey,
17
+ DbAdminIndex,
18
+ DbAdminMutation,
19
+ DbAdminMutationResult,
20
+ DbAdminQueryResult,
21
+ DbAdminRowsRequest,
22
+ DbAdminRowsResult,
23
+ DbAdminSort,
24
+ DbAdminTableSchema,
25
+ DbAdminTableSummary,
26
+ } from "./types.js";