@tightknitai/block-kitchen 0.10.9 → 0.10.11

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, Sun, Moon, MessageSquare, AppWindow, Home, Plus, ChevronDown, BookOpen, Undo2, Redo2, Trash2, Code2, Pencil, Send, Search, Check, Copy, Info, CheckCircle2, LayoutGrid, ArrowUp, ArrowDown, Type, Bold, Italic, Strikethrough, Underline, Code, 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, Type, Bold, Italic, Strikethrough, Underline, Code, List, ListOrdered, Quote, Smile, Link as Link$1 } from 'lucide-react';
4
4
  import * as React from 'react';
5
5
  import { createContext, useState, useRef, useEffect, useCallback, useMemo, useId, useContext } from 'react';
6
6
  import { clsx } from 'clsx';
@@ -13,16 +13,21 @@ import { nanoid } from 'nanoid';
13
13
  import { Slot } from '@radix-ui/react-slot';
14
14
  import { cva } from 'class-variance-authority';
15
15
  import * as LabelPrimitive from '@radix-ui/react-label';
16
+ import * as PopoverPrimitive from '@radix-ui/react-popover';
16
17
  import 'slack-blocks-to-jsx/dist/style.css';
17
18
  import { Message } from 'slack-blocks-to-jsx';
18
19
  import { CSS } from '@dnd-kit/utilities';
19
- import * as PopoverPrimitive from '@radix-ui/react-popover';
20
20
  import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
21
21
  import Link from '@tiptap/extension-link';
22
- import { Node, ReactNodeViewRenderer, mergeAttributes, NodeViewWrapper, useEditor, EditorContent } from '@tiptap/react';
22
+ import { Node as Node$1, ReactNodeViewRenderer, mergeAttributes, NodeViewWrapper, useEditor, EditorContent } from '@tiptap/react';
23
23
  import StarterKit from '@tiptap/starter-kit';
24
24
 
25
25
  // src/components/block-kitchen.tsx
26
+
27
+ // src/lib/channel-label.ts
28
+ function channelLabel(name) {
29
+ return `# ${name}`;
30
+ }
26
31
  function cn(...inputs) {
27
32
  return twMerge(clsx(inputs));
28
33
  }
@@ -3035,6 +3040,220 @@ var Label = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ j
3035
3040
  }
3036
3041
  ));
3037
3042
  Label.displayName = LabelPrimitive.Root.displayName;
3043
+ var Popover = PopoverPrimitive.Root;
3044
+ var PopoverTrigger = PopoverPrimitive.Trigger;
3045
+ var PopoverAnchor = PopoverPrimitive.Anchor;
3046
+ var PopoverContent = React.forwardRef(({ className, align = "start", sideOffset = 8, collisionPadding = 16, ...props }, ref) => /* @__PURE__ */ jsx(PopoverPrimitive.Portal, { children: /* @__PURE__ */ jsx(
3047
+ PopoverPrimitive.Content,
3048
+ {
3049
+ ref,
3050
+ align,
3051
+ sideOffset,
3052
+ collisionPadding,
3053
+ className: cn(
3054
+ "bk-portal-content z-50 max-h-[var(--radix-popover-content-available-height)] w-80 overflow-y-auto rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none",
3055
+ "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
3056
+ className
3057
+ ),
3058
+ ...props
3059
+ }
3060
+ ) }));
3061
+ PopoverContent.displayName = PopoverPrimitive.Content.displayName;
3062
+ function matchesQuery(name, query) {
3063
+ const needle = query.replace(/^\s*#\s*/, "").trim().toLowerCase();
3064
+ return needle === "" || name.toLowerCase().includes(needle);
3065
+ }
3066
+ function ChannelCombobox({
3067
+ id,
3068
+ channels,
3069
+ value,
3070
+ onChange,
3071
+ placeholder = "Search channels\u2026",
3072
+ className
3073
+ }) {
3074
+ const [open, setOpen] = useState(false);
3075
+ const [query, setQuery] = useState("");
3076
+ const [activeIndex, setActiveIndex] = useState(-1);
3077
+ const inputRef = useRef(null);
3078
+ const listRef = useRef(null);
3079
+ const domId = useId();
3080
+ const listboxId = `${domId}-listbox`;
3081
+ const optionDomId = (index) => `${domId}-option-${index}`;
3082
+ const selected = channels.find((c) => c.id === value) ?? null;
3083
+ const filtered = useMemo(
3084
+ () => open ? channels.filter((c) => matchesQuery(c.name, query)) : channels,
3085
+ [channels, open, query]
3086
+ );
3087
+ const inputValue = open ? query : selected ? channelLabel(selected.name) : "";
3088
+ useEffect(() => {
3089
+ if (!open) {
3090
+ setActiveIndex(-1);
3091
+ return;
3092
+ }
3093
+ setActiveIndex((prev) => {
3094
+ if (prev >= 0 && prev < filtered.length) {
3095
+ return prev;
3096
+ }
3097
+ const selectedIndex = filtered.findIndex((c) => c.id === value);
3098
+ return selectedIndex >= 0 ? selectedIndex : filtered.length > 0 ? 0 : -1;
3099
+ });
3100
+ }, [open, filtered, value]);
3101
+ useEffect(() => {
3102
+ if (!open || activeIndex < 0) {
3103
+ return;
3104
+ }
3105
+ const row = listRef.current?.querySelectorAll('[role="option"]')[activeIndex];
3106
+ row?.scrollIntoView?.({ block: "nearest" });
3107
+ }, [open, activeIndex]);
3108
+ const openList = () => {
3109
+ setQuery("");
3110
+ setOpen(true);
3111
+ };
3112
+ const closeList = () => {
3113
+ setOpen(false);
3114
+ setQuery("");
3115
+ };
3116
+ const commit = (channel) => {
3117
+ onChange(channel.id);
3118
+ closeList();
3119
+ inputRef.current?.focus();
3120
+ };
3121
+ const moveActive = (delta) => {
3122
+ if (!open) {
3123
+ openList();
3124
+ return;
3125
+ }
3126
+ if (filtered.length === 0) {
3127
+ return;
3128
+ }
3129
+ setActiveIndex((prev) => {
3130
+ const next = prev + delta;
3131
+ return next < 0 ? filtered.length - 1 : next >= filtered.length ? 0 : next;
3132
+ });
3133
+ };
3134
+ const handleKeyDown = (e) => {
3135
+ switch (e.key) {
3136
+ case "ArrowDown":
3137
+ e.preventDefault();
3138
+ moveActive(1);
3139
+ break;
3140
+ case "ArrowUp":
3141
+ e.preventDefault();
3142
+ moveActive(-1);
3143
+ break;
3144
+ case "Home":
3145
+ if (open && filtered.length > 0) {
3146
+ e.preventDefault();
3147
+ setActiveIndex(0);
3148
+ }
3149
+ break;
3150
+ case "End":
3151
+ if (open && filtered.length > 0) {
3152
+ e.preventDefault();
3153
+ setActiveIndex(filtered.length - 1);
3154
+ }
3155
+ break;
3156
+ case "Enter": {
3157
+ if (!open) {
3158
+ break;
3159
+ }
3160
+ e.preventDefault();
3161
+ const active = filtered[activeIndex];
3162
+ if (active) {
3163
+ commit(active);
3164
+ }
3165
+ break;
3166
+ }
3167
+ case "Escape":
3168
+ if (open) {
3169
+ e.stopPropagation();
3170
+ closeList();
3171
+ }
3172
+ break;
3173
+ case "Tab":
3174
+ if (open) {
3175
+ closeList();
3176
+ }
3177
+ break;
3178
+ }
3179
+ };
3180
+ return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: (next) => next ? openList() : closeList(), children: [
3181
+ /* @__PURE__ */ jsx(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
3182
+ /* @__PURE__ */ jsx(
3183
+ Input,
3184
+ {
3185
+ ref: inputRef,
3186
+ id,
3187
+ role: "combobox",
3188
+ "aria-expanded": open,
3189
+ "aria-controls": listboxId,
3190
+ "aria-autocomplete": "list",
3191
+ "aria-activedescendant": open && activeIndex >= 0 ? optionDomId(activeIndex) : void 0,
3192
+ autoComplete: "off",
3193
+ spellCheck: false,
3194
+ value: inputValue,
3195
+ placeholder,
3196
+ onChange: (e) => {
3197
+ setQuery(e.target.value);
3198
+ setOpen(true);
3199
+ },
3200
+ onKeyDown: handleKeyDown,
3201
+ onClick: () => open ? closeList() : openList(),
3202
+ className: cn("pr-9", className)
3203
+ }
3204
+ ),
3205
+ /* @__PURE__ */ jsx(
3206
+ ChevronsUpDown,
3207
+ {
3208
+ "aria-hidden": true,
3209
+ className: "pointer-events-none absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground"
3210
+ }
3211
+ )
3212
+ ] }) }),
3213
+ /* @__PURE__ */ jsx(
3214
+ PopoverContent,
3215
+ {
3216
+ ref: listRef,
3217
+ onOpenAutoFocus: (e) => e.preventDefault(),
3218
+ onCloseAutoFocus: (e) => e.preventDefault(),
3219
+ onInteractOutside: (e) => {
3220
+ if (e.target instanceof Node && inputRef.current?.contains(e.target)) {
3221
+ e.preventDefault();
3222
+ }
3223
+ },
3224
+ sideOffset: 4,
3225
+ className: "max-h-[min(15rem,var(--radix-popover-content-available-height))] w-[var(--radix-popover-trigger-width)] min-w-48 p-1",
3226
+ children: /* @__PURE__ */ jsx("div", { role: "listbox", id: listboxId, "aria-label": "Channels", children: filtered.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-2 py-3 text-center text-xs text-muted-foreground", children: "No channels match that search." }) : filtered.map((channel, index) => {
3227
+ const isSelected = channel.id === value;
3228
+ return (
3229
+ // biome-ignore lint/a11y/useFocusableInteractive: options in an aria-activedescendant combobox must stay unfocusable — focus belongs to the input, which is what makes typing filter the list.
3230
+ // biome-ignore lint/a11y/useKeyWithClickEvents: the keyboard path lives on that same input, which drives this list via aria-activedescendant.
3231
+ /* @__PURE__ */ jsxs(
3232
+ "div",
3233
+ {
3234
+ id: optionDomId(index),
3235
+ role: "option",
3236
+ "aria-selected": isSelected,
3237
+ onPointerDown: (e) => e.preventDefault(),
3238
+ onClick: () => commit(channel),
3239
+ onMouseMove: () => setActiveIndex(index),
3240
+ className: cn(
3241
+ "flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-sm text-foreground",
3242
+ index === activeIndex && "bg-accent"
3243
+ ),
3244
+ children: [
3245
+ /* @__PURE__ */ jsx(Check, { className: cn("h-3.5 w-3.5 shrink-0", isSelected ? "opacity-100" : "opacity-0") }),
3246
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: channelLabel(channel.name) })
3247
+ ]
3248
+ },
3249
+ channel.id
3250
+ )
3251
+ );
3252
+ }) })
3253
+ }
3254
+ )
3255
+ ] });
3256
+ }
3038
3257
  function SlackBlockPreview({
3039
3258
  block,
3040
3259
  hooks,
@@ -3514,20 +3733,14 @@ function LoadMessageDialog({
3514
3733
  channels === null && !channelsError && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Loading channels\u2026" }),
3515
3734
  channelsError && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: channelsError }),
3516
3735
  channels && channels.length === 0 && !channelsError && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "No public channels available." }),
3517
- channels && channels.length > 0 && /* @__PURE__ */ jsxs(
3518
- "select",
3736
+ channels && channels.length > 0 && /* @__PURE__ */ jsx(
3737
+ ChannelCombobox,
3519
3738
  {
3520
3739
  id: "recent-channel-picker",
3740
+ channels,
3521
3741
  value: channelId,
3522
- onChange: (e) => setChannelId(e.target.value),
3523
- className: "flex h-9 w-full rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
3524
- children: [
3525
- /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: "Select a channel\u2026" }),
3526
- channels.map((c) => /* @__PURE__ */ jsxs("option", { value: c.id, children: [
3527
- "#",
3528
- c.name
3529
- ] }, c.id))
3530
- ]
3742
+ onChange: setChannelId,
3743
+ placeholder: "Select a channel\u2026"
3531
3744
  }
3532
3745
  )
3533
3746
  ] }),
@@ -3721,7 +3934,7 @@ function PreviewPane({
3721
3934
  return /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 shrink-0 flex-col gap-2 lg:min-h-0 lg:flex-1", children: [
3722
3935
  /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 flex-wrap items-center gap-2", children: [
3723
3936
  /* @__PURE__ */ jsx("span", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: "Preview" }),
3724
- target?.channelName || target?.channelId ? /* @__PURE__ */ jsx("span", { className: "rounded border px-1.5 py-px text-[10px] font-medium text-muted-foreground", children: target.channelName ? `#${target.channelName}` : target.channelId }) : null,
3937
+ target?.channelName || target?.channelId ? /* @__PURE__ */ jsx("span", { className: "rounded border px-1.5 py-px text-[10px] font-medium text-muted-foreground", children: target.channelName ? channelLabel(target.channelName) : target.channelId }) : null,
3725
3938
  target?.editableVia ? /* @__PURE__ */ jsxs("span", { className: "rounded border px-1.5 py-px text-[10px] font-medium uppercase tracking-wide text-muted-foreground", children: [
3726
3939
  "Editable as ",
3727
3940
  editableLabel(target.editableVia)
@@ -4177,19 +4390,7 @@ function SendDialog({
4177
4390
  channels === null && !channelsError && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Loading channels\u2026" }),
4178
4391
  channelsError && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: channelsError }),
4179
4392
  channels && channels.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "No public channels available." }),
4180
- channels && channels.length > 0 && /* @__PURE__ */ jsx(
4181
- "select",
4182
- {
4183
- id: "channel-picker",
4184
- value: channelId,
4185
- onChange: (e) => setChannelId(e.target.value),
4186
- className: "flex h-9 w-full rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
4187
- children: channels.map((c) => /* @__PURE__ */ jsxs("option", { value: c.id, children: [
4188
- "#",
4189
- c.name
4190
- ] }, c.id))
4191
- }
4192
- )
4393
+ channels && channels.length > 0 && /* @__PURE__ */ jsx(ChannelCombobox, { id: "channel-picker", channels, value: channelId, onChange: setChannelId })
4193
4394
  ] }),
4194
4395
  renderSendExtras ? /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-1.5", children: renderSendExtras({
4195
4396
  channelId: channelId || null,
@@ -4232,24 +4433,6 @@ function SendDialog({
4232
4433
  ] })
4233
4434
  ] }) });
4234
4435
  }
4235
- var Popover = PopoverPrimitive.Root;
4236
- var PopoverTrigger = PopoverPrimitive.Trigger;
4237
- var PopoverContent = React.forwardRef(({ className, align = "start", sideOffset = 8, collisionPadding = 16, ...props }, ref) => /* @__PURE__ */ jsx(PopoverPrimitive.Portal, { children: /* @__PURE__ */ jsx(
4238
- PopoverPrimitive.Content,
4239
- {
4240
- ref,
4241
- align,
4242
- sideOffset,
4243
- collisionPadding,
4244
- className: cn(
4245
- "bk-portal-content z-50 max-h-[var(--radix-popover-content-available-height)] w-80 overflow-y-auto rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none",
4246
- "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
4247
- className
4248
- ),
4249
- ...props
4250
- }
4251
- ) }));
4252
- PopoverContent.displayName = PopoverPrimitive.Content.displayName;
4253
4436
  var RadioGroup = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(RadioGroupPrimitive.Root, { className: cn("grid gap-2", className), ...props, ref }));
4254
4437
  RadioGroup.displayName = RadioGroupPrimitive.Root.displayName;
4255
4438
  var RadioGroupItem = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
@@ -7515,7 +7698,7 @@ function EmojiNodeView({ node }) {
7515
7698
  }
7516
7699
  ) : /* @__PURE__ */ jsx("span", { "aria-label": label, role: "img", children: unicode ? codepointsToGlyph(unicode) : label }) });
7517
7700
  }
7518
- var EmojiNode = Node.create({
7701
+ var EmojiNode = Node$1.create({
7519
7702
  name: "emoji",
7520
7703
  group: "inline",
7521
7704
  inline: true,
@@ -9284,11 +9467,13 @@ function BlockRow({
9284
9467
  {
9285
9468
  className: cn(
9286
9469
  // Hidden on mobile in favor of the inline row below; on desktop
9287
- // reveal on hover, on focus inside the row (so Tab-stops on the
9288
- // floating buttons themselves keep them visible), and when there
9470
+ // reveal on hover, on keyboard focus inside the row (so Tab-stops on
9471
+ // the floating buttons themselves keep them visible), and when there
9289
9472
  // are validation errors worth surfacing immediately.
9473
+ // :focus-visible, not :focus-within — a drag leaves pointer focus on
9474
+ // the block, which would otherwise pin the toolbar open until a click.
9290
9475
  "absolute -top-3 right-2 z-10 hidden items-center gap-0.5 rounded-md border bg-background p-0.5 shadow-sm transition-opacity md:flex",
9291
- hasErrors ? "opacity-100" : "opacity-0 group-hover:opacity-100 group-focus-within:opacity-100"
9476
+ hasErrors ? "opacity-100" : "opacity-0 group-hover:opacity-100 group-has-[:focus-visible]:opacity-100"
9292
9477
  ),
9293
9478
  children: [
9294
9479
  hasErrors ? /* @__PURE__ */ jsxs(Tooltip, { children: [
@@ -9598,7 +9783,7 @@ function ContainerRow({
9598
9783
  {
9599
9784
  className: cn(
9600
9785
  "absolute -top-3 right-2 z-10 hidden items-center gap-0.5 rounded-md border bg-background p-0.5 shadow-sm transition-opacity md:flex",
9601
- hasErrors ? "opacity-100" : "opacity-0 group-hover/container:opacity-100 group-focus-within/container:opacity-100"
9786
+ hasErrors ? "opacity-100" : "opacity-0 group-hover/container:opacity-100 group-has-[:focus-visible]/container:opacity-100"
9602
9787
  ),
9603
9788
  children: [
9604
9789
  hasErrors ? /* @__PURE__ */ jsxs(Tooltip, { children: [
@@ -10448,7 +10633,7 @@ function UpdateDialog({
10448
10633
  setStatus({ kind: "error", error: e instanceof Error ? e.message : "Update failed." });
10449
10634
  }
10450
10635
  };
10451
- const channelLabel = target.channelName ? `#${target.channelName}` : target.channelId;
10636
+ const channelText = target.channelName ? channelLabel(target.channelName) : target.channelId;
10452
10637
  return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(DialogContent, { className: "w-[calc(100vw-1.5rem)] max-w-lg rounded-lg", children: [
10453
10638
  /* @__PURE__ */ jsxs(DialogHeader, { children: [
10454
10639
  /* @__PURE__ */ jsx(DialogTitle, { children: "Update message" }),
@@ -10457,7 +10642,7 @@ function UpdateDialog({
10457
10642
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
10458
10643
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
10459
10644
  /* @__PURE__ */ jsx(Label, { children: "Channel" }),
10460
- /* @__PURE__ */ jsx("p", { className: "flex h-9 items-center rounded-md border border-input bg-muted px-3 text-sm text-muted-foreground", children: channelLabel })
10645
+ /* @__PURE__ */ jsx("p", { className: "flex h-9 items-center rounded-md border border-input bg-muted px-3 text-sm text-muted-foreground", children: channelText })
10461
10646
  ] }),
10462
10647
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
10463
10648
  /* @__PURE__ */ jsx(Label, { children: "Posting as" }),
@@ -10823,7 +11008,7 @@ function BlockKitchen(props) {
10823
11008
  loadEnabled,
10824
11009
  updateEnabled: Boolean(onUpdate),
10825
11010
  editBadge: activeEditTarget ? {
10826
- channelLabel: activeEditTarget.channelName ? `#${activeEditTarget.channelName}` : activeEditTarget.channelId,
11011
+ channelLabel: activeEditTarget.channelName ? channelLabel(activeEditTarget.channelName) : activeEditTarget.channelId,
10827
11012
  ts: activeEditTarget.ts
10828
11013
  } : null,
10829
11014
  onOpenLoad: () => setLoadOpen(true),