@tightknitai/block-kitchen 0.10.7 → 0.10.9

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 CHANGED
@@ -141,7 +141,7 @@ Given no height to work with — a plain `<div>` in ordinary document flow — i
141
141
  | `customEmojis` | `CustomEmoji[]` | no | Workspace custom emoji (`{ name, url, alias }`) the preview resolves. Entries with a `url` render `:name:` as the workspace image; alias entries (`url: null`) fall back to their target emoji. Render-only — never serialized into the emitted Block Kit JSON. A caller-supplied `previewHooks.emoji` takes precedence. |
142
142
  | `palette` | `PaletteSection[]` | no | The left-hand palette of draggable variants. Defaults to `defaultPalette`. Spread it to filter, reorder, or add your own pre-configured variants — see [Customizing the palette](#customizing-the-palette). |
143
143
  | `disabledBlockTypes` | `SupportedBlockType[]` | no | Block types to hide from the palette without rebuilding it. Filters at the variant level — a section keeps any variants whose block types aren't disabled; sections that end up empty are dropped. Convenient when you want the default palette minus a few types (e.g. `['image', 'table']` for a text-only builder). |
144
- | `paletteMode` | `'advanced' \| 'simple'` | no | How much of the palette the user meets first. `'advanced'` (default) renders the full sectioned palette with its search input. `'simple'` opens on a flat list of the variants flagged `basic` — in the built-in palette, just **Rich Text Section** — with no search and an "Advanced" link at the top that swaps in the full palette. Nothing is removed; it's a smaller first screen. See [Simple vs advanced palette](#simple-vs-advanced-palette). |
144
+ | `paletteMode` | `'advanced' \| 'simple'` | no | How much of the palette the user meets first. `'advanced'` (default) renders the full sectioned palette with its search input. `'simple'` opens on a flat list of the variants flagged `basic` — in the built-in palette: **Header**, **Divider**, **Rich Text Section** and **Image** — with no search and an "Advanced" link at the top that swaps in the full palette. Nothing is removed; it's a smaller first screen. See [Simple vs advanced palette](#simple-vs-advanced-palette). |
145
145
  | `defaultOpenSections` | `boolean \| string[]` | no | Which palette section headers are expanded on first paint. `true` (default) opens all sections; `false` collapses all (Slack-style); an array opens only sections whose `name` is in the list (e.g. `['Section', 'Actions']`). The palette also has a built-in search input that expands matching sections on demand. |
146
146
  | `showPaletteSearch` | `boolean` | no | Whether the palette renders the quick-search input above the section list. Defaults to `true`. Set `false` for compact palettes (e.g. when you've passed a small custom `palette`) where scanning by eye is faster than typing. Simple mode has no search either way — it appears with the rest of the palette behind "Advanced". |
147
147
  | `paletteSearchPlaceholder` | `string` | no | Placeholder text for the palette search input. Defaults to `'Search blocks…'`. Useful for localization. |
@@ -442,6 +442,8 @@ update against the target from `loadedMessage` / `onLoadedMessageChange`.
442
442
 
443
443
  ## Customizing the palette
444
444
 
445
+ On desktop the palette is a rail down the left-hand side, with a chevron handle on its outer border that hides and restores it — collapsed, the preview gets the full width, and the rail comes back with its open sections and search exactly as they were. Below the `md` breakpoint there's no rail: the toolbar's **Blocks** button opens the palette as a bottom sheet instead, and picking a block closes it.
446
+
445
447
  The default palette ships with curated presets for every supported block type. To narrow what's available, or add your own pre-configured variants (e.g. a "Help footer" section), pass a `palette` array. Define it at module scope (or wrap in `useMemo`) so it stays referentially stable across renders.
446
448
 
447
449
  ```tsx
@@ -476,13 +478,13 @@ Variant `id`s must be unique across the array — the drag-drop lookup keys by i
476
478
 
477
479
  ### Simple vs advanced palette
478
480
 
479
- By default the palette opens on everything it has: every section, plus the search input. For consumers whose users are writing ordinary messages rather than building interactive apps, `paletteMode="simple"` opens on a one-block starter list instead — no sections, no search — behind an **Advanced** link that swaps in the full palette (and back).
481
+ By default the palette opens on everything it has: every section, plus the search input. For consumers whose users are writing ordinary messages rather than building interactive apps, `paletteMode="simple"` opens on a short starter list instead — no sections, no search — behind an **Advanced** link that swaps in the full palette (and back).
480
482
 
481
483
  ```tsx
482
484
  <BlockKitchen paletteMode="simple" {...rest} />
483
485
  ```
484
486
 
485
- Which variants the simple list holds is a property of the palette, not the flag: a variant opts in with `basic: true`. The built-in palette flags exactly one — **Rich Text Section** — so a custom palette that wants its own starter block says so:
487
+ Which variants the simple list holds is a property of the palette, not the flag: a variant opts in with `basic: true`. The built-in palette flags four — **Header**, **Divider**, **Rich Text Section** and **Image** — listed in palette order. A custom palette that wants its own starter blocks says so:
486
488
 
487
489
  ```tsx
488
490
  const PALETTE: readonly PaletteSection[] = [
@@ -497,8 +499,12 @@ const PALETTE: readonly PaletteSection[] = [
497
499
  ];
498
500
  ```
499
501
 
502
+ The flat list has no section headings to lean on, so a variant whose label only reads in context can give simple mode a self-describing one with `basicLabel` — that's how the built-in image variant (**No title**, under **Image**) shows up as **Image** there.
503
+
500
504
  A palette that flags none of its variants `basic` has no simple list to show, so it renders as `'advanced'` regardless — the link is withheld rather than leading to an empty rail.
501
505
 
506
+ The builder remembers which side of that switch the user is on, so the mobile sheet — which is torn down every time it closes — reopens on the full palette rather than sending them back through the **Advanced** link on every block.
507
+
502
508
  ## Templates (`TemplatePicker`)
503
509
 
504
510
  `TemplatePicker` is a standalone, page-sized picker that renders `Template`s as a grid of cards with live block previews, grouped into category sections — modeled on Slack's own Block Kit Builder templates page. It's pure UI: it owns no dialog and no layout, so you decide whether it's a route, a modal, a slide-over, or a sidebar next to the builder.
@@ -1,5 +1,5 @@
1
- import { S as SupportedBlock } from './palette-QbOx7kAa.cjs';
2
- export { a1 as SupportedBlockType } from './palette-QbOx7kAa.cjs';
1
+ import { S as SupportedBlock } from './palette-CvSGn2z6.cjs';
2
+ export { a1 as SupportedBlockType } from './palette-CvSGn2z6.cjs';
3
3
  import 'react';
4
4
  import 'slack-web-api-client';
5
5
 
package/dist/helpers.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as SupportedBlock } from './palette-QbOx7kAa.js';
2
- export { a1 as SupportedBlockType } from './palette-QbOx7kAa.js';
1
+ import { S as SupportedBlock } from './palette-CvSGn2z6.js';
2
+ export { a1 as SupportedBlockType } from './palette-CvSGn2z6.js';
3
3
  import 'react';
4
4
  import 'slack-web-api-client';
5
5
 
package/dist/index.cjs CHANGED
@@ -4,10 +4,10 @@ var core = require('@dnd-kit/core');
4
4
  var sortable = require('@dnd-kit/sortable');
5
5
  var lucideReact = require('lucide-react');
6
6
  var React = require('react');
7
- var jsxRuntime = require('react/jsx-runtime');
8
- var DialogPrimitive = require('@radix-ui/react-dialog');
9
7
  var clsx = require('clsx');
10
8
  var tailwindMerge = require('tailwind-merge');
9
+ var jsxRuntime = require('react/jsx-runtime');
10
+ var DialogPrimitive = require('@radix-ui/react-dialog');
11
11
  var TooltipPrimitive = require('@radix-ui/react-tooltip');
12
12
  var slackBlockKitValidator = require('@tightknitai/slack-block-kit-validator');
13
13
  var nanoid = require('nanoid');
@@ -53,6 +53,9 @@ var Link__default = /*#__PURE__*/_interopDefault(Link);
53
53
  var StarterKit__default = /*#__PURE__*/_interopDefault(StarterKit);
54
54
 
55
55
  // src/components/block-kitchen.tsx
56
+ function cn(...inputs) {
57
+ return tailwindMerge.twMerge(clsx.clsx(inputs));
58
+ }
56
59
 
57
60
  // src/lib/container-blocks.ts
58
61
  var CONTAINER_CHILD_TYPES = /* @__PURE__ */ new Set([
@@ -734,6 +737,7 @@ var defaultPalette = [
734
737
  {
735
738
  id: "structure_header",
736
739
  label: "Header",
740
+ basic: true,
737
741
  factory: () => ({
738
742
  type: "header",
739
743
  text: { type: "plain_text", text: "Heading text", emoji: true }
@@ -742,6 +746,7 @@ var defaultPalette = [
742
746
  {
743
747
  id: "structure_divider",
744
748
  label: "Divider",
749
+ basic: true,
745
750
  factory: () => ({ type: "divider" })
746
751
  },
747
752
  {
@@ -849,8 +854,9 @@ var defaultPalette = [
849
854
  {
850
855
  id: "rich_text_section",
851
856
  label: "Rich Text Section",
852
- // The one variant simple mode offers, where it's listed without its
853
- // section heading — hence the self-describing label.
857
+ // The block a message is mostly made of, and so one of the handful
858
+ // simple mode offers — listed there without its section heading,
859
+ // hence the self-describing label.
854
860
  basic: true,
855
861
  factory: () => ({
856
862
  type: "rich_text",
@@ -887,6 +893,11 @@ var defaultPalette = [
887
893
  {
888
894
  id: "image_no_title",
889
895
  label: "No title",
896
+ // The plain image is the one an ordinary message reaches for, so it
897
+ // carries simple mode's image slot — under its own name there, with
898
+ // no "Image" heading overhead to lend "No title" its meaning.
899
+ basic: true,
900
+ basicLabel: "Image",
890
901
  factory: () => ({
891
902
  type: "image",
892
903
  image_url: "https://placehold.co/600x300?text=Image",
@@ -1709,9 +1720,6 @@ function labelForBlockType(type) {
1709
1720
  return "Container";
1710
1721
  }
1711
1722
  }
1712
- function cn(...inputs) {
1713
- return tailwindMerge.twMerge(clsx.clsx(inputs));
1714
- }
1715
1723
  var Sheet = DialogPrimitive__namespace.Root;
1716
1724
  var SheetOverlay = React__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
1717
1725
  DialogPrimitive__namespace.Overlay,
@@ -3812,14 +3820,14 @@ function isDefaultOpen(sectionName, config) {
3812
3820
  return config.includes(sectionName);
3813
3821
  }
3814
3822
  var PALETTE_TITLE = "Message Elements";
3815
- var ADVANCED_HELP = "Browse every Slack block type \u2014 sections, images, buttons, inputs, tables and more \u2014 plus a search box.";
3816
- var BASIC_HELP = "Go back to the short list: just the rich text block for writing a message.";
3823
+ var ADVANCED_HELP = "Browse every Slack block type \u2014 sections, images, buttons, inputs, tables and more.";
3824
+ var BASIC_HELP = "Go back to the short list: the few blocks an ordinary message is made of.";
3817
3825
  function basicVariants(sections) {
3818
3826
  const out = [];
3819
3827
  for (const section of sections) {
3820
3828
  for (const variant of section.variants) {
3821
3829
  if (variant.basic) {
3822
- out.push(variant);
3830
+ out.push(variant.basicLabel ? { ...variant, label: variant.basicLabel } : variant);
3823
3831
  }
3824
3832
  }
3825
3833
  }
@@ -3848,10 +3856,12 @@ function Palette({
3848
3856
  showSearch = true,
3849
3857
  searchPlaceholder = "Search blocks\u2026",
3850
3858
  mode = "advanced",
3859
+ defaultAdvanced = false,
3860
+ onAdvancedChange,
3851
3861
  variant = "aside"
3852
3862
  }) {
3853
3863
  const [query, setQuery] = React.useState("");
3854
- const [advancedOpen, setAdvancedOpen] = React.useState(false);
3864
+ const [advancedOpen, setAdvancedOpen] = React.useState(defaultAdvanced);
3855
3865
  const [prevMode, setPrevMode] = React.useState(mode);
3856
3866
  if (prevMode !== mode) {
3857
3867
  setPrevMode(mode);
@@ -3873,7 +3883,7 @@ function Palette({
3873
3883
  {
3874
3884
  className: cn(
3875
3885
  "flex min-h-0 flex-col overflow-x-hidden overflow-y-auto",
3876
- isSheet ? "w-full flex-1 bg-background" : "w-72 shrink-0 border-r bg-muted/20"
3886
+ isSheet ? "w-full flex-1 bg-background" : "w-56 shrink-0 border-r bg-muted/20"
3877
3887
  ),
3878
3888
  children: [
3879
3889
  offersSimple || searchVisible ? (
@@ -3906,7 +3916,9 @@ function Palette({
3906
3916
  isSheet,
3907
3917
  onToggle: () => {
3908
3918
  setQuery("");
3909
- setAdvancedOpen((v) => !v);
3919
+ const next = !advancedOpen;
3920
+ setAdvancedOpen(next);
3921
+ onAdvancedChange?.(next);
3910
3922
  }
3911
3923
  }
3912
3924
  )
@@ -4034,8 +4046,8 @@ function PaletteItem({
4034
4046
  isDragging && "opacity-50"
4035
4047
  ),
4036
4048
  children: [
4037
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-md bg-muted text-muted-foreground transition-colors group-hover:bg-primary/10 group-hover:text-primary", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "h-4 w-4" }) }),
4038
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate", children: variant.label })
4049
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate", children: variant.label }),
4050
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-md bg-muted text-muted-foreground transition-colors group-hover:bg-primary/10 group-hover:text-primary", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "h-4 w-4" }) })
4039
4051
  ]
4040
4052
  }
4041
4053
  );
@@ -4045,7 +4057,7 @@ function PaletteItem({
4045
4057
  {
4046
4058
  ref: setNodeRef,
4047
4059
  className: cn(
4048
- "group flex items-center gap-2 rounded px-2 py-1.5 text-sm text-muted-foreground transition-colors hover:bg-accent hover:text-foreground has-[:focus-visible]:bg-accent has-[:focus-visible]:text-foreground",
4060
+ "group flex items-center gap-2 rounded px-2 py-1 text-sm text-muted-foreground transition-colors hover:bg-accent hover:text-foreground has-[:focus-visible]:bg-accent has-[:focus-visible]:text-foreground",
4049
4061
  isDragging && "opacity-50"
4050
4062
  ),
4051
4063
  children: [
@@ -4053,7 +4065,7 @@ function PaletteItem({
4053
4065
  "div",
4054
4066
  {
4055
4067
  ref: setActivatorNodeRef,
4056
- className: "-my-1.5 flex min-w-0 flex-1 cursor-grab items-center gap-2 rounded py-1.5 active:cursor-grabbing focus-visible:outline-none",
4068
+ className: "-my-1 flex min-w-0 flex-1 cursor-grab items-center gap-2 rounded py-1 active:cursor-grabbing focus-visible:outline-none",
4057
4069
  ...attributes,
4058
4070
  ...listeners,
4059
4071
  children: [
@@ -6100,12 +6112,44 @@ function HeaderEditor({ block, onChange }) {
6100
6112
  ] });
6101
6113
  }
6102
6114
  function ImageEditor({ block, onChange }) {
6103
- if (!("image_url" in block)) {
6104
- return /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "This image references a Slack file. Not editable in the visual builder." });
6105
- }
6115
+ const source = "slack_file" in block ? "slack_file" : "url";
6106
6116
  const titleText = block.title?.text ?? "";
6117
+ const setSource = (next) => {
6118
+ if (next === source) {
6119
+ return;
6120
+ }
6121
+ if (next === "url") {
6122
+ const { slack_file: _omit, ...rest } = block;
6123
+ onChange({ image_url: "", ...rest });
6124
+ } else {
6125
+ const { image_url: _omit, ...rest } = block;
6126
+ onChange({ slack_file: { id: "" }, ...rest });
6127
+ }
6128
+ };
6107
6129
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3", children: [
6108
- /* @__PURE__ */ jsxRuntime.jsx(EditorField, { label: "Image URL", help: "A publicly accessible image URL (PNG, JPG, GIF).", htmlFor: "img-url", children: /* @__PURE__ */ jsxRuntime.jsx(
6130
+ /* @__PURE__ */ jsxRuntime.jsx(
6131
+ EditorField,
6132
+ {
6133
+ label: "Image source",
6134
+ help: "Where the image comes from: a public URL, or a file already uploaded to your Slack workspace.",
6135
+ children: /* @__PURE__ */ jsxRuntime.jsx(
6136
+ RadioGroup,
6137
+ {
6138
+ value: source,
6139
+ onValueChange: (v) => setSource(v),
6140
+ className: "flex flex-row flex-wrap gap-3",
6141
+ children: [
6142
+ ["url", "Public URL"],
6143
+ ["slack_file", "Slack file"]
6144
+ ].map(([value, label]) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
6145
+ /* @__PURE__ */ jsxRuntime.jsx(RadioGroupItem, { value, id: `img-source-${value}` }),
6146
+ /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: `img-source-${value}`, className: "text-xs", children: label })
6147
+ ] }, value))
6148
+ }
6149
+ )
6150
+ }
6151
+ ),
6152
+ "slack_file" in block ? /* @__PURE__ */ jsxRuntime.jsx(SlackFileFields, { block, onChange }) : /* @__PURE__ */ jsxRuntime.jsx(EditorField, { label: "Image URL", help: "A publicly accessible image URL (PNG, JPG, GIF).", htmlFor: "img-url", children: /* @__PURE__ */ jsxRuntime.jsx(
6109
6153
  Input,
6110
6154
  {
6111
6155
  id: "img-url",
@@ -6149,6 +6193,51 @@ function ImageEditor({ block, onChange }) {
6149
6193
  ) })
6150
6194
  ] });
6151
6195
  }
6196
+ function SlackFileFields({
6197
+ block,
6198
+ onChange
6199
+ }) {
6200
+ const fileId = "id" in block.slack_file ? block.slack_file.id : "";
6201
+ const fileUrl = "url" in block.slack_file ? block.slack_file.url : "";
6202
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6203
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] leading-snug text-muted-foreground", children: "References a file already uploaded to your Slack workspace. The preview can't render workspace files \u2014 switch the source to Public URL to see the image here." }),
6204
+ /* @__PURE__ */ jsxRuntime.jsx(
6205
+ EditorField,
6206
+ {
6207
+ label: "File ID",
6208
+ help: "The Slack file's ID. Slack accepts an ID or a URL, not both, so filling this clears File URL.",
6209
+ htmlFor: "img-file-id",
6210
+ children: /* @__PURE__ */ jsxRuntime.jsx(
6211
+ Input,
6212
+ {
6213
+ id: "img-file-id",
6214
+ value: fileId,
6215
+ placeholder: "e.g. F0123456789",
6216
+ onChange: (e) => onChange({ ...block, slack_file: { id: e.target.value } })
6217
+ }
6218
+ )
6219
+ }
6220
+ ),
6221
+ /* @__PURE__ */ jsxRuntime.jsx(
6222
+ EditorField,
6223
+ {
6224
+ label: "File URL",
6225
+ help: "The file's Slack-hosted URL. Filling this clears File ID.",
6226
+ htmlFor: "img-file-url",
6227
+ children: /* @__PURE__ */ jsxRuntime.jsx(
6228
+ Input,
6229
+ {
6230
+ id: "img-file-url",
6231
+ type: "url",
6232
+ value: fileUrl,
6233
+ placeholder: "e.g. https://files.slack.com/files-pri/T0123-F0123/image.png",
6234
+ onChange: (e) => onChange({ ...block, slack_file: { url: e.target.value } })
6235
+ }
6236
+ )
6237
+ }
6238
+ )
6239
+ ] });
6240
+ }
6152
6241
  function InputEditor({ block, onChange }) {
6153
6242
  const updateElement = (next) => {
6154
6243
  onChange({ ...block, element: next });
@@ -10030,7 +10119,7 @@ function Toolbar2({
10030
10119
  variant: "ghost",
10031
10120
  size: "sm",
10032
10121
  onClick: onOpenPalette,
10033
- className: "md:hidden",
10122
+ className: "gap-1 md:hidden",
10034
10123
  "aria-label": "Add a block",
10035
10124
  children: [
10036
10125
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-4 w-4" }),
@@ -10586,12 +10675,20 @@ function BlockKitchen(props) {
10586
10675
  useNotifyOnChange(activeEditTarget, loadedMessageKey, loading?.onLoadedMessageChange);
10587
10676
  const [issuesOpen, setIssuesOpen] = React.useState(false);
10588
10677
  const [paletteOpen, setPaletteOpen] = React.useState(false);
10678
+ const [paletteCollapsed, setPaletteCollapsed] = React.useState(false);
10679
+ const [paletteAdvanced, setPaletteAdvanced] = React.useState(false);
10680
+ const [prevPaletteMode, setPrevPaletteMode] = React.useState(paletteMode);
10681
+ if (prevPaletteMode !== paletteMode) {
10682
+ setPrevPaletteMode(paletteMode);
10683
+ setPaletteAdvanced(false);
10684
+ }
10589
10685
  const [openBlockId, setOpenBlockId] = React.useState(null);
10590
10686
  const [uncontrolledPreviewTheme, setUncontrolledPreviewTheme] = React.useState(defaultPreviewTheme);
10591
10687
  const isPreviewThemeControlled = controlledPreviewTheme !== void 0;
10592
10688
  const previewTheme = controlledPreviewTheme ?? uncontrolledPreviewTheme;
10593
10689
  const [previewSurface, setPreviewSurface] = React.useState(allowedSurfaces[0]);
10594
10690
  const [activePaletteVariantId, setActivePaletteVariantId] = React.useState(null);
10691
+ const paletteSheetRef = React.useRef(null);
10595
10692
  const isMobile = useIsMobile();
10596
10693
  const mergedPreviewHooks = React.useMemo(() => {
10597
10694
  if (!customEmojis || customEmojis.length === 0) {
@@ -10775,17 +10872,48 @@ function BlockKitchen(props) {
10775
10872
  }
10776
10873
  ),
10777
10874
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-0 flex-1 items-stretch", children: [
10778
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden min-h-0 md:flex", children: /* @__PURE__ */ jsxRuntime.jsx(
10779
- Palette,
10780
- {
10781
- onAddBlock: (block) => addBlock(block),
10782
- sections: paletteSections,
10783
- mode: paletteMode,
10784
- showSearch: showPaletteSearch,
10785
- searchPlaceholder: paletteSearchPlaceholder,
10786
- defaultOpenSections
10787
- }
10788
- ) }),
10875
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative hidden min-h-0 shrink-0 md:flex", children: [
10876
+ /* @__PURE__ */ jsxRuntime.jsx("div", { hidden: paletteCollapsed, className: cn("min-h-0", !paletteCollapsed && "flex"), children: /* @__PURE__ */ jsxRuntime.jsx(
10877
+ Palette,
10878
+ {
10879
+ onAddBlock: (block) => addBlock(block),
10880
+ sections: paletteSections,
10881
+ mode: paletteMode,
10882
+ showSearch: showPaletteSearch,
10883
+ searchPlaceholder: paletteSearchPlaceholder,
10884
+ defaultOpenSections,
10885
+ defaultAdvanced: paletteAdvanced,
10886
+ onAdvancedChange: setPaletteAdvanced
10887
+ }
10888
+ ) }),
10889
+ paletteCollapsed ? (
10890
+ // In a host that hands the builder no height of its own,
10891
+ // the rail is what the shell takes its height from (see
10892
+ // the shell's `max-h` note above) — so hiding it would
10893
+ // shrink the whole frame to the height of the message and
10894
+ // grow it back on the way out. This stands in for the rail
10895
+ // at no width. Inert to everything but layout.
10896
+ /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": true, className: "w-0 shrink-0 md:h-[var(--bk-max-height,100svh)]" })
10897
+ ) : null,
10898
+ /* @__PURE__ */ jsxRuntime.jsx(
10899
+ "button",
10900
+ {
10901
+ type: "button",
10902
+ onClick: () => setPaletteCollapsed((v) => !v),
10903
+ "aria-expanded": !paletteCollapsed,
10904
+ "aria-label": paletteCollapsed ? "Show block palette" : "Hide block palette",
10905
+ title: paletteCollapsed ? "Show block palette" : "Hide block palette",
10906
+ className: cn(
10907
+ // A hairline outline in the rail's own border color, so
10908
+ // the handle reads as part of the edge it rides rather
10909
+ // than a chevron floating loose over the border line.
10910
+ "absolute top-1/2 z-10 flex h-10 w-4 -translate-y-1/2 cursor-pointer appearance-none items-center justify-center rounded-sm border bg-background p-0 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
10911
+ paletteCollapsed ? "left-0" : "right-0 translate-x-1/2"
10912
+ ),
10913
+ children: paletteCollapsed ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "h-4 w-4", strokeWidth: 1.5 }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, { className: "h-4 w-4", strokeWidth: 1.5 })
10914
+ }
10915
+ )
10916
+ ] }),
10789
10917
  /* @__PURE__ */ jsxRuntime.jsx(
10790
10918
  Surface,
10791
10919
  {
@@ -10818,8 +10946,13 @@ function BlockKitchen(props) {
10818
10946
  /* @__PURE__ */ jsxRuntime.jsx(Sheet, { open: paletteOpen && isMobile, onOpenChange: setPaletteOpen, children: /* @__PURE__ */ jsxRuntime.jsxs(
10819
10947
  SheetContent,
10820
10948
  {
10949
+ ref: paletteSheetRef,
10821
10950
  side: "bottom",
10822
10951
  className: "bk-portal-content flex h-[85svh] max-h-[85svh] flex-col gap-3 p-0 sm:max-w-none",
10952
+ onOpenAutoFocus: (e) => {
10953
+ e.preventDefault();
10954
+ paletteSheetRef.current?.focus();
10955
+ },
10823
10956
  children: [
10824
10957
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1 px-4 pt-5", children: [
10825
10958
  /* @__PURE__ */ jsxRuntime.jsx(SheetTitle, { children: "Add a block" }),
@@ -10837,6 +10970,8 @@ function BlockKitchen(props) {
10837
10970
  showSearch: showPaletteSearch,
10838
10971
  searchPlaceholder: paletteSearchPlaceholder,
10839
10972
  defaultOpenSections,
10973
+ defaultAdvanced: paletteAdvanced,
10974
+ onAdvancedChange: setPaletteAdvanced,
10840
10975
  variant: "sheet"
10841
10976
  }
10842
10977
  ) })