@agent-native/core 0.26.8 → 0.27.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 (218) hide show
  1. package/dist/agent/run-ownership.d.ts +12 -0
  2. package/dist/agent/run-ownership.d.ts.map +1 -0
  3. package/dist/agent/run-ownership.js +39 -0
  4. package/dist/agent/run-ownership.js.map +1 -0
  5. package/dist/cli/index.js +2 -2
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/cli/skills.d.ts.map +1 -1
  8. package/dist/cli/skills.js +108 -3
  9. package/dist/cli/skills.js.map +1 -1
  10. package/dist/client/db-admin/DataGrid.d.ts +42 -0
  11. package/dist/client/db-admin/DataGrid.d.ts.map +1 -0
  12. package/dist/client/db-admin/DataGrid.js +204 -0
  13. package/dist/client/db-admin/DataGrid.js.map +1 -0
  14. package/dist/client/db-admin/DbAdminPage.d.ts +2 -0
  15. package/dist/client/db-admin/DbAdminPage.d.ts.map +1 -0
  16. package/dist/client/db-admin/DbAdminPage.js +72 -0
  17. package/dist/client/db-admin/DbAdminPage.js.map +1 -0
  18. package/dist/client/db-admin/DevDatabaseLink.d.ts +19 -0
  19. package/dist/client/db-admin/DevDatabaseLink.d.ts.map +1 -0
  20. package/dist/client/db-admin/DevDatabaseLink.js +25 -0
  21. package/dist/client/db-admin/DevDatabaseLink.js.map +1 -0
  22. package/dist/client/db-admin/EditableCell.d.ts +26 -0
  23. package/dist/client/db-admin/EditableCell.d.ts.map +1 -0
  24. package/dist/client/db-admin/EditableCell.js +150 -0
  25. package/dist/client/db-admin/EditableCell.js.map +1 -0
  26. package/dist/client/db-admin/FilterBar.d.ts +8 -0
  27. package/dist/client/db-admin/FilterBar.d.ts.map +1 -0
  28. package/dist/client/db-admin/FilterBar.js +68 -0
  29. package/dist/client/db-admin/FilterBar.js.map +1 -0
  30. package/dist/client/db-admin/ResultsGrid.d.ts +6 -0
  31. package/dist/client/db-admin/ResultsGrid.d.ts.map +1 -0
  32. package/dist/client/db-admin/ResultsGrid.js +41 -0
  33. package/dist/client/db-admin/ResultsGrid.js.map +1 -0
  34. package/dist/client/db-admin/RowSidePanel.d.ts +18 -0
  35. package/dist/client/db-admin/RowSidePanel.d.ts.map +1 -0
  36. package/dist/client/db-admin/RowSidePanel.js +104 -0
  37. package/dist/client/db-admin/RowSidePanel.js.map +1 -0
  38. package/dist/client/db-admin/SqlEditor.d.ts +8 -0
  39. package/dist/client/db-admin/SqlEditor.d.ts.map +1 -0
  40. package/dist/client/db-admin/SqlEditor.js +350 -0
  41. package/dist/client/db-admin/SqlEditor.js.map +1 -0
  42. package/dist/client/db-admin/TableBrowser.d.ts +10 -0
  43. package/dist/client/db-admin/TableBrowser.d.ts.map +1 -0
  44. package/dist/client/db-admin/TableBrowser.js +61 -0
  45. package/dist/client/db-admin/TableBrowser.js.map +1 -0
  46. package/dist/client/db-admin/TableEditor.d.ts +9 -0
  47. package/dist/client/db-admin/TableEditor.d.ts.map +1 -0
  48. package/dist/client/db-admin/TableEditor.js +254 -0
  49. package/dist/client/db-admin/TableEditor.js.map +1 -0
  50. package/dist/client/db-admin/cell-format.d.ts +55 -0
  51. package/dist/client/db-admin/cell-format.d.ts.map +1 -0
  52. package/dist/client/db-admin/cell-format.js +223 -0
  53. package/dist/client/db-admin/cell-format.js.map +1 -0
  54. package/dist/client/db-admin/changeset.d.ts +74 -0
  55. package/dist/client/db-admin/changeset.d.ts.map +1 -0
  56. package/dist/client/db-admin/changeset.js +169 -0
  57. package/dist/client/db-admin/changeset.js.map +1 -0
  58. package/dist/client/db-admin/export-utils.d.ts +15 -0
  59. package/dist/client/db-admin/export-utils.d.ts.map +1 -0
  60. package/dist/client/db-admin/export-utils.js +62 -0
  61. package/dist/client/db-admin/export-utils.js.map +1 -0
  62. package/dist/client/db-admin/index.d.ts +7 -0
  63. package/dist/client/db-admin/index.d.ts.map +1 -0
  64. package/dist/client/db-admin/index.js +8 -0
  65. package/dist/client/db-admin/index.js.map +1 -0
  66. package/dist/client/db-admin/sql-storage.d.ts +35 -0
  67. package/dist/client/db-admin/sql-storage.d.ts.map +1 -0
  68. package/dist/client/db-admin/sql-storage.js +117 -0
  69. package/dist/client/db-admin/sql-storage.js.map +1 -0
  70. package/dist/client/db-admin/storage.d.ts +24 -0
  71. package/dist/client/db-admin/storage.d.ts.map +1 -0
  72. package/dist/client/db-admin/storage.js +50 -0
  73. package/dist/client/db-admin/storage.js.map +1 -0
  74. package/dist/client/db-admin/useAgentSync.d.ts +22 -0
  75. package/dist/client/db-admin/useAgentSync.d.ts.map +1 -0
  76. package/dist/client/db-admin/useAgentSync.js +120 -0
  77. package/dist/client/db-admin/useAgentSync.js.map +1 -0
  78. package/dist/client/db-admin/useDbAdmin.d.ts +20 -0
  79. package/dist/client/db-admin/useDbAdmin.d.ts.map +1 -0
  80. package/dist/client/db-admin/useDbAdmin.js +154 -0
  81. package/dist/client/db-admin/useDbAdmin.js.map +1 -0
  82. package/dist/client/index.d.ts +1 -0
  83. package/dist/client/index.d.ts.map +1 -1
  84. package/dist/client/index.js +1 -0
  85. package/dist/client/index.js.map +1 -1
  86. package/dist/credentials/index.d.ts.map +1 -1
  87. package/dist/credentials/index.js +25 -5
  88. package/dist/credentials/index.js.map +1 -1
  89. package/dist/db-admin/agent-tools.d.ts +15 -0
  90. package/dist/db-admin/agent-tools.d.ts.map +1 -0
  91. package/dist/db-admin/agent-tools.js +147 -0
  92. package/dist/db-admin/agent-tools.js.map +1 -0
  93. package/dist/db-admin/operations.d.ts +17 -0
  94. package/dist/db-admin/operations.d.ts.map +1 -0
  95. package/dist/db-admin/operations.js +541 -0
  96. package/dist/db-admin/operations.js.map +1 -0
  97. package/dist/db-admin/routes.d.ts +5 -0
  98. package/dist/db-admin/routes.d.ts.map +1 -0
  99. package/dist/db-admin/routes.js +134 -0
  100. package/dist/db-admin/routes.js.map +1 -0
  101. package/dist/db-admin/types.d.ts +85 -0
  102. package/dist/db-admin/types.d.ts.map +1 -0
  103. package/dist/db-admin/types.js +9 -0
  104. package/dist/db-admin/types.js.map +1 -0
  105. package/dist/extensions/url-safety.d.ts +20 -0
  106. package/dist/extensions/url-safety.d.ts.map +1 -1
  107. package/dist/extensions/url-safety.js +43 -0
  108. package/dist/extensions/url-safety.js.map +1 -1
  109. package/dist/file-upload/actions/upload-image.d.ts.map +1 -1
  110. package/dist/file-upload/actions/upload-image.js +6 -1
  111. package/dist/file-upload/actions/upload-image.js.map +1 -1
  112. package/dist/integrations/adapters/email.d.ts.map +1 -1
  113. package/dist/integrations/adapters/email.js +112 -0
  114. package/dist/integrations/adapters/email.js.map +1 -1
  115. package/dist/integrations/types.d.ts +11 -0
  116. package/dist/integrations/types.d.ts.map +1 -1
  117. package/dist/integrations/types.js.map +1 -1
  118. package/dist/scripts/db/exec.d.ts.map +1 -1
  119. package/dist/scripts/db/exec.js +2 -1
  120. package/dist/scripts/db/exec.js.map +1 -1
  121. package/dist/scripts/db/index.d.ts.map +1 -1
  122. package/dist/scripts/db/index.js +1 -0
  123. package/dist/scripts/db/index.js.map +1 -1
  124. package/dist/scripts/db/migrate-encrypt-credentials.d.ts +28 -0
  125. package/dist/scripts/db/migrate-encrypt-credentials.d.ts.map +1 -0
  126. package/dist/scripts/db/migrate-encrypt-credentials.js +190 -0
  127. package/dist/scripts/db/migrate-encrypt-credentials.js.map +1 -0
  128. package/dist/scripts/db/query.d.ts.map +1 -1
  129. package/dist/scripts/db/query.js +2 -1
  130. package/dist/scripts/db/query.js.map +1 -1
  131. package/dist/scripts/db/safety.d.ts +1 -0
  132. package/dist/scripts/db/safety.d.ts.map +1 -1
  133. package/dist/scripts/db/safety.js +32 -0
  134. package/dist/scripts/db/safety.js.map +1 -1
  135. package/dist/scripts/db/scoping.d.ts.map +1 -1
  136. package/dist/scripts/db/scoping.js +11 -1
  137. package/dist/scripts/db/scoping.js.map +1 -1
  138. package/dist/secrets/crypto.d.ts +28 -0
  139. package/dist/secrets/crypto.d.ts.map +1 -0
  140. package/dist/secrets/crypto.js +81 -0
  141. package/dist/secrets/crypto.js.map +1 -0
  142. package/dist/secrets/storage.d.ts.map +1 -1
  143. package/dist/secrets/storage.js +3 -61
  144. package/dist/secrets/storage.js.map +1 -1
  145. package/dist/server/action-discovery.d.ts.map +1 -1
  146. package/dist/server/action-discovery.js +5 -2
  147. package/dist/server/action-discovery.js.map +1 -1
  148. package/dist/server/action-routes.d.ts.map +1 -1
  149. package/dist/server/action-routes.js +24 -7
  150. package/dist/server/action-routes.js.map +1 -1
  151. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  152. package/dist/server/agent-chat-plugin.js +39 -0
  153. package/dist/server/agent-chat-plugin.js.map +1 -1
  154. package/dist/server/auth.d.ts +1 -1
  155. package/dist/server/auth.d.ts.map +1 -1
  156. package/dist/server/auth.js.map +1 -1
  157. package/dist/server/better-auth-instance.js +3 -3
  158. package/dist/server/better-auth-instance.js.map +1 -1
  159. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  160. package/dist/server/core-routes-plugin.js +5 -0
  161. package/dist/server/core-routes-plugin.js.map +1 -1
  162. package/dist/server/csrf.d.ts.map +1 -1
  163. package/dist/server/csrf.js +9 -1
  164. package/dist/server/csrf.js.map +1 -1
  165. package/dist/server/design-token-utils.d.ts +8 -1
  166. package/dist/server/design-token-utils.d.ts.map +1 -1
  167. package/dist/server/design-token-utils.js +12 -4
  168. package/dist/server/design-token-utils.js.map +1 -1
  169. package/dist/templates/default/AGENTS.md +4 -4
  170. package/dist/templates/default/app/routes/database.tsx +13 -0
  171. package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +9 -2
  172. package/dist/templates/workspace-core/.agents/skills/sharing/SKILL.md +7 -1
  173. package/dist/vite/client.d.ts.map +1 -1
  174. package/dist/vite/client.js +4 -0
  175. package/dist/vite/client.js.map +1 -1
  176. package/docs/content/a2a-protocol.md +2 -2
  177. package/docs/content/actions.md +2 -54
  178. package/docs/content/agent-mentions.md +1 -1
  179. package/docs/content/agent-teams.md +1 -1
  180. package/docs/content/authentication.md +2 -2
  181. package/docs/content/cli-adapters.md +33 -17
  182. package/docs/content/client.md +11 -20
  183. package/docs/content/code-agents-ui.md +19 -6
  184. package/docs/content/context-awareness.md +36 -20
  185. package/docs/content/database.md +3 -3
  186. package/docs/content/deployment.md +8 -8
  187. package/docs/content/dispatch.md +1 -1
  188. package/docs/content/external-agents.md +5 -1
  189. package/docs/content/faq.md +1 -0
  190. package/docs/content/frames.md +110 -30
  191. package/docs/content/getting-started.md +15 -14
  192. package/docs/content/mcp-clients.md +1 -1
  193. package/docs/content/mcp-protocol.md +11 -88
  194. package/docs/content/messaging.md +1 -1
  195. package/docs/content/migration-workbench.md +13 -87
  196. package/docs/content/multi-app-workspace.md +2 -38
  197. package/docs/content/multi-tenancy.md +3 -26
  198. package/docs/content/onboarding.md +10 -3
  199. package/docs/content/recurring-jobs.md +2 -2
  200. package/docs/content/security.md +33 -1
  201. package/docs/content/server.md +1 -1
  202. package/docs/content/skills-guide.md +7 -4
  203. package/docs/content/template-assets.md +9 -9
  204. package/docs/content/template-brain.md +114 -388
  205. package/docs/content/template-clips.md +42 -2
  206. package/docs/content/template-content.md +1 -1
  207. package/docs/content/template-design.md +38 -0
  208. package/docs/content/template-dispatch.md +3 -3
  209. package/docs/content/template-forms.md +6 -6
  210. package/docs/content/template-starter.md +2 -2
  211. package/docs/content/using-your-agent.md +56 -0
  212. package/docs/content/workspace-management.md +6 -6
  213. package/docs/content/workspace.md +19 -0
  214. package/package.json +10 -3
  215. package/src/templates/default/AGENTS.md +4 -4
  216. package/src/templates/default/app/routes/database.tsx +13 -0
  217. package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +9 -2
  218. package/src/templates/workspace-core/.agents/skills/sharing/SKILL.md +7 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DevDatabaseLink.d.ts","sourceRoot":"","sources":["../../../src/client/db-admin/DevDatabaseLink.tsx"],"names":[],"mappings":"AAMA,MAAM,WAAW,oBAAoB;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iEAAiE;IACjE,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,EAC9B,SAAS,EACT,EAAgB,GACjB,EAAE,oBAAoB,2CAmBtB"}
@@ -0,0 +1,25 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Link } from "react-router";
3
+ import { IconDatabase } from "@tabler/icons-react";
4
+ import { useDevMode } from "../use-dev-mode.js";
5
+ import { appPath } from "../api-path.js";
6
+ import { cn } from "../utils.js";
7
+ /**
8
+ * Development-only entry point to the database admin.
9
+ *
10
+ * Renders a compact footer link (designed to sit next to `FeedbackButton` /
11
+ * `OrgSwitcher` in a template's sidebar footer) ONLY when the app is running in
12
+ * a development environment (`useDevMode().canToggle`). In production it renders
13
+ * nothing, so it is safe to drop into every template's chrome unconditionally.
14
+ *
15
+ * The page it links to (`/database`) and its backing routes are independently
16
+ * gated to `NODE_ENV === "development"` on the server, so this is purely a
17
+ * convenience affordance — never a security boundary.
18
+ */
19
+ export function DevDatabaseLink({ className, to = "/database", }) {
20
+ const { canToggle } = useDevMode();
21
+ if (!canToggle)
22
+ return null;
23
+ return (_jsxs(Link, { to: appPath(to), title: "Database admin \u2014 development only", className: cn("flex w-full items-center gap-2 rounded-md border border-border/50 px-2.5 py-1.5 text-xs font-medium text-muted-foreground hover:bg-accent/50 hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring", className), children: [_jsx(IconDatabase, { className: "h-3.5 w-3.5 shrink-0" }), _jsx("span", { className: "flex-1 truncate text-left", children: "Database" }), _jsx("span", { className: "rounded bg-muted px-1 py-0.5 text-[9px] uppercase tracking-wide text-muted-foreground", children: "dev" })] }));
24
+ }
25
+ //# sourceMappingURL=DevDatabaseLink.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DevDatabaseLink.js","sourceRoot":"","sources":["../../../src/client/db-admin/DevDatabaseLink.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AACzC,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAQjC;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,eAAe,CAAC,EAC9B,SAAS,EACT,EAAE,GAAG,WAAW,GACK;IACrB,MAAM,EAAE,SAAS,EAAE,GAAG,UAAU,EAAE,CAAC;IACnC,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC;IAC5B,OAAO,CACL,MAAC,IAAI,IACH,EAAE,EAAE,OAAO,CAAC,EAAE,CAAC,EACf,KAAK,EAAC,wCAAmC,EACzC,SAAS,EAAE,EAAE,CACX,oOAAoO,EACpO,SAAS,CACV,aAED,KAAC,YAAY,IAAC,SAAS,EAAC,sBAAsB,GAAG,EACjD,eAAM,SAAS,EAAC,2BAA2B,yBAAgB,EAC3D,eAAM,SAAS,EAAC,uFAAuF,oBAEhG,IACF,CACR,CAAC;AACJ,CAAC","sourcesContent":["import { Link } from \"react-router\";\nimport { IconDatabase } from \"@tabler/icons-react\";\nimport { useDevMode } from \"../use-dev-mode.js\";\nimport { appPath } from \"../api-path.js\";\nimport { cn } from \"../utils.js\";\n\nexport interface DevDatabaseLinkProps {\n className?: string;\n /** Route path for the DB admin page. Defaults to `/database`. */\n to?: string;\n}\n\n/**\n * Development-only entry point to the database admin.\n *\n * Renders a compact footer link (designed to sit next to `FeedbackButton` /\n * `OrgSwitcher` in a template's sidebar footer) ONLY when the app is running in\n * a development environment (`useDevMode().canToggle`). In production it renders\n * nothing, so it is safe to drop into every template's chrome unconditionally.\n *\n * The page it links to (`/database`) and its backing routes are independently\n * gated to `NODE_ENV === \"development\"` on the server, so this is purely a\n * convenience affordance — never a security boundary.\n */\nexport function DevDatabaseLink({\n className,\n to = \"/database\",\n}: DevDatabaseLinkProps) {\n const { canToggle } = useDevMode();\n if (!canToggle) return null;\n return (\n <Link\n to={appPath(to)}\n title=\"Database admin — development only\"\n className={cn(\n \"flex w-full items-center gap-2 rounded-md border border-border/50 px-2.5 py-1.5 text-xs font-medium text-muted-foreground hover:bg-accent/50 hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n className,\n )}\n >\n <IconDatabase className=\"h-3.5 w-3.5 shrink-0\" />\n <span className=\"flex-1 truncate text-left\">Database</span>\n <span className=\"rounded bg-muted px-1 py-0.5 text-[9px] uppercase tracking-wide text-muted-foreground\">\n dev\n </span>\n </Link>\n );\n}\n"]}
@@ -0,0 +1,26 @@
1
+ import { type EditorKind } from "./cell-format.js";
2
+ import type { DbAdminColumn } from "../../db-admin/types.js";
3
+ export interface EditableCellProps {
4
+ column: DbAdminColumn;
5
+ kind: EditorKind;
6
+ value: unknown;
7
+ /** Whether this cell holds a staged (uncommitted) edit. */
8
+ dirty?: boolean;
9
+ /** Whether editing is allowed (false when the table has no PK). */
10
+ editable?: boolean;
11
+ /** Whether this cell is the keyboard-focused/active cell in the grid. */
12
+ active?: boolean;
13
+ /** True if the editor should open immediately (e.g. typing began). */
14
+ editing?: boolean;
15
+ /** Commit a new value into the changeset. */
16
+ onCommit: (value: unknown) => void;
17
+ /** Request entering edit mode. */
18
+ onStartEdit?: () => void;
19
+ /** Request leaving edit mode without committing. */
20
+ onCancelEdit?: () => void;
21
+ /** Move focus after Enter ("down") or Tab ("right"). */
22
+ onNavigate?: (dir: "up" | "down" | "left" | "right") => void;
23
+ className?: string;
24
+ }
25
+ export declare function EditableCell({ column, kind, value, dirty, editable, active, editing, onCommit, onStartEdit, onCancelEdit, onNavigate, className, }: EditableCellProps): import("react/jsx-runtime").JSX.Element;
26
+ //# sourceMappingURL=EditableCell.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EditableCell.d.ts","sourceRoot":"","sources":["../../../src/client/db-admin/EditableCell.tsx"],"names":[],"mappings":"AAYA,OAAO,EACL,KAAK,UAAU,EAQhB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAE7D,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,aAAa,CAAC;IACtB,IAAI,EAAE,UAAU,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,2DAA2D;IAC3D,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,mEAAmE;IACnE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,yEAAyE;IACzE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,sEAAsE;IACtE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,6CAA6C;IAC7C,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,kCAAkC;IAClC,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,oDAAoD;IACpD,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,wDAAwD;IACxD,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,KAAK,IAAI,CAAC;IAC7D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAMD,wBAAgB,YAAY,CAAC,EAC3B,MAAM,EACN,IAAI,EACJ,KAAK,EACL,KAAK,EACL,QAAe,EACf,MAAM,EACN,OAAO,EACP,QAAQ,EACR,WAAW,EACX,YAAY,EACZ,UAAU,EACV,SAAS,GACV,EAAE,iBAAiB,2CA4FnB"}
@@ -0,0 +1,150 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from "react";
3
+ import { IconChevronDown, IconCircleX, IconMaximize, } from "@tabler/icons-react";
4
+ import { cn } from "../utils.js";
5
+ import { Popover, PopoverContent, PopoverTrigger, } from "../components/ui/popover.js";
6
+ import { formatCellValue, inferEnumValues, valueToEditString, parseEditValue, ParseError, cycleTriStateBoolean, formatJsonPretty, } from "./cell-format.js";
7
+ const NULL_TOKEN = (_jsx("span", { className: "italic text-muted-foreground/60 select-none", children: "NULL" }));
8
+ export function EditableCell({ column, kind, value, dirty, editable = true, active, editing, onCommit, onStartEdit, onCancelEdit, onNavigate, className, }) {
9
+ const display = formatCellValue(value, kind);
10
+ // Boolean cells toggle in place rather than opening a text editor.
11
+ if (kind === "boolean") {
12
+ return (_jsx(BooleanCell, { value: value, dirty: dirty, editable: editable, active: active, onCommit: onCommit, onNavigate: onNavigate, className: className }));
13
+ }
14
+ const baseCell = cn("relative h-full w-full px-2 py-1 text-xs truncate outline-none", "font-mono", active && "ring-1 ring-inset ring-ring", dirty && "bg-amber-500/10 ring-1 ring-inset ring-amber-500/50", editable && "cursor-text", className);
15
+ if (editing && editable) {
16
+ if (kind === "enum") {
17
+ return (_jsx(EnumEditor, { column: column, value: value, onCommit: onCommit, onCancel: onCancelEdit, onNavigate: onNavigate }));
18
+ }
19
+ if (kind === "json") {
20
+ return (_jsx(JsonEditor, { value: value, onCommit: onCommit, onCancel: onCancelEdit }));
21
+ }
22
+ return (_jsx(InlineTextEditor, { kind: kind, value: value, nullable: column.nullable, onCommit: onCommit, onCancel: onCancelEdit, onNavigate: onNavigate }));
23
+ }
24
+ return (_jsx("div", { role: "gridcell", tabIndex: active ? 0 : -1, className: baseCell, onDoubleClick: () => editable && onStartEdit?.(), onKeyDown: (e) => {
25
+ if (!editable)
26
+ return;
27
+ if (e.key === "Enter" || e.key === "F2") {
28
+ e.preventDefault();
29
+ onStartEdit?.();
30
+ }
31
+ }, title: display.isNull ? "NULL" : display.text, children: _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("span", { className: "truncate", children: display.isNull ? NULL_TOKEN : display.text }), editable && (kind === "json" || kind === "text") && (_jsx("button", { type: "button", tabIndex: -1, onClick: (e) => {
32
+ e.stopPropagation();
33
+ onStartEdit?.();
34
+ }, className: "ml-auto shrink-0 text-muted-foreground/50 opacity-0 hover:text-foreground group-hover:opacity-100", "aria-label": "Expand editor", children: _jsx(IconMaximize, { className: "h-3 w-3" }) }))] }) }));
35
+ }
36
+ // ─── Boolean (tri-state) ─────────────────────────────────────────────────────
37
+ function BooleanCell({ value, dirty, editable, active, onCommit, onNavigate, className, }) {
38
+ const label = value === true ? "true" : value === false ? "false" : null;
39
+ return (_jsx("div", { role: "gridcell", tabIndex: active ? 0 : -1, className: cn("h-full w-full px-2 py-1 text-xs font-mono outline-none cursor-pointer select-none", active && "ring-1 ring-inset ring-ring", dirty && "bg-amber-500/10 ring-1 ring-inset ring-amber-500/50", className), onClick: () => editable && onCommit(cycleTriStateBoolean(value)), onKeyDown: (e) => {
40
+ if (!editable)
41
+ return;
42
+ if (e.key === " " || e.key === "Enter") {
43
+ e.preventDefault();
44
+ onCommit(cycleTriStateBoolean(value));
45
+ }
46
+ else if (e.key === "ArrowDown") {
47
+ e.preventDefault();
48
+ onNavigate?.("down");
49
+ }
50
+ else if (e.key === "ArrowUp") {
51
+ e.preventDefault();
52
+ onNavigate?.("up");
53
+ }
54
+ }, children: label === null ? NULL_TOKEN : label }));
55
+ }
56
+ // ─── Inline text / number / timestamp / uuid editor ──────────────────────────
57
+ function InlineTextEditor({ kind, value, nullable, onCommit, onCancel, onNavigate, }) {
58
+ const ref = useRef(null);
59
+ const [text, setText] = useState(() => valueToEditString(value, kind));
60
+ const [error, setError] = useState(null);
61
+ useEffect(() => {
62
+ ref.current?.focus();
63
+ ref.current?.select();
64
+ }, []);
65
+ const commit = (nav) => {
66
+ try {
67
+ const parsed = parseEditValue(text, kind);
68
+ setError(null);
69
+ onCommit(parsed);
70
+ if (nav)
71
+ onNavigate?.(nav);
72
+ }
73
+ catch (err) {
74
+ setError(err instanceof ParseError ? err.message : String(err));
75
+ }
76
+ };
77
+ return (_jsxs("div", { className: "relative h-full w-full", children: [_jsx("input", { ref: ref, type: kind === "number" ? "text" : "text", inputMode: kind === "number" ? "decimal" : undefined, value: text, onChange: (e) => setText(e.target.value), onBlur: () => commit(), onKeyDown: (e) => {
78
+ if (e.key === "Enter") {
79
+ e.preventDefault();
80
+ commit("down");
81
+ }
82
+ else if (e.key === "Tab") {
83
+ e.preventDefault();
84
+ commit("right");
85
+ }
86
+ else if (e.key === "Escape") {
87
+ e.preventDefault();
88
+ onCancel?.();
89
+ }
90
+ }, className: cn("h-full w-full bg-background px-2 py-1 text-xs font-mono outline-none", "ring-2 ring-inset ring-ring", error && "ring-destructive") }), nullable && (_jsx("button", { type: "button", tabIndex: -1, title: "Set NULL", onMouseDown: (e) => {
91
+ e.preventDefault();
92
+ onCommit(null);
93
+ }, className: "absolute right-1 top-1/2 -translate-y-1/2 text-muted-foreground/50 hover:text-foreground", children: _jsx(IconCircleX, { className: "h-3.5 w-3.5" }) })), error && (_jsx("div", { className: "absolute left-0 top-full z-50 mt-0.5 rounded border border-destructive bg-popover px-2 py-1 text-[11px] text-destructive shadow-md", children: error }))] }));
94
+ }
95
+ // ─── Enum (select) editor ────────────────────────────────────────────────────
96
+ function EnumEditor({ column, value, onCommit, onCancel, onNavigate, }) {
97
+ const ref = useRef(null);
98
+ const options = inferEnumValues(column) ?? [];
99
+ useEffect(() => {
100
+ ref.current?.focus();
101
+ }, []);
102
+ return (_jsxs("select", { ref: ref, defaultValue: value === null || value === undefined ? "" : String(value), onChange: (e) => {
103
+ const v = e.target.value;
104
+ onCommit(v === "" ? null : v);
105
+ onNavigate?.("down");
106
+ }, onBlur: () => onCancel?.(), onKeyDown: (e) => {
107
+ if (e.key === "Escape")
108
+ onCancel?.();
109
+ }, className: "h-full w-full appearance-none bg-background px-2 py-1 text-xs font-mono outline-none ring-2 ring-inset ring-ring", children: [column.nullable && _jsx("option", { value: "", children: "NULL" }), options.map((opt) => (_jsx("option", { value: opt, children: opt }, opt)))] }));
110
+ }
111
+ // ─── JSON / long-text expanding editor ───────────────────────────────────────
112
+ function JsonEditor({ value, onCommit, onCancel, }) {
113
+ const [open, setOpen] = useState(true);
114
+ const [text, setText] = useState(() => formatJsonPretty(value));
115
+ const [error, setError] = useState(null);
116
+ const commit = () => {
117
+ if (text.trim() === "") {
118
+ onCommit(null);
119
+ setOpen(false);
120
+ return;
121
+ }
122
+ try {
123
+ const parsed = parseEditValue(text, "json");
124
+ setError(null);
125
+ onCommit(parsed);
126
+ setOpen(false);
127
+ }
128
+ catch (err) {
129
+ setError(err instanceof ParseError ? err.message : String(err));
130
+ }
131
+ };
132
+ return (_jsxs(Popover, { open: open, onOpenChange: (o) => {
133
+ if (!o)
134
+ onCancel?.();
135
+ setOpen(o);
136
+ }, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: "flex h-full w-full items-center gap-1 px-2 py-1 text-xs font-mono ring-2 ring-inset ring-ring outline-none", children: [_jsx("span", { className: "truncate", children: formatJsonPretty(value) || "{}" }), _jsx(IconChevronDown, { className: "ml-auto h-3 w-3 shrink-0" })] }) }), _jsxs(PopoverContent, { align: "start", className: "w-[28rem] p-2", onOpenAutoFocus: (e) => e.preventDefault(), children: [_jsx("textarea", { autoFocus: true, value: text, onChange: (e) => setText(e.target.value), onKeyDown: (e) => {
137
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
138
+ e.preventDefault();
139
+ commit();
140
+ }
141
+ else if (e.key === "Escape") {
142
+ e.preventDefault();
143
+ onCancel?.();
144
+ }
145
+ }, rows: 10, spellCheck: false, className: cn("w-full resize-y rounded border border-border bg-background p-2 font-mono text-xs outline-none focus:ring-1 focus:ring-ring", error && "border-destructive") }), error && (_jsx("div", { className: "mt-1 text-[11px] text-destructive", children: error })), _jsxs("div", { className: "mt-2 flex items-center justify-between", children: [_jsx("button", { type: "button", onClick: () => {
146
+ onCommit(null);
147
+ setOpen(false);
148
+ }, className: "rounded px-2 py-1 text-[11px] text-muted-foreground hover:text-foreground", children: "Set NULL" }), _jsxs("div", { className: "flex gap-1", children: [_jsx("button", { type: "button", onClick: () => onCancel?.(), className: "rounded px-2 py-1 text-[11px] text-muted-foreground hover:bg-muted hover:text-foreground", children: "Cancel" }), _jsx("button", { type: "button", onClick: commit, className: "rounded bg-primary px-2 py-1 text-[11px] font-medium text-primary-foreground hover:bg-primary/90", children: "Apply" })] })] })] })] }));
149
+ }
150
+ //# sourceMappingURL=EditableCell.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EditableCell.js","sourceRoot":"","sources":["../../../src/client/db-admin/EditableCell.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport {\n IconChevronDown,\n IconCircleX,\n IconMaximize,\n} from \"@tabler/icons-react\";\nimport { cn } from \"../utils.js\";\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"../components/ui/popover.js\";\nimport {\n type EditorKind,\n formatCellValue,\n inferEnumValues,\n valueToEditString,\n parseEditValue,\n ParseError,\n cycleTriStateBoolean,\n formatJsonPretty,\n} from \"./cell-format.js\";\nimport type { DbAdminColumn } from \"../../db-admin/types.js\";\n\nexport interface EditableCellProps {\n column: DbAdminColumn;\n kind: EditorKind;\n value: unknown;\n /** Whether this cell holds a staged (uncommitted) edit. */\n dirty?: boolean;\n /** Whether editing is allowed (false when the table has no PK). */\n editable?: boolean;\n /** Whether this cell is the keyboard-focused/active cell in the grid. */\n active?: boolean;\n /** True if the editor should open immediately (e.g. typing began). */\n editing?: boolean;\n /** Commit a new value into the changeset. */\n onCommit: (value: unknown) => void;\n /** Request entering edit mode. */\n onStartEdit?: () => void;\n /** Request leaving edit mode without committing. */\n onCancelEdit?: () => void;\n /** Move focus after Enter (\"down\") or Tab (\"right\"). */\n onNavigate?: (dir: \"up\" | \"down\" | \"left\" | \"right\") => void;\n className?: string;\n}\n\nconst NULL_TOKEN = (\n <span className=\"italic text-muted-foreground/60 select-none\">NULL</span>\n);\n\nexport function EditableCell({\n column,\n kind,\n value,\n dirty,\n editable = true,\n active,\n editing,\n onCommit,\n onStartEdit,\n onCancelEdit,\n onNavigate,\n className,\n}: EditableCellProps) {\n const display = formatCellValue(value, kind);\n\n // Boolean cells toggle in place rather than opening a text editor.\n if (kind === \"boolean\") {\n return (\n <BooleanCell\n value={value}\n dirty={dirty}\n editable={editable}\n active={active}\n onCommit={onCommit}\n onNavigate={onNavigate}\n className={className}\n />\n );\n }\n\n const baseCell = cn(\n \"relative h-full w-full px-2 py-1 text-xs truncate outline-none\",\n \"font-mono\",\n active && \"ring-1 ring-inset ring-ring\",\n dirty && \"bg-amber-500/10 ring-1 ring-inset ring-amber-500/50\",\n editable && \"cursor-text\",\n className,\n );\n\n if (editing && editable) {\n if (kind === \"enum\") {\n return (\n <EnumEditor\n column={column}\n value={value}\n onCommit={onCommit}\n onCancel={onCancelEdit}\n onNavigate={onNavigate}\n />\n );\n }\n if (kind === \"json\") {\n return (\n <JsonEditor value={value} onCommit={onCommit} onCancel={onCancelEdit} />\n );\n }\n return (\n <InlineTextEditor\n kind={kind}\n value={value}\n nullable={column.nullable}\n onCommit={onCommit}\n onCancel={onCancelEdit}\n onNavigate={onNavigate}\n />\n );\n }\n\n return (\n <div\n role=\"gridcell\"\n tabIndex={active ? 0 : -1}\n className={baseCell}\n onDoubleClick={() => editable && onStartEdit?.()}\n onKeyDown={(e) => {\n if (!editable) return;\n if (e.key === \"Enter\" || e.key === \"F2\") {\n e.preventDefault();\n onStartEdit?.();\n }\n }}\n title={display.isNull ? \"NULL\" : display.text}\n >\n <div className=\"flex items-center gap-1\">\n <span className=\"truncate\">\n {display.isNull ? NULL_TOKEN : display.text}\n </span>\n {editable && (kind === \"json\" || kind === \"text\") && (\n <button\n type=\"button\"\n tabIndex={-1}\n onClick={(e) => {\n e.stopPropagation();\n onStartEdit?.();\n }}\n className=\"ml-auto shrink-0 text-muted-foreground/50 opacity-0 hover:text-foreground group-hover:opacity-100\"\n aria-label=\"Expand editor\"\n >\n <IconMaximize className=\"h-3 w-3\" />\n </button>\n )}\n </div>\n </div>\n );\n}\n\n// ─── Boolean (tri-state) ─────────────────────────────────────────────────────\n\nfunction BooleanCell({\n value,\n dirty,\n editable,\n active,\n onCommit,\n onNavigate,\n className,\n}: {\n value: unknown;\n dirty?: boolean;\n editable: boolean;\n active?: boolean;\n onCommit: (v: unknown) => void;\n onNavigate?: (dir: \"up\" | \"down\" | \"left\" | \"right\") => void;\n className?: string;\n}) {\n const label = value === true ? \"true\" : value === false ? \"false\" : null;\n return (\n <div\n role=\"gridcell\"\n tabIndex={active ? 0 : -1}\n className={cn(\n \"h-full w-full px-2 py-1 text-xs font-mono outline-none cursor-pointer select-none\",\n active && \"ring-1 ring-inset ring-ring\",\n dirty && \"bg-amber-500/10 ring-1 ring-inset ring-amber-500/50\",\n className,\n )}\n onClick={() => editable && onCommit(cycleTriStateBoolean(value))}\n onKeyDown={(e) => {\n if (!editable) return;\n if (e.key === \" \" || e.key === \"Enter\") {\n e.preventDefault();\n onCommit(cycleTriStateBoolean(value));\n } else if (e.key === \"ArrowDown\") {\n e.preventDefault();\n onNavigate?.(\"down\");\n } else if (e.key === \"ArrowUp\") {\n e.preventDefault();\n onNavigate?.(\"up\");\n }\n }}\n >\n {label === null ? NULL_TOKEN : label}\n </div>\n );\n}\n\n// ─── Inline text / number / timestamp / uuid editor ──────────────────────────\n\nfunction InlineTextEditor({\n kind,\n value,\n nullable,\n onCommit,\n onCancel,\n onNavigate,\n}: {\n kind: EditorKind;\n value: unknown;\n nullable: boolean;\n onCommit: (v: unknown) => void;\n onCancel?: () => void;\n onNavigate?: (dir: \"up\" | \"down\" | \"left\" | \"right\") => void;\n}) {\n const ref = useRef<HTMLInputElement>(null);\n const [text, setText] = useState(() => valueToEditString(value, kind));\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n ref.current?.focus();\n ref.current?.select();\n }, []);\n\n const commit = (nav?: \"down\" | \"right\") => {\n try {\n const parsed = parseEditValue(text, kind);\n setError(null);\n onCommit(parsed);\n if (nav) onNavigate?.(nav);\n } catch (err) {\n setError(err instanceof ParseError ? err.message : String(err));\n }\n };\n\n return (\n <div className=\"relative h-full w-full\">\n <input\n ref={ref}\n type={kind === \"number\" ? \"text\" : \"text\"}\n inputMode={kind === \"number\" ? \"decimal\" : undefined}\n value={text}\n onChange={(e) => setText(e.target.value)}\n onBlur={() => commit()}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n commit(\"down\");\n } else if (e.key === \"Tab\") {\n e.preventDefault();\n commit(\"right\");\n } else if (e.key === \"Escape\") {\n e.preventDefault();\n onCancel?.();\n }\n }}\n className={cn(\n \"h-full w-full bg-background px-2 py-1 text-xs font-mono outline-none\",\n \"ring-2 ring-inset ring-ring\",\n error && \"ring-destructive\",\n )}\n />\n {nullable && (\n <button\n type=\"button\"\n tabIndex={-1}\n title=\"Set NULL\"\n onMouseDown={(e) => {\n e.preventDefault();\n onCommit(null);\n }}\n className=\"absolute right-1 top-1/2 -translate-y-1/2 text-muted-foreground/50 hover:text-foreground\"\n >\n <IconCircleX className=\"h-3.5 w-3.5\" />\n </button>\n )}\n {error && (\n <div className=\"absolute left-0 top-full z-50 mt-0.5 rounded border border-destructive bg-popover px-2 py-1 text-[11px] text-destructive shadow-md\">\n {error}\n </div>\n )}\n </div>\n );\n}\n\n// ─── Enum (select) editor ────────────────────────────────────────────────────\n\nfunction EnumEditor({\n column,\n value,\n onCommit,\n onCancel,\n onNavigate,\n}: {\n column: DbAdminColumn;\n value: unknown;\n onCommit: (v: unknown) => void;\n onCancel?: () => void;\n onNavigate?: (dir: \"up\" | \"down\" | \"left\" | \"right\") => void;\n}) {\n const ref = useRef<HTMLSelectElement>(null);\n const options = inferEnumValues(column) ?? [];\n\n useEffect(() => {\n ref.current?.focus();\n }, []);\n\n return (\n <select\n ref={ref}\n defaultValue={value === null || value === undefined ? \"\" : String(value)}\n onChange={(e) => {\n const v = e.target.value;\n onCommit(v === \"\" ? null : v);\n onNavigate?.(\"down\");\n }}\n onBlur={() => onCancel?.()}\n onKeyDown={(e) => {\n if (e.key === \"Escape\") onCancel?.();\n }}\n className=\"h-full w-full appearance-none bg-background px-2 py-1 text-xs font-mono outline-none ring-2 ring-inset ring-ring\"\n >\n {column.nullable && <option value=\"\">NULL</option>}\n {options.map((opt) => (\n <option key={opt} value={opt}>\n {opt}\n </option>\n ))}\n </select>\n );\n}\n\n// ─── JSON / long-text expanding editor ───────────────────────────────────────\n\nfunction JsonEditor({\n value,\n onCommit,\n onCancel,\n}: {\n value: unknown;\n onCommit: (v: unknown) => void;\n onCancel?: () => void;\n}) {\n const [open, setOpen] = useState(true);\n const [text, setText] = useState(() => formatJsonPretty(value));\n const [error, setError] = useState<string | null>(null);\n\n const commit = () => {\n if (text.trim() === \"\") {\n onCommit(null);\n setOpen(false);\n return;\n }\n try {\n const parsed = parseEditValue(text, \"json\");\n setError(null);\n onCommit(parsed);\n setOpen(false);\n } catch (err) {\n setError(err instanceof ParseError ? err.message : String(err));\n }\n };\n\n return (\n <Popover\n open={open}\n onOpenChange={(o) => {\n if (!o) onCancel?.();\n setOpen(o);\n }}\n >\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n className=\"flex h-full w-full items-center gap-1 px-2 py-1 text-xs font-mono ring-2 ring-inset ring-ring outline-none\"\n >\n <span className=\"truncate\">{formatJsonPretty(value) || \"{}\"}</span>\n <IconChevronDown className=\"ml-auto h-3 w-3 shrink-0\" />\n </button>\n </PopoverTrigger>\n <PopoverContent\n align=\"start\"\n className=\"w-[28rem] p-2\"\n onOpenAutoFocus={(e) => e.preventDefault()}\n >\n <textarea\n autoFocus\n value={text}\n onChange={(e) => setText(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n commit();\n } else if (e.key === \"Escape\") {\n e.preventDefault();\n onCancel?.();\n }\n }}\n rows={10}\n spellCheck={false}\n className={cn(\n \"w-full resize-y rounded border border-border bg-background p-2 font-mono text-xs outline-none focus:ring-1 focus:ring-ring\",\n error && \"border-destructive\",\n )}\n />\n {error && (\n <div className=\"mt-1 text-[11px] text-destructive\">{error}</div>\n )}\n <div className=\"mt-2 flex items-center justify-between\">\n <button\n type=\"button\"\n onClick={() => {\n onCommit(null);\n setOpen(false);\n }}\n className=\"rounded px-2 py-1 text-[11px] text-muted-foreground hover:text-foreground\"\n >\n Set NULL\n </button>\n <div className=\"flex gap-1\">\n <button\n type=\"button\"\n onClick={() => onCancel?.()}\n className=\"rounded px-2 py-1 text-[11px] text-muted-foreground hover:bg-muted hover:text-foreground\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={commit}\n className=\"rounded bg-primary px-2 py-1 text-[11px] font-medium text-primary-foreground hover:bg-primary/90\"\n >\n Apply\n </button>\n </div>\n </div>\n </PopoverContent>\n </Popover>\n );\n}\n"]}
@@ -0,0 +1,8 @@
1
+ import type { DbAdminColumn, DbAdminFilter } from "../../db-admin/types.js";
2
+ export interface FilterBarProps {
3
+ columns: DbAdminColumn[];
4
+ filters: DbAdminFilter[];
5
+ onChange: (filters: DbAdminFilter[]) => void;
6
+ }
7
+ export declare function FilterBar({ columns, filters, onChange }: FilterBarProps): import("react/jsx-runtime").JSX.Element;
8
+ //# sourceMappingURL=FilterBar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FilterBar.d.ts","sourceRoot":"","sources":["../../../src/client/db-admin/FilterBar.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,aAAa,EACb,aAAa,EAEd,MAAM,yBAAyB,CAAC;AA6BjC,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,QAAQ,EAAE,CAAC,OAAO,EAAE,aAAa,EAAE,KAAK,IAAI,CAAC;CAC9C;AAED,wBAAgB,SAAS,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,cAAc,2CA4BvE"}
@@ -0,0 +1,68 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { IconFilter, IconPlus, IconX } from "@tabler/icons-react";
4
+ import { cn } from "../utils.js";
5
+ import { Popover, PopoverContent, PopoverTrigger, } from "../components/ui/popover.js";
6
+ const OPS = [
7
+ { value: "eq", label: "=", needsValue: true },
8
+ { value: "neq", label: "≠", needsValue: true },
9
+ { value: "lt", label: "<", needsValue: true },
10
+ { value: "lte", label: "≤", needsValue: true },
11
+ { value: "gt", label: ">", needsValue: true },
12
+ { value: "gte", label: "≥", needsValue: true },
13
+ { value: "like", label: "like", needsValue: true },
14
+ { value: "ilike", label: "ilike", needsValue: true },
15
+ { value: "in", label: "in", needsValue: true },
16
+ { value: "is_null", label: "is null", needsValue: false },
17
+ { value: "not_null", label: "not null", needsValue: false },
18
+ ];
19
+ function opNeedsValue(op) {
20
+ return OPS.find((o) => o.value === op)?.needsValue ?? true;
21
+ }
22
+ function opLabel(op) {
23
+ return OPS.find((o) => o.value === op)?.label ?? op;
24
+ }
25
+ const selectCls = "h-7 rounded-md border border-border bg-background px-2 text-xs outline-none focus:ring-1 focus:ring-ring";
26
+ const inputCls = "h-7 rounded-md border border-border bg-background px-2 text-xs outline-none focus:ring-1 focus:ring-ring";
27
+ export function FilterBar({ columns, filters, onChange }) {
28
+ const removeAt = (i) => onChange(filters.filter((_, idx) => idx !== i));
29
+ return (_jsxs("div", { className: "flex flex-wrap items-center gap-1.5", children: [filters.map((f, i) => (_jsx(FilterChip, { filter: f, onRemove: () => removeAt(i) }, `${f.column}-${f.op}-${i}`))), _jsx(AddFilterPopover, { columns: columns, onAdd: (f) => onChange([...filters, f]) }), filters.length > 0 && (_jsx("button", { type: "button", onClick: () => onChange([]), className: "ml-1 text-xs text-muted-foreground hover:text-foreground", children: "Clear all" }))] }));
30
+ }
31
+ function FilterChip({ filter, onRemove, }) {
32
+ const valueText = opNeedsValue(filter.op)
33
+ ? ` ${String(filter.value ?? "")}`
34
+ : "";
35
+ return (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-md border border-border bg-muted px-2 py-1 text-xs", children: [_jsx("span", { className: "font-medium text-foreground", children: filter.column }), _jsx("span", { className: "text-muted-foreground", children: opLabel(filter.op) }), valueText && _jsx("span", { className: "text-foreground", children: valueText.trim() }), _jsx("button", { type: "button", onClick: onRemove, className: "ml-0.5 text-muted-foreground hover:text-foreground", "aria-label": "Remove filter", children: _jsx(IconX, { className: "h-3 w-3" }) })] }));
36
+ }
37
+ function AddFilterPopover({ columns, onAdd, }) {
38
+ const [open, setOpen] = useState(false);
39
+ const [column, setColumn] = useState(columns[0]?.name ?? "");
40
+ const [op, setOp] = useState("eq");
41
+ const [value, setValue] = useState("");
42
+ const reset = () => {
43
+ setColumn(columns[0]?.name ?? "");
44
+ setOp("eq");
45
+ setValue("");
46
+ };
47
+ const submit = () => {
48
+ if (!column)
49
+ return;
50
+ const filter = { column, op };
51
+ if (opNeedsValue(op))
52
+ filter.value = value;
53
+ onAdd(filter);
54
+ reset();
55
+ setOpen(false);
56
+ };
57
+ return (_jsxs(Popover, { open: open, onOpenChange: (o) => {
58
+ setOpen(o);
59
+ if (!o)
60
+ reset();
61
+ }, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: "inline-flex items-center gap-1 rounded-md border border-dashed border-border px-2 py-1 text-xs text-muted-foreground hover:border-border hover:text-foreground", children: [_jsx(IconFilter, { className: "h-3.5 w-3.5" }), "Add filter"] }) }), _jsxs(PopoverContent, { align: "start", className: "w-auto min-w-[20rem] p-2", children: [_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("select", { value: column, onChange: (e) => setColumn(e.target.value), className: cn(selectCls, "max-w-[10rem] flex-1"), children: columns.map((c) => (_jsx("option", { value: c.name, children: c.name }, c.name))) }), _jsx("select", { value: op, onChange: (e) => setOp(e.target.value), className: selectCls, children: OPS.map((o) => (_jsx("option", { value: o.value, children: o.label }, o.value))) }), opNeedsValue(op) && (_jsx("input", { autoFocus: true, value: value, onChange: (e) => setValue(e.target.value), onKeyDown: (e) => {
62
+ if (e.key === "Enter") {
63
+ e.preventDefault();
64
+ submit();
65
+ }
66
+ }, placeholder: op === "in" ? "a, b, c" : "value", className: cn(inputCls, "flex-1") }))] }), _jsx("div", { className: "mt-2 flex justify-end", children: _jsxs("button", { type: "button", onClick: submit, className: "inline-flex items-center gap-1 rounded-md bg-primary px-2.5 py-1 text-xs font-medium text-primary-foreground hover:bg-primary/90", children: [_jsx(IconPlus, { className: "h-3.5 w-3.5" }), "Apply filter"] }) })] })] }));
67
+ }
68
+ //# sourceMappingURL=FilterBar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FilterBar.js","sourceRoot":"","sources":["../../../src/client/db-admin/FilterBar.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useState } from \"react\";\nimport { IconFilter, IconPlus, IconX } from \"@tabler/icons-react\";\nimport { cn } from \"../utils.js\";\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"../components/ui/popover.js\";\nimport type {\n DbAdminColumn,\n DbAdminFilter,\n DbAdminFilterOp,\n} from \"../../db-admin/types.js\";\n\nconst OPS: { value: DbAdminFilterOp; label: string; needsValue: boolean }[] = [\n { value: \"eq\", label: \"=\", needsValue: true },\n { value: \"neq\", label: \"≠\", needsValue: true },\n { value: \"lt\", label: \"<\", needsValue: true },\n { value: \"lte\", label: \"≤\", needsValue: true },\n { value: \"gt\", label: \">\", needsValue: true },\n { value: \"gte\", label: \"≥\", needsValue: true },\n { value: \"like\", label: \"like\", needsValue: true },\n { value: \"ilike\", label: \"ilike\", needsValue: true },\n { value: \"in\", label: \"in\", needsValue: true },\n { value: \"is_null\", label: \"is null\", needsValue: false },\n { value: \"not_null\", label: \"not null\", needsValue: false },\n];\n\nfunction opNeedsValue(op: DbAdminFilterOp): boolean {\n return OPS.find((o) => o.value === op)?.needsValue ?? true;\n}\n\nfunction opLabel(op: DbAdminFilterOp): string {\n return OPS.find((o) => o.value === op)?.label ?? op;\n}\n\nconst selectCls =\n \"h-7 rounded-md border border-border bg-background px-2 text-xs outline-none focus:ring-1 focus:ring-ring\";\nconst inputCls =\n \"h-7 rounded-md border border-border bg-background px-2 text-xs outline-none focus:ring-1 focus:ring-ring\";\n\nexport interface FilterBarProps {\n columns: DbAdminColumn[];\n filters: DbAdminFilter[];\n onChange: (filters: DbAdminFilter[]) => void;\n}\n\nexport function FilterBar({ columns, filters, onChange }: FilterBarProps) {\n const removeAt = (i: number) =>\n onChange(filters.filter((_, idx) => idx !== i));\n\n return (\n <div className=\"flex flex-wrap items-center gap-1.5\">\n {filters.map((f, i) => (\n <FilterChip\n key={`${f.column}-${f.op}-${i}`}\n filter={f}\n onRemove={() => removeAt(i)}\n />\n ))}\n <AddFilterPopover\n columns={columns}\n onAdd={(f) => onChange([...filters, f])}\n />\n {filters.length > 0 && (\n <button\n type=\"button\"\n onClick={() => onChange([])}\n className=\"ml-1 text-xs text-muted-foreground hover:text-foreground\"\n >\n Clear all\n </button>\n )}\n </div>\n );\n}\n\nfunction FilterChip({\n filter,\n onRemove,\n}: {\n filter: DbAdminFilter;\n onRemove: () => void;\n}) {\n const valueText = opNeedsValue(filter.op)\n ? ` ${String(filter.value ?? \"\")}`\n : \"\";\n return (\n <span className=\"inline-flex items-center gap-1 rounded-md border border-border bg-muted px-2 py-1 text-xs\">\n <span className=\"font-medium text-foreground\">{filter.column}</span>\n <span className=\"text-muted-foreground\">{opLabel(filter.op)}</span>\n {valueText && <span className=\"text-foreground\">{valueText.trim()}</span>}\n <button\n type=\"button\"\n onClick={onRemove}\n className=\"ml-0.5 text-muted-foreground hover:text-foreground\"\n aria-label=\"Remove filter\"\n >\n <IconX className=\"h-3 w-3\" />\n </button>\n </span>\n );\n}\n\nfunction AddFilterPopover({\n columns,\n onAdd,\n}: {\n columns: DbAdminColumn[];\n onAdd: (f: DbAdminFilter) => void;\n}) {\n const [open, setOpen] = useState(false);\n const [column, setColumn] = useState(columns[0]?.name ?? \"\");\n const [op, setOp] = useState<DbAdminFilterOp>(\"eq\");\n const [value, setValue] = useState(\"\");\n\n const reset = () => {\n setColumn(columns[0]?.name ?? \"\");\n setOp(\"eq\");\n setValue(\"\");\n };\n\n const submit = () => {\n if (!column) return;\n const filter: DbAdminFilter = { column, op };\n if (opNeedsValue(op)) filter.value = value;\n onAdd(filter);\n reset();\n setOpen(false);\n };\n\n return (\n <Popover\n open={open}\n onOpenChange={(o) => {\n setOpen(o);\n if (!o) reset();\n }}\n >\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n className=\"inline-flex items-center gap-1 rounded-md border border-dashed border-border px-2 py-1 text-xs text-muted-foreground hover:border-border hover:text-foreground\"\n >\n <IconFilter className=\"h-3.5 w-3.5\" />\n Add filter\n </button>\n </PopoverTrigger>\n <PopoverContent align=\"start\" className=\"w-auto min-w-[20rem] p-2\">\n <div className=\"flex items-center gap-1.5\">\n <select\n value={column}\n onChange={(e) => setColumn(e.target.value)}\n className={cn(selectCls, \"max-w-[10rem] flex-1\")}\n >\n {columns.map((c) => (\n <option key={c.name} value={c.name}>\n {c.name}\n </option>\n ))}\n </select>\n <select\n value={op}\n onChange={(e) => setOp(e.target.value as DbAdminFilterOp)}\n className={selectCls}\n >\n {OPS.map((o) => (\n <option key={o.value} value={o.value}>\n {o.label}\n </option>\n ))}\n </select>\n {opNeedsValue(op) && (\n <input\n autoFocus\n value={value}\n onChange={(e) => setValue(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n submit();\n }\n }}\n placeholder={op === \"in\" ? \"a, b, c\" : \"value\"}\n className={cn(inputCls, \"flex-1\")}\n />\n )}\n </div>\n <div className=\"mt-2 flex justify-end\">\n <button\n type=\"button\"\n onClick={submit}\n className=\"inline-flex items-center gap-1 rounded-md bg-primary px-2.5 py-1 text-xs font-medium text-primary-foreground hover:bg-primary/90\"\n >\n <IconPlus className=\"h-3.5 w-3.5\" />\n Apply filter\n </button>\n </div>\n </PopoverContent>\n </Popover>\n );\n}\n"]}
@@ -0,0 +1,6 @@
1
+ export interface ResultsGridProps {
2
+ columns: string[];
3
+ rows: Record<string, unknown>[];
4
+ }
5
+ export declare function ResultsGrid({ columns, rows }: ResultsGridProps): import("react/jsx-runtime").JSX.Element;
6
+ //# sourceMappingURL=ResultsGrid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ResultsGrid.d.ts","sourceRoot":"","sources":["../../../src/client/db-admin/ResultsGrid.tsx"],"names":[],"mappings":"AAWA,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;CACjC;AAqBD,wBAAgB,WAAW,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,gBAAgB,2CA6E9D"}
@@ -0,0 +1,41 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Read-only results table for the SQL editor. Renders a clean, dense grid with a
4
+ * sticky header, zebra striping, dim NULL tokens, stringified objects, and
5
+ * horizontal scrolling. DDL/DML statements that return no columns render a
6
+ * "Query OK" placeholder instead.
7
+ *
8
+ * Results are LIMIT-capped upstream, so no virtualization is needed.
9
+ */
10
+ import { IconCircleCheck } from "@tabler/icons-react";
11
+ import { cn } from "../utils.js";
12
+ function renderCell(value) {
13
+ if (value === null || value === undefined) {
14
+ return _jsx("span", { className: "italic text-muted-foreground/60", children: "NULL" });
15
+ }
16
+ if (typeof value === "object") {
17
+ let str;
18
+ try {
19
+ str = JSON.stringify(value);
20
+ }
21
+ catch {
22
+ str = String(value);
23
+ }
24
+ return _jsx("span", { className: "text-foreground/80", children: str });
25
+ }
26
+ if (typeof value === "boolean") {
27
+ return _jsx("span", { className: "text-foreground/80", children: String(value) });
28
+ }
29
+ return _jsx("span", { children: String(value) });
30
+ }
31
+ export function ResultsGrid({ columns, rows }) {
32
+ if (columns.length === 0) {
33
+ return (_jsxs("div", { className: "flex h-full flex-col items-center justify-center gap-2 py-12 text-muted-foreground", children: [_jsx(IconCircleCheck, { size: 28, className: "text-green-500" }), _jsx("span", { className: "text-sm", children: "Query OK" })] }));
34
+ }
35
+ return (_jsx("div", { className: "h-full overflow-auto", children: _jsxs("table", { className: "w-full border-collapse text-left font-mono text-xs", children: [_jsx("thead", { className: "sticky top-0 z-10", children: _jsxs("tr", { children: [_jsx("th", { className: "sticky left-0 z-20 w-px border-b border-r border-border bg-muted px-3 py-1.5 text-right font-sans text-[11px] font-medium text-muted-foreground select-none", children: "#" }), columns.map((col, i) => (_jsx("th", { className: "whitespace-nowrap border-b border-r border-border bg-muted px-3 py-1.5 font-sans text-[11px] font-semibold text-foreground", children: col }, `${col}-${i}`)))] }) }), _jsx("tbody", { children: rows.length === 0 ? (_jsx("tr", { children: _jsx("td", { colSpan: columns.length + 1, className: "px-3 py-8 text-center font-sans text-sm text-muted-foreground", children: "No rows returned" }) })) : (rows.map((row, rowIndex) => (_jsxs("tr", { className: cn("group", rowIndex % 2 === 1 ? "bg-muted/30" : "bg-background"), children: [_jsx("td", { className: cn("sticky left-0 z-10 w-px border-b border-r border-border px-3 py-1 text-right font-sans text-[11px] text-muted-foreground select-none", rowIndex % 2 === 1 ? "bg-muted/60" : "bg-background"), children: rowIndex + 1 }), columns.map((col, colIndex) => (_jsx("td", { className: "max-w-md truncate whitespace-nowrap border-b border-r border-border px-3 py-1 align-top text-foreground", title: row[col] === null || row[col] === undefined
36
+ ? "NULL"
37
+ : typeof row[col] === "object"
38
+ ? JSON.stringify(row[col])
39
+ : String(row[col]), children: renderCell(row[col]) }, `${col}-${colIndex}`)))] }, rowIndex)))) })] }) }));
40
+ }
41
+ //# sourceMappingURL=ResultsGrid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ResultsGrid.js","sourceRoot":"","sources":["../../../src/client/db-admin/ResultsGrid.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * Read-only results table for the SQL editor. Renders a clean, dense grid with a\n * sticky header, zebra striping, dim NULL tokens, stringified objects, and\n * horizontal scrolling. DDL/DML statements that return no columns render a\n * \"Query OK\" placeholder instead.\n *\n * Results are LIMIT-capped upstream, so no virtualization is needed.\n */\nimport { IconCircleCheck } from \"@tabler/icons-react\";\nimport { cn } from \"../utils.js\";\n\nexport interface ResultsGridProps {\n columns: string[];\n rows: Record<string, unknown>[];\n}\n\nfunction renderCell(value: unknown): React.ReactNode {\n if (value === null || value === undefined) {\n return <span className=\"italic text-muted-foreground/60\">NULL</span>;\n }\n if (typeof value === \"object\") {\n let str: string;\n try {\n str = JSON.stringify(value);\n } catch {\n str = String(value);\n }\n return <span className=\"text-foreground/80\">{str}</span>;\n }\n if (typeof value === \"boolean\") {\n return <span className=\"text-foreground/80\">{String(value)}</span>;\n }\n return <span>{String(value)}</span>;\n}\n\nexport function ResultsGrid({ columns, rows }: ResultsGridProps) {\n if (columns.length === 0) {\n return (\n <div className=\"flex h-full flex-col items-center justify-center gap-2 py-12 text-muted-foreground\">\n <IconCircleCheck size={28} className=\"text-green-500\" />\n <span className=\"text-sm\">Query OK</span>\n </div>\n );\n }\n\n return (\n <div className=\"h-full overflow-auto\">\n <table className=\"w-full border-collapse text-left font-mono text-xs\">\n <thead className=\"sticky top-0 z-10\">\n <tr>\n <th className=\"sticky left-0 z-20 w-px border-b border-r border-border bg-muted px-3 py-1.5 text-right font-sans text-[11px] font-medium text-muted-foreground select-none\">\n #\n </th>\n {columns.map((col, i) => (\n <th\n key={`${col}-${i}`}\n className=\"whitespace-nowrap border-b border-r border-border bg-muted px-3 py-1.5 font-sans text-[11px] font-semibold text-foreground\"\n >\n {col}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {rows.length === 0 ? (\n <tr>\n <td\n colSpan={columns.length + 1}\n className=\"px-3 py-8 text-center font-sans text-sm text-muted-foreground\"\n >\n No rows returned\n </td>\n </tr>\n ) : (\n rows.map((row, rowIndex) => (\n <tr\n key={rowIndex}\n className={cn(\n \"group\",\n rowIndex % 2 === 1 ? \"bg-muted/30\" : \"bg-background\",\n )}\n >\n <td\n className={cn(\n \"sticky left-0 z-10 w-px border-b border-r border-border px-3 py-1 text-right font-sans text-[11px] text-muted-foreground select-none\",\n rowIndex % 2 === 1 ? \"bg-muted/60\" : \"bg-background\",\n )}\n >\n {rowIndex + 1}\n </td>\n {columns.map((col, colIndex) => (\n <td\n key={`${col}-${colIndex}`}\n className=\"max-w-md truncate whitespace-nowrap border-b border-r border-border px-3 py-1 align-top text-foreground\"\n title={\n row[col] === null || row[col] === undefined\n ? \"NULL\"\n : typeof row[col] === \"object\"\n ? JSON.stringify(row[col])\n : String(row[col])\n }\n >\n {renderCell(row[col])}\n </td>\n ))}\n </tr>\n ))\n )}\n </tbody>\n </table>\n </div>\n );\n}\n"]}
@@ -0,0 +1,18 @@
1
+ import type { DbAdminTableSchema } from "../../db-admin/types.js";
2
+ export type RowSidePanelMode = "insert" | "edit";
3
+ export interface RowSidePanelProps {
4
+ schema: DbAdminTableSchema;
5
+ mode: RowSidePanelMode;
6
+ /** For edit mode: the original row values. */
7
+ row?: Record<string, unknown>;
8
+ /** For edit mode: staged overrides already in the changeset. */
9
+ staged?: Record<string, unknown>;
10
+ onClose: () => void;
11
+ /**
12
+ * Persist into the changeset. For insert mode `values` is the full new-row
13
+ * object; for edit mode it is only the changed columns.
14
+ */
15
+ onSave: (values: Record<string, unknown>) => void;
16
+ }
17
+ export declare function RowSidePanel({ schema, mode, row, staged, onClose, onSave, }: RowSidePanelProps): any;
18
+ //# sourceMappingURL=RowSidePanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RowSidePanel.d.ts","sourceRoot":"","sources":["../../../src/client/db-admin/RowSidePanel.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAEV,kBAAkB,EACnB,MAAM,yBAAyB,CAAC;AAEjC,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEjD,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,kBAAkB,CAAC;IAC3B,IAAI,EAAE,gBAAgB,CAAC;IACvB,8CAA8C;IAC9C,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB;;;OAGG;IACH,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;CACnD;AA2BD,wBAAgB,YAAY,CAAC,EAC3B,MAAM,EACN,IAAI,EACJ,GAAG,EACH,MAAM,EACN,OAAO,EACP,MAAM,GACP,EAAE,iBAAiB,OA4InB"}
@@ -0,0 +1,104 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ import { createPortal } from "react-dom";
4
+ import { IconKey, IconX, IconRefresh, IconCircleX } from "@tabler/icons-react";
5
+ import { cn } from "../utils.js";
6
+ import { inferEditorKind, inferEnumValues, valueToEditString, parseEditValue, ParseError, } from "./cell-format.js";
7
+ function initialFieldState(col, kind, value, isInsert) {
8
+ const isNull = value === null || value === undefined;
9
+ return {
10
+ text: isNull ? "" : valueToEditString(value, kind),
11
+ isNull: isInsert ? false : isNull,
12
+ touched: false,
13
+ };
14
+ }
15
+ export function RowSidePanel({ schema, mode, row, staged, onClose, onSave, }) {
16
+ const isInsert = mode === "insert";
17
+ const [fields, setFields] = useState(() => {
18
+ const init = {};
19
+ for (const col of schema.columns) {
20
+ const kind = inferEditorKind(col);
21
+ const original = row?.[col.name];
22
+ const value = staged && Object.prototype.hasOwnProperty.call(staged, col.name)
23
+ ? staged[col.name]
24
+ : original;
25
+ init[col.name] = initialFieldState(col, kind, value, isInsert);
26
+ }
27
+ return init;
28
+ });
29
+ // Close on Escape.
30
+ useEffect(() => {
31
+ const onKey = (e) => {
32
+ if (e.key === "Escape")
33
+ onClose();
34
+ };
35
+ window.addEventListener("keydown", onKey);
36
+ return () => window.removeEventListener("keydown", onKey);
37
+ }, [onClose]);
38
+ const update = (name, patch) => setFields((prev) => ({
39
+ ...prev,
40
+ [name]: { ...prev[name], ...patch, touched: true },
41
+ }));
42
+ const save = () => {
43
+ const out = {};
44
+ let hadError = false;
45
+ const nextFields = { ...fields };
46
+ for (const col of schema.columns) {
47
+ const kind = inferEditorKind(col);
48
+ const fs = fields[col.name];
49
+ const isGenerated = col.pk && col.autoIncrement;
50
+ if (fs.isNull) {
51
+ // Explicit NULL.
52
+ if (!isInsert || fs.touched)
53
+ out[col.name] = null;
54
+ continue;
55
+ }
56
+ // Insert: blank, untouched, auto/pk/default columns are left out entirely.
57
+ if (isInsert && fs.text === "" && !fs.touched) {
58
+ continue;
59
+ }
60
+ if (isInsert && fs.text === "" && (isGenerated || col.defaultValue)) {
61
+ continue;
62
+ }
63
+ // Edit: only include columns the user touched.
64
+ if (!isInsert && !fs.touched)
65
+ continue;
66
+ try {
67
+ const parsed = parseEditValue(fs.text, kind, {
68
+ allowEmptyString: kind === "text",
69
+ });
70
+ out[col.name] = parsed;
71
+ }
72
+ catch (err) {
73
+ hadError = true;
74
+ nextFields[col.name] = {
75
+ ...fs,
76
+ error: err instanceof ParseError ? err.message : String(err),
77
+ };
78
+ }
79
+ }
80
+ if (hadError) {
81
+ setFields(nextFields);
82
+ return;
83
+ }
84
+ onSave(out);
85
+ onClose();
86
+ };
87
+ const panel = (_jsxs("div", { className: "fixed inset-0 z-[400] flex justify-end", children: [_jsx("div", { className: "absolute inset-0 bg-black/30 backdrop-blur-[1px]", onClick: onClose }), _jsxs("div", { className: "relative flex h-full w-full max-w-md flex-col border-l border-border bg-card shadow-xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-border px-4 py-3", children: [_jsxs("div", { children: [_jsx("h2", { className: "text-sm font-semibold text-foreground", children: isInsert ? "Insert row" : "Edit row" }), _jsx("p", { className: "text-xs text-muted-foreground", children: schema.name })] }), _jsx("button", { type: "button", onClick: onClose, className: "rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground", "aria-label": "Close", children: _jsx(IconX, { className: "h-4 w-4" }) })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-4 py-3", children: _jsx("div", { className: "flex flex-col gap-4", children: schema.columns.map((col) => (_jsx(RowField, { column: col, state: fields[col.name], isInsert: isInsert, onChange: (patch) => update(col.name, patch) }, col.name))) }) }), _jsxs("div", { className: "flex items-center justify-end gap-2 border-t border-border px-4 py-3", children: [_jsx("button", { type: "button", onClick: onClose, className: "rounded-md px-3 py-1.5 text-xs text-muted-foreground hover:bg-muted hover:text-foreground", children: "Cancel" }), _jsx("button", { type: "button", onClick: save, className: "rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90", children: isInsert ? "Stage insert" : "Stage changes" })] })] })] }));
88
+ if (typeof document === "undefined")
89
+ return null;
90
+ return createPortal(panel, document.body);
91
+ }
92
+ function RowField({ column, state, isInsert, onChange, }) {
93
+ const kind = inferEditorKind(column);
94
+ const enumValues = inferEnumValues(column);
95
+ const isGenerated = column.pk && column.autoIncrement;
96
+ return (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsxs("div", { className: "flex items-center gap-1.5", children: [column.pk && _jsx(IconKey, { className: "h-3 w-3 text-amber-500" }), _jsx("label", { className: "text-xs font-medium text-foreground", children: column.name }), _jsx("span", { className: "rounded bg-muted px-1 py-0.5 font-mono text-[10px] text-muted-foreground", children: column.type }), !column.nullable && (_jsx("span", { className: "text-[10px] text-destructive", children: "required" })), column.nullable && (_jsx("button", { type: "button", onClick: () => onChange({ isNull: !state.isNull }), className: cn("ml-auto rounded px-1.5 py-0.5 text-[10px]", state.isNull
97
+ ? "bg-muted text-foreground ring-1 ring-inset ring-border"
98
+ : "text-muted-foreground hover:text-foreground"), children: state.isNull ? "NULL" : "set null" }))] }), state.isNull ? (_jsxs("div", { className: "flex items-center gap-2 rounded-md border border-dashed border-border bg-muted/30 px-2 py-1.5 text-xs italic text-muted-foreground/70", children: ["NULL", _jsx("button", { type: "button", onClick: () => onChange({ isNull: false }), className: "ml-auto text-muted-foreground hover:text-foreground", "aria-label": "Clear NULL", children: _jsx(IconCircleX, { className: "h-3.5 w-3.5" }) })] })) : enumValues ? (_jsxs("select", { value: state.text, onChange: (e) => onChange({ text: e.target.value, error: null }), className: "h-8 rounded-md border border-border bg-background px-2 text-xs outline-none focus:ring-1 focus:ring-ring", children: [_jsx("option", { value: "", children: "\u2014" }), enumValues.map((v) => (_jsx("option", { value: v, children: v }, v)))] })) : kind === "boolean" ? (_jsxs("select", { value: state.text, onChange: (e) => onChange({ text: e.target.value, error: null }), className: "h-8 rounded-md border border-border bg-background px-2 text-xs outline-none focus:ring-1 focus:ring-ring", children: [_jsx("option", { value: "", children: "\u2014" }), _jsx("option", { value: "true", children: "true" }), _jsx("option", { value: "false", children: "false" })] })) : kind === "json" ? (_jsx("textarea", { value: state.text, onChange: (e) => onChange({ text: e.target.value, error: null }), rows: 4, spellCheck: false, placeholder: isGenerated ? "(auto-generated)" : "{ }", className: cn("resize-y rounded-md border border-border bg-background p-2 font-mono text-xs outline-none focus:ring-1 focus:ring-ring", state.error && "border-destructive") })) : (_jsx("input", { type: "text", inputMode: kind === "number" ? "decimal" : undefined, value: state.text, onChange: (e) => onChange({ text: e.target.value, error: null }), placeholder: isGenerated
99
+ ? "(auto-generated)"
100
+ : column.defaultValue
101
+ ? `default: ${column.defaultValue}`
102
+ : "", className: cn("h-8 rounded-md border border-border bg-background px-2 font-mono text-xs outline-none focus:ring-1 focus:ring-ring", state.error && "border-destructive") })), column.defaultValue && !isGenerated && !state.isNull && (_jsxs("span", { className: "text-[10px] text-muted-foreground/70", children: ["default: ", column.defaultValue] })), isGenerated && (_jsxs("span", { className: "flex items-center gap-1 text-[10px] text-muted-foreground/70", children: [_jsx(IconRefresh, { className: "h-3 w-3" }), "auto-generated \u2014 leave blank"] })), state.error && (_jsx("span", { className: "text-[10px] text-destructive", children: state.error }))] }));
103
+ }
104
+ //# sourceMappingURL=RowSidePanel.js.map