discordjs-choice-selectmenu-builder
Version:
An utility builder to represent arrays as paginated Discord select menus.
1 lines • 48.5 kB
Source Map (JSON)
{"version":3,"sources":["../src/ChoiceSelectMenuBuilder.ts","../src/PageManager.ts"],"sourcesContent":["import {\r\n APISelectMenuOption,\r\n APIStringSelectComponent,\r\n ActionRowBuilder,\r\n ButtonBuilder,\r\n ButtonInteraction,\r\n ButtonStyle,\r\n Collection,\r\n Interaction,\r\n StringSelectMenuBuilder,\r\n StringSelectMenuInteraction\r\n} from 'discord.js';\r\nimport { PageManager } from './PageManager';\r\n\r\n/**\r\n * Represents a callback function that is passed to Array prototype methods such as\r\n * `.map()`, `.filter()` and `.forEach()` .\r\n */\r\nexport type ArrayCallback<T, ReturnValue> = (\r\n element: T,\r\n index: number,\r\n array: T[]\r\n) => ReturnValue;\r\n\r\ntype PageSelectComponent<ChoiceType> = {\r\n /**\r\n * The custom ID that this select menu uses.\r\n */\r\n customId?: string;\r\n /**\r\n * A collection of selected values and the index they are found at.\r\n * The key is used to ensure pagination is applied correctly\r\n * The values represent the selected items of the array.\r\n */\r\n selected: Collection<number, ChoiceType>;\r\n /**\r\n * The placeholder to display on the select menu.\r\n */\r\n placeholder?: string | ((minChoices: number, maxChoices: number) => string);\r\n /**\r\n * The callback function to transform an array element into a readable\r\n * label string. Note that discord's character limit on labels apply.\r\n * @param option - An element from the `options` array.\r\n * @param index - The element's index in the `options` array.\r\n * @returns - A string that must be below Discord's character limit for\r\n * select menu option labels.\r\n * @see {@link https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-option-structure}\r\n */\r\n labelFn: (option: ChoiceType, index: number) => string;\r\n /**\r\n * The callback function to transform an array element into a readable\r\n * description string. Note that discord's character limit on descriptions apply.\r\n * Will not create a description by default.\r\n * @param option - An element from the `options` array.\r\n * @param index - The element's index in the `options` array.\r\n * @returns - A string that must be below Discord's character limit for\r\n * select menu option descriptions.\r\n * @see {@link https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-option-structure}\r\n */\r\n descriptionFn?: (option: ChoiceType, index: number) => string;\r\n\r\n /**\r\n * Contains data related to pages and methods to change them.\r\n */\r\n pages: PageManager<ChoiceType>;\r\n /**\r\n * The button style for the nagivator buttons, which include\r\n * ⏮️,◀️,▶️, and ⏭️.\r\n * Note that navigator buttons only show up if there are more\r\n * options than the defined page length of this builder.\r\n */\r\n navigatorStyle: Exclude<ButtonStyle, ButtonStyle.Link>;\r\n /**\r\n * The button style for the center button displaying the available\r\n * pages.\r\n * Note that navigator buttons only show up if there are more\r\n * options than the defined page length of this builder.\r\n */\r\n pageLabelStyle: Exclude<ButtonStyle, ButtonStyle.Link>;\r\n};\r\n\r\ntype ChoiceSelectMenuBuilderLimits = {\r\n /**\r\n * The maximum amount of select menu options that\r\n * discord accepts. As of Jan 26 2025, it is 25.\r\n */\r\n MenuLength: 25;\r\n /**\r\n * The maximum amount of characters that an option label\r\n * may have. As of Jan 26 2025, it is 100.\r\n */\r\n OptionLabel: 100;\r\n /**\r\n * The maximum amount of characters that an option description\r\n * may have. As of Jan 26 2025, it is 100.\r\n */\r\n OptionDescription: 100;\r\n};\r\n\r\n/**\r\n * The three types of Tuples that are generated\r\n * by the UserChoiceComponent. It either includes\r\n * only the select menu, or it also includes\r\n * the page buttons. If there are no elements\r\n * present, it will return empty.\r\n */\r\ntype PageSelectMenuActionRow =\r\n | []\r\n | [menu: ActionRowBuilder<StringSelectMenuBuilder>]\r\n | [\r\n pageButtons: ActionRowBuilder<ButtonBuilder>,\r\n menu: ActionRowBuilder<StringSelectMenuBuilder>\r\n ];\r\n\r\n/**\r\n * Represents the three main types of setting values as selected.\r\n * - If the passed type is a function, that function will be called to set the values.\r\n * - If the passed type is an array, it will default to `Array#includes()`.\r\n * - Otherwise, `Object.is()` equality is used.\r\n */\r\nexport type ChoiceSelectCallback<ChoiceType> =\r\n | ChoiceType\r\n | ChoiceType[]\r\n | ArrayCallback<ChoiceType, boolean>;\r\n\r\nfunction trimString<T extends string | undefined>(input: T, len: number): T {\r\n if (input === undefined) return undefined as T;\r\n\r\n if (input.length < len - 4) {\r\n return input;\r\n } else {\r\n return (input.slice(0, len - 4) + ' ...') as T;\r\n }\r\n}\r\n\r\nexport class ChoiceSelectMenuBuilder<ChoiceType> {\r\n /**\r\n * Manages a select menu interface to select elements in an array.\r\n * @param choices - The array of choices to represent.\r\n * @param selected - A callback function that defines what values are chosen.\r\n */\r\n public constructor(\r\n choices: ChoiceType[],\r\n selected?: ChoiceSelectCallback<ChoiceType>\r\n ) {\r\n const collection = new Collection<number, ChoiceType>();\r\n this.options = choices;\r\n this.data = {\r\n selected: collection,\r\n labelFn: (value) => `${value}`,\r\n pages: new PageManager(choices, collection, 0),\r\n navigatorStyle: ButtonStyle.Primary,\r\n pageLabelStyle: ButtonStyle.Danger\r\n };\r\n\r\n if (typeof selected !== 'undefined') {\r\n this.addValues(selected);\r\n }\r\n }\r\n\r\n /**\r\n * Configure the discord limits that this builder uses.\r\n * As they may change over the years, this provides a way to\r\n * update them for compatability.\r\n * @param config The config options to update to new values. If a property is omitted, it is\r\n * reset to the initial value.\r\n */\r\n static configureLimits(config: Partial<ChoiceSelectMenuBuilderLimits>) {\r\n ChoiceSelectMenuBuilder.DiscordLimits = {\r\n MenuLength: config.MenuLength ?? 25,\r\n OptionDescription: config.OptionDescription ?? 100,\r\n OptionLabel: config.OptionLabel ?? 100\r\n };\r\n }\r\n\r\n /**\r\n * The limits that Discord enforces on certain select menu related properties.\r\n * Updated as of Jan 26, 2025.\r\n */\r\n protected static DiscordLimits: ChoiceSelectMenuBuilderLimits = {\r\n MenuLength: 25,\r\n OptionLabel: 100,\r\n OptionDescription: 100\r\n };\r\n protected static EnforceDiscordLimits: boolean = true;\r\n\r\n /**\r\n * Contains all data related to this builder instance.\r\n */\r\n data: PageSelectComponent<ChoiceType>;\r\n\r\n /**\r\n * The reference to the array this builder represents.\r\n */\r\n options: ChoiceType[];\r\n\r\n /**\r\n * Sets the custom ID of this builder.\r\n * @param {string} customId - The custom ID to set\r\n */\r\n public setCustomId(customId: string): this {\r\n this.data.customId = customId;\r\n return this;\r\n }\r\n\r\n /**\r\n * Set the minimum amount of choices of this builder. Defaults to\r\n * 0 for every new instance.\r\n * @param amount - The minimum amount of choices to select in this menu.\r\n */\r\n public setMinChoices(amount: number): this {\r\n const { DiscordLimits } = ChoiceSelectMenuBuilder;\r\n\r\n if (amount > DiscordLimits.MenuLength)\r\n throw new Error(\"MinChoices may not be above Discord's limit\");\r\n if (amount < 0) throw new Error('MinChoices must not be negative.');\r\n // empty array is an exception, since it wouldn't show a select menu\r\n if (this.options.length && amount > this.options.length)\r\n throw new Error(\r\n 'MinChoices must not exceed the amount of available options.'\r\n );\r\n\r\n this.data.pages.minChoices = amount;\r\n return this;\r\n }\r\n\r\n /**\r\n * Set the maximum amount of choices of this select menu.\r\n * @param amount - The maximum amount of choices to select in this menu.\r\n */\r\n public setMaxChoices(amount: number): this {\r\n if (amount <= 0) throw new Error('MaxChoices may not be 0 or lower.');\r\n // empty array is an exception, since it wouldn't show a select menu\r\n if (this.options.length && amount > this.options.length)\r\n throw new Error(\r\n 'MaxChoices must not exceed the amount of available options.'\r\n );\r\n this.data.pages.maxChoices = amount;\r\n return this;\r\n }\r\n\r\n /**\r\n * Sets the callback function to transform an array element into a readable\r\n * label string. Discord's label character limit applies.\r\n * @param labelFn - The callback function to transform the element.\r\n * The returned string must be below Discord's label character limit.\r\n * @see {@link https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-option-structure}\r\n */\r\n public setLabel(labelFn: PageSelectComponent<ChoiceType>['labelFn']): this {\r\n this.data.labelFn = labelFn;\r\n return this;\r\n }\r\n\r\n /**\r\n * Sets the callback function to transform an array element into a readable\r\n * description string. Discord's description character limit applies.\r\n * Will not create a description by default.\r\n * @param descriptionFn - The callback function to transform the element.\r\n * The returned string must be below Discord's description character limit.\r\n * @see {@link https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-option-structure}\r\n */\r\n public setDescription(\r\n descriptionFn: NonNullable<\r\n PageSelectComponent<ChoiceType>['descriptionFn']\r\n > | null\r\n ): this {\r\n if (descriptionFn === null) {\r\n delete this.data.descriptionFn;\r\n return this;\r\n }\r\n this.data.descriptionFn = descriptionFn;\r\n return this;\r\n }\r\n\r\n /**\r\n * Set the button styles for the navigator buttons.\r\n * @param style - The style to apply on the navigator buttons.\r\n */\r\n public setNavigatorStyle(\r\n style: Exclude<ButtonStyle, ButtonStyle.Link>\r\n ): this {\r\n this.data.navigatorStyle = style;\r\n return this;\r\n }\r\n\r\n /**\r\n * Set the button styles for the center button displaying the current page.\r\n * @param style - The style to apply on the center button displaying the current page.\r\n */\r\n public setPageLabelStyle(\r\n style: Exclude<ButtonStyle, ButtonStyle.Link>\r\n ): this {\r\n this.data.pageLabelStyle = style;\r\n return this;\r\n }\r\n\r\n /**\r\n * Set the placeholder of this builder's select menu.\r\n * @param placeholder - A static string to set as placeholder, or\r\n * a callback function to dynamically set the placeholder. Passes\r\n * the minimum and maximum choices of the current select menu.\r\n *\r\n * The placeholder must be below Discord's placeholder character limit.\r\n * @see {@link https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-menu-structure\r\n */\r\n public setPlaceholder(\r\n placeholder:\r\n | string\r\n | ((minChoices: number, maxChoices: number) => string)\r\n | null\r\n ): this {\r\n if (placeholder === null) {\r\n delete this.data.placeholder;\r\n return this;\r\n }\r\n this.data.placeholder = placeholder;\r\n return this;\r\n }\r\n\r\n /**\r\n * Set the selected values of this builder.\r\n * @param selected - A callback function or an object / array of objects to use.\r\n * - If the passed type is a function, that function will be called to set the values.\r\n * - If the passed type is an array, it will default to `Array#includes()`.\r\n * - Otherwise, `Object.is()` equality is used.\r\n */\r\n public setValues(selected: ChoiceSelectCallback<ChoiceType>): this {\r\n this.data.selected.clear();\r\n this.addValues(selected);\r\n return this;\r\n }\r\n\r\n /**\r\n * Add the selected values of this builder.\r\n * @param selected - A callback function or an object / array of objects to use.\r\n * - If the passed type is a function, that function will be called to set the values.\r\n * - If the passed type is an array, it will default to `Array#includes()`.\r\n * - Otherwise, `Object.is()` equality is used.\r\n */\r\n public addValues(selected: ChoiceSelectCallback<ChoiceType>): this {\r\n const selectFn = this.narrowSelectCallback(selected);\r\n const collection = this.data.selected;\r\n this.options.forEach((option, index, arr) => {\r\n if (selectFn(option, index, arr)) {\r\n collection.set(index, option);\r\n }\r\n });\r\n\r\n const maxChoices = this.data.pages.maxChoices ?? this.options.length;\r\n\r\n if (maxChoices < this.data.selected.size)\r\n throw new Error(\r\n 'Selected values exceed the configured maximum amount.'\r\n );\r\n return this;\r\n }\r\n\r\n /**\r\n * Filters the selected values based on the provided function.\r\n * @param valueFn - The callback function to use as filter. If this function\r\n * returns false, the selected value is removed from this menu.\r\n */\r\n public filterValues(\r\n valueFn: (value: ChoiceType, index: number) => boolean\r\n ): this {\r\n const filtered = this.data.selected.filter(valueFn);\r\n this.data.selected = filtered;\r\n this.data.pages.selected = filtered;\r\n return this;\r\n }\r\n\r\n /**\r\n * Clears all selected values from this menu.\r\n */\r\n public clearValues(): this {\r\n this.data.selected.clear();\r\n return this;\r\n }\r\n\r\n /**\r\n * Removes the last selected value and returns it.\r\n * If there are no selected values, it will return undefined.\r\n *\r\n * This value may be undefined even if `minChoices > 0`.\r\n */\r\n public popValue(): ChoiceType | undefined {\r\n const lastKey = this.data.selected.lastKey();\r\n if (typeof lastKey === 'undefined') return undefined;\r\n\r\n const value = this.data.selected.get(lastKey);\r\n this.data.selected.delete(lastKey);\r\n return value;\r\n }\r\n\r\n /**\r\n * Returns a shallow copy of options that are visible on the current page.\r\n * If no page is specified, it will return the current page.\r\n * @param page - The page to fetch options from.\r\n */\r\n public optionsOnPage(page: number = this.data.pages.current): ChoiceType[] {\r\n return this.data.pages.getPage(page).map((v) => v[1]);\r\n }\r\n\r\n /**\r\n * Determines the selected values on the current page. If no\r\n * parameter is provided, it will take the current page.\r\n *\r\n * Note that if `minChoices > 0`, selected values will always exist on the page.\r\n * @param page - The page to fetch the selected options from.\r\n */\r\n public selectedOnPage(onPage = this.data.pages.current): ChoiceType[] {\r\n const page = this.data.pages.getPage(onPage);\r\n return page\r\n .filter((v) => this.data.selected.has(v[0]))\r\n .map((v) => v[1]);\r\n }\r\n\r\n /**\r\n * The selected values of this select menu.\r\n * Returns a shallow copy of the provided choices.\r\n * If you only need the first property, consider using\r\n * {@link ChoiceSelectMenuBuilder#firstValue}\r\n *\r\n * This array may be empty even if `minChoices > 0`.\r\n */\r\n public get values(): ChoiceType[] {\r\n return [...this.data.selected.values()];\r\n }\r\n\r\n /**\r\n * The first selected value of this select menu.\r\n *\r\n * This value may be undefined even if `minChoices > 0`.\r\n */\r\n public get firstValue(): ChoiceType | undefined {\r\n return this.data.selected.first();\r\n }\r\n\r\n /**\r\n * Provides default function behaviour for non-functions passed to\r\n * methods.\r\n * ```\r\n * const selected = [1, 2, 3];\r\n * selectMenu.narrowSelectCallback(selected) // this is now (v) => selected.includes(v)\r\n * ```\r\n * @param selected - The provided value or function to narrow down into a select function.\r\n * @returns - A function callback that can be used in `Array.prototype.filter()` and the like.\r\n */\r\n private narrowSelectCallback(\r\n selected: ChoiceSelectCallback<ChoiceType> | undefined\r\n ): ArrayCallback<ChoiceType, boolean> {\r\n if (typeof selected === 'undefined') {\r\n return () => false;\r\n }\r\n if (Array.isArray(selected)) {\r\n return (v) => selected.includes(v);\r\n }\r\n if (typeof selected !== 'function') {\r\n return (v) => Object.is(v, selected);\r\n }\r\n return selected as ArrayCallback<ChoiceType, boolean>;\r\n }\r\n\r\n /**\r\n * Changes the paginated menu to the first page. If the maximum\r\n * amount of choices has been reached, it only skips to pages with\r\n * selections on it.\r\n */\r\n public toFirstPage(): this {\r\n this.data.pages.first();\r\n return this;\r\n }\r\n\r\n /**\r\n * Changes the paginated menu to the previous page. If the maximum\r\n * amount of choices has been reached, only skips to pages with\r\n * selections on it.\r\n */\r\n public toPreviousPage(): this {\r\n this.data.pages.previous();\r\n return this;\r\n }\r\n\r\n /**\r\n * Changes the paginated menu to the next page. If the maximum\r\n * amount of choices has been reached, only skips to pages with\r\n * selections on it.\r\n */\r\n public toNextPage(): this {\r\n this.data.pages.next();\r\n return this;\r\n }\r\n\r\n /**\r\n * Changes the paginated menu to the last page. If the maximum\r\n * amount of choices has been reached, it only skips to pages with\r\n * selections on it.\r\n */\r\n public toLastPage(): this {\r\n this.data.pages.last();\r\n return this;\r\n }\r\n\r\n /**\r\n * Creates the action row based on this builder.\r\n * If the `choices` array is empty, no select menu will be generated.\r\n * If the array exceeds discord's limit for select menus,\r\n * a second row of page buttons will be passed.\r\n */\r\n public toActionRow(): PageSelectMenuActionRow {\r\n // ----------------------------------\r\n // Below Select Menu Minimum\r\n if (this.options.length === 0) return [];\r\n if (typeof this.data.customId === 'undefined') {\r\n throw new Error(\r\n 'ChoiceSelectMenuBuilder.customId: expected a string primitive'\r\n );\r\n }\r\n\r\n const { customId, placeholder, selected, pages } = this.data;\r\n\r\n const currentMax = Math.min(\r\n // - maxChoices could be = this.options.length, so\r\n // cap at this.optionsAtPage().length\r\n // - if there's only one page, then\r\n // this.selected.length === this.selectedOnPage().length,\r\n // so they cancel each other out. This is only for pagination\r\n // purposes.\r\n (pages.maxChoices ?? this.options.length) -\r\n selected.size +\r\n this.selectedOnPage().length,\r\n this.optionsOnPage().length\r\n );\r\n\r\n const selectMenuData = {\r\n custom_id: customId,\r\n min_values: pages.minChoices,\r\n max_values: currentMax\r\n } as Partial<APIStringSelectComponent>;\r\n\r\n switch (typeof placeholder) {\r\n case 'function':\r\n selectMenuData.placeholder = placeholder(\r\n pages.minChoices,\r\n currentMax\r\n );\r\n break;\r\n case 'string':\r\n selectMenuData.placeholder = placeholder;\r\n break;\r\n default:\r\n break;\r\n }\r\n\r\n const selectMenu = new StringSelectMenuBuilder(selectMenuData);\r\n\r\n selectMenu.addOptions(\r\n pages.getPage().map(this.toAPISelectMenuOption, this)\r\n );\r\n\r\n if (pages.max === 0) {\r\n return [\r\n new ActionRowBuilder<StringSelectMenuBuilder>({\r\n components: [selectMenu]\r\n })\r\n ];\r\n }\r\n\r\n return [\r\n this.navigatorButtons,\r\n new ActionRowBuilder<StringSelectMenuBuilder>({\r\n components: [selectMenu]\r\n })\r\n ];\r\n }\r\n\r\n /**\r\n * Determines whether or not the interaction belongs to this builder.\r\n * If the interaction belongs to this builder, it handles the received\r\n * interaction response.\r\n * @param interaction - The component interaction response to check\r\n */\r\n public isInteraction(interaction: Interaction): boolean {\r\n if (!this.hasComponent(interaction)) return false;\r\n\r\n if (interaction.isButton()) {\r\n const getPageButtonId = interaction.customId.split('--')?.pop();\r\n switch (getPageButtonId) {\r\n case 'firstPage':\r\n this.toFirstPage();\r\n break;\r\n case 'prevPage':\r\n this.toPreviousPage();\r\n break;\r\n case 'nextPage':\r\n this.toNextPage();\r\n break;\r\n case 'lastPage':\r\n this.toLastPage();\r\n break;\r\n default:\r\n break;\r\n }\r\n return true;\r\n }\r\n\r\n this.updateSelectedFromValues(interaction.values);\r\n return true;\r\n }\r\n\r\n /**\r\n * Determines whether or not the interaction belongs to this builder.\r\n * @param interaction - The interaction to narrow\r\n */\r\n private hasComponent(\r\n interaction: Interaction\r\n ): interaction is ButtonInteraction | StringSelectMenuInteraction {\r\n if (interaction.isCommand() || interaction.isAutocomplete())\r\n return false;\r\n if (typeof this.data.customId === 'undefined') return false;\r\n return interaction.customId.startsWith(this.data.customId);\r\n }\r\n\r\n /**\r\n * Parses an array of values (from a select menu)\r\n * into the selected values. This assumes that the StringSelectMenuInteraction\r\n * belongs to this ChoiceSelectMenuBuilder. If that assumption is not met or there\r\n * is some issue with the custom IDs, they will be filtered out.\r\n * @param values - The values to transform into selected values.\r\n */\r\n private updateSelectedFromValues(values: string[]): void {\r\n const { pages } = this.data;\r\n if (!pages.carrySelected) {\r\n // remove keys on current page\r\n const currentPage = pages.getPage().map((v) => v[0]);\r\n const start = Math.min(...currentPage);\r\n const end = Math.max(...currentPage);\r\n this.filterValues((_, i) => i >= end || i < start);\r\n } else {\r\n this.data.selected.clear();\r\n }\r\n\r\n const idsOnPage = values\r\n .map((v) => Number(v.split('--')?.pop()))\r\n .filter((n) => !isNaN(n) && isFinite(n));\r\n\r\n for (const i of idsOnPage) {\r\n const selectedOption = this.options.at(i);\r\n if (typeof selectedOption === 'undefined') continue;\r\n this.data.selected.set(i, selectedOption);\r\n }\r\n }\r\n\r\n /**\r\n * Transforms the provided option into a usable API Select Menu Option.\r\n * @param row - The row to transform, including its index and element.\r\n */\r\n private toAPISelectMenuOption(\r\n row: [index: number, element: ChoiceType]\r\n ): APISelectMenuOption {\r\n const { OptionLabel, OptionDescription } =\r\n ChoiceSelectMenuBuilder.DiscordLimits;\r\n\r\n const [i, o] = row;\r\n return {\r\n label: trimString(this.data.labelFn(o, i), OptionLabel),\r\n description: trimString(\r\n this.data.descriptionFn?.(o, i),\r\n OptionDescription\r\n ),\r\n default: this.data.selected.has(i),\r\n value: `${this.data.customId}--${i}`\r\n } as APISelectMenuOption;\r\n }\r\n\r\n /**\r\n * Generates the page buttons for the currently selected page.\r\n * Disables buttons dependent on what page the user is on and\r\n * how many choices are remaining.\r\n */\r\n private get navigatorButtons(): ActionRowBuilder<ButtonBuilder> {\r\n //\r\n // Basic Button Template\r\n //\r\n const { customId, selected, pages, navigatorStyle, pageLabelStyle } =\r\n this.data;\r\n const currentPage = pages.getPage().map((v) => v[0]);\r\n const start = Math.min(...currentPage);\r\n const end = Math.max(...currentPage);\r\n const max = pages.max;\r\n // ----------------------------------\r\n // Page buttons logic\r\n let isAtStart = pages.current === 0;\r\n let isAtEnd = pages.current === max;\r\n\r\n if (\r\n !pages.carrySelected &&\r\n selected.size >= (pages.maxChoices ?? this.options.length)\r\n ) {\r\n // EDGE CASE SCENARIO\r\n // There are 3 pages and 5 max choices:\r\n // Page 1 - 0\r\n // Page 2 - 3 - Current\r\n // Page 3 - 2\r\n //\r\n // you must not select page 1, as you cannot have a maxchoice set of 0.\r\n // therefore, we check what pages have selections on them.\r\n isAtStart ||= selected.every((_, n) => n >= start);\r\n isAtEnd ||= selected.every((_, n) => n <= end);\r\n }\r\n return new ActionRowBuilder<ButtonBuilder>().addComponents(\r\n new ButtonBuilder()\r\n .setLabel('⏮️')\r\n .setStyle(navigatorStyle)\r\n .setDisabled(isAtStart)\r\n .setCustomId(`${customId}--firstPage`),\r\n new ButtonBuilder()\r\n .setLabel('◀️')\r\n .setStyle(navigatorStyle)\r\n .setDisabled(isAtStart)\r\n .setCustomId(`${customId}--prevPage`),\r\n // The center button displays what page you're currently on.\r\n new ButtonBuilder()\r\n .setLabel(`Page ${pages.current + 1}/${max + 1}`)\r\n .setStyle(pageLabelStyle)\r\n .setDisabled(true)\r\n .setCustomId('btn-never'),\r\n new ButtonBuilder()\r\n .setLabel('▶️')\r\n .setStyle(navigatorStyle)\r\n .setDisabled(isAtEnd)\r\n .setCustomId(`${customId}--nextPage`),\r\n new ButtonBuilder()\r\n .setLabel('⏭️')\r\n .setStyle(navigatorStyle)\r\n .setDisabled(isAtEnd)\r\n .setCustomId(`${customId}--lastPage`)\r\n );\r\n }\r\n}\r\n\r\n/**\r\n * @callback visualizeCallback\r\n * @template ChoiceType\r\n * @param {ChoiceType} option An element from the `options` array.\r\n * @param {number} index The element's index in the `options` array.\r\n */\r\n","import { Collection } from 'discord.js';\r\n\r\nexport class PageManager<T> {\r\n constructor(\r\n array: T[],\r\n selected: Collection<number, T>,\r\n minChoices: number,\r\n maxChoices?: number,\r\n currentPage = 0\r\n ) {\r\n this.current = currentPage;\r\n this.array = array;\r\n this.selected = selected;\r\n this.minChoices = minChoices;\r\n if (maxChoices) {\r\n this.maxChoices = maxChoices;\r\n }\r\n }\r\n\r\n /**\r\n * The length that a single select menu page can have.\r\n */\r\n length = 25;\r\n\r\n /**\r\n * A reference to the array to paginate.\r\n */\r\n array: T[];\r\n /**\r\n * A reference to the selected elements.\r\n */\r\n selected: Collection<number, T>;\r\n\r\n /**\r\n * The 0-indexed page the builder is currently on.\r\n * This is always in the range `0 <= current <= max`\r\n */\r\n current: number;\r\n /**\r\n * The minimum amount of choices that a user must make.\r\n * Note that it only prevents selecting less than this value, it\r\n * can still be visually shown without any selections.\r\n */\r\n minChoices: number;\r\n /**\r\n * The maximum amount of choices that a user may make.\r\n * This value defaults to `options.length`.\r\n */\r\n maxChoices?: number;\r\n\r\n get carrySelected(): boolean {\r\n return this.minChoices > 0;\r\n }\r\n\r\n /**\r\n * The maximum 0-indexed page the builder can reach.\r\n * This property is derived from the page's length\r\n */\r\n get max(): number {\r\n if (!this.carrySelected)\r\n return Math.ceil(this.array.length / this.length) - 1;\r\n\r\n /*\r\n Intuition:\r\n The selected values must exist on every page, so the page size\r\n will be reduced. However, spread across all pages, all the selected\r\n values exist. Therefore, we do not need to always remove the same\r\n amount per page.\r\n */\r\n const notSelected = this.array.length - this.selected.size;\r\n const newPageSize = this.length - this.selected.size;\r\n\r\n return Math.ceil(notSelected / newPageSize) - 1;\r\n }\r\n\r\n getPage(pageNumber?: number): [index: number, element: T][] {\r\n const page = Math.min(this.max, pageNumber ?? this.current);\r\n\r\n if (!this.carrySelected) {\r\n const start = page * this.length;\r\n const end = start + this.length;\r\n return this.getSlice(start, end, false);\r\n }\r\n\r\n const keys = [...this.selected.keys()];\r\n\r\n let currentPage = 0;\r\n let start = 0;\r\n\r\n while (currentPage < page) {\r\n start = this.getEndIndex(start, keys);\r\n currentPage++;\r\n }\r\n\r\n const end = this.getEndIndex(start, keys);\r\n\r\n const output = [\r\n ...this.selected.entries(),\r\n ...this.getSlice(start, end, true)\r\n ] as [number, T][];\r\n\r\n if (output.length > this.length)\r\n throw new Error(\r\n `Generated page exceeds set page length.\\nExpected: <Array>.length <= ${this.length}\\nActual: ${output.length}`\r\n );\r\n return output;\r\n }\r\n\r\n /**\r\n * Returns a slice of the array, along with its actual index location.\r\n * @param start - The start of the slice.\r\n * @param end - The end of the slice.\r\n * @param removeSelected - Removes selected indeces from the sliced array.\r\n */\r\n private getSlice(\r\n start: number,\r\n end: number,\r\n removeSelected: boolean\r\n ): [index: number, element: T][] {\r\n const newSlice: [number, T][] = this.array\r\n .slice(start, end)\r\n .map((e, i) => [i + start, e]);\r\n\r\n if (!removeSelected) return newSlice;\r\n return newSlice\r\n .filter(([i, _]) => !this.selected.has(i))\r\n .slice(0, this.length - this.selected.size);\r\n }\r\n\r\n /**\r\n * Get the end index of the page starting from the start parameter.\r\n * Will include more elements if the keys exist on the page.\r\n * @param start - The offset to base the end index on.\r\n * @param selectedIndeces - The selected indeces\r\n */\r\n private getEndIndex(start: number, selectedIndeces: number[]): number {\r\n let end = start + this.length;\r\n const withinBounds = selectedIndeces.filter(\r\n (i) => i >= start && i < end\r\n ).length;\r\n return end - selectedIndeces.length + withinBounds;\r\n }\r\n\r\n public first(): void {\r\n if (this.hasFreePageMovement()) {\r\n this.current = 0;\r\n return;\r\n }\r\n\r\n // we want to avoid exceeding our maxChoices. Therefore, if we have\r\n // already a full amount of choices, we only go back the first\r\n // page that has selections on it.\r\n\r\n // maxChoices is always > 0, so selected.size cannot be 0 from if guard above\r\n const minSelected = Math.min(...this.selected.keys());\r\n if (!isFinite(minSelected)) return;\r\n\r\n this.current = Math.ceil(minSelected / this.length);\r\n\r\n if (minSelected % this.length !== 0) {\r\n this.current -= 1;\r\n }\r\n }\r\n\r\n public previous(): void {\r\n if (this.hasFreePageMovement()) {\r\n this.current = Math.max(0, this.current - 1);\r\n return;\r\n }\r\n\r\n // we want to avoid exceeding our maxChoices. Therefore, if we have\r\n // already a full amount of choices, we only go back to the closest\r\n // page that has selections on it.\r\n const currentPageStart = this.current * this.length;\r\n // maxChoices is always > 0, so selected.size cannot be 0 from if guard above\r\n const maxPreviousIndex = Math.max(\r\n ...this.selected.filter((_, n) => n < currentPageStart).keys()\r\n );\r\n if (!isFinite(maxPreviousIndex)) return;\r\n\r\n this.current = Math.ceil(maxPreviousIndex / this.length);\r\n\r\n if (maxPreviousIndex % this.length !== 0) {\r\n this.current -= 1;\r\n }\r\n }\r\n\r\n public next(): void {\r\n const max = this.max;\r\n if (this.hasFreePageMovement()) {\r\n this.current = Math.min(max, this.current + 1);\r\n return;\r\n }\r\n\r\n // we want to avoid exceeding our maxChoices. Therefore, if we have\r\n // already a full amount of choices, we only go forward to the closest\r\n // page that has selections on it.\r\n const currentPageEnd = this.current * this.length + this.length;\r\n const minNextIndex = Math.min(\r\n ...this.selected.filter((_, n) => n >= currentPageEnd).keys()\r\n );\r\n if (!isFinite(minNextIndex)) return;\r\n\r\n this.current = Math.ceil(minNextIndex / this.length);\r\n\r\n if (minNextIndex % this.length !== 0) {\r\n this.current -= 1;\r\n }\r\n }\r\n\r\n public last(): void {\r\n const max = this.max;\r\n if (this.hasFreePageMovement()) {\r\n this.current = max;\r\n return;\r\n }\r\n\r\n // we want to avoid exceeding our maxChoices. Therefore, if we have\r\n // already a full amount of choices, we only go forward to the last\r\n // page that has selections on it.\r\n const maxSelected = Math.max(...this.selected.keys());\r\n if (!isFinite(maxSelected)) return;\r\n // maxChoices is always > 0, so selected.size cannot be 0 from if guard above\r\n this.current = Math.ceil(maxSelected / this.length);\r\n\r\n if (maxSelected % this.length !== 0) {\r\n this.current -= 1;\r\n }\r\n }\r\n\r\n private hasFreePageMovement(): boolean {\r\n return (\r\n // carrySelected follows the user no matter where\r\n this.carrySelected ||\r\n // no pagination\r\n this.array.length <= this.length ||\r\n // maxChoices has not yet been filled\r\n this.selected.size < (this.maxChoices ?? this.array.length)\r\n );\r\n }\r\n}\r\n"],"mappings":"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","names":["start","end"]}