@tightknitai/block-kitchen 0.10.18 → 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
@@ -22,6 +22,7 @@ var RadioGroupPrimitive = require('@radix-ui/react-radio-group');
22
22
  var Link = require('@tiptap/extension-link');
23
23
  var react = require('@tiptap/react');
24
24
  var StarterKit = require('@tiptap/starter-kit');
25
+ var suggestion = require('@tiptap/suggestion');
25
26
 
26
27
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
27
28
 
@@ -1095,6 +1096,58 @@ var defaultPalette = [
1095
1096
  ]
1096
1097
  ]
1097
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
+ })
1098
1151
  }
1099
1152
  ]
1100
1153
  },
@@ -1759,6 +1812,8 @@ function labelForBlockType(type) {
1759
1812
  return "Data Visualization";
1760
1813
  case "container":
1761
1814
  return "Container";
1815
+ case "data_table":
1816
+ return "Data Table";
1762
1817
  }
1763
1818
  }
1764
1819
  var Sheet = DialogPrimitive__namespace.Root;
@@ -1863,6 +1918,13 @@ function CustomEmojiProvider({ customEmojis, children }) {
1863
1918
  function useCustomEmojis() {
1864
1919
  return React.useContext(CustomEmojiContext);
1865
1920
  }
1921
+ var MentionsContext = React.createContext(null);
1922
+ function MentionsProvider({ mentions, children }) {
1923
+ return /* @__PURE__ */ jsxRuntime.jsx(MentionsContext.Provider, { value: mentions ?? null, children });
1924
+ }
1925
+ function useMentions() {
1926
+ return React.useContext(MentionsContext);
1927
+ }
1866
1928
 
1867
1929
  // src/lib/error-grouping.ts
1868
1930
  function toValidatorSurface(surface) {
@@ -3363,12 +3425,27 @@ function ChannelCombobox({
3363
3425
  ] });
3364
3426
  }
3365
3427
  var EMBED_SELECTOR = "iframe[src], embed[src], object[data], source[src], video[src], audio[src], track[src]";
3428
+ function useLatestHooks(hooks) {
3429
+ const latest = React.useRef(hooks);
3430
+ latest.current = hooks;
3431
+ const [live] = React.useState(
3432
+ () => new Proxy(
3433
+ {},
3434
+ {
3435
+ get: (_target, key) => typeof key === "string" ? latest.current?.[key] : void 0,
3436
+ has: (_target, key) => typeof key === "string" && latest.current !== void 0 && key in latest.current
3437
+ }
3438
+ )
3439
+ );
3440
+ return live;
3441
+ }
3366
3442
  function SlackBlockPreview({
3367
3443
  block,
3368
3444
  hooks,
3369
3445
  theme = "light"
3370
3446
  }) {
3371
3447
  const rootRef = React.useRef(null);
3448
+ const liveHooks = useLatestHooks(hooks);
3372
3449
  const safeBlock = React.useMemo(() => sanitizeBlock(block), [block]);
3373
3450
  React.useEffect(() => {
3374
3451
  const root = rootRef.current;
@@ -3423,7 +3500,7 @@ function SlackBlockPreview({
3423
3500
  withoutWrapper: true,
3424
3501
  theme,
3425
3502
  blocks: [safeBlock],
3426
- hooks
3503
+ hooks: liveHooks
3427
3504
  }
3428
3505
  ) })
3429
3506
  );
@@ -5196,14 +5273,16 @@ function makeEmojiImportResolver(customByName, index = getEmojiIndex()) {
5196
5273
  }
5197
5274
 
5198
5275
  // src/lib/rich-text-tiptap.ts
5276
+ var USER_MENTION_NODE = "userMention";
5199
5277
  var SUPPORTED_BLOCK_KINDS = /* @__PURE__ */ new Set([
5200
5278
  "rich_text_section",
5201
5279
  "rich_text_list",
5202
5280
  "rich_text_quote",
5203
5281
  "rich_text_preformatted"
5204
5282
  ]);
5205
- var SUPPORTED_INLINE_KINDS = /* @__PURE__ */ new Set(["text", "link", "emoji"]);
5283
+ var SUPPORTED_INLINE_KINDS = /* @__PURE__ */ new Set(["text", "link", "emoji", "user"]);
5206
5284
  var SUPPORTED_TEXT_STYLE_KEYS = /* @__PURE__ */ new Set(["bold", "italic", "strike", "underline", "code"]);
5285
+ var SUPPORTED_USER_STYLE_KEYS = /* @__PURE__ */ new Set(["bold", "italic", "strike", "underline"]);
5207
5286
  var MAX_LIST_INDENT = 8;
5208
5287
  function detectLossy(block) {
5209
5288
  const out = [];
@@ -5226,6 +5305,17 @@ function detectLossy(block) {
5226
5305
  if ("border" in el && (el.border ?? 0) > 0) {
5227
5306
  out.push({ label: "Quote/preformatted border", where });
5228
5307
  }
5308
+ if (el.type === "rich_text_preformatted") {
5309
+ el.elements?.forEach((inline, i) => {
5310
+ const sub = `${where}.elements[${i}]`;
5311
+ if (inline.type !== "text") {
5312
+ out.push({ label: `In code block: ${inline.type}`, where: sub });
5313
+ } else if (Object.values(inline.style ?? {}).some(Boolean)) {
5314
+ out.push({ label: "Styled text in code block", where: sub });
5315
+ }
5316
+ });
5317
+ return;
5318
+ }
5229
5319
  scanInlines(el.elements ?? [], where, out);
5230
5320
  });
5231
5321
  return out;
@@ -5247,6 +5337,13 @@ function scanInlines(inlines, where, out) {
5247
5337
  }
5248
5338
  }
5249
5339
  }
5340
+ if (inline.type === "user") {
5341
+ for (const k of Object.keys(inline.style ?? {})) {
5342
+ if (!SUPPORTED_USER_STYLE_KEYS.has(k)) {
5343
+ out.push({ label: `Mention style: ${k}`, where: sub });
5344
+ }
5345
+ }
5346
+ }
5250
5347
  });
5251
5348
  }
5252
5349
  var defaultEmojiResolver = (el) => ({
@@ -5358,6 +5455,17 @@ function inlinesToPM(inlines, resolveEmoji) {
5358
5455
  continue;
5359
5456
  }
5360
5457
  out.push({ type: "emoji", attrs });
5458
+ } else if (inline.type === "user") {
5459
+ const user = inline;
5460
+ if (!user.user_id) {
5461
+ continue;
5462
+ }
5463
+ const node = { type: USER_MENTION_NODE, attrs: { userId: user.user_id, name: null } };
5464
+ const marks = styleToMarks(user.style);
5465
+ if (marks.length > 0) {
5466
+ node.marks = marks;
5467
+ }
5468
+ out.push(node);
5361
5469
  } else if (inline.type === "link") {
5362
5470
  const link = inline;
5363
5471
  const text = link.text || link.url || "";
@@ -5491,6 +5599,19 @@ function proseMirrorInlinesToRichTextElements(nodes) {
5491
5599
  out.push(emoji);
5492
5600
  continue;
5493
5601
  }
5602
+ if (node.type === USER_MENTION_NODE) {
5603
+ const userId = String(node.attrs?.userId ?? "");
5604
+ if (!userId) {
5605
+ continue;
5606
+ }
5607
+ const user = { type: "user", user_id: userId };
5608
+ const style2 = userStyle(node.marks ?? []);
5609
+ if (Object.keys(style2).length > 0) {
5610
+ user.style = style2;
5611
+ }
5612
+ out.push(user);
5613
+ continue;
5614
+ }
5494
5615
  if (node.type === "hardBreak") {
5495
5616
  out.push({ type: "text", text: "\n" });
5496
5617
  continue;
@@ -5545,6 +5666,15 @@ function marksToStyle(marks) {
5545
5666
  }
5546
5667
  return style;
5547
5668
  }
5669
+ function userStyle(marks) {
5670
+ const style = {};
5671
+ for (const [key, value] of Object.entries(marksToStyle(marks))) {
5672
+ if (SUPPORTED_USER_STYLE_KEYS.has(key)) {
5673
+ style[key] = value;
5674
+ }
5675
+ }
5676
+ return style;
5677
+ }
5548
5678
  function mergeAdjacentTextRuns(inlines) {
5549
5679
  const out = [];
5550
5680
  for (const inline of inlines) {
@@ -5611,6 +5741,91 @@ var EmojiNode = react.Node.create({
5611
5741
  return react.ReactNodeViewRenderer(EmojiNodeView);
5612
5742
  }
5613
5743
  });
5744
+ var MENTION_SEARCH_DEBOUNCE_MS = 200;
5745
+ function insertUserMention(editor, range, user) {
5746
+ const to = editor.state.doc.textBetween(range.to, Math.min(range.to + 1, editor.state.doc.content.size)) === " " ? range.to + 1 : range.to;
5747
+ editor.chain().focus().insertContentAt({ from: range.from, to }, [
5748
+ { type: USER_MENTION_NODE, attrs: { userId: user.id, name: user.name } },
5749
+ { type: "text", text: " " }
5750
+ ]).run();
5751
+ }
5752
+ function insertMentionTrigger(editor) {
5753
+ const { from } = editor.state.selection;
5754
+ const before = from > 1 ? editor.state.doc.textBetween(from - 1, from, void 0, "\uFFFC") : "";
5755
+ const needsSpace = before !== "" && !/\s/.test(before);
5756
+ editor.chain().focus().insertContent(needsSpace ? " @" : "@").run();
5757
+ }
5758
+ function dismissMentionSuggestion(editor) {
5759
+ suggestion.exitSuggestion(editor.view);
5760
+ }
5761
+ var MentionSuggestion = react.Extension.create({
5762
+ name: "userMentionSuggestion",
5763
+ addOptions() {
5764
+ return {
5765
+ isEnabled: () => false,
5766
+ search: async () => [],
5767
+ render: () => ({})
5768
+ };
5769
+ },
5770
+ addProseMirrorPlugins() {
5771
+ const { isEnabled, search, render } = this.options;
5772
+ return [
5773
+ suggestion.Suggestion({
5774
+ editor: this.editor,
5775
+ char: "@",
5776
+ debounce: MENTION_SEARCH_DEBOUNCE_MS,
5777
+ allow: () => isEnabled(),
5778
+ items: ({ query, signal }) => search(query, signal),
5779
+ command: ({ editor, range, props }) => insertUserMention(editor, range, props),
5780
+ render
5781
+ })
5782
+ ];
5783
+ }
5784
+ });
5785
+ function userMentionLabel(attrs, getUserName) {
5786
+ return getUserName?.(attrs.userId) ?? attrs.name ?? attrs.userId;
5787
+ }
5788
+ function UserMentionNodeView({ node }) {
5789
+ const mentions = useMentions();
5790
+ const attrs = node.attrs;
5791
+ return /* @__PURE__ */ jsxRuntime.jsxs(react.NodeViewWrapper, { as: "span", className: "bk-user-mention", "data-user-mention": attrs.userId, children: [
5792
+ "@",
5793
+ userMentionLabel(attrs, mentions?.getUserName)
5794
+ ] });
5795
+ }
5796
+ var UserMentionNode = react.Node.create({
5797
+ name: USER_MENTION_NODE,
5798
+ group: "inline",
5799
+ inline: true,
5800
+ atom: true,
5801
+ selectable: true,
5802
+ addAttributes() {
5803
+ return {
5804
+ userId: {
5805
+ default: "",
5806
+ parseHTML: (element) => element.getAttribute("data-user-mention") ?? "",
5807
+ renderHTML: (attributes) => ({ "data-user-mention": attributes.userId })
5808
+ },
5809
+ name: {
5810
+ default: null,
5811
+ parseHTML: (element) => element.getAttribute("data-user-name"),
5812
+ renderHTML: (attributes) => attributes.name ? { "data-user-name": attributes.name } : {}
5813
+ }
5814
+ };
5815
+ },
5816
+ parseHTML() {
5817
+ return [{ tag: "span[data-user-mention]" }];
5818
+ },
5819
+ renderHTML({ node, HTMLAttributes }) {
5820
+ return ["span", react.mergeAttributes(HTMLAttributes), `@${userMentionLabel(node.attrs)}`];
5821
+ },
5822
+ renderText({ node }) {
5823
+ return `@${userMentionLabel(node.attrs)}`;
5824
+ },
5825
+ addNodeView() {
5826
+ return react.ReactNodeViewRenderer(UserMentionNodeView);
5827
+ }
5828
+ });
5614
5829
 
5615
5830
  // src/lib/emoji-recents.ts
5616
5831
  var STORAGE_KEY = "bk:emoji-recents";
@@ -5892,6 +6107,133 @@ function EmojiPickerButton({
5892
6107
  ) })
5893
6108
  ] });
5894
6109
  }
6110
+ function nextMentionPickerState(props, prev) {
6111
+ return {
6112
+ items: props.loading && prev ? prev.items : props.items,
6113
+ loading: props.loading,
6114
+ clientRect: props.clientRect ?? null,
6115
+ command: props.command
6116
+ };
6117
+ }
6118
+ function MentionPicker({
6119
+ editor,
6120
+ state,
6121
+ keyHandlerRef,
6122
+ onDismiss
6123
+ }) {
6124
+ const { items, loading, clientRect, command } = state;
6125
+ const [activeIndex, setActiveIndex] = React.useState(0);
6126
+ const [shownItems, setShownItems] = React.useState(items);
6127
+ if (shownItems !== items) {
6128
+ setShownItems(items);
6129
+ setActiveIndex(0);
6130
+ }
6131
+ const listRef = React.useRef(null);
6132
+ React.useEffect(() => {
6133
+ keyHandlerRef.current = (event) => {
6134
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
6135
+ if (items.length > 0) {
6136
+ const step = event.key === "ArrowDown" ? 1 : -1;
6137
+ setActiveIndex((index) => (index + step + items.length) % items.length);
6138
+ }
6139
+ return true;
6140
+ }
6141
+ if (event.key === "Enter" || event.key === "Tab") {
6142
+ const user = items[activeIndex];
6143
+ if (!user) {
6144
+ return false;
6145
+ }
6146
+ command(user);
6147
+ return true;
6148
+ }
6149
+ return false;
6150
+ };
6151
+ return () => {
6152
+ keyHandlerRef.current = () => false;
6153
+ };
6154
+ }, [keyHandlerRef, items, activeIndex, command]);
6155
+ React.useEffect(() => {
6156
+ const row = listRef.current?.querySelectorAll('[role="option"]')[activeIndex];
6157
+ row?.scrollIntoView?.({ block: "nearest" });
6158
+ }, [activeIndex]);
6159
+ const anchorRef = React.useMemo(
6160
+ () => ({
6161
+ current: {
6162
+ getBoundingClientRect: () => clientRect?.() ?? editor.view.dom.getBoundingClientRect()
6163
+ }
6164
+ }),
6165
+ [clientRect, editor]
6166
+ );
6167
+ return /* @__PURE__ */ jsxRuntime.jsxs(
6168
+ Popover,
6169
+ {
6170
+ open: true,
6171
+ onOpenChange: (open) => {
6172
+ if (!open) {
6173
+ onDismiss();
6174
+ }
6175
+ },
6176
+ children: [
6177
+ /* @__PURE__ */ jsxRuntime.jsx(PopoverAnchor, { virtualRef: anchorRef }),
6178
+ /* @__PURE__ */ jsxRuntime.jsx(
6179
+ PopoverContent,
6180
+ {
6181
+ side: "bottom",
6182
+ align: "start",
6183
+ sideOffset: 4,
6184
+ className: "max-h-[min(15rem,var(--radix-popover-content-available-height))] w-64 p-1",
6185
+ onOpenAutoFocus: (e) => e.preventDefault(),
6186
+ onCloseAutoFocus: (e) => e.preventDefault(),
6187
+ onEscapeKeyDown: (e) => e.stopPropagation(),
6188
+ onInteractOutside: (e) => {
6189
+ if (e.target instanceof Node && editor.view.dom.contains(e.target)) {
6190
+ e.preventDefault();
6191
+ }
6192
+ },
6193
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: listRef, role: "listbox", "aria-label": "People to mention", children: items.length === 0 ? /* @__PURE__ */ jsxRuntime.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) => (
6194
+ // biome-ignore lint/a11y/useFocusableInteractive: focus stays in the editor, which drives this list through the suggestion plugin's key handling.
6195
+ // biome-ignore lint/a11y/useKeyWithClickEvents: the keyboard path is the editor's, forwarded via keyHandlerRef.
6196
+ /* @__PURE__ */ jsxRuntime.jsxs(
6197
+ "div",
6198
+ {
6199
+ role: "option",
6200
+ "aria-selected": index === activeIndex,
6201
+ onPointerDown: (e) => e.preventDefault(),
6202
+ onClick: () => command(user),
6203
+ onMouseMove: () => setActiveIndex(index),
6204
+ className: cn(
6205
+ "flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-sm text-foreground",
6206
+ index === activeIndex && "bg-accent"
6207
+ ),
6208
+ children: [
6209
+ /* @__PURE__ */ jsxRuntime.jsx(MentionAvatar, { user }),
6210
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: user.name })
6211
+ ]
6212
+ },
6213
+ user.id
6214
+ )
6215
+ )) })
6216
+ }
6217
+ )
6218
+ ]
6219
+ }
6220
+ );
6221
+ }
6222
+ function MentionAvatar({ user }) {
6223
+ const [failed, setFailed] = React.useState(false);
6224
+ const src = user.avatarUrl && isSafeImageSrc(user.avatarUrl) ? user.avatarUrl : null;
6225
+ if (src && !failed) {
6226
+ return /* @__PURE__ */ jsxRuntime.jsx("img", { src, alt: "", className: "h-5 w-5 shrink-0 rounded object-cover", onError: () => setFailed(true) });
6227
+ }
6228
+ return /* @__PURE__ */ jsxRuntime.jsx(
6229
+ "span",
6230
+ {
6231
+ "aria-hidden": "true",
6232
+ className: "flex h-5 w-5 shrink-0 items-center justify-center rounded bg-muted text-[10px] font-medium text-muted-foreground",
6233
+ children: Array.from(user.name.trim())[0]?.toUpperCase() ?? "@"
6234
+ }
6235
+ );
6236
+ }
5895
6237
  var BLOCK_ELEMENT_LABEL = {
5896
6238
  rich_text_section: "Section",
5897
6239
  rich_text_list: "List",
@@ -6467,7 +6809,7 @@ function LossyBanner({ reasons }) {
6467
6809
  ] });
6468
6810
  }
6469
6811
  var RICH_TEXT_INPUT_RULE_ALLOWLIST = ["bold", "italic", "strike", "code"];
6470
- function buildRichTextExtensions() {
6812
+ function buildRichTextExtensions(mentionSuggestion) {
6471
6813
  return [
6472
6814
  StarterKit__default.default.configure({
6473
6815
  heading: false,
@@ -6500,7 +6842,11 @@ function buildRichTextExtensions() {
6500
6842
  // via the link button.
6501
6843
  shouldAutoLink: hasExplicitSafeScheme,
6502
6844
  HTMLAttributes: { rel: "noreferrer noopener", target: "_blank" }
6503
- })
6845
+ }),
6846
+ // Always registered, so mentions already in the content round-trip with
6847
+ // or without the picker.
6848
+ UserMentionNode,
6849
+ ...mentionSuggestion ? [MentionSuggestion.configure(mentionSuggestion)] : []
6504
6850
  ];
6505
6851
  }
6506
6852
  function RichTextWysiwygEditor({ block, onChange }) {
@@ -6527,8 +6873,25 @@ function RichTextWysiwygEditor({ block, onChange }) {
6527
6873
  },
6528
6874
  [onChange]
6529
6875
  );
6876
+ const mentions = useMentions();
6877
+ const mentionsEnabled = mentions !== null;
6878
+ const mentionsRef = React.useRef(mentions);
6879
+ React.useEffect(() => {
6880
+ mentionsRef.current = mentions;
6881
+ }, [mentions]);
6882
+ const [mentionPicker, setMentionPicker] = React.useState(null);
6883
+ const mentionKeyHandlerRef = React.useRef(() => false);
6530
6884
  const editor = react.useEditor({
6531
- extensions: buildRichTextExtensions(),
6885
+ extensions: buildRichTextExtensions({
6886
+ isEnabled: () => mentionsRef.current !== null,
6887
+ search: async (query, signal) => await mentionsRef.current?.searchUsers(query, { signal }) ?? [],
6888
+ render: () => ({
6889
+ onStart: (props) => setMentionPicker((prev) => nextMentionPickerState(props, prev)),
6890
+ onUpdate: (props) => setMentionPicker((prev) => nextMentionPickerState(props, prev)),
6891
+ onExit: () => setMentionPicker(null),
6892
+ onKeyDown: ({ event }) => mentionKeyHandlerRef.current(event)
6893
+ })
6894
+ }),
6532
6895
  enableInputRules: RICH_TEXT_INPUT_RULE_ALLOWLIST,
6533
6896
  content: richTextToProseMirror(block, resolveEmoji),
6534
6897
  editorProps: {
@@ -6538,10 +6901,16 @@ function RichTextWysiwygEditor({ block, onChange }) {
6538
6901
  },
6539
6902
  onUpdate: ({ editor: ed }) => handleUpdate(ed)
6540
6903
  });
6904
+ const seenBlockRef = React.useRef(block);
6541
6905
  React.useEffect(() => {
6542
6906
  if (!editor) {
6543
6907
  return;
6544
6908
  }
6909
+ const blockChanged = seenBlockRef.current !== block;
6910
+ seenBlockRef.current = block;
6911
+ if (!blockChanged && lastEmittedRef.current) {
6912
+ return;
6913
+ }
6545
6914
  if (lastEmittedRef.current && JSON.stringify(lastEmittedRef.current) === JSON.stringify(block)) {
6546
6915
  return;
6547
6916
  }
@@ -6553,11 +6922,26 @@ function RichTextWysiwygEditor({ block, onChange }) {
6553
6922
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-[120px] rounded-md border border-input bg-background" });
6554
6923
  }
6555
6924
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
6556
- /* @__PURE__ */ jsxRuntime.jsx(Toolbar, { editor }),
6557
- /* @__PURE__ */ jsxRuntime.jsx(react.EditorContent, { editor })
6925
+ /* @__PURE__ */ jsxRuntime.jsx(
6926
+ Toolbar,
6927
+ {
6928
+ editor,
6929
+ mention: mentionsEnabled ? { active: mentionPicker !== null, insert: () => insertMentionTrigger(editor) } : null
6930
+ }
6931
+ ),
6932
+ /* @__PURE__ */ jsxRuntime.jsx(react.EditorContent, { editor }),
6933
+ mentionPicker && /* @__PURE__ */ jsxRuntime.jsx(
6934
+ MentionPicker,
6935
+ {
6936
+ editor,
6937
+ state: mentionPicker,
6938
+ keyHandlerRef: mentionKeyHandlerRef,
6939
+ onDismiss: () => dismissMentionSuggestion(editor)
6940
+ }
6941
+ )
6558
6942
  ] });
6559
6943
  }
6560
- function Toolbar({ editor }) {
6944
+ function Toolbar({ editor, mention }) {
6561
6945
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-0.5 rounded-md border bg-muted/30 p-1", children: [
6562
6946
  /* @__PURE__ */ jsxRuntime.jsx(
6563
6947
  ToolbarButton,
@@ -6616,6 +7000,7 @@ function Toolbar({ editor }) {
6616
7000
  }).run()
6617
7001
  }
6618
7002
  ),
7003
+ mention && /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { label: "Mention", active: mention.active, onClick: mention.insert, Icon: lucideReact.AtSign }),
6619
7004
  /* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
6620
7005
  /* @__PURE__ */ jsxRuntime.jsx(
6621
7006
  ToolbarButton,
@@ -7479,6 +7864,387 @@ function ImageItemCard({
7479
7864
  ) })
7480
7865
  ] });
7481
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
+ }
7482
8248
  var CHART_TYPES = ["line", "bar", "area", "pie"];
7483
8249
  function isCartesian(chart) {
7484
8250
  return chart.type !== "pie";
@@ -7533,46 +8299,6 @@ function DataVisualizationEditor({ block, onChange }) {
7533
8299
  isCartesian(chart) ? /* @__PURE__ */ jsxRuntime.jsx(CartesianFields, { chart, onChange: setChart }) : /* @__PURE__ */ jsxRuntime.jsx(PieFields, { chart, onChange: setChart })
7534
8300
  ] });
7535
8301
  }
7536
- function NumberInput({
7537
- id,
7538
- value,
7539
- onCommit,
7540
- ariaLabel,
7541
- className
7542
- }) {
7543
- const [draft, setDraft] = React.useState(String(value));
7544
- React.useEffect(() => {
7545
- setDraft(String(value));
7546
- }, [value]);
7547
- return /* @__PURE__ */ jsxRuntime.jsx(
7548
- Input,
7549
- {
7550
- id,
7551
- type: "number",
7552
- inputMode: "numeric",
7553
- "aria-label": ariaLabel,
7554
- value: draft,
7555
- className,
7556
- onChange: (e) => {
7557
- const raw = e.target.value;
7558
- setDraft(raw);
7559
- if (raw === "" || raw === "-" || raw === "." || raw === "-.") {
7560
- return;
7561
- }
7562
- const next = Number(raw);
7563
- if (!Number.isNaN(next)) {
7564
- onCommit(next);
7565
- }
7566
- },
7567
- onBlur: () => {
7568
- const next = Number(draft);
7569
- const safe = draft.trim() === "" || Number.isNaN(next) ? 0 : next;
7570
- onCommit(safe);
7571
- setDraft(String(safe));
7572
- }
7573
- }
7574
- );
7575
- }
7576
8302
  function CartesianFields({ chart, onChange }) {
7577
8303
  const series = chart.series;
7578
8304
  const categories = chart.axis_config?.categories && chart.axis_config.categories.length > 0 ? chart.axis_config.categories : series[0]?.data.map((d) => d.label) ?? [];
@@ -9115,45 +9841,13 @@ function ImageAccessoryFields({ image, onChange }) {
9115
9841
  ] });
9116
9842
  }
9117
9843
  var ALIGN_OPTIONS = ["left", "center", "right"];
9118
- function cellAsText(cell) {
9119
- if (cell.type === "raw_text") {
9120
- return cell.text;
9121
- }
9122
- return "[rich_text]";
9844
+ function alignColumnSettings(settings, edit) {
9845
+ return edit.kind === "add" ? [...settings, null] : settings.filter((_, c) => c !== edit.index);
9123
9846
  }
9124
9847
  function TableEditor({ block, onChange }) {
9125
9848
  const rows = block.rows ?? [];
9126
9849
  const columnCount = rows[0]?.length ?? 0;
9127
9850
  const columnSettings = block.column_settings ?? [];
9128
- const setCell = (rowIdx, colIdx, text) => {
9129
- const nextRows = rows.map(
9130
- (row, r) => r === rowIdx ? row.map((cell, c) => c === colIdx ? { type: "raw_text", text } : cell) : row
9131
- );
9132
- onChange({ ...block, rows: nextRows });
9133
- };
9134
- const addRow = () => {
9135
- const blankRow = Array.from({ length: columnCount || 1 }, () => ({ type: "raw_text", text: "" }));
9136
- onChange({ ...block, rows: [...rows, blankRow] });
9137
- };
9138
- const removeRow = (rowIdx) => {
9139
- if (rows.length <= 1) {
9140
- return;
9141
- }
9142
- onChange({ ...block, rows: rows.filter((_, r) => r !== rowIdx) });
9143
- };
9144
- const addColumn = () => {
9145
- const nextRows = rows.map((row) => [...row, { type: "raw_text", text: "" }]);
9146
- const nextSettings = [...columnSettings, null];
9147
- onChange({ ...block, rows: nextRows, column_settings: nextSettings });
9148
- };
9149
- const removeColumn = (colIdx) => {
9150
- if (columnCount <= 1) {
9151
- return;
9152
- }
9153
- const nextRows = rows.map((row) => row.filter((_, c) => c !== colIdx));
9154
- const nextSettings = columnSettings.filter((_, c) => c !== colIdx);
9155
- onChange({ ...block, rows: nextRows, column_settings: nextSettings });
9156
- };
9157
9851
  const setColumnAlign = (colIdx, align) => {
9158
9852
  const next = Array.from({ length: columnCount }, (_, i) => columnSettings[i] ?? null);
9159
9853
  if (align === "") {
@@ -9164,81 +9858,45 @@ function TableEditor({ block, onChange }) {
9164
9858
  }
9165
9859
  onChange({ ...block, column_settings: next });
9166
9860
  };
9167
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3", children: [
9168
- /* @__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: [
9169
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
9170
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-6 shrink-0", "aria-hidden": "true" }),
9171
- Array.from({ length: columnCount }, (_, c) => {
9172
- const setting = columnSettings[c];
9173
- const align = setting?.align ?? "";
9174
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-[120px] flex-1 items-center gap-1", children: [
9175
- /* @__PURE__ */ jsxRuntime.jsxs(
9176
- "select",
9177
- {
9178
- "aria-label": `Column ${c + 1} alignment`,
9179
- value: align,
9180
- onChange: (e) => setColumnAlign(c, e.target.value),
9181
- className: "h-7 flex-1 rounded-sm border border-input bg-background px-1.5 text-[11px] text-muted-foreground",
9182
- children: [
9183
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "Default" }),
9184
- ALIGN_OPTIONS.map((a) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: a, children: a }, a))
9185
- ]
9186
- }
9187
- ),
9188
- /* @__PURE__ */ jsxRuntime.jsx(
9189
- "button",
9190
- {
9191
- type: "button",
9192
- "aria-label": `Remove column ${c + 1}`,
9193
- onClick: () => removeColumn(c),
9194
- disabled: columnCount <= 1,
9195
- 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",
9196
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-3 w-3" })
9197
- }
9198
- )
9199
- ] }, c);
9200
- })
9201
- ] }),
9202
- rows.map((row, r) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
9203
- /* @__PURE__ */ jsxRuntime.jsx(
9204
- "button",
9205
- {
9206
- type: "button",
9207
- "aria-label": `Remove row ${r + 1}`,
9208
- onClick: () => removeRow(r),
9209
- disabled: rows.length <= 1,
9210
- 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",
9211
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-3 w-3" })
9212
- }
9213
- ),
9214
- row.map((cell, c) => {
9215
- const isRichText = cell.type === "rich_text";
9216
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-[120px] flex-1 flex-col gap-0.5", children: [
9217
- /* @__PURE__ */ jsxRuntime.jsx(
9218
- Input,
9219
- {
9220
- value: cellAsText(cell),
9221
- placeholder: r === 0 ? `Header ${c + 1}` : "Cell text",
9222
- onChange: (e) => setCell(r, c, e.target.value),
9223
- className: "h-8 text-xs"
9224
- }
9225
- ),
9226
- isRichText ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-muted-foreground", children: "Was rich-text; editing replaces with plain text." }) : null
9227
- ] }, c);
9228
- })
9229
- ] }, r))
9230
- ] }) }),
9231
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
9232
- /* @__PURE__ */ jsxRuntime.jsxs(Button, { type: "button", size: "sm", onClick: addRow, disabled: rows.length >= 100, children: [
9233
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-3.5 w-3.5" }),
9234
- " Add row"
9235
- ] }),
9236
- /* @__PURE__ */ jsxRuntime.jsxs(Button, { type: "button", size: "sm", onClick: addColumn, disabled: columnCount >= 20, children: [
9237
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-3.5 w-3.5" }),
9238
- " Add column"
9239
- ] })
9240
- ] })
9241
- ] });
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
+ );
9242
9900
  }
9243
9901
  var STATUSES2 = ["pending", "in_progress", "complete", "error"];
9244
9902
  function TaskCardEditor({ block, onChange }) {
@@ -9579,6 +10237,8 @@ function dispatch(block, onChange) {
9579
10237
  return /* @__PURE__ */ jsxRuntime.jsx(DataVisualizationEditor, { block, onChange: (next) => onChange(next) });
9580
10238
  case "container":
9581
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) });
9582
10242
  default:
9583
10243
  return null;
9584
10244
  }
@@ -9602,7 +10262,8 @@ var BLOCK_TYPE_LABELS = {
9602
10262
  plan: "Plan",
9603
10263
  task_card: "Task Card",
9604
10264
  data_visualization: "Data Visualization",
9605
- container: "Container"
10265
+ container: "Container",
10266
+ data_table: "Data Table"
9606
10267
  };
9607
10268
  function BlockRow({
9608
10269
  builderBlock,
@@ -10952,6 +11613,7 @@ function BlockKitchen(props) {
10952
11613
  onValidationChange,
10953
11614
  previewHooks,
10954
11615
  customEmojis,
11616
+ mentions,
10955
11617
  loadChannels,
10956
11618
  loadSendAsUserStatus,
10957
11619
  onSend,
@@ -11312,7 +11974,7 @@ function BlockKitchen(props) {
11312
11974
  }
11313
11975
  )
11314
11976
  ] }),
11315
- /* @__PURE__ */ jsxRuntime.jsx(
11977
+ /* @__PURE__ */ jsxRuntime.jsx(MentionsProvider, { mentions, children: /* @__PURE__ */ jsxRuntime.jsx(
11316
11978
  Surface,
11317
11979
  {
11318
11980
  blocks,
@@ -11332,7 +11994,7 @@ function BlockKitchen(props) {
11332
11994
  isPaletteDrag: activePaletteVariant !== null,
11333
11995
  onOpenPalette: () => setPaletteOpen(true)
11334
11996
  }
11335
- )
11997
+ ) })
11336
11998
  ] })
11337
11999
  ]
11338
12000
  }