@sqlrooms/cells 0.29.0-rc.1 → 0.29.0-rc.2

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 (158) hide show
  1. package/dist/cellsSlice.d.ts.map +1 -1
  2. package/dist/cellsSlice.js +191 -73
  3. package/dist/cellsSlice.js.map +1 -1
  4. package/dist/components/AggregationSelector.d.ts +8 -0
  5. package/dist/components/AggregationSelector.d.ts.map +1 -0
  6. package/dist/components/AggregationSelector.js +13 -0
  7. package/dist/components/AggregationSelector.js.map +1 -0
  8. package/dist/components/CellSourceSelector.d.ts +11 -0
  9. package/dist/components/CellSourceSelector.d.ts.map +1 -0
  10. package/dist/components/CellSourceSelector.js +35 -0
  11. package/dist/components/CellSourceSelector.js.map +1 -0
  12. package/dist/components/ChartTypeSelector.d.ts +8 -0
  13. package/dist/components/ChartTypeSelector.d.ts.map +1 -0
  14. package/dist/components/ChartTypeSelector.js +13 -0
  15. package/dist/components/ChartTypeSelector.js.map +1 -0
  16. package/dist/components/CollapsibleSectionButton.d.ts +10 -0
  17. package/dist/components/CollapsibleSectionButton.d.ts.map +1 -0
  18. package/dist/components/CollapsibleSectionButton.js +10 -0
  19. package/dist/components/CollapsibleSectionButton.js.map +1 -0
  20. package/dist/components/ColorSelector.d.ts +24 -0
  21. package/dist/components/ColorSelector.d.ts.map +1 -0
  22. package/dist/components/ColorSelector.js +17 -0
  23. package/dist/components/ColorSelector.js.map +1 -0
  24. package/dist/components/FieldSelector.d.ts +11 -0
  25. package/dist/components/FieldSelector.d.ts.map +1 -0
  26. package/dist/components/FieldSelector.js +11 -0
  27. package/dist/components/FieldSelector.js.map +1 -0
  28. package/dist/components/FieldTypeIcon.d.ts +8 -0
  29. package/dist/components/FieldTypeIcon.d.ts.map +1 -0
  30. package/dist/components/FieldTypeIcon.js +27 -0
  31. package/dist/components/FieldTypeIcon.js.map +1 -0
  32. package/dist/components/InputCellContent.d.ts.map +1 -1
  33. package/dist/components/InputCellContent.js +3 -3
  34. package/dist/components/InputCellContent.js.map +1 -1
  35. package/dist/components/SelectionListener.d.ts +15 -0
  36. package/dist/components/SelectionListener.d.ts.map +1 -0
  37. package/dist/components/SelectionListener.js +74 -0
  38. package/dist/components/SelectionListener.js.map +1 -0
  39. package/dist/components/SheetsTabBar.d.ts.map +1 -1
  40. package/dist/components/SheetsTabBar.js +2 -1
  41. package/dist/components/SheetsTabBar.js.map +1 -1
  42. package/dist/components/SqlCellBody.d.ts +6 -0
  43. package/dist/components/SqlCellBody.d.ts.map +1 -0
  44. package/dist/components/SqlCellBody.js +12 -0
  45. package/dist/components/SqlCellBody.js.map +1 -0
  46. package/dist/components/SqlCellConnectionSelector.d.ts +9 -0
  47. package/dist/components/SqlCellConnectionSelector.d.ts.map +1 -0
  48. package/dist/components/SqlCellConnectionSelector.js +21 -0
  49. package/dist/components/SqlCellConnectionSelector.js.map +1 -0
  50. package/dist/components/SqlCellContent.d.ts.map +1 -1
  51. package/dist/components/SqlCellContent.js +29 -154
  52. package/dist/components/SqlCellContent.js.map +1 -1
  53. package/dist/components/SqlCellDependentsMenu.d.ts +11 -0
  54. package/dist/components/SqlCellDependentsMenu.d.ts.map +1 -0
  55. package/dist/components/SqlCellDependentsMenu.js +62 -0
  56. package/dist/components/SqlCellDependentsMenu.js.map +1 -0
  57. package/dist/components/SqlCellEditor.d.ts +14 -0
  58. package/dist/components/SqlCellEditor.d.ts.map +1 -0
  59. package/dist/components/SqlCellEditor.js +17 -0
  60. package/dist/components/SqlCellEditor.js.map +1 -0
  61. package/dist/components/SqlCellResultNameEditor.d.ts +8 -0
  62. package/dist/components/SqlCellResultNameEditor.d.ts.map +1 -0
  63. package/dist/components/SqlCellResultNameEditor.js +38 -0
  64. package/dist/components/SqlCellResultNameEditor.js.map +1 -0
  65. package/dist/components/SqlCellResults.d.ts +17 -0
  66. package/dist/components/SqlCellResults.d.ts.map +1 -0
  67. package/dist/components/SqlCellResults.js +45 -0
  68. package/dist/components/SqlCellResults.js.map +1 -0
  69. package/dist/components/SqlCellRunButton.d.ts +5 -6
  70. package/dist/components/SqlCellRunButton.d.ts.map +1 -1
  71. package/dist/components/SqlCellRunButton.js +11 -7
  72. package/dist/components/SqlCellRunButton.js.map +1 -1
  73. package/dist/components/SqlQueryPreview.d.ts +9 -0
  74. package/dist/components/SqlQueryPreview.d.ts.map +1 -0
  75. package/dist/components/SqlQueryPreview.js +14 -0
  76. package/dist/components/SqlQueryPreview.js.map +1 -0
  77. package/dist/components/TextCellContent.d.ts.map +1 -1
  78. package/dist/components/TextCellContent.js +24 -7
  79. package/dist/components/TextCellContent.js.map +1 -1
  80. package/dist/components/VegaCellContent.d.ts +0 -1
  81. package/dist/components/VegaCellContent.d.ts.map +1 -1
  82. package/dist/components/VegaCellContent.js +61 -47
  83. package/dist/components/VegaCellContent.js.map +1 -1
  84. package/dist/components/VegaCellHeader.d.ts +11 -0
  85. package/dist/components/VegaCellHeader.d.ts.map +1 -0
  86. package/dist/components/VegaCellHeader.js +31 -0
  87. package/dist/components/VegaCellHeader.js.map +1 -0
  88. package/dist/components/VegaConfigPanel.d.ts +5 -0
  89. package/dist/components/VegaConfigPanel.d.ts.map +1 -1
  90. package/dist/components/VegaConfigPanel.js +53 -63
  91. package/dist/components/VegaConfigPanel.js.map +1 -1
  92. package/dist/crdt.d.ts +61 -0
  93. package/dist/crdt.d.ts.map +1 -0
  94. package/dist/crdt.js +82 -0
  95. package/dist/crdt.js.map +1 -0
  96. package/dist/dagSlice.d.ts +3 -0
  97. package/dist/dagSlice.d.ts.map +1 -0
  98. package/dist/dagSlice.js +51 -0
  99. package/dist/dagSlice.js.map +1 -0
  100. package/dist/dagUtils.d.ts +2 -1
  101. package/dist/dagUtils.d.ts.map +1 -1
  102. package/dist/dagUtils.js +35 -11
  103. package/dist/dagUtils.js.map +1 -1
  104. package/dist/defaultCellRegistry.d.ts.map +1 -1
  105. package/dist/defaultCellRegistry.js +63 -24
  106. package/dist/defaultCellRegistry.js.map +1 -1
  107. package/dist/execution.d.ts.map +1 -1
  108. package/dist/execution.js +2 -1
  109. package/dist/execution.js.map +1 -1
  110. package/dist/helpers.d.ts +14 -2
  111. package/dist/helpers.d.ts.map +1 -1
  112. package/dist/helpers.js +56 -2
  113. package/dist/helpers.js.map +1 -1
  114. package/dist/hooks/useVegaCellDataSource.d.ts +18 -0
  115. package/dist/hooks/useVegaCellDataSource.d.ts.map +1 -0
  116. package/dist/hooks/useVegaCellDataSource.js +40 -0
  117. package/dist/hooks/useVegaCellDataSource.js.map +1 -0
  118. package/dist/hooks/useVegaCellQuery.d.ts +16 -0
  119. package/dist/hooks/useVegaCellQuery.d.ts.map +1 -0
  120. package/dist/hooks/useVegaCellQuery.js +25 -0
  121. package/dist/hooks/useVegaCellQuery.js.map +1 -0
  122. package/dist/index.d.ts +3 -2
  123. package/dist/index.d.ts.map +1 -1
  124. package/dist/index.js +3 -2
  125. package/dist/index.js.map +1 -1
  126. package/dist/sqlHelpers.d.ts +3 -3
  127. package/dist/sqlHelpers.d.ts.map +1 -1
  128. package/dist/sqlHelpers.js +9 -5
  129. package/dist/sqlHelpers.js.map +1 -1
  130. package/dist/typeGuards.d.ts +2 -0
  131. package/dist/typeGuards.d.ts.map +1 -0
  132. package/dist/typeGuards.js +4 -0
  133. package/dist/typeGuards.js.map +1 -0
  134. package/dist/types.d.ts +97 -26
  135. package/dist/types.d.ts.map +1 -1
  136. package/dist/types.js +17 -2
  137. package/dist/types.js.map +1 -1
  138. package/dist/useValidateResultName.d.ts +34 -0
  139. package/dist/useValidateResultName.d.ts.map +1 -0
  140. package/dist/useValidateResultName.js +111 -0
  141. package/dist/useValidateResultName.js.map +1 -0
  142. package/dist/utils.d.ts +8 -0
  143. package/dist/utils.d.ts.map +1 -1
  144. package/dist/utils.js +16 -0
  145. package/dist/utils.js.map +1 -1
  146. package/dist/validation.d.ts +16 -0
  147. package/dist/validation.d.ts.map +1 -0
  148. package/dist/validation.js +40 -0
  149. package/dist/validation.js.map +1 -0
  150. package/dist/vegaSelectionUtils.d.ts +14 -0
  151. package/dist/vegaSelectionUtils.d.ts.map +1 -0
  152. package/dist/vegaSelectionUtils.js +97 -0
  153. package/dist/vegaSelectionUtils.js.map +1 -0
  154. package/dist/vegaSpecBuilder.d.ts +34 -0
  155. package/dist/vegaSpecBuilder.d.ts.map +1 -0
  156. package/dist/vegaSpecBuilder.js +90 -0
  157. package/dist/vegaSpecBuilder.js.map +1 -0
  158. package/package.json +12 -13
@@ -0,0 +1,27 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Calendar, Hash, Type, HelpCircle, ToggleLeft, Binary, Braces, Box, Map, } from 'lucide-react';
3
+ import { cn } from '@sqlrooms/ui';
4
+ export const FieldTypeIcon = ({ typeCategory, className, }) => {
5
+ const iconClass = cn('h-3.5 w-3.5 shrink-0 text-gray-400', className);
6
+ switch (typeCategory) {
7
+ case 'number':
8
+ return _jsx(Hash, { className: iconClass });
9
+ case 'string':
10
+ return _jsx(Type, { className: iconClass });
11
+ case 'datetime':
12
+ return _jsx(Calendar, { className: iconClass });
13
+ case 'boolean':
14
+ return _jsx(ToggleLeft, { className: iconClass });
15
+ case 'binary':
16
+ return _jsx(Binary, { className: iconClass });
17
+ case 'json':
18
+ return _jsx(Braces, { className: iconClass });
19
+ case 'struct':
20
+ return _jsx(Box, { className: iconClass });
21
+ case 'geometry':
22
+ return _jsx(Map, { className: iconClass });
23
+ default:
24
+ return _jsx(HelpCircle, { className: iconClass });
25
+ }
26
+ };
27
+ //# sourceMappingURL=FieldTypeIcon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FieldTypeIcon.js","sourceRoot":"","sources":["../../src/components/FieldTypeIcon.tsx"],"names":[],"mappings":";AAEA,OAAO,EACL,QAAQ,EACR,IAAI,EACJ,IAAI,EACJ,UAAU,EACV,UAAU,EACV,MAAM,EACN,MAAM,EACN,GAAG,EACH,GAAG,GACJ,MAAM,cAAc,CAAC;AACtB,OAAO,EAAC,EAAE,EAAC,MAAM,cAAc,CAAC;AAOhC,MAAM,CAAC,MAAM,aAAa,GAAiC,CAAC,EAC1D,YAAY,EACZ,SAAS,GACV,EAAE,EAAE;IACH,MAAM,SAAS,GAAG,EAAE,CAAC,oCAAoC,EAAE,SAAS,CAAC,CAAC;IAEtE,QAAQ,YAAY,EAAE,CAAC;QACrB,KAAK,QAAQ;YACX,OAAO,KAAC,IAAI,IAAC,SAAS,EAAE,SAAS,GAAI,CAAC;QACxC,KAAK,QAAQ;YACX,OAAO,KAAC,IAAI,IAAC,SAAS,EAAE,SAAS,GAAI,CAAC;QACxC,KAAK,UAAU;YACb,OAAO,KAAC,QAAQ,IAAC,SAAS,EAAE,SAAS,GAAI,CAAC;QAC5C,KAAK,SAAS;YACZ,OAAO,KAAC,UAAU,IAAC,SAAS,EAAE,SAAS,GAAI,CAAC;QAC9C,KAAK,QAAQ;YACX,OAAO,KAAC,MAAM,IAAC,SAAS,EAAE,SAAS,GAAI,CAAC;QAC1C,KAAK,MAAM;YACT,OAAO,KAAC,MAAM,IAAC,SAAS,EAAE,SAAS,GAAI,CAAC;QAC1C,KAAK,QAAQ;YACX,OAAO,KAAC,GAAG,IAAC,SAAS,EAAE,SAAS,GAAI,CAAC;QACvC,KAAK,UAAU;YACb,OAAO,KAAC,GAAG,IAAC,SAAS,EAAE,SAAS,GAAI,CAAC;QACvC;YACE,OAAO,KAAC,UAAU,IAAC,SAAS,EAAE,SAAS,GAAI,CAAC;IAChD,CAAC;AACH,CAAC,CAAC","sourcesContent":["import React from 'react';\nimport {type ColumnTypeCategory} from '@sqlrooms/duckdb';\nimport {\n Calendar,\n Hash,\n Type,\n HelpCircle,\n ToggleLeft,\n Binary,\n Braces,\n Box,\n Map,\n} from 'lucide-react';\nimport {cn} from '@sqlrooms/ui';\n\nexport interface FieldTypeIconProps {\n typeCategory: ColumnTypeCategory | undefined;\n className?: string;\n}\n\nexport const FieldTypeIcon: React.FC<FieldTypeIconProps> = ({\n typeCategory,\n className,\n}) => {\n const iconClass = cn('h-3.5 w-3.5 shrink-0 text-gray-400', className);\n\n switch (typeCategory) {\n case 'number':\n return <Hash className={iconClass} />;\n case 'string':\n return <Type className={iconClass} />;\n case 'datetime':\n return <Calendar className={iconClass} />;\n case 'boolean':\n return <ToggleLeft className={iconClass} />;\n case 'binary':\n return <Binary className={iconClass} />;\n case 'json':\n return <Braces className={iconClass} />;\n case 'struct':\n return <Box className={iconClass} />;\n case 'geometry':\n return <Map className={iconClass} />;\n default:\n return <HelpCircle className={iconClass} />;\n }\n};\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"InputCellContent.d.ts","sourceRoot":"","sources":["../../src/components/InputCellContent.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAoB,MAAM,OAAO,CAAC;AAYzC,OAAO,KAAK,EAAC,kBAAkB,EAAE,SAAS,EAAmB,MAAM,UAAU,CAAC;AAI9E,MAAM,MAAM,qBAAqB,GAAG;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,SAAS,CAAC;IAChB,eAAe,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,KAAK,CAAC,YAAY,CAAC;CACpE,CAAC;AAqDF,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CAwD5D,CAAC"}
1
+ {"version":3,"file":"InputCellContent.d.ts","sourceRoot":"","sources":["../../src/components/InputCellContent.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAoB,MAAM,OAAO,CAAC;AAEzC,OAAO,KAAK,EAAO,kBAAkB,EAAE,SAAS,EAAa,MAAM,UAAU,CAAC;AAG9E,MAAM,MAAM,qBAAqB,GAAG;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,SAAS,CAAC;IAChB,eAAe,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,KAAK,CAAC,YAAY,CAAC;CACpE,CAAC;AAqDF,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CAwD5D,CAAC"}
@@ -1,9 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { EditableText, Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Slider, } from '@sqlrooms/ui';
3
+ import { produce } from 'immer';
2
4
  import { useCallback } from 'react';
3
- import { EditableText, Input, Slider, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@sqlrooms/ui';
4
5
  import { useCellsStore } from '../hooks';
5
6
  import { InputConfigPanel } from './Input/ConfigPanel/InputConfigPanel';
6
- import { produce } from 'immer';
7
7
  const RenderInput = ({ input, updateInput }) => {
8
8
  switch (input.kind) {
9
9
  case 'text':
@@ -28,7 +28,7 @@ export const InputCellContent = ({ id, cell, renderContainer, }) => {
28
28
  }
29
29
  }), isValueChange ? { cascade: true } : undefined);
30
30
  }, [id, updateCell]);
31
- const header = (_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(EditableText, { value: input.varName, onChange: (varName) => updateInput({ varName }), className: "h-6 text-xs font-semibold shadow-none outline-none ring-0" }), _jsx("span", { className: "text-[10px] font-bold uppercase text-gray-400", children: "Input" })] }), _jsx(InputConfigPanel, { input: input, updateInput: updateInput, onRemove: () => removeCell(id) })] }));
31
+ const header = (_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(EditableText, { value: input.varName, onChange: (varName) => updateInput({ varName }), className: "h-6 text-xs font-semibold shadow-none ring-0 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-1" }), _jsx("span", { className: "text-[10px] font-bold text-gray-400 uppercase", children: "Input" })] }), _jsx(InputConfigPanel, { input: input, updateInput: updateInput, onRemove: () => removeCell(id) })] }));
32
32
  const content = (_jsx("div", { className: "p-2", children: _jsx(RenderInput, { input: input, updateInput: updateInput }) }));
33
33
  return renderContainer({
34
34
  header,
@@ -1 +1 @@
1
- {"version":3,"file":"InputCellContent.js","sourceRoot":"","sources":["../../src/components/InputCellContent.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, {useCallback} from 'react';\nimport {\n EditableText,\n Input,\n Slider,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@sqlrooms/ui';\nimport {useCellsStore, type CellsStoreState} from '../hooks';\nimport type {CellContainerProps, InputCell, InputUnion, Cell} from '../types';\nimport {InputConfigPanel} from './Input/ConfigPanel/InputConfigPanel';\nimport {produce} from 'immer';\n\nexport type InputCellContentProps = {\n id: string;\n cell: InputCell;\n renderContainer: (props: CellContainerProps) => React.ReactElement;\n};\n\nconst RenderInput: React.FC<{\n input: InputUnion;\n updateInput: (patch: Partial<InputUnion>) => void;\n}> = ({input, updateInput}) => {\n switch (input.kind) {\n case 'text':\n return (\n <Input\n className=\"h-8 w-full rounded border px-2 py-1 text-sm\"\n value={input.value}\n onChange={(e) => updateInput({value: e.target.value})}\n />\n );\n case 'slider':\n return (\n <div className=\"flex h-8 items-center space-x-1\">\n <Slider\n min={input.min}\n max={input.max}\n step={input.step}\n value={[input.value as number]}\n onValueChange={(value) => updateInput({value: Number(value)})}\n />\n <span className=\"min-w-[3ch] text-right text-sm font-medium\">\n {input.value}\n </span>\n </div>\n );\n case 'dropdown':\n return (\n <Select\n value={input.value as string}\n onValueChange={(value) => updateInput({value})}\n >\n <SelectTrigger className=\"h-8 text-xs shadow-none\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent onCloseAutoFocus={(e) => e.preventDefault()}>\n {(input.options as string[]).map((option: string) => (\n <SelectItem className=\"text-xs\" key={option} value={option}>\n {option}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n );\n default:\n return null;\n }\n};\n\nexport const InputCellContent: React.FC<InputCellContentProps> = ({\n id,\n cell,\n renderContainer,\n}) => {\n const updateCell = useCellsStore((s: CellsStoreState) => s.cells.updateCell);\n const removeCell = useCellsStore((s: CellsStoreState) => s.cells.removeCell);\n const input = cell.data.input;\n\n const updateInput = useCallback(\n (patch: Partial<InputUnion>) => {\n const isValueChange = 'value' in patch;\n updateCell(\n id,\n (c: Cell) =>\n produce(c, (draft) => {\n if (draft.type === 'input') {\n draft.data.input = {...draft.data.input, ...patch} as any;\n }\n }),\n isValueChange ? {cascade: true} : undefined,\n );\n },\n [id, updateCell],\n );\n\n const header = (\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"flex items-center gap-2\">\n <EditableText\n value={input.varName}\n onChange={(varName) => updateInput({varName})}\n className=\"h-6 text-xs font-semibold shadow-none outline-none ring-0\"\n />\n <span className=\"text-[10px] font-bold uppercase text-gray-400\">\n Input\n </span>\n </div>\n <InputConfigPanel\n input={input}\n updateInput={updateInput}\n onRemove={() => removeCell(id)}\n />\n </div>\n );\n\n const content = (\n <div className=\"p-2\">\n <RenderInput input={input} updateInput={updateInput} />\n </div>\n );\n\n return renderContainer({\n header,\n content,\n });\n};\n"]}
1
+ {"version":3,"file":"InputCellContent.js","sourceRoot":"","sources":["../../src/components/InputCellContent.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n EditableText,\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Slider,\n} from '@sqlrooms/ui';\nimport {produce} from 'immer';\nimport React, {useCallback} from 'react';\nimport {useCellsStore, type CellsStoreState} from '../hooks';\nimport type {Cell, CellContainerProps, InputCell, InputUnion} from '../types';\nimport {InputConfigPanel} from './Input/ConfigPanel/InputConfigPanel';\n\nexport type InputCellContentProps = {\n id: string;\n cell: InputCell;\n renderContainer: (props: CellContainerProps) => React.ReactElement;\n};\n\nconst RenderInput: React.FC<{\n input: InputUnion;\n updateInput: (patch: Partial<InputUnion>) => void;\n}> = ({input, updateInput}) => {\n switch (input.kind) {\n case 'text':\n return (\n <Input\n className=\"h-8 w-full rounded border px-2 py-1 text-sm\"\n value={input.value}\n onChange={(e) => updateInput({value: e.target.value})}\n />\n );\n case 'slider':\n return (\n <div className=\"flex h-8 items-center space-x-1\">\n <Slider\n min={input.min}\n max={input.max}\n step={input.step}\n value={[input.value as number]}\n onValueChange={(value) => updateInput({value: Number(value)})}\n />\n <span className=\"min-w-[3ch] text-right text-sm font-medium\">\n {input.value}\n </span>\n </div>\n );\n case 'dropdown':\n return (\n <Select\n value={input.value as string}\n onValueChange={(value) => updateInput({value})}\n >\n <SelectTrigger className=\"h-8 text-xs shadow-none\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent onCloseAutoFocus={(e) => e.preventDefault()}>\n {(input.options as string[]).map((option: string) => (\n <SelectItem className=\"text-xs\" key={option} value={option}>\n {option}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n );\n default:\n return null;\n }\n};\n\nexport const InputCellContent: React.FC<InputCellContentProps> = ({\n id,\n cell,\n renderContainer,\n}) => {\n const updateCell = useCellsStore((s: CellsStoreState) => s.cells.updateCell);\n const removeCell = useCellsStore((s: CellsStoreState) => s.cells.removeCell);\n const input = cell.data.input;\n\n const updateInput = useCallback(\n (patch: Partial<InputUnion>) => {\n const isValueChange = 'value' in patch;\n updateCell(\n id,\n (c: Cell) =>\n produce(c, (draft) => {\n if (draft.type === 'input') {\n draft.data.input = {...draft.data.input, ...patch} as any;\n }\n }),\n isValueChange ? {cascade: true} : undefined,\n );\n },\n [id, updateCell],\n );\n\n const header = (\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"flex items-center gap-2\">\n <EditableText\n value={input.varName}\n onChange={(varName) => updateInput({varName})}\n className=\"h-6 text-xs font-semibold shadow-none ring-0 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-1\"\n />\n <span className=\"text-[10px] font-bold text-gray-400 uppercase\">\n Input\n </span>\n </div>\n <InputConfigPanel\n input={input}\n updateInput={updateInput}\n onRemove={() => removeCell(id)}\n />\n </div>\n );\n\n const content = (\n <div className=\"p-2\">\n <RenderInput input={input} updateInput={updateInput} />\n </div>\n );\n\n return renderContainer({\n header,\n content,\n });\n};\n"]}
@@ -0,0 +1,15 @@
1
+ import React from 'react';
2
+ import type { BrushFieldType } from '../types';
3
+ type SelectionListenerProps = {
4
+ cellId: string;
5
+ sqlId: string;
6
+ brushField: string;
7
+ brushFieldType?: BrushFieldType;
8
+ };
9
+ /**
10
+ * Attaches a debounced Vega signal listener for the brush param
11
+ * and reports selection changes to the cross-filter state.
12
+ */
13
+ export declare const SelectionListener: React.FC<SelectionListenerProps>;
14
+ export {};
15
+ //# sourceMappingURL=SelectionListener.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SelectionListener.d.ts","sourceRoot":"","sources":["../../src/components/SelectionListener.tsx"],"names":[],"mappings":"AACA,OAAO,KAA0B,MAAM,OAAO,CAAC;AAE/C,OAAO,KAAK,EAAC,cAAc,EAAuB,MAAM,UAAU,CAAC;AAGnE,KAAK,sBAAsB,GAAG;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC,sBAAsB,CA8E9D,CAAC"}
@@ -0,0 +1,74 @@
1
+ import { useVegaChartContext } from '@sqlrooms/vega';
2
+ import { useEffect, useRef } from 'react';
3
+ import { useCellsStore } from '../hooks';
4
+ import { BRUSH_PARAM_NAME } from '../vegaSelectionUtils';
5
+ /**
6
+ * Attaches a debounced Vega signal listener for the brush param
7
+ * and reports selection changes to the cross-filter state.
8
+ */
9
+ export const SelectionListener = ({ cellId, sqlId, brushField, brushFieldType, }) => {
10
+ const { embed } = useVegaChartContext();
11
+ const setCrossFilterSelection = useCellsStore((s) => s.cells.setCrossFilterSelection);
12
+ const timerRef = useRef(null);
13
+ useEffect(() => {
14
+ if (!embed?.view) {
15
+ return;
16
+ }
17
+ const handler = (_name, value) => {
18
+ if (timerRef.current) {
19
+ clearTimeout(timerRef.current);
20
+ }
21
+ timerRef.current = setTimeout(() => {
22
+ if (!value || Object.keys(value).length === 0) {
23
+ setCrossFilterSelection(cellId, sqlId, null);
24
+ return;
25
+ }
26
+ const fieldValue = value.x ?? value[brushField];
27
+ if (Array.isArray(fieldValue) && fieldValue.length === 2) {
28
+ const selection = {
29
+ field: brushField,
30
+ fieldType: brushFieldType,
31
+ type: 'interval',
32
+ value: fieldValue,
33
+ };
34
+ setCrossFilterSelection(cellId, sqlId, selection);
35
+ }
36
+ else {
37
+ setCrossFilterSelection(cellId, sqlId, null);
38
+ }
39
+ }, 200);
40
+ };
41
+ try {
42
+ embed.view.addSignalListener(BRUSH_PARAM_NAME, handler);
43
+ }
44
+ catch (error) {
45
+ // Signal doesn't exist yet (view is still rendering a spec without the brush param).
46
+ // Will retry when embed reference changes after the new spec is compiled.
47
+ console.warn('Failed to add signal listener:', error);
48
+ return;
49
+ }
50
+ return () => {
51
+ if (timerRef.current) {
52
+ clearTimeout(timerRef.current);
53
+ }
54
+ try {
55
+ embed.view.removeSignalListener(BRUSH_PARAM_NAME, handler);
56
+ }
57
+ catch (error) {
58
+ // View may have been finalized
59
+ console.warn('Failed to remove signal listener:', error);
60
+ }
61
+ // Clear this chart's selection from crossFilterSelections
62
+ setCrossFilterSelection(cellId, sqlId, null);
63
+ };
64
+ }, [
65
+ embed,
66
+ cellId,
67
+ sqlId,
68
+ brushField,
69
+ brushFieldType,
70
+ setCrossFilterSelection,
71
+ ]);
72
+ return null;
73
+ };
74
+ //# sourceMappingURL=SelectionListener.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SelectionListener.js","sourceRoot":"","sources":["../../src/components/SelectionListener.tsx"],"names":[],"mappings":"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","sourcesContent":["import {SignalListenerHandler, useVegaChartContext} from '@sqlrooms/vega';\nimport React, {useEffect, useRef} from 'react';\nimport {useCellsStore} from '../hooks';\nimport type {BrushFieldType, CrossFilterSelection} from '../types';\nimport {BRUSH_PARAM_NAME} from '../vegaSelectionUtils';\n\ntype SelectionListenerProps = {\n cellId: string;\n sqlId: string;\n brushField: string;\n brushFieldType?: BrushFieldType;\n};\n\n/**\n * Attaches a debounced Vega signal listener for the brush param\n * and reports selection changes to the cross-filter state.\n */\nexport const SelectionListener: React.FC<SelectionListenerProps> = ({\n cellId,\n sqlId,\n brushField,\n brushFieldType,\n}) => {\n const {embed} = useVegaChartContext();\n const setCrossFilterSelection = useCellsStore(\n (s) => s.cells.setCrossFilterSelection,\n );\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n if (!embed?.view) {\n return;\n }\n\n const handler: SignalListenerHandler = (_name, value) => {\n if (timerRef.current) {\n clearTimeout(timerRef.current);\n }\n\n timerRef.current = setTimeout(() => {\n if (!value || Object.keys(value).length === 0) {\n setCrossFilterSelection(cellId, sqlId, null);\n return;\n }\n\n const fieldValue = value.x ?? value[brushField];\n\n if (Array.isArray(fieldValue) && fieldValue.length === 2) {\n const selection: CrossFilterSelection = {\n field: brushField,\n fieldType: brushFieldType,\n type: 'interval',\n value: fieldValue,\n };\n setCrossFilterSelection(cellId, sqlId, selection);\n } else {\n setCrossFilterSelection(cellId, sqlId, null);\n }\n }, 200);\n };\n\n try {\n embed.view.addSignalListener(BRUSH_PARAM_NAME, handler);\n } catch (error) {\n // Signal doesn't exist yet (view is still rendering a spec without the brush param).\n // Will retry when embed reference changes after the new spec is compiled.\n console.warn('Failed to add signal listener:', error);\n return;\n }\n\n return () => {\n if (timerRef.current) {\n clearTimeout(timerRef.current);\n }\n\n try {\n embed.view.removeSignalListener(BRUSH_PARAM_NAME, handler);\n } catch (error) {\n // View may have been finalized\n console.warn('Failed to remove signal listener:', error);\n }\n\n // Clear this chart's selection from crossFilterSelections\n setCrossFilterSelection(cellId, sqlId, null);\n };\n }, [\n embed,\n cellId,\n sqlId,\n brushField,\n brushFieldType,\n setCrossFilterSelection,\n ]);\n\n return null;\n};\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"SheetsTabBar.d.ts","sourceRoot":"","sources":["../../src/components/SheetsTabBar.tsx"],"names":[],"mappings":"AAiBA,OAAO,KAAuC,MAAM,OAAO,CAAC;AAK5D,MAAM,MAAM,iBAAiB,GAAG;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAQF,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAmIpD,CAAC"}
1
+ {"version":3,"file":"SheetsTabBar.d.ts","sourceRoot":"","sources":["../../src/components/SheetsTabBar.tsx"],"names":[],"mappings":"AAkBA,OAAO,KAAuC,MAAM,OAAO,CAAC;AAK5D,MAAM,MAAM,iBAAiB,GAAG;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AASF,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAmIpD,CAAC"}
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, TabStrip, } from '@sqlrooms/ui';
3
- import { AppWindow, FileText, LayoutDashboard, PencilIcon, PlusIcon, TrashIcon, } from 'lucide-react';
3
+ import { AppWindow, BarChart3, FileText, LayoutDashboard, PencilIcon, PlusIcon, TrashIcon, } from 'lucide-react';
4
4
  import React, { useCallback, useMemo, useState } from 'react';
5
5
  import { useCellsStore } from '../hooks';
6
6
  import { DeleteSheetModal } from './DeleteSheetModal';
@@ -8,6 +8,7 @@ const TYPE_ICONS = {
8
8
  notebook: FileText,
9
9
  canvas: LayoutDashboard,
10
10
  app: AppWindow,
11
+ dashboard: BarChart3,
11
12
  };
12
13
  export const SheetsTabBar = ({ className }) => {
13
14
  const sheetOrder = useCellsStore((s) => s.cells.config.sheetOrder);
@@ -1 +1 @@
1
- {"version":3,"file":"SheetsTabBar.js","sourceRoot":"","sources":["../../src/components/SheetsTabBar.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n TabStrip,\n TabDescriptor,\n} from '@sqlrooms/ui';\nimport {\n AppWindow,\n FileText,\n LayoutDashboard,\n PencilIcon,\n PlusIcon,\n TrashIcon,\n} from 'lucide-react';\nimport React, {useCallback, useMemo, useState} from 'react';\nimport {useCellsStore} from '../hooks';\nimport {SheetType} from '../types';\nimport {DeleteSheetModal} from './DeleteSheetModal';\n\nexport type SheetsTabBarProps = {\n className?: string;\n};\n\nconst TYPE_ICONS: Record<SheetType, React.ElementType> = {\n notebook: FileText,\n canvas: LayoutDashboard,\n app: AppWindow,\n};\n\nexport const SheetsTabBar: React.FC<SheetsTabBarProps> = ({className}) => {\n const sheetOrder = useCellsStore((s) => s.cells.config.sheetOrder);\n const sheets = useCellsStore((s) => s.cells.config.sheets);\n const currentSheetId = useCellsStore((s) => s.cells.config.currentSheetId);\n const supportedTypes = useCellsStore((s) => s.cells.supportedSheetTypes);\n const addSheet = useCellsStore((s) => s.cells.addSheet);\n const removeSheet = useCellsStore((s) => s.cells.removeSheet);\n const closeSheet = useCellsStore((s) => s.cells.closeSheet);\n const openSheet = useCellsStore((s) => s.cells.openSheet);\n const setSheetOrder = useCellsStore((s) => s.cells.setSheetOrder);\n const renameSheet = useCellsStore((s) => s.cells.renameSheet);\n const setCurrentSheet = useCellsStore((s) => s.cells.setCurrentSheet);\n\n const [sheetToDelete, setSheetToDelete] = useState<string | null>(null);\n\n const handleDeleteRequest = useCallback(\n (sheetId: string) => {\n const sheet = sheets[sheetId];\n if (sheet && sheet.cellIds.length === 0) {\n removeSheet(sheetId);\n } else {\n setSheetToDelete(sheetId);\n }\n },\n [sheets, removeSheet],\n );\n\n const handleConfirmDelete = useCallback(() => {\n if (sheetToDelete) {\n removeSheet(sheetToDelete);\n setSheetToDelete(null);\n }\n }, [sheetToDelete, removeSheet]);\n\n const tabs = useMemo(\n () =>\n Object.values(sheets).map((sheet) => ({\n id: sheet.id,\n name: sheet.title || 'Untitled',\n type: sheet.type || 'notebook',\n })),\n [sheets],\n );\n\n return (\n <TabStrip\n className={className}\n tabs={tabs}\n openTabs={sheetOrder}\n selectedTabId={currentSheetId}\n onSelect={setCurrentSheet}\n onRename={renameSheet}\n onClose={closeSheet}\n onOpenTabsChange={setSheetOrder}\n renderTabTitle={(tab: TabDescriptor) => {\n const Icon = TYPE_ICONS[tab.type as SheetType] || FileText;\n return (\n <div className=\"flex min-w-0 items-center gap-2\">\n <Icon className=\"text-muted-foreground h-3.5 w-3.5 shrink-0\" />\n <div className=\"truncate text-sm\">{tab.name}</div>\n </div>\n );\n }}\n renderTabMenu={(tab) => (\n <>\n <TabStrip.MenuItem onClick={() => renameSheet(tab.id, tab.name)}>\n <PencilIcon className=\"mr-2 h-4 w-4\" />\n Rename\n </TabStrip.MenuItem>\n <TabStrip.MenuSeparator />\n <TabStrip.MenuItem\n variant=\"destructive\"\n onClick={() => handleDeleteRequest(tab.id)}\n >\n <TrashIcon className=\"mr-2 h-4 w-4\" />\n Delete\n </TabStrip.MenuItem>\n </>\n )}\n renderSearchItemActions={(tab) => (\n <>\n <TabStrip.SearchItemAction\n icon={<PencilIcon className=\"h-3 w-3\" />}\n aria-label={`Rename ${tab.name}`}\n onClick={() => renameSheet(tab.id, tab.name)}\n />\n <TabStrip.SearchItemAction\n icon={<TrashIcon className=\"h-3 w-3\" />}\n aria-label={`Delete ${tab.name}`}\n onClick={() => handleDeleteRequest(tab.id)}\n />\n </>\n )}\n >\n <TabStrip.SearchDropdown />\n <TabStrip.Tabs tabClassName=\"pl-2\" />\n\n {supportedTypes.length === 1 ? (\n <AddNewSheetButton\n onClick={() => addSheet(undefined, supportedTypes[0])}\n />\n ) : (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <AddNewSheetButton />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\">\n {supportedTypes.map((type) => {\n const Icon = TYPE_ICONS[type];\n return (\n <DropdownMenuItem\n key={type}\n onClick={() => addSheet(undefined, type)}\n className=\"capitalize\"\n >\n <Icon className=\"mr-2 h-4 w-4\" />\n New {type}\n </DropdownMenuItem>\n );\n })}\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n <DeleteSheetModal\n isOpen={sheetToDelete !== null}\n onClose={() => setSheetToDelete(null)}\n onConfirm={handleConfirmDelete}\n sheetTitle={sheetToDelete ? sheets[sheetToDelete]?.title : undefined}\n />\n </TabStrip>\n );\n};\n\nconst AddNewSheetButton = React.forwardRef<\n HTMLButtonElement,\n React.ComponentPropsWithoutRef<typeof Button>\n>(({className, ...props}, ref) => {\n return (\n <Button\n ref={ref}\n size=\"icon\"\n variant=\"ghost\"\n className={`hover:bg-primary/10 h-full w-8 rounded-none ${className ?? ''}`}\n {...props}\n >\n <PlusIcon className=\"h-4 w-4\" />\n </Button>\n );\n});\nAddNewSheetButton.displayName = 'AddNewSheetButton';\n"]}
1
+ {"version":3,"file":"SheetsTabBar.js","sourceRoot":"","sources":["../../src/components/SheetsTabBar.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n TabStrip,\n TabDescriptor,\n} from '@sqlrooms/ui';\nimport {\n AppWindow,\n BarChart3,\n FileText,\n LayoutDashboard,\n PencilIcon,\n PlusIcon,\n TrashIcon,\n} from 'lucide-react';\nimport React, {useCallback, useMemo, useState} from 'react';\nimport {useCellsStore} from '../hooks';\nimport {SheetType} from '../types';\nimport {DeleteSheetModal} from './DeleteSheetModal';\n\nexport type SheetsTabBarProps = {\n className?: string;\n};\n\nconst TYPE_ICONS: Record<SheetType, React.ElementType> = {\n notebook: FileText,\n canvas: LayoutDashboard,\n app: AppWindow,\n dashboard: BarChart3,\n};\n\nexport const SheetsTabBar: React.FC<SheetsTabBarProps> = ({className}) => {\n const sheetOrder = useCellsStore((s) => s.cells.config.sheetOrder);\n const sheets = useCellsStore((s) => s.cells.config.sheets);\n const currentSheetId = useCellsStore((s) => s.cells.config.currentSheetId);\n const supportedTypes = useCellsStore((s) => s.cells.supportedSheetTypes);\n const addSheet = useCellsStore((s) => s.cells.addSheet);\n const removeSheet = useCellsStore((s) => s.cells.removeSheet);\n const closeSheet = useCellsStore((s) => s.cells.closeSheet);\n const openSheet = useCellsStore((s) => s.cells.openSheet);\n const setSheetOrder = useCellsStore((s) => s.cells.setSheetOrder);\n const renameSheet = useCellsStore((s) => s.cells.renameSheet);\n const setCurrentSheet = useCellsStore((s) => s.cells.setCurrentSheet);\n\n const [sheetToDelete, setSheetToDelete] = useState<string | null>(null);\n\n const handleDeleteRequest = useCallback(\n (sheetId: string) => {\n const sheet = sheets[sheetId];\n if (sheet && sheet.cellIds.length === 0) {\n removeSheet(sheetId);\n } else {\n setSheetToDelete(sheetId);\n }\n },\n [sheets, removeSheet],\n );\n\n const handleConfirmDelete = useCallback(() => {\n if (sheetToDelete) {\n removeSheet(sheetToDelete);\n setSheetToDelete(null);\n }\n }, [sheetToDelete, removeSheet]);\n\n const tabs = useMemo(\n () =>\n Object.values(sheets).map((sheet) => ({\n id: sheet.id,\n name: sheet.title || 'Untitled',\n type: sheet.type || 'notebook',\n })),\n [sheets],\n );\n\n return (\n <TabStrip\n className={className}\n tabs={tabs}\n openTabs={sheetOrder}\n selectedTabId={currentSheetId}\n onSelect={setCurrentSheet}\n onRename={renameSheet}\n onClose={closeSheet}\n onOpenTabsChange={setSheetOrder}\n renderTabTitle={(tab: TabDescriptor) => {\n const Icon = TYPE_ICONS[tab.type as SheetType] || FileText;\n return (\n <div className=\"flex min-w-0 items-center gap-2\">\n <Icon className=\"text-muted-foreground h-3.5 w-3.5 shrink-0\" />\n <div className=\"truncate text-sm\">{tab.name}</div>\n </div>\n );\n }}\n renderTabMenu={(tab) => (\n <>\n <TabStrip.MenuItem onClick={() => renameSheet(tab.id, tab.name)}>\n <PencilIcon className=\"mr-2 h-4 w-4\" />\n Rename\n </TabStrip.MenuItem>\n <TabStrip.MenuSeparator />\n <TabStrip.MenuItem\n variant=\"destructive\"\n onClick={() => handleDeleteRequest(tab.id)}\n >\n <TrashIcon className=\"mr-2 h-4 w-4\" />\n Delete\n </TabStrip.MenuItem>\n </>\n )}\n renderSearchItemActions={(tab) => (\n <>\n <TabStrip.SearchItemAction\n icon={<PencilIcon className=\"h-3 w-3\" />}\n aria-label={`Rename ${tab.name}`}\n onClick={() => renameSheet(tab.id, tab.name)}\n />\n <TabStrip.SearchItemAction\n icon={<TrashIcon className=\"h-3 w-3\" />}\n aria-label={`Delete ${tab.name}`}\n onClick={() => handleDeleteRequest(tab.id)}\n />\n </>\n )}\n >\n <TabStrip.SearchDropdown />\n <TabStrip.Tabs tabClassName=\"pl-2\" />\n\n {supportedTypes.length === 1 ? (\n <AddNewSheetButton\n onClick={() => addSheet(undefined, supportedTypes[0])}\n />\n ) : (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <AddNewSheetButton />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\">\n {supportedTypes.map((type) => {\n const Icon = TYPE_ICONS[type];\n return (\n <DropdownMenuItem\n key={type}\n onClick={() => addSheet(undefined, type)}\n className=\"capitalize\"\n >\n <Icon className=\"mr-2 h-4 w-4\" />\n New {type}\n </DropdownMenuItem>\n );\n })}\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n <DeleteSheetModal\n isOpen={sheetToDelete !== null}\n onClose={() => setSheetToDelete(null)}\n onConfirm={handleConfirmDelete}\n sheetTitle={sheetToDelete ? sheets[sheetToDelete]?.title : undefined}\n />\n </TabStrip>\n );\n};\n\nconst AddNewSheetButton = React.forwardRef<\n HTMLButtonElement,\n React.ComponentPropsWithoutRef<typeof Button>\n>(({className, ...props}, ref) => {\n return (\n <Button\n ref={ref}\n size=\"icon\"\n variant=\"ghost\"\n className={`hover:bg-primary/10 h-full w-8 rounded-none ${className ?? ''}`}\n {...props}\n >\n <PlusIcon className=\"h-4 w-4\" />\n </Button>\n );\n});\nAddNewSheetButton.displayName = 'AddNewSheetButton';\n"]}
@@ -0,0 +1,6 @@
1
+ import type { SqlCellBodyProps } from '../types';
2
+ /**
3
+ * Editable SQL cell body with controls and optional result rendering.
4
+ */
5
+ export declare function SqlCellBody({ sql, onSqlChange, onRun, onCancel, status, resultName, renderResult, runLabel, disabled, }: SqlCellBodyProps): import("react/jsx-runtime").JSX.Element;
6
+ //# sourceMappingURL=SqlCellBody.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SqlCellBody.d.ts","sourceRoot":"","sources":["../../src/components/SqlCellBody.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,UAAU,CAAC;AAG/C;;GAEG;AACH,wBAAgB,WAAW,CAAC,EAC1B,GAAG,EACH,WAAW,EACX,KAAK,EACL,QAAQ,EACR,MAAM,EACN,UAAU,EACV,YAAY,EACZ,QAAgB,EAChB,QAAQ,GACT,EAAE,gBAAgB,2CAmClB"}
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { QueryDataTable } from '@sqlrooms/data-table';
3
+ import { SqlMonacoEditor } from '@sqlrooms/sql-editor';
4
+ import { SqlCellRunButton } from './SqlCellRunButton';
5
+ /**
6
+ * Editable SQL cell body with controls and optional result rendering.
7
+ */
8
+ export function SqlCellBody({ sql, onSqlChange, onRun, onCancel, status, resultName, renderResult, runLabel = 'Run', disabled, }) {
9
+ const showResultTable = !renderResult && resultName && status?.state === 'success';
10
+ return (_jsxs("div", { className: "flex w-full flex-col gap-2", children: [_jsx("div", { className: "p-2", children: _jsx(SqlCellRunButton, { onRun: onRun, onCancel: onCancel, status: status, runLabel: runLabel, disabled: disabled }) }), _jsxs("div", { className: "relative flex min-h-[200px] flex-col", children: [_jsx("div", { children: _jsx(SqlMonacoEditor, { className: "absolute inset-0 h-full w-full", value: sql, onChange: (v) => onSqlChange(v || ''), options: { minimap: { enabled: false } } }) }), renderResult, showResultTable && (_jsx("div", { className: "overflow-hidden border-t", children: _jsx(QueryDataTable, { query: `SELECT * FROM ${resultName}`, fontSize: "text-xs" }) }))] })] }));
11
+ }
12
+ //# sourceMappingURL=SqlCellBody.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SqlCellBody.js","sourceRoot":"","sources":["../../src/components/SqlCellBody.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAC,cAAc,EAAC,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAC,eAAe,EAAC,MAAM,sBAAsB,CAAC;AAErD,OAAO,EAAC,gBAAgB,EAAC,MAAM,oBAAoB,CAAC;AAEpD;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,EAC1B,GAAG,EACH,WAAW,EACX,KAAK,EACL,QAAQ,EACR,MAAM,EACN,UAAU,EACV,YAAY,EACZ,QAAQ,GAAG,KAAK,EAChB,QAAQ,GACS;IACjB,MAAM,eAAe,GACnB,CAAC,YAAY,IAAI,UAAU,IAAI,MAAM,EAAE,KAAK,KAAK,SAAS,CAAC;IAC7D,OAAO,CACL,eAAK,SAAS,EAAC,4BAA4B,aACzC,cAAK,SAAS,EAAC,KAAK,YAClB,KAAC,gBAAgB,IACf,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,QAAQ,GAClB,GACE,EACN,eAAK,SAAS,EAAC,sCAAsC,aACnD,wBACE,KAAC,eAAe,IACd,SAAS,EAAC,gCAAgC,EAC1C,KAAK,EAAE,GAAG,EACV,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,IAAI,EAAE,CAAC,EACrC,OAAO,EAAE,EAAC,OAAO,EAAE,EAAC,OAAO,EAAE,KAAK,EAAC,EAAC,GACpC,GACE,EACL,YAAY,EACZ,eAAe,IAAI,CAClB,cAAK,SAAS,EAAC,0BAA0B,YACvC,KAAC,cAAc,IACb,KAAK,EAAE,iBAAiB,UAAU,EAAE,EACpC,QAAQ,EAAC,SAAS,GAClB,GACE,CACP,IACG,IACF,CACP,CAAC;AACJ,CAAC","sourcesContent":["import {QueryDataTable} from '@sqlrooms/data-table';\nimport {SqlMonacoEditor} from '@sqlrooms/sql-editor';\nimport type {SqlCellBodyProps} from '../types';\nimport {SqlCellRunButton} from './SqlCellRunButton';\n\n/**\n * Editable SQL cell body with controls and optional result rendering.\n */\nexport function SqlCellBody({\n sql,\n onSqlChange,\n onRun,\n onCancel,\n status,\n resultName,\n renderResult,\n runLabel = 'Run',\n disabled,\n}: SqlCellBodyProps) {\n const showResultTable =\n !renderResult && resultName && status?.state === 'success';\n return (\n <div className=\"flex w-full flex-col gap-2\">\n <div className=\"p-2\">\n <SqlCellRunButton\n onRun={onRun}\n onCancel={onCancel}\n status={status}\n runLabel={runLabel}\n disabled={disabled}\n />\n </div>\n <div className=\"relative flex min-h-[200px] flex-col\">\n <div>\n <SqlMonacoEditor\n className=\"absolute inset-0 h-full w-full\"\n value={sql}\n onChange={(v) => onSqlChange(v || '')}\n options={{minimap: {enabled: false}}}\n />\n </div>\n {renderResult}\n {showResultTable && (\n <div className=\"overflow-hidden border-t\">\n <QueryDataTable\n query={`SELECT * FROM ${resultName}`}\n fontSize=\"text-xs\"\n />\n </div>\n )}\n </div>\n </div>\n );\n}\n"]}
@@ -0,0 +1,9 @@
1
+ import { type DbConnection } from '@sqlrooms/db';
2
+ import React from 'react';
3
+ export type SqlCellConnectionSelectorProps = {
4
+ connectors: Record<string, DbConnection>;
5
+ selectedConnectorId: string;
6
+ onChange: (connectorId: string) => void;
7
+ };
8
+ export declare const SqlCellConnectionSelector: React.FC<SqlCellConnectionSelectorProps>;
9
+ //# sourceMappingURL=SqlCellConnectionSelector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SqlCellConnectionSelector.d.ts","sourceRoot":"","sources":["../../src/components/SqlCellConnectionSelector.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA4B,KAAK,YAAY,EAAC,MAAM,cAAc,CAAC;AAQ1E,OAAO,KAAgB,MAAM,OAAO,CAAC;AAErC,MAAM,MAAM,8BAA8B,GAAG;IAC3C,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACzC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,QAAQ,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC,CAAC;AAEF,eAAO,MAAM,yBAAyB,EAAE,KAAK,CAAC,EAAE,CAC9C,8BAA8B,CAkC/B,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { getCoreDuckDbConnectionId } from '@sqlrooms/db';
3
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@sqlrooms/ui';
4
+ import { useMemo } from 'react';
5
+ export const SqlCellConnectionSelector = ({ connectors, selectedConnectorId, onChange }) => {
6
+ const connectionOptions = useMemo(() => {
7
+ const entries = Object.values(connectors);
8
+ if (!entries.length) {
9
+ return [{ id: getCoreDuckDbConnectionId(), title: 'Core DuckDB' }];
10
+ }
11
+ return entries.map(({ id, title }) => ({
12
+ id,
13
+ title: title || id,
14
+ }));
15
+ }, [connectors]);
16
+ if (connectionOptions.length <= 1) {
17
+ return null;
18
+ }
19
+ return (_jsx("div", { className: "flex-1", children: _jsxs(Select, { value: selectedConnectorId, onValueChange: onChange, children: [_jsx(SelectTrigger, { className: "h-7 text-xs", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: connectionOptions.map(({ id, title }) => (_jsx(SelectItem, { value: id, children: title }, id))) })] }) }));
20
+ };
21
+ //# sourceMappingURL=SqlCellConnectionSelector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SqlCellConnectionSelector.js","sourceRoot":"","sources":["../../src/components/SqlCellConnectionSelector.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAC,yBAAyB,EAAoB,MAAM,cAAc,CAAC;AAC1E,OAAO,EACL,MAAM,EACN,aAAa,EACb,UAAU,EACV,aAAa,EACb,WAAW,GACZ,MAAM,cAAc,CAAC;AACtB,OAAc,EAAC,OAAO,EAAC,MAAM,OAAO,CAAC;AAQrC,MAAM,CAAC,MAAM,yBAAyB,GAElC,CAAC,EAAC,UAAU,EAAE,mBAAmB,EAAE,QAAQ,EAAC,EAAE,EAAE;IAClD,MAAM,iBAAiB,GAAG,OAAO,CAAC,GAAG,EAAE;QACrC,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC1C,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;YACpB,OAAO,CAAC,EAAC,EAAE,EAAE,yBAAyB,EAAE,EAAE,KAAK,EAAE,aAAa,EAAC,CAAC,CAAC;QACnE,CAAC;QAED,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,EAAC,EAAE,EAAE,KAAK,EAAC,EAAE,EAAE,CAAC,CAAC;YACnC,EAAE;YACF,KAAK,EAAE,KAAK,IAAI,EAAE;SACnB,CAAC,CAAC,CAAC;IACN,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC;IAEjB,IAAI,iBAAiB,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;QAClC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,CACL,cAAK,SAAS,EAAC,QAAQ,YACrB,MAAC,MAAM,IAAC,KAAK,EAAE,mBAAmB,EAAE,aAAa,EAAE,QAAQ,aACzD,KAAC,aAAa,IAAC,SAAS,EAAC,aAAa,YACpC,KAAC,WAAW,KAAG,GACD,EAChB,KAAC,aAAa,cACX,iBAAiB,CAAC,GAAG,CAAC,CAAC,EAAC,EAAE,EAAE,KAAK,EAAC,EAAE,EAAE,CAAC,CACtC,KAAC,UAAU,IAAU,KAAK,EAAE,EAAE,YAC3B,KAAK,IADS,EAAE,CAEN,CACd,CAAC,GACY,IACT,GACL,CACP,CAAC;AACJ,CAAC,CAAC","sourcesContent":["import {getCoreDuckDbConnectionId, type DbConnection} from '@sqlrooms/db';\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@sqlrooms/ui';\nimport React, {useMemo} from 'react';\n\nexport type SqlCellConnectionSelectorProps = {\n connectors: Record<string, DbConnection>;\n selectedConnectorId: string;\n onChange: (connectorId: string) => void;\n};\n\nexport const SqlCellConnectionSelector: React.FC<\n SqlCellConnectionSelectorProps\n> = ({connectors, selectedConnectorId, onChange}) => {\n const connectionOptions = useMemo(() => {\n const entries = Object.values(connectors);\n if (!entries.length) {\n return [{id: getCoreDuckDbConnectionId(), title: 'Core DuckDB'}];\n }\n\n return entries.map(({id, title}) => ({\n id,\n title: title || id,\n }));\n }, [connectors]);\n\n if (connectionOptions.length <= 1) {\n return null;\n }\n\n return (\n <div className=\"flex-1\">\n <Select value={selectedConnectorId} onValueChange={onChange}>\n <SelectTrigger className=\"h-7 text-xs\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {connectionOptions.map(({id, title}) => (\n <SelectItem key={id} value={id}>\n {title}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n );\n};\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"SqlCellContent.d.ts","sourceRoot":"","sources":["../../src/components/SqlCellContent.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAA0D,MAAM,OAAO,CAAC;AAE/E,OAAO,KAAK,EAAC,kBAAkB,EAAkB,OAAO,EAAC,MAAM,UAAU,CAAC;AAI1E,MAAM,MAAM,mBAAmB,GAAG;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,OAAO,CAAC;IACd,eAAe,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,KAAK,CAAC,YAAY,CAAC;CACpE,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CAqZxD,CAAC"}
1
+ {"version":3,"file":"SqlCellContent.d.ts","sourceRoot":"","sources":["../../src/components/SqlCellContent.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAgD,MAAM,OAAO,CAAC;AAErE,OAAO,KAAK,EACV,kBAAkB,EAElB,OAAO,EAER,MAAM,UAAU,CAAC;AAQlB,MAAM,MAAM,mBAAmB,GAAG;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,OAAO,CAAC;IACd,eAAe,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,KAAK,CAAC,YAAY,CAAC;CACpE,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CA8KxD,CAAC"}
@@ -1,15 +1,15 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { DataTablePaginated, QueryDataTableActionsMenu, useArrowDataTable, } from '@sqlrooms/data-table';
3
2
  import { getCoreDuckDbConnectionId } from '@sqlrooms/db';
4
3
  import { useRoomStoreApi } from '@sqlrooms/room-store';
5
- import { SqlMonacoEditor } from '@sqlrooms/sql-editor';
6
- import { Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuTrigger, EditableText, Tooltip, TooltipContent, TooltipTrigger, } from '@sqlrooms/ui';
7
- import { convertToValidColumnOrTableName } from '@sqlrooms/utils';
8
4
  import { produce } from 'immer';
9
5
  import { CornerDownRightIcon } from 'lucide-react';
10
- import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
6
+ import { useCallback, useEffect, useMemo, useRef } from 'react';
11
7
  import { useCellsStore } from '../hooks';
12
- import { getEffectiveResultName, isValidSqlIdentifier } from '../utils';
8
+ import { SqlCellConnectionSelector } from './SqlCellConnectionSelector';
9
+ import { SqlCellDependentsMenu } from './SqlCellDependentsMenu';
10
+ import { SqlCellEditor } from './SqlCellEditor';
11
+ import { SqlCellResultNameEditor } from './SqlCellResultNameEditor';
12
+ import { SqlCellResults } from './SqlCellResults';
13
13
  import { SqlCellRunButton } from './SqlCellRunButton';
14
14
  export const SqlCellContent = ({ id, cell, renderContainer, }) => {
15
15
  const storeApi = useRoomStoreApi();
@@ -26,10 +26,12 @@ export const SqlCellContent = ({ id, cell, renderContainer, }) => {
26
26
  const getCellResult = useCellsStore((s) => s.cells.getCellResult);
27
27
  const fetchCellResultPage = useCellsStore((s) => s.cells.fetchCellResultPage);
28
28
  const dbConnections = useCellsStore((s) => s.db.config.connections);
29
+ const connector = useCellsStore((s) => s.db.connector);
30
+ const tableSchemas = useCellsStore((s) => s.db.tables);
29
31
  // Re-read the cache whenever resultVersion or pageVersion changes
32
+ const cellResult = useMemo(() => getCellResult(id),
30
33
  // eslint-disable-next-line react-hooks/exhaustive-deps
31
- const cellResult = useMemo(() => getCellResult(id), [getCellResult, id, resultVersion, pageVersion]);
32
- const arrowTableData = useArrowDataTable(cellResult?.arrowTable);
34
+ [getCellResult, id, resultVersion, pageVersion]);
33
35
  const handleSqlChange = useCallback((v) => {
34
36
  updateCell(id, (c) => produce(c, (draft) => {
35
37
  if (draft.type === 'sql') {
@@ -40,6 +42,17 @@ export const SqlCellContent = ({ id, cell, renderContainer, }) => {
40
42
  const handleRun = useCallback(() => {
41
43
  runCell(id);
42
44
  }, [id, runCell]);
45
+ const handleRunQuery = useCallback((query) => {
46
+ // Sync the actual query being executed to store before running
47
+ // to avoid race condition with onChange callback
48
+ storeApi.setState(produce(storeApi.getState(), (draft) => {
49
+ const c = draft.cells?.config?.data?.[id];
50
+ if (c && c.type === 'sql') {
51
+ c.data.sql = query;
52
+ }
53
+ }));
54
+ handleRunRef.current();
55
+ }, [id, storeApi]);
43
56
  const handleConnectorChange = useCallback((connectorId) => {
44
57
  updateCell(id, (c) => produce(c, (draft) => {
45
58
  if (draft.type === 'sql') {
@@ -50,171 +63,33 @@ export const SqlCellContent = ({ id, cell, renderContainer, }) => {
50
63
  const handleCancel = useCallback(() => {
51
64
  cancelCell(id);
52
65
  }, [id, cancelCell]);
53
- const validateResultName = useCallback((value) => {
54
- const nextValue = value.trim();
55
- return Boolean(nextValue) && isValidSqlIdentifier(nextValue);
56
- }, []);
57
- const handleResultNameInputChange = useCallback((value) => {
58
- setIsResultNameInvalid(!validateResultName(value));
59
- }, [validateResultName]);
60
66
  const handleResultNameChange = useCallback((value) => {
61
- const nextValue = value.trim();
62
- if (!validateResultName(nextValue)) {
63
- setIsResultNameInvalid(true);
64
- return;
65
- }
66
67
  updateCell(id, (c) => produce(c, (draft) => {
67
68
  if (draft.type === 'sql') {
68
- draft.data.resultName = nextValue;
69
+ draft.data.resultName = value;
69
70
  }
70
71
  }));
71
- setIsResultNameInvalid(false);
72
- }, [id, updateCell, validateResultName]);
73
- const effectiveResultName = getEffectiveResultName(cell.data, convertToValidColumnOrTableName);
74
- const explicitResultName = cell.data.resultName || '';
72
+ }, [id, updateCell]);
73
+ const resultName = cell.data.resultName || '';
75
74
  const selectedConnectorId = cell.data.connectorId || getCoreDuckDbConnectionId();
76
- const connectionOptions = useMemo(() => {
77
- const entries = Object.values(dbConnections);
78
- if (!entries.length) {
79
- return [{ id: getCoreDuckDbConnectionId(), title: 'Core DuckDB' }];
80
- }
81
- return entries.map((conn) => ({
82
- id: conn.id,
83
- title: conn.title || conn.id,
84
- }));
85
- }, [dbConnections]);
86
- const [dependentsMenuOpen, setDependentsMenuOpen] = useState(false);
87
- const closeDependentsMenuTimerRef = useRef(null);
88
- const [isResultNameInvalid, setIsResultNameInvalid] = useState(false);
89
- // eslint-disable-next-line react-hooks/exhaustive-deps
90
- const downstreamCellIds = useMemo(() => {
91
- if (!currentSheetId)
92
- return [];
93
- return getDownstream(currentSheetId, id);
94
- }, [currentSheetId, getDownstream, id, cellsData, sheets]);
95
- const downstreamCells = useMemo(() => downstreamCellIds.map((cellId) => {
96
- const downstreamCell = cellsData[cellId];
97
- return {
98
- id: cellId,
99
- label: downstreamCell?.data?.title?.trim() || 'Untitled',
100
- };
101
- }), [cellsData, downstreamCellIds]);
102
- const scrollToDependentCell = useCallback((targetCellId) => {
103
- const selectorTargets = [
104
- `[data-cell-container-id="${targetCellId}"]`,
105
- `#cell-${targetCellId}`,
106
- ];
107
- const el = selectorTargets
108
- .map((selector) => document.querySelector(selector))
109
- .find(Boolean) ?? null;
110
- if (el) {
111
- el.scrollIntoView({ behavior: 'smooth', block: 'center' });
112
- }
113
- }, []);
114
75
  const status = cellStatus?.type === 'sql'
115
76
  ? {
116
77
  state: cellStatus.status,
117
78
  message: cellStatus.lastError,
118
- resultName: cellStatus.resultView || cellStatus.resultName,
79
+ resultName: cellStatus.resultView ||
80
+ cellStatus.resultName,
119
81
  lastRunTime: cellStatus.lastRunTime,
120
82
  }
121
83
  : undefined;
122
- const resultName = status?.resultName;
123
84
  const isRunning = status?.state === 'running';
124
- // Pagination and sorting state for the result table
125
- const [sorting, setSorting] = useState([]);
126
- const [pagination, setPagination] = useState({
127
- pageIndex: 0,
128
- pageSize: 10,
129
- });
130
- // Fetch new page when pagination or sorting changes (not on initial render
131
- // since executeSqlCell already fetches the first page)
132
- const isFirstRender = useRef(true);
133
- // eslint-disable-next-line react-hooks/exhaustive-deps
134
- useEffect(() => {
135
- if (isFirstRender.current) {
136
- isFirstRender.current = false;
137
- return;
138
- }
139
- if (resultName) {
140
- fetchCellResultPage(id, pagination, sorting);
141
- }
142
- }, [pagination, sorting, id, fetchCellResultPage]);
143
- const clearDependentsCloseTimer = useCallback(() => {
144
- if (closeDependentsMenuTimerRef.current) {
145
- clearTimeout(closeDependentsMenuTimerRef.current);
146
- closeDependentsMenuTimerRef.current = null;
147
- }
148
- }, []);
149
- const openDependentsMenu = useCallback(() => {
150
- clearDependentsCloseTimer();
151
- setDependentsMenuOpen(true);
152
- }, [clearDependentsCloseTimer]);
153
- const scheduleCloseDependentsMenu = useCallback(() => {
154
- clearDependentsCloseTimer();
155
- closeDependentsMenuTimerRef.current = setTimeout(() => {
156
- setDependentsMenuOpen(false);
157
- closeDependentsMenuTimerRef.current = null;
158
- }, 120);
159
- }, [clearDependentsCloseTimer]);
160
- useEffect(() => {
161
- return () => {
162
- clearDependentsCloseTimer();
163
- };
164
- }, [clearDependentsCloseTimer]);
165
- // Reset pagination when a new result arrives (new run)
166
- const prevResultVersion = useRef(resultVersion);
167
- useEffect(() => {
168
- if (resultVersion !== prevResultVersion.current) {
169
- prevResultVersion.current = resultVersion;
170
- setPagination((prev) => prev.pageIndex === 0 ? prev : { ...prev, pageIndex: 0 });
171
- setSorting((prev) => (prev.length === 0 ? prev : []));
172
- }
173
- }, [resultVersion]);
174
85
  const handleRunRef = useRef(handleRun);
175
86
  useEffect(() => {
176
87
  handleRunRef.current = handleRun;
177
88
  }, [handleRun]);
178
- const handleSqlEditorMount = useCallback((editor, monaco) => {
179
- // Use onKeyDown instead of addCommand to scope the shortcut
180
- // to THIS specific editor instance. Monaco's addCommand registers
181
- // globally, so the last editor mounted wins -- breaking multi-cell
182
- // notebooks.
183
- editor.onKeyDown((e) => {
184
- if ((e.ctrlKey || e.metaKey) && e.keyCode === monaco.KeyCode.Enter) {
185
- e.preventDefault();
186
- e.stopPropagation();
187
- // Flush the current editor value directly to the store
188
- // synchronously before running. Monaco's onChange may not have
189
- // fired yet for the latest content, so we read the editor model
190
- // directly and write it to the Zustand store in a single
191
- // synchronous call, bypassing the async updateCell path.
192
- const currentSql = editor.getValue();
193
- storeApi.setState(produce(storeApi.getState(), (draft) => {
194
- const c = draft.cells?.config?.data?.[id];
195
- if (c && c.type === 'sql') {
196
- c.data.sql = currentSql;
197
- }
198
- }));
199
- handleRunRef.current();
200
- }
201
- });
202
- }, [id, storeApi]);
203
- const content = (_jsxs("div", { className: "flex flex-col", children: [_jsx("div", { className: "h-full w-full py-1", children: _jsx("div", { className: "relative h-full min-h-[200px] w-full", children: _jsx(SqlMonacoEditor, { className: "absolute inset-0 h-full w-full", value: cell.data.sql, onChange: handleSqlChange, onMount: handleSqlEditorMount, options: {
204
- minimap: { enabled: false },
205
- scrollBeyondLastLine: false,
206
- scrollbar: {
207
- alwaysConsumeMouseWheel: false,
208
- },
209
- } }) }) }), status?.state === 'error' ? (_jsx("div", { className: "relative max-h-[400px] overflow-auto p-4", children: _jsx("span", { className: "font-mono text-xs whitespace-pre-wrap text-red-600", children: status.message }) })) : resultName && arrowTableData ? (_jsxs("div", { className: "relative min-h-[200px] overflow-hidden", children: [_jsx(DataTablePaginated, { className: "absolute inset-0 h-full w-full", fontSize: "text-xs", data: arrowTableData.data, columns: arrowTableData.columns, numRows: cellResult?.totalRows, pagination: pagination, onPaginationChange: setPagination, sorting: sorting, onSortingChange: setSorting, isFetching: isRunning, footerActions: _jsx(QueryDataTableActionsMenu, { query: `SELECT * FROM ${resultName}` }) }), _jsx("div", { "aria-hidden": !isRunning, className: `bg-background/45 pointer-events-none absolute inset-0 z-10 transition-opacity duration-200 ${isRunning ? 'animate-pulse opacity-100' : 'opacity-0'}` })] })) : null] }));
210
- const footer = (_jsxs("div", { className: "text-muted-foreground flex items-center gap-1 px-2 py-1 text-xs", children: [_jsx(CornerDownRightIcon, { className: "h-3 w-3" }), downstreamCells.length > 0 ? (_jsxs(DropdownMenu, { open: dependentsMenuOpen, onOpenChange: setDependentsMenuOpen, children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { className: "h-5", size: "xs", variant: "secondary", "aria-label": `${downstreamCells.length} dependent cells`, onMouseEnter: openDependentsMenu, onMouseLeave: scheduleCloseDependentsMenu, children: downstreamCells.length }) }), _jsxs(DropdownMenuContent, { align: "start", onCloseAutoFocus: (e) => e.preventDefault(), onMouseEnter: openDependentsMenu, onMouseLeave: scheduleCloseDependentsMenu, children: [_jsx(DropdownMenuLabel, { className: "text-xs", children: "Referenced in" }), downstreamCells.map((dependentCell) => (_jsx(DropdownMenuItem, { className: "cursor-pointer text-xs", onSelect: (e) => {
211
- e.preventDefault();
212
- scrollToDependentCell(dependentCell.id);
213
- }, children: dependentCell.label }, dependentCell.id)))] })] })) : null, _jsxs(Tooltip, { open: isResultNameInvalid, children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("div", { children: _jsx(EditableText, { className: `h-6 w-40 font-mono text-xs text-green-500 shadow-none ${isResultNameInvalid
214
- ? 'border-red-500 ring-1 ring-red-500 focus:border-red-500 focus:ring-red-500'
215
- : ''}`, value: explicitResultName || effectiveResultName, onInputChange: handleResultNameInputChange, onChange: handleResultNameChange }) }) }), _jsx(TooltipContent, { side: "top", children: _jsx("span", { className: "text-xs", children: "Invalid result name" }) })] })] }));
89
+ const content = (_jsxs("div", { className: "flex flex-col", children: [_jsx(SqlCellEditor, { sql: cell.data.sql, connector: connector, tableSchemas: tableSchemas, onChange: handleSqlChange, onRunQuery: handleRunQuery }), _jsx(SqlCellResults, { cellId: id, cellResult: cellResult, resultName: resultName, isRunning: isRunning, errorMessage: status?.state === 'error' ? status.message : undefined, resultVersion: resultVersion, fetchCellResultPage: fetchCellResultPage })] }));
90
+ const footer = (_jsxs("div", { className: "text-muted-foreground flex items-center gap-1 px-2 py-1 text-xs", children: [_jsx(CornerDownRightIcon, { className: "h-3 w-3" }), _jsx(SqlCellDependentsMenu, { cellId: id, currentSheetId: currentSheetId, cellsData: cellsData, sheets: sheets, getDownstream: getDownstream }), _jsx(SqlCellResultNameEditor, { cellId: id, value: resultName, onChange: handleResultNameChange })] }));
216
91
  return renderContainer({
217
- header: (_jsxs("div", { className: "flex w-full items-center gap-2", children: [_jsx("div", { className: "flex-1", children: _jsx("select", { className: "border-input bg-background text-foreground h-7 rounded border px-2 text-xs", value: selectedConnectorId, onChange: (e) => handleConnectorChange(e.target.value), children: connectionOptions.map((connection) => (_jsx("option", { value: connection.id, children: connection.title }, connection.id))) }) }), _jsx(SqlCellRunButton, { onRun: handleRun, onCancel: handleCancel, status: status, runLabel: "Run" })] })),
92
+ header: (_jsxs("div", { className: "flex w-full items-center gap-2", children: [_jsx(SqlCellConnectionSelector, { connectors: dbConnections, selectedConnectorId: selectedConnectorId, onChange: handleConnectorChange }), _jsx(SqlCellRunButton, { onRun: handleRun, onCancel: handleCancel, status: status, runLabel: "Run" })] })),
218
93
  content,
219
94
  footer,
220
95
  });