@tightknitai/block-kitchen 0.9.9 → 0.10.0

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.
@@ -1,4 +1,4 @@
1
- import { ComponentType, SVGProps } from 'react';
1
+ import { ComponentType, SVGProps, ReactNode } from 'react';
2
2
  import { SectionBlock, HeaderBlock, DividerBlock, ContextBlock, ActionsBlock, ImageBlock, RichTextBlock, ImageElement, Button, ViewInputBlock } from 'slack-web-api-client';
3
3
 
4
4
  /**
@@ -571,6 +571,38 @@ interface SendPayload {
571
571
  channelId: string;
572
572
  blocks: SupportedBlock[];
573
573
  sendAsUser: boolean;
574
+ /**
575
+ * Values collected from the host's
576
+ * {@link BlockKitchenSendProps.renderSendExtras} fields, exactly as
577
+ * accumulated through {@link SendExtrasContext.setExtras}. Present
578
+ * (possibly `{}`) iff `renderSendExtras` is wired; absent otherwise, so
579
+ * existing consumers never see a new key.
580
+ */
581
+ extras?: Record<string, unknown>;
582
+ }
583
+ /**
584
+ * Context passed to {@link BlockKitchenSendProps.renderSendExtras} on every
585
+ * render of the send dialog. The `extras` object is owned by the dialog —
586
+ * it resets each time the dialog opens (alongside the channel and identity
587
+ * pickers) and is delivered on {@link SendPayload.extras} when the user
588
+ * confirms.
589
+ */
590
+ interface SendExtrasContext {
591
+ /**
592
+ * Destination channel currently selected in the dialog, or `null` while
593
+ * channels are still loading / none is selected. Lets extras react to the
594
+ * destination (e.g. only offer cross-posting for some channels).
595
+ */
596
+ channelId: string | null;
597
+ /** Whether "post as the user" (vs. the app bot) is currently selected. */
598
+ sendAsUser: boolean;
599
+ /** The values collected so far. Render controlled inputs from these. */
600
+ extras: Record<string, unknown>;
601
+ /**
602
+ * Shallow-merges `patch` into {@link SendExtrasContext.extras}. Keys you
603
+ * never set simply don't appear in the payload.
604
+ */
605
+ setExtras: (patch: Record<string, unknown>) => void;
574
606
  }
575
607
  /**
576
608
  * Result returned from {@link BlockKitchenProps.onSend}.
@@ -580,6 +612,74 @@ interface SendResult {
580
612
  ok: boolean;
581
613
  error?: string;
582
614
  }
615
+ /**
616
+ * Snapshot of the builder's validation verdict, reported through
617
+ * {@link BlockKitchenBaseProps.onValidationChange}. Mirrors exactly what the
618
+ * built-in issues chip / issues sheet show, so a host-owned CTA (e.g. in
619
+ * compose-only mode) can gate on the same verdict without re-running the
620
+ * validator and risking drift.
621
+ */
622
+ interface ValidationSummary {
623
+ /** True iff the current draft has zero validation errors. */
624
+ valid: boolean;
625
+ /** Total error count — the number the toolbar's issues chip displays. */
626
+ errorCount: number;
627
+ /**
628
+ * Raw validator messages, verbatim from
629
+ * `@tightknitai/slack-block-kit-validator`. Block-scoped messages are
630
+ * rooted at `blocks[N]`, indexing into the payload `toSlackBlocks` emits.
631
+ */
632
+ errors: readonly string[];
633
+ }
634
+ /**
635
+ * Context handed to {@link PrimaryActionConfig.onClick}: everything a
636
+ * host-owned commitment step needs, captured at click time.
637
+ */
638
+ interface PrimaryActionContext {
639
+ /**
640
+ * The current draft in the builder's native format — the same shape
641
+ * `onChange` reports and `initialBlocks` accepts. Persist this to re-open
642
+ * the draft later; run it through `toSlackBlocks` when you need the
643
+ * Slack-ready payload.
644
+ */
645
+ blocks: SupportedBlock[];
646
+ /**
647
+ * The validation verdict at click time — the same summary
648
+ * {@link BlockKitchenBaseProps.onValidationChange} reports.
649
+ */
650
+ validation: ValidationSummary;
651
+ /**
652
+ * The existing message currently loaded into the builder (via
653
+ * {@link BlockKitchenBaseProps.loading}), or `null` when composing a new
654
+ * message. Carries `channelId` + `ts` + the host's editability verdict, so
655
+ * a host-owned commitment step can offer "update in place" alongside
656
+ * "post as new".
657
+ */
658
+ loadedMessage: LoadedMessage | null;
659
+ }
660
+ /**
661
+ * A host-owned primary action for compose-only mode, rendered in the
662
+ * toolbar slot where the built-in "Review & send" button normally sits.
663
+ * Use it when the builder's commitment step belongs to your app — e.g. a
664
+ * message drafter whose CTA is "Save template" — but you still want the
665
+ * button inside the builder chrome. For a CTA outside the builder, skip
666
+ * this and use {@link BlockKitchenBaseProps.onChange} +
667
+ * {@link BlockKitchenBaseProps.onValidationChange} instead.
668
+ */
669
+ interface PrimaryActionConfig {
670
+ /** Label + accessible name for the toolbar button. */
671
+ label: string;
672
+ /** Called on click with the current draft and validation verdict. */
673
+ onClick: (context: PrimaryActionContext) => void;
674
+ /**
675
+ * Disable the button while the draft has validation errors, mirroring the
676
+ * built-in Send gating. Defaults to `false`: a drafter typically allows
677
+ * committing a work-in-progress draft and surfaces the verdict in its own
678
+ * flow instead. The button is never disabled for an empty draft — gate on
679
+ * `context.blocks.length` in `onClick` if you need that.
680
+ */
681
+ disableWhenInvalid?: boolean;
682
+ }
583
683
  /**
584
684
  * Which token can edit a loaded message, as computed by the host.
585
685
  * `chat.update` only edits a message authored by the calling token:
@@ -589,7 +689,7 @@ interface SendResult {
589
689
  */
590
690
  type EditableVia = 'bot' | 'user';
591
691
  /**
592
- * Input passed to {@link EditingConfig.onLoadMessage}. The user pastes a
692
+ * Input passed to {@link LoadingConfig.onLoadMessage}. The user pastes a
593
693
  * Slack message permalink (Slack's "Copy link"); the host parses
594
694
  * `channel + ts` out of it — the package never touches raw timestamps.
595
695
  */
@@ -597,38 +697,56 @@ interface LoadMessageInput {
597
697
  link: string;
598
698
  }
599
699
  /**
600
- * Result of loading an existing message for editing, computed by the host.
601
- *
602
- * On `ok`, the package enters edit mode: it hydrates the editor with
603
- * `blocks`, locks the destination to `channelId`, and constrains the
604
- * post-as identity to `editableVia`.
605
- *
606
- * On `!ok`, the package renders the host's `reason` inline and offers
607
- * "Open as a new message instead". If the message round-trips through the
608
- * block editor (e.g. someone else's block message), the host may include
609
- * `blocks` so that fallback can hydrate the draft; for non-block or
610
- * attachment-only messages, omit it.
700
+ * The existing message currently loaded into the builder: its source
701
+ * coordinates (`channelId` + `ts`), the host's editability verdict, and
702
+ * display metadata for the preview header / loaded banner. The `ok` branch
703
+ * of {@link LoadResult} is this plus `blocks`, so the loaded-message field
704
+ * list lives in exactly one place. Reported through
705
+ * {@link LoadingConfig.onLoadedMessageChange} and
706
+ * {@link PrimaryActionContext.loadedMessage}, and consumed by the built-in
707
+ * update flow ({@link BlockKitchenSendProps.onUpdate}).
611
708
  */
612
- type LoadResult = {
613
- ok: true;
709
+ interface LoadedMessage {
614
710
  channelId: string;
615
- /** Channel name for the edit-mode badge (display only). Falls back to `channelId`. */
711
+ /**
712
+ * Channel name for the loaded banner (display only). Falls back to
713
+ * `channelId`.
714
+ */
616
715
  channelName?: string;
617
716
  ts: string;
618
- blocks: SupportedBlock[];
619
717
  editableVia: EditableVia;
620
718
  workspaceName?: string;
621
719
  /**
622
- * Display name of the existing message's author. When provided, the
623
- * preview header shows it instead of `workspaceName`. Optional.
720
+ * Display name of the message's author. When provided, the preview header
721
+ * shows it instead of `workspaceName`. Optional.
624
722
  */
625
723
  username?: string;
626
724
  /**
627
- * Avatar image URL of the existing message's author, shown in the
628
- * preview header. Optional; ignored if it isn't a safe `http(s)` URL.
725
+ * Avatar image URL of the message's author, shown in the preview header.
726
+ * Optional; ignored if it isn't a safe `http(s)` URL.
629
727
  */
630
728
  iconUrl?: string;
631
- } | {
729
+ }
730
+ /**
731
+ * Result of loading an existing message, computed by the host.
732
+ *
733
+ * On `ok` — the {@link LoadedMessage} shape plus `blocks` — the package
734
+ * hydrates the editor with `blocks` and shows the loaded banner. Only the
735
+ * *update* flow ({@link BlockKitchenSendProps.onUpdate}) locks the
736
+ * destination to `channelId` and constrains the post-as identity to
737
+ * `editableVia`; the plain send dialog always posts a brand-new message
738
+ * with free channel/identity choice, loaded draft or not.
739
+ *
740
+ * On `!ok`, the package renders the host's `reason` inline and offers
741
+ * "Open as a new message instead". If the message round-trips through the
742
+ * block editor (e.g. someone else's block message), the host may include
743
+ * `blocks` so that fallback can hydrate the draft; for non-block or
744
+ * attachment-only messages, omit it.
745
+ */
746
+ type LoadResult = (LoadedMessage & {
747
+ ok: true;
748
+ blocks: SupportedBlock[];
749
+ }) | {
632
750
  ok: false;
633
751
  reason: string;
634
752
  blocks?: SupportedBlock[];
@@ -642,7 +760,7 @@ type LoadResult = {
642
760
  oauthUrl?: string;
643
761
  };
644
762
  /**
645
- * Payload passed to {@link EditingConfig.onUpdate}. Sibling to
763
+ * Payload passed to {@link BlockKitchenSendProps.onUpdate}. Sibling to
646
764
  * {@link SendPayload} but carries the source `channel + ts`. `chat.update`
647
765
  * requires re-sending the full blocks payload (no partial update).
648
766
  */
@@ -653,7 +771,7 @@ interface UpdatePayload {
653
771
  asUser: boolean;
654
772
  }
655
773
  /**
656
- * Result returned from {@link EditingConfig.onUpdate}. Mirrors
774
+ * Result returned from {@link BlockKitchenSendProps.onUpdate}. Mirrors
657
775
  * {@link SendResult}; `error` is rendered as-is.
658
776
  */
659
777
  interface UpdateResult {
@@ -662,7 +780,7 @@ interface UpdateResult {
662
780
  }
663
781
  /**
664
782
  * One entry in the "recent messages from this app" picker, returned by
665
- * {@link EditingConfig.loadRecentMessages} for the selected channel. These are
783
+ * {@link LoadingConfig.loadRecentMessages} for the selected channel. These are
666
784
  * editable-by-construction — the app authored them — so they load straight into
667
785
  * edit mode without a separate verdict round-trip. `editableVia` defaults to
668
786
  * `'bot'` when omitted.
@@ -683,43 +801,70 @@ interface RecentMessage {
683
801
  iconUrl?: string;
684
802
  }
685
803
  /**
686
- * Opt-in edit-mode configuration. Presence of {@link BlockKitchenProps.editing}
687
- * enables loading an existing message and dispatching an update; omit it to
688
- * keep the send-only behavior. The package stays integration-agnostic — it
689
- * makes no network calls and has no Slack-token knowledge; the host brokers
690
- * I/O and computes the editability verdict.
804
+ * Opt-in configuration for loading an existing message into the editor —
805
+ * a *composition* concern, so it works in both send mode and
806
+ * [compose-only mode](https://github.com/TightknitAI/block-kitchen#compose-only-mode-bring-your-own-send-flow).
807
+ * Presence of {@link BlockKitchenBaseProps.loading} adds the toolbar's
808
+ * "Find message" entry: the user pastes a Slack permalink (or picks a recent
809
+ * message), the host loads it, and the draft is hydrated with its blocks.
810
+ *
811
+ * What happens *after* composing stays mode-specific: in send mode, pair
812
+ * with {@link BlockKitchenSendProps.onUpdate} to update the message in place;
813
+ * in compose-only mode the host owns the commitment step and receives the
814
+ * loaded target via {@link PrimaryActionContext.loadedMessage} /
815
+ * {@link LoadingConfig.onLoadedMessageChange}.
691
816
  */
692
- interface EditingConfig {
817
+ interface LoadingConfig {
693
818
  /**
694
819
  * Host parses the pasted permalink, fetches the message, and returns a
695
820
  * host-computed editability verdict plus blocks. See {@link LoadResult}.
696
821
  */
697
822
  onLoadMessage: (input: LoadMessageInput) => Promise<LoadResult>;
698
823
  /**
699
- * Dispatches the update for the loaded message. Sibling to
700
- * {@link BlockKitchenProps.onSend}; carries `channel + ts`.
824
+ * Optional. When provided (together with a channel source — see
825
+ * {@link LoadingConfig.loadChannels}), the load dialog adds a "recent
826
+ * messages from this app" picker alongside the paste-a-link input. The user
827
+ * first picks a channel; only then is this called with the chosen
828
+ * `channelId`, scoping the lookup to that single channel. Returns messages
829
+ * the app authored in that channel (editable-by-construction); picking one
830
+ * loads it directly. Omit to offer the paste-link entry only.
701
831
  */
702
- onUpdate: (payload: UpdatePayload) => Promise<UpdateResult>;
832
+ loadRecentMessages?: (channelId: string) => Promise<RecentMessage[]>;
703
833
  /**
704
- * Optional. When provided, the load dialog adds a "recent messages from this
705
- * app" picker alongside the paste-a-link input. The user first picks a channel
706
- * (reusing {@link BlockKitchenProps.loadChannels}); only then is this called
707
- * with the chosen `channelId`, scoping the lookup to that single channel.
708
- * Returns messages the app authored in that channel (editable-by-construction);
709
- * picking one loads it straight into edit mode. Omit to offer the paste-link
710
- * entry only.
834
+ * Channels used to scope the recent-messages picker. Only consulted when
835
+ * {@link LoadingConfig.loadRecentMessages} is provided. In send mode this
836
+ * defaults to the send integration's
837
+ * {@link BlockKitchenSendProps.loadChannels}; in compose-only mode there is
838
+ * no send integration, so provide it here or the picker is hidden.
711
839
  */
712
- loadRecentMessages?: (channelId: string) => Promise<RecentMessage[]>;
840
+ loadChannels?: () => Promise<ChannelOption[]>;
713
841
  /**
714
- * Optional. Pre-load a message straight into edit mode at mount, skipping the
715
- * load dialog. Same shape the host returns from {@link onLoadMessage} on
716
- * success. Read once at mount (like {@link BlockKitchenProps.initialBlocks});
717
- * changing it later needs a remount. When set, its `blocks` seed the draft and
718
- * {@link BlockKitchenProps.initialBlocks} is ignored.
842
+ * Optional. Pre-load a message at mount, skipping the load dialog. Same
843
+ * shape the host returns from {@link LoadingConfig.onLoadMessage} on
844
+ * success. Read once at mount (like
845
+ * {@link BlockKitchenBaseProps.initialBlocks}); changing it later needs a
846
+ * remount. When set, its `blocks` seed the draft and `initialBlocks` is
847
+ * ignored.
719
848
  */
720
849
  initialTarget?: Extract<LoadResult, {
721
850
  ok: true;
722
851
  }>;
852
+ /**
853
+ * Notified when the loaded message changes: the target after a successful
854
+ * load (including a mount-time {@link LoadingConfig.initialTarget}), and
855
+ * `null` when the user exits it ("Switch to a new message" / "Open as a
856
+ * new message instead"). Useful in compose-only mode to keep host state in
857
+ * sync — e.g. so a host-owned commitment step knows whether to offer
858
+ * "update in place" without waiting for a
859
+ * {@link PrimaryActionConfig.onClick}.
860
+ *
861
+ * Dedupe is by value, so this can re-fire for the same `channelId` + `ts`
862
+ * when the host's verdict or display metadata changed (e.g. `editableVia`
863
+ * flipping after a Slack sign-in) — key host state on the full target,
864
+ * not `ts` alone. Notifications flow only while `loading` is configured;
865
+ * removing the config mid-session is treated as unsubscribing.
866
+ */
867
+ onLoadedMessageChange?: (message: LoadedMessage | null) => void;
723
868
  }
724
869
  /**
725
870
  * A workspace custom emoji as configured in Slack. Mirrors the shape Slack's
@@ -794,11 +939,15 @@ interface Template {
794
939
  blocks: SupportedBlock[];
795
940
  }
796
941
  /**
797
- * Props for the top-level {@link BlockKitchen} component.
942
+ * Props shared by every {@link BlockKitchen} configuration.
798
943
  * The package is integration-agnostic: every I/O concern is brokered
799
944
  * through these props. The component never makes a network call itself.
945
+ *
946
+ * Combined with either {@link BlockKitchenSendProps} (the built-in send
947
+ * flow) or {@link BlockKitchenComposeOnlyProps} (compose-only: no send
948
+ * button, the host owns the send flow) to form {@link BlockKitchenProps}.
800
949
  */
801
- interface BlockKitchenProps {
950
+ interface BlockKitchenBaseProps {
802
951
  /**
803
952
  * Workspace name shown in the preview chrome to mimic a Slack message header.
804
953
  * Cosmetic only.
@@ -813,6 +962,15 @@ interface BlockKitchenProps {
813
962
  * (URL search param, localStorage, etc).
814
963
  */
815
964
  onChange?: (blocks: SupportedBlock[]) => void;
965
+ /**
966
+ * Fires when the draft's validation verdict changes (piggybacking on the
967
+ * builder's own debounced validation pass — not on every keystroke, and
968
+ * not when the verdict is unchanged). Always scoped to the `message`
969
+ * surface, matching what Send would post. Useful in compose-only mode: a
970
+ * host-rendered CTA can disable itself on `!valid` using the exact verdict
971
+ * the issues sheet displays.
972
+ */
973
+ onValidationChange?: (summary: ValidationSummary) => void;
816
974
  /**
817
975
  * Optional hooks forwarded to the underlying `<Message>` component for
818
976
  * resolving user / channel / emoji directives. If omitted, those
@@ -831,33 +989,21 @@ interface BlockKitchenProps {
831
989
  */
832
990
  customEmojis?: CustomEmoji[];
833
991
  /**
834
- * Returns channels available to send to. Called when the send dialog opens.
835
- */
836
- loadChannels: () => Promise<ChannelOption[]>;
837
- /**
838
- * Returns whether the current user can post as themselves and, if not, an
839
- * OAuth URL to start the install flow.
840
- */
841
- loadSendAsUserStatus: () => Promise<SendAsUserStatus>;
842
- /**
843
- * Called when the user submits the send dialog. Should return
844
- * `{ ok: true }` on success or `{ ok: false, error }` on failure.
845
- */
846
- onSend: (payload: SendPayload) => Promise<SendResult>;
847
- /**
848
- * Opt-in edit mode. When provided, the toolbar exposes an "Edit existing
849
- * message" entry: the user pastes a Slack message link, the host loads it
850
- * and returns an editability verdict ({@link EditingConfig.onLoadMessage}),
851
- * and a successful load flips the primary action from Send to "Update
852
- * message" ({@link EditingConfig.onUpdate}). Omit to keep today's send-only
853
- * behavior. The user-token path reuses {@link BlockKitchenProps.loadSendAsUserStatus}.
992
+ * Opt-in: load an existing Slack message into the editor. Adds a
993
+ * "Find message" toolbar entry — the user pastes a permalink (or picks a
994
+ * recent message) and the draft is hydrated with its blocks. Loading is a
995
+ * *composition* concern, so it works in both send mode and compose-only
996
+ * mode. Pair with {@link BlockKitchenSendProps.onUpdate} (send mode) for
997
+ * the built-in update-in-place flow, or consume
998
+ * {@link PrimaryActionContext.loadedMessage} /
999
+ * {@link LoadingConfig.onLoadedMessageChange} (compose-only) to run your
1000
+ * own.
854
1001
  */
855
- editing?: EditingConfig;
1002
+ loading?: LoadingConfig;
856
1003
  /**
857
1004
  * Label + accessible name for the toolbar button that opens the
858
- * load-message dialog (the edit-mode entry point). Defaults to
859
- * `'Find message'`. Only shown when {@link BlockKitchenProps.editing} is set
860
- * and no message is currently loaded.
1005
+ * load-message dialog. Defaults to `'Find message'`. Only shown when
1006
+ * {@link BlockKitchenBaseProps.loading} is configured.
861
1007
  */
862
1008
  loadButtonLabel?: string;
863
1009
  /**
@@ -869,7 +1015,7 @@ interface BlockKitchenProps {
869
1015
  updateButtonLabel?: string;
870
1016
  /**
871
1017
  * Label for the update dialog's final confirm button, which dispatches the
872
- * update via {@link EditingConfig.onUpdate}. Defaults to `'Update message'`
1018
+ * update via {@link BlockKitchenSendProps.onUpdate}. Defaults to `'Update message'`
873
1019
  * (and shows `'Updating…'` while in flight).
874
1020
  */
875
1021
  confirmUpdateLabel?: string;
@@ -1022,5 +1168,93 @@ interface BlockKitchenProps {
1022
1168
  */
1023
1169
  theme?: BrandTheme | BrandPreset;
1024
1170
  }
1171
+ /**
1172
+ * The send integration: the trio of I/O callbacks powering the built-in
1173
+ * send dialog, plus opt-in edit mode (which reuses them). All-or-nothing by
1174
+ * design — see {@link BlockKitchenComposeOnlyProps} for the alternative.
1175
+ */
1176
+ interface BlockKitchenSendProps {
1177
+ /**
1178
+ * Returns channels available to send to. Called when the send dialog opens.
1179
+ */
1180
+ loadChannels: () => Promise<ChannelOption[]>;
1181
+ /**
1182
+ * Returns whether the current user can post as themselves and, if not, an
1183
+ * OAuth URL to start the install flow.
1184
+ */
1185
+ loadSendAsUserStatus: () => Promise<SendAsUserStatus>;
1186
+ /**
1187
+ * Called when the user submits the send dialog. Should return
1188
+ * `{ ok: true }` on success or `{ ok: false, error }` on failure.
1189
+ */
1190
+ onSend: (payload: SendPayload) => Promise<SendResult>;
1191
+ /**
1192
+ * Opt-in update-in-place mode. Dispatches the update for the loaded
1193
+ * message — sibling to {@link BlockKitchenSendProps.onSend}, but carries
1194
+ * the source `channel + ts`. When provided alongside
1195
+ * {@link BlockKitchenBaseProps.loading}, a loaded message flips the
1196
+ * primary action from Send to "Update message". Omit to keep send-only
1197
+ * behavior: with `loading` alone, a loaded message can still be posted as
1198
+ * a brand-new message. The user-token path reuses
1199
+ * {@link BlockKitchenSendProps.loadSendAsUserStatus}.
1200
+ */
1201
+ onUpdate?: (payload: UpdatePayload) => Promise<UpdateResult>;
1202
+ /**
1203
+ * Renders host-defined fields inside the built-in send dialog, below the
1204
+ * channel and identity pickers. Values collected via
1205
+ * {@link SendExtrasContext.setExtras} are delivered on
1206
+ * {@link SendPayload.extras} when the user confirms. Use this when the
1207
+ * built-in flow is almost right and you need a few extra controls (a
1208
+ * cross-post toggle, a custom sender name) without rebuilding the dialog.
1209
+ */
1210
+ renderSendExtras?: (context: SendExtrasContext) => ReactNode;
1211
+ /**
1212
+ * Unavailable with the send integration: the built-in Send/Update flow
1213
+ * owns the toolbar's primary action. A host-owned
1214
+ * {@link PrimaryActionConfig} exists only in compose-only mode.
1215
+ */
1216
+ primaryAction?: undefined;
1217
+ }
1218
+ /**
1219
+ * Compose-only mode: omit the entire send integration and the builder
1220
+ * renders no send button — it becomes a pure editor. The host owns the
1221
+ * moment of commitment: mirror the draft via
1222
+ * {@link BlockKitchenBaseProps.onChange}, gate a host-rendered CTA via
1223
+ * {@link BlockKitchenBaseProps.onValidationChange}, and hand the finished
1224
+ * payload (`toSlackBlocks(draft)`) to your own audience/delivery flow.
1225
+ *
1226
+ * The explicit `undefined` members make the send trio all-or-nothing at the
1227
+ * type level: wiring only some of the three is a type error instead of a
1228
+ * silently dead send button.
1229
+ */
1230
+ interface BlockKitchenComposeOnlyProps {
1231
+ loadChannels?: undefined;
1232
+ loadSendAsUserStatus?: undefined;
1233
+ onSend?: undefined;
1234
+ /**
1235
+ * The update-in-place flow (`chat.update`) depends on the send
1236
+ * integration, so it is unavailable here. *Loading* an existing message
1237
+ * is a composition concern and stays available via
1238
+ * {@link BlockKitchenBaseProps.loading} — the host receives the loaded
1239
+ * target ({@link PrimaryActionContext.loadedMessage} /
1240
+ * {@link LoadingConfig.onLoadedMessageChange}) and owns any update flow.
1241
+ */
1242
+ onUpdate?: undefined;
1243
+ /** Extends the built-in send dialog, which doesn't exist in this mode. */
1244
+ renderSendExtras?: undefined;
1245
+ /**
1246
+ * Optional host-owned button rendered in the toolbar slot where the
1247
+ * built-in "Review & send" normally sits — e.g. "Save template" for a
1248
+ * message drafter. `onClick` receives the current draft and validation
1249
+ * verdict ({@link PrimaryActionContext}). Omit it to render no primary
1250
+ * action at all and place your CTA outside the builder.
1251
+ */
1252
+ primaryAction?: PrimaryActionConfig;
1253
+ }
1254
+ /**
1255
+ * Props for the top-level {@link BlockKitchen} component: the shared base
1256
+ * plus either the full send integration or none of it.
1257
+ */
1258
+ type BlockKitchenProps = BlockKitchenBaseProps & (BlockKitchenSendProps | BlockKitchenComposeOnlyProps);
1025
1259
 
1026
- export { type Template as $, type AlertBlock as A, type BlockKitchenProps as B, type CardBlock as C, type DataVisualizationBlock as D, type EditableVia as E, type FeedbackButtonSubobject as F, type PlanBlock as G, type HeaderLevel as H, type IconButtonElement as I, type PreviewHooks as J, type PreviewSurface as K, type LoadMessageInput as L, type MarkdownBlock as M, type PreviewTheme as N, type SendPayload as O, type PaletteSection as P, type SendResult as Q, type RecentMessage as R, type SendAsUserStatus as S, type SupportedBlock as T, type SupportedBlockType as U, type SupportedHeaderBlock as V, type TableBlock as W, type TableCell as X, type TableColumnSetting as Y, type TaskCardBlock as Z, type TaskCardStatus as _, type AlertLevel as a, type UpdatePayload as a0, type UpdateResult as a1, type UrlSourceElement as a2, type VideoBlock as a3, buildVariantById as a4, defaultPalette as a5, extraAlertVariant as a6, legacyInputVariants as a7, type BrandPreset as b, type BrandTheme as c, type BrandTokens as d, type BuilderBlock as e, type CarouselBlock as f, type CartesianChart as g, type ChannelOption as h, type Chart as i, type ChartAxisConfig as j, type ChartDataPoint as k, type ChartSeries as l, type ChartType as m, type ContainerBlock as n, type ContainerChildBlock as o, type ContextActionsBlock as p, type ContextActionsElement as q, type CustomEmoji as r, type EditingConfig as s, type FeedbackButtonsElement as t, type IconButtonIcon as u, type InputBlock as v, type LoadResult as w, type PaletteVariant as x, type PieChart as y, type PieChartSegment as z };
1260
+ export { type SupportedBlockType as $, type AlertBlock as A, type BlockKitchenBaseProps as B, type CardBlock as C, type DataVisualizationBlock as D, type EditableVia as E, type FeedbackButtonSubobject as F, type LoadingConfig as G, type HeaderLevel as H, type IconButtonElement as I, type PaletteVariant as J, type PieChart as K, type LoadMessageInput as L, type MarkdownBlock as M, type PieChartSegment as N, type PlanBlock as O, type PaletteSection as P, type PreviewHooks as Q, type PreviewSurface as R, type PreviewTheme as S, type PrimaryActionConfig as T, type PrimaryActionContext as U, type RecentMessage as V, type SendAsUserStatus as W, type SendExtrasContext as X, type SendPayload as Y, type SendResult as Z, type SupportedBlock as _, type AlertLevel as a, type SupportedHeaderBlock as a0, type TableBlock as a1, type TableCell as a2, type TableColumnSetting as a3, type TaskCardBlock as a4, type TaskCardStatus as a5, type Template as a6, type UpdatePayload as a7, type UpdateResult as a8, type UrlSourceElement as a9, type ValidationSummary as aa, type VideoBlock as ab, buildVariantById as ac, defaultPalette as ad, extraAlertVariant as ae, legacyInputVariants as af, type BlockKitchenComposeOnlyProps as b, type BlockKitchenProps as c, type BlockKitchenSendProps as d, type BrandPreset as e, type BrandTheme as f, type BrandTokens as g, type BuilderBlock as h, type CarouselBlock as i, type CartesianChart as j, type ChannelOption as k, type Chart as l, type ChartAxisConfig as m, type ChartDataPoint as n, type ChartSeries as o, type ChartType as p, type ContainerBlock as q, type ContainerChildBlock as r, type ContextActionsBlock as s, type ContextActionsElement as t, type CustomEmoji as u, type FeedbackButtonsElement as v, type IconButtonIcon as w, type InputBlock as x, type LoadResult as y, type LoadedMessage as z };