@tightknitai/block-kitchen 0.10.19 → 0.10.21

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, Hash, List, ListOrdered, Quote, 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';
@@ -21,6 +21,7 @@ import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
21
21
  import Link from '@tiptap/extension-link';
22
22
  import { Node as Node$1, ReactNodeViewRenderer, mergeAttributes, Extension, NodeViewWrapper, useEditor, EditorContent } from '@tiptap/react';
23
23
  import StarterKit from '@tiptap/starter-kit';
24
+ import { PluginKey } from '@tiptap/pm/state';
24
25
  import { Suggestion, exitSuggestion } from '@tiptap/suggestion';
25
26
 
26
27
  // src/components/block-kitchen.tsx
@@ -1066,6 +1067,58 @@ var defaultPalette = [
1066
1067
  ]
1067
1068
  ]
1068
1069
  })
1070
+ },
1071
+ {
1072
+ // Slack's documented example.
1073
+ id: "data_table_basic",
1074
+ label: "Data table",
1075
+ factory: () => ({
1076
+ type: "data_table",
1077
+ caption: "A Fabulous Table",
1078
+ rows: [
1079
+ [
1080
+ { type: "raw_text", text: "Name" },
1081
+ { type: "raw_text", text: "Department" },
1082
+ { type: "raw_text", text: "Badge" }
1083
+ ],
1084
+ [
1085
+ { type: "raw_text", text: "Data Refinement Department" },
1086
+ { type: "raw_text", text: "MDR" },
1087
+ {
1088
+ type: "rich_text",
1089
+ elements: [
1090
+ { type: "rich_text_section", elements: [{ type: "text", text: "Blue", style: { bold: true } }] }
1091
+ ]
1092
+ }
1093
+ ],
1094
+ [
1095
+ { type: "raw_text", text: "Art Sourcing Department" },
1096
+ { type: "raw_text", text: "O&D" },
1097
+ {
1098
+ type: "rich_text",
1099
+ elements: [
1100
+ {
1101
+ type: "rich_text_section",
1102
+ elements: [
1103
+ { type: "text", text: "Green " },
1104
+ { type: "text", text: "review", style: { italic: true } }
1105
+ ]
1106
+ }
1107
+ ]
1108
+ }
1109
+ ],
1110
+ [
1111
+ { type: "raw_text", text: "Wellness Department" },
1112
+ { type: "raw_text", text: "Wellness Center" },
1113
+ {
1114
+ type: "rich_text",
1115
+ elements: [
1116
+ { type: "rich_text_section", elements: [{ type: "text", text: "Limited", style: { bold: true } }] }
1117
+ ]
1118
+ }
1119
+ ]
1120
+ ]
1121
+ })
1069
1122
  }
1070
1123
  ]
1071
1124
  },
@@ -1730,6 +1783,8 @@ function labelForBlockType(type) {
1730
1783
  return "Data Visualization";
1731
1784
  case "container":
1732
1785
  return "Container";
1786
+ case "data_table":
1787
+ return "Data Table";
1733
1788
  }
1734
1789
  }
1735
1790
  var Sheet = DialogPrimitive.Root;
@@ -5190,15 +5245,16 @@ function makeEmojiImportResolver(customByName, index = getEmojiIndex()) {
5190
5245
 
5191
5246
  // src/lib/rich-text-tiptap.ts
5192
5247
  var USER_MENTION_NODE = "userMention";
5248
+ var CHANNEL_MENTION_NODE = "channelMention";
5193
5249
  var SUPPORTED_BLOCK_KINDS = /* @__PURE__ */ new Set([
5194
5250
  "rich_text_section",
5195
5251
  "rich_text_list",
5196
5252
  "rich_text_quote",
5197
5253
  "rich_text_preformatted"
5198
5254
  ]);
5199
- var SUPPORTED_INLINE_KINDS = /* @__PURE__ */ new Set(["text", "link", "emoji", "user"]);
5255
+ var SUPPORTED_INLINE_KINDS = /* @__PURE__ */ new Set(["text", "link", "emoji", "user", "channel"]);
5200
5256
  var SUPPORTED_TEXT_STYLE_KEYS = /* @__PURE__ */ new Set(["bold", "italic", "strike", "underline", "code"]);
5201
- var SUPPORTED_USER_STYLE_KEYS = /* @__PURE__ */ new Set(["bold", "italic", "strike", "underline"]);
5257
+ var SUPPORTED_MENTION_STYLE_KEYS = /* @__PURE__ */ new Set(["bold", "italic", "strike", "underline"]);
5202
5258
  var MAX_LIST_INDENT = 8;
5203
5259
  function detectLossy(block) {
5204
5260
  const out = [];
@@ -5253,10 +5309,13 @@ function scanInlines(inlines, where, out) {
5253
5309
  }
5254
5310
  }
5255
5311
  }
5256
- if (inline.type === "user") {
5312
+ if (inline.type === "user" || inline.type === "channel") {
5313
+ if (inline.type === "channel" && !inline.channel_id) {
5314
+ out.push({ label: "Channel without id", where: sub });
5315
+ }
5257
5316
  for (const k of Object.keys(inline.style ?? {})) {
5258
- if (!SUPPORTED_USER_STYLE_KEYS.has(k)) {
5259
- out.push({ label: `Mention style: ${k}`, where: sub });
5317
+ if (!SUPPORTED_MENTION_STYLE_KEYS.has(k)) {
5318
+ out.push({ label: `${inline.type === "channel" ? "Channel" : "Mention"} style: ${k}`, where: sub });
5260
5319
  }
5261
5320
  }
5262
5321
  }
@@ -5382,6 +5441,20 @@ function inlinesToPM(inlines, resolveEmoji) {
5382
5441
  node.marks = marks;
5383
5442
  }
5384
5443
  out.push(node);
5444
+ } else if (inline.type === "channel") {
5445
+ const channel = inline;
5446
+ if (!channel.channel_id) {
5447
+ continue;
5448
+ }
5449
+ const node = {
5450
+ type: CHANNEL_MENTION_NODE,
5451
+ attrs: { channelId: channel.channel_id, name: null }
5452
+ };
5453
+ const marks = styleToMarks(channel.style);
5454
+ if (marks.length > 0) {
5455
+ node.marks = marks;
5456
+ }
5457
+ out.push(node);
5385
5458
  } else if (inline.type === "link") {
5386
5459
  const link = inline;
5387
5460
  const text = link.text || link.url || "";
@@ -5521,13 +5594,26 @@ function proseMirrorInlinesToRichTextElements(nodes) {
5521
5594
  continue;
5522
5595
  }
5523
5596
  const user = { type: "user", user_id: userId };
5524
- const style2 = userStyle(node.marks ?? []);
5597
+ const style2 = mentionStyle(node.marks ?? []);
5525
5598
  if (Object.keys(style2).length > 0) {
5526
5599
  user.style = style2;
5527
5600
  }
5528
5601
  out.push(user);
5529
5602
  continue;
5530
5603
  }
5604
+ if (node.type === CHANNEL_MENTION_NODE) {
5605
+ const channelId = String(node.attrs?.channelId ?? "");
5606
+ if (!channelId) {
5607
+ continue;
5608
+ }
5609
+ const channel = { type: "channel", channel_id: channelId };
5610
+ const style2 = mentionStyle(node.marks ?? []);
5611
+ if (Object.keys(style2).length > 0) {
5612
+ channel.style = style2;
5613
+ }
5614
+ out.push(channel);
5615
+ continue;
5616
+ }
5531
5617
  if (node.type === "hardBreak") {
5532
5618
  out.push({ type: "text", text: "\n" });
5533
5619
  continue;
@@ -5582,10 +5668,10 @@ function marksToStyle(marks) {
5582
5668
  }
5583
5669
  return style;
5584
5670
  }
5585
- function userStyle(marks) {
5671
+ function mentionStyle(marks) {
5586
5672
  const style = {};
5587
5673
  for (const [key, value] of Object.entries(marksToStyle(marks))) {
5588
- if (SUPPORTED_USER_STYLE_KEYS.has(key)) {
5674
+ if (SUPPORTED_MENTION_STYLE_KEYS.has(key)) {
5589
5675
  style[key] = value;
5590
5676
  }
5591
5677
  }
@@ -5614,50 +5700,52 @@ function sameStyle(a, b) {
5614
5700
  }
5615
5701
  return ak.every((k, i) => k === bk[i]);
5616
5702
  }
5617
- function EmojiNodeView({ node }) {
5618
- const { name, src, unicode } = node.attrs;
5619
- const label = `:${name}:`;
5620
- return /* @__PURE__ */ jsx(NodeViewWrapper, { as: "span", className: "bk-emoji-node", "data-emoji": name, title: label, children: src ? /* @__PURE__ */ jsx(
5621
- "img",
5622
- {
5623
- src,
5624
- alt: label,
5625
- style: {
5626
- display: "inline-block",
5627
- width: "1.25em",
5628
- height: "1.25em",
5629
- verticalAlign: "-0.25em",
5630
- objectFit: "contain"
5631
- }
5632
- }
5633
- ) : /* @__PURE__ */ jsx("span", { "aria-label": label, role: "img", children: unicode ? codepointsToGlyph(unicode) : label }) });
5703
+ function channelMentionLabel(attrs, getChannelName) {
5704
+ return getChannelName?.(attrs.channelId) ?? attrs.name ?? attrs.channelId;
5634
5705
  }
5635
- var EmojiNode = Node$1.create({
5636
- name: "emoji",
5706
+ function ChannelMentionNodeView({ node }) {
5707
+ const mentions = useMentions();
5708
+ const attrs = node.attrs;
5709
+ return /* @__PURE__ */ jsxs(NodeViewWrapper, { as: "span", className: "bk-channel-mention", "data-channel-mention": attrs.channelId, children: [
5710
+ "#",
5711
+ channelMentionLabel(attrs, mentions?.getChannelName)
5712
+ ] });
5713
+ }
5714
+ var ChannelMentionNode = Node$1.create({
5715
+ name: CHANNEL_MENTION_NODE,
5637
5716
  group: "inline",
5638
5717
  inline: true,
5639
5718
  atom: true,
5640
5719
  selectable: true,
5641
5720
  addAttributes() {
5642
5721
  return {
5643
- name: { default: "" },
5644
- src: { default: null },
5645
- unicode: { default: null },
5646
- skinTone: { default: null }
5722
+ channelId: {
5723
+ default: "",
5724
+ parseHTML: (element) => element.getAttribute("data-channel-mention") ?? "",
5725
+ renderHTML: (attributes) => ({ "data-channel-mention": attributes.channelId })
5726
+ },
5727
+ name: {
5728
+ default: null,
5729
+ parseHTML: (element) => element.getAttribute("data-channel-name"),
5730
+ renderHTML: (attributes) => attributes.name ? { "data-channel-name": attributes.name } : {}
5731
+ }
5647
5732
  };
5648
5733
  },
5649
5734
  parseHTML() {
5650
- return [{ tag: "span[data-emoji]" }];
5735
+ return [{ tag: "span[data-channel-mention]" }];
5651
5736
  },
5652
- renderHTML({ HTMLAttributes }) {
5653
- const name = HTMLAttributes.name ?? "";
5654
- return ["span", mergeAttributes({ "data-emoji": name }, HTMLAttributes), `:${name}:`];
5737
+ renderHTML({ node, HTMLAttributes }) {
5738
+ return ["span", mergeAttributes(HTMLAttributes), `#${channelMentionLabel(node.attrs)}`];
5739
+ },
5740
+ renderText({ node }) {
5741
+ return `#${channelMentionLabel(node.attrs)}`;
5655
5742
  },
5656
5743
  addNodeView() {
5657
- return ReactNodeViewRenderer(EmojiNodeView);
5744
+ return ReactNodeViewRenderer(ChannelMentionNodeView);
5658
5745
  }
5659
5746
  });
5660
5747
  var MENTION_SEARCH_DEBOUNCE_MS = 200;
5748
+ var USER_MENTION_SUGGESTION_PLUGIN_KEY = new PluginKey("userMentionSuggestion");
5661
5749
  function insertUserMention(editor, range, user) {
5662
5750
  const to = editor.state.doc.textBetween(range.to, Math.min(range.to + 1, editor.state.doc.content.size)) === " " ? range.to + 1 : range.to;
5663
5751
  editor.chain().focus().insertContentAt({ from: range.from, to }, [
@@ -5666,13 +5754,16 @@ function insertUserMention(editor, range, user) {
5666
5754
  ]).run();
5667
5755
  }
5668
5756
  function insertMentionTrigger(editor) {
5757
+ insertSuggestionTrigger(editor, "@");
5758
+ }
5759
+ function insertSuggestionTrigger(editor, char) {
5669
5760
  const { from } = editor.state.selection;
5670
5761
  const before = from > 1 ? editor.state.doc.textBetween(from - 1, from, void 0, "\uFFFC") : "";
5671
5762
  const needsSpace = before !== "" && !/\s/.test(before);
5672
- editor.chain().focus().insertContent(needsSpace ? " @" : "@").run();
5763
+ editor.chain().focus().insertContent(needsSpace ? ` ${char}` : char).run();
5673
5764
  }
5674
5765
  function dismissMentionSuggestion(editor) {
5675
- exitSuggestion(editor.view);
5766
+ exitSuggestion(editor.view, USER_MENTION_SUGGESTION_PLUGIN_KEY);
5676
5767
  }
5677
5768
  var MentionSuggestion = Extension.create({
5678
5769
  name: "userMentionSuggestion",
@@ -5688,6 +5779,7 @@ var MentionSuggestion = Extension.create({
5688
5779
  return [
5689
5780
  Suggestion({
5690
5781
  editor: this.editor,
5782
+ pluginKey: USER_MENTION_SUGGESTION_PLUGIN_KEY,
5691
5783
  char: "@",
5692
5784
  debounce: MENTION_SEARCH_DEBOUNCE_MS,
5693
5785
  allow: () => isEnabled(),
@@ -5698,6 +5790,90 @@ var MentionSuggestion = Extension.create({
5698
5790
  ];
5699
5791
  }
5700
5792
  });
5793
+
5794
+ // src/lib/tiptap-channel-suggestion.ts
5795
+ var CHANNEL_MENTION_SUGGESTION_PLUGIN_KEY = new PluginKey("channelMentionSuggestion");
5796
+ function insertChannelMention(editor, range, channel) {
5797
+ const to = editor.state.doc.textBetween(range.to, Math.min(range.to + 1, editor.state.doc.content.size)) === " " ? range.to + 1 : range.to;
5798
+ editor.chain().focus().insertContentAt({ from: range.from, to }, [
5799
+ { type: CHANNEL_MENTION_NODE, attrs: { channelId: channel.id, name: channel.name } },
5800
+ { type: "text", text: " " }
5801
+ ]).run();
5802
+ }
5803
+ function insertChannelTrigger(editor) {
5804
+ insertSuggestionTrigger(editor, "#");
5805
+ }
5806
+ function dismissChannelSuggestion(editor) {
5807
+ exitSuggestion(editor.view, CHANNEL_MENTION_SUGGESTION_PLUGIN_KEY);
5808
+ }
5809
+ var ChannelSuggestion = Extension.create({
5810
+ name: "channelMentionSuggestion",
5811
+ addOptions() {
5812
+ return {
5813
+ isEnabled: () => false,
5814
+ search: async () => [],
5815
+ render: () => ({})
5816
+ };
5817
+ },
5818
+ addProseMirrorPlugins() {
5819
+ const { isEnabled, search, render } = this.options;
5820
+ return [
5821
+ Suggestion({
5822
+ editor: this.editor,
5823
+ pluginKey: CHANNEL_MENTION_SUGGESTION_PLUGIN_KEY,
5824
+ char: "#",
5825
+ debounce: MENTION_SEARCH_DEBOUNCE_MS,
5826
+ allow: () => isEnabled(),
5827
+ items: ({ query, signal }) => search(query, signal),
5828
+ command: ({ editor, range, props }) => insertChannelMention(editor, range, props),
5829
+ render
5830
+ })
5831
+ ];
5832
+ }
5833
+ });
5834
+ function EmojiNodeView({ node }) {
5835
+ const { name, src, unicode } = node.attrs;
5836
+ const label = `:${name}:`;
5837
+ return /* @__PURE__ */ jsx(NodeViewWrapper, { as: "span", className: "bk-emoji-node", "data-emoji": name, title: label, children: src ? /* @__PURE__ */ jsx(
5838
+ "img",
5839
+ {
5840
+ src,
5841
+ alt: label,
5842
+ style: {
5843
+ display: "inline-block",
5844
+ width: "1.25em",
5845
+ height: "1.25em",
5846
+ verticalAlign: "-0.25em",
5847
+ objectFit: "contain"
5848
+ }
5849
+ }
5850
+ ) : /* @__PURE__ */ jsx("span", { "aria-label": label, role: "img", children: unicode ? codepointsToGlyph(unicode) : label }) });
5851
+ }
5852
+ var EmojiNode = Node$1.create({
5853
+ name: "emoji",
5854
+ group: "inline",
5855
+ inline: true,
5856
+ atom: true,
5857
+ selectable: true,
5858
+ addAttributes() {
5859
+ return {
5860
+ name: { default: "" },
5861
+ src: { default: null },
5862
+ unicode: { default: null },
5863
+ skinTone: { default: null }
5864
+ };
5865
+ },
5866
+ parseHTML() {
5867
+ return [{ tag: "span[data-emoji]" }];
5868
+ },
5869
+ renderHTML({ HTMLAttributes }) {
5870
+ const name = HTMLAttributes.name ?? "";
5871
+ return ["span", mergeAttributes({ "data-emoji": name }, HTMLAttributes), `:${name}:`];
5872
+ },
5873
+ addNodeView() {
5874
+ return ReactNodeViewRenderer(EmojiNodeView);
5875
+ }
5876
+ });
5701
5877
  function userMentionLabel(attrs, getUserName) {
5702
5878
  return getUserName?.(attrs.userId) ?? attrs.name ?? attrs.userId;
5703
5879
  }
@@ -6033,6 +6209,7 @@ function nextMentionPickerState(props, prev) {
6033
6209
  }
6034
6210
  function MentionPicker({
6035
6211
  editor,
6212
+ kind,
6036
6213
  state,
6037
6214
  keyHandlerRef,
6038
6215
  onDismiss
@@ -6045,6 +6222,27 @@ function MentionPicker({
6045
6222
  setActiveIndex(0);
6046
6223
  }
6047
6224
  const listRef = useRef(null);
6225
+ const listboxId = useId();
6226
+ const activeOptionId = items[activeIndex] ? `${listboxId}-${activeIndex}` : void 0;
6227
+ useEffect(() => {
6228
+ const root = editor.view.dom;
6229
+ root.setAttribute("role", "combobox");
6230
+ root.setAttribute("aria-autocomplete", "list");
6231
+ root.setAttribute("aria-expanded", "true");
6232
+ root.setAttribute("aria-controls", listboxId);
6233
+ if (activeOptionId) {
6234
+ root.setAttribute("aria-activedescendant", activeOptionId);
6235
+ } else {
6236
+ root.removeAttribute("aria-activedescendant");
6237
+ }
6238
+ return () => {
6239
+ root.removeAttribute("role");
6240
+ root.removeAttribute("aria-autocomplete");
6241
+ root.removeAttribute("aria-expanded");
6242
+ root.removeAttribute("aria-controls");
6243
+ root.removeAttribute("aria-activedescendant");
6244
+ };
6245
+ }, [activeOptionId, editor, listboxId]);
6048
6246
  useEffect(() => {
6049
6247
  keyHandlerRef.current = (event) => {
6050
6248
  if (event.key === "ArrowDown" || event.key === "ArrowUp") {
@@ -6055,11 +6253,11 @@ function MentionPicker({
6055
6253
  return true;
6056
6254
  }
6057
6255
  if (event.key === "Enter" || event.key === "Tab") {
6058
- const user = items[activeIndex];
6059
- if (!user) {
6256
+ const item = items[activeIndex];
6257
+ if (!item) {
6060
6258
  return false;
6061
6259
  }
6062
- command(user);
6260
+ command(item);
6063
6261
  return true;
6064
6262
  }
6065
6263
  return false;
@@ -6106,29 +6304,39 @@ function MentionPicker({
6106
6304
  e.preventDefault();
6107
6305
  }
6108
6306
  },
6109
- children: /* @__PURE__ */ jsx("div", { ref: listRef, role: "listbox", "aria-label": "People to mention", children: items.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-2 py-3 text-center text-xs text-muted-foreground", children: loading ? "Searching\u2026" : "No people match that search." }) : items.map((user, index) => (
6110
- // biome-ignore lint/a11y/useFocusableInteractive: focus stays in the editor, which drives this list through the suggestion plugin's key handling.
6111
- // biome-ignore lint/a11y/useKeyWithClickEvents: the keyboard path is the editor's, forwarded via keyHandlerRef.
6112
- /* @__PURE__ */ jsxs(
6113
- "div",
6114
- {
6115
- role: "option",
6116
- "aria-selected": index === activeIndex,
6117
- onPointerDown: (e) => e.preventDefault(),
6118
- onClick: () => command(user),
6119
- onMouseMove: () => setActiveIndex(index),
6120
- className: cn(
6121
- "flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-sm text-foreground",
6122
- index === activeIndex && "bg-accent"
6123
- ),
6124
- children: [
6125
- /* @__PURE__ */ jsx(MentionAvatar, { user }),
6126
- /* @__PURE__ */ jsx("span", { className: "truncate", children: user.name })
6127
- ]
6128
- },
6129
- user.id
6130
- )
6131
- )) })
6307
+ children: /* @__PURE__ */ jsx(
6308
+ "div",
6309
+ {
6310
+ ref: listRef,
6311
+ id: listboxId,
6312
+ role: "listbox",
6313
+ "aria-label": kind === "channel" ? "Channels to mention" : "People to mention",
6314
+ children: items.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-2 py-3 text-center text-xs text-muted-foreground", children: loading ? "Searching\u2026" : `No ${kind === "channel" ? "channels" : "people"} match that search.` }) : items.map((item, index) => (
6315
+ // biome-ignore lint/a11y/useFocusableInteractive: focus stays in the editor, which drives this list through the suggestion plugin's key handling.
6316
+ // biome-ignore lint/a11y/useKeyWithClickEvents: the keyboard path is the editor's, forwarded via keyHandlerRef.
6317
+ /* @__PURE__ */ jsxs(
6318
+ "div",
6319
+ {
6320
+ id: `${listboxId}-${index}`,
6321
+ role: "option",
6322
+ "aria-selected": index === activeIndex,
6323
+ onPointerDown: (e) => e.preventDefault(),
6324
+ onClick: () => command(item),
6325
+ onMouseMove: () => setActiveIndex(index),
6326
+ className: cn(
6327
+ "flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-sm text-foreground",
6328
+ index === activeIndex && "bg-accent"
6329
+ ),
6330
+ children: [
6331
+ kind === "channel" ? /* @__PURE__ */ jsx("span", { className: "flex h-5 w-5 shrink-0 items-center justify-center rounded bg-muted text-muted-foreground", children: /* @__PURE__ */ jsx(Hash, { className: "h-3.5 w-3.5" }) }) : /* @__PURE__ */ jsx(MentionAvatar, { user: item }),
6332
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: kind === "channel" ? `#${item.name}` : item.name })
6333
+ ]
6334
+ },
6335
+ item.id
6336
+ )
6337
+ ))
6338
+ }
6339
+ )
6132
6340
  }
6133
6341
  )
6134
6342
  ]
@@ -6725,7 +6933,7 @@ function LossyBanner({ reasons }) {
6725
6933
  ] });
6726
6934
  }
6727
6935
  var RICH_TEXT_INPUT_RULE_ALLOWLIST = ["bold", "italic", "strike", "code"];
6728
- function buildRichTextExtensions(mentionSuggestion) {
6936
+ function buildRichTextExtensions(mentionSuggestion, channelSuggestion) {
6729
6937
  return [
6730
6938
  StarterKit.configure({
6731
6939
  heading: false,
@@ -6759,10 +6967,11 @@ function buildRichTextExtensions(mentionSuggestion) {
6759
6967
  shouldAutoLink: hasExplicitSafeScheme,
6760
6968
  HTMLAttributes: { rel: "noreferrer noopener", target: "_blank" }
6761
6969
  }),
6762
- // Always registered, so mentions already in the content round-trip with
6763
- // or without the picker.
6970
+ // Always registered, so mentions already in the content round-trip with or without pickers.
6764
6971
  UserMentionNode,
6765
- ...mentionSuggestion ? [MentionSuggestion.configure(mentionSuggestion)] : []
6972
+ ChannelMentionNode,
6973
+ ...mentionSuggestion ? [MentionSuggestion.configure(mentionSuggestion)] : [],
6974
+ ...channelSuggestion ? [ChannelSuggestion.configure(channelSuggestion)] : []
6766
6975
  ];
6767
6976
  }
6768
6977
  function RichTextWysiwygEditor({ block, onChange }) {
@@ -6790,24 +6999,39 @@ function RichTextWysiwygEditor({ block, onChange }) {
6790
6999
  [onChange]
6791
7000
  );
6792
7001
  const mentions = useMentions();
6793
- const mentionsEnabled = mentions !== null;
7002
+ const userMentionsEnabled = mentions !== null;
7003
+ const channelMentionsEnabled = mentions?.searchChannels !== void 0;
6794
7004
  const mentionsRef = useRef(mentions);
6795
7005
  useEffect(() => {
6796
7006
  mentionsRef.current = mentions;
6797
7007
  }, [mentions]);
6798
7008
  const [mentionPicker, setMentionPicker] = useState(null);
6799
7009
  const mentionKeyHandlerRef = useRef(() => false);
7010
+ const [channelPicker, setChannelPicker] = useState(null);
7011
+ const channelKeyHandlerRef = useRef(() => false);
6800
7012
  const editor = useEditor({
6801
- extensions: buildRichTextExtensions({
6802
- isEnabled: () => mentionsRef.current !== null,
6803
- search: async (query, signal) => await mentionsRef.current?.searchUsers(query, { signal }) ?? [],
6804
- render: () => ({
6805
- onStart: (props) => setMentionPicker((prev) => nextMentionPickerState(props, prev)),
6806
- onUpdate: (props) => setMentionPicker((prev) => nextMentionPickerState(props, prev)),
6807
- onExit: () => setMentionPicker(null),
6808
- onKeyDown: ({ event }) => mentionKeyHandlerRef.current(event)
6809
- })
6810
- }),
7013
+ extensions: buildRichTextExtensions(
7014
+ {
7015
+ isEnabled: () => mentionsRef.current !== null,
7016
+ search: async (query, signal) => await mentionsRef.current?.searchUsers(query, { signal }) ?? [],
7017
+ render: () => ({
7018
+ onStart: (props) => setMentionPicker((prev) => nextMentionPickerState(props, prev)),
7019
+ onUpdate: (props) => setMentionPicker((prev) => nextMentionPickerState(props, prev)),
7020
+ onExit: () => setMentionPicker(null),
7021
+ onKeyDown: ({ event }) => mentionKeyHandlerRef.current(event)
7022
+ })
7023
+ },
7024
+ {
7025
+ isEnabled: () => mentionsRef.current?.searchChannels !== void 0,
7026
+ search: async (query, signal) => await mentionsRef.current?.searchChannels?.(query, { signal }) ?? [],
7027
+ render: () => ({
7028
+ onStart: (props) => setChannelPicker((prev) => nextMentionPickerState(props, prev)),
7029
+ onUpdate: (props) => setChannelPicker((prev) => nextMentionPickerState(props, prev)),
7030
+ onExit: () => setChannelPicker(null),
7031
+ onKeyDown: ({ event }) => channelKeyHandlerRef.current(event)
7032
+ })
7033
+ }
7034
+ ),
6811
7035
  enableInputRules: RICH_TEXT_INPUT_RULE_ALLOWLIST,
6812
7036
  content: richTextToProseMirror(block, resolveEmoji),
6813
7037
  editorProps: {
@@ -6842,7 +7066,8 @@ function RichTextWysiwygEditor({ block, onChange }) {
6842
7066
  Toolbar,
6843
7067
  {
6844
7068
  editor,
6845
- mention: mentionsEnabled ? { active: mentionPicker !== null, insert: () => insertMentionTrigger(editor) } : null
7069
+ mention: userMentionsEnabled ? { active: mentionPicker !== null, insert: () => insertMentionTrigger(editor) } : null,
7070
+ channel: channelMentionsEnabled ? { active: channelPicker !== null, insert: () => insertChannelTrigger(editor) } : null
6846
7071
  }
6847
7072
  ),
6848
7073
  /* @__PURE__ */ jsx(EditorContent, { editor }),
@@ -6850,14 +7075,29 @@ function RichTextWysiwygEditor({ block, onChange }) {
6850
7075
  MentionPicker,
6851
7076
  {
6852
7077
  editor,
7078
+ kind: "user",
6853
7079
  state: mentionPicker,
6854
7080
  keyHandlerRef: mentionKeyHandlerRef,
6855
7081
  onDismiss: () => dismissMentionSuggestion(editor)
6856
7082
  }
7083
+ ),
7084
+ channelPicker && /* @__PURE__ */ jsx(
7085
+ MentionPicker,
7086
+ {
7087
+ editor,
7088
+ kind: "channel",
7089
+ state: channelPicker,
7090
+ keyHandlerRef: channelKeyHandlerRef,
7091
+ onDismiss: () => dismissChannelSuggestion(editor)
7092
+ }
6857
7093
  )
6858
7094
  ] });
6859
7095
  }
6860
- function Toolbar({ editor, mention }) {
7096
+ function Toolbar({
7097
+ editor,
7098
+ mention,
7099
+ channel
7100
+ }) {
6861
7101
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-0.5 rounded-md border bg-muted/30 p-1", children: [
6862
7102
  /* @__PURE__ */ jsx(
6863
7103
  ToolbarButton,
@@ -6917,6 +7157,7 @@ function Toolbar({ editor, mention }) {
6917
7157
  }
6918
7158
  ),
6919
7159
  mention && /* @__PURE__ */ jsx(ToolbarButton, { label: "Mention", active: mention.active, onClick: mention.insert, Icon: AtSign }),
7160
+ channel && /* @__PURE__ */ jsx(ToolbarButton, { label: "Channel", active: channel.active, onClick: channel.insert, Icon: Hash }),
6920
7161
  /* @__PURE__ */ jsx(Divider, {}),
6921
7162
  /* @__PURE__ */ jsx(
6922
7163
  ToolbarButton,
@@ -7780,6 +8021,387 @@ function ImageItemCard({
7780
8021
  ) })
7781
8022
  ] });
7782
8023
  }
8024
+ 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";
8025
+ function CellGrid({
8026
+ label,
8027
+ help,
8028
+ rows,
8029
+ minRows,
8030
+ maxRows,
8031
+ maxColumns,
8032
+ lockFirstRow = false,
8033
+ blankCell,
8034
+ renderCell,
8035
+ renderColumnControl,
8036
+ onChange
8037
+ }) {
8038
+ const columnCount = rows[0]?.length ?? 0;
8039
+ const setCell = (rowIdx, colIdx, next) => {
8040
+ onChange(rows.map((row, r) => r === rowIdx ? row.map((cell, c) => c === colIdx ? next : cell) : row));
8041
+ };
8042
+ const addRow = () => {
8043
+ onChange([...rows, Array.from({ length: columnCount || 1 }, blankCell)]);
8044
+ };
8045
+ const removeRow = (rowIdx) => {
8046
+ if (rows.length <= minRows) {
8047
+ return;
8048
+ }
8049
+ onChange(rows.filter((_, r) => r !== rowIdx));
8050
+ };
8051
+ const addColumn = () => {
8052
+ onChange(
8053
+ rows.map((row) => [...row, blankCell()]),
8054
+ { kind: "add" }
8055
+ );
8056
+ };
8057
+ const removeColumn = (colIdx) => {
8058
+ if (columnCount <= 1) {
8059
+ return;
8060
+ }
8061
+ onChange(
8062
+ rows.map((row) => row.filter((_, c) => c !== colIdx)),
8063
+ { kind: "remove", index: colIdx }
8064
+ );
8065
+ };
8066
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
8067
+ /* @__PURE__ */ jsx(EditorField, { label, help, children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 overflow-x-auto", children: [
8068
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
8069
+ /* @__PURE__ */ jsx("span", { className: "w-6 shrink-0", "aria-hidden": "true" }),
8070
+ Array.from({ length: columnCount }, (_, c) => /* @__PURE__ */ jsxs("div", { className: "flex min-w-[120px] flex-1 items-center gap-1", children: [
8071
+ renderColumnControl ? renderColumnControl(c) : /* @__PURE__ */ jsxs("span", { className: "flex-1 truncate px-1.5 text-[11px] text-muted-foreground", children: [
8072
+ "Column ",
8073
+ c + 1
8074
+ ] }),
8075
+ /* @__PURE__ */ jsx(
8076
+ "button",
8077
+ {
8078
+ type: "button",
8079
+ "aria-label": `Remove column ${c + 1}`,
8080
+ onClick: () => removeColumn(c),
8081
+ disabled: columnCount <= 1,
8082
+ className: REMOVE_BUTTON_CLASS,
8083
+ children: /* @__PURE__ */ jsx(Trash2, { className: "h-3 w-3" })
8084
+ }
8085
+ )
8086
+ ] }, c))
8087
+ ] }),
8088
+ rows.map((row, r) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
8089
+ lockFirstRow && r === 0 ? /* @__PURE__ */ jsx("span", { className: "w-6 shrink-0", "aria-hidden": "true" }) : /* @__PURE__ */ jsx(
8090
+ "button",
8091
+ {
8092
+ type: "button",
8093
+ "aria-label": `Remove row ${r + 1}`,
8094
+ onClick: () => removeRow(r),
8095
+ disabled: rows.length <= minRows,
8096
+ className: REMOVE_BUTTON_CLASS,
8097
+ children: /* @__PURE__ */ jsx(Trash2, { className: "h-3 w-3" })
8098
+ }
8099
+ ),
8100
+ row.map((cell, c) => /* @__PURE__ */ jsx("div", { className: "flex min-w-[120px] flex-1 flex-col gap-0.5", children: renderCell(
8101
+ cell,
8102
+ { row: r, col: c, label: `Row ${r + 1}, column ${c + 1}` },
8103
+ (next) => setCell(r, c, next)
8104
+ ) }, c))
8105
+ ] }, r))
8106
+ ] }) }),
8107
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
8108
+ /* @__PURE__ */ jsxs(Button, { type: "button", size: "sm", onClick: addRow, disabled: rows.length >= maxRows, children: [
8109
+ /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" }),
8110
+ " Add row"
8111
+ ] }),
8112
+ /* @__PURE__ */ jsxs(Button, { type: "button", size: "sm", onClick: addColumn, disabled: columnCount >= maxColumns, children: [
8113
+ /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" }),
8114
+ " Add column"
8115
+ ] })
8116
+ ] })
8117
+ ] });
8118
+ }
8119
+ function CellTextInput({
8120
+ cell,
8121
+ label,
8122
+ placeholder,
8123
+ leading,
8124
+ richTextNote = "Was rich-text; editing replaces with plain text.",
8125
+ onChange
8126
+ }) {
8127
+ const input = /* @__PURE__ */ jsx(
8128
+ Input,
8129
+ {
8130
+ "aria-label": label,
8131
+ value: cell.type === "raw_text" ? cell.text : "[rich_text]",
8132
+ placeholder,
8133
+ onChange: (e) => onChange({ type: "raw_text", text: e.target.value }),
8134
+ className: "h-8 text-xs"
8135
+ }
8136
+ );
8137
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
8138
+ leading ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
8139
+ leading,
8140
+ input
8141
+ ] }) : input,
8142
+ cell.type === "rich_text" ? /* @__PURE__ */ jsx("span", { className: cn("text-[10px] text-muted-foreground", leading && "pl-7"), children: richTextNote }) : null
8143
+ ] });
8144
+ }
8145
+ function NumberInput({
8146
+ id,
8147
+ value,
8148
+ onCommit,
8149
+ ariaLabel,
8150
+ className
8151
+ }) {
8152
+ const [draft, setDraft] = useState(String(value));
8153
+ useEffect(() => {
8154
+ setDraft(String(value));
8155
+ }, [value]);
8156
+ return /* @__PURE__ */ jsx(
8157
+ Input,
8158
+ {
8159
+ id,
8160
+ type: "number",
8161
+ inputMode: "numeric",
8162
+ "aria-label": ariaLabel,
8163
+ value: draft,
8164
+ className,
8165
+ onChange: (e) => {
8166
+ const raw = e.target.value;
8167
+ setDraft(raw);
8168
+ if (raw === "" || raw === "-" || raw === "." || raw === "-.") {
8169
+ return;
8170
+ }
8171
+ const next = Number(raw);
8172
+ if (!Number.isNaN(next)) {
8173
+ onCommit(next);
8174
+ }
8175
+ },
8176
+ onBlur: () => {
8177
+ const next = Number(draft);
8178
+ const safe = draft.trim() === "" || Number.isNaN(next) ? 0 : next;
8179
+ onCommit(safe);
8180
+ setDraft(String(safe));
8181
+ }
8182
+ }
8183
+ );
8184
+ }
8185
+ var MAX_ROWS = 201;
8186
+ var MIN_ROWS = 2;
8187
+ var MAX_COLUMNS = 20;
8188
+ var MAX_PAGE_SIZE = 100;
8189
+ 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";
8190
+ function withOptional(block, key, value) {
8191
+ const next = { ...block };
8192
+ if (value === void 0) {
8193
+ delete next[key];
8194
+ } else {
8195
+ next[key] = value;
8196
+ }
8197
+ return next;
8198
+ }
8199
+ function realignRowHeader(index, edit) {
8200
+ if (index === void 0 || edit.kind === "add" || edit.index > index) {
8201
+ return index;
8202
+ }
8203
+ return edit.index === index ? void 0 : index - 1;
8204
+ }
8205
+ function cellText(cell) {
8206
+ switch (cell?.type) {
8207
+ case "raw_text":
8208
+ return cell.text;
8209
+ case "raw_number":
8210
+ return cell.text ?? String(cell.value);
8211
+ case "action_cell":
8212
+ return cell.element.text.text;
8213
+ default:
8214
+ return "";
8215
+ }
8216
+ }
8217
+ function toNumberCell(cell) {
8218
+ const text = cell.text.trim();
8219
+ const parsed = text === "" ? Number.NaN : Number(text.replace(/,/g, ""));
8220
+ if (Number.isFinite(parsed)) {
8221
+ return String(parsed) === text ? { type: "raw_number", value: parsed } : { type: "raw_number", value: parsed, text };
8222
+ }
8223
+ return text ? { type: "raw_number", value: 0, text } : { type: "raw_number", value: 0 };
8224
+ }
8225
+ function toTextCell(cell) {
8226
+ return { type: "raw_text", text: cell.text ?? String(cell.value) };
8227
+ }
8228
+ function DataTableEditor({ block, onChange }) {
8229
+ const rows = block.rows ?? [];
8230
+ const header = rows[0] ?? [];
8231
+ const rowHeader = block.row_header_column_index ?? 0;
8232
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
8233
+ /* @__PURE__ */ jsx(EditorField, { label: "Caption", help: "Required. Names the table for screen readers.", htmlFor: "data-table-caption", children: /* @__PURE__ */ jsx(
8234
+ Input,
8235
+ {
8236
+ id: "data-table-caption",
8237
+ value: block.caption,
8238
+ placeholder: "e.g. Open deals by stage",
8239
+ onChange: (e) => onChange({ ...block, caption: e.target.value })
8240
+ }
8241
+ ) }),
8242
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
8243
+ /* @__PURE__ */ jsx(
8244
+ EditorField,
8245
+ {
8246
+ label: "Rows per page",
8247
+ help: "Optional, 1-100. Slack shows 5 when empty.",
8248
+ htmlFor: "data-table-page-size",
8249
+ children: /* @__PURE__ */ jsx(
8250
+ Input,
8251
+ {
8252
+ id: "data-table-page-size",
8253
+ type: "number",
8254
+ min: 1,
8255
+ max: MAX_PAGE_SIZE,
8256
+ value: block.page_size ?? "",
8257
+ placeholder: "5",
8258
+ onChange: (e) => {
8259
+ const n = Math.trunc(Number(e.target.value));
8260
+ const next = e.target.value === "" || Number.isNaN(n) ? void 0 : Math.min(MAX_PAGE_SIZE, Math.max(1, n));
8261
+ onChange(withOptional(block, "page_size", next));
8262
+ }
8263
+ }
8264
+ )
8265
+ }
8266
+ ),
8267
+ /* @__PURE__ */ jsx(
8268
+ EditorField,
8269
+ {
8270
+ label: "Row header column",
8271
+ help: "The column that identifies each row for screen readers.",
8272
+ htmlFor: "data-table-row-header",
8273
+ children: /* @__PURE__ */ jsxs(
8274
+ "select",
8275
+ {
8276
+ id: "data-table-row-header",
8277
+ value: rowHeader,
8278
+ disabled: header.length === 0,
8279
+ onChange: (e) => {
8280
+ const index = Number(e.target.value);
8281
+ onChange(withOptional(block, "row_header_column_index", index === 0 ? void 0 : index));
8282
+ },
8283
+ className: SELECT_CLASS,
8284
+ children: [
8285
+ header.map((cell, c) => {
8286
+ const text = cellText(cell);
8287
+ return /* @__PURE__ */ jsx("option", { value: c, children: text ? `Column ${c + 1}: ${text}` : `Column ${c + 1}` }, c);
8288
+ }),
8289
+ rowHeader >= header.length ? /* @__PURE__ */ jsxs("option", { value: rowHeader, children: [
8290
+ "Column ",
8291
+ rowHeader + 1,
8292
+ " (missing)"
8293
+ ] }) : null
8294
+ ]
8295
+ }
8296
+ )
8297
+ }
8298
+ )
8299
+ ] }),
8300
+ /* @__PURE__ */ jsx(
8301
+ CellGrid,
8302
+ {
8303
+ label: "Cells",
8304
+ 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.",
8305
+ rows,
8306
+ minRows: MIN_ROWS,
8307
+ maxRows: MAX_ROWS,
8308
+ maxColumns: MAX_COLUMNS,
8309
+ lockFirstRow: true,
8310
+ blankCell: () => ({ type: "raw_text", text: "" }),
8311
+ onChange: (nextRows, edit) => {
8312
+ const next = { ...block, rows: nextRows };
8313
+ onChange(
8314
+ edit ? withOptional(next, "row_header_column_index", realignRowHeader(block.row_header_column_index, edit)) : next
8315
+ );
8316
+ },
8317
+ renderCell: (cell, position, update) => /* @__PURE__ */ jsx(DataTableCellField, { cell, position, onChange: update })
8318
+ }
8319
+ )
8320
+ ] });
8321
+ }
8322
+ function DataTableCellField({
8323
+ cell,
8324
+ position,
8325
+ onChange
8326
+ }) {
8327
+ const { row, col, label } = position;
8328
+ if (cell.type === "action_cell") {
8329
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
8330
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
8331
+ /* @__PURE__ */ jsx(ToggleSpacer, {}),
8332
+ /* @__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: [
8333
+ /* @__PURE__ */ jsx(MousePointerClick, { className: "h-3 w-3 shrink-0", "aria-hidden": "true" }),
8334
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: cellText(cell) || "Button" })
8335
+ ] })
8336
+ ] }),
8337
+ /* @__PURE__ */ jsx("span", { className: "pl-7 text-[10px] text-muted-foreground", children: "Button cell. Edit it via View JSON." })
8338
+ ] });
8339
+ }
8340
+ if (cell.type === "raw_number") {
8341
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
8342
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
8343
+ /* @__PURE__ */ jsx(NumberToggle, { label, pressed: true, onClick: () => onChange(toTextCell(cell)) }),
8344
+ /* @__PURE__ */ jsx(
8345
+ NumberInput,
8346
+ {
8347
+ value: cell.value,
8348
+ ariaLabel: `${label} value`,
8349
+ onCommit: (value) => onChange({ ...cell, value }),
8350
+ className: "h-8 text-xs"
8351
+ }
8352
+ )
8353
+ ] }),
8354
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
8355
+ /* @__PURE__ */ jsx(ToggleSpacer, {}),
8356
+ /* @__PURE__ */ jsx(
8357
+ Input,
8358
+ {
8359
+ "aria-label": `${label} display text`,
8360
+ value: cell.text ?? "",
8361
+ placeholder: "Display as (optional)",
8362
+ onChange: (e) => {
8363
+ const { text: _omit, ...rest } = cell;
8364
+ onChange(e.target.value ? { ...rest, text: e.target.value } : rest);
8365
+ },
8366
+ className: "h-7 text-[11px]"
8367
+ }
8368
+ )
8369
+ ] })
8370
+ ] });
8371
+ }
8372
+ const isHeader = row === 0;
8373
+ return /* @__PURE__ */ jsx(
8374
+ CellTextInput,
8375
+ {
8376
+ cell,
8377
+ label,
8378
+ placeholder: isHeader ? `Header ${col + 1}` : "Cell text",
8379
+ richTextNote: isHeader ? "Header cells can't be rich text. Type to replace it." : void 0,
8380
+ leading: !isHeader && cell.type === "raw_text" ? /* @__PURE__ */ jsx(NumberToggle, { label, pressed: false, onClick: () => onChange(toNumberCell(cell)) }) : /* @__PURE__ */ jsx(ToggleSpacer, {}),
8381
+ onChange
8382
+ }
8383
+ );
8384
+ }
8385
+ function ToggleSpacer() {
8386
+ return /* @__PURE__ */ jsx("span", { className: "h-6 w-6 shrink-0", "aria-hidden": "true" });
8387
+ }
8388
+ function NumberToggle({ label, pressed, onClick }) {
8389
+ return /* @__PURE__ */ jsx(
8390
+ "button",
8391
+ {
8392
+ type: "button",
8393
+ "aria-label": `${label} as number`,
8394
+ "aria-pressed": pressed,
8395
+ title: pressed ? "Number cell. Click to make it text." : "Text cell. Click to make it a number.",
8396
+ onClick,
8397
+ className: cn(
8398
+ "flex h-6 w-6 shrink-0 items-center justify-center rounded transition-colors",
8399
+ pressed ? "bg-primary/10 text-foreground" : "text-muted-foreground hover:bg-accent hover:text-foreground"
8400
+ ),
8401
+ children: /* @__PURE__ */ jsx(Hash, { className: "h-3 w-3" })
8402
+ }
8403
+ );
8404
+ }
7783
8405
  var CHART_TYPES = ["line", "bar", "area", "pie"];
7784
8406
  function isCartesian(chart) {
7785
8407
  return chart.type !== "pie";
@@ -7834,46 +8456,6 @@ function DataVisualizationEditor({ block, onChange }) {
7834
8456
  isCartesian(chart) ? /* @__PURE__ */ jsx(CartesianFields, { chart, onChange: setChart }) : /* @__PURE__ */ jsx(PieFields, { chart, onChange: setChart })
7835
8457
  ] });
7836
8458
  }
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
8459
  function CartesianFields({ chart, onChange }) {
7878
8460
  const series = chart.series;
7879
8461
  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 +9998,13 @@ function ImageAccessoryFields({ image, onChange }) {
9416
9998
  ] });
9417
9999
  }
9418
10000
  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]";
10001
+ function alignColumnSettings(settings, edit) {
10002
+ return edit.kind === "add" ? [...settings, null] : settings.filter((_, c) => c !== edit.index);
9424
10003
  }
9425
10004
  function TableEditor({ block, onChange }) {
9426
10005
  const rows = block.rows ?? [];
9427
10006
  const columnCount = rows[0]?.length ?? 0;
9428
10007
  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
10008
  const setColumnAlign = (colIdx, align) => {
9459
10009
  const next = Array.from({ length: columnCount }, (_, i) => columnSettings[i] ?? null);
9460
10010
  if (align === "") {
@@ -9465,81 +10015,45 @@ function TableEditor({ block, onChange }) {
9465
10015
  }
9466
10016
  onChange({ ...block, column_settings: next });
9467
10017
  };
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
- ] });
10018
+ return /* @__PURE__ */ jsx(
10019
+ CellGrid,
10020
+ {
10021
+ label: "Cells",
10022
+ help: "Up to 100 rows and 20 columns. Slack allows one table per message.",
10023
+ rows,
10024
+ minRows: 1,
10025
+ maxRows: 100,
10026
+ maxColumns: 20,
10027
+ blankCell: () => ({ type: "raw_text", text: "" }),
10028
+ onChange: (nextRows, edit) => onChange({
10029
+ ...block,
10030
+ rows: nextRows,
10031
+ ...edit ? { column_settings: alignColumnSettings(columnSettings, edit) } : {}
10032
+ }),
10033
+ renderColumnControl: (c) => /* @__PURE__ */ jsxs(
10034
+ "select",
10035
+ {
10036
+ "aria-label": `Column ${c + 1} alignment`,
10037
+ value: columnSettings[c]?.align ?? "",
10038
+ onChange: (e) => setColumnAlign(c, e.target.value),
10039
+ className: "h-7 flex-1 rounded-sm border border-input bg-background px-1.5 text-[11px] text-muted-foreground",
10040
+ children: [
10041
+ /* @__PURE__ */ jsx("option", { value: "", children: "Default" }),
10042
+ ALIGN_OPTIONS.map((a) => /* @__PURE__ */ jsx("option", { value: a, children: a }, a))
10043
+ ]
10044
+ }
10045
+ ),
10046
+ renderCell: (cell, { row, col, label }, update) => /* @__PURE__ */ jsx(
10047
+ CellTextInput,
10048
+ {
10049
+ cell,
10050
+ label,
10051
+ placeholder: row === 0 ? `Header ${col + 1}` : "Cell text",
10052
+ onChange: update
10053
+ }
10054
+ )
10055
+ }
10056
+ );
9543
10057
  }
9544
10058
  var STATUSES2 = ["pending", "in_progress", "complete", "error"];
9545
10059
  function TaskCardEditor({ block, onChange }) {
@@ -9880,6 +10394,8 @@ function dispatch(block, onChange) {
9880
10394
  return /* @__PURE__ */ jsx(DataVisualizationEditor, { block, onChange: (next) => onChange(next) });
9881
10395
  case "container":
9882
10396
  return /* @__PURE__ */ jsx(ContainerEditor, { block, onChange: (next) => onChange(next) });
10397
+ case "data_table":
10398
+ return /* @__PURE__ */ jsx(DataTableEditor, { block, onChange: (next) => onChange(next) });
9883
10399
  default:
9884
10400
  return null;
9885
10401
  }
@@ -9903,7 +10419,8 @@ var BLOCK_TYPE_LABELS = {
9903
10419
  plan: "Plan",
9904
10420
  task_card: "Task Card",
9905
10421
  data_visualization: "Data Visualization",
9906
- container: "Container"
10422
+ container: "Container",
10423
+ data_table: "Data Table"
9907
10424
  };
9908
10425
  function BlockRow({
9909
10426
  builderBlock,