@tightknitai/block-kitchen 0.10.23 → 0.10.25

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
@@ -139,6 +139,8 @@ Given no height to work with — a plain `<div>` in ordinary document flow — i
139
139
  | `confirmUpdateLabel` | `string` | no | Label for the update dialog's final confirm button. Defaults to `'Update message'` (shows `'Updating…'` while in flight). |
140
140
  | `previewHooks` | `PreviewHooks` | no | Hooks forwarded to `slack-blocks-to-jsx`'s `<Message>` for resolving user / channel / emoji directives. |
141
141
  | `blockActions` | `BlockAction[]` | no | Host buttons added to every block's action toolbar, before Edit / Duplicate / Delete. Each is `{ id, label, icon, onSelect }`, and `onSelect` receives the block's `{ index, childIndex }`. See [Adding your own block actions](#adding-your-own-block-actions-blockactions). |
142
+ | `renderBlockAdornment` | `(position: BlockPosition) => ReactNode` | no | Host content shown beside every block, at its right edge, all the time (unlike `blockActions`, which show on hover), e.g. a count of comments on the block. Return `null` for a block with nothing to show. See [Adding your own block actions](#adding-your-own-block-actions-blockactions). |
143
+ | `blockEditors` | `CustomBlockEditor[]` | no | Host forms that replace the built-in editor for the blocks they match. Each is `{ id, label, match, component }`. The block stays ordinary Block Kit. See [Editing a block your way](#editing-a-block-your-way-blockeditors). |
142
144
  | `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. |
143
145
  | `mentions` | `{ searchUsers, getUserName?, searchChannels?, getChannelName? }` | no | Opt-in rich-text mentions. `@` and the Mention button search users; when `searchChannels` is provided, `#` and the Channel button search channels. Picks serialize as Slack `user` / `channel` elements. See [Mentions](#mentions-opt-in). |
144
146
  | `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). |
@@ -149,6 +151,7 @@ Given no height to work with — a plain `<div>` in ordinary document flow — i
149
151
  | `paletteSearchPlaceholder` | `string` | no | Placeholder text for the palette search input. Defaults to `'Search blocks…'`. Useful for localization. |
150
152
  | `allowedSurfaces` | `PreviewSurface[]` | no | Allowlist of preview surfaces (`'message'`, `'modal'`, `'app_home'`). Defaults to `['message']` — surface dropdown is hidden when only one surface is allowed. The first entry is the initial selection. |
151
153
  | `showThemeControl` | `boolean` | no | Defaults to `true`. When `false`, the toolbar's light/dark toggle is hidden and the theme stays at `defaultPreviewTheme`. Ignored when `previewTheme` is set (a controlled theme always hides the toggle). |
154
+ | `undoRedoShortcuts` | `boolean` | no | Defaults to `true`: `Ctrl`/`Cmd` + `Z` undoes and `Ctrl`/`Cmd` + `Shift` + `Z` (or `Ctrl` + `Y`) redoes, like the toolbar's Undo / Redo buttons. Pass `false` to leave those keys to the host. See [Undo & redo](#undo--redo). |
152
155
  | `defaultPreviewTheme` | `'light' \| 'dark'` | no | Initial (uncontrolled) preview theme. Pass the host app's current theme so the preview opens matched to the consuming app's appearance. Ignored when `previewTheme` is provided. |
153
156
  | `previewTheme` | `'light' \| 'dark'` | no | Controlled preview theme. When set, the preview renders in this theme, follows it reactively, and the toolbar's light/dark toggle is hidden so the host app fully owns the theme. Leave unset to keep the preview uncontrolled (seeded from `defaultPreviewTheme`, toggle shown). |
154
157
  | `sendButtonLabel` | `string` | no | Label and accessible name for the toolbar's Send button (which opens the send dialog). Defaults to `'Review & send'` (the dialog is the review step). Override it for product-specific copy, e.g. `'Send to channel…'`. |
@@ -467,6 +470,19 @@ top-level block). Map it onto the blocks `onChange` last reported; builder
467
470
  ids never leave the package. An `svg` icon is sized to match the built-in
468
471
  ones.
469
472
 
473
+ For something that should show all the time, like a count of comments on
474
+ each block, pass `renderBlockAdornment`. It's called with the same position
475
+ and renders beside the block, at its right edge; return `null` for a block
476
+ with nothing to show:
477
+
478
+ ```tsx
479
+ <BlockKitchen
480
+ renderBlockAdornment={({ index, childIndex }) =>
481
+ childIndex === null && counts[index] ? <CommentCount count={counts[index]} /> : null
482
+ }
483
+ />
484
+ ```
485
+
470
486
  Each block's row also carries `data-bk-block-index` (plus
471
487
  `data-bk-child-index` inside a container), so the host can find a block on
472
488
  the canvas, for example to scroll to it or highlight it:
@@ -475,6 +491,41 @@ the canvas, for example to scroll to it or highlight it:
475
491
  document.querySelector(`.bk-root [data-bk-block-index="${index}"]:not([data-bk-child-index])`);
476
492
  ```
477
493
 
494
+ ## Editing a block your way (`blockEditors`)
495
+
496
+ Some blocks mean something to your app beyond their Block Kit shape: a
497
+ "Share on LinkedIn" button, say, whose URL is built from the text of a post.
498
+ Editing that URL by hand in the built-in buttons form is no way to write a
499
+ post. `blockEditors` lets the host supply the form instead:
500
+
501
+ ```tsx
502
+ const LINKEDIN_EDITOR: CustomBlockEditor = {
503
+ id: "linkedin-share",
504
+ label: "Share on LinkedIn",
505
+ match: (block) => isLinkedInShareBlock(block), // your own check
506
+ component: ({ block, onChange }) => (
507
+ <Textarea
508
+ value={postTextFrom(block)}
509
+ onChange={(e) => onChange(withPostText(block, e.target.value))}
510
+ />
511
+ ),
512
+ };
513
+
514
+ <BlockKitchen blockEditors={[LINKEDIN_EDITOR]} {...rest} />;
515
+ ```
516
+
517
+ When a block's edit popover opens, the first editor whose `match` accepts the
518
+ block replaces the built-in form, under the heading "Edit {label}". The
519
+ popover keeps its validation errors, Block ID field and Done button. Blocks no
520
+ editor matches keep the built-in form, and so does every block when the prop
521
+ is left out.
522
+
523
+ The block itself stays ordinary Block Kit, so the preview, validation, the
524
+ JSON view and sending all treat it like any other. That also means `match` has
525
+ to recognize the block from what it holds (a URL, an `action_id`): a message
526
+ loaded back from Slack carries nothing else. Keep the array referentially
527
+ stable (module scope or `useMemo`).
528
+
478
529
  ## Customizing the palette
479
530
 
480
531
  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.
@@ -511,6 +562,28 @@ const PALETTE: readonly PaletteSection[] = [
511
562
 
512
563
  Variant `id`s must be unique across the array — the drag-drop lookup keys by id.
513
564
 
565
+ ### Badges and locked variants
566
+
567
+ A variant can carry a `badge`, a small tag beside its label such as the plan
568
+ it belongs to. Set `locked` as well to show a block without letting the user
569
+ add it: it can't be dragged, has no add button, and picking it calls
570
+ `onLockedSelect`, for example to open an upgrade prompt.
571
+
572
+ ```tsx
573
+ const shareVariant: PaletteVariant = {
574
+ id: "linkedin_share",
575
+ label: "Share on LinkedIn",
576
+ factory: () => linkedInShareBlock(),
577
+ badge: { label: "Enterprise", icon: Crown },
578
+ locked: !plan.includesLinkedIn,
579
+ onLockedSelect: () => openUpgradeDialog(),
580
+ };
581
+ ```
582
+
583
+ A locked variant without `onLockedSelect` is inert. In the mobile sheet,
584
+ picking one closes the sheet first, so whatever the host opens isn't stacked
585
+ on top of it.
586
+
514
587
  ### Simple vs advanced palette
515
588
 
516
589
  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).
@@ -714,8 +787,8 @@ Defense-in-depth: blocks are validated against [slack-block-kit-validator](https
714
787
  Every edit to the draft is undoable — adding, deleting, duplicating,
715
788
  reordering, dragging in and out of containers, editing a block's fields, and
716
789
  the toolbar's **Clear** and **View JSON → apply**. The toolbar shows **Undo**
717
- and **Redo** buttons (disabled when there's nothing to step to), and the
718
- keyboard shortcuts work whenever focus is inside the builder:
790
+ and **Redo** buttons (disabled when there's nothing to step to), and these
791
+ keyboard shortcuts do the same:
719
792
 
720
793
  | Shortcut | Action |
721
794
  |---|---|
@@ -729,10 +802,23 @@ Notes:
729
802
  character at a time. Editing a different block, or any structural change,
730
803
  starts a new step.
731
804
  - **Native text undo is preserved.** While a text input, textarea, or the
732
- inline rich-text editor is focused, `Ctrl/Cmd+Z` performs the browser's
733
- own per-character undo — the builder only takes over when focus is on the
734
- canvas or toolbar. The shortcut is scoped to the builder, so it never
735
- hijacks the host app's own `Ctrl/Cmd+Z`.
805
+ inline rich-text editor is focused (including the fields in a block's
806
+ editor popover), `Ctrl/Cmd+Z` performs the browser's own per-character
807
+ undo. The builder only takes over when focus is on the canvas or toolbar.
808
+ - **The shortcuts are scoped to the builder**, so they don't hijack the host
809
+ page's own `Ctrl/Cmd+Z`. They act while focus is inside the builder, or
810
+ while nothing on the page has focus (focus is on `<body>`, or on a host
811
+ element wrapping the builder) and the last click landed in the builder.
812
+ That second case covers a click on the canvas background, or deleting a
813
+ block, which unmounts the Delete button that had focus. After a click
814
+ elsewhere on the page, or with focus in one of the host's own fields, the
815
+ keys are left alone.
816
+ - With nothing to undo or redo, or when something else already handled the
817
+ key press (`defaultPrevented`), the shortcut does nothing and doesn't call
818
+ `preventDefault`.
819
+ - **Opting out.** Pass `undoRedoShortcuts={false}` to turn the shortcuts off
820
+ and leave those keys to the host. The toolbar buttons still work, and
821
+ their tooltips stop mentioning the shortcuts.
736
822
  - **Loading is a fresh baseline.** [Loading an existing
737
823
  message](#loading-an-existing-message-opt-in) (or "open as new") starts a
738
824
  new document with an empty history — undo won't step back across the load
@@ -1,5 +1,5 @@
1
- import { S as SupportedBlock } from './palette-DFTL0Han.cjs';
2
- export { aa as SupportedBlockType } from './palette-DFTL0Han.cjs';
1
+ import { S as SupportedBlock } from './palette-DTE9U5si.cjs';
2
+ export { ad as SupportedBlockType } from './palette-DTE9U5si.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-DFTL0Han.js';
2
- export { aa as SupportedBlockType } from './palette-DFTL0Han.js';
1
+ import { S as SupportedBlock } from './palette-DTE9U5si.js';
2
+ export { ad as SupportedBlockType } from './palette-DTE9U5si.js';
3
3
  import 'react';
4
4
  import 'slack-web-api-client';
5
5