mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-09 16:13:31 +01:00
feat: label component and select in modal for v14 (#11090)
* feat: handle recieve label components * chore: missed fixes * fix: missing id when transforming * chore: add missing things * fix: test * feat: send label * fix: un-break it * chore: test * chore: missing required in typings
This commit is contained in:
@@ -162,6 +162,7 @@ exports.InteractionWebhook = require('./structures/InteractionWebhook');
|
||||
exports.Invite = require('./structures/Invite');
|
||||
exports.InviteStageInstance = require('./structures/InviteStageInstance');
|
||||
exports.InviteGuild = require('./structures/InviteGuild');
|
||||
exports.LabelComponent = require('./structures/LabelComponent');
|
||||
exports.Message = require('./structures/Message').Message;
|
||||
exports.Attachment = require('./structures/Attachment');
|
||||
exports.AttachmentBuilder = require('./structures/AttachmentBuilder');
|
||||
|
||||
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 { Component } = require('./Component.js');
|
||||
const { createComponent } = require('../util/Components.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() };
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = LabelComponent;
|
||||
@@ -11,7 +11,8 @@ class ModalSubmitFields {
|
||||
constructor(components) {
|
||||
/**
|
||||
* The components within the modal
|
||||
* @type {ActionRowModalData[]}
|
||||
*
|
||||
* @type {Array<ActionRowModalData | LabelModalData>}
|
||||
*/
|
||||
this.components = components;
|
||||
|
||||
@@ -20,7 +21,16 @@ class ModalSubmitFields {
|
||||
* @type {Collection<string, ModalData>}
|
||||
*/
|
||||
this.fields = components.reduce((accumulator, next) => {
|
||||
next.components.forEach(component => 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());
|
||||
}
|
||||
@@ -50,6 +60,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;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ModalSubmitFields;
|
||||
|
||||
@@ -9,16 +9,38 @@ const InteractionResponses = require('./interfaces/InteractionResponses');
|
||||
const getMessage = lazy(() => require('./Message').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
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -47,7 +69,8 @@ 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));
|
||||
|
||||
@@ -88,16 +111,32 @@ class ModalSubmitInteraction extends BaseInteraction {
|
||||
* @returns {ModalData[]}
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,13 +15,6 @@ const MessagePayload = require('../MessagePayload');
|
||||
let deprecationEmittedForEphemeralOption = false;
|
||||
let deprecationEmittedForFetchReplyOption = false;
|
||||
|
||||
/**
|
||||
* @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.
|
||||
* @interface
|
||||
|
||||
@@ -14,6 +14,20 @@ 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 {Array<ActionRow | LabelData>} components The components within this modal
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {BaseComponentData} LabelData
|
||||
* @property {string} label The label to use
|
||||
* @property {string} [description] The optional description for the label
|
||||
* @property {StringSelectMenuComponentData|TextInputComponentData} component The component within the label
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {BaseComponentData} ButtonComponentData
|
||||
* @property {ButtonStyle} style The style of the button
|
||||
@@ -24,6 +38,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
|
||||
@@ -199,6 +224,7 @@ const ChannelSelectMenuComponent = require('../structures/ChannelSelectMenuCompo
|
||||
const Component = require('../structures/Component');
|
||||
const ContainerComponent = require('../structures/ContainerComponent');
|
||||
const FileComponent = require('../structures/FileComponent');
|
||||
const LabelComponent = require('../structures/LabelComponent');
|
||||
const MediaGalleryComponent = require('../structures/MediaGalleryComponent');
|
||||
const MentionableSelectMenuBuilder = require('../structures/MentionableSelectMenuBuilder');
|
||||
const MentionableSelectMenuComponent = require('../structures/MentionableSelectMenuComponent');
|
||||
@@ -231,6 +257,7 @@ const ComponentTypeToComponent = {
|
||||
[ComponentType.Section]: SectionComponent,
|
||||
[ComponentType.Separator]: SeparatorComponent,
|
||||
[ComponentType.Thumbnail]: ThumbnailComponent,
|
||||
[ComponentType.Label]: LabelComponent,
|
||||
};
|
||||
|
||||
const ComponentTypeToBuilder = {
|
||||
|
||||
56
packages/discord.js/typings/index.d.ts
vendored
56
packages/discord.js/typings/index.d.ts
vendored
@@ -55,6 +55,7 @@ import {
|
||||
APIInteractionDataResolvedChannel,
|
||||
APIInteractionDataResolvedGuildMember,
|
||||
APIInteractionGuildMember,
|
||||
APILabelComponent,
|
||||
APIMessage,
|
||||
APIMessageComponent,
|
||||
APIOverwrite,
|
||||
@@ -352,6 +353,14 @@ export class ActionRowBuilder<
|
||||
): ActionRowBuilder<ComponentType>;
|
||||
}
|
||||
|
||||
export type ComponentInLabelData = StringSelectMenuComponentData | TextInputComponentData;
|
||||
|
||||
export interface LabelData extends BaseComponentData {
|
||||
component: ComponentInLabelData;
|
||||
description?: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export type MessageActionRowComponent =
|
||||
| ButtonComponent
|
||||
| StringSelectMenuComponent
|
||||
@@ -912,6 +921,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;
|
||||
@@ -2757,33 +2772,49 @@ export interface ModalComponentData {
|
||||
customId: string;
|
||||
title: string;
|
||||
components: readonly (
|
||||
| JSONEncodable<APIActionRowComponent<APIComponentInModalActionRow>>
|
||||
| JSONEncodable<APIActionRowComponent<APIComponentInModalActionRow> | APILabelComponent>
|
||||
| ActionRowData<ModalActionRowComponentData>
|
||||
| LabelData
|
||||
)[];
|
||||
}
|
||||
|
||||
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 {
|
||||
type: ComponentType.ActionRow;
|
||||
components: readonly TextInputModalData[];
|
||||
}
|
||||
|
||||
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): { type: Type } & TextInputModalData;
|
||||
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>
|
||||
@@ -2807,7 +2838,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;
|
||||
@@ -4036,6 +4067,8 @@ export class Formatters extends null {
|
||||
export type ComponentData =
|
||||
| MessageActionRowComponentData
|
||||
| ModalActionRowComponentData
|
||||
| LabelData
|
||||
| ComponentInLabelData
|
||||
| ComponentInContainerData
|
||||
| ContainerComponentData
|
||||
| ThumbnailComponentData;
|
||||
@@ -7238,6 +7271,7 @@ export interface BaseSelectMenuComponentData extends BaseComponentData {
|
||||
export interface StringSelectMenuComponentData extends BaseSelectMenuComponentData {
|
||||
type: ComponentType.StringSelect;
|
||||
options: readonly SelectMenuComponentOptionData[];
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
export interface UserSelectMenuComponentData extends BaseSelectMenuComponentData {
|
||||
|
||||
@@ -2566,6 +2566,34 @@ chatInputInteraction.showModal({
|
||||
],
|
||||
});
|
||||
|
||||
chatInputInteraction.showModal({
|
||||
title: 'abc',
|
||||
custom_id: 'abc',
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.Label,
|
||||
label: 'label',
|
||||
component: {
|
||||
custom_id: 'aa',
|
||||
type: ComponentType.TextInput,
|
||||
style: TextInputStyle.Short,
|
||||
label: 'label',
|
||||
},
|
||||
},
|
||||
{
|
||||
components: [
|
||||
{
|
||||
custom_id: 'aa',
|
||||
label: 'label',
|
||||
style: TextInputStyle.Short,
|
||||
type: ComponentType.TextInput,
|
||||
},
|
||||
],
|
||||
type: ComponentType.ActionRow,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
declare const stringSelectMenuData: APIStringSelectComponent;
|
||||
StringSelectMenuBuilder.from(stringSelectMenuData);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user