@tightknitai/block-kitchen 0.9.9 → 0.9.10

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.d.cts CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as react from 'react';
2
- import { B as BlockKitchenProps, $ as Template, K as PreviewSurface, N as PreviewTheme } from './palette-CKP8CK5Z.cjs';
3
- export { A as AlertBlock, a as AlertLevel, b as BrandPreset, c as BrandTheme, d as BrandTokens, e as BuilderBlock, C as CardBlock, f as CarouselBlock, g as CartesianChart, h as ChannelOption, i as Chart, j as ChartAxisConfig, k as ChartDataPoint, l as ChartSeries, m as ChartType, n as ContainerBlock, o as ContainerChildBlock, p as ContextActionsBlock, q as ContextActionsElement, r as CustomEmoji, D as DataVisualizationBlock, E as EditableVia, s as EditingConfig, F as FeedbackButtonSubobject, t as FeedbackButtonsElement, H as HeaderLevel, I as IconButtonElement, u as IconButtonIcon, v as InputBlock, L as LoadMessageInput, w as LoadResult, M as MarkdownBlock, P as PaletteSection, x as PaletteVariant, y as PieChart, z as PieChartSegment, G as PlanBlock, J as PreviewHooks, R as RecentMessage, S as SendAsUserStatus, O as SendPayload, Q as SendResult, T as SupportedBlock, U as SupportedBlockType, V as SupportedHeaderBlock, W as TableBlock, X as TableCell, Y as TableColumnSetting, Z as TaskCardBlock, _ as TaskCardStatus, a0 as UpdatePayload, a1 as UpdateResult, a2 as UrlSourceElement, a3 as VideoBlock, a5 as defaultPalette, a6 as extraAlertVariant, a7 as legacyInputVariants } from './palette-CKP8CK5Z.cjs';
2
+ import { ReactNode } from 'react';
3
+ import { c as BlockKitchenProps, Z as SupportedBlock, k as ChannelOption, V as SendAsUserStatus, X as SendPayload, W as SendExtrasContext, a5 as Template, Q as PreviewSurface, R as PreviewTheme } from './palette-Duj901T6.cjs';
4
+ export { A as AlertBlock, a as AlertLevel, B as BlockKitchenBaseProps, b as BlockKitchenComposeOnlyProps, d as BlockKitchenSendProps, e as BrandPreset, f as BrandTheme, g as BrandTokens, h as BuilderBlock, C as CardBlock, i as CarouselBlock, j as CartesianChart, l as Chart, m as ChartAxisConfig, n as ChartDataPoint, o as ChartSeries, p as ChartType, q as ContainerBlock, r as ContainerChildBlock, s as ContextActionsBlock, t as ContextActionsElement, u as CustomEmoji, D as DataVisualizationBlock, E as EditableVia, v as EditingConfig, F as FeedbackButtonSubobject, w as FeedbackButtonsElement, H as HeaderLevel, I as IconButtonElement, x as IconButtonIcon, y as InputBlock, L as LoadMessageInput, z as LoadResult, M as MarkdownBlock, P as PaletteSection, G as PaletteVariant, J as PieChart, K as PieChartSegment, N as PlanBlock, O as PreviewHooks, S as PrimaryActionConfig, T as PrimaryActionContext, U as RecentMessage, Y as SendResult, _ as SupportedBlockType, $ as SupportedHeaderBlock, a0 as TableBlock, a1 as TableCell, a2 as TableColumnSetting, a3 as TaskCardBlock, a4 as TaskCardStatus, a6 as UpdatePayload, a7 as UpdateResult, a8 as UrlSourceElement, a9 as ValidationSummary, aa as VideoBlock, ac as defaultPalette, ad as extraAlertVariant, ae as legacyInputVariants } from './palette-Duj901T6.cjs';
4
5
  export { decodeBlocksFromString, encodeBlocksToString, toSlackBlocks } from './helpers.cjs';
5
6
  import 'slack-web-api-client';
6
7
 
@@ -14,6 +15,85 @@ import 'slack-web-api-client';
14
15
  */
15
16
  declare function BlockKitchen(props: BlockKitchenProps): react.JSX.Element;
16
17
 
18
+ /**
19
+ * Props for {@link SendDialog}. Exported so hosts building a bespoke send
20
+ * flow (compose-only mode) can reuse the built-in dialog standalone instead
21
+ * of rebuilding channel loading, identity, and sending states from scratch.
22
+ */
23
+ interface SendDialogProps {
24
+ /** Whether the dialog is open. */
25
+ open: boolean;
26
+ /** Notified when the user closes the dialog. */
27
+ onOpenChange: (open: boolean) => void;
28
+ /** The draft blocks to send (builder-native format). */
29
+ blocks: SupportedBlock[];
30
+ /** Returns channels available to send to. Called when the dialog opens. */
31
+ loadChannels: () => Promise<ChannelOption[]>;
32
+ /** Returns user-token status + OAuth URL. */
33
+ loadSendAsUserStatus: () => Promise<SendAsUserStatus>;
34
+ /** Terminal action; should return `{ ok }` or `{ ok: false, error }`. */
35
+ onSend: (payload: SendPayload) => Promise<{
36
+ ok: boolean;
37
+ error?: string;
38
+ }>;
39
+ /**
40
+ * Renders host-defined fields below the built-in pickers; collected
41
+ * values are delivered on {@link SendPayload.extras}.
42
+ */
43
+ renderSendExtras?: (context: SendExtrasContext) => ReactNode;
44
+ /** Label for the final confirm button. Defaults to `'Send'`. */
45
+ confirmSendLabel?: string;
46
+ /** Total validation errors against the current draft. */
47
+ errorCount: number;
48
+ /** Asks the parent to open the global issues panel. */
49
+ onShowIssues?: () => void;
50
+ }
51
+ /**
52
+ * Modal dialog that collects the destination channel + send-as identity
53
+ * (plus any host-defined extras), then calls the consumer's `onSend`.
54
+ *
55
+ * Channels and user-token status are loaded async via callback props on open.
56
+ * The consumer brokers all I/O; the dialog never makes a network call.
57
+ * @param props - {@link SendDialogProps}
58
+ * @returns the rendered send dialog
59
+ */
60
+ declare function SendDialog({ open, onOpenChange, blocks, loadChannels, loadSendAsUserStatus, onSend, renderSendExtras, confirmSendLabel, errorCount, onShowIssues }: SendDialogProps): react.JSX.Element;
61
+
62
+ /**
63
+ * Tracks the "act as the user" Slack token status for a dialog and drives the
64
+ * OAuth sign-in flow. Shared by {@link SendDialog} and {@link UpdateDialog}.
65
+ *
66
+ * While `open && enabled` it fetches status on open and refreshes on window
67
+ * focus. Once `startSignIn` opens the OAuth tab it also background-polls until
68
+ * the token appears or the retry cap is hit, so the dialog unlocks itself
69
+ * without a manual reload. All work stops when the dialog closes, the token
70
+ * path doesn't apply, or the component unmounts.
71
+ *
72
+ * @param loadStatus - consumer callback returning user-token status + OAuth URL
73
+ * @param options.open - whether the host dialog is open
74
+ * @param options.enabled - whether the user-token path applies (idle when false)
75
+ * @returns `{ userStatus, polling, startSignIn }`
76
+ */
77
+ declare function useSlackSignIn(loadStatus: () => Promise<SendAsUserStatus>, { open, enabled }: {
78
+ open: boolean;
79
+ enabled: boolean;
80
+ }): {
81
+ userStatus: SendAsUserStatus | null;
82
+ polling: boolean;
83
+ startSignIn: () => void;
84
+ };
85
+ /**
86
+ * The "Sign in with Slack" button used by both dialogs. Opens OAuth and shows a
87
+ * spinner while {@link useSlackSignIn} polls for the completed round-trip. The
88
+ * surrounding explanatory copy stays with each caller since it differs.
89
+ * @param props.onClick - starts the sign-in flow (typically `startSignIn`)
90
+ * @param props.polling - whether a background poll is in flight
91
+ */
92
+ declare function SlackSignInButton({ onClick, polling }: {
93
+ onClick: () => void;
94
+ polling: boolean;
95
+ }): react.JSX.Element;
96
+
17
97
  /**
18
98
  * Standalone, page-sized picker that displays {@link Template}s as a
19
99
  * grid of cards with live block previews, organized into category
@@ -51,4 +131,4 @@ declare function TemplatePicker({ templates, onSelect, surface, theme, heading,
51
131
  className?: string;
52
132
  }): react.JSX.Element;
53
133
 
54
- export { BlockKitchen, BlockKitchenProps, PreviewSurface, PreviewTheme, Template, TemplatePicker };
134
+ export { BlockKitchen, BlockKitchenProps, ChannelOption, PreviewSurface, PreviewTheme, SendAsUserStatus, SendDialog, type SendDialogProps, SendExtrasContext, SendPayload, SlackSignInButton, SupportedBlock, Template, TemplatePicker, useSlackSignIn };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as react from 'react';
2
- import { B as BlockKitchenProps, $ as Template, K as PreviewSurface, N as PreviewTheme } from './palette-CKP8CK5Z.js';
3
- export { A as AlertBlock, a as AlertLevel, b as BrandPreset, c as BrandTheme, d as BrandTokens, e as BuilderBlock, C as CardBlock, f as CarouselBlock, g as CartesianChart, h as ChannelOption, i as Chart, j as ChartAxisConfig, k as ChartDataPoint, l as ChartSeries, m as ChartType, n as ContainerBlock, o as ContainerChildBlock, p as ContextActionsBlock, q as ContextActionsElement, r as CustomEmoji, D as DataVisualizationBlock, E as EditableVia, s as EditingConfig, F as FeedbackButtonSubobject, t as FeedbackButtonsElement, H as HeaderLevel, I as IconButtonElement, u as IconButtonIcon, v as InputBlock, L as LoadMessageInput, w as LoadResult, M as MarkdownBlock, P as PaletteSection, x as PaletteVariant, y as PieChart, z as PieChartSegment, G as PlanBlock, J as PreviewHooks, R as RecentMessage, S as SendAsUserStatus, O as SendPayload, Q as SendResult, T as SupportedBlock, U as SupportedBlockType, V as SupportedHeaderBlock, W as TableBlock, X as TableCell, Y as TableColumnSetting, Z as TaskCardBlock, _ as TaskCardStatus, a0 as UpdatePayload, a1 as UpdateResult, a2 as UrlSourceElement, a3 as VideoBlock, a5 as defaultPalette, a6 as extraAlertVariant, a7 as legacyInputVariants } from './palette-CKP8CK5Z.js';
2
+ import { ReactNode } from 'react';
3
+ import { c as BlockKitchenProps, Z as SupportedBlock, k as ChannelOption, V as SendAsUserStatus, X as SendPayload, W as SendExtrasContext, a5 as Template, Q as PreviewSurface, R as PreviewTheme } from './palette-Duj901T6.js';
4
+ export { A as AlertBlock, a as AlertLevel, B as BlockKitchenBaseProps, b as BlockKitchenComposeOnlyProps, d as BlockKitchenSendProps, e as BrandPreset, f as BrandTheme, g as BrandTokens, h as BuilderBlock, C as CardBlock, i as CarouselBlock, j as CartesianChart, l as Chart, m as ChartAxisConfig, n as ChartDataPoint, o as ChartSeries, p as ChartType, q as ContainerBlock, r as ContainerChildBlock, s as ContextActionsBlock, t as ContextActionsElement, u as CustomEmoji, D as DataVisualizationBlock, E as EditableVia, v as EditingConfig, F as FeedbackButtonSubobject, w as FeedbackButtonsElement, H as HeaderLevel, I as IconButtonElement, x as IconButtonIcon, y as InputBlock, L as LoadMessageInput, z as LoadResult, M as MarkdownBlock, P as PaletteSection, G as PaletteVariant, J as PieChart, K as PieChartSegment, N as PlanBlock, O as PreviewHooks, S as PrimaryActionConfig, T as PrimaryActionContext, U as RecentMessage, Y as SendResult, _ as SupportedBlockType, $ as SupportedHeaderBlock, a0 as TableBlock, a1 as TableCell, a2 as TableColumnSetting, a3 as TaskCardBlock, a4 as TaskCardStatus, a6 as UpdatePayload, a7 as UpdateResult, a8 as UrlSourceElement, a9 as ValidationSummary, aa as VideoBlock, ac as defaultPalette, ad as extraAlertVariant, ae as legacyInputVariants } from './palette-Duj901T6.js';
4
5
  export { decodeBlocksFromString, encodeBlocksToString, toSlackBlocks } from './helpers.js';
5
6
  import 'slack-web-api-client';
6
7
 
@@ -14,6 +15,85 @@ import 'slack-web-api-client';
14
15
  */
15
16
  declare function BlockKitchen(props: BlockKitchenProps): react.JSX.Element;
16
17
 
18
+ /**
19
+ * Props for {@link SendDialog}. Exported so hosts building a bespoke send
20
+ * flow (compose-only mode) can reuse the built-in dialog standalone instead
21
+ * of rebuilding channel loading, identity, and sending states from scratch.
22
+ */
23
+ interface SendDialogProps {
24
+ /** Whether the dialog is open. */
25
+ open: boolean;
26
+ /** Notified when the user closes the dialog. */
27
+ onOpenChange: (open: boolean) => void;
28
+ /** The draft blocks to send (builder-native format). */
29
+ blocks: SupportedBlock[];
30
+ /** Returns channels available to send to. Called when the dialog opens. */
31
+ loadChannels: () => Promise<ChannelOption[]>;
32
+ /** Returns user-token status + OAuth URL. */
33
+ loadSendAsUserStatus: () => Promise<SendAsUserStatus>;
34
+ /** Terminal action; should return `{ ok }` or `{ ok: false, error }`. */
35
+ onSend: (payload: SendPayload) => Promise<{
36
+ ok: boolean;
37
+ error?: string;
38
+ }>;
39
+ /**
40
+ * Renders host-defined fields below the built-in pickers; collected
41
+ * values are delivered on {@link SendPayload.extras}.
42
+ */
43
+ renderSendExtras?: (context: SendExtrasContext) => ReactNode;
44
+ /** Label for the final confirm button. Defaults to `'Send'`. */
45
+ confirmSendLabel?: string;
46
+ /** Total validation errors against the current draft. */
47
+ errorCount: number;
48
+ /** Asks the parent to open the global issues panel. */
49
+ onShowIssues?: () => void;
50
+ }
51
+ /**
52
+ * Modal dialog that collects the destination channel + send-as identity
53
+ * (plus any host-defined extras), then calls the consumer's `onSend`.
54
+ *
55
+ * Channels and user-token status are loaded async via callback props on open.
56
+ * The consumer brokers all I/O; the dialog never makes a network call.
57
+ * @param props - {@link SendDialogProps}
58
+ * @returns the rendered send dialog
59
+ */
60
+ declare function SendDialog({ open, onOpenChange, blocks, loadChannels, loadSendAsUserStatus, onSend, renderSendExtras, confirmSendLabel, errorCount, onShowIssues }: SendDialogProps): react.JSX.Element;
61
+
62
+ /**
63
+ * Tracks the "act as the user" Slack token status for a dialog and drives the
64
+ * OAuth sign-in flow. Shared by {@link SendDialog} and {@link UpdateDialog}.
65
+ *
66
+ * While `open && enabled` it fetches status on open and refreshes on window
67
+ * focus. Once `startSignIn` opens the OAuth tab it also background-polls until
68
+ * the token appears or the retry cap is hit, so the dialog unlocks itself
69
+ * without a manual reload. All work stops when the dialog closes, the token
70
+ * path doesn't apply, or the component unmounts.
71
+ *
72
+ * @param loadStatus - consumer callback returning user-token status + OAuth URL
73
+ * @param options.open - whether the host dialog is open
74
+ * @param options.enabled - whether the user-token path applies (idle when false)
75
+ * @returns `{ userStatus, polling, startSignIn }`
76
+ */
77
+ declare function useSlackSignIn(loadStatus: () => Promise<SendAsUserStatus>, { open, enabled }: {
78
+ open: boolean;
79
+ enabled: boolean;
80
+ }): {
81
+ userStatus: SendAsUserStatus | null;
82
+ polling: boolean;
83
+ startSignIn: () => void;
84
+ };
85
+ /**
86
+ * The "Sign in with Slack" button used by both dialogs. Opens OAuth and shows a
87
+ * spinner while {@link useSlackSignIn} polls for the completed round-trip. The
88
+ * surrounding explanatory copy stays with each caller since it differs.
89
+ * @param props.onClick - starts the sign-in flow (typically `startSignIn`)
90
+ * @param props.polling - whether a background poll is in flight
91
+ */
92
+ declare function SlackSignInButton({ onClick, polling }: {
93
+ onClick: () => void;
94
+ polling: boolean;
95
+ }): react.JSX.Element;
96
+
17
97
  /**
18
98
  * Standalone, page-sized picker that displays {@link Template}s as a
19
99
  * grid of cards with live block previews, organized into category
@@ -51,4 +131,4 @@ declare function TemplatePicker({ templates, onSelect, surface, theme, heading,
51
131
  className?: string;
52
132
  }): react.JSX.Element;
53
133
 
54
- export { BlockKitchen, BlockKitchenProps, PreviewSurface, PreviewTheme, Template, TemplatePicker };
134
+ export { BlockKitchen, BlockKitchenProps, ChannelOption, PreviewSurface, PreviewTheme, SendAsUserStatus, SendDialog, type SendDialogProps, SendExtrasContext, SendPayload, SlackSignInButton, SupportedBlock, Template, TemplatePicker, useSlackSignIn };
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
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, GripVertical, Sun, Moon, MessageSquare, AppWindow, Home, MailSearch, Plus, ChevronDown, BookOpen, ExternalLink, AlertTriangle, Trash2, Code2, Pencil, Send, Search, Check, Copy, Info, CheckCircle2, ChevronRight, LayoutGrid, ArrowUp, ArrowDown, Loader2, Type, Bold, Italic, Strikethrough, Underline, Code, List, ListOrdered, Quote, Undo2, Redo2, 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, GripVertical, Sun, Moon, MessageSquare, AppWindow, Home, MailSearch, Plus, ChevronDown, BookOpen, Trash2, Code2, Pencil, Send, Search, Check, Copy, Info, CheckCircle2, ChevronRight, LayoutGrid, ArrowUp, ArrowDown, Type, Bold, Italic, Strikethrough, Underline, Code, List, ListOrdered, Quote, Undo2, Redo2, Smile, Link as Link$1 } from 'lucide-react';
4
4
  import * as React from 'react';
5
- import { createContext, useMemo, useEffect, useState, useCallback, useRef, useId, useContext } from 'react';
5
+ import { createContext, useState, useRef, useEffect, useCallback, useMemo, useId, useContext } from 'react';
6
6
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
7
  import * as DialogPrimitive from '@radix-ui/react-dialog';
8
8
  import { clsx } from 'clsx';
@@ -2015,6 +2015,7 @@ function computeValidation(blocks, surface) {
2015
2015
  const grouped = groupValidatorErrors(result.errors, blocks);
2016
2016
  return {
2017
2017
  valid: result.valid,
2018
+ errors: result.errors,
2018
2019
  byBlockId: grouped.byBlockId,
2019
2020
  general: grouped.general,
2020
2021
  total: grouped.total
@@ -3532,6 +3533,7 @@ function SendDialog({
3532
3533
  loadChannels,
3533
3534
  loadSendAsUserStatus,
3534
3535
  onSend,
3536
+ renderSendExtras,
3535
3537
  confirmSendLabel = "Send",
3536
3538
  errorCount,
3537
3539
  onShowIssues
@@ -3541,6 +3543,10 @@ function SendDialog({
3541
3543
  const [channelId, setChannelId] = useState("");
3542
3544
  const [sendAs, setSendAs] = useState("bot");
3543
3545
  const [status, setStatus] = useState({ kind: "idle" });
3546
+ const [extras, setExtrasState] = useState({});
3547
+ const setExtras = useCallback((patch) => {
3548
+ setExtrasState((prev) => ({ ...prev, ...patch }));
3549
+ }, []);
3544
3550
  const { userStatus, polling, startSignIn } = useSlackSignIn(loadSendAsUserStatus, { open, enabled: true });
3545
3551
  const loadChannelsRef = useRef(loadChannels);
3546
3552
  useEffect(() => {
@@ -3555,6 +3561,7 @@ function SendDialog({
3555
3561
  setChannelsError(null);
3556
3562
  setChannelId("");
3557
3563
  setSendAs("bot");
3564
+ setExtrasState({});
3558
3565
  let cancelled = false;
3559
3566
  loadChannelsRef.current().then((list) => {
3560
3567
  if (cancelled) {
@@ -3582,7 +3589,10 @@ function SendDialog({
3582
3589
  const result = await onSend({
3583
3590
  channelId,
3584
3591
  blocks: toSlackBlocks(blocks),
3585
- sendAsUser: sendAs === "user"
3592
+ sendAsUser: sendAs === "user",
3593
+ // Only introduce the key when the extras slot is wired, so existing
3594
+ // consumers never see a new payload field.
3595
+ ...renderSendExtras ? { extras } : {}
3586
3596
  });
3587
3597
  if (result.ok) {
3588
3598
  setStatus({ kind: "idle" });
@@ -3649,6 +3659,12 @@ function SendDialog({
3649
3659
  }
3650
3660
  )
3651
3661
  ] }),
3662
+ renderSendExtras ? /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-1.5", children: renderSendExtras({
3663
+ channelId: channelId || null,
3664
+ sendAsUser: sendAs === "user",
3665
+ extras,
3666
+ setExtras
3667
+ }) }) : null,
3652
3668
  errorCount > 0 ? /* @__PURE__ */ jsxs(
3653
3669
  "button",
3654
3670
  {
@@ -9395,6 +9411,8 @@ function Toolbar2({
9395
9411
  showThemeControl = true,
9396
9412
  docsLink,
9397
9413
  errorCount,
9414
+ showSend = true,
9415
+ primaryAction,
9398
9416
  sendButtonLabel = "Review & send",
9399
9417
  editingEnabled = false,
9400
9418
  editBadge,
@@ -9529,7 +9547,24 @@ function Toolbar2({
9529
9547
  /* @__PURE__ */ jsx(Code2, { className: "h-3.5 w-3.5" }),
9530
9548
  /* @__PURE__ */ jsx("span", { className: "hidden md:inline", children: "View JSON" })
9531
9549
  ] }),
9532
- editBadge ? (
9550
+ !showSend ? primaryAction ? (
9551
+ // Compose-only mode with a host-owned primary action: same
9552
+ // placement and styling as the built-in send button. No icon —
9553
+ // the action's meaning is the host's, so the label always shows
9554
+ // (the send button can collapse to its icon on small screens;
9555
+ // an icon-less button can't).
9556
+ /* @__PURE__ */ jsx(
9557
+ Button,
9558
+ {
9559
+ type: "button",
9560
+ size: "sm",
9561
+ onClick: primaryAction.onClick,
9562
+ disabled: primaryAction.disabled,
9563
+ "aria-label": primaryAction.label,
9564
+ children: /* @__PURE__ */ jsx("span", { children: primaryAction.label })
9565
+ }
9566
+ )
9567
+ ) : null : editBadge ? (
9533
9568
  // A message is loaded: split button. Main action updates it in
9534
9569
  // place; the menu also offers posting the blocks as a new message.
9535
9570
  /* @__PURE__ */ jsxs("div", { className: "flex items-stretch", children: [
@@ -9772,12 +9807,15 @@ function BlockKitchen(props) {
9772
9807
  workspaceName,
9773
9808
  initialBlocks,
9774
9809
  onChange,
9810
+ onValidationChange,
9775
9811
  previewHooks,
9776
9812
  customEmojis,
9777
9813
  loadChannels,
9778
9814
  loadSendAsUserStatus,
9779
9815
  onSend,
9816
+ renderSendExtras,
9780
9817
  editing,
9818
+ primaryAction,
9781
9819
  loadButtonLabel,
9782
9820
  updateButtonLabel,
9783
9821
  confirmUpdateLabel,
@@ -9794,6 +9832,13 @@ function BlockKitchen(props) {
9794
9832
  sendButtonLabel,
9795
9833
  confirmSendLabel,
9796
9834
  theme
9835
+ // Widen the all-or-nothing union so the branch-specific props
9836
+ // destructure as independent optionals: untyped JS consumers can still
9837
+ // pass partial wiring (handled by the runtime guards below), and the
9838
+ // correlated-union narrowing would otherwise flag those guards as
9839
+ // "always true". `primaryAction` is pulled from the compose-only branch
9840
+ // (where it carries a real type) rather than the send branch's
9841
+ // `undefined` pin, which would collapse the intersection to `undefined`.
9797
9842
  } = props;
9798
9843
  const paletteSections = useMemo(() => {
9799
9844
  const sections = palette ?? defaultPalette;
@@ -9812,13 +9857,36 @@ function BlockKitchen(props) {
9812
9857
  }, [palette, disabledBlockTypes]);
9813
9858
  const variantById = useMemo(() => buildVariantById(paletteSections), [paletteSections]);
9814
9859
  const allowedSurfaces = allowedSurfacesProp && allowedSurfacesProp.length > 0 ? allowedSurfacesProp : ["message"];
9815
- const seededBlocks = editing?.initialTarget?.blocks ?? initialBlocks;
9860
+ const sendEnabled = Boolean(loadChannels && loadSendAsUserStatus && onSend);
9861
+ const editingConfig = sendEnabled ? editing : void 0;
9862
+ const seededBlocks = editingConfig?.initialTarget?.blocks ?? initialBlocks;
9816
9863
  useEffect(() => {
9817
- if (editing?.initialTarget && initialBlocks) {
9864
+ if (editingConfig?.initialTarget && initialBlocks) {
9818
9865
  console.warn(
9819
9866
  "[BlockKitchen] Both `initialBlocks` and `editing.initialTarget` were provided; using the target\u2019s blocks and ignoring `initialBlocks`."
9820
9867
  );
9821
9868
  }
9869
+ const sendPropsProvided = [loadChannels, loadSendAsUserStatus, onSend].filter(Boolean).length;
9870
+ if (sendPropsProvided > 0 && sendPropsProvided < 3) {
9871
+ console.warn(
9872
+ "[BlockKitchen] Partial send wiring: `loadChannels`, `loadSendAsUserStatus`, and `onSend` are all-or-nothing. The send button is hidden until all three are provided."
9873
+ );
9874
+ }
9875
+ if (editing && !sendEnabled) {
9876
+ console.warn(
9877
+ "[BlockKitchen] `editing` requires the send integration (`loadChannels`, `loadSendAsUserStatus`, `onSend`); ignoring `editing`."
9878
+ );
9879
+ }
9880
+ if (renderSendExtras && !sendEnabled) {
9881
+ console.warn(
9882
+ "[BlockKitchen] `renderSendExtras` extends the built-in send dialog, which requires the send integration (`loadChannels`, `loadSendAsUserStatus`, `onSend`); ignoring `renderSendExtras`."
9883
+ );
9884
+ }
9885
+ if (primaryAction && sendEnabled) {
9886
+ console.warn(
9887
+ "[BlockKitchen] `primaryAction` is only available in compose-only mode \u2014 with the send integration wired, the built-in Send/Update flow owns the toolbar\u2019s primary action; ignoring `primaryAction`."
9888
+ );
9889
+ }
9822
9890
  }, []);
9823
9891
  const { blocks, addBlock, addChild, updateBlock, removeBlock, duplicateBlock, reorderBlock, moveBlock, replaceAll } = useBlockKitchenState({ initialBlocks: seededBlocks, onChange });
9824
9892
  const childParentById = useMemo(() => {
@@ -9836,8 +9904,8 @@ function BlockKitchen(props) {
9836
9904
  const [sendOpen, setSendOpen] = useState(false);
9837
9905
  const [updateOpen, setUpdateOpen] = useState(false);
9838
9906
  const [loadOpen, setLoadOpen] = useState(false);
9839
- const [editTarget, setEditTarget] = useState(() => editing?.initialTarget ?? null);
9840
- const activeEditTarget = editing ? editTarget : null;
9907
+ const [editTarget, setEditTarget] = useState(() => editingConfig?.initialTarget ?? null);
9908
+ const activeEditTarget = editingConfig ? editTarget : null;
9841
9909
  const [issuesOpen, setIssuesOpen] = useState(false);
9842
9910
  const [paletteOpen, setPaletteOpen] = useState(false);
9843
9911
  const [openBlockId, setOpenBlockId] = useState(null);
@@ -9855,6 +9923,22 @@ function BlockKitchen(props) {
9855
9923
  return { emoji: makeEmojiHook(byName), ...previewHooks };
9856
9924
  }, [customEmojis, previewHooks]);
9857
9925
  const validation = useBlockKitValidation(blocks, "message");
9926
+ const validationSummary = useMemo(
9927
+ () => ({ valid: validation.valid, errorCount: validation.total, errors: validation.errors }),
9928
+ [validation]
9929
+ );
9930
+ const lastNotifiedValidationRef = useRef(null);
9931
+ useEffect(() => {
9932
+ if (!onValidationChange) {
9933
+ return;
9934
+ }
9935
+ const key = JSON.stringify([validationSummary.valid, validationSummary.errorCount, ...validationSummary.errors]);
9936
+ if (lastNotifiedValidationRef.current === key) {
9937
+ return;
9938
+ }
9939
+ lastNotifiedValidationRef.current = key;
9940
+ onValidationChange(validationSummary);
9941
+ }, [validationSummary, onValidationChange]);
9858
9942
  const sensors = useSensors(
9859
9943
  useSensor(PointerSensor, { activationConstraint: { distance: 4 } }),
9860
9944
  useSensor(TouchSensor, { activationConstraint: { delay: 150, tolerance: 8 } }),
@@ -9958,8 +10042,14 @@ function BlockKitchen(props) {
9958
10042
  allowedSurfaces,
9959
10043
  showThemeControl: isPreviewThemeControlled ? false : showThemeControl,
9960
10044
  docsLink,
10045
+ showSend: sendEnabled,
10046
+ primaryAction: !sendEnabled && primaryAction ? {
10047
+ label: primaryAction.label,
10048
+ onClick: () => primaryAction.onClick({ blocks: blockPayloads, validation: validationSummary }),
10049
+ disabled: primaryAction.disableWhenInvalid ? !validationSummary.valid : false
10050
+ } : null,
9961
10051
  sendButtonLabel,
9962
- editingEnabled: !!editing,
10052
+ editingEnabled: !!editingConfig,
9963
10053
  editBadge: activeEditTarget ? {
9964
10054
  channelLabel: activeEditTarget.channelName ? `#${activeEditTarget.channelName}` : activeEditTarget.channelId,
9965
10055
  ts: activeEditTarget.ts
@@ -10041,7 +10131,7 @@ function BlockKitchen(props) {
10041
10131
  }
10042
10132
  ) }),
10043
10133
  /* @__PURE__ */ jsx(JsonDrawer, { open: jsonOpen, onOpenChange: setJsonOpen, blocks: blockPayloads, onApply: replaceAll }),
10044
- /* @__PURE__ */ jsx(
10134
+ loadChannels && loadSendAsUserStatus && onSend ? /* @__PURE__ */ jsx(
10045
10135
  SendDialog,
10046
10136
  {
10047
10137
  open: sendOpen,
@@ -10050,6 +10140,7 @@ function BlockKitchen(props) {
10050
10140
  loadChannels,
10051
10141
  loadSendAsUserStatus,
10052
10142
  onSend,
10143
+ renderSendExtras,
10053
10144
  confirmSendLabel,
10054
10145
  errorCount: validation.total,
10055
10146
  onShowIssues: () => {
@@ -10057,8 +10148,8 @@ function BlockKitchen(props) {
10057
10148
  setIssuesOpen(true);
10058
10149
  }
10059
10150
  }
10060
- ),
10061
- activeEditTarget && editing ? /* @__PURE__ */ jsx(
10151
+ ) : null,
10152
+ activeEditTarget && editingConfig && loadSendAsUserStatus ? /* @__PURE__ */ jsx(
10062
10153
  UpdateDialog,
10063
10154
  {
10064
10155
  open: updateOpen,
@@ -10066,7 +10157,7 @@ function BlockKitchen(props) {
10066
10157
  target: activeEditTarget,
10067
10158
  blocks: blockPayloads,
10068
10159
  loadSendAsUserStatus,
10069
- onUpdate: editing.onUpdate,
10160
+ onUpdate: editingConfig.onUpdate,
10070
10161
  confirmUpdateLabel,
10071
10162
  errorCount: validation.total,
10072
10163
  onShowIssues: () => {
@@ -10075,13 +10166,13 @@ function BlockKitchen(props) {
10075
10166
  }
10076
10167
  }
10077
10168
  ) : null,
10078
- editing ? /* @__PURE__ */ jsx(
10169
+ editingConfig && loadChannels ? /* @__PURE__ */ jsx(
10079
10170
  LoadMessageDialog,
10080
10171
  {
10081
10172
  open: loadOpen,
10082
10173
  onOpenChange: setLoadOpen,
10083
- onLoadMessage: editing.onLoadMessage,
10084
- loadRecentMessages: editing.loadRecentMessages,
10174
+ onLoadMessage: editingConfig.onLoadMessage,
10175
+ loadRecentMessages: editingConfig.loadRecentMessages,
10085
10176
  loadChannels,
10086
10177
  onLoaded: (result) => {
10087
10178
  replaceAll(result.blocks);
@@ -10275,6 +10366,6 @@ function fromBase64Url(b64url) {
10275
10366
  return b64 + "=".repeat(padLen);
10276
10367
  }
10277
10368
 
10278
- export { BlockKitchen, TemplatePicker, decodeBlocksFromString, defaultPalette, encodeBlocksToString, extraAlertVariant, legacyInputVariants, toSlackBlocks };
10369
+ export { BlockKitchen, SendDialog, SlackSignInButton, TemplatePicker, decodeBlocksFromString, defaultPalette, encodeBlocksToString, extraAlertVariant, legacyInputVariants, toSlackBlocks, useSlackSignIn };
10279
10370
  //# sourceMappingURL=index.js.map
10280
10371
  //# sourceMappingURL=index.js.map