@nkootstra/block-kit 0.3.3 → 0.3.4

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
@@ -7059,19 +7059,26 @@ const FORMATTING_GROUPS = [
7059
7059
  }
7060
7060
  ]
7061
7061
  ];
7062
- /** The (inert) formatting toolbar Slack shows above a modal's rich-text editor; its buttons stay
7063
- * dimmed because they only act on a selection in Slack's own editor. */
7062
+ /** The formatting bar Slack shows above a modal's rich-text editor. The editor here is plain text,
7063
+ * so its buttons are there to look like Slack's but marked unavailable; they dim until the editor
7064
+ * has focus, as Slack's do. */
7064
7065
  function RichTextToolbar() {
7065
7066
  return /* @__PURE__ */ jsx("div", {
7066
7067
  className: "sbk-rich-text-input__toolbar",
7067
- "aria-hidden": "true",
7068
- children: FORMATTING_GROUPS.map((group, i) => /* @__PURE__ */ jsxs(Fragment$1, { children: [i > 0 && /* @__PURE__ */ jsx("span", { className: "sbk-rich-text-input__separator" }), group.map((icon) => /* @__PURE__ */ jsx("span", {
7068
+ role: "toolbar",
7069
+ "aria-orientation": "horizontal",
7070
+ "aria-label": "Formatting",
7071
+ children: FORMATTING_GROUPS.map((group, i) => /* @__PURE__ */ jsxs(Fragment$1, { children: [i > 0 && /* @__PURE__ */ jsx("span", { className: "sbk-rich-text-input__separator" }), group.map((icon) => /* @__PURE__ */ jsx("button", {
7072
+ type: "button",
7069
7073
  className: "sbk-rich-text-input__tool",
7070
- title: icon.label,
7074
+ "aria-label": icon.label,
7075
+ "aria-disabled": "true",
7076
+ tabIndex: -1,
7071
7077
  children: /* @__PURE__ */ jsx("svg", {
7072
7078
  viewBox: "0 0 20 20",
7073
7079
  width: "18",
7074
7080
  height: "18",
7081
+ "aria-hidden": "true",
7075
7082
  children: /* @__PURE__ */ jsx("path", {
7076
7083
  fill: "currentColor",
7077
7084
  fillRule: icon.evenOdd ? "evenodd" : void 0,
@@ -7082,11 +7089,52 @@ function RichTextToolbar() {
7082
7089
  }, icon.label))] }, group[0]?.label))
7083
7090
  });
7084
7091
  }
7092
+ /** Slack's "Aa" (show formatting) and emoji glyphs, 20-unit paths as Slack ships them. */
7093
+ const FORMATTING_ICON = "M6.941 3.952c-.459-1.378-2.414-1.363-2.853.022l-4.053 12.8a.75.75 0 0 0 1.43.452l1.101-3.476h6.06l1.163 3.487a.75.75 0 1 0 1.423-.474zm1.185 8.298L5.518 4.427 3.041 12.25zm6.198-5.537a4.74 4.74 0 0 1 3.037-.081A3.74 3.74 0 0 1 20 10.208V17a.75.75 0 0 1-1.5 0v-.745a8 8 0 0 1-2.847 1.355 3 3 0 0 1-3.15-1.143C10.848 14.192 12.473 11 15.287 11H18.5v-.792c0-.984-.641-1.853-1.581-2.143a3.24 3.24 0 0 0-2.077.056l-.242.089a2.22 2.22 0 0 0-1.34 1.382l-.048.145a.75.75 0 0 1-1.423-.474l.048-.145a3.72 3.72 0 0 1 2.244-2.315zM18.5 12.5h-3.213c-1.587 0-2.504 1.801-1.57 3.085.357.491.98.717 1.572.57a6.5 6.5 0 0 0 2.47-1.223l.741-.593z";
7094
+ const EMOJI_ICON = "M2.5 10a7.5 7.5 0 1 1 15 0 7.5 7.5 0 0 1-15 0M10 1a9 9 0 1 0 0 18 9 9 0 0 0 0-18M7.5 9.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3M14 8a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0m-6.385 3.766a.75.75 0 1 0-1.425.468C6.796 14.08 8.428 15 10.027 15s3.23-.92 3.838-2.766a.75.75 0 1 0-1.425-.468c-.38 1.155-1.38 1.734-2.413 1.734s-2.032-.58-2.412-1.734";
7095
+ function Glyph({ d }) {
7096
+ return /* @__PURE__ */ jsx("svg", {
7097
+ viewBox: "0 0 20 20",
7098
+ width: "18",
7099
+ height: "18",
7100
+ "aria-hidden": "true",
7101
+ children: /* @__PURE__ */ jsx("path", {
7102
+ fill: "currentColor",
7103
+ fillRule: "evenodd",
7104
+ clipRule: "evenodd",
7105
+ d
7106
+ })
7107
+ });
7108
+ }
7109
+ /** The row Slack draws under the editor: "Aa" shows or hides the formatting bar (underlined while
7110
+ * it's shown), and an emoji button, unavailable until block-kit has an emoji picker. */
7111
+ function RichTextComposerActions({ formatting, onToggleFormatting }) {
7112
+ return /* @__PURE__ */ jsxs("div", {
7113
+ className: "sbk-rich-text-input__footer",
7114
+ role: "toolbar",
7115
+ "aria-orientation": "horizontal",
7116
+ "aria-label": "Composer actions",
7117
+ children: [/* @__PURE__ */ jsx("button", {
7118
+ type: "button",
7119
+ className: "sbk-rich-text-input__action sbk-rich-text-input__action--formatting",
7120
+ "aria-label": "Show formatting",
7121
+ "aria-pressed": formatting,
7122
+ onClick: onToggleFormatting,
7123
+ children: /* @__PURE__ */ jsx(Glyph, { d: FORMATTING_ICON })
7124
+ }), /* @__PURE__ */ jsx("button", {
7125
+ type: "button",
7126
+ className: "sbk-rich-text-input__action",
7127
+ "aria-label": "Emoji",
7128
+ "aria-disabled": "true",
7129
+ tabIndex: -1,
7130
+ children: /* @__PURE__ */ jsx(Glyph, { d: EMOJI_ICON })
7131
+ })]
7132
+ });
7133
+ }
7085
7134
  //#endregion
7086
7135
  //#region src/elements/RichTextInput.tsx
7087
- /** Wraps plain text into the `rich_text` block shape Slack uses for this element's value. A
7088
- * full WYSIWYG (bold/lists/links) is out of scope; the brief allows a contentEditable here, under
7089
- * Slack's (inert) formatting toolbar. */
7136
+ /** Wraps plain text into the `rich_text` block shape Slack uses for this element's value. The
7137
+ * editor is plain text; Slack's formatting bar and composer row are drawn around it for parity. */
7090
7138
  function toRichText(text) {
7091
7139
  return {
7092
7140
  type: "rich_text",
@@ -7115,6 +7163,8 @@ function RichTextInput({ element, blockId }) {
7115
7163
  const invalid = useInvalidProps();
7116
7164
  const actionId = element.action_id ?? "";
7117
7165
  const initialText = fromRichText(element.initial_value);
7166
+ const [formatting, setFormatting] = useState(true);
7167
+ const [empty, setEmpty] = useState(initialText === "");
7118
7168
  useEffect(() => {
7119
7169
  if (element.initial_value) setValue(blockId, actionId, {
7120
7170
  type: "rich_text_input",
@@ -7122,7 +7172,9 @@ function RichTextInput({ element, blockId }) {
7122
7172
  });
7123
7173
  }, []);
7124
7174
  function onInput() {
7125
- const richText = toRichText(ref.current?.innerText ?? "");
7175
+ const text = ref.current?.innerText ?? "";
7176
+ setEmpty(text.trim() === "");
7177
+ const richText = toRichText(text);
7126
7178
  setValue(blockId, actionId, {
7127
7179
  type: "rich_text_input",
7128
7180
  rich_text_value: richText
@@ -7134,25 +7186,39 @@ function RichTextInput({ element, blockId }) {
7134
7186
  rich_text_value: richText
7135
7187
  });
7136
7188
  }
7189
+ const placeholder = element.placeholder?.text;
7137
7190
  return /* @__PURE__ */ jsxs("div", {
7138
7191
  className: "sbk-rich-text-input",
7139
- children: [/* @__PURE__ */ jsx(RichTextToolbar, {}), /* @__PURE__ */ jsx("div", {
7140
- ref,
7141
- className: "sbk-rich-text-input__editor",
7142
- contentEditable: true,
7143
- suppressContentEditableWarning: true,
7144
- role: "textbox",
7145
- "aria-multiline": "true",
7146
- ...invalid,
7147
- "aria-label": element.placeholder?.text ?? actionId,
7148
- "data-placeholder": element.placeholder?.text,
7149
- onInput,
7150
- style: {
7151
- minHeight: element.min_lines ? `${element.min_lines * 22}px` : void 0,
7152
- maxHeight: element.max_lines ? `${element.max_lines * 22}px` : void 0
7153
- },
7154
- children: initialText
7155
- })]
7192
+ children: [
7193
+ formatting && /* @__PURE__ */ jsx(RichTextToolbar, {}),
7194
+ /* @__PURE__ */ jsxs("div", {
7195
+ className: "sbk-rich-text-input__body",
7196
+ children: [/* @__PURE__ */ jsx("div", {
7197
+ ref,
7198
+ className: "sbk-rich-text-input__editor",
7199
+ contentEditable: true,
7200
+ suppressContentEditableWarning: true,
7201
+ role: "textbox",
7202
+ "aria-multiline": "true",
7203
+ ...invalid,
7204
+ "aria-label": placeholder ?? actionId,
7205
+ onInput,
7206
+ style: {
7207
+ minHeight: element.min_lines ? `${element.min_lines * 22}px` : void 0,
7208
+ maxHeight: element.max_lines ? `${element.max_lines * 22}px` : void 0
7209
+ },
7210
+ children: initialText
7211
+ }), placeholder && empty && /* @__PURE__ */ jsx("div", {
7212
+ className: "sbk-rich-text-input__placeholder",
7213
+ "aria-hidden": "true",
7214
+ children: placeholder
7215
+ })]
7216
+ }),
7217
+ /* @__PURE__ */ jsx(RichTextComposerActions, {
7218
+ formatting,
7219
+ onToggleFormatting: () => setFormatting((shown) => !shown)
7220
+ })
7221
+ ]
7156
7222
  });
7157
7223
  }
7158
7224
  //#endregion
@@ -10113,7 +10179,23 @@ function renderTopLevel(elements, ctx) {
10113
10179
  i++;
10114
10180
  }
10115
10181
  const forest = buildListForest(run);
10116
- for (const group of forest) nodes.push(renderListGroup(group, ctx, key++, 0));
10182
+ const lists = forest.map((group) => renderListGroup(group, ctx, key++, 0));
10183
+ if (!forest.some((group) => group.border)) {
10184
+ nodes.push(...lists);
10185
+ continue;
10186
+ }
10187
+ const quoted = [];
10188
+ while (i < elements.length && elements[i]?.type === "rich_text_quote") {
10189
+ quoted.push(/* @__PURE__ */ jsx("div", {
10190
+ className: "sbk-rich-text__quote-text",
10191
+ children: renderSectionChildren(elements[i].elements, ctx)
10192
+ }, key++));
10193
+ i++;
10194
+ }
10195
+ nodes.push(/* @__PURE__ */ jsxs("blockquote", {
10196
+ className: "sbk-rich-text__quote sbk-rich-text__quote--group",
10197
+ children: [lists, quoted]
10198
+ }, key++));
10117
10199
  continue;
10118
10200
  }
10119
10201
  switch (el.type) {