@tightknitai/block-kitchen 0.10.19 → 0.10.20

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.
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { useSensors, useSensor, PointerSensor, TouchSensor, KeyboardSensor, pointerWithin, closestCenter, DndContext, DragOverlay, useDroppable, useDraggable } from '@dnd-kit/core';
2
2
  import { sortableKeyboardCoordinates, SortableContext, verticalListSortingStrategy, useSortable } from '@dnd-kit/sortable';
3
- import { X, Circle, Sparkles, FileText, AlignLeft, MousePointerClick, TextCursorInput, LayoutTemplate, Bell, Pilcrow, Image, Video, GalleryHorizontal, Table, ChartColumnBig, Container, Loader2, ExternalLink, AlertTriangle, ChevronRight, ChevronLeft, GripVertical, ChevronsUpDown, Check, Sun, Moon, MessageSquare, AppWindow, Home, Plus, ChevronDown, BookOpen, Undo2, Redo2, Trash2, Code2, Pencil, Send, Search, Copy, Info, CheckCircle2, LayoutGrid, ArrowUp, ArrowDown, Type, Bold, Italic, Strikethrough, Underline, Code, AtSign, List, ListOrdered, Quote, Smile, Link as Link$1 } from 'lucide-react';
3
+ import { X, Circle, Sparkles, FileText, AlignLeft, MousePointerClick, TextCursorInput, LayoutTemplate, Bell, Pilcrow, Image, Video, GalleryHorizontal, Table, ChartColumnBig, Container, Loader2, ExternalLink, AlertTriangle, ChevronRight, ChevronLeft, GripVertical, ChevronsUpDown, Check, Sun, Moon, MessageSquare, AppWindow, Home, Plus, ChevronDown, BookOpen, Undo2, Redo2, Trash2, Code2, Pencil, Send, Search, Copy, Info, CheckCircle2, LayoutGrid, ArrowUp, ArrowDown, Type, Bold, Italic, Strikethrough, Underline, Code, AtSign, List, ListOrdered, Quote, Hash, Smile, Link as Link$1 } from 'lucide-react';
4
4
  import * as React from 'react';
5
5
  import { createContext, useContext, useState, useRef, useEffect, useCallback, useMemo, useId } from 'react';
6
6
  import { clsx } from 'clsx';
@@ -1066,6 +1066,58 @@ var defaultPalette = [
1066
1066
  ]
1067
1067
  ]
1068
1068
  })
1069
+ },
1070
+ {
1071
+ // Slack's documented example.
1072
+ id: "data_table_basic",
1073
+ label: "Data table",
1074
+ factory: () => ({
1075
+ type: "data_table",
1076
+ caption: "A Fabulous Table",
1077
+ rows: [
1078
+ [
1079
+ { type: "raw_text", text: "Name" },
1080
+ { type: "raw_text", text: "Department" },
1081
+ { type: "raw_text", text: "Badge" }
1082
+ ],
1083
+ [
1084
+ { type: "raw_text", text: "Data Refinement Department" },
1085
+ { type: "raw_text", text: "MDR" },
1086
+ {
1087
+ type: "rich_text",
1088
+ elements: [
1089
+ { type: "rich_text_section", elements: [{ type: "text", text: "Blue", style: { bold: true } }] }
1090
+ ]
1091
+ }
1092
+ ],
1093
+ [
1094
+ { type: "raw_text", text: "Art Sourcing Department" },
1095
+ { type: "raw_text", text: "O&D" },
1096
+ {
1097
+ type: "rich_text",
1098
+ elements: [
1099
+ {
1100
+ type: "rich_text_section",
1101
+ elements: [
1102
+ { type: "text", text: "Green " },
1103
+ { type: "text", text: "review", style: { italic: true } }
1104
+ ]
1105
+ }
1106
+ ]
1107
+ }
1108
+ ],
1109
+ [
1110
+ { type: "raw_text", text: "Wellness Department" },
1111
+ { type: "raw_text", text: "Wellness Center" },
1112
+ {
1113
+ type: "rich_text",
1114
+ elements: [
1115
+ { type: "rich_text_section", elements: [{ type: "text", text: "Limited", style: { bold: true } }] }
1116
+ ]
1117
+ }
1118
+ ]
1119
+ ]
1120
+ })
1069
1121
  }
1070
1122
  ]
1071
1123
  },
@@ -1730,6 +1782,8 @@ function labelForBlockType(type) {
1730
1782
  return "Data Visualization";
1731
1783
  case "container":
1732
1784
  return "Container";
1785
+ case "data_table":
1786
+ return "Data Table";
1733
1787
  }
1734
1788
  }
1735
1789
  var Sheet = DialogPrimitive.Root;
@@ -7780,6 +7834,387 @@ function ImageItemCard({
7780
7834
  ) })
7781
7835
  ] });
7782
7836
  }
7837
+ var REMOVE_BUTTON_CLASS = "flex h-6 w-6 shrink-0 items-center justify-center rounded text-muted-foreground hover:bg-destructive/10 hover:text-destructive disabled:cursor-not-allowed disabled:opacity-40";
7838
+ function CellGrid({
7839
+ label,
7840
+ help,
7841
+ rows,
7842
+ minRows,
7843
+ maxRows,
7844
+ maxColumns,
7845
+ lockFirstRow = false,
7846
+ blankCell,
7847
+ renderCell,
7848
+ renderColumnControl,
7849
+ onChange
7850
+ }) {
7851
+ const columnCount = rows[0]?.length ?? 0;
7852
+ const setCell = (rowIdx, colIdx, next) => {
7853
+ onChange(rows.map((row, r) => r === rowIdx ? row.map((cell, c) => c === colIdx ? next : cell) : row));
7854
+ };
7855
+ const addRow = () => {
7856
+ onChange([...rows, Array.from({ length: columnCount || 1 }, blankCell)]);
7857
+ };
7858
+ const removeRow = (rowIdx) => {
7859
+ if (rows.length <= minRows) {
7860
+ return;
7861
+ }
7862
+ onChange(rows.filter((_, r) => r !== rowIdx));
7863
+ };
7864
+ const addColumn = () => {
7865
+ onChange(
7866
+ rows.map((row) => [...row, blankCell()]),
7867
+ { kind: "add" }
7868
+ );
7869
+ };
7870
+ const removeColumn = (colIdx) => {
7871
+ if (columnCount <= 1) {
7872
+ return;
7873
+ }
7874
+ onChange(
7875
+ rows.map((row) => row.filter((_, c) => c !== colIdx)),
7876
+ { kind: "remove", index: colIdx }
7877
+ );
7878
+ };
7879
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
7880
+ /* @__PURE__ */ jsx(EditorField, { label, help, children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 overflow-x-auto", children: [
7881
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
7882
+ /* @__PURE__ */ jsx("span", { className: "w-6 shrink-0", "aria-hidden": "true" }),
7883
+ Array.from({ length: columnCount }, (_, c) => /* @__PURE__ */ jsxs("div", { className: "flex min-w-[120px] flex-1 items-center gap-1", children: [
7884
+ renderColumnControl ? renderColumnControl(c) : /* @__PURE__ */ jsxs("span", { className: "flex-1 truncate px-1.5 text-[11px] text-muted-foreground", children: [
7885
+ "Column ",
7886
+ c + 1
7887
+ ] }),
7888
+ /* @__PURE__ */ jsx(
7889
+ "button",
7890
+ {
7891
+ type: "button",
7892
+ "aria-label": `Remove column ${c + 1}`,
7893
+ onClick: () => removeColumn(c),
7894
+ disabled: columnCount <= 1,
7895
+ className: REMOVE_BUTTON_CLASS,
7896
+ children: /* @__PURE__ */ jsx(Trash2, { className: "h-3 w-3" })
7897
+ }
7898
+ )
7899
+ ] }, c))
7900
+ ] }),
7901
+ rows.map((row, r) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
7902
+ lockFirstRow && r === 0 ? /* @__PURE__ */ jsx("span", { className: "w-6 shrink-0", "aria-hidden": "true" }) : /* @__PURE__ */ jsx(
7903
+ "button",
7904
+ {
7905
+ type: "button",
7906
+ "aria-label": `Remove row ${r + 1}`,
7907
+ onClick: () => removeRow(r),
7908
+ disabled: rows.length <= minRows,
7909
+ className: REMOVE_BUTTON_CLASS,
7910
+ children: /* @__PURE__ */ jsx(Trash2, { className: "h-3 w-3" })
7911
+ }
7912
+ ),
7913
+ row.map((cell, c) => /* @__PURE__ */ jsx("div", { className: "flex min-w-[120px] flex-1 flex-col gap-0.5", children: renderCell(
7914
+ cell,
7915
+ { row: r, col: c, label: `Row ${r + 1}, column ${c + 1}` },
7916
+ (next) => setCell(r, c, next)
7917
+ ) }, c))
7918
+ ] }, r))
7919
+ ] }) }),
7920
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
7921
+ /* @__PURE__ */ jsxs(Button, { type: "button", size: "sm", onClick: addRow, disabled: rows.length >= maxRows, children: [
7922
+ /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" }),
7923
+ " Add row"
7924
+ ] }),
7925
+ /* @__PURE__ */ jsxs(Button, { type: "button", size: "sm", onClick: addColumn, disabled: columnCount >= maxColumns, children: [
7926
+ /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" }),
7927
+ " Add column"
7928
+ ] })
7929
+ ] })
7930
+ ] });
7931
+ }
7932
+ function CellTextInput({
7933
+ cell,
7934
+ label,
7935
+ placeholder,
7936
+ leading,
7937
+ richTextNote = "Was rich-text; editing replaces with plain text.",
7938
+ onChange
7939
+ }) {
7940
+ const input = /* @__PURE__ */ jsx(
7941
+ Input,
7942
+ {
7943
+ "aria-label": label,
7944
+ value: cell.type === "raw_text" ? cell.text : "[rich_text]",
7945
+ placeholder,
7946
+ onChange: (e) => onChange({ type: "raw_text", text: e.target.value }),
7947
+ className: "h-8 text-xs"
7948
+ }
7949
+ );
7950
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
7951
+ leading ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
7952
+ leading,
7953
+ input
7954
+ ] }) : input,
7955
+ cell.type === "rich_text" ? /* @__PURE__ */ jsx("span", { className: cn("text-[10px] text-muted-foreground", leading && "pl-7"), children: richTextNote }) : null
7956
+ ] });
7957
+ }
7958
+ function NumberInput({
7959
+ id,
7960
+ value,
7961
+ onCommit,
7962
+ ariaLabel,
7963
+ className
7964
+ }) {
7965
+ const [draft, setDraft] = useState(String(value));
7966
+ useEffect(() => {
7967
+ setDraft(String(value));
7968
+ }, [value]);
7969
+ return /* @__PURE__ */ jsx(
7970
+ Input,
7971
+ {
7972
+ id,
7973
+ type: "number",
7974
+ inputMode: "numeric",
7975
+ "aria-label": ariaLabel,
7976
+ value: draft,
7977
+ className,
7978
+ onChange: (e) => {
7979
+ const raw = e.target.value;
7980
+ setDraft(raw);
7981
+ if (raw === "" || raw === "-" || raw === "." || raw === "-.") {
7982
+ return;
7983
+ }
7984
+ const next = Number(raw);
7985
+ if (!Number.isNaN(next)) {
7986
+ onCommit(next);
7987
+ }
7988
+ },
7989
+ onBlur: () => {
7990
+ const next = Number(draft);
7991
+ const safe = draft.trim() === "" || Number.isNaN(next) ? 0 : next;
7992
+ onCommit(safe);
7993
+ setDraft(String(safe));
7994
+ }
7995
+ }
7996
+ );
7997
+ }
7998
+ var MAX_ROWS = 201;
7999
+ var MIN_ROWS = 2;
8000
+ var MAX_COLUMNS = 20;
8001
+ var MAX_PAGE_SIZE = 100;
8002
+ var SELECT_CLASS = "flex h-9 w-full rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50";
8003
+ function withOptional(block, key, value) {
8004
+ const next = { ...block };
8005
+ if (value === void 0) {
8006
+ delete next[key];
8007
+ } else {
8008
+ next[key] = value;
8009
+ }
8010
+ return next;
8011
+ }
8012
+ function realignRowHeader(index, edit) {
8013
+ if (index === void 0 || edit.kind === "add" || edit.index > index) {
8014
+ return index;
8015
+ }
8016
+ return edit.index === index ? void 0 : index - 1;
8017
+ }
8018
+ function cellText(cell) {
8019
+ switch (cell?.type) {
8020
+ case "raw_text":
8021
+ return cell.text;
8022
+ case "raw_number":
8023
+ return cell.text ?? String(cell.value);
8024
+ case "action_cell":
8025
+ return cell.element.text.text;
8026
+ default:
8027
+ return "";
8028
+ }
8029
+ }
8030
+ function toNumberCell(cell) {
8031
+ const text = cell.text.trim();
8032
+ const parsed = text === "" ? Number.NaN : Number(text.replace(/,/g, ""));
8033
+ if (Number.isFinite(parsed)) {
8034
+ return String(parsed) === text ? { type: "raw_number", value: parsed } : { type: "raw_number", value: parsed, text };
8035
+ }
8036
+ return text ? { type: "raw_number", value: 0, text } : { type: "raw_number", value: 0 };
8037
+ }
8038
+ function toTextCell(cell) {
8039
+ return { type: "raw_text", text: cell.text ?? String(cell.value) };
8040
+ }
8041
+ function DataTableEditor({ block, onChange }) {
8042
+ const rows = block.rows ?? [];
8043
+ const header = rows[0] ?? [];
8044
+ const rowHeader = block.row_header_column_index ?? 0;
8045
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
8046
+ /* @__PURE__ */ jsx(EditorField, { label: "Caption", help: "Required. Names the table for screen readers.", htmlFor: "data-table-caption", children: /* @__PURE__ */ jsx(
8047
+ Input,
8048
+ {
8049
+ id: "data-table-caption",
8050
+ value: block.caption,
8051
+ placeholder: "e.g. Open deals by stage",
8052
+ onChange: (e) => onChange({ ...block, caption: e.target.value })
8053
+ }
8054
+ ) }),
8055
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
8056
+ /* @__PURE__ */ jsx(
8057
+ EditorField,
8058
+ {
8059
+ label: "Rows per page",
8060
+ help: "Optional, 1-100. Slack shows 5 when empty.",
8061
+ htmlFor: "data-table-page-size",
8062
+ children: /* @__PURE__ */ jsx(
8063
+ Input,
8064
+ {
8065
+ id: "data-table-page-size",
8066
+ type: "number",
8067
+ min: 1,
8068
+ max: MAX_PAGE_SIZE,
8069
+ value: block.page_size ?? "",
8070
+ placeholder: "5",
8071
+ onChange: (e) => {
8072
+ const n = Math.trunc(Number(e.target.value));
8073
+ const next = e.target.value === "" || Number.isNaN(n) ? void 0 : Math.min(MAX_PAGE_SIZE, Math.max(1, n));
8074
+ onChange(withOptional(block, "page_size", next));
8075
+ }
8076
+ }
8077
+ )
8078
+ }
8079
+ ),
8080
+ /* @__PURE__ */ jsx(
8081
+ EditorField,
8082
+ {
8083
+ label: "Row header column",
8084
+ help: "The column that identifies each row for screen readers.",
8085
+ htmlFor: "data-table-row-header",
8086
+ children: /* @__PURE__ */ jsxs(
8087
+ "select",
8088
+ {
8089
+ id: "data-table-row-header",
8090
+ value: rowHeader,
8091
+ disabled: header.length === 0,
8092
+ onChange: (e) => {
8093
+ const index = Number(e.target.value);
8094
+ onChange(withOptional(block, "row_header_column_index", index === 0 ? void 0 : index));
8095
+ },
8096
+ className: SELECT_CLASS,
8097
+ children: [
8098
+ header.map((cell, c) => {
8099
+ const text = cellText(cell);
8100
+ return /* @__PURE__ */ jsx("option", { value: c, children: text ? `Column ${c + 1}: ${text}` : `Column ${c + 1}` }, c);
8101
+ }),
8102
+ rowHeader >= header.length ? /* @__PURE__ */ jsxs("option", { value: rowHeader, children: [
8103
+ "Column ",
8104
+ rowHeader + 1,
8105
+ " (missing)"
8106
+ ] }) : null
8107
+ ]
8108
+ }
8109
+ )
8110
+ }
8111
+ )
8112
+ ] }),
8113
+ /* @__PURE__ */ jsx(
8114
+ CellGrid,
8115
+ {
8116
+ label: "Cells",
8117
+ help: "The first row is the header. Up to 200 more rows and 20 columns. Use # to make a cell a number; a column of only numbers sorts numerically.",
8118
+ rows,
8119
+ minRows: MIN_ROWS,
8120
+ maxRows: MAX_ROWS,
8121
+ maxColumns: MAX_COLUMNS,
8122
+ lockFirstRow: true,
8123
+ blankCell: () => ({ type: "raw_text", text: "" }),
8124
+ onChange: (nextRows, edit) => {
8125
+ const next = { ...block, rows: nextRows };
8126
+ onChange(
8127
+ edit ? withOptional(next, "row_header_column_index", realignRowHeader(block.row_header_column_index, edit)) : next
8128
+ );
8129
+ },
8130
+ renderCell: (cell, position, update) => /* @__PURE__ */ jsx(DataTableCellField, { cell, position, onChange: update })
8131
+ }
8132
+ )
8133
+ ] });
8134
+ }
8135
+ function DataTableCellField({
8136
+ cell,
8137
+ position,
8138
+ onChange
8139
+ }) {
8140
+ const { row, col, label } = position;
8141
+ if (cell.type === "action_cell") {
8142
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
8143
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
8144
+ /* @__PURE__ */ jsx(ToggleSpacer, {}),
8145
+ /* @__PURE__ */ jsxs("div", { className: "flex h-8 min-w-0 flex-1 items-center gap-1.5 rounded-md border border-dashed px-2 text-xs text-muted-foreground", children: [
8146
+ /* @__PURE__ */ jsx(MousePointerClick, { className: "h-3 w-3 shrink-0", "aria-hidden": "true" }),
8147
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: cellText(cell) || "Button" })
8148
+ ] })
8149
+ ] }),
8150
+ /* @__PURE__ */ jsx("span", { className: "pl-7 text-[10px] text-muted-foreground", children: "Button cell. Edit it via View JSON." })
8151
+ ] });
8152
+ }
8153
+ if (cell.type === "raw_number") {
8154
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
8155
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
8156
+ /* @__PURE__ */ jsx(NumberToggle, { label, pressed: true, onClick: () => onChange(toTextCell(cell)) }),
8157
+ /* @__PURE__ */ jsx(
8158
+ NumberInput,
8159
+ {
8160
+ value: cell.value,
8161
+ ariaLabel: `${label} value`,
8162
+ onCommit: (value) => onChange({ ...cell, value }),
8163
+ className: "h-8 text-xs"
8164
+ }
8165
+ )
8166
+ ] }),
8167
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
8168
+ /* @__PURE__ */ jsx(ToggleSpacer, {}),
8169
+ /* @__PURE__ */ jsx(
8170
+ Input,
8171
+ {
8172
+ "aria-label": `${label} display text`,
8173
+ value: cell.text ?? "",
8174
+ placeholder: "Display as (optional)",
8175
+ onChange: (e) => {
8176
+ const { text: _omit, ...rest } = cell;
8177
+ onChange(e.target.value ? { ...rest, text: e.target.value } : rest);
8178
+ },
8179
+ className: "h-7 text-[11px]"
8180
+ }
8181
+ )
8182
+ ] })
8183
+ ] });
8184
+ }
8185
+ const isHeader = row === 0;
8186
+ return /* @__PURE__ */ jsx(
8187
+ CellTextInput,
8188
+ {
8189
+ cell,
8190
+ label,
8191
+ placeholder: isHeader ? `Header ${col + 1}` : "Cell text",
8192
+ richTextNote: isHeader ? "Header cells can't be rich text. Type to replace it." : void 0,
8193
+ leading: !isHeader && cell.type === "raw_text" ? /* @__PURE__ */ jsx(NumberToggle, { label, pressed: false, onClick: () => onChange(toNumberCell(cell)) }) : /* @__PURE__ */ jsx(ToggleSpacer, {}),
8194
+ onChange
8195
+ }
8196
+ );
8197
+ }
8198
+ function ToggleSpacer() {
8199
+ return /* @__PURE__ */ jsx("span", { className: "h-6 w-6 shrink-0", "aria-hidden": "true" });
8200
+ }
8201
+ function NumberToggle({ label, pressed, onClick }) {
8202
+ return /* @__PURE__ */ jsx(
8203
+ "button",
8204
+ {
8205
+ type: "button",
8206
+ "aria-label": `${label} as number`,
8207
+ "aria-pressed": pressed,
8208
+ title: pressed ? "Number cell. Click to make it text." : "Text cell. Click to make it a number.",
8209
+ onClick,
8210
+ className: cn(
8211
+ "flex h-6 w-6 shrink-0 items-center justify-center rounded transition-colors",
8212
+ pressed ? "bg-primary/10 text-foreground" : "text-muted-foreground hover:bg-accent hover:text-foreground"
8213
+ ),
8214
+ children: /* @__PURE__ */ jsx(Hash, { className: "h-3 w-3" })
8215
+ }
8216
+ );
8217
+ }
7783
8218
  var CHART_TYPES = ["line", "bar", "area", "pie"];
7784
8219
  function isCartesian(chart) {
7785
8220
  return chart.type !== "pie";
@@ -7834,46 +8269,6 @@ function DataVisualizationEditor({ block, onChange }) {
7834
8269
  isCartesian(chart) ? /* @__PURE__ */ jsx(CartesianFields, { chart, onChange: setChart }) : /* @__PURE__ */ jsx(PieFields, { chart, onChange: setChart })
7835
8270
  ] });
7836
8271
  }
7837
- function NumberInput({
7838
- id,
7839
- value,
7840
- onCommit,
7841
- ariaLabel,
7842
- className
7843
- }) {
7844
- const [draft, setDraft] = useState(String(value));
7845
- useEffect(() => {
7846
- setDraft(String(value));
7847
- }, [value]);
7848
- return /* @__PURE__ */ jsx(
7849
- Input,
7850
- {
7851
- id,
7852
- type: "number",
7853
- inputMode: "numeric",
7854
- "aria-label": ariaLabel,
7855
- value: draft,
7856
- className,
7857
- onChange: (e) => {
7858
- const raw = e.target.value;
7859
- setDraft(raw);
7860
- if (raw === "" || raw === "-" || raw === "." || raw === "-.") {
7861
- return;
7862
- }
7863
- const next = Number(raw);
7864
- if (!Number.isNaN(next)) {
7865
- onCommit(next);
7866
- }
7867
- },
7868
- onBlur: () => {
7869
- const next = Number(draft);
7870
- const safe = draft.trim() === "" || Number.isNaN(next) ? 0 : next;
7871
- onCommit(safe);
7872
- setDraft(String(safe));
7873
- }
7874
- }
7875
- );
7876
- }
7877
8272
  function CartesianFields({ chart, onChange }) {
7878
8273
  const series = chart.series;
7879
8274
  const categories = chart.axis_config?.categories && chart.axis_config.categories.length > 0 ? chart.axis_config.categories : series[0]?.data.map((d) => d.label) ?? [];
@@ -9416,45 +9811,13 @@ function ImageAccessoryFields({ image, onChange }) {
9416
9811
  ] });
9417
9812
  }
9418
9813
  var ALIGN_OPTIONS = ["left", "center", "right"];
9419
- function cellAsText(cell) {
9420
- if (cell.type === "raw_text") {
9421
- return cell.text;
9422
- }
9423
- return "[rich_text]";
9814
+ function alignColumnSettings(settings, edit) {
9815
+ return edit.kind === "add" ? [...settings, null] : settings.filter((_, c) => c !== edit.index);
9424
9816
  }
9425
9817
  function TableEditor({ block, onChange }) {
9426
9818
  const rows = block.rows ?? [];
9427
9819
  const columnCount = rows[0]?.length ?? 0;
9428
9820
  const columnSettings = block.column_settings ?? [];
9429
- const setCell = (rowIdx, colIdx, text) => {
9430
- const nextRows = rows.map(
9431
- (row, r) => r === rowIdx ? row.map((cell, c) => c === colIdx ? { type: "raw_text", text } : cell) : row
9432
- );
9433
- onChange({ ...block, rows: nextRows });
9434
- };
9435
- const addRow = () => {
9436
- const blankRow = Array.from({ length: columnCount || 1 }, () => ({ type: "raw_text", text: "" }));
9437
- onChange({ ...block, rows: [...rows, blankRow] });
9438
- };
9439
- const removeRow = (rowIdx) => {
9440
- if (rows.length <= 1) {
9441
- return;
9442
- }
9443
- onChange({ ...block, rows: rows.filter((_, r) => r !== rowIdx) });
9444
- };
9445
- const addColumn = () => {
9446
- const nextRows = rows.map((row) => [...row, { type: "raw_text", text: "" }]);
9447
- const nextSettings = [...columnSettings, null];
9448
- onChange({ ...block, rows: nextRows, column_settings: nextSettings });
9449
- };
9450
- const removeColumn = (colIdx) => {
9451
- if (columnCount <= 1) {
9452
- return;
9453
- }
9454
- const nextRows = rows.map((row) => row.filter((_, c) => c !== colIdx));
9455
- const nextSettings = columnSettings.filter((_, c) => c !== colIdx);
9456
- onChange({ ...block, rows: nextRows, column_settings: nextSettings });
9457
- };
9458
9821
  const setColumnAlign = (colIdx, align) => {
9459
9822
  const next = Array.from({ length: columnCount }, (_, i) => columnSettings[i] ?? null);
9460
9823
  if (align === "") {
@@ -9465,81 +9828,45 @@ function TableEditor({ block, onChange }) {
9465
9828
  }
9466
9829
  onChange({ ...block, column_settings: next });
9467
9830
  };
9468
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
9469
- /* @__PURE__ */ jsx(EditorField, { label: "Cells", help: "Up to 100 rows and 20 columns. Slack allows one table per message.", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 overflow-x-auto", children: [
9470
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
9471
- /* @__PURE__ */ jsx("span", { className: "w-6 shrink-0", "aria-hidden": "true" }),
9472
- Array.from({ length: columnCount }, (_, c) => {
9473
- const setting = columnSettings[c];
9474
- const align = setting?.align ?? "";
9475
- return /* @__PURE__ */ jsxs("div", { className: "flex min-w-[120px] flex-1 items-center gap-1", children: [
9476
- /* @__PURE__ */ jsxs(
9477
- "select",
9478
- {
9479
- "aria-label": `Column ${c + 1} alignment`,
9480
- value: align,
9481
- onChange: (e) => setColumnAlign(c, e.target.value),
9482
- className: "h-7 flex-1 rounded-sm border border-input bg-background px-1.5 text-[11px] text-muted-foreground",
9483
- children: [
9484
- /* @__PURE__ */ jsx("option", { value: "", children: "Default" }),
9485
- ALIGN_OPTIONS.map((a) => /* @__PURE__ */ jsx("option", { value: a, children: a }, a))
9486
- ]
9487
- }
9488
- ),
9489
- /* @__PURE__ */ jsx(
9490
- "button",
9491
- {
9492
- type: "button",
9493
- "aria-label": `Remove column ${c + 1}`,
9494
- onClick: () => removeColumn(c),
9495
- disabled: columnCount <= 1,
9496
- className: "flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:bg-destructive/10 hover:text-destructive disabled:cursor-not-allowed disabled:opacity-40",
9497
- children: /* @__PURE__ */ jsx(Trash2, { className: "h-3 w-3" })
9498
- }
9499
- )
9500
- ] }, c);
9501
- })
9502
- ] }),
9503
- rows.map((row, r) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
9504
- /* @__PURE__ */ jsx(
9505
- "button",
9506
- {
9507
- type: "button",
9508
- "aria-label": `Remove row ${r + 1}`,
9509
- onClick: () => removeRow(r),
9510
- disabled: rows.length <= 1,
9511
- className: "flex h-6 w-6 shrink-0 items-center justify-center rounded text-muted-foreground hover:bg-destructive/10 hover:text-destructive disabled:cursor-not-allowed disabled:opacity-40",
9512
- children: /* @__PURE__ */ jsx(Trash2, { className: "h-3 w-3" })
9513
- }
9514
- ),
9515
- row.map((cell, c) => {
9516
- const isRichText = cell.type === "rich_text";
9517
- return /* @__PURE__ */ jsxs("div", { className: "flex min-w-[120px] flex-1 flex-col gap-0.5", children: [
9518
- /* @__PURE__ */ jsx(
9519
- Input,
9520
- {
9521
- value: cellAsText(cell),
9522
- placeholder: r === 0 ? `Header ${c + 1}` : "Cell text",
9523
- onChange: (e) => setCell(r, c, e.target.value),
9524
- className: "h-8 text-xs"
9525
- }
9526
- ),
9527
- isRichText ? /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground", children: "Was rich-text; editing replaces with plain text." }) : null
9528
- ] }, c);
9529
- })
9530
- ] }, r))
9531
- ] }) }),
9532
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
9533
- /* @__PURE__ */ jsxs(Button, { type: "button", size: "sm", onClick: addRow, disabled: rows.length >= 100, children: [
9534
- /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" }),
9535
- " Add row"
9536
- ] }),
9537
- /* @__PURE__ */ jsxs(Button, { type: "button", size: "sm", onClick: addColumn, disabled: columnCount >= 20, children: [
9538
- /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" }),
9539
- " Add column"
9540
- ] })
9541
- ] })
9542
- ] });
9831
+ return /* @__PURE__ */ jsx(
9832
+ CellGrid,
9833
+ {
9834
+ label: "Cells",
9835
+ help: "Up to 100 rows and 20 columns. Slack allows one table per message.",
9836
+ rows,
9837
+ minRows: 1,
9838
+ maxRows: 100,
9839
+ maxColumns: 20,
9840
+ blankCell: () => ({ type: "raw_text", text: "" }),
9841
+ onChange: (nextRows, edit) => onChange({
9842
+ ...block,
9843
+ rows: nextRows,
9844
+ ...edit ? { column_settings: alignColumnSettings(columnSettings, edit) } : {}
9845
+ }),
9846
+ renderColumnControl: (c) => /* @__PURE__ */ jsxs(
9847
+ "select",
9848
+ {
9849
+ "aria-label": `Column ${c + 1} alignment`,
9850
+ value: columnSettings[c]?.align ?? "",
9851
+ onChange: (e) => setColumnAlign(c, e.target.value),
9852
+ className: "h-7 flex-1 rounded-sm border border-input bg-background px-1.5 text-[11px] text-muted-foreground",
9853
+ children: [
9854
+ /* @__PURE__ */ jsx("option", { value: "", children: "Default" }),
9855
+ ALIGN_OPTIONS.map((a) => /* @__PURE__ */ jsx("option", { value: a, children: a }, a))
9856
+ ]
9857
+ }
9858
+ ),
9859
+ renderCell: (cell, { row, col, label }, update) => /* @__PURE__ */ jsx(
9860
+ CellTextInput,
9861
+ {
9862
+ cell,
9863
+ label,
9864
+ placeholder: row === 0 ? `Header ${col + 1}` : "Cell text",
9865
+ onChange: update
9866
+ }
9867
+ )
9868
+ }
9869
+ );
9543
9870
  }
9544
9871
  var STATUSES2 = ["pending", "in_progress", "complete", "error"];
9545
9872
  function TaskCardEditor({ block, onChange }) {
@@ -9880,6 +10207,8 @@ function dispatch(block, onChange) {
9880
10207
  return /* @__PURE__ */ jsx(DataVisualizationEditor, { block, onChange: (next) => onChange(next) });
9881
10208
  case "container":
9882
10209
  return /* @__PURE__ */ jsx(ContainerEditor, { block, onChange: (next) => onChange(next) });
10210
+ case "data_table":
10211
+ return /* @__PURE__ */ jsx(DataTableEditor, { block, onChange: (next) => onChange(next) });
9883
10212
  default:
9884
10213
  return null;
9885
10214
  }
@@ -9903,7 +10232,8 @@ var BLOCK_TYPE_LABELS = {
9903
10232
  plan: "Plan",
9904
10233
  task_card: "Task Card",
9905
10234
  data_visualization: "Data Visualization",
9906
- container: "Container"
10235
+ container: "Container",
10236
+ data_table: "Data Table"
9907
10237
  };
9908
10238
  function BlockRow({
9909
10239
  builderBlock,