@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.cjs CHANGED
@@ -14,10 +14,10 @@ var nanoid = require('nanoid');
14
14
  var reactSlot = require('@radix-ui/react-slot');
15
15
  var classVarianceAuthority = require('class-variance-authority');
16
16
  var LabelPrimitive = require('@radix-ui/react-label');
17
+ var PopoverPrimitive = require('@radix-ui/react-popover');
17
18
  require('slack-blocks-to-jsx/dist/style.css');
18
19
  var slackBlocksToJsx = require('slack-blocks-to-jsx');
19
20
  var utilities = require('@dnd-kit/utilities');
20
- var PopoverPrimitive = require('@radix-ui/react-popover');
21
21
  var RadioGroupPrimitive = require('@radix-ui/react-radio-group');
22
22
  var Link = require('@tiptap/extension-link');
23
23
  var react = require('@tiptap/react');
@@ -53,6 +53,11 @@ var Link__default = /*#__PURE__*/_interopDefault(Link);
53
53
  var StarterKit__default = /*#__PURE__*/_interopDefault(StarterKit);
54
54
 
55
55
  // src/components/block-kitchen.tsx
56
+
57
+ // src/lib/channel-label.ts
58
+ function channelLabel(name) {
59
+ return `# ${name}`;
60
+ }
56
61
  function cn(...inputs) {
57
62
  return tailwindMerge.twMerge(clsx.clsx(inputs));
58
63
  }
@@ -3065,6 +3070,220 @@ var Label = React__namespace.forwardRef(({ className, ...props }, ref) => /* @__
3065
3070
  }
3066
3071
  ));
3067
3072
  Label.displayName = LabelPrimitive__namespace.Root.displayName;
3073
+ var Popover = PopoverPrimitive__namespace.Root;
3074
+ var PopoverTrigger = PopoverPrimitive__namespace.Trigger;
3075
+ var PopoverAnchor = PopoverPrimitive__namespace.Anchor;
3076
+ var PopoverContent = React__namespace.forwardRef(({ className, align = "start", sideOffset = 8, collisionPadding = 16, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(PopoverPrimitive__namespace.Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(
3077
+ PopoverPrimitive__namespace.Content,
3078
+ {
3079
+ ref,
3080
+ align,
3081
+ sideOffset,
3082
+ collisionPadding,
3083
+ className: cn(
3084
+ "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",
3085
+ "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
3086
+ className
3087
+ ),
3088
+ ...props
3089
+ }
3090
+ ) }));
3091
+ PopoverContent.displayName = PopoverPrimitive__namespace.Content.displayName;
3092
+ function matchesQuery(name, query) {
3093
+ const needle = query.replace(/^\s*#\s*/, "").trim().toLowerCase();
3094
+ return needle === "" || name.toLowerCase().includes(needle);
3095
+ }
3096
+ function ChannelCombobox({
3097
+ id,
3098
+ channels,
3099
+ value,
3100
+ onChange,
3101
+ placeholder = "Search channels\u2026",
3102
+ className
3103
+ }) {
3104
+ const [open, setOpen] = React.useState(false);
3105
+ const [query, setQuery] = React.useState("");
3106
+ const [activeIndex, setActiveIndex] = React.useState(-1);
3107
+ const inputRef = React.useRef(null);
3108
+ const listRef = React.useRef(null);
3109
+ const domId = React.useId();
3110
+ const listboxId = `${domId}-listbox`;
3111
+ const optionDomId = (index) => `${domId}-option-${index}`;
3112
+ const selected = channels.find((c) => c.id === value) ?? null;
3113
+ const filtered = React.useMemo(
3114
+ () => open ? channels.filter((c) => matchesQuery(c.name, query)) : channels,
3115
+ [channels, open, query]
3116
+ );
3117
+ const inputValue = open ? query : selected ? channelLabel(selected.name) : "";
3118
+ React.useEffect(() => {
3119
+ if (!open) {
3120
+ setActiveIndex(-1);
3121
+ return;
3122
+ }
3123
+ setActiveIndex((prev) => {
3124
+ if (prev >= 0 && prev < filtered.length) {
3125
+ return prev;
3126
+ }
3127
+ const selectedIndex = filtered.findIndex((c) => c.id === value);
3128
+ return selectedIndex >= 0 ? selectedIndex : filtered.length > 0 ? 0 : -1;
3129
+ });
3130
+ }, [open, filtered, value]);
3131
+ React.useEffect(() => {
3132
+ if (!open || activeIndex < 0) {
3133
+ return;
3134
+ }
3135
+ const row = listRef.current?.querySelectorAll('[role="option"]')[activeIndex];
3136
+ row?.scrollIntoView?.({ block: "nearest" });
3137
+ }, [open, activeIndex]);
3138
+ const openList = () => {
3139
+ setQuery("");
3140
+ setOpen(true);
3141
+ };
3142
+ const closeList = () => {
3143
+ setOpen(false);
3144
+ setQuery("");
3145
+ };
3146
+ const commit = (channel) => {
3147
+ onChange(channel.id);
3148
+ closeList();
3149
+ inputRef.current?.focus();
3150
+ };
3151
+ const moveActive = (delta) => {
3152
+ if (!open) {
3153
+ openList();
3154
+ return;
3155
+ }
3156
+ if (filtered.length === 0) {
3157
+ return;
3158
+ }
3159
+ setActiveIndex((prev) => {
3160
+ const next = prev + delta;
3161
+ return next < 0 ? filtered.length - 1 : next >= filtered.length ? 0 : next;
3162
+ });
3163
+ };
3164
+ const handleKeyDown = (e) => {
3165
+ switch (e.key) {
3166
+ case "ArrowDown":
3167
+ e.preventDefault();
3168
+ moveActive(1);
3169
+ break;
3170
+ case "ArrowUp":
3171
+ e.preventDefault();
3172
+ moveActive(-1);
3173
+ break;
3174
+ case "Home":
3175
+ if (open && filtered.length > 0) {
3176
+ e.preventDefault();
3177
+ setActiveIndex(0);
3178
+ }
3179
+ break;
3180
+ case "End":
3181
+ if (open && filtered.length > 0) {
3182
+ e.preventDefault();
3183
+ setActiveIndex(filtered.length - 1);
3184
+ }
3185
+ break;
3186
+ case "Enter": {
3187
+ if (!open) {
3188
+ break;
3189
+ }
3190
+ e.preventDefault();
3191
+ const active = filtered[activeIndex];
3192
+ if (active) {
3193
+ commit(active);
3194
+ }
3195
+ break;
3196
+ }
3197
+ case "Escape":
3198
+ if (open) {
3199
+ e.stopPropagation();
3200
+ closeList();
3201
+ }
3202
+ break;
3203
+ case "Tab":
3204
+ if (open) {
3205
+ closeList();
3206
+ }
3207
+ break;
3208
+ }
3209
+ };
3210
+ return /* @__PURE__ */ jsxRuntime.jsxs(Popover, { open, onOpenChange: (next) => next ? openList() : closeList(), children: [
3211
+ /* @__PURE__ */ jsxRuntime.jsx(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
3212
+ /* @__PURE__ */ jsxRuntime.jsx(
3213
+ Input,
3214
+ {
3215
+ ref: inputRef,
3216
+ id,
3217
+ role: "combobox",
3218
+ "aria-expanded": open,
3219
+ "aria-controls": listboxId,
3220
+ "aria-autocomplete": "list",
3221
+ "aria-activedescendant": open && activeIndex >= 0 ? optionDomId(activeIndex) : void 0,
3222
+ autoComplete: "off",
3223
+ spellCheck: false,
3224
+ value: inputValue,
3225
+ placeholder,
3226
+ onChange: (e) => {
3227
+ setQuery(e.target.value);
3228
+ setOpen(true);
3229
+ },
3230
+ onKeyDown: handleKeyDown,
3231
+ onClick: () => open ? closeList() : openList(),
3232
+ className: cn("pr-9", className)
3233
+ }
3234
+ ),
3235
+ /* @__PURE__ */ jsxRuntime.jsx(
3236
+ lucideReact.ChevronsUpDown,
3237
+ {
3238
+ "aria-hidden": true,
3239
+ className: "pointer-events-none absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground"
3240
+ }
3241
+ )
3242
+ ] }) }),
3243
+ /* @__PURE__ */ jsxRuntime.jsx(
3244
+ PopoverContent,
3245
+ {
3246
+ ref: listRef,
3247
+ onOpenAutoFocus: (e) => e.preventDefault(),
3248
+ onCloseAutoFocus: (e) => e.preventDefault(),
3249
+ onInteractOutside: (e) => {
3250
+ if (e.target instanceof Node && inputRef.current?.contains(e.target)) {
3251
+ e.preventDefault();
3252
+ }
3253
+ },
3254
+ sideOffset: 4,
3255
+ className: "max-h-[min(15rem,var(--radix-popover-content-available-height))] w-[var(--radix-popover-trigger-width)] min-w-48 p-1",
3256
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { role: "listbox", id: listboxId, "aria-label": "Channels", children: filtered.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "px-2 py-3 text-center text-xs text-muted-foreground", children: "No channels match that search." }) : filtered.map((channel, index) => {
3257
+ const isSelected = channel.id === value;
3258
+ return (
3259
+ // 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.
3260
+ // biome-ignore lint/a11y/useKeyWithClickEvents: the keyboard path lives on that same input, which drives this list via aria-activedescendant.
3261
+ /* @__PURE__ */ jsxRuntime.jsxs(
3262
+ "div",
3263
+ {
3264
+ id: optionDomId(index),
3265
+ role: "option",
3266
+ "aria-selected": isSelected,
3267
+ onPointerDown: (e) => e.preventDefault(),
3268
+ onClick: () => commit(channel),
3269
+ onMouseMove: () => setActiveIndex(index),
3270
+ className: cn(
3271
+ "flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-sm text-foreground",
3272
+ index === activeIndex && "bg-accent"
3273
+ ),
3274
+ children: [
3275
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: cn("h-3.5 w-3.5 shrink-0", isSelected ? "opacity-100" : "opacity-0") }),
3276
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: channelLabel(channel.name) })
3277
+ ]
3278
+ },
3279
+ channel.id
3280
+ )
3281
+ );
3282
+ }) })
3283
+ }
3284
+ )
3285
+ ] });
3286
+ }
3068
3287
  function SlackBlockPreview({
3069
3288
  block,
3070
3289
  hooks,
@@ -3544,20 +3763,14 @@ function LoadMessageDialog({
3544
3763
  channels === null && !channelsError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "Loading channels\u2026" }),
3545
3764
  channelsError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-destructive", children: channelsError }),
3546
3765
  channels && channels.length === 0 && !channelsError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "No public channels available." }),
3547
- channels && channels.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
3548
- "select",
3766
+ channels && channels.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
3767
+ ChannelCombobox,
3549
3768
  {
3550
3769
  id: "recent-channel-picker",
3770
+ channels,
3551
3771
  value: channelId,
3552
- onChange: (e) => setChannelId(e.target.value),
3553
- 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",
3554
- children: [
3555
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", disabled: true, children: "Select a channel\u2026" }),
3556
- channels.map((c) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: c.id, children: [
3557
- "#",
3558
- c.name
3559
- ] }, c.id))
3560
- ]
3772
+ onChange: setChannelId,
3773
+ placeholder: "Select a channel\u2026"
3561
3774
  }
3562
3775
  )
3563
3776
  ] }),
@@ -3751,7 +3964,7 @@ function PreviewPane({
3751
3964
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 shrink-0 flex-col gap-2 lg:min-h-0 lg:flex-1", children: [
3752
3965
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 flex-wrap items-center gap-2", children: [
3753
3966
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: "Preview" }),
3754
- target?.channelName || target?.channelId ? /* @__PURE__ */ jsxRuntime.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,
3967
+ target?.channelName || target?.channelId ? /* @__PURE__ */ jsxRuntime.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,
3755
3968
  target?.editableVia ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "rounded border px-1.5 py-px text-[10px] font-medium uppercase tracking-wide text-muted-foreground", children: [
3756
3969
  "Editable as ",
3757
3970
  editableLabel(target.editableVia)
@@ -4207,19 +4420,7 @@ function SendDialog({
4207
4420
  channels === null && !channelsError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "Loading channels\u2026" }),
4208
4421
  channelsError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-destructive", children: channelsError }),
4209
4422
  channels && channels.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "No public channels available." }),
4210
- channels && channels.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
4211
- "select",
4212
- {
4213
- id: "channel-picker",
4214
- value: channelId,
4215
- onChange: (e) => setChannelId(e.target.value),
4216
- 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",
4217
- children: channels.map((c) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: c.id, children: [
4218
- "#",
4219
- c.name
4220
- ] }, c.id))
4221
- }
4222
- )
4423
+ channels && channels.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(ChannelCombobox, { id: "channel-picker", channels, value: channelId, onChange: setChannelId })
4223
4424
  ] }),
4224
4425
  renderSendExtras ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-1.5", children: renderSendExtras({
4225
4426
  channelId: channelId || null,
@@ -4262,24 +4463,6 @@ function SendDialog({
4262
4463
  ] })
4263
4464
  ] }) });
4264
4465
  }
4265
- var Popover = PopoverPrimitive__namespace.Root;
4266
- var PopoverTrigger = PopoverPrimitive__namespace.Trigger;
4267
- var PopoverContent = React__namespace.forwardRef(({ className, align = "start", sideOffset = 8, collisionPadding = 16, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(PopoverPrimitive__namespace.Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(
4268
- PopoverPrimitive__namespace.Content,
4269
- {
4270
- ref,
4271
- align,
4272
- sideOffset,
4273
- collisionPadding,
4274
- className: cn(
4275
- "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",
4276
- "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
4277
- className
4278
- ),
4279
- ...props
4280
- }
4281
- ) }));
4282
- PopoverContent.displayName = PopoverPrimitive__namespace.Content.displayName;
4283
4466
  var RadioGroup = React__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(RadioGroupPrimitive__namespace.Root, { className: cn("grid gap-2", className), ...props, ref }));
4284
4467
  RadioGroup.displayName = RadioGroupPrimitive__namespace.Root.displayName;
4285
4468
  var RadioGroupItem = React__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
@@ -9314,11 +9497,13 @@ function BlockRow({
9314
9497
  {
9315
9498
  className: cn(
9316
9499
  // Hidden on mobile in favor of the inline row below; on desktop
9317
- // reveal on hover, on focus inside the row (so Tab-stops on the
9318
- // floating buttons themselves keep them visible), and when there
9500
+ // reveal on hover, on keyboard focus inside the row (so Tab-stops on
9501
+ // the floating buttons themselves keep them visible), and when there
9319
9502
  // are validation errors worth surfacing immediately.
9503
+ // :focus-visible, not :focus-within — a drag leaves pointer focus on
9504
+ // the block, which would otherwise pin the toolbar open until a click.
9320
9505
  "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",
9321
- hasErrors ? "opacity-100" : "opacity-0 group-hover:opacity-100 group-focus-within:opacity-100"
9506
+ hasErrors ? "opacity-100" : "opacity-0 group-hover:opacity-100 group-has-[:focus-visible]:opacity-100"
9322
9507
  ),
9323
9508
  children: [
9324
9509
  hasErrors ? /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
@@ -9628,7 +9813,7 @@ function ContainerRow({
9628
9813
  {
9629
9814
  className: cn(
9630
9815
  "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",
9631
- hasErrors ? "opacity-100" : "opacity-0 group-hover/container:opacity-100 group-focus-within/container:opacity-100"
9816
+ hasErrors ? "opacity-100" : "opacity-0 group-hover/container:opacity-100 group-has-[:focus-visible]/container:opacity-100"
9632
9817
  ),
9633
9818
  children: [
9634
9819
  hasErrors ? /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
@@ -10478,7 +10663,7 @@ function UpdateDialog({
10478
10663
  setStatus({ kind: "error", error: e instanceof Error ? e.message : "Update failed." });
10479
10664
  }
10480
10665
  };
10481
- const channelLabel = target.channelName ? `#${target.channelName}` : target.channelId;
10666
+ const channelText = target.channelName ? channelLabel(target.channelName) : target.channelId;
10482
10667
  return /* @__PURE__ */ jsxRuntime.jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(DialogContent, { className: "w-[calc(100vw-1.5rem)] max-w-lg rounded-lg", children: [
10483
10668
  /* @__PURE__ */ jsxRuntime.jsxs(DialogHeader, { children: [
10484
10669
  /* @__PURE__ */ jsxRuntime.jsx(DialogTitle, { children: "Update message" }),
@@ -10487,7 +10672,7 @@ function UpdateDialog({
10487
10672
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4", children: [
10488
10673
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1.5", children: [
10489
10674
  /* @__PURE__ */ jsxRuntime.jsx(Label, { children: "Channel" }),
10490
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "flex h-9 items-center rounded-md border border-input bg-muted px-3 text-sm text-muted-foreground", children: channelLabel })
10675
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "flex h-9 items-center rounded-md border border-input bg-muted px-3 text-sm text-muted-foreground", children: channelText })
10491
10676
  ] }),
10492
10677
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1.5", children: [
10493
10678
  /* @__PURE__ */ jsxRuntime.jsx(Label, { children: "Posting as" }),
@@ -10853,7 +11038,7 @@ function BlockKitchen(props) {
10853
11038
  loadEnabled,
10854
11039
  updateEnabled: Boolean(onUpdate),
10855
11040
  editBadge: activeEditTarget ? {
10856
- channelLabel: activeEditTarget.channelName ? `#${activeEditTarget.channelName}` : activeEditTarget.channelId,
11041
+ channelLabel: activeEditTarget.channelName ? channelLabel(activeEditTarget.channelName) : activeEditTarget.channelId,
10857
11042
  ts: activeEditTarget.ts
10858
11043
  } : null,
10859
11044
  onOpenLoad: () => setLoadOpen(true),