mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-14 10:33:30 +01:00
feat: new select menus (#8793)
* feat(builders): new select menus * chore: better re-exporting of deprecated classes * feat: new select menus * chore: typings * chore: add missing todo comment * chore: finish updating tests * chore: add runtime deprecation warnings * chore: format deprecation warning * feat(BaseInteraction): isAnySelectMenu * chore: requested changes * fix: deprecation comments * chore: update @deprecated comments in typings * chore: add tests for select menu type narrowing * fix: bad auto imports Co-authored-by: Julian Vennen <julian@aternos.org> * fix: properly handle resolved members * fix: collectors * chore: suggested changes Co-authored-by: Almeida <almeidx@pm.me> * fix(typings): bad class extends * feat(ChannelSelectMenuBuilder): validation * chore: update todo comment * refactor(ChannelSelectMenu): better handling of channel_types state * chore: style nit * chore: suggested nits Co-authored-by: Aura Román <kyradiscord@gmail.com> Co-authored-by: Julian Vennen <julian@aternos.org> Co-authored-by: Almeida <almeidx@pm.me> Co-authored-by: Aura Román <kyradiscord@gmail.com> Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
import type { APISelectMenuComponent } from 'discord-api-types/v10';
|
||||
import { customIdValidator, disabledValidator, minMaxValidator, placeholderValidator } from '../Assertions.js';
|
||||
import { ComponentBuilder } from '../Component.js';
|
||||
|
||||
export class BaseSelectMenuBuilder<
|
||||
SelectMenuType extends APISelectMenuComponent,
|
||||
> extends ComponentBuilder<SelectMenuType> {
|
||||
/**
|
||||
* Sets the placeholder for this select menu
|
||||
*
|
||||
* @param placeholder - The placeholder to use for this select menu
|
||||
*/
|
||||
public setPlaceholder(placeholder: string) {
|
||||
this.data.placeholder = placeholderValidator.parse(placeholder);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum values that must be selected in the select menu
|
||||
*
|
||||
* @param minValues - The minimum values that must be selected
|
||||
*/
|
||||
public setMinValues(minValues: number) {
|
||||
this.data.min_values = minMaxValidator.parse(minValues);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum values that must be selected in the select menu
|
||||
*
|
||||
* @param maxValues - The maximum values that must be selected
|
||||
*/
|
||||
public setMaxValues(maxValues: number) {
|
||||
this.data.max_values = minMaxValidator.parse(maxValues);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the custom id for this select menu
|
||||
*
|
||||
* @param customId - The custom id to use for this select menu
|
||||
*/
|
||||
public setCustomId(customId: string) {
|
||||
this.data.custom_id = customIdValidator.parse(customId);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this select menu is disabled
|
||||
*
|
||||
* @param disabled - Whether this select menu is disabled
|
||||
*/
|
||||
public setDisabled(disabled = true) {
|
||||
this.data.disabled = disabledValidator.parse(disabled);
|
||||
return this;
|
||||
}
|
||||
|
||||
public toJSON(): SelectMenuType {
|
||||
customIdValidator.parse(this.data.custom_id);
|
||||
return {
|
||||
...this.data,
|
||||
} as SelectMenuType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import type { APIChannelSelectComponent, ChannelType } from 'discord-api-types/v10';
|
||||
import { ComponentType } from 'discord-api-types/v10';
|
||||
import { normalizeArray, type RestOrArray } from '../../util/normalizeArray.js';
|
||||
import { channelTypesValidator, customIdValidator } from '../Assertions.js';
|
||||
import { BaseSelectMenuBuilder } from './BaseSelectMenu.js';
|
||||
|
||||
export class ChannelSelectMenuBuilder extends BaseSelectMenuBuilder<APIChannelSelectComponent> {
|
||||
/**
|
||||
* Creates a new select menu from API data
|
||||
*
|
||||
* @param data - The API data to create this select menu with
|
||||
* @example
|
||||
* Creating a select menu from an API data object
|
||||
* ```ts
|
||||
* const selectMenu = new ChannelSelectMenuBuilder({
|
||||
* custom_id: 'a cool select menu',
|
||||
* placeholder: 'select an option',
|
||||
* max_values: 2,
|
||||
* });
|
||||
* ```
|
||||
* @example
|
||||
* Creating a select menu using setters and API data
|
||||
* ```ts
|
||||
* const selectMenu = new ChannelSelectMenuBuilder({
|
||||
* custom_id: 'a cool select menu',
|
||||
* })
|
||||
* .addChannelTypes(ChannelType.GuildText, ChannelType.GuildAnnouncement)
|
||||
* .setMinValues(2)
|
||||
* ```
|
||||
*/
|
||||
public constructor(data?: Partial<APIChannelSelectComponent>) {
|
||||
super({ ...data, type: ComponentType.ChannelSelect });
|
||||
}
|
||||
|
||||
public addChannelTypes(...types: RestOrArray<ChannelType>) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
types = normalizeArray(types);
|
||||
|
||||
this.data.channel_types ??= [];
|
||||
this.data.channel_types.push(...channelTypesValidator.parse(types));
|
||||
return this;
|
||||
}
|
||||
|
||||
public setChannelTypes(...types: RestOrArray<ChannelType>) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
types = normalizeArray(types);
|
||||
|
||||
this.data.channel_types ??= [];
|
||||
this.data.channel_types.splice(0, this.data.channel_types.length, ...channelTypesValidator.parse(types));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc ComponentBuilder.toJSON}
|
||||
*/
|
||||
public override toJSON(): APIChannelSelectComponent {
|
||||
customIdValidator.parse(this.data.custom_id);
|
||||
|
||||
return {
|
||||
...this.data,
|
||||
} as APIChannelSelectComponent;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { APIMentionableSelectComponent } from 'discord-api-types/v10';
|
||||
import { ComponentType } from 'discord-api-types/v10';
|
||||
import { BaseSelectMenuBuilder } from './BaseSelectMenu.js';
|
||||
|
||||
export class MentionableSelectMenuBuilder extends BaseSelectMenuBuilder<APIMentionableSelectComponent> {
|
||||
/**
|
||||
* Creates a new select menu from API data
|
||||
*
|
||||
* @param data - The API data to create this select menu with
|
||||
* @example
|
||||
* Creating a select menu from an API data object
|
||||
* ```ts
|
||||
* const selectMenu = new MentionableSelectMenuBuilder({
|
||||
* custom_id: 'a cool select menu',
|
||||
* placeholder: 'select an option',
|
||||
* max_values: 2,
|
||||
* });
|
||||
* ```
|
||||
* @example
|
||||
* Creating a select menu using setters and API data
|
||||
* ```ts
|
||||
* const selectMenu = new MentionableSelectMenuBuilder({
|
||||
* custom_id: 'a cool select menu',
|
||||
* })
|
||||
* .setMinValues(1)
|
||||
* ```
|
||||
*/
|
||||
public constructor(data?: Partial<APIMentionableSelectComponent>) {
|
||||
super({ ...data, type: ComponentType.MentionableSelect });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { APIRoleSelectComponent } from 'discord-api-types/v10';
|
||||
import { ComponentType } from 'discord-api-types/v10';
|
||||
import { BaseSelectMenuBuilder } from './BaseSelectMenu.js';
|
||||
|
||||
export class RoleSelectMenuBuilder extends BaseSelectMenuBuilder<APIRoleSelectComponent> {
|
||||
/**
|
||||
* Creates a new select menu from API data
|
||||
*
|
||||
* @param data - The API data to create this select menu with
|
||||
* @example
|
||||
* Creating a select menu from an API data object
|
||||
* ```ts
|
||||
* const selectMenu = new RoleSelectMenuBuilder({
|
||||
* custom_id: 'a cool select menu',
|
||||
* placeholder: 'select an option',
|
||||
* max_values: 2,
|
||||
* });
|
||||
* ```
|
||||
* @example
|
||||
* Creating a select menu using setters and API data
|
||||
* ```ts
|
||||
* const selectMenu = new RoleSelectMenuBuilder({
|
||||
* custom_id: 'a cool select menu',
|
||||
* })
|
||||
* .setMinValues(1)
|
||||
* ```
|
||||
*/
|
||||
public constructor(data?: Partial<APIRoleSelectComponent>) {
|
||||
super({ ...data, type: ComponentType.RoleSelect });
|
||||
}
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
import { ComponentType, type APISelectMenuComponent, type APISelectMenuOption } from 'discord-api-types/v10';
|
||||
import { normalizeArray, type RestOrArray } from '../../util/normalizeArray.js';
|
||||
import {
|
||||
customIdValidator,
|
||||
disabledValidator,
|
||||
jsonOptionValidator,
|
||||
minMaxValidator,
|
||||
optionsLengthValidator,
|
||||
placeholderValidator,
|
||||
validateRequiredSelectMenuParameters,
|
||||
} from '../Assertions.js';
|
||||
import { ComponentBuilder } from '../Component.js';
|
||||
import { SelectMenuOptionBuilder } from './SelectMenuOption.js';
|
||||
|
||||
/**
|
||||
* Represents a select menu component
|
||||
*/
|
||||
export class SelectMenuBuilder extends ComponentBuilder<APISelectMenuComponent> {
|
||||
/**
|
||||
* The options within this select menu
|
||||
*/
|
||||
public readonly options: SelectMenuOptionBuilder[];
|
||||
|
||||
/**
|
||||
* Creates a new select menu from API data
|
||||
*
|
||||
* @param data - The API data to create this select menu with
|
||||
* @example
|
||||
* Creating a select menu from an API data object
|
||||
* ```ts
|
||||
* const selectMenu = new SelectMenuBuilder({
|
||||
* custom_id: 'a cool select menu',
|
||||
* placeholder: 'select an option',
|
||||
* max_values: 2,
|
||||
* options: [
|
||||
* { label: 'option 1', value: '1' },
|
||||
* { label: 'option 2', value: '2' },
|
||||
* { label: 'option 3', value: '3' },
|
||||
* ],
|
||||
* });
|
||||
* ```
|
||||
* @example
|
||||
* Creating a select menu using setters and API data
|
||||
* ```ts
|
||||
* const selectMenu = new SelectMenuBuilder({
|
||||
* custom_id: 'a cool select menu',
|
||||
* })
|
||||
* .setMinValues(1)
|
||||
* .addOptions({
|
||||
* label: 'Catchy',
|
||||
* value: 'catch',
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
public constructor(data?: Partial<APISelectMenuComponent>) {
|
||||
const { options, ...initData } = data ?? {};
|
||||
super({ type: ComponentType.SelectMenu, ...initData });
|
||||
this.options = options?.map((option) => new SelectMenuOptionBuilder(option)) ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the placeholder for this select menu
|
||||
*
|
||||
* @param placeholder - The placeholder to use for this select menu
|
||||
*/
|
||||
public setPlaceholder(placeholder: string) {
|
||||
this.data.placeholder = placeholderValidator.parse(placeholder);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum values that must be selected in the select menu
|
||||
*
|
||||
* @param minValues - The minimum values that must be selected
|
||||
*/
|
||||
public setMinValues(minValues: number) {
|
||||
this.data.min_values = minMaxValidator.parse(minValues);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum values that must be selected in the select menu
|
||||
*
|
||||
* @param maxValues - The maximum values that must be selected
|
||||
*/
|
||||
public setMaxValues(maxValues: number) {
|
||||
this.data.max_values = minMaxValidator.parse(maxValues);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the custom id for this select menu
|
||||
*
|
||||
* @param customId - The custom id to use for this select menu
|
||||
*/
|
||||
public setCustomId(customId: string) {
|
||||
this.data.custom_id = customIdValidator.parse(customId);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this select menu is disabled
|
||||
*
|
||||
* @param disabled - Whether this select menu is disabled
|
||||
*/
|
||||
public setDisabled(disabled = true) {
|
||||
this.data.disabled = disabledValidator.parse(disabled);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds options to this select menu
|
||||
*
|
||||
* @param options - The options to add to this select menu
|
||||
* @returns
|
||||
*/
|
||||
public addOptions(...options: RestOrArray<APISelectMenuOption | SelectMenuOptionBuilder>) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
options = normalizeArray(options);
|
||||
optionsLengthValidator.parse(this.options.length + options.length);
|
||||
this.options.push(
|
||||
...options.map((option) =>
|
||||
option instanceof SelectMenuOptionBuilder
|
||||
? option
|
||||
: new SelectMenuOptionBuilder(jsonOptionValidator.parse(option)),
|
||||
),
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the options on this select menu
|
||||
*
|
||||
* @param options - The options to set on this select menu
|
||||
*/
|
||||
public setOptions(...options: RestOrArray<APISelectMenuOption | SelectMenuOptionBuilder>) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
options = normalizeArray(options);
|
||||
optionsLengthValidator.parse(options.length);
|
||||
this.options.splice(
|
||||
0,
|
||||
this.options.length,
|
||||
...options.map((option) =>
|
||||
option instanceof SelectMenuOptionBuilder
|
||||
? option
|
||||
: new SelectMenuOptionBuilder(jsonOptionValidator.parse(option)),
|
||||
),
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc ComponentBuilder.toJSON}
|
||||
*/
|
||||
public toJSON(): APISelectMenuComponent {
|
||||
validateRequiredSelectMenuParameters(this.options, this.data.custom_id);
|
||||
|
||||
return {
|
||||
...this.data,
|
||||
options: this.options.map((option) => option.toJSON()),
|
||||
} as APISelectMenuComponent;
|
||||
}
|
||||
}
|
||||
106
packages/builders/src/components/selectMenu/StringSelectMenu.ts
Normal file
106
packages/builders/src/components/selectMenu/StringSelectMenu.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import type { APIStringSelectComponent } from 'discord-api-types/v10';
|
||||
import { ComponentType, type APISelectMenuOption } from 'discord-api-types/v10';
|
||||
import { normalizeArray, type RestOrArray } from '../../util/normalizeArray.js';
|
||||
import { jsonOptionValidator, optionsLengthValidator, validateRequiredSelectMenuParameters } from '../Assertions.js';
|
||||
import { BaseSelectMenuBuilder } from './BaseSelectMenu.js';
|
||||
import { StringSelectMenuOptionBuilder } from './StringSelectMenuOption.js';
|
||||
|
||||
/**
|
||||
* Represents a string select menu component
|
||||
*/
|
||||
export class StringSelectMenuBuilder extends BaseSelectMenuBuilder<APIStringSelectComponent> {
|
||||
/**
|
||||
* The options within this select menu
|
||||
*/
|
||||
public readonly options: StringSelectMenuOptionBuilder[];
|
||||
|
||||
/**
|
||||
* Creates a new select menu from API data
|
||||
*
|
||||
* @param data - The API data to create this select menu with
|
||||
* @example
|
||||
* Creating a select menu from an API data object
|
||||
* ```ts
|
||||
* const selectMenu = new StringSelectMenuBuilder({
|
||||
* custom_id: 'a cool select menu',
|
||||
* placeholder: 'select an option',
|
||||
* max_values: 2,
|
||||
* options: [
|
||||
* { label: 'option 1', value: '1' },
|
||||
* { label: 'option 2', value: '2' },
|
||||
* { label: 'option 3', value: '3' },
|
||||
* ],
|
||||
* });
|
||||
* ```
|
||||
* @example
|
||||
* Creating a select menu using setters and API data
|
||||
* ```ts
|
||||
* const selectMenu = new StringSelectMenuBuilder({
|
||||
* custom_id: 'a cool select menu',
|
||||
* })
|
||||
* .setMinValues(1)
|
||||
* .addOptions({
|
||||
* label: 'Catchy',
|
||||
* value: 'catch',
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
public constructor(data?: Partial<APIStringSelectComponent>) {
|
||||
const { options, ...initData } = data ?? {};
|
||||
super({ ...initData, type: ComponentType.StringSelect });
|
||||
this.options = options?.map((option: APISelectMenuOption) => new StringSelectMenuOptionBuilder(option)) ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds options to this select menu
|
||||
*
|
||||
* @param options - The options to add to this select menu
|
||||
* @returns
|
||||
*/
|
||||
public addOptions(...options: RestOrArray<APISelectMenuOption | StringSelectMenuOptionBuilder>) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
options = normalizeArray(options);
|
||||
optionsLengthValidator.parse(this.options.length + options.length);
|
||||
this.options.push(
|
||||
...options.map((option) =>
|
||||
option instanceof StringSelectMenuOptionBuilder
|
||||
? option
|
||||
: new StringSelectMenuOptionBuilder(jsonOptionValidator.parse(option)),
|
||||
),
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the options on this select menu
|
||||
*
|
||||
* @param options - The options to set on this select menu
|
||||
*/
|
||||
public setOptions(...options: RestOrArray<APISelectMenuOption | StringSelectMenuOptionBuilder>) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
options = normalizeArray(options);
|
||||
optionsLengthValidator.parse(options.length);
|
||||
this.options.splice(
|
||||
0,
|
||||
this.options.length,
|
||||
...options.map((option) =>
|
||||
option instanceof StringSelectMenuOptionBuilder
|
||||
? option
|
||||
: new StringSelectMenuOptionBuilder(jsonOptionValidator.parse(option)),
|
||||
),
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc ComponentBuilder.toJSON}
|
||||
*/
|
||||
public override toJSON(): APIStringSelectComponent {
|
||||
validateRequiredSelectMenuParameters(this.options, this.data.custom_id);
|
||||
|
||||
return {
|
||||
...this.data,
|
||||
options: this.options.map((option) => option.toJSON()),
|
||||
} as APIStringSelectComponent;
|
||||
}
|
||||
}
|
||||
@@ -8,15 +8,15 @@ import {
|
||||
} from '../Assertions.js';
|
||||
|
||||
/**
|
||||
* Represents a option within a select menu component
|
||||
* Represents an option within a string select menu component
|
||||
*/
|
||||
export class SelectMenuOptionBuilder implements JSONEncodable<APISelectMenuOption> {
|
||||
export class StringSelectMenuOptionBuilder implements JSONEncodable<APISelectMenuOption> {
|
||||
/**
|
||||
* Creates a new select menu option from API data
|
||||
* Creates a new string select menu option from API data
|
||||
*
|
||||
* @param data - The API data to create this select menu option with
|
||||
* @param data - The API data to create this string select menu option with
|
||||
* @example
|
||||
* Creating a select menu option from an API data object
|
||||
* Creating a string select menu option from an API data object
|
||||
* ```ts
|
||||
* const selectMenuOption = new SelectMenuOptionBuilder({
|
||||
* label: 'catchy label',
|
||||
@@ -24,7 +24,7 @@ export class SelectMenuOptionBuilder implements JSONEncodable<APISelectMenuOptio
|
||||
* });
|
||||
* ```
|
||||
* @example
|
||||
* Creating a select menu option using setters and API data
|
||||
* Creating a string select menu option using setters and API data
|
||||
* ```ts
|
||||
* const selectMenuOption = new SelectMenuOptionBuilder({
|
||||
* default: true,
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { APIUserSelectComponent } from 'discord-api-types/v10';
|
||||
import { ComponentType } from 'discord-api-types/v10';
|
||||
import { BaseSelectMenuBuilder } from './BaseSelectMenu.js';
|
||||
|
||||
export class UserSelectMenuBuilder extends BaseSelectMenuBuilder<APIUserSelectComponent> {
|
||||
/**
|
||||
* Creates a new select menu from API data
|
||||
*
|
||||
* @param data - The API data to create this select menu with
|
||||
* @example
|
||||
* Creating a select menu from an API data object
|
||||
* ```ts
|
||||
* const selectMenu = new UserSelectMenuBuilder({
|
||||
* custom_id: 'a cool select menu',
|
||||
* placeholder: 'select an option',
|
||||
* max_values: 2,
|
||||
* });
|
||||
* ```
|
||||
* @example
|
||||
* Creating a select menu using setters and API data
|
||||
* ```ts
|
||||
* const selectMenu = new UserSelectMenuBuilder({
|
||||
* custom_id: 'a cool select menu',
|
||||
* })
|
||||
* .setMinValues(1)
|
||||
* ```
|
||||
*/
|
||||
public constructor(data?: Partial<APIUserSelectComponent>) {
|
||||
super({ ...data, type: ComponentType.UserSelect });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user