@tightknitai/block-kitchen 0.10.26 → 0.10.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { useSensors, useSensor, PointerSensor, TouchSensor, KeyboardSensor, pointerWithin, closestCenter, DndContext, DragOverlay, useDroppable, useDraggable } from '@dnd-kit/core';
2
2
  import { sortableKeyboardCoordinates, SortableContext, verticalListSortingStrategy, useSortable } from '@dnd-kit/sortable';
3
- import { X, Circle, Sparkles, FileText, AlignLeft, MousePointerClick, TextCursorInput, LayoutTemplate, Bell, Pilcrow, Image, Video, GalleryHorizontal, Table, ChartColumnBig, Container, Loader2, ExternalLink, AlertTriangle, ChevronRight, ChevronLeft, GripVertical, ChevronsUpDown, Check, Sun, Moon, MessageSquare, AppWindow, Home, Plus, ChevronDown, BookOpen, Undo2, Redo2, Trash2, Code2, Pencil, Send, Search, Copy, Info, CheckCircle2, LayoutGrid, ArrowUp, ArrowDown, Lock, Type, Bold, Italic, Strikethrough, Underline, Code, AtSign, Hash, List, ListOrdered, Quote, Smile, Link as Link$1 } from 'lucide-react';
3
+ import { X, Circle, Sparkles, FileText, AlignLeft, MousePointerClick, TextCursorInput, LayoutTemplate, Bell, Pilcrow, Image, Video, GalleryHorizontal, Table, ChartColumnBig, Container, Loader2, ExternalLink, AlertTriangle, ChevronRight, ChevronLeft, GripVertical, ChevronsUpDown, Check, Sun, Moon, MessageSquare, AppWindow, Home, Plus, ChevronDown, BookOpen, Undo2, Redo2, Trash2, Code2, Pencil, Send, Search, Copy, Info, CheckCircle2, LayoutGrid, ArrowUp, ArrowDown, ImagePlus, Lock, Type, Upload, Bold, Italic, Strikethrough, Underline, Code, AtSign, Hash, List, ListOrdered, Quote, Smile, Link as Link$1 } from 'lucide-react';
4
4
  import * as React from 'react';
5
5
  import { createContext, useContext, useState, useRef, useEffect, useCallback, useMemo, useId, useSyncExternalStore, useLayoutEffect } from 'react';
6
6
  import { clsx } from 'clsx';
@@ -1928,6 +1928,36 @@ function CustomEmojiProvider({ customEmojis, children }) {
1928
1928
  function useCustomEmojis() {
1929
1929
  return useContext(CustomEmojiContext);
1930
1930
  }
1931
+ var DEFAULT_IMAGE_UPLOAD_ACCEPT = ["image/png", "image/jpeg", "image/gif"];
1932
+ var ImageUploadContext = createContext(null);
1933
+ function ImageUploadProvider({
1934
+ imageUpload,
1935
+ children
1936
+ }) {
1937
+ const uploadImage = imageUpload?.uploadImage;
1938
+ const accept = imageUpload?.accept;
1939
+ const value = useMemo(
1940
+ () => uploadImage ? { uploadImage, accept: accept ?? DEFAULT_IMAGE_UPLOAD_ACCEPT } : null,
1941
+ [uploadImage, accept]
1942
+ );
1943
+ return /* @__PURE__ */ jsx(ImageUploadContext.Provider, { value, children });
1944
+ }
1945
+ function useImageUpload() {
1946
+ return useContext(ImageUploadContext);
1947
+ }
1948
+ function isAcceptedImage(file, accept) {
1949
+ return accept.includes(file.type);
1950
+ }
1951
+ function uploadErrorMessage(error) {
1952
+ return error instanceof Error && error.message ? error.message : "Upload failed. Try again.";
1953
+ }
1954
+ function isFileDrag(dataTransfer) {
1955
+ return Boolean(dataTransfer && Array.from(dataTransfer.types).includes("Files"));
1956
+ }
1957
+ function formatList(accept) {
1958
+ const names = accept.map((type) => type.replace(/^image\//, "").toUpperCase());
1959
+ return names.length > 1 ? `${names.slice(0, -1).join(", ")} or ${names[names.length - 1]}` : names[0] ?? "";
1960
+ }
1931
1961
  var MentionsContext = createContext(null);
1932
1962
  function MentionsProvider({ mentions, children }) {
1933
1963
  return /* @__PURE__ */ jsx(MentionsContext.Provider, { value: mentions ?? null, children });
@@ -3239,6 +3269,13 @@ var PopoverContent = React.forwardRef(({ className, align = "start", sideOffset
3239
3269
  }
3240
3270
  ) }));
3241
3271
  PopoverContent.displayName = PopoverPrimitive.Content.displayName;
3272
+ function returnFocusUnlessMoved(event) {
3273
+ const active = document.activeElement;
3274
+ const closing = event.target instanceof Node ? event.target : null;
3275
+ if (active && active !== document.body && !closing?.contains(active)) {
3276
+ event.preventDefault();
3277
+ }
3278
+ }
3242
3279
  function matchesQuery(name, query) {
3243
3280
  const needle = query.replace(/^\s*#\s*/, "").trim().toLowerCase();
3244
3281
  return needle === "" || name.toLowerCase().includes(needle);
@@ -4730,6 +4767,73 @@ function SendDialog({
4730
4767
  ] })
4731
4768
  ] }) });
4732
4769
  }
4770
+ var ALT_TEXT_MAX_LENGTH = 2e3;
4771
+ function altTextFromFileName(file) {
4772
+ const name = file.name.replace(/\.[^.]+$/, "").trim();
4773
+ return (name || "Image").slice(0, ALT_TEXT_MAX_LENGTH);
4774
+ }
4775
+ function useCanvasImageDrop({
4776
+ upload,
4777
+ enabled,
4778
+ onAddBlock
4779
+ }) {
4780
+ const [dragging, setDragging] = useState(false);
4781
+ const [pending, setPending] = useState(0);
4782
+ const [error, setError] = useState(null);
4783
+ const onAddBlockRef = useRef(onAddBlock);
4784
+ onAddBlockRef.current = onAddBlock;
4785
+ const addFiles = async (activeUpload, files) => {
4786
+ const images = files.filter((file) => isAcceptedImage(file, activeUpload.accept));
4787
+ const skipped = files.length - images.length;
4788
+ setError(
4789
+ skipped > 0 ? `${skipped === 1 ? "1 file was" : `${skipped} files were`} skipped. Only ${formatList(activeUpload.accept)} images can be added.` : null
4790
+ );
4791
+ if (images.length === 0) {
4792
+ return;
4793
+ }
4794
+ setPending((count) => count + images.length);
4795
+ const results = await Promise.allSettled(images.map((file) => activeUpload.uploadImage(file)));
4796
+ setPending((count) => count - images.length);
4797
+ const failures = [];
4798
+ results.forEach((result, idx) => {
4799
+ const file = images[idx];
4800
+ if (result.status === "fulfilled") {
4801
+ onAddBlockRef.current?.({ type: "image", image_url: result.value.url, alt_text: altTextFromFileName(file) });
4802
+ } else {
4803
+ failures.push(`Couldn't upload "${file.name}": ${uploadErrorMessage(result.reason)}`);
4804
+ }
4805
+ });
4806
+ if (failures.length > 0) {
4807
+ setError(failures.join(" "));
4808
+ }
4809
+ };
4810
+ const isOwnEvent = (e) => isFileDrag(e.dataTransfer) && e.currentTarget.contains(e.target);
4811
+ const handlers = upload && enabled && onAddBlock ? {
4812
+ onDragOver: (e) => {
4813
+ if (!isOwnEvent(e)) {
4814
+ return;
4815
+ }
4816
+ e.preventDefault();
4817
+ e.dataTransfer.dropEffect = "copy";
4818
+ setDragging(true);
4819
+ },
4820
+ onDragLeave: (e) => {
4821
+ const next = e.relatedTarget;
4822
+ if (!next || !e.currentTarget.contains(next)) {
4823
+ setDragging(false);
4824
+ }
4825
+ },
4826
+ onDrop: (e) => {
4827
+ if (!isOwnEvent(e)) {
4828
+ return;
4829
+ }
4830
+ e.preventDefault();
4831
+ setDragging(false);
4832
+ void addFiles(upload, Array.from(e.dataTransfer.files));
4833
+ }
4834
+ } : void 0;
4835
+ return { handlers, dragging, pending, error, dismissError: () => setError(null) };
4836
+ }
4733
4837
  function BlockActionButtons({
4734
4838
  actions,
4735
4839
  position,
@@ -4984,6 +5088,129 @@ function AlertEditor({ block, onChange }) {
4984
5088
  ) })
4985
5089
  ] });
4986
5090
  }
5091
+ function ImageUrlInput({
5092
+ id,
5093
+ value,
5094
+ placeholder,
5095
+ onChange
5096
+ }) {
5097
+ const upload = useImageUpload();
5098
+ const input = /* @__PURE__ */ jsx(Input, { id, type: "url", value, placeholder, onChange: (e) => onChange(e.target.value) });
5099
+ if (!upload) {
5100
+ return input;
5101
+ }
5102
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
5103
+ /* @__PURE__ */ jsx(UploadDropZone, { upload, inputId: id, onUploaded: onChange, variant: "inline" }),
5104
+ input
5105
+ ] });
5106
+ }
5107
+ function UploadDropZone({
5108
+ upload,
5109
+ inputId,
5110
+ onUploaded,
5111
+ variant
5112
+ }) {
5113
+ const [uploading, setUploading] = useState(false);
5114
+ const [dragging, setDragging] = useState(false);
5115
+ const [error, setError] = useState(null);
5116
+ const fileInputRef = useRef(null);
5117
+ const onUploadedRef = useRef(onUploaded);
5118
+ onUploadedRef.current = onUploaded;
5119
+ const uploadFile = async (file) => {
5120
+ if (!isAcceptedImage(file, upload.accept)) {
5121
+ setError(`"${file.name}" isn't a supported image. Use ${formatList(upload.accept)}.`);
5122
+ return;
5123
+ }
5124
+ setError(null);
5125
+ setUploading(true);
5126
+ try {
5127
+ const { url } = await upload.uploadImage(file);
5128
+ onUploadedRef.current(url);
5129
+ } catch (err) {
5130
+ setError(uploadErrorMessage(err));
5131
+ } finally {
5132
+ setUploading(false);
5133
+ }
5134
+ };
5135
+ const onDragOver = (e) => {
5136
+ if (!isFileDrag(e.dataTransfer)) {
5137
+ return;
5138
+ }
5139
+ e.preventDefault();
5140
+ e.stopPropagation();
5141
+ setDragging(true);
5142
+ };
5143
+ const onDrop = (e) => {
5144
+ if (!isFileDrag(e.dataTransfer)) {
5145
+ return;
5146
+ }
5147
+ e.preventDefault();
5148
+ e.stopPropagation();
5149
+ setDragging(false);
5150
+ const [file] = Array.from(e.dataTransfer.files);
5151
+ if (file && !uploading) {
5152
+ void uploadFile(file);
5153
+ }
5154
+ };
5155
+ const fileInput = /* @__PURE__ */ jsx(
5156
+ "input",
5157
+ {
5158
+ ref: fileInputRef,
5159
+ id: `${inputId}-file`,
5160
+ type: "file",
5161
+ accept: upload.accept.join(","),
5162
+ className: "hidden",
5163
+ tabIndex: -1,
5164
+ "aria-hidden": "true",
5165
+ onChange: (e) => {
5166
+ const file = e.target.files?.[0];
5167
+ e.target.value = "";
5168
+ if (file) {
5169
+ void uploadFile(file);
5170
+ }
5171
+ }
5172
+ }
5173
+ );
5174
+ const pickButton = (label) => /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", className: "h-7", disabled: uploading, onClick: () => fileInputRef.current?.click(), children: label });
5175
+ const dropHandlers = { onDragOver, onDragLeave: () => setDragging(false), onDrop };
5176
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
5177
+ variant === "panel" ? /* @__PURE__ */ jsxs(
5178
+ "section",
5179
+ {
5180
+ "aria-label": "Upload an image",
5181
+ ...dropHandlers,
5182
+ className: cn(
5183
+ "flex flex-col items-center justify-center gap-2 rounded-md border border-dashed px-4 py-6 text-center text-xs text-muted-foreground transition-colors",
5184
+ dragging && "border-primary bg-primary/5 text-primary"
5185
+ ),
5186
+ children: [
5187
+ /* @__PURE__ */ jsx(Upload, { className: cn("h-5 w-5", uploading && "animate-pulse"), "aria-hidden": "true" }),
5188
+ /* @__PURE__ */ jsx("span", { "aria-live": "polite", children: uploading ? "Uploading\u2026" : dragging ? "Drop to upload" : "Drag an image here, or" }),
5189
+ pickButton("Choose file"),
5190
+ /* @__PURE__ */ jsx("span", { className: "text-[11px]", children: formatList(upload.accept) }),
5191
+ fileInput
5192
+ ]
5193
+ }
5194
+ ) : /* @__PURE__ */ jsxs(
5195
+ "section",
5196
+ {
5197
+ "aria-label": "Upload an image",
5198
+ ...dropHandlers,
5199
+ className: cn(
5200
+ "flex items-center gap-2 rounded-md border border-dashed px-3 py-2 text-[11px] text-muted-foreground transition-colors",
5201
+ dragging && "border-primary bg-primary/5 text-primary"
5202
+ ),
5203
+ children: [
5204
+ /* @__PURE__ */ jsx(Upload, { className: cn("h-3.5 w-3.5 shrink-0", uploading && "animate-pulse"), "aria-hidden": "true" }),
5205
+ /* @__PURE__ */ jsx("span", { className: "flex-1", "aria-live": "polite", children: uploading ? "Uploading\u2026" : dragging ? "Drop to upload" : "Drop an image here, or paste a URL below" }),
5206
+ pickButton("Upload image"),
5207
+ fileInput
5208
+ ]
5209
+ }
5210
+ ),
5211
+ error ? /* @__PURE__ */ jsx("p", { role: "alert", className: "text-[11px] leading-snug text-destructive", children: error }) : null
5212
+ ] });
5213
+ }
4987
5214
  function CardEditor({
4988
5215
  block,
4989
5216
  onChange,
@@ -5090,13 +5317,12 @@ function ImageFields({
5090
5317
  };
5091
5318
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 rounded-md border bg-muted/20 p-3", children: [
5092
5319
  /* @__PURE__ */ jsx(EditorField, { label, help, htmlFor: `${idPrefix}-url`, children: /* @__PURE__ */ jsx(
5093
- Input,
5320
+ ImageUrlInput,
5094
5321
  {
5095
5322
  id: `${idPrefix}-url`,
5096
- type: "url",
5097
5323
  value: url,
5098
5324
  placeholder: "e.g. https://example.com/image.png",
5099
- onChange: (e) => update({ url: e.target.value })
5325
+ onChange: (next) => update({ url: next })
5100
5326
  }
5101
5327
  ) }),
5102
5328
  /* @__PURE__ */ jsx(EditorField, { label: "Alt text", help: "Describes the image for screen readers.", htmlFor: `${idPrefix}-alt`, children: /* @__PURE__ */ jsx(
@@ -7656,15 +7882,14 @@ function ContainerEditor({ block, onChange }) {
7656
7882
  }
7657
7883
  ) : null,
7658
7884
  /* @__PURE__ */ jsx(EditorField, { label: "Icon URL", help: "Optional image shown left of the title.", htmlFor: "container-icon", children: /* @__PURE__ */ jsx(
7659
- Input,
7885
+ ImageUrlInput,
7660
7886
  {
7661
7887
  id: "container-icon",
7662
- type: "url",
7663
7888
  value: iconUrl,
7664
7889
  placeholder: "https://example.com/icon.png",
7665
- onChange: (e) => onChange({
7890
+ onChange: (url) => onChange({
7666
7891
  ...block,
7667
- icon: e.target.value ? { type: "image", image_url: e.target.value, alt_text: iconAlt || "Container icon" } : void 0
7892
+ icon: url ? { type: "image", image_url: url, alt_text: iconAlt || "Container icon" } : void 0
7668
7893
  })
7669
7894
  }
7670
7895
  ) }),
@@ -8162,13 +8387,12 @@ function ImageItemCard({
8162
8387
  help: "A publicly accessible image URL. Best at small sizes (around 20px).",
8163
8388
  htmlFor: `ctx-img-url-${index}`,
8164
8389
  children: /* @__PURE__ */ jsx(
8165
- Input,
8390
+ ImageUrlInput,
8166
8391
  {
8167
8392
  id: `ctx-img-url-${index}`,
8168
- type: "url",
8169
8393
  value: element.image_url,
8170
8394
  placeholder: "e.g. https://example.com/avatar.png",
8171
- onChange: (e) => onChange({ ...element, image_url: e.target.value })
8395
+ onChange: (url) => onChange({ ...element, image_url: url })
8172
8396
  }
8173
8397
  )
8174
8398
  }
@@ -8910,36 +9134,44 @@ function HeaderEditor({ block, onChange }) {
8910
9134
  ] });
8911
9135
  }
8912
9136
  function ImageEditor({ block, onChange }) {
8913
- const source = "slack_file" in block ? "slack_file" : "url";
9137
+ const upload = useImageUpload();
9138
+ const [urlSource, setUrlSource] = useState(upload ? "upload" : "url");
9139
+ const isSlackFile = "slack_file" in block;
9140
+ const source = isSlackFile ? "slack_file" : upload ? urlSource : "url";
8914
9141
  const titleText = block.title?.text ?? "";
8915
9142
  const setSource = (next) => {
8916
9143
  if (next === source) {
8917
9144
  return;
8918
9145
  }
8919
- if (next === "url") {
9146
+ if (next !== "slack_file") {
9147
+ setUrlSource(next);
9148
+ }
9149
+ if (next !== "slack_file" && isSlackFile) {
8920
9150
  const { slack_file: _omit, ...rest } = block;
8921
9151
  onChange({ image_url: "", ...rest });
8922
- } else {
9152
+ } else if (next === "slack_file") {
8923
9153
  const { image_url: _omit, ...rest } = block;
8924
9154
  onChange({ slack_file: { id: "" }, ...rest });
8925
9155
  }
8926
9156
  };
9157
+ const sourceOptions = [
9158
+ ...upload ? [["upload", "Upload image"]] : [],
9159
+ ["url", "Public URL"],
9160
+ ["slack_file", "Slack file"]
9161
+ ];
8927
9162
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
8928
9163
  /* @__PURE__ */ jsx(
8929
9164
  EditorField,
8930
9165
  {
8931
9166
  label: "Image source",
8932
- help: "Where the image comes from: a public URL, or a file already uploaded to your Slack workspace.",
9167
+ help: upload ? "Where the image comes from: a file from your device, a public URL, or a file already uploaded to your Slack workspace." : "Where the image comes from: a public URL, or a file already uploaded to your Slack workspace.",
8933
9168
  children: /* @__PURE__ */ jsx(
8934
9169
  RadioGroup,
8935
9170
  {
8936
9171
  value: source,
8937
9172
  onValueChange: (v) => setSource(v),
8938
9173
  className: "flex flex-row flex-wrap gap-3",
8939
- children: [
8940
- ["url", "Public URL"],
8941
- ["slack_file", "Slack file"]
8942
- ].map(([value, label]) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
9174
+ children: sourceOptions.map(([value, label]) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
8943
9175
  /* @__PURE__ */ jsx(RadioGroupItem, { value, id: `img-source-${value}` }),
8944
9176
  /* @__PURE__ */ jsx(Label, { htmlFor: `img-source-${value}`, className: "text-xs", children: label })
8945
9177
  ] }, value))
@@ -8947,7 +9179,15 @@ function ImageEditor({ block, onChange }) {
8947
9179
  )
8948
9180
  }
8949
9181
  ),
8950
- "slack_file" in block ? /* @__PURE__ */ jsx(SlackFileFields, { block, onChange }) : /* @__PURE__ */ jsx(EditorField, { label: "Image URL", help: "A publicly accessible image URL (PNG, JPG, GIF).", htmlFor: "img-url", children: /* @__PURE__ */ jsx(
9182
+ "slack_file" in block ? /* @__PURE__ */ jsx(SlackFileFields, { block, onChange }) : source === "upload" && upload ? /* @__PURE__ */ jsx(
9183
+ UploadDropZone,
9184
+ {
9185
+ upload,
9186
+ inputId: "img-upload",
9187
+ variant: "panel",
9188
+ onUploaded: (url) => onChange({ ...block, image_url: url })
9189
+ }
9190
+ ) : /* @__PURE__ */ jsx(EditorField, { label: "Image URL", help: "A publicly accessible image URL (PNG, JPG, GIF).", htmlFor: "img-url", children: /* @__PURE__ */ jsx(
8951
9191
  Input,
8952
9192
  {
8953
9193
  id: "img-url",
@@ -10130,13 +10370,12 @@ function ImageAccessoryFields({ image, onChange }) {
10130
10370
  help: "A publicly accessible image URL (PNG, JPG, GIF).",
10131
10371
  htmlFor: "section-acc-img-url",
10132
10372
  children: /* @__PURE__ */ jsx(
10133
- Input,
10373
+ ImageUrlInput,
10134
10374
  {
10135
10375
  id: "section-acc-img-url",
10136
- type: "url",
10137
10376
  value: image.image_url ?? "",
10138
10377
  placeholder: "e.g. https://example.com/thumb.png",
10139
- onChange: (e) => onChange({ ...image, image_url: e.target.value })
10378
+ onChange: (url) => onChange({ ...image, image_url: url })
10140
10379
  }
10141
10380
  )
10142
10381
  }
@@ -10499,10 +10738,24 @@ function BlockEditor({
10499
10738
  const custom = useCustomBlockEditor(block);
10500
10739
  const CustomForm = custom?.component;
10501
10740
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
10502
- /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsxs("h3", { className: "text-sm font-semibold text-foreground", children: [
10503
- "Edit ",
10504
- custom ? custom.label : labelForBlockType(block.type)
10505
- ] }) }),
10741
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
10742
+ /* @__PURE__ */ jsxs("h3", { className: "text-sm font-semibold text-foreground", children: [
10743
+ "Edit ",
10744
+ custom ? custom.label : labelForBlockType(block.type)
10745
+ ] }),
10746
+ onDone ? /* @__PURE__ */ jsx(
10747
+ Button,
10748
+ {
10749
+ type: "button",
10750
+ variant: "ghost",
10751
+ size: "icon",
10752
+ "aria-label": "Close editor",
10753
+ className: "-my-1 -mr-1 h-7 w-7 text-muted-foreground",
10754
+ onClick: onDone,
10755
+ children: /* @__PURE__ */ jsx(X, {})
10756
+ }
10757
+ ) : null
10758
+ ] }),
10506
10759
  errors && errors.length > 0 ? /* @__PURE__ */ jsx("ul", { className: "flex flex-col gap-0.5 rounded-md border border-destructive/30 bg-destructive/5 p-2 text-xs text-destructive", children: errors.map((err, i) => /* @__PURE__ */ jsx("li", { children: err }, i)) }) : null,
10507
10760
  CustomForm ? /* @__PURE__ */ jsx(CustomForm, { block, onChange }) : dispatch(block, onChange),
10508
10761
  /* @__PURE__ */ jsx(
@@ -10664,15 +10917,23 @@ function BlockRow({
10664
10917
  children: preview
10665
10918
  }
10666
10919
  ) }),
10667
- /* @__PURE__ */ jsx(PopoverContent, { className: "w-[min(32rem,calc(100vw-1.5rem))] sm:w-[32rem]", align: "start", children: /* @__PURE__ */ jsx(
10668
- BlockEditor,
10920
+ /* @__PURE__ */ jsx(
10921
+ PopoverContent,
10669
10922
  {
10670
- block: builderBlock.block,
10671
- errors,
10672
- onChange: (next) => onUpdate(builderBlock.id, next),
10673
- onDone: () => onOpenChange?.(false)
10923
+ className: "w-[min(32rem,calc(100vw-1.5rem))] sm:w-[32rem]",
10924
+ align: "start",
10925
+ onCloseAutoFocus: returnFocusUnlessMoved,
10926
+ children: /* @__PURE__ */ jsx(
10927
+ BlockEditor,
10928
+ {
10929
+ block: builderBlock.block,
10930
+ errors,
10931
+ onChange: (next) => onUpdate(builderBlock.id, next),
10932
+ onDone: () => onOpenChange?.(false)
10933
+ }
10934
+ )
10674
10935
  }
10675
- ) })
10936
+ )
10676
10937
  ] });
10677
10938
  const adornment = position && renderBlockAdornment?.(position);
10678
10939
  const hoverChrome = /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -10938,7 +11199,7 @@ function ContainerRow({
10938
11199
  hasErrors ? "border-destructive/60" : "border-border"
10939
11200
  ),
10940
11201
  children: [
10941
- /* @__PURE__ */ jsxs(Popover, { open: isOpen, onOpenChange: (open) => onOpenBlockChange?.(open ? id : null), children: [
11202
+ /* @__PURE__ */ jsxs(Popover, { open: isOpen, onOpenChange: (open) => onOpenBlockChange?.(id, open), children: [
10942
11203
  /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
10943
11204
  "div",
10944
11205
  {
@@ -10950,7 +11211,7 @@ function ContainerRow({
10950
11211
  onKeyDown: (e) => {
10951
11212
  if (e.key === "Enter" || e.key === " ") {
10952
11213
  e.preventDefault();
10953
- onOpenBlockChange?.(!isOpen ? id : null);
11214
+ onOpenBlockChange?.(id, !isOpen);
10954
11215
  }
10955
11216
  },
10956
11217
  className: "flex w-full cursor-grab items-center gap-2 border-b bg-muted/40 px-3 py-2 text-left transition-colors hover:bg-muted active:cursor-grabbing",
@@ -10964,15 +11225,23 @@ function ContainerRow({
10964
11225
  ]
10965
11226
  }
10966
11227
  ) }),
10967
- /* @__PURE__ */ jsx(PopoverContent, { className: "w-[min(32rem,calc(100vw-1.5rem))] sm:w-[32rem]", align: "start", children: /* @__PURE__ */ jsx(
10968
- BlockEditor,
11228
+ /* @__PURE__ */ jsx(
11229
+ PopoverContent,
10969
11230
  {
10970
- block,
10971
- errors,
10972
- onChange: (next) => onUpdate(id, next),
10973
- onDone: () => onOpenBlockChange?.(null)
11231
+ className: "w-[min(32rem,calc(100vw-1.5rem))] sm:w-[32rem]",
11232
+ align: "start",
11233
+ onCloseAutoFocus: returnFocusUnlessMoved,
11234
+ children: /* @__PURE__ */ jsx(
11235
+ BlockEditor,
11236
+ {
11237
+ block,
11238
+ errors,
11239
+ onChange: (next) => onUpdate(id, next),
11240
+ onDone: () => onOpenBlockChange?.(id, false)
11241
+ }
11242
+ )
10974
11243
  }
10975
- ) })
11244
+ )
10976
11245
  ] }),
10977
11246
  /* @__PURE__ */ jsx(
10978
11247
  "div",
@@ -11001,7 +11270,7 @@ function ContainerRow({
11001
11270
  previewTheme,
11002
11271
  errors: errorsByBlockId?.get(child.id),
11003
11272
  isOpen: openBlockId === child.id,
11004
- onOpenChange: (open) => onOpenBlockChange?.(open ? child.id : null),
11273
+ onOpenChange: (open) => onOpenBlockChange?.(child.id, open),
11005
11274
  onUpdate,
11006
11275
  onDuplicate,
11007
11276
  onDelete,
@@ -11036,7 +11305,7 @@ function ContainerRow({
11036
11305
  {
11037
11306
  type: "button",
11038
11307
  "aria-label": `Show ${errors.length} validation ${errors.length === 1 ? "issue" : "issues"}`,
11039
- onClick: () => onOpenBlockChange?.(id),
11308
+ onClick: () => onOpenBlockChange?.(id, true),
11040
11309
  className: "flex h-6 w-6 items-center justify-center rounded text-destructive hover:bg-destructive/10 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
11041
11310
  children: /* @__PURE__ */ jsx(AlertTriangle, { className: "h-3.5 w-3.5" })
11042
11311
  }
@@ -11055,7 +11324,7 @@ function ContainerRow({
11055
11324
  /* @__PURE__ */ jsx(ToolbarButton2, { label: "Move down", disabled: index >= total - 1, onClick: () => onReorder(id, index + 1), children: /* @__PURE__ */ jsx(ArrowDown, { className: "h-3.5 w-3.5" }) })
11056
11325
  ] }) : null,
11057
11326
  /* @__PURE__ */ jsx(BlockActionButtons, { actions: blockActions, position, variant: "toolbar" }),
11058
- /* @__PURE__ */ jsx(ToolbarButton2, { label: "Edit", onClick: () => onOpenBlockChange?.(id), children: /* @__PURE__ */ jsx(Pencil, { className: "h-3.5 w-3.5" }) }),
11327
+ /* @__PURE__ */ jsx(ToolbarButton2, { label: "Edit", onClick: () => onOpenBlockChange?.(id, true), children: /* @__PURE__ */ jsx(Pencil, { className: "h-3.5 w-3.5" }) }),
11059
11328
  /* @__PURE__ */ jsx(ToolbarButton2, { label: "Duplicate", onClick: () => onDuplicate(id), children: /* @__PURE__ */ jsx(Copy, { className: "h-3.5 w-3.5" }) }),
11060
11329
  /* @__PURE__ */ jsx(ToolbarButton2, { label: "Delete", destructive: true, onClick: () => onDelete(id), children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" }) })
11061
11330
  ]
@@ -11088,7 +11357,7 @@ function ContainerRow({
11088
11357
  {
11089
11358
  type: "button",
11090
11359
  "aria-label": "Edit container",
11091
- onClick: () => onOpenBlockChange?.(id),
11360
+ onClick: () => onOpenBlockChange?.(id, true),
11092
11361
  className: "flex h-9 w-9 items-center justify-center rounded text-muted-foreground hover:bg-accent hover:text-foreground",
11093
11362
  children: /* @__PURE__ */ jsx(Pencil, { className: "h-4 w-4" })
11094
11363
  }
@@ -11166,10 +11435,14 @@ function Surface({
11166
11435
  isPaletteDrag = false,
11167
11436
  onOpenPalette,
11168
11437
  blockActions,
11169
- renderBlockAdornment
11438
+ renderBlockAdornment,
11439
+ onAddBlock,
11440
+ imageDropEnabled = true
11170
11441
  }) {
11171
11442
  const { setNodeRef, isOver } = useDroppable({ id: SURFACE_DROPPABLE_ID });
11172
11443
  const isDark = previewTheme === "dark";
11444
+ const imageDrop = useCanvasImageDrop({ upload: useImageUpload(), enabled: imageDropEnabled, onAddBlock });
11445
+ const isDropTarget = isPaletteDrag || imageDrop.dragging;
11173
11446
  const showEndDropZone = isPaletteDrag && isOver && blocks.length > 0;
11174
11447
  const blocksList = /* @__PURE__ */ jsx(
11175
11448
  "div",
@@ -11178,10 +11451,19 @@ function Surface({
11178
11451
  className: cn(
11179
11452
  "flex min-h-[180px] flex-col px-3 py-2 transition-colors sm:min-h-[240px] sm:px-5",
11180
11453
  isDark ? "bg-[#1a1d21]" : "bg-white",
11181
- isPaletteDrag && (isDark ? "bg-[#22262c] outline-2 outline-primary/40 -outline-offset-2 outline-dashed" : "bg-[#f5f8ff] outline-2 outline-primary/40 -outline-offset-2 outline-dashed"),
11182
- isPaletteDrag && isOver && (isDark ? "bg-[#2c3036]" : "bg-[#eaf0ff]")
11454
+ isDropTarget && (isDark ? "bg-[#22262c] outline-2 outline-primary/40 -outline-offset-2 outline-dashed" : "bg-[#f5f8ff] outline-2 outline-primary/40 -outline-offset-2 outline-dashed"),
11455
+ (isPaletteDrag && isOver || imageDrop.dragging) && (isDark ? "bg-[#2c3036]" : "bg-[#eaf0ff]")
11183
11456
  ),
11184
- children: blocks.length === 0 ? /* @__PURE__ */ jsx(EmptyState, { isDark, isPaletteDrag, isOver, onOpenPalette }) : /* @__PURE__ */ jsxs(SortableContext, { items: blocks.map((b) => b.id), strategy: verticalListSortingStrategy, children: [
11457
+ children: blocks.length === 0 ? /* @__PURE__ */ jsx(
11458
+ EmptyState,
11459
+ {
11460
+ isDark,
11461
+ isPaletteDrag,
11462
+ isOver,
11463
+ isFileDrag: imageDrop.dragging,
11464
+ onOpenPalette
11465
+ }
11466
+ ) : /* @__PURE__ */ jsxs(SortableContext, { items: blocks.map((b) => b.id), strategy: verticalListSortingStrategy, children: [
11185
11467
  blocks.map(
11186
11468
  (block, idx) => block.block.type === "container" ? /* @__PURE__ */ jsx(
11187
11469
  ContainerRow,
@@ -11211,7 +11493,7 @@ function Surface({
11211
11493
  previewTheme,
11212
11494
  errors: errorsByBlockId?.get(block.id),
11213
11495
  isOpen: openBlockId === block.id,
11214
- onOpenChange: (open) => onOpenBlockChange?.(open ? block.id : null),
11496
+ onOpenChange: (open) => onOpenBlockChange?.(block.id, open),
11215
11497
  onUpdate,
11216
11498
  onDuplicate,
11217
11499
  onDelete,
@@ -11226,7 +11508,7 @@ function Surface({
11226
11508
  block.id
11227
11509
  )
11228
11510
  ),
11229
- showEndDropZone ? /* @__PURE__ */ jsx(DropIndicator2, {}) : null
11511
+ showEndDropZone || imageDrop.dragging ? /* @__PURE__ */ jsx(DropIndicator2, {}) : null
11230
11512
  ] })
11231
11513
  }
11232
11514
  );
@@ -11235,16 +11517,28 @@ function Surface({
11235
11517
  {
11236
11518
  "aria-label": "Block preview",
11237
11519
  className: cn("flex min-w-0 flex-1 flex-col overflow-y-auto bg-muted p-3 sm:p-6"),
11238
- children: /* @__PURE__ */ jsx("div", { className: "mx-auto w-full max-w-2xl", children: previewSurface === "modal" ? /* @__PURE__ */ jsx(ModalFrame, { isDark, children: blocksList }) : previewSurface === "app_home" ? /* @__PURE__ */ jsx(AppHomeFrame, { workspaceName, isDark, children: blocksList }) : /* @__PURE__ */ jsx(
11239
- SlackMessageFrame,
11240
- {
11241
- workspaceName,
11242
- authorName,
11243
- authorIcon,
11244
- isDark,
11245
- children: blocksList
11246
- }
11247
- ) })
11520
+ ...imageDrop.handlers,
11521
+ children: /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-2xl", children: [
11522
+ imageDrop.handlers ? /* @__PURE__ */ jsx(
11523
+ ImageDropStatus,
11524
+ {
11525
+ dragging: imageDrop.dragging,
11526
+ pending: imageDrop.pending,
11527
+ error: imageDrop.error,
11528
+ onDismissError: imageDrop.dismissError
11529
+ }
11530
+ ) : null,
11531
+ previewSurface === "modal" ? /* @__PURE__ */ jsx(ModalFrame, { isDark, children: blocksList }) : previewSurface === "app_home" ? /* @__PURE__ */ jsx(AppHomeFrame, { workspaceName, isDark, children: blocksList }) : /* @__PURE__ */ jsx(
11532
+ SlackMessageFrame,
11533
+ {
11534
+ workspaceName,
11535
+ authorName,
11536
+ authorIcon,
11537
+ isDark,
11538
+ children: blocksList
11539
+ }
11540
+ )
11541
+ ] })
11248
11542
  }
11249
11543
  );
11250
11544
  }
@@ -11373,9 +11667,10 @@ function EmptyState({
11373
11667
  isDark,
11374
11668
  isPaletteDrag,
11375
11669
  isOver,
11670
+ isFileDrag: isFileDrag2 = false,
11376
11671
  onOpenPalette
11377
11672
  }) {
11378
- const active = isPaletteDrag && isOver;
11673
+ const active = isPaletteDrag && isOver || isFileDrag2;
11379
11674
  return /* @__PURE__ */ jsxs(
11380
11675
  "div",
11381
11676
  {
@@ -11402,7 +11697,7 @@ function EmptyState({
11402
11697
  "text-sm font-semibold transition-colors",
11403
11698
  active ? "text-primary" : isDark ? "text-white" : "text-[#1d1c1d]"
11404
11699
  ),
11405
- children: active ? "Drop to add block" : "Start adding blocks!"
11700
+ children: isFileDrag2 ? "Drop to add image" : active ? "Drop to add block" : "Start adding blocks!"
11406
11701
  }
11407
11702
  ),
11408
11703
  /* @__PURE__ */ jsx("p", { className: "text-xs", children: active ? "Release to insert it here." : /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -11426,6 +11721,40 @@ function EmptyState({
11426
11721
  }
11427
11722
  );
11428
11723
  }
11724
+ function ImageDropStatus({
11725
+ dragging,
11726
+ pending,
11727
+ error,
11728
+ onDismissError
11729
+ }) {
11730
+ const progress = dragging ? "Drop images to add them at the end of the message." : pending > 0 ? `Uploading ${pending === 1 ? "1 image" : `${pending} images`}\u2026` : null;
11731
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
11732
+ /* @__PURE__ */ jsx("div", { role: "status", className: "empty:hidden", children: progress ? /* @__PURE__ */ jsxs("p", { className: "mb-2 flex items-center gap-1.5 text-xs text-muted-foreground", children: [
11733
+ /* @__PURE__ */ jsx(ImagePlus, { className: cn("h-3.5 w-3.5", pending > 0 && !dragging && "animate-pulse"), "aria-hidden": "true" }),
11734
+ progress
11735
+ ] }) : null }),
11736
+ error ? /* @__PURE__ */ jsxs(
11737
+ "div",
11738
+ {
11739
+ role: "alert",
11740
+ className: "mb-2 flex items-start gap-2 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive",
11741
+ children: [
11742
+ /* @__PURE__ */ jsx("p", { className: "flex-1", children: error }),
11743
+ /* @__PURE__ */ jsx(
11744
+ "button",
11745
+ {
11746
+ type: "button",
11747
+ "aria-label": "Dismiss",
11748
+ onClick: onDismissError,
11749
+ className: "rounded p-0.5 hover:bg-destructive/10 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
11750
+ children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" })
11751
+ }
11752
+ )
11753
+ ]
11754
+ }
11755
+ ) : null
11756
+ ] });
11757
+ }
11429
11758
  function DropIndicator2() {
11430
11759
  return /* @__PURE__ */ jsx("div", { "aria-hidden": "true", className: "pointer-events-none relative my-1 h-0.5 w-full rounded-full bg-primary", children: /* @__PURE__ */ jsx("span", { className: "-left-1 -top-[3px] absolute h-2 w-2 rounded-full bg-primary shadow-[0_0_0_2px_var(--color-background)]" }) });
11431
11760
  }
@@ -11988,6 +12317,7 @@ function BlockKitchen(props) {
11988
12317
  blockEditors,
11989
12318
  customEmojis,
11990
12319
  mentions,
12320
+ imageUpload,
11991
12321
  loadChannels,
11992
12322
  loadSendAsUserStatus,
11993
12323
  onSend,
@@ -12118,6 +12448,9 @@ function BlockKitchen(props) {
12118
12448
  setPaletteAdvanced(false);
12119
12449
  }
12120
12450
  const [openBlockId, setOpenBlockId] = useState(null);
12451
+ const setBlockOpen = useCallback((id, open) => {
12452
+ setOpenBlockId((current) => open ? id : current === id ? null : current);
12453
+ }, []);
12121
12454
  const [uncontrolledPreviewTheme, setUncontrolledPreviewTheme] = useState(defaultPreviewTheme);
12122
12455
  const isPreviewThemeControlled = controlledPreviewTheme !== void 0;
12123
12456
  const previewTheme = controlledPreviewTheme ?? uncontrolledPreviewTheme;
@@ -12371,7 +12704,7 @@ function BlockKitchen(props) {
12371
12704
  }
12372
12705
  )
12373
12706
  ] }),
12374
- /* @__PURE__ */ jsx(MentionsProvider, { mentions, children: /* @__PURE__ */ jsx(BlockEditorsProvider, { blockEditors, children: /* @__PURE__ */ jsx(
12707
+ /* @__PURE__ */ jsx(MentionsProvider, { mentions, children: /* @__PURE__ */ jsx(ImageUploadProvider, { imageUpload, children: /* @__PURE__ */ jsx(BlockEditorsProvider, { blockEditors, children: /* @__PURE__ */ jsx(
12375
12708
  Surface,
12376
12709
  {
12377
12710
  blocks,
@@ -12383,7 +12716,7 @@ function BlockKitchen(props) {
12383
12716
  previewSurface,
12384
12717
  errorsByBlockId: validation.byBlockId,
12385
12718
  openBlockId,
12386
- onOpenBlockChange: setOpenBlockId,
12719
+ onOpenBlockChange: setBlockOpen,
12387
12720
  onUpdate: updateBlock,
12388
12721
  onDuplicate: duplicateBlock,
12389
12722
  onDelete: removeBlock,
@@ -12391,9 +12724,11 @@ function BlockKitchen(props) {
12391
12724
  isPaletteDrag: activePaletteVariant !== null,
12392
12725
  onOpenPalette: () => setPaletteOpen(true),
12393
12726
  blockActions,
12394
- renderBlockAdornment
12727
+ renderBlockAdornment,
12728
+ onAddBlock: addBlock,
12729
+ imageDropEnabled: !disabledBlockTypes?.includes("image")
12395
12730
  }
12396
- ) }) })
12731
+ ) }) }) })
12397
12732
  ] })
12398
12733
  ]
12399
12734
  }