Repository: speee/jsx-slack (ts)

Issue:
maxSelectedItems attribute is no longer working
`maxSelectedItems` attribute for multiple select menu seems to no longer work.

`max_selected_items` field is still defined in [`@slack/types`](https://github.com/slackapi/node-slack-sdk/blob/95a561876d734c51d69824bf75cf8b64b57d6908/packages/types/src/index.ts#L134) and
Slack Block Kit Builder does not throw error even if defined `max_selected_items` in JSON, but the actual component ignores this restriction. In addition, all descriptions about `max_selected_items` field are removed from [the documentation of Slack API](https://api.slack.com/reference/block-kit/block-elements#multi_select).

Pull request description:
Revert removal of maxSelectedItems attribute
This reverts commit 7d028fee68ba7146ea7db651d588997cedb971ca (#94), reversing changes made to 5a46260288db11a8615a478c1107d214398485d1.

Closes #93 again.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3b035f0..c22e390 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,10 +6,6 @@
 
 - Upgrade dependent packages to the latest version ([#92](https://github.com/speee/jsx-slack/pull/92))
 
-### Removed
-
-- `maxSelectedItems` prop in multiple select components (Slack has no longer supported `max_selected_items` field) ([#93](https://github.com/speee/jsx-slack/issues/93), [#94](https://github.com/speee/jsx-slack/pull/94))
-
 ## v0.12.0 - 2019-11-22
 
 ### Added
diff --git a/demo/schema.js b/demo/schema.js
index cfaa15b..10a7877 100644
--- a/demo/schema.js
+++ b/demo/schema.js
@@ -21,6 +21,7 @@ const blockInteractiveCommonAttrs = {
 }
 const multipleSelectAttrs = {
   multiple: [],
+  maxSelectedItems: null,
 }
 const inputIntrinsicAttrs = {
   label: null,
diff --git a/docs/block-elements.md b/docs/block-elements.md
index c550894..e5d76fc 100644
--- a/docs/block-elements.md
+++ b/docs/block-elements.md
@@ -55,7 +55,6 @@ A menu element with static options passed by `<Option>` or `<Optgroup>`. It has
 
 - `name` / `actionId` (optional): An identifier for the action.
 - `placeholder` (optional): A plain text to be shown at first.
-- `multiple` (optional): A boolean value that shows whether [multiple options can be selected][multiple select].
 - `value` (optional): A value of item to show initially. It must choose value from defined `<Option>` elements in children. It can pass multiple string values by array when `multiple` is enabled.
 - `confirm` (optional): [`<Confirm>` element](#confirm) to show confirmation dialog.
 
@@ -89,6 +88,11 @@ By defining `multiple` attribute, you also can provide [the selectable menu from
 
 > :warning: **Slack does not allow to place the multi-select menu in `Actions` block.** So jsx-slack throws error if you're trying to use `multiple` attribute in the children of `<Actions>`.
 
+##### Props for [multiple select]
+
+- `multiple` (optional): A boolean value that shows whether multiple options can be selected.
+- `maxSelectedItems` (optional): A maximum number of items to allow selected.
+
 #### As [an input component for modal](#input-components-for-modal)
 
 In `<Modal>` container, select-like components will work as [the input component for modal](#input-components-for-modal) by passing suitable props such as required `label` prop. Thereby it would allow natural templating like as HTML form.
@@ -195,11 +199,15 @@ It requires setup JSON entry URL in your Slack app. [Learn about external source
 
 - `name` / `actionId` (optional): An identifier for the action.
 - `placeholder` (optional): A plain text to be shown at first.
-- `multiple` (optional): A boolean value that shows whether [multiple options can be selected][multiple select].
 - `initialOption` (optional): An initial option exactly matched to provided options from external source. It allows raw JSON object or `<Option>`. It can pass multiple options by array when `multiple` is enabled.
 - `minQueryLength` (optional): A length of typed characters to begin JSON request.
 - `confirm` (optional): [`<Confirm>` element](#confirm) to show confirmation dialog.
 
+##### Props for [multiple select]
+
+- `multiple` (optional): A boolean value that shows whether multiple options can be selected.
+- `maxSelectedItems` (optional): A maximum number of items to allow selected.
+
 ##### Props for modal's input
 
 - `label` (**required**): The label string for the element.
@@ -240,10 +248,14 @@ A select menu with options consisted of users in the current workspace.
 
 - `name` / `actionId` (optional): An identifier for the action.
 - `placeholder` (optional): A plain text to be shown at first.
-- `multiple` (optional): A boolean value that shows whether [multiple options can be selected][multiple select].
 - `initialUser` (optional): The initial user ID. It can pass multiple string values by array when `multiple` is enabled.
 - `confirm` (optional): [`<Confirm>` element](#confirm) to show confirmation dialog.
 
+##### Props for [multiple select]
+
+- `multiple` (optional): A boolean value that shows whether multiple options can be selected.
+- `maxSelectedItems` (optional): A maximum number of items to allow selected.
+
 ##### Props for modal's input
 
 - `label` (**required**): The label string for the element.
@@ -259,10 +271,14 @@ A select menu with options consisted of any type of conversations in the current
 
 - `name` / `actionId` (optional): An identifier for the action.
 - `placeholder` (optional): A plain text to be shown at first.
-- `multiple` (optional): A boolean value that shows whether [multiple options can be selected][multiple select].
 - `initialConversation` (optional): The initial conversation ID. It can pass multiple string values by array when `multiple` is enabled.
 - `confirm` (optional): [`<Confirm>` element](#confirm) to show confirmation dialog.
 
+##### Props for [multiple select]
+
+- `multiple` (optional): A boolean value that shows whether multiple options can be selected.
+- `maxSelectedItems` (optional): A maximum number of items to allow selected.
+
 ##### Props for modal's input
 
 - `label` (**required**): The label string for the element.
@@ -278,10 +294,14 @@ A select menu with options consisted of public channels in the current workspace
 
 - `name` / `actionId` (optional): An identifier for the action.
 - `placeholder` (optional): A plain text to be shown at first.
-- `multiple` (optional): A boolean value that shows whether [multiple options can be selected][multiple select].
 - `initialChannel` (optional): The initial channel ID. It can pass multiple string values by array when `multiple` is enabled.
 - `confirm` (optional): [`<Confirm>` element](#confirm) to show confirmation dialog.
 
+##### Props for [multiple select]
+
+- `multiple` (optional): A boolean value that shows whether multiple options can be selected.
+- `maxSelectedItems` (optional): A maximum number of items to allow selected.
+
 ##### Props for modal's input
 
 - `label` (**required**): The label string for the element.
diff --git a/src/block-kit/elements/Select.tsx b/src/block-kit/elements/Select.tsx
index f4568fa..270773e 100644
--- a/src/block-kit/elements/Select.tsx
+++ b/src/block-kit/elements/Select.tsx
@@ -39,6 +39,7 @@ export interface SingleSelectPropsBase {
 
 export interface MultiSelectPropsBase
   extends Omit<SingleSelectPropsBase, 'multiple'> {
+  maxSelectedItems?: number
   multiple: true
 }
 
@@ -173,12 +174,15 @@ export const Optgroup: JSXSlack.FC<OptgroupProps> = props => (
 
 const baseProps = (
   props: SelectPropsBase
-): Pick<
-  SlackSelect | MultiSelect,
-  'action_id' | 'confirm' | 'placeholder'
-> => ({
+):
+  | Pick<SlackSelect, 'action_id' | 'confirm' | 'placeholder'>
+  | Pick<
+      MultiSelect,
+      'action_id' | 'confirm' | 'placeholder' | 'max_selected_items'
+    > => ({
   action_id: props.actionId || props.name,
   confirm: props.confirm ? JSXSlack(props.confirm) : undefined,
+  max_selected_items: coerceToInteger(props.maxSelectedItems),
   placeholder: props.placeholder ? plainText(props.placeholder) : undefined,
 })
 


Test patch:
diff --git a/test/block-kit/layout-blocks.tsx b/test/block-kit/layout-blocks.tsx
index 1389b8c..8cd3e9a 100644
--- a/test/block-kit/layout-blocks.tsx
+++ b/test/block-kit/layout-blocks.tsx
@@ -151,7 +151,7 @@ describe('Layout blocks', () => {
         <Blocks>
           <Section>
             Select
-            <Select multiple value={['a', 'c']}>
+            <Select multiple maxSelectedItems={2} value={['a', 'c']}>
               <Option value="a">a</Option>
               <Option value="b">b</Option>
               <Option value="c">c</Option>
@@ -161,17 +161,27 @@ describe('Layout blocks', () => {
       )
 
       expect(s.accessory.type).toBe('multi_static_select')
+      expect(s.accessory.max_selected_items).toBe(2)
       expect(s.accessory.initial_options).toHaveLength(2)
 
       // Multiple select for external sources
       for (const accessory of [
         <ExternalSelect
           multiple
+          maxSelectedItems={2}
           initialOption={<Option value="a">a</Option>}
         />,
-        <UsersSelect multiple initialUser="U00000000" />,
-        <ConversationsSelect multiple initialConversation={['C00000000']} />,
-        <ChannelsSelect multiple initialChannel="D00000000" />,
+        <UsersSelect multiple maxSelectedItems={2} initialUser="U00000000" />,
+        <ConversationsSelect
+          multiple
+          maxSelectedItems={2}
+          initialConversation={['C00000000']}
+        />,
+        <ChannelsSelect
+          multiple
+          maxSelectedItems={2}
+          initialChannel="D00000000"
+        />,
       ]) {
         const [ms] = JSXSlack(
           <Blocks>
@@ -180,6 +190,7 @@ describe('Layout blocks', () => {
         )
 
         expect(ms.accessory.type.startsWith('multi_')).toBe(true)
+        expect(ms.accessory.max_selected_items).toBe(2)
 
         const initialKey: any = Object.keys(ms.accessory).find(k =>
           k.startsWith('initial_')