Skip to content

Commit 3305129

Browse files
authored
Label and Select Menu support for Modals (#1377)
1 parent 71b7cd0 commit 3305129

File tree

13 files changed

+307
-20
lines changed

13 files changed

+307
-20
lines changed

src/Discord/Builders/Components/ActionRow.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
*/
2525
class ActionRow extends Layout
2626
{
27+
/** Usage of ActionRow in Modal is deprecated. Use `Component::Label` as the top-level container. */
2728
public const USAGE = ['Message', 'Modal'];
2829

2930
/**

src/Discord/Builders/Components/Component.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ abstract class Component implements JsonSerializable
4343
public const TYPE_SEPARATOR = 14;
4444
public const TYPE_CONTENT_INVENTORY_ENTRY = 16; // Not documented
4545
public const TYPE_CONTAINER = 17;
46+
public const TYPE_LABEL = 18;
4647

4748
/** @deprecated 7.4.0 Use `Component::TYPE_STRING_SELECT` */
4849
public const TYPE_SELECT_MENU = 3;
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is a part of the DiscordPHP project.
7+
*
8+
* Copyright (c) 2015-present David Cole <[email protected]>
9+
*
10+
* This file is subject to the MIT license that is bundled
11+
* with this source code in the LICENSE.md file.
12+
*/
13+
14+
namespace Discord\Builders\Components;
15+
16+
use function Discord\poly_strlen;
17+
18+
/**
19+
* A Label is a top-level component.
20+
*
21+
* @link https://discord.com/developers/docs/components/reference#label
22+
*
23+
* @todo Update to match Discord's documentation upon public release.
24+
* @todo Update Label class to extend the relevant base class.
25+
* @todo Confirm if Label will be usable in Message components.
26+
*
27+
* @since 10.19.0
28+
*
29+
* @property int $type 18 for label component.
30+
* @property string $label The text for the label. Must be between 1 and 45 characters.
31+
* @property string|null $description Optional description for the label. Max 100 characters.
32+
* @property StringSelect|TextInput $component The component associated with the label.
33+
*/
34+
class Label extends ComponentObject
35+
{
36+
public const USAGE = ['Modal'];
37+
38+
/**
39+
* Component type.
40+
*
41+
* @var int
42+
*/
43+
protected $type = Component::TYPE_LABEL;
44+
45+
/**
46+
* The text for the label.
47+
*
48+
* @var string
49+
*/
50+
protected $label;
51+
52+
/**
53+
* Optional description for the label.
54+
*
55+
* @var string|null
56+
*/
57+
protected $description;
58+
59+
/**
60+
* The component associated with the label.
61+
*
62+
* @var StringSelect|TextInput
63+
*/
64+
protected $component;
65+
66+
/**
67+
* Creates a new label component.
68+
*
69+
* @param string $label The text for the label.
70+
* @param StringSelect|TextInput $component The component associated with the label.
71+
* @param string|null $description Optional description for the label.
72+
*
73+
* @return self
74+
*/
75+
public static function new(string $label, $component, ?string $description = null): self
76+
{
77+
$label_component = new self();
78+
79+
$label_component->setLabel($label);
80+
$label_component->setComponent($component);
81+
$label_component->setDescription($description);
82+
83+
return $label_component;
84+
}
85+
86+
/**
87+
* Sets the label text.
88+
*
89+
* @param string $label The text for the label. Must be between 1 and 45 characters.
90+
*
91+
* @return self
92+
*/
93+
public function setLabel(string $label): self
94+
{
95+
if (poly_strlen($label) === 0 || poly_strlen($label) > 45) {
96+
throw new \LengthException('Label must be between 1 and 45 in length.');
97+
}
98+
99+
$this->label = $label;
100+
101+
return $this;
102+
}
103+
104+
/**
105+
* Sets the description text.
106+
*
107+
* @param string|null $description The description for the label. Max 100 characters.
108+
*
109+
* @return self
110+
*/
111+
public function setDescription(?string $description = null): self
112+
{
113+
if (poly_strlen($description) === 0) {
114+
$description = null;
115+
}
116+
117+
if (poly_strlen($description) > 100) {
118+
throw new \LengthException('Description must be between 0 and 100 in length.');
119+
}
120+
121+
$this->description = $description;
122+
123+
return $this;
124+
}
125+
126+
/** Sets the component associated with the label.
127+
*
128+
* @param RoleSelect|UserSelect|MentionableSelect|StringSelect|TextInput $component The component associated with the label.
129+
*
130+
* @return self
131+
*/
132+
public function setComponent($component): self
133+
{
134+
$this->component = $component;
135+
136+
return $this;
137+
}
138+
139+
/**
140+
* @inheritDoc
141+
*/
142+
public function jsonSerialize(): array
143+
{
144+
$data = [
145+
'type' => $this->type,
146+
'label' => $this->label,
147+
'component' => $this->component,
148+
];
149+
150+
if (isset($this->description)) {
151+
$data['description'] = $this->description;
152+
}
153+
154+
return $data;
155+
}
156+
}

src/Discord/Builders/Components/MentionableSelect.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,18 @@ class MentionableSelect extends SelectMenu
3030
* @var int
3131
*/
3232
protected $type = Component::TYPE_MENTIONABLE_SELECT;
33+
34+
/**
35+
* Set if this component is required to be filled, default false. (Modal only)
36+
*
37+
* @param bool $required
38+
*
39+
* @return $this
40+
*/
41+
public function setRequired(bool $required): self
42+
{
43+
$this->required = $required;
44+
45+
return $this;
46+
}
3347
}

src/Discord/Builders/Components/RoleSelect.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,18 @@ class RoleSelect extends SelectMenu
3030
* @var int
3131
*/
3232
protected $type = Component::TYPE_ROLE_SELECT;
33+
34+
/**
35+
* Set if this component is required to be filled, default false. (Modal only)
36+
*
37+
* @param bool $required
38+
*
39+
* @return $this
40+
*/
41+
public function setRequired(bool $required): self
42+
{
43+
$this->required = $required;
44+
45+
return $this;
46+
}
3347
}

src/Discord/Builders/Components/SelectMenu.php

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,13 @@ abstract class SelectMenu extends Interactive
105105
*/
106106
protected $disabled;
107107

108+
/**
109+
* Whether the select menu is required. Defaults to true. (Modal only).
110+
*
111+
* @var bool|null
112+
*/
113+
protected $required;
114+
108115
/**
109116
* Callback used to listen for `INTERACTION_CREATE` events.
110117
*
@@ -298,7 +305,7 @@ public function setMaxValues(?int $max_values): self
298305
}
299306

300307
/**
301-
* Sets the select menus disabled state.
308+
* Sets the select menus disabled state. (Message only)
302309
*
303310
* @param bool $disabled
304311
*
@@ -534,24 +541,31 @@ public function jsonSerialize(): array
534541

535542
if (isset($this->min_values)) {
536543
if (isset($this->options) && $this->min_values > count($this->options)) {
537-
throw new \OutOfBoundsException('There are less options than the minimum number of options to be selected.');
544+
throw new \DomainException('There are less options than the minimum number of options to be selected.');
538545
}
539546

540547
$content['min_values'] = $this->min_values;
541548
}
542549

543-
if ($this->max_values) {
550+
if (isset($this->max_values) && $this->max_values) {
544551
if (isset($this->options) && $this->max_values > count($this->options)) {
545-
throw new \OutOfBoundsException('There are less options than the maximum number of options to be selected.');
552+
throw new \DomainException('There are less options than the maximum number of options to be selected.');
546553
}
547554

548555
$content['max_values'] = $this->max_values;
549556
}
550557

551-
if ($this->disabled) {
558+
if (isset($this->disabled) && $this->disabled) {
552559
$content['disabled'] = true;
553560
}
554561

562+
if (isset($this->required)) {
563+
$content['required'] = true;
564+
if ($this->min_values === null || $this->min_values === 0) {
565+
throw new \LengthException('Required select menus must have a minimum value greater than 0.');
566+
}
567+
}
568+
555569
return $content;
556570
}
557571
}

src/Discord/Builders/Components/StringSelect.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
*/
2424
class StringSelect extends SelectMenu
2525
{
26-
public const USAGE = ['Message'];
26+
public const USAGE = ['Message', 'Modal'];
2727

2828
/**
2929
* Component type.
@@ -39,6 +39,20 @@ class StringSelect extends SelectMenu
3939
*/
4040
protected $options = [];
4141

42+
/**
43+
* Sets whether the select menu is required. (Modal only).
44+
*
45+
* @param bool|null $required
46+
*
47+
* @return $this
48+
*/
49+
public function setRequired(?bool $required = false): self
50+
{
51+
$this->required = $required;
52+
53+
return $this;
54+
}
55+
4256
/**
4357
* Adds an option to the select menu. Maximum 25 options.
4458
*

src/Discord/Builders/Components/TextInput.php

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,10 @@ class TextInput extends Interactive
4646
/**
4747
* Label for the text input.
4848
*
49-
* @var string
49+
* Deprecated for use with modals. Use a top-level Component::Label.
50+
*
51+
* @var string|null
52+
*
5053
*/
5154
private $label;
5255

@@ -92,7 +95,7 @@ class TextInput extends Interactive
9295
* @param int $style The style of the text input.
9396
* @param string|null $custom_id The custom ID of the text input. If not given, a UUID will be used
9497
*/
95-
public function __construct(string $label, int $style, ?string $custom_id = null)
98+
public function __construct(?string $label = null, int $style, ?string $custom_id = null)
9699
{
97100
$this->setLabel($label);
98101
$this->setStyle($style);
@@ -108,7 +111,7 @@ public function __construct(string $label, int $style, ?string $custom_id = null
108111
*
109112
* @return self
110113
*/
111-
public static function new(string $label, int $style, ?string $custom_id = null): self
114+
public static function new(?string $label = null, int $style, ?string $custom_id = null): self
112115
{
113116
return new self($label, $style, $custom_id);
114117
}
@@ -156,15 +159,15 @@ public function setStyle(int $style): self
156159
/**
157160
* Sets the label of the text input.
158161
*
159-
* @param string $label Label of the text input. Maximum 45 characters.
162+
* @param string|null $label Label of the text input. Maximum 45 characters.
160163
*
161164
* @throws \LengthException
162165
*
163166
* @return $this
164167
*/
165-
public function setLabel(string $label): self
168+
public function setLabel(?string $label = null): self
166169
{
167-
if (poly_strlen($label) > 45) {
170+
if (isset($label) && poly_strlen($label) > 45) {
168171
throw new \LengthException('Label must be maximum 45 characters.');
169172
}
170173

@@ -318,17 +321,20 @@ public function isRequired(): ?bool
318321
}
319322

320323
/**
321-
* {@inheritDoc}
324+
* @inheritDoc
322325
*/
323326
public function jsonSerialize(): array
324327
{
325328
$content = [
326329
'type' => $this->type,
327330
'custom_id' => $this->custom_id,
328331
'style' => $this->style,
329-
'label' => $this->label,
330332
];
331333

334+
if (isset($this->label)) {
335+
$content['label'] = $this->label;
336+
}
337+
332338
if (isset($this->min_length)) {
333339
$content['min_length'] = $this->min_length;
334340
}

src/Discord/Builders/Components/UserSelect.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,18 @@ class UserSelect extends SelectMenu
3030
* @var int
3131
*/
3232
protected $type = Component::TYPE_USER_SELECT;
33+
34+
/**
35+
* Set if this component is required to be filled, default false. (Modal only)
36+
*
37+
* @param bool $required
38+
*
39+
* @return $this
40+
*/
41+
public function setRequired(bool $required): self
42+
{
43+
$this->required = $required;
44+
45+
return $this;
46+
}
3347
}

0 commit comments

Comments
 (0)