@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.
- package/README.md +255 -26
- package/dist/helpers.d.cts +2 -2
- package/dist/helpers.d.ts +2 -2
- package/dist/index.cjs +176 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +83 -3
- package/dist/index.d.ts +83 -3
- package/dist/index.js +176 -45
- package/dist/index.js.map +1 -1
- package/dist/{palette-CKP8CK5Z.d.cts → palette-C8sPJuXx.d.cts} +309 -75
- package/dist/{palette-CKP8CK5Z.d.ts → palette-C8sPJuXx.d.ts} +309 -75
- package/dist/palette.d.cts +1 -1
- package/dist/palette.d.ts +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { c as BlockKitchenProps, _ as SupportedBlock, k as ChannelOption, W as SendAsUserStatus, Y as SendPayload, X as SendExtrasContext, a6 as Template, R as PreviewSurface, S as PreviewTheme } from './palette-C8sPJuXx.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, F as FeedbackButtonSubobject, v as FeedbackButtonsElement, H as HeaderLevel, I as IconButtonElement, w as IconButtonIcon, x as InputBlock, L as LoadMessageInput, y as LoadResult, z as LoadedMessage, G as LoadingConfig, M as MarkdownBlock, P as PaletteSection, J as PaletteVariant, K as PieChart, N as PieChartSegment, O as PlanBlock, Q as PreviewHooks, T as PrimaryActionConfig, U as PrimaryActionContext, V as RecentMessage, Z as SendResult, $ as SupportedBlockType, a0 as SupportedHeaderBlock, a1 as TableBlock, a2 as TableCell, a3 as TableColumnSetting, a4 as TaskCardBlock, a5 as TaskCardStatus, a7 as UpdatePayload, a8 as UpdateResult, a9 as UrlSourceElement, aa as ValidationSummary, ab as VideoBlock, ad as defaultPalette, ae as extraAlertVariant, af as legacyInputVariants } from './palette-C8sPJuXx.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 {
|
|
3
|
-
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { c as BlockKitchenProps, _ as SupportedBlock, k as ChannelOption, W as SendAsUserStatus, Y as SendPayload, X as SendExtrasContext, a6 as Template, R as PreviewSurface, S as PreviewTheme } from './palette-C8sPJuXx.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, F as FeedbackButtonSubobject, v as FeedbackButtonsElement, H as HeaderLevel, I as IconButtonElement, w as IconButtonIcon, x as InputBlock, L as LoadMessageInput, y as LoadResult, z as LoadedMessage, G as LoadingConfig, M as MarkdownBlock, P as PaletteSection, J as PaletteVariant, K as PieChart, N as PieChartSegment, O as PlanBlock, Q as PreviewHooks, T as PrimaryActionConfig, U as PrimaryActionContext, V as RecentMessage, Z as SendResult, $ as SupportedBlockType, a0 as SupportedHeaderBlock, a1 as TableBlock, a2 as TableCell, a3 as TableColumnSetting, a4 as TaskCardBlock, a5 as TaskCardStatus, a7 as UpdatePayload, a8 as UpdateResult, a9 as UrlSourceElement, aa as ValidationSummary, ab as VideoBlock, ad as defaultPalette, ae as extraAlertVariant, af as legacyInputVariants } from './palette-C8sPJuXx.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,
|
|
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,
|
|
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
|
|
@@ -2225,6 +2226,29 @@ function useBlockKitchenState({
|
|
|
2225
2226
|
replaceAll
|
|
2226
2227
|
};
|
|
2227
2228
|
}
|
|
2229
|
+
function useDevWarning(condition, message) {
|
|
2230
|
+
const warnedRef = useRef(false);
|
|
2231
|
+
useEffect(() => {
|
|
2232
|
+
if (condition && !warnedRef.current) {
|
|
2233
|
+
warnedRef.current = true;
|
|
2234
|
+
console.warn(message);
|
|
2235
|
+
}
|
|
2236
|
+
}, [condition, message]);
|
|
2237
|
+
}
|
|
2238
|
+
function useNotifyOnChange(value, toKey, callback) {
|
|
2239
|
+
const lastNotifiedRef = useRef(null);
|
|
2240
|
+
useEffect(() => {
|
|
2241
|
+
if (!callback) {
|
|
2242
|
+
return;
|
|
2243
|
+
}
|
|
2244
|
+
const key = toKey(value);
|
|
2245
|
+
if (lastNotifiedRef.current === key) {
|
|
2246
|
+
return;
|
|
2247
|
+
}
|
|
2248
|
+
lastNotifiedRef.current = key;
|
|
2249
|
+
callback(value);
|
|
2250
|
+
}, [value, toKey, callback]);
|
|
2251
|
+
}
|
|
2228
2252
|
|
|
2229
2253
|
// src/lib/brand-theme.ts
|
|
2230
2254
|
var TOKEN_TO_CSS_VAR = {
|
|
@@ -3022,7 +3046,7 @@ function LoadMessageDialog({
|
|
|
3022
3046
|
loadRecentMessagesRef.current = loadRecentMessages;
|
|
3023
3047
|
loadChannelsRef.current = loadChannels;
|
|
3024
3048
|
});
|
|
3025
|
-
const hasRecent = !!loadRecentMessages;
|
|
3049
|
+
const hasRecent = !!loadRecentMessages && !!loadChannels;
|
|
3026
3050
|
useEffect(() => {
|
|
3027
3051
|
if (!open) {
|
|
3028
3052
|
return;
|
|
@@ -3033,7 +3057,10 @@ function LoadMessageDialog({
|
|
|
3033
3057
|
setRecent(null);
|
|
3034
3058
|
setRecentError(null);
|
|
3035
3059
|
setSelectedRecent(null);
|
|
3036
|
-
|
|
3060
|
+
}, [open]);
|
|
3061
|
+
useEffect(() => {
|
|
3062
|
+
const load = loadChannelsRef.current;
|
|
3063
|
+
if (!open || !hasRecent || !load) {
|
|
3037
3064
|
setChannels([]);
|
|
3038
3065
|
setChannelsError(null);
|
|
3039
3066
|
return;
|
|
@@ -3041,7 +3068,7 @@ function LoadMessageDialog({
|
|
|
3041
3068
|
setChannels(null);
|
|
3042
3069
|
setChannelsError(null);
|
|
3043
3070
|
let cancelled = false;
|
|
3044
|
-
|
|
3071
|
+
load().then((list) => {
|
|
3045
3072
|
if (!cancelled) {
|
|
3046
3073
|
setChannels(list);
|
|
3047
3074
|
}
|
|
@@ -3053,7 +3080,7 @@ function LoadMessageDialog({
|
|
|
3053
3080
|
return () => {
|
|
3054
3081
|
cancelled = true;
|
|
3055
3082
|
};
|
|
3056
|
-
}, [open]);
|
|
3083
|
+
}, [open, hasRecent]);
|
|
3057
3084
|
useEffect(() => {
|
|
3058
3085
|
if (!open || !loadRecentMessagesRef.current || !channelId) {
|
|
3059
3086
|
return;
|
|
@@ -3532,6 +3559,7 @@ function SendDialog({
|
|
|
3532
3559
|
loadChannels,
|
|
3533
3560
|
loadSendAsUserStatus,
|
|
3534
3561
|
onSend,
|
|
3562
|
+
renderSendExtras,
|
|
3535
3563
|
confirmSendLabel = "Send",
|
|
3536
3564
|
errorCount,
|
|
3537
3565
|
onShowIssues
|
|
@@ -3541,6 +3569,10 @@ function SendDialog({
|
|
|
3541
3569
|
const [channelId, setChannelId] = useState("");
|
|
3542
3570
|
const [sendAs, setSendAs] = useState("bot");
|
|
3543
3571
|
const [status, setStatus] = useState({ kind: "idle" });
|
|
3572
|
+
const [extras, setExtrasState] = useState({});
|
|
3573
|
+
const setExtras = useCallback((patch) => {
|
|
3574
|
+
setExtrasState((prev) => ({ ...prev, ...patch }));
|
|
3575
|
+
}, []);
|
|
3544
3576
|
const { userStatus, polling, startSignIn } = useSlackSignIn(loadSendAsUserStatus, { open, enabled: true });
|
|
3545
3577
|
const loadChannelsRef = useRef(loadChannels);
|
|
3546
3578
|
useEffect(() => {
|
|
@@ -3555,6 +3587,7 @@ function SendDialog({
|
|
|
3555
3587
|
setChannelsError(null);
|
|
3556
3588
|
setChannelId("");
|
|
3557
3589
|
setSendAs("bot");
|
|
3590
|
+
setExtrasState({});
|
|
3558
3591
|
let cancelled = false;
|
|
3559
3592
|
loadChannelsRef.current().then((list) => {
|
|
3560
3593
|
if (cancelled) {
|
|
@@ -3582,7 +3615,10 @@ function SendDialog({
|
|
|
3582
3615
|
const result = await onSend({
|
|
3583
3616
|
channelId,
|
|
3584
3617
|
blocks: toSlackBlocks(blocks),
|
|
3585
|
-
sendAsUser: sendAs === "user"
|
|
3618
|
+
sendAsUser: sendAs === "user",
|
|
3619
|
+
// Only introduce the key when the extras slot is wired, so existing
|
|
3620
|
+
// consumers never see a new payload field.
|
|
3621
|
+
...renderSendExtras ? { extras } : {}
|
|
3586
3622
|
});
|
|
3587
3623
|
if (result.ok) {
|
|
3588
3624
|
setStatus({ kind: "idle" });
|
|
@@ -3649,6 +3685,12 @@ function SendDialog({
|
|
|
3649
3685
|
}
|
|
3650
3686
|
)
|
|
3651
3687
|
] }),
|
|
3688
|
+
renderSendExtras ? /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-1.5", children: renderSendExtras({
|
|
3689
|
+
channelId: channelId || null,
|
|
3690
|
+
sendAsUser: sendAs === "user",
|
|
3691
|
+
extras,
|
|
3692
|
+
setExtras
|
|
3693
|
+
}) }) : null,
|
|
3652
3694
|
errorCount > 0 ? /* @__PURE__ */ jsxs(
|
|
3653
3695
|
"button",
|
|
3654
3696
|
{
|
|
@@ -9395,8 +9437,11 @@ function Toolbar2({
|
|
|
9395
9437
|
showThemeControl = true,
|
|
9396
9438
|
docsLink,
|
|
9397
9439
|
errorCount,
|
|
9440
|
+
showSend = true,
|
|
9441
|
+
primaryAction,
|
|
9398
9442
|
sendButtonLabel = "Review & send",
|
|
9399
|
-
|
|
9443
|
+
loadEnabled = false,
|
|
9444
|
+
updateEnabled = false,
|
|
9400
9445
|
editBadge,
|
|
9401
9446
|
onOpenLoad,
|
|
9402
9447
|
onOpenUpdate,
|
|
@@ -9416,7 +9461,7 @@ function Toolbar2({
|
|
|
9416
9461
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
9417
9462
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-x-2 gap-y-1 border-b bg-background px-2 py-1.5 sm:px-3 sm:py-2", children: [
|
|
9418
9463
|
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-1 sm:gap-2", children: [
|
|
9419
|
-
|
|
9464
|
+
loadEnabled ? /* @__PURE__ */ jsxs(Button, { type: "button", size: "sm", onClick: onOpenLoad, "aria-label": loadButtonLabel, children: [
|
|
9420
9465
|
/* @__PURE__ */ jsx(MailSearch, { className: "h-3.5 w-3.5" }),
|
|
9421
9466
|
/* @__PURE__ */ jsx("span", { className: "hidden md:inline", children: loadButtonLabel })
|
|
9422
9467
|
] }) : null,
|
|
@@ -9529,9 +9574,29 @@ function Toolbar2({
|
|
|
9529
9574
|
/* @__PURE__ */ jsx(Code2, { className: "h-3.5 w-3.5" }),
|
|
9530
9575
|
/* @__PURE__ */ jsx("span", { className: "hidden md:inline", children: "View JSON" })
|
|
9531
9576
|
] }),
|
|
9532
|
-
|
|
9533
|
-
//
|
|
9534
|
-
//
|
|
9577
|
+
!showSend ? primaryAction ? (
|
|
9578
|
+
// Compose-only mode with a host-owned primary action: same
|
|
9579
|
+
// placement and styling as the built-in send button. No icon —
|
|
9580
|
+
// the action's meaning is the host's, so the label always shows
|
|
9581
|
+
// (the send button can collapse to its icon on small screens;
|
|
9582
|
+
// an icon-less button can't).
|
|
9583
|
+
/* @__PURE__ */ jsx(
|
|
9584
|
+
Button,
|
|
9585
|
+
{
|
|
9586
|
+
type: "button",
|
|
9587
|
+
size: "sm",
|
|
9588
|
+
onClick: primaryAction.onClick,
|
|
9589
|
+
disabled: primaryAction.disabled,
|
|
9590
|
+
"aria-label": primaryAction.label,
|
|
9591
|
+
children: /* @__PURE__ */ jsx("span", { children: primaryAction.label })
|
|
9592
|
+
}
|
|
9593
|
+
)
|
|
9594
|
+
) : null : editBadge && updateEnabled ? (
|
|
9595
|
+
// A message is loaded and update-in-place is wired: split button.
|
|
9596
|
+
// Main action updates it in place; the menu also offers posting
|
|
9597
|
+
// the blocks as a new message. Without `updateEnabled` (loading
|
|
9598
|
+
// configured but no `editing.onUpdate`), the plain send button
|
|
9599
|
+
// below stays — the loaded message can only be posted as new.
|
|
9535
9600
|
/* @__PURE__ */ jsxs("div", { className: "flex items-stretch", children: [
|
|
9536
9601
|
/* @__PURE__ */ jsxs(
|
|
9537
9602
|
Button,
|
|
@@ -9604,7 +9669,7 @@ function Toolbar2({
|
|
|
9604
9669
|
] })
|
|
9605
9670
|
] })
|
|
9606
9671
|
] }),
|
|
9607
|
-
|
|
9672
|
+
loadEnabled && editBadge ? /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-1 border-b bg-muted px-2 py-2 text-foreground sm:px-3", children: [
|
|
9608
9673
|
/* @__PURE__ */ jsx(Pencil, { className: "h-4 w-4 shrink-0 text-primary" }),
|
|
9609
9674
|
/* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1 text-sm", children: [
|
|
9610
9675
|
"References an existing message in ",
|
|
@@ -9767,17 +9832,34 @@ function UpdateDialog({
|
|
|
9767
9832
|
] })
|
|
9768
9833
|
] }) });
|
|
9769
9834
|
}
|
|
9835
|
+
function toLoadedMessage({
|
|
9836
|
+
ok: _ok,
|
|
9837
|
+
blocks: _blocks,
|
|
9838
|
+
...loadedMessage
|
|
9839
|
+
}) {
|
|
9840
|
+
return loadedMessage;
|
|
9841
|
+
}
|
|
9842
|
+
function loadedMessageKey(message) {
|
|
9843
|
+
return message ? JSON.stringify(message) : null;
|
|
9844
|
+
}
|
|
9845
|
+
function validationSummaryKey(summary) {
|
|
9846
|
+
return JSON.stringify([summary.valid, summary.errorCount, ...summary.errors]);
|
|
9847
|
+
}
|
|
9770
9848
|
function BlockKitchen(props) {
|
|
9771
9849
|
const {
|
|
9772
9850
|
workspaceName,
|
|
9773
9851
|
initialBlocks,
|
|
9774
9852
|
onChange,
|
|
9853
|
+
onValidationChange,
|
|
9775
9854
|
previewHooks,
|
|
9776
9855
|
customEmojis,
|
|
9777
9856
|
loadChannels,
|
|
9778
9857
|
loadSendAsUserStatus,
|
|
9779
9858
|
onSend,
|
|
9780
|
-
|
|
9859
|
+
renderSendExtras,
|
|
9860
|
+
loading,
|
|
9861
|
+
onUpdate: onUpdateProp,
|
|
9862
|
+
primaryAction,
|
|
9781
9863
|
loadButtonLabel,
|
|
9782
9864
|
updateButtonLabel,
|
|
9783
9865
|
confirmUpdateLabel,
|
|
@@ -9794,6 +9876,13 @@ function BlockKitchen(props) {
|
|
|
9794
9876
|
sendButtonLabel,
|
|
9795
9877
|
confirmSendLabel,
|
|
9796
9878
|
theme
|
|
9879
|
+
// Widen the all-or-nothing union so the branch-specific props
|
|
9880
|
+
// destructure as independent optionals: untyped JS consumers can still
|
|
9881
|
+
// pass partial wiring (handled by the runtime guards below), and the
|
|
9882
|
+
// correlated-union narrowing would otherwise flag those guards as
|
|
9883
|
+
// "always true". `primaryAction` is pulled from the compose-only branch
|
|
9884
|
+
// (where it carries a real type) rather than the send branch's
|
|
9885
|
+
// `undefined` pin, which would collapse the intersection to `undefined`.
|
|
9797
9886
|
} = props;
|
|
9798
9887
|
const paletteSections = useMemo(() => {
|
|
9799
9888
|
const sections = palette ?? defaultPalette;
|
|
@@ -9812,14 +9901,40 @@ function BlockKitchen(props) {
|
|
|
9812
9901
|
}, [palette, disabledBlockTypes]);
|
|
9813
9902
|
const variantById = useMemo(() => buildVariantById(paletteSections), [paletteSections]);
|
|
9814
9903
|
const allowedSurfaces = allowedSurfacesProp && allowedSurfacesProp.length > 0 ? allowedSurfacesProp : ["message"];
|
|
9815
|
-
const
|
|
9816
|
-
|
|
9817
|
-
|
|
9818
|
-
|
|
9819
|
-
|
|
9820
|
-
|
|
9821
|
-
|
|
9822
|
-
|
|
9904
|
+
const sendEnabled = Boolean(loadChannels && loadSendAsUserStatus && onSend);
|
|
9905
|
+
const loadEnabled = Boolean(loading);
|
|
9906
|
+
const recentChannelSource = loading?.loadChannels ?? (sendEnabled ? loadChannels : void 0);
|
|
9907
|
+
const onUpdate = sendEnabled ? onUpdateProp : void 0;
|
|
9908
|
+
const seededBlocks = loading?.initialTarget?.blocks ?? initialBlocks;
|
|
9909
|
+
const sendPropsProvided = [loadChannels, loadSendAsUserStatus, onSend].filter(Boolean).length;
|
|
9910
|
+
useDevWarning(
|
|
9911
|
+
Boolean(loading?.initialTarget && initialBlocks),
|
|
9912
|
+
"[BlockKitchen] Both `initialBlocks` and an `initialTarget` were provided; using the target\u2019s blocks and ignoring `initialBlocks`."
|
|
9913
|
+
);
|
|
9914
|
+
useDevWarning(
|
|
9915
|
+
sendPropsProvided > 0 && sendPropsProvided < 3,
|
|
9916
|
+
"[BlockKitchen] Partial send wiring: `loadChannels`, `loadSendAsUserStatus`, and `onSend` are all-or-nothing. The send button is hidden until all three are provided."
|
|
9917
|
+
);
|
|
9918
|
+
useDevWarning(
|
|
9919
|
+
Boolean(onUpdateProp && !sendEnabled),
|
|
9920
|
+
"[BlockKitchen] `onUpdate` requires the send integration (`loadChannels`, `loadSendAsUserStatus`, `onSend`); ignoring `onUpdate`. To load an existing message without the send integration, use the `loading` prop instead."
|
|
9921
|
+
);
|
|
9922
|
+
useDevWarning(
|
|
9923
|
+
Boolean(sendEnabled && onUpdateProp && !loading),
|
|
9924
|
+
"[BlockKitchen] `onUpdate` needs a loaded message, but no load source is configured \u2014 provide the `loading` prop. The update flow is unreachable."
|
|
9925
|
+
);
|
|
9926
|
+
useDevWarning(
|
|
9927
|
+
Boolean(loading?.loadRecentMessages && !recentChannelSource),
|
|
9928
|
+
"[BlockKitchen] `loading.loadRecentMessages` needs a channel list to scope its lookup \u2014 provide `loading.loadChannels` (compose-only mode has no send integration to reuse). Hiding the recent-messages picker."
|
|
9929
|
+
);
|
|
9930
|
+
useDevWarning(
|
|
9931
|
+
Boolean(renderSendExtras && !sendEnabled),
|
|
9932
|
+
"[BlockKitchen] `renderSendExtras` extends the built-in send dialog, which requires the send integration (`loadChannels`, `loadSendAsUserStatus`, `onSend`); ignoring `renderSendExtras`."
|
|
9933
|
+
);
|
|
9934
|
+
useDevWarning(
|
|
9935
|
+
Boolean(primaryAction && sendEnabled),
|
|
9936
|
+
"[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`."
|
|
9937
|
+
);
|
|
9823
9938
|
const { blocks, addBlock, addChild, updateBlock, removeBlock, duplicateBlock, reorderBlock, moveBlock, replaceAll } = useBlockKitchenState({ initialBlocks: seededBlocks, onChange });
|
|
9824
9939
|
const childParentById = useMemo(() => {
|
|
9825
9940
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -9836,8 +9951,11 @@ function BlockKitchen(props) {
|
|
|
9836
9951
|
const [sendOpen, setSendOpen] = useState(false);
|
|
9837
9952
|
const [updateOpen, setUpdateOpen] = useState(false);
|
|
9838
9953
|
const [loadOpen, setLoadOpen] = useState(false);
|
|
9839
|
-
const [editTarget, setEditTarget] = useState(
|
|
9840
|
-
|
|
9954
|
+
const [editTarget, setEditTarget] = useState(
|
|
9955
|
+
() => loading?.initialTarget ? toLoadedMessage(loading.initialTarget) : null
|
|
9956
|
+
);
|
|
9957
|
+
const activeEditTarget = loadEnabled ? editTarget : null;
|
|
9958
|
+
useNotifyOnChange(activeEditTarget, loadedMessageKey, loading?.onLoadedMessageChange);
|
|
9841
9959
|
const [issuesOpen, setIssuesOpen] = useState(false);
|
|
9842
9960
|
const [paletteOpen, setPaletteOpen] = useState(false);
|
|
9843
9961
|
const [openBlockId, setOpenBlockId] = useState(null);
|
|
@@ -9855,6 +9973,11 @@ function BlockKitchen(props) {
|
|
|
9855
9973
|
return { emoji: makeEmojiHook(byName), ...previewHooks };
|
|
9856
9974
|
}, [customEmojis, previewHooks]);
|
|
9857
9975
|
const validation = useBlockKitValidation(blocks, "message");
|
|
9976
|
+
const validationSummary = useMemo(
|
|
9977
|
+
() => ({ valid: validation.valid, errorCount: validation.total, errors: validation.errors }),
|
|
9978
|
+
[validation]
|
|
9979
|
+
);
|
|
9980
|
+
useNotifyOnChange(validationSummary, validationSummaryKey, onValidationChange);
|
|
9858
9981
|
const sensors = useSensors(
|
|
9859
9982
|
useSensor(PointerSensor, { activationConstraint: { distance: 4 } }),
|
|
9860
9983
|
useSensor(TouchSensor, { activationConstraint: { delay: 150, tolerance: 8 } }),
|
|
@@ -9958,8 +10081,19 @@ function BlockKitchen(props) {
|
|
|
9958
10081
|
allowedSurfaces,
|
|
9959
10082
|
showThemeControl: isPreviewThemeControlled ? false : showThemeControl,
|
|
9960
10083
|
docsLink,
|
|
10084
|
+
showSend: sendEnabled,
|
|
10085
|
+
primaryAction: !sendEnabled && primaryAction ? {
|
|
10086
|
+
label: primaryAction.label,
|
|
10087
|
+
onClick: () => primaryAction.onClick({
|
|
10088
|
+
blocks: blockPayloads,
|
|
10089
|
+
validation: validationSummary,
|
|
10090
|
+
loadedMessage: activeEditTarget
|
|
10091
|
+
}),
|
|
10092
|
+
disabled: primaryAction.disableWhenInvalid ? !validationSummary.valid : false
|
|
10093
|
+
} : null,
|
|
9961
10094
|
sendButtonLabel,
|
|
9962
|
-
|
|
10095
|
+
loadEnabled,
|
|
10096
|
+
updateEnabled: Boolean(onUpdate),
|
|
9963
10097
|
editBadge: activeEditTarget ? {
|
|
9964
10098
|
channelLabel: activeEditTarget.channelName ? `#${activeEditTarget.channelName}` : activeEditTarget.channelId,
|
|
9965
10099
|
ts: activeEditTarget.ts
|
|
@@ -9967,9 +10101,13 @@ function BlockKitchen(props) {
|
|
|
9967
10101
|
onOpenLoad: () => setLoadOpen(true),
|
|
9968
10102
|
onOpenUpdate: () => setUpdateOpen(true),
|
|
9969
10103
|
onExitEdit: () => {
|
|
10104
|
+
if (sendEnabled) {
|
|
10105
|
+
setEditTarget(null);
|
|
10106
|
+
replaceAll([]);
|
|
10107
|
+
setLoadOpen(true);
|
|
10108
|
+
return;
|
|
10109
|
+
}
|
|
9970
10110
|
setEditTarget(null);
|
|
9971
|
-
replaceAll([]);
|
|
9972
|
-
setLoadOpen(true);
|
|
9973
10111
|
},
|
|
9974
10112
|
loadButtonLabel,
|
|
9975
10113
|
updateButtonLabel
|
|
@@ -10041,7 +10179,7 @@ function BlockKitchen(props) {
|
|
|
10041
10179
|
}
|
|
10042
10180
|
) }),
|
|
10043
10181
|
/* @__PURE__ */ jsx(JsonDrawer, { open: jsonOpen, onOpenChange: setJsonOpen, blocks: blockPayloads, onApply: replaceAll }),
|
|
10044
|
-
/* @__PURE__ */ jsx(
|
|
10182
|
+
loadChannels && loadSendAsUserStatus && onSend ? /* @__PURE__ */ jsx(
|
|
10045
10183
|
SendDialog,
|
|
10046
10184
|
{
|
|
10047
10185
|
open: sendOpen,
|
|
@@ -10050,6 +10188,7 @@ function BlockKitchen(props) {
|
|
|
10050
10188
|
loadChannels,
|
|
10051
10189
|
loadSendAsUserStatus,
|
|
10052
10190
|
onSend,
|
|
10191
|
+
renderSendExtras,
|
|
10053
10192
|
confirmSendLabel,
|
|
10054
10193
|
errorCount: validation.total,
|
|
10055
10194
|
onShowIssues: () => {
|
|
@@ -10057,8 +10196,8 @@ function BlockKitchen(props) {
|
|
|
10057
10196
|
setIssuesOpen(true);
|
|
10058
10197
|
}
|
|
10059
10198
|
}
|
|
10060
|
-
),
|
|
10061
|
-
activeEditTarget &&
|
|
10199
|
+
) : null,
|
|
10200
|
+
activeEditTarget && onUpdate && loadSendAsUserStatus ? /* @__PURE__ */ jsx(
|
|
10062
10201
|
UpdateDialog,
|
|
10063
10202
|
{
|
|
10064
10203
|
open: updateOpen,
|
|
@@ -10066,7 +10205,7 @@ function BlockKitchen(props) {
|
|
|
10066
10205
|
target: activeEditTarget,
|
|
10067
10206
|
blocks: blockPayloads,
|
|
10068
10207
|
loadSendAsUserStatus,
|
|
10069
|
-
onUpdate
|
|
10208
|
+
onUpdate,
|
|
10070
10209
|
confirmUpdateLabel,
|
|
10071
10210
|
errorCount: validation.total,
|
|
10072
10211
|
onShowIssues: () => {
|
|
@@ -10075,25 +10214,17 @@ function BlockKitchen(props) {
|
|
|
10075
10214
|
}
|
|
10076
10215
|
}
|
|
10077
10216
|
) : null,
|
|
10078
|
-
|
|
10217
|
+
loading ? /* @__PURE__ */ jsx(
|
|
10079
10218
|
LoadMessageDialog,
|
|
10080
10219
|
{
|
|
10081
10220
|
open: loadOpen,
|
|
10082
10221
|
onOpenChange: setLoadOpen,
|
|
10083
|
-
onLoadMessage:
|
|
10084
|
-
loadRecentMessages:
|
|
10085
|
-
loadChannels,
|
|
10222
|
+
onLoadMessage: loading.onLoadMessage,
|
|
10223
|
+
loadRecentMessages: loading.loadRecentMessages,
|
|
10224
|
+
loadChannels: recentChannelSource,
|
|
10086
10225
|
onLoaded: (result) => {
|
|
10087
10226
|
replaceAll(result.blocks);
|
|
10088
|
-
setEditTarget(
|
|
10089
|
-
channelId: result.channelId,
|
|
10090
|
-
channelName: result.channelName,
|
|
10091
|
-
ts: result.ts,
|
|
10092
|
-
editableVia: result.editableVia,
|
|
10093
|
-
workspaceName: result.workspaceName,
|
|
10094
|
-
username: result.username,
|
|
10095
|
-
iconUrl: result.iconUrl
|
|
10096
|
-
});
|
|
10227
|
+
setEditTarget(toLoadedMessage(result));
|
|
10097
10228
|
setLoadOpen(false);
|
|
10098
10229
|
},
|
|
10099
10230
|
onOpenAsNew: (loadedBlocks) => {
|
|
@@ -10275,6 +10406,6 @@ function fromBase64Url(b64url) {
|
|
|
10275
10406
|
return b64 + "=".repeat(padLen);
|
|
10276
10407
|
}
|
|
10277
10408
|
|
|
10278
|
-
export { BlockKitchen, TemplatePicker, decodeBlocksFromString, defaultPalette, encodeBlocksToString, extraAlertVariant, legacyInputVariants, toSlackBlocks };
|
|
10409
|
+
export { BlockKitchen, SendDialog, SlackSignInButton, TemplatePicker, decodeBlocksFromString, defaultPalette, encodeBlocksToString, extraAlertVariant, legacyInputVariants, toSlackBlocks, useSlackSignIn };
|
|
10279
10410
|
//# sourceMappingURL=index.js.map
|
|
10280
10411
|
//# sourceMappingURL=index.js.map
|