@nkootstra/block-kit 0.1.1 → 0.3.0

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
@@ -2,11 +2,11 @@
2
2
 
3
3
  `@nkootstra/block-kit` renders Slack [Block Kit](https://docs.slack.dev/block-kit/) JSON in React, pixel-for-pixel the way Slack does. Pass it the `blocks` your app sends to `chat.postMessage` or `views.open` and it draws the message, modal or Home tab, with working buttons, menus, date pickers and inputs.
4
4
 
5
- Documentation: **<https://block-kit.kootstra.io>**
5
+ Documentation: **<https://docs.block-kit.dev>**
6
6
 
7
7
  ## Install
8
8
 
9
- The package needs React 19 or later.
9
+ The package needs React 18 or later.
10
10
 
11
11
  ```sh
12
12
  bun add @nkootstra/block-kit @fontsource/lato @fontsource/roboto-mono
@@ -33,7 +33,7 @@ export function App() {
33
33
  }
34
34
  ```
35
35
 
36
- The [installation guide](https://block-kit.kootstra.io/installation) lists the remaining font weights and the entry points, and the [quickstart](https://block-kit.kootstra.io/quickstart) wires up an interactive approval message.
36
+ The [installation guide](https://docs.block-kit.dev/installation) lists the remaining font weights and the entry points, and the [quickstart](https://docs.block-kit.dev/quickstart) wires up an interactive approval message.
37
37
 
38
38
  ## Entry points
39
39
 
@@ -580,6 +580,20 @@ interface PayloadContext {
580
580
  }
581
581
  /** What `onSubmit` may return: Slack's `response_action` ack, or nothing to just close the modal. */
582
582
  type SubmitResult = ViewResponseAction | undefined | void;
583
+ /** What block-kit passes to a `linkComponent`: an anchor's props, ready to spread. */
584
+ interface LinkProps {
585
+ href: string;
586
+ className?: string;
587
+ target?: string;
588
+ rel?: string;
589
+ children?: ReactNode;
590
+ }
591
+ /** A resolved mention, as `mentionHref` gets it. */
592
+ interface MentionRef {
593
+ type: "user" | "channel" | "usergroup";
594
+ /** Slack id, e.g. `U0123ABC`, `C0123ABC` or `S0123ABC`. */
595
+ id: string;
596
+ }
583
597
  interface BlockKitContextValue {
584
598
  surface: Surface;
585
599
  /** The provider's `theme`. Popovers, tooltips and dialogs render outside its wrapper, so they set it themselves. */
@@ -594,6 +608,10 @@ interface BlockKitContextValue {
594
608
  /** Called with a `view_closed` payload when a `<Modal>`'s close (X) button is pressed. */
595
609
  onClose?: (payload: ReturnType<typeof buildViewClosedPayload>) => void;
596
610
  resolvers: Resolvers;
611
+ /** Renders links from the payload; an `<a>` unless the provider's `linkComponent` says otherwise. */
612
+ linkComponent?: ComponentType<LinkProps>;
613
+ /** The app's page for a mention, if it has one. */
614
+ mentionHref?: (mention: MentionRef) => string | undefined;
597
615
  emoji: EmojiOptions;
598
616
  /** Validation errors keyed by block_id, as returned in `response_action: "errors"`. */
599
617
  errors: Record<string, string>;
@@ -656,6 +674,20 @@ interface BlockKitProviderProps {
656
674
  /** Called whenever an input value changes, with the full `state.values`. */
657
675
  onStateChange?: (state: StateValues) => void;
658
676
  resolvers?: Resolvers;
677
+ /**
678
+ * Renders every link that comes from the payload, e.g. a router's `<Link>` for client-side
679
+ * navigation or a component that rewrites URLs: links in mrkdwn, rich text, markdown and dates,
680
+ * data tables, plan and task card sources, video titles, attachment authors and titles, and
681
+ * mentions linked by `mentionHref`. It gets an anchor's props to spread: `href`, `className`,
682
+ * `children`, and `target`/`rel` for links that open a new tab, as Slack's do.
683
+ */
684
+ linkComponent?: ComponentType<LinkProps>;
685
+ /**
686
+ * Links resolved mentions to the app's own pages: return a URL for a user, channel or user
687
+ * group, or `undefined` to leave it unlinked. Mention links open in the same tab and render
688
+ * through `linkComponent`.
689
+ */
690
+ mentionHref?: (mention: MentionRef) => string | undefined;
659
691
  emoji?: EmojiOptions;
660
692
  errors?: Record<string, string>;
661
693
  timeZone?: string;
@@ -907,5 +939,5 @@ declare const VALIDATION_MESSAGES: {
907
939
  */
908
940
  declare function validateView(blocks: readonly Json[] | readonly unknown[], state: StateValues): Record<string, string>;
909
941
  //#endregion
910
- export { BlockSuggestionPayload as $, BlockAction as A, StackedView as B, SlackAttachment as C, BlockProps as Ct, elementComponents as D, SlackReaction as E, EmojiOptions as F, SurfaceScopeProps as G, SubmitResult as H, MessageApi as I, ViewsApi as J, SurfaceScopeValue as K, MessageUpdate as L, BlockKitProvider as M, BlockKitProviderProps as N, Element as O, ElementState as P, BlockActionsPayload as Q, PayloadContext as R, MessageProps as S, Blocks as St, SlackMessageLike as T, Json as Tt, Surface as U, StateValues as V, SurfaceScope as W, useContainer as X, useBlockKit as Y, useLiveView as Z, HomeTabView as _, buildBlockSuggestionPayload as _t, Tooltip as a, MessageBlockActionsPayload as at, Message as b, blockComponents as bt, TextObject as c, PayloadIdentity as ct, ViewProps as d, SuggestionOption as dt, BuildBlockActionsPayloadOptions as et, Modal as f, ViewBlockActionsPayload as ft, HomeTabProps as g, buildBlockActionsPayload as gt, HomeTab as h, ViewResponseAction as ht, UserMentionProps as i, Container as it, BlockKitContextValue as j, ActionContext as k, AnyView as l, PayloadTeam as lt, ModalView as m, ViewLike as mt, validateView as n, BuildViewClosedPayloadOptions as nt, TooltipProps as o, MessageContainer as ot, ModalProps as p, ViewContainer as pt, UserProfile as q, UserMention as r, BuildViewSubmissionPayloadOptions as rt, Text as s, OptionsResponse as st, VALIDATION_MESSAGES as t, BuildBlockSuggestionPayloadOptions as tt, View as u, PayloadUser as ut, Mrkdwn as v, buildViewClosedPayload as vt, SlackAttachmentField as w, ElementProps as wt, MessageApp as x, Block as xt, MrkdwnProps as y, buildViewSubmissionPayload as yt, Resolvers as z };
911
- //# sourceMappingURL=index-FPjJcmn3.d.ts.map
942
+ export { useLiveView as $, BlockAction as A, PayloadContext as B, SlackAttachment as C, Block as Ct, elementComponents as D, Json as Dt, SlackReaction as E, ElementProps as Et, EmojiOptions as F, Surface as G, StackedView as H, LinkProps as I, SurfaceScopeValue as J, SurfaceScope as K, MentionRef as L, BlockKitProvider as M, BlockKitProviderProps as N, Element as O, ElementState as P, useContainer as Q, MessageApi as R, MessageProps as S, blockComponents as St, SlackMessageLike as T, BlockProps as Tt, StateValues as U, Resolvers as V, SubmitResult as W, ViewsApi as X, UserProfile as Y, useBlockKit as Z, HomeTabView as _, ViewResponseAction as _t, Tooltip as a, BuildViewSubmissionPayloadOptions as at, Message as b, buildViewClosedPayload as bt, TextObject as c, MessageContainer as ct, ViewProps as d, PayloadTeam as dt, BlockActionsPayload as et, Modal as f, PayloadUser as ft, HomeTabProps as g, ViewLike as gt, HomeTab as h, ViewContainer as ht, UserMentionProps as i, BuildViewClosedPayloadOptions as it, BlockKitContextValue as j, ActionContext as k, AnyView as l, OptionsResponse as lt, ModalView as m, ViewBlockActionsPayload as mt, validateView as n, BuildBlockActionsPayloadOptions as nt, TooltipProps as o, Container as ot, ModalProps as p, SuggestionOption as pt, SurfaceScopeProps as q, UserMention as r, BuildBlockSuggestionPayloadOptions as rt, Text as s, MessageBlockActionsPayload as st, VALIDATION_MESSAGES as t, BlockSuggestionPayload as tt, View as u, PayloadIdentity as ut, Mrkdwn as v, buildBlockActionsPayload as vt, SlackAttachmentField as w, Blocks as wt, MessageApp as x, buildViewSubmissionPayload as xt, MrkdwnProps as y, buildBlockSuggestionPayload as yt, MessageUpdate as z };
943
+ //# sourceMappingURL=index-D139DZfD.d.ts.map
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
 
2
- import { $ as BlockSuggestionPayload, A as BlockAction, B as StackedView, C as SlackAttachment, Ct as BlockProps, D as elementComponents, E as SlackReaction, F as EmojiOptions, G as SurfaceScopeProps, H as SubmitResult, I as MessageApi, J as ViewsApi, K as SurfaceScopeValue, L as MessageUpdate, M as BlockKitProvider, N as BlockKitProviderProps, O as Element, P as ElementState, Q as BlockActionsPayload, R as PayloadContext, S as MessageProps, St as Blocks, T as SlackMessageLike, Tt as Json, U as Surface, V as StateValues, W as SurfaceScope, X as useContainer, Y as useBlockKit, Z as useLiveView, _ as HomeTabView, _t as buildBlockSuggestionPayload, a as Tooltip, at as MessageBlockActionsPayload, b as Message, bt as blockComponents, c as TextObject, ct as PayloadIdentity, d as ViewProps, dt as SuggestionOption, et as BuildBlockActionsPayloadOptions, f as Modal, ft as ViewBlockActionsPayload, g as HomeTabProps, gt as buildBlockActionsPayload, h as HomeTab, ht as ViewResponseAction, i as UserMentionProps, it as Container, j as BlockKitContextValue, k as ActionContext, l as AnyView, lt as PayloadTeam, m as ModalView, mt as ViewLike, n as validateView, nt as BuildViewClosedPayloadOptions, o as TooltipProps, ot as MessageContainer, p as ModalProps, pt as ViewContainer, q as UserProfile, r as UserMention, rt as BuildViewSubmissionPayloadOptions, s as Text, st as OptionsResponse, t as VALIDATION_MESSAGES, tt as BuildBlockSuggestionPayloadOptions, u as View, ut as PayloadUser, v as Mrkdwn, vt as buildViewClosedPayload, w as SlackAttachmentField, wt as ElementProps, x as MessageApp, xt as Block, y as MrkdwnProps, yt as buildViewSubmissionPayload, z as Resolvers } from "./index-FPjJcmn3.js";
3
- export { type ActionContext, type AnyView, Block, type BlockAction, type BlockActionsPayload, type BlockKitContextValue, BlockKitProvider, type BlockKitProviderProps, type BlockProps, type BlockSuggestionPayload, Blocks, type BuildBlockActionsPayloadOptions, type BuildBlockSuggestionPayloadOptions, type BuildViewClosedPayloadOptions, type BuildViewSubmissionPayloadOptions, type Container, Element, type ElementProps, type ElementState, type EmojiOptions, HomeTab, type HomeTabProps, type HomeTabView, type Json, Message, type MessageApi, type MessageApp, type MessageBlockActionsPayload, type MessageContainer, type MessageProps, type MessageUpdate, Modal, type ModalProps, type ModalView, Mrkdwn, type MrkdwnProps, type OptionsResponse, type PayloadContext, type PayloadIdentity, type PayloadTeam, type PayloadUser, type Resolvers, type SlackAttachment, type SlackAttachmentField, type SlackMessageLike, type SlackReaction, type StackedView, type StateValues, type SubmitResult, type SuggestionOption, type Surface, SurfaceScope, type SurfaceScopeProps, type SurfaceScopeValue, Text, type TextObject, Tooltip, type TooltipProps, UserMention, type UserMentionProps, type UserProfile, VALIDATION_MESSAGES, View, type ViewBlockActionsPayload, type ViewContainer, type ViewLike, type ViewProps, type ViewResponseAction, type ViewsApi, blockComponents, buildBlockActionsPayload, buildBlockSuggestionPayload, buildViewClosedPayload, buildViewSubmissionPayload, elementComponents, useBlockKit, useContainer, useLiveView, validateView };
2
+ import { $ as useLiveView, A as BlockAction, B as PayloadContext, C as SlackAttachment, Ct as Block, D as elementComponents, Dt as Json, E as SlackReaction, Et as ElementProps, F as EmojiOptions, G as Surface, H as StackedView, I as LinkProps, J as SurfaceScopeValue, K as SurfaceScope, L as MentionRef, M as BlockKitProvider, N as BlockKitProviderProps, O as Element, P as ElementState, Q as useContainer, R as MessageApi, S as MessageProps, St as blockComponents, T as SlackMessageLike, Tt as BlockProps, U as StateValues, V as Resolvers, W as SubmitResult, X as ViewsApi, Y as UserProfile, Z as useBlockKit, _ as HomeTabView, _t as ViewResponseAction, a as Tooltip, at as BuildViewSubmissionPayloadOptions, b as Message, bt as buildViewClosedPayload, c as TextObject, ct as MessageContainer, d as ViewProps, dt as PayloadTeam, et as BlockActionsPayload, f as Modal, ft as PayloadUser, g as HomeTabProps, gt as ViewLike, h as HomeTab, ht as ViewContainer, i as UserMentionProps, it as BuildViewClosedPayloadOptions, j as BlockKitContextValue, k as ActionContext, l as AnyView, lt as OptionsResponse, m as ModalView, mt as ViewBlockActionsPayload, n as validateView, nt as BuildBlockActionsPayloadOptions, o as TooltipProps, ot as Container, p as ModalProps, pt as SuggestionOption, q as SurfaceScopeProps, r as UserMention, rt as BuildBlockSuggestionPayloadOptions, s as Text, st as MessageBlockActionsPayload, t as VALIDATION_MESSAGES, tt as BlockSuggestionPayload, u as View, ut as PayloadIdentity, v as Mrkdwn, vt as buildBlockActionsPayload, w as SlackAttachmentField, wt as Blocks, x as MessageApp, xt as buildViewSubmissionPayload, y as MrkdwnProps, yt as buildBlockSuggestionPayload, z as MessageUpdate } from "./index-D139DZfD.js";
3
+ export { type ActionContext, type AnyView, Block, type BlockAction, type BlockActionsPayload, type BlockKitContextValue, BlockKitProvider, type BlockKitProviderProps, type BlockProps, type BlockSuggestionPayload, Blocks, type BuildBlockActionsPayloadOptions, type BuildBlockSuggestionPayloadOptions, type BuildViewClosedPayloadOptions, type BuildViewSubmissionPayloadOptions, type Container, Element, type ElementProps, type ElementState, type EmojiOptions, HomeTab, type HomeTabProps, type HomeTabView, type Json, type LinkProps, type MentionRef, Message, type MessageApi, type MessageApp, type MessageBlockActionsPayload, type MessageContainer, type MessageProps, type MessageUpdate, Modal, type ModalProps, type ModalView, Mrkdwn, type MrkdwnProps, type OptionsResponse, type PayloadContext, type PayloadIdentity, type PayloadTeam, type PayloadUser, type Resolvers, type SlackAttachment, type SlackAttachmentField, type SlackMessageLike, type SlackReaction, type StackedView, type StateValues, type SubmitResult, type SuggestionOption, type Surface, SurfaceScope, type SurfaceScopeProps, type SurfaceScopeValue, Text, type TextObject, Tooltip, type TooltipProps, UserMention, type UserMentionProps, type UserProfile, VALIDATION_MESSAGES, View, type ViewBlockActionsPayload, type ViewContainer, type ViewLike, type ViewProps, type ViewResponseAction, type ViewsApi, blockComponents, buildBlockActionsPayload, buildBlockSuggestionPayload, buildViewClosedPayload, buildViewSubmissionPayload, elementComponents, useBlockKit, useContainer, useLiveView, validateView };
package/dist/index.js CHANGED
@@ -865,7 +865,7 @@ function newHash() {
865
865
  return `${Math.floor(Date.now() / 1e3)}.${Math.random().toString(36).slice(2, 10)}`;
866
866
  }
867
867
  function BlockKitProvider(props) {
868
- const { children, surface = "message", onAction, onPayload, onSubmit, onClose, onOptions, onStateChange, resolvers, emoji, errors, timeZone, identity, theme } = props;
868
+ const { children, surface = "message", onAction, onPayload, onSubmit, onClose, onOptions, onStateChange, resolvers, linkComponent, mentionHref, emoji, errors, timeZone, identity, theme } = props;
869
869
  const [state, setState] = useState({});
870
870
  const stateRef = useRef(state);
871
871
  const containerRef = useRef(void 0);
@@ -1041,6 +1041,8 @@ function BlockKitProvider(props) {
1041
1041
  onSubmit,
1042
1042
  onClose,
1043
1043
  resolvers: resolvers ?? {},
1044
+ linkComponent,
1045
+ mentionHref,
1044
1046
  emoji: emoji ?? {},
1045
1047
  errors: stackedView?.errors ?? errors ?? {},
1046
1048
  timeZone,
@@ -1061,6 +1063,8 @@ function BlockKitProvider(props) {
1061
1063
  onSubmit,
1062
1064
  onClose,
1063
1065
  resolvers,
1066
+ linkComponent,
1067
+ mentionHref,
1064
1068
  emoji,
1065
1069
  errors,
1066
1070
  timeZone,
@@ -1228,6 +1232,13 @@ function actionTs(now = Date.now()) {
1228
1232
  return (now / 1e3).toFixed(6);
1229
1233
  }
1230
1234
  //#endregion
1235
+ //#region src/useClientLayoutEffect.ts
1236
+ /**
1237
+ * `useLayoutEffect` in the browser and `useEffect` on the server, where neither runs. React 18
1238
+ * warns about every `useLayoutEffect` it meets while server rendering; React 19 doesn't.
1239
+ */
1240
+ const useClientLayoutEffect = typeof document === "undefined" ? useEffect : useLayoutEffect;
1241
+ //#endregion
1231
1242
  //#region src/Tooltip.tsx
1232
1243
  /** How long the pointer rests on an anchor before its tooltip shows. */
1233
1244
  const SHOW_DELAY_MS = 300;
@@ -1245,7 +1256,7 @@ function Tooltip({ label, children, placement = "top" }) {
1245
1256
  const tipRef = useRef(null);
1246
1257
  const timer = useRef(void 0);
1247
1258
  useEffect(() => () => clearTimeout(timer.current), []);
1248
- useLayoutEffect(() => {
1259
+ useClientLayoutEffect(() => {
1249
1260
  if (!open) {
1250
1261
  setPosition(null);
1251
1262
  return;
@@ -5599,6 +5610,44 @@ function Emoji({ name, skinTone, size = 22 }) {
5599
5610
  });
5600
5611
  }
5601
5612
  //#endregion
5613
+ //#region src/Link.tsx
5614
+ /**
5615
+ * A link from the payload, rendered by the provider's `linkComponent` when it has one. Without an
5616
+ * `href` (a payload missing its URL) it stays a plain `<a>`, so the app's component always gets one.
5617
+ */
5618
+ function Link({ href, children, ...props }) {
5619
+ const { linkComponent: Component } = useBlockKit();
5620
+ if (Component && href !== void 0) return /* @__PURE__ */ jsx(Component, {
5621
+ href,
5622
+ ...props,
5623
+ children
5624
+ });
5625
+ return /* @__PURE__ */ jsx("a", {
5626
+ href,
5627
+ ...props,
5628
+ children
5629
+ });
5630
+ }
5631
+ /**
5632
+ * A resolved channel or user group mention: a link to the app's page for it when `mentionHref`
5633
+ * returns one, otherwise the plain pill. Both keep the pill's class, so they look the same.
5634
+ */
5635
+ function MentionLink({ type, id, children }) {
5636
+ const { mentionHref } = useBlockKit();
5637
+ const href = mentionHref?.({
5638
+ type,
5639
+ id
5640
+ });
5641
+ return href === void 0 ? /* @__PURE__ */ jsx("span", {
5642
+ className: "sbk-mention",
5643
+ children
5644
+ }) : /* @__PURE__ */ jsx(Link, {
5645
+ className: "sbk-mention",
5646
+ href,
5647
+ children
5648
+ });
5649
+ }
5650
+ //#endregion
5602
5651
  //#region src/UserMention.tsx
5603
5652
  function localTime(timeZone) {
5604
5653
  try {
@@ -5617,13 +5666,13 @@ function localTime(timeZone) {
5617
5666
  * `resolvers.userProfile` and falling back to the display name alone.
5618
5667
  */
5619
5668
  function UserMention({ id, name }) {
5620
- const { resolvers, theme } = useBlockKit();
5669
+ const { resolvers, theme, mentionHref } = useBlockKit();
5621
5670
  const [open, setOpen] = useState(false);
5622
5671
  const [position, setPosition] = useState(null);
5623
5672
  const anchorRef = useRef(null);
5624
5673
  const cardRef = useRef(null);
5625
5674
  const cardId = useId();
5626
- useLayoutEffect(() => {
5675
+ useClientLayoutEffect(() => {
5627
5676
  if (!open) {
5628
5677
  setPosition(null);
5629
5678
  return;
@@ -5668,6 +5717,10 @@ function UserMention({ id, name }) {
5668
5717
  }
5669
5718
  const profile = open ? resolvers.userProfile?.(id) ?? { name } : void 0;
5670
5719
  const time = profile?.timeZone ? localTime(profile.timeZone) : void 0;
5720
+ const href = profile ? mentionHref?.({
5721
+ type: "user",
5722
+ id
5723
+ }) : void 0;
5671
5724
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("span", {
5672
5725
  ref: anchorRef,
5673
5726
  className: "sbk-mention sbk-mention--user",
@@ -5707,7 +5760,11 @@ function UserMention({ id, name }) {
5707
5760
  children: [
5708
5761
  /* @__PURE__ */ jsxs("div", {
5709
5762
  className: "sbk-profile-card__name",
5710
- children: [profile.name, profile.pronouns && /* @__PURE__ */ jsx("span", {
5763
+ children: [href === void 0 ? profile.name : /* @__PURE__ */ jsx(Link, {
5764
+ className: "sbk-profile-card__name-link",
5765
+ href,
5766
+ children: profile.name
5767
+ }), profile.pronouns && /* @__PURE__ */ jsx("span", {
5711
5768
  className: "sbk-profile-card__pronouns",
5712
5769
  children: profile.pronouns
5713
5770
  })]
@@ -5767,7 +5824,7 @@ function renderNode(node, resolvers, timeZone, emojiSize) {
5767
5824
  className: "sbk-mrkdwn__quote",
5768
5825
  children: renderNodes(node.children, resolvers, timeZone, emojiSize)
5769
5826
  });
5770
- case "link": return /* @__PURE__ */ jsx("a", {
5827
+ case "link": return /* @__PURE__ */ jsx(Link, {
5771
5828
  className: "sbk-link",
5772
5829
  href: node.url,
5773
5830
  target: "_blank",
@@ -5783,8 +5840,9 @@ function renderNode(node, resolvers, timeZone, emojiSize) {
5783
5840
  }
5784
5841
  case "usergroup": {
5785
5842
  const name = resolvers.usergroup?.(node.id);
5786
- return name ? /* @__PURE__ */ jsxs("span", {
5787
- className: "sbk-mention",
5843
+ return name ? /* @__PURE__ */ jsxs(MentionLink, {
5844
+ type: "usergroup",
5845
+ id: node.id,
5788
5846
  children: ["@", name]
5789
5847
  }) : /* @__PURE__ */ jsx("span", {
5790
5848
  className: "sbk-mention--loading",
@@ -5793,8 +5851,9 @@ function renderNode(node, resolvers, timeZone, emojiSize) {
5793
5851
  }
5794
5852
  case "channel": {
5795
5853
  const name = resolvers.channel?.(node.id) ?? node.label;
5796
- return name ? /* @__PURE__ */ jsxs("span", {
5797
- className: "sbk-mention",
5854
+ return name ? /* @__PURE__ */ jsxs(MentionLink, {
5855
+ type: "channel",
5856
+ id: node.id,
5798
5857
  children: ["#", name]
5799
5858
  }) : /* @__PURE__ */ jsxs("span", {
5800
5859
  className: "sbk-mention--private",
@@ -5807,7 +5866,7 @@ function renderNode(node, resolvers, timeZone, emojiSize) {
5807
5866
  });
5808
5867
  case "date": {
5809
5868
  const text = formatSlackDate(node.timestamp, node.format, { timeZone });
5810
- return node.url ? /* @__PURE__ */ jsx("a", {
5869
+ return node.url ? /* @__PURE__ */ jsx(Link, {
5811
5870
  className: "sbk-link",
5812
5871
  href: node.url,
5813
5872
  target: "_blank",
@@ -5979,7 +6038,9 @@ function Button({ element, blockId }) {
5979
6038
  * Slack's `focus_on_load`: the element takes focus when its modal or Home tab opens. Slack ignores
5980
6039
  * it in messages and allows it on only one element per view.
5981
6040
  *
5982
- * Pass the element's own ref when it already has one; otherwise attach the returned ref.
6041
+ * Pass the element's own ref when it already has one; otherwise attach the returned ref. The return
6042
+ * type is `useRef`'s own, which differs between @types/react 18 and 19, so it fits a `ref` prop in
6043
+ * both.
5983
6044
  */
5984
6045
  function useFocusOnLoad(element, existing) {
5985
6046
  const { surface } = useBlockKit();
@@ -6247,10 +6308,10 @@ function Popover({ anchorRef, onDismiss, gap = DEFAULT_GAP, children }) {
6247
6308
  const layerRef = useRef(null);
6248
6309
  const [position, setPosition] = useState(null);
6249
6310
  const dismiss = useRef(onDismiss);
6250
- useLayoutEffect(() => {
6311
+ useClientLayoutEffect(() => {
6251
6312
  dismiss.current = onDismiss;
6252
6313
  });
6253
- useLayoutEffect(() => {
6314
+ useClientLayoutEffect(() => {
6254
6315
  function place() {
6255
6316
  const anchor = anchorRef.current;
6256
6317
  const layer = layerRef.current;
@@ -8179,7 +8240,8 @@ function Collapse({ open, className, children }) {
8179
8240
  const [shown, setShown] = useState(open);
8180
8241
  if (open && !shown) setShown(true);
8181
8242
  const was = useRef(open);
8182
- useLayoutEffect(() => {
8243
+ useClientLayoutEffect(() => {
8244
+ ref.current?.toggleAttribute("inert", !open);
8183
8245
  if (was.current === open) return;
8184
8246
  was.current = open;
8185
8247
  const el = ref.current;
@@ -8197,7 +8259,6 @@ function Collapse({ open, className, children }) {
8197
8259
  return /* @__PURE__ */ jsx("div", {
8198
8260
  ref,
8199
8261
  className: className ? `sbk-collapse ${className}` : "sbk-collapse",
8200
- inert: !open,
8201
8262
  onTransitionEnd: (e) => {
8202
8263
  if (e.target !== e.currentTarget || e.propertyName !== "height") return;
8203
8264
  if (open) e.currentTarget.style.height = "";
@@ -8355,7 +8416,7 @@ function RichTextMiniSection({ section }) {
8355
8416
  function RichTextMiniLeaf({ element }) {
8356
8417
  if (element.type === "link") {
8357
8418
  const text = typeof element.text === "string" ? element.text : String(element.url ?? "");
8358
- return /* @__PURE__ */ jsx("a", {
8419
+ return /* @__PURE__ */ jsx(Link, {
8359
8420
  className: "sbk-rtmini__link",
8360
8421
  href: typeof element.url === "string" ? element.url : void 0,
8361
8422
  target: "_blank",
@@ -8390,7 +8451,7 @@ function DataTableCell({ cell }) {
8390
8451
  if (!cell || typeof cell !== "object") return null;
8391
8452
  switch (cell.type) {
8392
8453
  case "rich_text": return /* @__PURE__ */ jsx(RichTextMini, { value: cell });
8393
- case "url": return /* @__PURE__ */ jsx("a", {
8454
+ case "url": return /* @__PURE__ */ jsx(Link, {
8394
8455
  className: "sbk-rtmini__link",
8395
8456
  href: typeof cell.url === "string" ? cell.url : void 0,
8396
8457
  target: "_blank",
@@ -9703,7 +9764,7 @@ function renderInlineNode(node) {
9703
9764
  });
9704
9765
  case "link": {
9705
9766
  const link = node;
9706
- return /* @__PURE__ */ jsx("a", {
9767
+ return /* @__PURE__ */ jsx(Link, {
9707
9768
  className: "sbk-link",
9708
9769
  href: link.url,
9709
9770
  target: "_blank",
@@ -9886,7 +9947,7 @@ function TaskRow({ task }) {
9886
9947
  }) : null,
9887
9948
  sources.length > 0 ? /* @__PURE__ */ jsx("ul", {
9888
9949
  className: "sbk-plan__task-sources",
9889
- children: sources.map((source, i) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("a", {
9950
+ children: sources.map((source, i) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(Link, {
9890
9951
  className: "sbk-plan__task-source-link",
9891
9952
  href: source.url,
9892
9953
  target: "_blank",
@@ -10023,7 +10084,7 @@ function renderLeaf(el, ctx, emojiSize, isFirst = false) {
10023
10084
  content = renderTextWithBreaks(el.text, isFirst);
10024
10085
  break;
10025
10086
  case "link":
10026
- content = /* @__PURE__ */ jsx("a", {
10087
+ content = /* @__PURE__ */ jsx(Link, {
10027
10088
  className: "sbk-link",
10028
10089
  href: el.url,
10029
10090
  target: "_blank",
@@ -10053,8 +10114,9 @@ function renderLeaf(el, ctx, emojiSize, isFirst = false) {
10053
10114
  }
10054
10115
  case "usergroup": {
10055
10116
  const name = ctx.resolvers.usergroup?.(el.usergroup_id);
10056
- content = name ? /* @__PURE__ */ jsxs("span", {
10057
- className: "sbk-mention",
10117
+ content = name ? /* @__PURE__ */ jsxs(MentionLink, {
10118
+ type: "usergroup",
10119
+ id: el.usergroup_id,
10058
10120
  children: ["@", name]
10059
10121
  }) : /* @__PURE__ */ jsx("span", {
10060
10122
  className: "sbk-mention--loading",
@@ -10064,8 +10126,9 @@ function renderLeaf(el, ctx, emojiSize, isFirst = false) {
10064
10126
  }
10065
10127
  case "channel": {
10066
10128
  const name = ctx.resolvers.channel?.(el.channel_id);
10067
- content = name ? /* @__PURE__ */ jsxs("span", {
10068
- className: "sbk-mention",
10129
+ content = name ? /* @__PURE__ */ jsxs(MentionLink, {
10130
+ type: "channel",
10131
+ id: el.channel_id,
10069
10132
  children: ["#", name]
10070
10133
  }) : /* @__PURE__ */ jsxs("span", {
10071
10134
  className: "sbk-mention--private",
@@ -10081,7 +10144,7 @@ function renderLeaf(el, ctx, emojiSize, isFirst = false) {
10081
10144
  break;
10082
10145
  case "date": {
10083
10146
  const text = formatSlackDate(el.timestamp, el.format, { timeZone: ctx.timeZone });
10084
- content = el.url ? /* @__PURE__ */ jsx("a", {
10147
+ content = el.url ? /* @__PURE__ */ jsx(Link, {
10085
10148
  className: "sbk-link",
10086
10149
  href: el.url,
10087
10150
  target: "_blank",
@@ -10155,7 +10218,7 @@ function renderTextWithBreaks(value, isFirst) {
10155
10218
  */
10156
10219
  function renderPreformattedLines(elements) {
10157
10220
  return elements.map((el, i) => {
10158
- if (el.type === "link") return /* @__PURE__ */ jsx(Fragment$1, { children: applyStyle(/* @__PURE__ */ jsx("a", {
10221
+ if (el.type === "link") return /* @__PURE__ */ jsx(Fragment$1, { children: applyStyle(/* @__PURE__ */ jsx(Link, {
10159
10222
  className: "sbk-link",
10160
10223
  href: el.url,
10161
10224
  target: "_blank",
@@ -10381,7 +10444,7 @@ function TaskCard({ block }) {
10381
10444
  children: /* @__PURE__ */ jsx(RichTextMini, { value: block.output })
10382
10445
  }) : null, sources.length > 0 ? /* @__PURE__ */ jsx("ul", {
10383
10446
  className: "sbk-task-card__sources",
10384
- children: sources.map((source, i) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("a", {
10447
+ children: sources.map((source, i) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(Link, {
10385
10448
  className: "sbk-task-card__source-link",
10386
10449
  href: source.url,
10387
10450
  target: "_blank",
@@ -10467,7 +10530,7 @@ function Video({ block }) {
10467
10530
  }),
10468
10531
  title && /* @__PURE__ */ jsxs("div", {
10469
10532
  className: "sbk-video__title",
10470
- children: [titleUrl ? /* @__PURE__ */ jsx("a", {
10533
+ children: [titleUrl ? /* @__PURE__ */ jsx(Link, {
10471
10534
  href: titleUrl,
10472
10535
  target: "_blank",
10473
10536
  rel: "noreferrer",
@@ -10478,7 +10541,7 @@ function Video({ block }) {
10478
10541
  children: /* @__PURE__ */ jsx(CaretDownIcon, {})
10479
10542
  })]
10480
10543
  }),
10481
- /* @__PURE__ */ jsxs("a", {
10544
+ /* @__PURE__ */ jsxs(Link, {
10482
10545
  className: "sbk-video__frame",
10483
10546
  href: typeof json.video_url === "string" ? json.video_url : titleUrl,
10484
10547
  target: "_blank",
@@ -10745,7 +10808,7 @@ function Attachment({ attachment }) {
10745
10808
  className: "sbk-attachment__author-icon",
10746
10809
  src: attachment.author_icon,
10747
10810
  alt: ""
10748
- }), attachment.author_link ? /* @__PURE__ */ jsx("a", {
10811
+ }), attachment.author_link ? /* @__PURE__ */ jsx(Link, {
10749
10812
  className: "sbk-link",
10750
10813
  href: attachment.author_link,
10751
10814
  target: "_blank",
@@ -10755,7 +10818,7 @@ function Attachment({ attachment }) {
10755
10818
  }),
10756
10819
  attachment.title && /* @__PURE__ */ jsx("div", {
10757
10820
  className: "sbk-attachment__title",
10758
- children: attachment.title_link ? /* @__PURE__ */ jsx("a", {
10821
+ children: attachment.title_link ? /* @__PURE__ */ jsx(Link, {
10759
10822
  className: "sbk-link",
10760
10823
  href: attachment.title_link,
10761
10824
  target: "_blank",