mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-09 16:13:31 +01:00
feat!: label component and selects in modal (#11081)
BREAKING CHANGE: TextInputComponentData no longer accepts label BREAKING CHANGE: ActionRow and ActionRowData no longer accept TextInput BREAKING CHANGE: `ModalSubmitInteraction#transformComponent` is now private and no longer exposed publicly --------- Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com>
This commit is contained in:
@@ -180,6 +180,7 @@ exports.InteractionCallbackResponse =
|
||||
exports.InteractionCollector = require('./structures/InteractionCollector.js').InteractionCollector;
|
||||
exports.InteractionWebhook = require('./structures/InteractionWebhook.js').InteractionWebhook;
|
||||
exports.InviteGuild = require('./structures/InviteGuild.js').InviteGuild;
|
||||
exports.LabelComponent = require('./structures/LabelComponent.js').LabelComponent;
|
||||
exports.MediaChannel = require('./structures/MediaChannel.js').MediaChannel;
|
||||
exports.MediaGalleryComponent = require('./structures/MediaGalleryComponent.js').MediaGalleryComponent;
|
||||
exports.MediaGalleryItem = require('./structures/MediaGalleryItem.js').MediaGalleryItem;
|
||||
|
||||
54
packages/discord.js/src/structures/LabelComponent.js
Normal file
54
packages/discord.js/src/structures/LabelComponent.js
Normal file
@@ -0,0 +1,54 @@
|
||||
'use strict';
|
||||
|
||||
const { createComponent } = require('../util/Components.js');
|
||||
const { Component } = require('./Component.js');
|
||||
|
||||
/**
|
||||
* Represents a label component
|
||||
*
|
||||
* @extends {Component}
|
||||
*/
|
||||
class LabelComponent extends Component {
|
||||
constructor({ component, ...data }) {
|
||||
super(data);
|
||||
|
||||
/**
|
||||
* The component in this label
|
||||
*
|
||||
* @type {Component}
|
||||
* @readonly
|
||||
*/
|
||||
this.component = createComponent(component);
|
||||
}
|
||||
|
||||
/**
|
||||
* The label of the component
|
||||
*
|
||||
* @type {string}
|
||||
* @readonly
|
||||
*/
|
||||
get label() {
|
||||
return this.data.label;
|
||||
}
|
||||
|
||||
/**
|
||||
* The description of this component
|
||||
*
|
||||
* @type {?string}
|
||||
* @readonly
|
||||
*/
|
||||
get description() {
|
||||
return this.data.description ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the API-compatible JSON for this component
|
||||
*
|
||||
* @returns {APILabelComponent}
|
||||
*/
|
||||
toJSON() {
|
||||
return { ...this.data, component: this.component.toJSON() };
|
||||
}
|
||||
}
|
||||
|
||||
exports.LabelComponent = LabelComponent;
|
||||
@@ -12,7 +12,7 @@ class ModalSubmitFields {
|
||||
/**
|
||||
* The components within the modal
|
||||
*
|
||||
* @type {ActionRowModalData[]}
|
||||
* @type {Array<ActionRowModalData | LabelModalData>}
|
||||
*/
|
||||
this.components = components;
|
||||
|
||||
@@ -22,7 +22,16 @@ class ModalSubmitFields {
|
||||
* @type {Collection<string, ModalData>}
|
||||
*/
|
||||
this.fields = components.reduce((accumulator, next) => {
|
||||
for (const component of next.components) accumulator.set(component.customId, component);
|
||||
// NOTE: for legacy support of action rows in modals, which has `components`
|
||||
if ('components' in next) {
|
||||
for (const component of next.components) accumulator.set(component.customId, component);
|
||||
}
|
||||
|
||||
// For label component
|
||||
if ('component' in next) {
|
||||
accumulator.set(next.component.customId, next.component);
|
||||
}
|
||||
|
||||
return accumulator;
|
||||
}, new Collection());
|
||||
}
|
||||
@@ -54,6 +63,16 @@ class ModalSubmitFields {
|
||||
getTextInputValue(customId) {
|
||||
return this.getField(customId, ComponentType.TextInput).value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the values of a string select component given a custom id
|
||||
*
|
||||
* @param {string} customId The custom id of the string select component
|
||||
* @returns {string[]}
|
||||
*/
|
||||
getStringSelectValues(customId) {
|
||||
return this.getField(customId, ComponentType.StringSelect).values;
|
||||
}
|
||||
}
|
||||
|
||||
exports.ModalSubmitFields = ModalSubmitFields;
|
||||
|
||||
@@ -9,16 +9,38 @@ const { InteractionResponses } = require('./interfaces/InteractionResponses.js')
|
||||
const getMessage = lazy(() => require('./Message.js').Message);
|
||||
|
||||
/**
|
||||
* @typedef {Object} ModalData
|
||||
* @property {string} value The value of the field
|
||||
* @typedef {Object} BaseModalData
|
||||
* @property {ComponentType} type The component type of the field
|
||||
* @property {string} customId The custom id of the field
|
||||
* @property {number} id The id of the field
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {BaseModalData} TextInputModalData
|
||||
* @property {string} value The value of the field
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {BaseModalData} StringSelectModalData
|
||||
* @property {string[]} values The values of the field
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {TextInputModalData | StringSelectModalData} ModalData
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} LabelModalData
|
||||
* @property {ModalData} component The component within the label
|
||||
* @property {ComponentType} type The component type of the label
|
||||
* @property {number} id The id of the label
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} ActionRowModalData
|
||||
* @property {ModalData[]} components The components of this action row
|
||||
* @property {TextInputModalData[]} components The components of this action row
|
||||
* @property {ComponentType} type The component type of the action row
|
||||
* @property {number} id The id of the action row
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -51,7 +73,7 @@ class ModalSubmitInteraction extends BaseInteraction {
|
||||
/**
|
||||
* The components within the modal
|
||||
*
|
||||
* @type {ActionRowModalData[]}
|
||||
* @type {Array<ActionRowModalData | LabelModalData>}
|
||||
*/
|
||||
this.components = data.data.components?.map(component => ModalSubmitInteraction.transformComponent(component));
|
||||
|
||||
@@ -96,18 +118,35 @@ class ModalSubmitInteraction extends BaseInteraction {
|
||||
*
|
||||
* @param {*} rawComponent The data to transform
|
||||
* @returns {ModalData[]}
|
||||
* @private
|
||||
*/
|
||||
static transformComponent(rawComponent) {
|
||||
return rawComponent.components
|
||||
? {
|
||||
type: rawComponent.type,
|
||||
components: rawComponent.components.map(component => this.transformComponent(component)),
|
||||
}
|
||||
: {
|
||||
value: rawComponent.value,
|
||||
type: rawComponent.type,
|
||||
customId: rawComponent.custom_id,
|
||||
};
|
||||
if ('components' in rawComponent) {
|
||||
return {
|
||||
type: rawComponent.type,
|
||||
id: rawComponent.id,
|
||||
components: rawComponent.components.map(component => this.transformComponent(component)),
|
||||
};
|
||||
}
|
||||
|
||||
if ('component' in rawComponent) {
|
||||
return {
|
||||
type: rawComponent.type,
|
||||
id: rawComponent.id,
|
||||
component: this.transformComponent(rawComponent.component),
|
||||
};
|
||||
}
|
||||
|
||||
const data = {
|
||||
type: rawComponent.type,
|
||||
customId: rawComponent.custom_id,
|
||||
id: rawComponent.id,
|
||||
};
|
||||
|
||||
if (rawComponent.value) data.value = rawComponent.value;
|
||||
if (rawComponent.values) data.values = rawComponent.values;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,13 +9,6 @@ const { InteractionCallbackResponse } = require('../InteractionCallbackResponse.
|
||||
const { InteractionCollector } = require('../InteractionCollector.js');
|
||||
const { MessagePayload } = require('../MessagePayload.js');
|
||||
|
||||
/**
|
||||
* @typedef {Object} ModalComponentData
|
||||
* @property {string} title The title of the modal
|
||||
* @property {string} customId The custom id of the modal
|
||||
* @property {ActionRow[]} components The components within this modal
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface for classes that support shared interaction response types.
|
||||
*
|
||||
|
||||
@@ -15,6 +15,24 @@ const { ComponentType } = require('discord-api-types/v10');
|
||||
* @property {ComponentData[]} components The components in this action row
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} ModalComponentData
|
||||
* @property {string} title The title of the modal
|
||||
* @property {string} customId The custom id of the modal
|
||||
* @property {LabelData[]} components The components within this modal
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {StringSelectMenuComponentData|TextInputComponentData} ComponentInLabelData
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {BaseComponentData} LabelData
|
||||
* @property {string} label The label to use
|
||||
* @property {string} [description] The optional description for the label
|
||||
* @property {ComponentInLabelData} component The component within the label
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {BaseComponentData} ButtonComponentData
|
||||
* @property {ButtonStyle} style The style of the button
|
||||
@@ -25,6 +43,17 @@ const { ComponentType } = require('discord-api-types/v10');
|
||||
* @property {string} [url] The URL of the button
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {BaseComponentData} StringSelectMenuComponentData
|
||||
* @property {string} customId The custom id of the select menu
|
||||
* @property {boolean} [disabled] Whether the select menu is disabled or not
|
||||
* @property {number} [maxValues] The maximum amount of options that can be selected
|
||||
* @property {number} [minValues] The minimum amount of options that can be selected
|
||||
* @property {SelectMenuComponentOptionData[]} [options] The options in this select menu
|
||||
* @property {string} [placeholder] The placeholder of the select menu
|
||||
* @property {boolean} [required] Whether this component is required in modals
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} SelectMenuComponentOptionData
|
||||
* @property {string} label The label of the option
|
||||
@@ -52,7 +81,6 @@ const { ComponentType } = require('discord-api-types/v10');
|
||||
* @typedef {BaseComponentData} TextInputComponentData
|
||||
* @property {string} customId The custom id of the text input
|
||||
* @property {TextInputStyle} style The style of the text input
|
||||
* @property {string} label The text that appears on top of the text input field
|
||||
* @property {number} [minLength] The minimum number of characters that can be entered in the text input
|
||||
* @property {number} [maxLength] The maximum number of characters that can be entered in the text input
|
||||
* @property {boolean} [required] Whether or not the text input is required or not
|
||||
@@ -186,6 +214,7 @@ const { ChannelSelectMenuComponent } = require('../structures/ChannelSelectMenuC
|
||||
const { Component } = require('../structures/Component.js');
|
||||
const { ContainerComponent } = require('../structures/ContainerComponent.js');
|
||||
const { FileComponent } = require('../structures/FileComponent.js');
|
||||
const { LabelComponent } = require('../structures/LabelComponent.js');
|
||||
const { MediaGalleryComponent } = require('../structures/MediaGalleryComponent.js');
|
||||
const { MentionableSelectMenuComponent } = require('../structures/MentionableSelectMenuComponent.js');
|
||||
const { RoleSelectMenuComponent } = require('../structures/RoleSelectMenuComponent.js');
|
||||
@@ -213,4 +242,5 @@ const ComponentTypeToClass = {
|
||||
[ComponentType.Section]: SectionComponent,
|
||||
[ComponentType.Separator]: SeparatorComponent,
|
||||
[ComponentType.Thumbnail]: ThumbnailComponent,
|
||||
[ComponentType.Label]: LabelComponent,
|
||||
};
|
||||
|
||||
74
packages/discord.js/typings/index.d.ts
vendored
74
packages/discord.js/typings/index.d.ts
vendored
@@ -50,6 +50,7 @@ import {
|
||||
APIInteractionDataResolvedChannel,
|
||||
APIInteractionDataResolvedGuildMember,
|
||||
APIInteractionGuildMember,
|
||||
APILabelComponent,
|
||||
APIMediaGalleryComponent,
|
||||
APIMediaGalleryItem,
|
||||
APIMentionableSelectComponent,
|
||||
@@ -65,6 +66,7 @@ import {
|
||||
APIMessageTopLevelComponent,
|
||||
APIMessageUserSelectInteractionData,
|
||||
APIModalComponent,
|
||||
APIModalInteractionResponseCallbackComponent,
|
||||
APIModalInteractionResponseCallbackData,
|
||||
APIModalSubmitInteraction,
|
||||
APIOverwrite,
|
||||
@@ -248,6 +250,7 @@ export class Activity {
|
||||
export type ActivityFlagsString = keyof typeof ActivityFlags;
|
||||
|
||||
export interface BaseComponentData {
|
||||
id?: number;
|
||||
type: ComponentType;
|
||||
}
|
||||
|
||||
@@ -260,17 +263,22 @@ export type MessageActionRowComponentData =
|
||||
| StringSelectMenuComponentData
|
||||
| UserSelectMenuComponentData;
|
||||
|
||||
export type ModalActionRowComponentData = JSONEncodable<APIComponentInModalActionRow> | TextInputComponentData;
|
||||
export type ActionRowComponentData = MessageActionRowComponentData;
|
||||
|
||||
export type ActionRowComponentData = MessageActionRowComponentData | ModalActionRowComponentData;
|
||||
|
||||
export type ActionRowComponent = MessageActionRowComponent | ModalActionRowComponent;
|
||||
export type ActionRowComponent = MessageActionRowComponent;
|
||||
|
||||
export interface ActionRowData<ComponentType extends ActionRowComponentData | JSONEncodable<APIComponentInActionRow>>
|
||||
extends BaseComponentData {
|
||||
components: readonly ComponentType[];
|
||||
}
|
||||
|
||||
export type ComponentInLabelData = StringSelectMenuComponentData | TextInputComponentData;
|
||||
export interface LabelData extends BaseComponentData {
|
||||
component: ComponentInLabelData;
|
||||
description?: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export type MessageActionRowComponent =
|
||||
| ButtonComponent
|
||||
| ChannelSelectMenuComponent
|
||||
@@ -278,12 +286,11 @@ export type MessageActionRowComponent =
|
||||
| RoleSelectMenuComponent
|
||||
| StringSelectMenuComponent
|
||||
| UserSelectMenuComponent;
|
||||
export type ModalActionRowComponent = TextInputComponent;
|
||||
|
||||
export class ActionRow<ComponentType extends MessageActionRowComponent | ModalActionRowComponent> extends Component<
|
||||
APIActionRowComponent<APIComponentInMessageActionRow | APIComponentInModalActionRow>
|
||||
export class ActionRow<ComponentType extends MessageActionRowComponent> extends Component<
|
||||
APIActionRowComponent<APIComponentInMessageActionRow>
|
||||
> {
|
||||
private constructor(data: APIActionRowComponent<APIComponentInMessageActionRow | APIComponentInModalActionRow>);
|
||||
private constructor(data: APIActionRowComponent<APIComponentInMessageActionRow>);
|
||||
public readonly components: ComponentType[];
|
||||
public toJSON(): APIActionRowComponent<ReturnType<ComponentType['toJSON']>>;
|
||||
}
|
||||
@@ -740,6 +747,12 @@ export class TextInputComponent extends Component<APITextInputComponent> {
|
||||
public get value(): string;
|
||||
}
|
||||
|
||||
export class LabelComponent extends Component<APILabelComponent> {
|
||||
public component: StringSelectMenuComponent | TextInputComponent;
|
||||
public get label(): string;
|
||||
public get description(): string | null;
|
||||
}
|
||||
|
||||
export class BaseSelectMenuComponent<Data extends APISelectMenuComponent> extends Component<Data> {
|
||||
protected constructor(data: Data);
|
||||
public get placeholder(): string | null;
|
||||
@@ -2527,36 +2540,48 @@ export interface MessageReactionEventDetails {
|
||||
}
|
||||
|
||||
export interface ModalComponentData {
|
||||
components: readonly (
|
||||
| ActionRowData<ModalActionRowComponentData>
|
||||
| JSONEncodable<APIActionRowComponent<APIComponentInModalActionRow>>
|
||||
)[];
|
||||
components: readonly LabelData[];
|
||||
customId: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface BaseModalData {
|
||||
export interface BaseModalData<Type extends ComponentType> {
|
||||
customId: string;
|
||||
type: ComponentType;
|
||||
id: number;
|
||||
type: Type;
|
||||
}
|
||||
|
||||
export interface TextInputModalData extends BaseModalData {
|
||||
type: ComponentType.TextInput;
|
||||
export interface TextInputModalData extends BaseModalData<ComponentType.TextInput> {
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface StringSelectModalData extends BaseModalData<ComponentType.StringSelect> {
|
||||
values: readonly string[];
|
||||
}
|
||||
|
||||
export type ModalData = StringSelectModalData | TextInputModalData;
|
||||
|
||||
export interface LabelModalData {
|
||||
component: readonly ModalData[];
|
||||
id: number;
|
||||
type: ComponentType.Label;
|
||||
}
|
||||
export interface ActionRowModalData {
|
||||
components: readonly TextInputModalData[];
|
||||
type: ComponentType.ActionRow;
|
||||
}
|
||||
|
||||
export class ModalSubmitFields {
|
||||
private constructor(components: readonly (readonly ModalActionRowComponent[])[]);
|
||||
public components: ActionRowModalData[];
|
||||
public fields: Collection<string, TextInputModalData>;
|
||||
public getField<Type extends ComponentType>(customId: string, type: Type): TextInputModalData & { type: Type };
|
||||
public getField(customId: string, type?: ComponentType): TextInputModalData;
|
||||
private constructor(components: readonly (ActionRowModalData | LabelModalData)[]);
|
||||
public components: (ActionRowModalData | LabelModalData)[];
|
||||
public fields: Collection<string, StringSelectModalData | TextInputModalData>;
|
||||
public getField<Type extends ComponentType>(
|
||||
customId: string,
|
||||
type: Type,
|
||||
): { type: Type } & (StringSelectModalData | TextInputModalData);
|
||||
public getField(customId: string, type?: ComponentType): StringSelectModalData | TextInputModalData;
|
||||
public getTextInputValue(customId: string): string;
|
||||
public getStringSelectValues(customId: string): readonly string[];
|
||||
}
|
||||
|
||||
export interface ModalMessageModalSubmitInteraction<Cached extends CacheType = CacheType>
|
||||
@@ -2579,7 +2604,7 @@ export class ModalSubmitInteraction<Cached extends CacheType = CacheType> extend
|
||||
private constructor(client: Client<true>, data: APIModalSubmitInteraction);
|
||||
public type: InteractionType.ModalSubmit;
|
||||
public readonly customId: string;
|
||||
public readonly components: ActionRowModalData[];
|
||||
public readonly components: (ActionRowModalData | LabelModalData)[];
|
||||
public readonly fields: ModalSubmitFields;
|
||||
public deferred: boolean;
|
||||
public ephemeral: boolean | null;
|
||||
@@ -3645,9 +3670,10 @@ export function verifyString(data: string, error?: typeof Error, errorMessage?:
|
||||
|
||||
export type ComponentData =
|
||||
| ComponentInContainerData
|
||||
| ComponentInLabelData
|
||||
| ContainerComponentData
|
||||
| LabelData
|
||||
| MessageActionRowComponentData
|
||||
| ModalActionRowComponentData
|
||||
| ThumbnailComponentData;
|
||||
|
||||
export interface SendSoundboardSoundOptions {
|
||||
@@ -6669,6 +6695,7 @@ export interface BaseSelectMenuComponentData extends BaseComponentData {
|
||||
|
||||
export interface StringSelectMenuComponentData extends BaseSelectMenuComponentData {
|
||||
options: readonly SelectMenuComponentOptionData[];
|
||||
required?: boolean;
|
||||
type: ComponentType.StringSelect;
|
||||
}
|
||||
|
||||
@@ -6718,7 +6745,6 @@ export interface SelectMenuComponentOptionData {
|
||||
|
||||
export interface TextInputComponentData extends BaseComponentData {
|
||||
customId: string;
|
||||
label: string;
|
||||
maxLength?: number;
|
||||
minLength?: number;
|
||||
placeholder?: string;
|
||||
|
||||
@@ -2584,15 +2584,30 @@ await chatInputInteraction.showModal({
|
||||
custom_id: 'abc',
|
||||
components: [
|
||||
{
|
||||
components: [
|
||||
{
|
||||
custom_id: 'aa',
|
||||
label: 'label',
|
||||
style: TextInputStyle.Short,
|
||||
type: ComponentType.TextInput,
|
||||
},
|
||||
],
|
||||
type: ComponentType.ActionRow,
|
||||
component: {
|
||||
type: ComponentType.StringSelect,
|
||||
id: 2,
|
||||
custom_id: 'aa',
|
||||
options: [{ label: 'a', value: 'b' }],
|
||||
},
|
||||
type: ComponentType.Label,
|
||||
label: 'yo',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await chatInputInteraction.showModal({
|
||||
title: 'abc',
|
||||
customId: 'abc',
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.Label,
|
||||
component: {
|
||||
type: ComponentType.TextInput,
|
||||
style: TextInputStyle.Short,
|
||||
customId: 'aa',
|
||||
},
|
||||
label: 'yo',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user