@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.cjs CHANGED
@@ -1096,6 +1096,58 @@ var defaultPalette = [
1096
1096
  ]
1097
1097
  ]
1098
1098
  })
1099
+ },
1100
+ {
1101
+ // Slack's documented example.
1102
+ id: "data_table_basic",
1103
+ label: "Data table",
1104
+ factory: () => ({
1105
+ type: "data_table",
1106
+ caption: "A Fabulous Table",
1107
+ rows: [
1108
+ [
1109
+ { type: "raw_text", text: "Name" },
1110
+ { type: "raw_text", text: "Department" },
1111
+ { type: "raw_text", text: "Badge" }
1112
+ ],
1113
+ [
1114
+ { type: "raw_text", text: "Data Refinement Department" },
1115
+ { type: "raw_text", text: "MDR" },
1116
+ {
1117
+ type: "rich_text",
1118
+ elements: [
1119
+ { type: "rich_text_section", elements: [{ type: "text", text: "Blue", style: { bold: true } }] }
1120
+ ]
1121
+ }
1122
+ ],
1123
+ [
1124
+ { type: "raw_text", text: "Art Sourcing Department" },
1125
+ { type: "raw_text", text: "O&D" },
1126
+ {
1127
+ type: "rich_text",
1128
+ elements: [
1129
+ {
1130
+ type: "rich_text_section",
1131
+ elements: [
1132
+ { type: "text", text: "Green " },
1133
+ { type: "text", text: "review", style: { italic: true } }
1134
+ ]
1135
+ }
1136
+ ]
1137
+ }
1138
+ ],
1139
+ [
1140
+ { type: "raw_text", text: "Wellness Department" },
1141
+ { type: "raw_text", text: "Wellness Center" },
1142
+ {
1143
+ type: "rich_text",
1144
+ elements: [
1145
+ { type: "rich_text_section", elements: [{ type: "text", text: "Limited", style: { bold: true } }] }
1146
+ ]
1147
+ }
1148
+ ]
1149
+ ]
1150
+ })
1099
1151
  }
1100
1152
  ]
1101
1153
  },
@@ -1760,6 +1812,8 @@ function labelForBlockType(type) {
1760
1812
  return "Data Visualization";
1761
1813
  case "container":
1762
1814
  return "Container";
1815
+ case "data_table":
1816
+ return "Data Table";
1763
1817
  }
1764
1818
  }
1765
1819
  var Sheet = DialogPrimitive__namespace.Root;
@@ -7810,6 +7864,387 @@ function ImageItemCard({
7810
7864
  ) })
7811
7865
  ] });
7812
7866
  }
7867
+ 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";
7868
+ function CellGrid({
7869
+ label,
7870
+ help,
7871
+ rows,
7872
+ minRows,
7873
+ maxRows,
7874
+ maxColumns,
7875
+ lockFirstRow = false,
7876
+ blankCell,
7877
+ renderCell,
7878
+ renderColumnControl,
7879
+ onChange
7880
+ }) {
7881
+ const columnCount = rows[0]?.length ?? 0;
7882
+ const setCell = (rowIdx, colIdx, next) => {
7883
+ onChange(rows.map((row, r) => r === rowIdx ? row.map((cell, c) => c === colIdx ? next : cell) : row));
7884
+ };
7885
+ const addRow = () => {
7886
+ onChange([...rows, Array.from({ length: columnCount || 1 }, blankCell)]);
7887
+ };
7888
+ const removeRow = (rowIdx) => {
7889
+ if (rows.length <= minRows) {
7890
+ return;
7891
+ }
7892
+ onChange(rows.filter((_, r) => r !== rowIdx));
7893
+ };
7894
+ const addColumn = () => {
7895
+ onChange(
7896
+ rows.map((row) => [...row, blankCell()]),
7897
+ { kind: "add" }
7898
+ );
7899
+ };
7900
+ const removeColumn = (colIdx) => {
7901
+ if (columnCount <= 1) {
7902
+ return;
7903
+ }
7904
+ onChange(
7905
+ rows.map((row) => row.filter((_, c) => c !== colIdx)),
7906
+ { kind: "remove", index: colIdx }
7907
+ );
7908
+ };
7909
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3", children: [
7910
+ /* @__PURE__ */ jsxRuntime.jsx(EditorField, { label, help, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 overflow-x-auto", children: [
7911
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
7912
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-6 shrink-0", "aria-hidden": "true" }),
7913
+ Array.from({ length: columnCount }, (_, c) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-[120px] flex-1 items-center gap-1", children: [
7914
+ renderColumnControl ? renderColumnControl(c) : /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex-1 truncate px-1.5 text-[11px] text-muted-foreground", children: [
7915
+ "Column ",
7916
+ c + 1
7917
+ ] }),
7918
+ /* @__PURE__ */ jsxRuntime.jsx(
7919
+ "button",
7920
+ {
7921
+ type: "button",
7922
+ "aria-label": `Remove column ${c + 1}`,
7923
+ onClick: () => removeColumn(c),
7924
+ disabled: columnCount <= 1,
7925
+ className: REMOVE_BUTTON_CLASS,
7926
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-3 w-3" })
7927
+ }
7928
+ )
7929
+ ] }, c))
7930
+ ] }),
7931
+ rows.map((row, r) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
7932
+ lockFirstRow && r === 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-6 shrink-0", "aria-hidden": "true" }) : /* @__PURE__ */ jsxRuntime.jsx(
7933
+ "button",
7934
+ {
7935
+ type: "button",
7936
+ "aria-label": `Remove row ${r + 1}`,
7937
+ onClick: () => removeRow(r),
7938
+ disabled: rows.length <= minRows,
7939
+ className: REMOVE_BUTTON_CLASS,
7940
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-3 w-3" })
7941
+ }
7942
+ ),
7943
+ row.map((cell, c) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-w-[120px] flex-1 flex-col gap-0.5", children: renderCell(
7944
+ cell,
7945
+ { row: r, col: c, label: `Row ${r + 1}, column ${c + 1}` },
7946
+ (next) => setCell(r, c, next)
7947
+ ) }, c))
7948
+ ] }, r))
7949
+ ] }) }),
7950
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
7951
+ /* @__PURE__ */ jsxRuntime.jsxs(Button, { type: "button", size: "sm", onClick: addRow, disabled: rows.length >= maxRows, children: [
7952
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-3.5 w-3.5" }),
7953
+ " Add row"
7954
+ ] }),
7955
+ /* @__PURE__ */ jsxRuntime.jsxs(Button, { type: "button", size: "sm", onClick: addColumn, disabled: columnCount >= maxColumns, children: [
7956
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-3.5 w-3.5" }),
7957
+ " Add column"
7958
+ ] })
7959
+ ] })
7960
+ ] });
7961
+ }
7962
+ function CellTextInput({
7963
+ cell,
7964
+ label,
7965
+ placeholder,
7966
+ leading,
7967
+ richTextNote = "Was rich-text; editing replaces with plain text.",
7968
+ onChange
7969
+ }) {
7970
+ const input = /* @__PURE__ */ jsxRuntime.jsx(
7971
+ Input,
7972
+ {
7973
+ "aria-label": label,
7974
+ value: cell.type === "raw_text" ? cell.text : "[rich_text]",
7975
+ placeholder,
7976
+ onChange: (e) => onChange({ type: "raw_text", text: e.target.value }),
7977
+ className: "h-8 text-xs"
7978
+ }
7979
+ );
7980
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
7981
+ leading ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
7982
+ leading,
7983
+ input
7984
+ ] }) : input,
7985
+ cell.type === "rich_text" ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("text-[10px] text-muted-foreground", leading && "pl-7"), children: richTextNote }) : null
7986
+ ] });
7987
+ }
7988
+ function NumberInput({
7989
+ id,
7990
+ value,
7991
+ onCommit,
7992
+ ariaLabel,
7993
+ className
7994
+ }) {
7995
+ const [draft, setDraft] = React.useState(String(value));
7996
+ React.useEffect(() => {
7997
+ setDraft(String(value));
7998
+ }, [value]);
7999
+ return /* @__PURE__ */ jsxRuntime.jsx(
8000
+ Input,
8001
+ {
8002
+ id,
8003
+ type: "number",
8004
+ inputMode: "numeric",
8005
+ "aria-label": ariaLabel,
8006
+ value: draft,
8007
+ className,
8008
+ onChange: (e) => {
8009
+ const raw = e.target.value;
8010
+ setDraft(raw);
8011
+ if (raw === "" || raw === "-" || raw === "." || raw === "-.") {
8012
+ return;
8013
+ }
8014
+ const next = Number(raw);
8015
+ if (!Number.isNaN(next)) {
8016
+ onCommit(next);
8017
+ }
8018
+ },
8019
+ onBlur: () => {
8020
+ const next = Number(draft);
8021
+ const safe = draft.trim() === "" || Number.isNaN(next) ? 0 : next;
8022
+ onCommit(safe);
8023
+ setDraft(String(safe));
8024
+ }
8025
+ }
8026
+ );
8027
+ }
8028
+ var MAX_ROWS = 201;
8029
+ var MIN_ROWS = 2;
8030
+ var MAX_COLUMNS = 20;
8031
+ var MAX_PAGE_SIZE = 100;
8032
+ 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";
8033
+ function withOptional(block, key, value) {
8034
+ const next = { ...block };
8035
+ if (value === void 0) {
8036
+ delete next[key];
8037
+ } else {
8038
+ next[key] = value;
8039
+ }
8040
+ return next;
8041
+ }
8042
+ function realignRowHeader(index, edit) {
8043
+ if (index === void 0 || edit.kind === "add" || edit.index > index) {
8044
+ return index;
8045
+ }
8046
+ return edit.index === index ? void 0 : index - 1;
8047
+ }
8048
+ function cellText(cell) {
8049
+ switch (cell?.type) {
8050
+ case "raw_text":
8051
+ return cell.text;
8052
+ case "raw_number":
8053
+ return cell.text ?? String(cell.value);
8054
+ case "action_cell":
8055
+ return cell.element.text.text;
8056
+ default:
8057
+ return "";
8058
+ }
8059
+ }
8060
+ function toNumberCell(cell) {
8061
+ const text = cell.text.trim();
8062
+ const parsed = text === "" ? Number.NaN : Number(text.replace(/,/g, ""));
8063
+ if (Number.isFinite(parsed)) {
8064
+ return String(parsed) === text ? { type: "raw_number", value: parsed } : { type: "raw_number", value: parsed, text };
8065
+ }
8066
+ return text ? { type: "raw_number", value: 0, text } : { type: "raw_number", value: 0 };
8067
+ }
8068
+ function toTextCell(cell) {
8069
+ return { type: "raw_text", text: cell.text ?? String(cell.value) };
8070
+ }
8071
+ function DataTableEditor({ block, onChange }) {
8072
+ const rows = block.rows ?? [];
8073
+ const header = rows[0] ?? [];
8074
+ const rowHeader = block.row_header_column_index ?? 0;
8075
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4", children: [
8076
+ /* @__PURE__ */ jsxRuntime.jsx(EditorField, { label: "Caption", help: "Required. Names the table for screen readers.", htmlFor: "data-table-caption", children: /* @__PURE__ */ jsxRuntime.jsx(
8077
+ Input,
8078
+ {
8079
+ id: "data-table-caption",
8080
+ value: block.caption,
8081
+ placeholder: "e.g. Open deals by stage",
8082
+ onChange: (e) => onChange({ ...block, caption: e.target.value })
8083
+ }
8084
+ ) }),
8085
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
8086
+ /* @__PURE__ */ jsxRuntime.jsx(
8087
+ EditorField,
8088
+ {
8089
+ label: "Rows per page",
8090
+ help: "Optional, 1-100. Slack shows 5 when empty.",
8091
+ htmlFor: "data-table-page-size",
8092
+ children: /* @__PURE__ */ jsxRuntime.jsx(
8093
+ Input,
8094
+ {
8095
+ id: "data-table-page-size",
8096
+ type: "number",
8097
+ min: 1,
8098
+ max: MAX_PAGE_SIZE,
8099
+ value: block.page_size ?? "",
8100
+ placeholder: "5",
8101
+ onChange: (e) => {
8102
+ const n = Math.trunc(Number(e.target.value));
8103
+ const next = e.target.value === "" || Number.isNaN(n) ? void 0 : Math.min(MAX_PAGE_SIZE, Math.max(1, n));
8104
+ onChange(withOptional(block, "page_size", next));
8105
+ }
8106
+ }
8107
+ )
8108
+ }
8109
+ ),
8110
+ /* @__PURE__ */ jsxRuntime.jsx(
8111
+ EditorField,
8112
+ {
8113
+ label: "Row header column",
8114
+ help: "The column that identifies each row for screen readers.",
8115
+ htmlFor: "data-table-row-header",
8116
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
8117
+ "select",
8118
+ {
8119
+ id: "data-table-row-header",
8120
+ value: rowHeader,
8121
+ disabled: header.length === 0,
8122
+ onChange: (e) => {
8123
+ const index = Number(e.target.value);
8124
+ onChange(withOptional(block, "row_header_column_index", index === 0 ? void 0 : index));
8125
+ },
8126
+ className: SELECT_CLASS,
8127
+ children: [
8128
+ header.map((cell, c) => {
8129
+ const text = cellText(cell);
8130
+ return /* @__PURE__ */ jsxRuntime.jsx("option", { value: c, children: text ? `Column ${c + 1}: ${text}` : `Column ${c + 1}` }, c);
8131
+ }),
8132
+ rowHeader >= header.length ? /* @__PURE__ */ jsxRuntime.jsxs("option", { value: rowHeader, children: [
8133
+ "Column ",
8134
+ rowHeader + 1,
8135
+ " (missing)"
8136
+ ] }) : null
8137
+ ]
8138
+ }
8139
+ )
8140
+ }
8141
+ )
8142
+ ] }),
8143
+ /* @__PURE__ */ jsxRuntime.jsx(
8144
+ CellGrid,
8145
+ {
8146
+ label: "Cells",
8147
+ 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.",
8148
+ rows,
8149
+ minRows: MIN_ROWS,
8150
+ maxRows: MAX_ROWS,
8151
+ maxColumns: MAX_COLUMNS,
8152
+ lockFirstRow: true,
8153
+ blankCell: () => ({ type: "raw_text", text: "" }),
8154
+ onChange: (nextRows, edit) => {
8155
+ const next = { ...block, rows: nextRows };
8156
+ onChange(
8157
+ edit ? withOptional(next, "row_header_column_index", realignRowHeader(block.row_header_column_index, edit)) : next
8158
+ );
8159
+ },
8160
+ renderCell: (cell, position, update) => /* @__PURE__ */ jsxRuntime.jsx(DataTableCellField, { cell, position, onChange: update })
8161
+ }
8162
+ )
8163
+ ] });
8164
+ }
8165
+ function DataTableCellField({
8166
+ cell,
8167
+ position,
8168
+ onChange
8169
+ }) {
8170
+ const { row, col, label } = position;
8171
+ if (cell.type === "action_cell") {
8172
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
8173
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
8174
+ /* @__PURE__ */ jsxRuntime.jsx(ToggleSpacer, {}),
8175
+ /* @__PURE__ */ jsxRuntime.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: [
8176
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MousePointerClick, { className: "h-3 w-3 shrink-0", "aria-hidden": "true" }),
8177
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: cellText(cell) || "Button" })
8178
+ ] })
8179
+ ] }),
8180
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pl-7 text-[10px] text-muted-foreground", children: "Button cell. Edit it via View JSON." })
8181
+ ] });
8182
+ }
8183
+ if (cell.type === "raw_number") {
8184
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
8185
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
8186
+ /* @__PURE__ */ jsxRuntime.jsx(NumberToggle, { label, pressed: true, onClick: () => onChange(toTextCell(cell)) }),
8187
+ /* @__PURE__ */ jsxRuntime.jsx(
8188
+ NumberInput,
8189
+ {
8190
+ value: cell.value,
8191
+ ariaLabel: `${label} value`,
8192
+ onCommit: (value) => onChange({ ...cell, value }),
8193
+ className: "h-8 text-xs"
8194
+ }
8195
+ )
8196
+ ] }),
8197
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
8198
+ /* @__PURE__ */ jsxRuntime.jsx(ToggleSpacer, {}),
8199
+ /* @__PURE__ */ jsxRuntime.jsx(
8200
+ Input,
8201
+ {
8202
+ "aria-label": `${label} display text`,
8203
+ value: cell.text ?? "",
8204
+ placeholder: "Display as (optional)",
8205
+ onChange: (e) => {
8206
+ const { text: _omit, ...rest } = cell;
8207
+ onChange(e.target.value ? { ...rest, text: e.target.value } : rest);
8208
+ },
8209
+ className: "h-7 text-[11px]"
8210
+ }
8211
+ )
8212
+ ] })
8213
+ ] });
8214
+ }
8215
+ const isHeader = row === 0;
8216
+ return /* @__PURE__ */ jsxRuntime.jsx(
8217
+ CellTextInput,
8218
+ {
8219
+ cell,
8220
+ label,
8221
+ placeholder: isHeader ? `Header ${col + 1}` : "Cell text",
8222
+ richTextNote: isHeader ? "Header cells can't be rich text. Type to replace it." : void 0,
8223
+ leading: !isHeader && cell.type === "raw_text" ? /* @__PURE__ */ jsxRuntime.jsx(NumberToggle, { label, pressed: false, onClick: () => onChange(toNumberCell(cell)) }) : /* @__PURE__ */ jsxRuntime.jsx(ToggleSpacer, {}),
8224
+ onChange
8225
+ }
8226
+ );
8227
+ }
8228
+ function ToggleSpacer() {
8229
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-6 w-6 shrink-0", "aria-hidden": "true" });
8230
+ }
8231
+ function NumberToggle({ label, pressed, onClick }) {
8232
+ return /* @__PURE__ */ jsxRuntime.jsx(
8233
+ "button",
8234
+ {
8235
+ type: "button",
8236
+ "aria-label": `${label} as number`,
8237
+ "aria-pressed": pressed,
8238
+ title: pressed ? "Number cell. Click to make it text." : "Text cell. Click to make it a number.",
8239
+ onClick,
8240
+ className: cn(
8241
+ "flex h-6 w-6 shrink-0 items-center justify-center rounded transition-colors",
8242
+ pressed ? "bg-primary/10 text-foreground" : "text-muted-foreground hover:bg-accent hover:text-foreground"
8243
+ ),
8244
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Hash, { className: "h-3 w-3" })
8245
+ }
8246
+ );
8247
+ }
7813
8248
  var CHART_TYPES = ["line", "bar", "area", "pie"];
7814
8249
  function isCartesian(chart) {
7815
8250
  return chart.type !== "pie";
@@ -7864,46 +8299,6 @@ function DataVisualizationEditor({ block, onChange }) {
7864
8299
  isCartesian(chart) ? /* @__PURE__ */ jsxRuntime.jsx(CartesianFields, { chart, onChange: setChart }) : /* @__PURE__ */ jsxRuntime.jsx(PieFields, { chart, onChange: setChart })
7865
8300
  ] });
7866
8301
  }
7867
- function NumberInput({
7868
- id,
7869
- value,
7870
- onCommit,
7871
- ariaLabel,
7872
- className
7873
- }) {
7874
- const [draft, setDraft] = React.useState(String(value));
7875
- React.useEffect(() => {
7876
- setDraft(String(value));
7877
- }, [value]);
7878
- return /* @__PURE__ */ jsxRuntime.jsx(
7879
- Input,
7880
- {
7881
- id,
7882
- type: "number",
7883
- inputMode: "numeric",
7884
- "aria-label": ariaLabel,
7885
- value: draft,
7886
- className,
7887
- onChange: (e) => {
7888
- const raw = e.target.value;
7889
- setDraft(raw);
7890
- if (raw === "" || raw === "-" || raw === "." || raw === "-.") {
7891
- return;
7892
- }
7893
- const next = Number(raw);
7894
- if (!Number.isNaN(next)) {
7895
- onCommit(next);
7896
- }
7897
- },
7898
- onBlur: () => {
7899
- const next = Number(draft);
7900
- const safe = draft.trim() === "" || Number.isNaN(next) ? 0 : next;
7901
- onCommit(safe);
7902
- setDraft(String(safe));
7903
- }
7904
- }
7905
- );
7906
- }
7907
8302
  function CartesianFields({ chart, onChange }) {
7908
8303
  const series = chart.series;
7909
8304
  const categories = chart.axis_config?.categories && chart.axis_config.categories.length > 0 ? chart.axis_config.categories : series[0]?.data.map((d) => d.label) ?? [];
@@ -9446,45 +9841,13 @@ function ImageAccessoryFields({ image, onChange }) {
9446
9841
  ] });
9447
9842
  }
9448
9843
  var ALIGN_OPTIONS = ["left", "center", "right"];
9449
- function cellAsText(cell) {
9450
- if (cell.type === "raw_text") {
9451
- return cell.text;
9452
- }
9453
- return "[rich_text]";
9844
+ function alignColumnSettings(settings, edit) {
9845
+ return edit.kind === "add" ? [...settings, null] : settings.filter((_, c) => c !== edit.index);
9454
9846
  }
9455
9847
  function TableEditor({ block, onChange }) {
9456
9848
  const rows = block.rows ?? [];
9457
9849
  const columnCount = rows[0]?.length ?? 0;
9458
9850
  const columnSettings = block.column_settings ?? [];
9459
- const setCell = (rowIdx, colIdx, text) => {
9460
- const nextRows = rows.map(
9461
- (row, r) => r === rowIdx ? row.map((cell, c) => c === colIdx ? { type: "raw_text", text } : cell) : row
9462
- );
9463
- onChange({ ...block, rows: nextRows });
9464
- };
9465
- const addRow = () => {
9466
- const blankRow = Array.from({ length: columnCount || 1 }, () => ({ type: "raw_text", text: "" }));
9467
- onChange({ ...block, rows: [...rows, blankRow] });
9468
- };
9469
- const removeRow = (rowIdx) => {
9470
- if (rows.length <= 1) {
9471
- return;
9472
- }
9473
- onChange({ ...block, rows: rows.filter((_, r) => r !== rowIdx) });
9474
- };
9475
- const addColumn = () => {
9476
- const nextRows = rows.map((row) => [...row, { type: "raw_text", text: "" }]);
9477
- const nextSettings = [...columnSettings, null];
9478
- onChange({ ...block, rows: nextRows, column_settings: nextSettings });
9479
- };
9480
- const removeColumn = (colIdx) => {
9481
- if (columnCount <= 1) {
9482
- return;
9483
- }
9484
- const nextRows = rows.map((row) => row.filter((_, c) => c !== colIdx));
9485
- const nextSettings = columnSettings.filter((_, c) => c !== colIdx);
9486
- onChange({ ...block, rows: nextRows, column_settings: nextSettings });
9487
- };
9488
9851
  const setColumnAlign = (colIdx, align) => {
9489
9852
  const next = Array.from({ length: columnCount }, (_, i) => columnSettings[i] ?? null);
9490
9853
  if (align === "") {
@@ -9495,81 +9858,45 @@ function TableEditor({ block, onChange }) {
9495
9858
  }
9496
9859
  onChange({ ...block, column_settings: next });
9497
9860
  };
9498
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3", children: [
9499
- /* @__PURE__ */ jsxRuntime.jsx(EditorField, { label: "Cells", help: "Up to 100 rows and 20 columns. Slack allows one table per message.", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 overflow-x-auto", children: [
9500
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
9501
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-6 shrink-0", "aria-hidden": "true" }),
9502
- Array.from({ length: columnCount }, (_, c) => {
9503
- const setting = columnSettings[c];
9504
- const align = setting?.align ?? "";
9505
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-[120px] flex-1 items-center gap-1", children: [
9506
- /* @__PURE__ */ jsxRuntime.jsxs(
9507
- "select",
9508
- {
9509
- "aria-label": `Column ${c + 1} alignment`,
9510
- value: align,
9511
- onChange: (e) => setColumnAlign(c, e.target.value),
9512
- className: "h-7 flex-1 rounded-sm border border-input bg-background px-1.5 text-[11px] text-muted-foreground",
9513
- children: [
9514
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "Default" }),
9515
- ALIGN_OPTIONS.map((a) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: a, children: a }, a))
9516
- ]
9517
- }
9518
- ),
9519
- /* @__PURE__ */ jsxRuntime.jsx(
9520
- "button",
9521
- {
9522
- type: "button",
9523
- "aria-label": `Remove column ${c + 1}`,
9524
- onClick: () => removeColumn(c),
9525
- disabled: columnCount <= 1,
9526
- 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",
9527
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-3 w-3" })
9528
- }
9529
- )
9530
- ] }, c);
9531
- })
9532
- ] }),
9533
- rows.map((row, r) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
9534
- /* @__PURE__ */ jsxRuntime.jsx(
9535
- "button",
9536
- {
9537
- type: "button",
9538
- "aria-label": `Remove row ${r + 1}`,
9539
- onClick: () => removeRow(r),
9540
- disabled: rows.length <= 1,
9541
- 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",
9542
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-3 w-3" })
9543
- }
9544
- ),
9545
- row.map((cell, c) => {
9546
- const isRichText = cell.type === "rich_text";
9547
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-[120px] flex-1 flex-col gap-0.5", children: [
9548
- /* @__PURE__ */ jsxRuntime.jsx(
9549
- Input,
9550
- {
9551
- value: cellAsText(cell),
9552
- placeholder: r === 0 ? `Header ${c + 1}` : "Cell text",
9553
- onChange: (e) => setCell(r, c, e.target.value),
9554
- className: "h-8 text-xs"
9555
- }
9556
- ),
9557
- isRichText ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-muted-foreground", children: "Was rich-text; editing replaces with plain text." }) : null
9558
- ] }, c);
9559
- })
9560
- ] }, r))
9561
- ] }) }),
9562
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
9563
- /* @__PURE__ */ jsxRuntime.jsxs(Button, { type: "button", size: "sm", onClick: addRow, disabled: rows.length >= 100, children: [
9564
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-3.5 w-3.5" }),
9565
- " Add row"
9566
- ] }),
9567
- /* @__PURE__ */ jsxRuntime.jsxs(Button, { type: "button", size: "sm", onClick: addColumn, disabled: columnCount >= 20, children: [
9568
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-3.5 w-3.5" }),
9569
- " Add column"
9570
- ] })
9571
- ] })
9572
- ] });
9861
+ return /* @__PURE__ */ jsxRuntime.jsx(
9862
+ CellGrid,
9863
+ {
9864
+ label: "Cells",
9865
+ help: "Up to 100 rows and 20 columns. Slack allows one table per message.",
9866
+ rows,
9867
+ minRows: 1,
9868
+ maxRows: 100,
9869
+ maxColumns: 20,
9870
+ blankCell: () => ({ type: "raw_text", text: "" }),
9871
+ onChange: (nextRows, edit) => onChange({
9872
+ ...block,
9873
+ rows: nextRows,
9874
+ ...edit ? { column_settings: alignColumnSettings(columnSettings, edit) } : {}
9875
+ }),
9876
+ renderColumnControl: (c) => /* @__PURE__ */ jsxRuntime.jsxs(
9877
+ "select",
9878
+ {
9879
+ "aria-label": `Column ${c + 1} alignment`,
9880
+ value: columnSettings[c]?.align ?? "",
9881
+ onChange: (e) => setColumnAlign(c, e.target.value),
9882
+ className: "h-7 flex-1 rounded-sm border border-input bg-background px-1.5 text-[11px] text-muted-foreground",
9883
+ children: [
9884
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "Default" }),
9885
+ ALIGN_OPTIONS.map((a) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: a, children: a }, a))
9886
+ ]
9887
+ }
9888
+ ),
9889
+ renderCell: (cell, { row, col, label }, update) => /* @__PURE__ */ jsxRuntime.jsx(
9890
+ CellTextInput,
9891
+ {
9892
+ cell,
9893
+ label,
9894
+ placeholder: row === 0 ? `Header ${col + 1}` : "Cell text",
9895
+ onChange: update
9896
+ }
9897
+ )
9898
+ }
9899
+ );
9573
9900
  }
9574
9901
  var STATUSES2 = ["pending", "in_progress", "complete", "error"];
9575
9902
  function TaskCardEditor({ block, onChange }) {
@@ -9910,6 +10237,8 @@ function dispatch(block, onChange) {
9910
10237
  return /* @__PURE__ */ jsxRuntime.jsx(DataVisualizationEditor, { block, onChange: (next) => onChange(next) });
9911
10238
  case "container":
9912
10239
  return /* @__PURE__ */ jsxRuntime.jsx(ContainerEditor, { block, onChange: (next) => onChange(next) });
10240
+ case "data_table":
10241
+ return /* @__PURE__ */ jsxRuntime.jsx(DataTableEditor, { block, onChange: (next) => onChange(next) });
9913
10242
  default:
9914
10243
  return null;
9915
10244
  }
@@ -9933,7 +10262,8 @@ var BLOCK_TYPE_LABELS = {
9933
10262
  plan: "Plan",
9934
10263
  task_card: "Task Card",
9935
10264
  data_visualization: "Data Visualization",
9936
- container: "Container"
10265
+ container: "Container",
10266
+ data_table: "Data Table"
9937
10267
  };
9938
10268
  function BlockRow({
9939
10269
  builderBlock,