{"_id":"@avion-block/discord-modals","name":"@avion-block/discord-modals","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@avion-block/discord-modals","version":"1.0.0","description":"Utilities and builders for creating and handling Discord modals in discord.js v14.","type":"commonjs","main":"dist/index.js","types":"dist/index.d.ts","keywords":["discord","discord.js","discordjs","modals","discord-modals","discord-components"],"scripts":{"build":"tsc -p tsconfig.json","clean":"rimraf dist","prepublishOnly":"npm run clean && npm run build","eslint-fix":"eslint src/ --fix --ext .ts --cache","eslint-err":"eslint src/ --ext .ts --cache"},"peerDependencies":{"discord.js":"^14"},"devDependencies":{"discord-api-types":"^0.38.40","discord.js":"^14.25.1","eslint":"10.0.2","rimraf":"^6.1.3","typescript":"^5.9.3"},"repository":{"type":"git","url":"git+https://github.com/AvionBlock/discord-modals.git"},"bugs":{"url":"https://github.com/AvionBlock/discord-modals/issues"},"homepage":"https://github.com/AvionBlock/discord-modals#readme","license":"ISC","packageManager":"pnpm@10.30.3+sha512.c961d1e0a2d8e354ecaa5166b822516668b7f44cb5bd95122d590dd81922f606f5473b6d23ec4a5be05e7fcd18e8488d47d978bbe981872f1145d06e9a740017","gitHead":"ce7983160ebfce859de99d3259c25d931d1b6749","_id":"@avion-block/discord-modals@1.0.0","_nodeVersion":"24.13.1","_npmVersion":"11.8.0","dist":{"integrity":"sha512-RsE0NfMsPTXDZOxWNbtU1lmt+OHGBHZJedmz8xPR+U2yFqqvkyOIf9ooGiYKetTCsJjSYcopOEcpsC5CuyLEaQ==","shasum":"dfc1c222781ff87e6a2ed448db931990249bda45","tarball":"https://registry.npmjs.org/@avion-block/discord-modals/-/discord-modals-1.0.0.tgz","fileCount":95,"unpackedSize":160126,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDvtXVHzzpH1hTrfm9Z7pjH3TH7skaVyNz4dZ/u16UwkwIgLKohPpP9TQRk/nhIODpdm3tgqJuPHTPjcNaxrgsfSvI="}]},"_npmUser":{"name":"avion_team","email":"git@avion.team"},"directories":{},"maintainers":[{"name":"avion_team","email":"git@avion.team"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/discord-modals_1.0.0_1772407557523_0.2869584456361334"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-01T23:25:57.432Z","1.0.0":"2026-03-01T23:25:57.717Z","modified":"2026-03-01T23:25:57.911Z"},"maintainers":[{"name":"avion_team","email":"git@avion.team"}],"description":"Utilities and builders for creating and handling Discord modals in discord.js v14.","homepage":"https://github.com/AvionBlock/discord-modals#readme","keywords":["discord","discord.js","discordjs","modals","discord-modals","discord-components"],"repository":{"type":"git","url":"git+https://github.com/AvionBlock/discord-modals.git"},"bugs":{"url":"https://github.com/AvionBlock/discord-modals/issues"},"license":"ISC","readme":"# discord-modals (Modal v2 / Label-based)\n\nLightweight helper library for **discord.js v14** to:\n\n- listen for **Modal Submit** interactions via the raw gateway event,\n- emit a typed `client.on(\"modalSubmit\", ...)` event,\n- provide a set of **Modal v2 builders** (Label-based format),\n- simplify replying / deferring / updating and showing modals.\n\n> ⚠️ **discord.js v14 only**.\n\n---\n\n## Features\n\n- ✅ `init(client)` wires raw `INTERACTION_CREATE` → emits `modalSubmit`\n- ✅ `ModalSubmitInteraction` wrapper:\n  - `fields`, `selectMenus`, and `components` (supports legacy + new payloads)\n  - helpers: `getTextInputValue`, `getField`, `getSelectMenuValues`, `getSelectMenu`\n  - response helpers mixin: `reply`, `deferReply`, `editReply`, `followUp`, `update`, `showModal`\n- ✅ Modal builders:\n  - `Modal`\n  - `ModalLabel`\n  - `TextInputComponent`\n  - `SelectMenuComponent`\n- 🧩 Backward-compat:\n  - accepts raw/legacy modal components and tries to normalize to `Label`\n  - `ModalActionRow` is kept for compatibility, but **deprecated**\n\n---\n\n## Install\n\n```bash\nnpm i @avion-block/discord-modals\n```\n\nPeer deps:\n\n- `discord.js` v14\n\n---\n\n## Quick start\n\n### 1) Initialize once\n\n```ts\nimport { Client, GatewayIntentBits } from \"discord.js\";\nimport initModals from \"@avion-block/discord-modals\";\n\nconst client = new Client({ intents: [GatewayIntentBits.Guilds] });\n\n// Attach the raw interaction listener\ninitModals(client);\n\nclient.login(process.env.DISCORD_TOKEN);\n```\n\n### 2) Listen to `modalSubmit`\n\n```ts\nclient.on(\"modalSubmit\", async (modal) => {\n  const name = modal.getTextInputValue(\"name\");\n  const roleIds = modal.getSelectMenuValues(\"roles\") ?? [];\n\n  await modal.reply({\n    content: `Name: ${name ?? \"(empty)\"}\\nRoles: ${roleIds.join(\", \") || \"(none)\"}`,\n    ephemeral: true,\n  });\n});\n```\n\n---\n\n## Showing a modal\n\nYou can show a modal using:\n\n- `modal.showModal(modalBuilderOrJson)` (available on the interaction wrapper)\n- or `showModal(modal, { client, interaction })`\n\n### Using the builder\n\n```ts\nimport {\n  Modal,\n  ModalLabel,\n  TextInputComponent,\n} from \"@avion-block/discord-modals\";\n\nclient.on(\"interactionCreate\", async (interaction) => {\n  if (!interaction.isChatInputCommand()) return;\n  if (interaction.commandName !== \"form\") return;\n\n  const modal = new Modal()\n    .setCustomId(\"my_form\")\n    .setTitle(\"Example form\")\n    .addLabelComponents(\n      new ModalLabel().setLabel(\"Your name\").setComponent(\n        new TextInputComponent()\n          .setCustomId(\"name\")\n          .setLabel(\"Name\") // used for inference / compatibility\n          .setPlaceholder(\"Type your name...\")\n          .setRequired(true),\n      ),\n    );\n\n  // If you already use this library's InteractionResponses mixin, you can:\n  // await (interaction as any).showModal(modal);\n  //\n  // Otherwise use the exported helper:\n  const { showModal } = await import(\"@avion-block/discord-modals\");\n  await showModal(modal, { client: interaction.client, interaction });\n});\n```\n\n### Using raw JSON\n\n```ts\nimport { showModal } from \"@avion-block/discord-modals\";\n\nawait showModal(\n  {\n    title: \"Raw modal\",\n    custom_id: \"raw_modal\",\n    components: [\n      {\n        type: 18,\n        label: \"Email\",\n        component: {\n          type: 4,\n          custom_id: \"email\",\n          label: \"Email\",\n          style: 1,\n          required: true,\n        },\n      },\n    ],\n  },\n  { client, interaction },\n);\n```\n\n---\n\n## Builders\n\n### `Modal`\n\n```ts\nconst modal = new Modal()\n  .setCustomId(\"settings\")\n  .setTitle(\"Settings\")\n  .addComponents(\n    // You can pass raw components: they’ll be wrapped into ModalLabel automatically.\n    new TextInputComponent().setCustomId(\"bio\").setLabel(\"Bio\"),\n  );\n```\n\nNotes:\n\n- Internally `Modal.components` stores `ModalLabel[]` (Modal v2 format).\n- If you add a raw component, the library wraps it in a label and tries to infer label text\n  from `component.label` or `component.placeholder`. If it can’t infer, it falls back to `\"Field\"`.\n\n### `ModalLabel`\n\n```ts\nconst label = new ModalLabel()\n  .setLabel(\"Username\")\n  .setDescription(\"This will be displayed publicly.\")\n  .setComponent(\n    new TextInputComponent().setCustomId(\"username\").setLabel(\"Username\"),\n  );\n```\n\n### `TextInputComponent`\n\n```ts\nimport { TextInputStyle } from \"discord-api-types/v10\";\n\nconst input = new TextInputComponent()\n  .setCustomId(\"feedback\")\n  .setLabel(\"Feedback\")\n  .setStyle(TextInputStyle.Paragraph) // or \"LONG\" / 2\n  .setMinLength(10)\n  .setMaxLength(500)\n  .setPlaceholder(\"Write your feedback...\")\n  .setRequired(true);\n```\n\n### `SelectMenuComponent` (StringSelect)\n\n```ts\nconst select = new SelectMenuComponent()\n  .setCustomId(\"color\")\n  .setPlaceholder(\"Pick a color\")\n  .setMinValues(1)\n  .setMaxValues(1)\n  .addOptions(\n    { label: \"Red\", value: \"red\" },\n    { label: \"Green\", value: \"green\" },\n  );\n```\n\n> Tip: for label inference on selects, make sure to set `placeholder` (or set label explicitly).\n\n---\n\n## Handling submitted values\n\nInside `client.on(\"modalSubmit\", modal => ...)`:\n\n### Text inputs\n\n```ts\nconst value = modal.getTextInputValue(\"name\"); // string | null\nconst field = modal.getField(\"name\"); // ModalSubmitField | null\n```\n\n### Select menus\n\n```ts\nconst values = modal.getSelectMenuValues(\"roles\"); // string[] | null\nconst menu = modal.getSelectMenu(\"roles\"); // ModalSubmitSelectMenu | null\n```\n\n### Where values are stored\n\n- `modal.fields: ModalSubmitField[]` (type 4 components)\n- `modal.selectMenus: ModalSubmitSelectMenu[]` (select components)\n- `modal.components: (ModalLabel | any)[]`\n  - wraps Label (type 18) into `ModalLabel` when possible\n  - keeps unknown/legacy structures as raw `any`\n\n---\n\n## Interaction response helpers (Mixin)\n\n`ModalSubmitInteraction` includes response helpers:\n\n```ts\nawait modal.deferReply({ ephemeral: true });\nawait modal.editReply({ content: \"Updated!\" });\nawait modal.followUp({ content: \"Follow up message\" });\n```\n\nAlso supports:\n\n- `reply`, `fetchReply`, `deleteReply`\n- `deferUpdate`, `update` (for message-component interactions)\n\n---\n\n## Deprecated: `ModalActionRow`\n\nDiscord Modal v2 uses **Label (type 18)**, not ActionRow.  \n`ModalActionRow` exists only for backward compatibility.\n\n```ts\nimport { ModalActionRow } from \"@avion-block/discord-modals\";\n\n/\\*\\*\n\n- @deprecated Prefer ModalLabel + component builders.\n  \\*/\n  const row = new ModalActionRow().addComponent(\n  new TextInputComponent().setCustomId(\"x\").setLabel(\"X\"),\n  );\n```\n\n---\n\n## Utilities\n\n- `Util.verifyString(...)`\n- `Util.parseEmoji(...)`\n- `Util.resolvePartialEmoji(...)`\n- `SnowflakeUtil.generate(...)`, `SnowflakeUtil.timestampFrom(...)`, etc.\n\n---\n\n## TypeScript typings\n\nThe library augments `discord.js` Client with:\n\n```ts\nclient.on(\"modalSubmit\", (modal) => { ... });\n```\n\n---\n\n## License\n\nMIT\n","readmeFilename":"README.md","_rev":"1-48a4b431f3de7142f48ee1dd4f5410ae"}