@minesa-org/mini-interaction 0.0.6 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,37 @@
1
+ import { type APIFileUploadComponent } from "discord-api-types/v10";
2
+ import type { JSONEncodable } from "./shared.js";
3
+ /** Shape describing initial file upload data accepted by the builder. */
4
+ export type FileUploadBuilderData = {
5
+ customId?: string;
6
+ minValues?: number;
7
+ maxValues?: number;
8
+ required?: boolean;
9
+ };
10
+ /** Builder for Discord file upload components used in modals. */
11
+ export declare class FileUploadBuilder implements JSONEncodable<APIFileUploadComponent> {
12
+ private data;
13
+ /**
14
+ * Creates a new file upload builder with optional seed data.
15
+ */
16
+ constructor(data?: FileUploadBuilderData);
17
+ /**
18
+ * Sets the custom identifier for this file upload component.
19
+ */
20
+ setCustomId(customId: string): this;
21
+ /**
22
+ * Sets the minimum number of files that must be uploaded (min 0, max 10).
23
+ */
24
+ setMinValues(minValues: number): this;
25
+ /**
26
+ * Sets the maximum number of files that can be uploaded (max 10).
27
+ */
28
+ setMaxValues(maxValues: number): this;
29
+ /**
30
+ * Sets whether files are required before submitting the modal.
31
+ */
32
+ setRequired(required: boolean): this;
33
+ /**
34
+ * Serialises the builder into an API compatible file upload component payload.
35
+ */
36
+ toJSON(): APIFileUploadComponent;
37
+ }
@@ -0,0 +1,59 @@
1
+ import { ComponentType, } from "discord-api-types/v10";
2
+ /** Builder for Discord file upload components used in modals. */
3
+ export class FileUploadBuilder {
4
+ data;
5
+ /**
6
+ * Creates a new file upload builder with optional seed data.
7
+ */
8
+ constructor(data = {}) {
9
+ this.data = {
10
+ customId: data.customId,
11
+ minValues: data.minValues,
12
+ maxValues: data.maxValues,
13
+ required: data.required,
14
+ };
15
+ }
16
+ /**
17
+ * Sets the custom identifier for this file upload component.
18
+ */
19
+ setCustomId(customId) {
20
+ this.data.customId = customId;
21
+ return this;
22
+ }
23
+ /**
24
+ * Sets the minimum number of files that must be uploaded (min 0, max 10).
25
+ */
26
+ setMinValues(minValues) {
27
+ this.data.minValues = minValues;
28
+ return this;
29
+ }
30
+ /**
31
+ * Sets the maximum number of files that can be uploaded (max 10).
32
+ */
33
+ setMaxValues(maxValues) {
34
+ this.data.maxValues = maxValues;
35
+ return this;
36
+ }
37
+ /**
38
+ * Sets whether files are required before submitting the modal.
39
+ */
40
+ setRequired(required) {
41
+ this.data.required = required;
42
+ return this;
43
+ }
44
+ /**
45
+ * Serialises the builder into an API compatible file upload component payload.
46
+ */
47
+ toJSON() {
48
+ if (!this.data.customId) {
49
+ throw new Error("[FileUploadBuilder] custom_id is required.");
50
+ }
51
+ return {
52
+ type: ComponentType.FileUpload,
53
+ custom_id: this.data.customId,
54
+ min_values: this.data.minValues,
55
+ max_values: this.data.maxValues,
56
+ required: this.data.required,
57
+ };
58
+ }
59
+ }
@@ -0,0 +1,35 @@
1
+ import { type APILabelComponent, type APIComponentInLabel } from "discord-api-types/v10";
2
+ import type { JSONEncodable } from "./shared.js";
3
+ /** Values accepted when composing label components. */
4
+ export type LabelComponentLike = JSONEncodable<APIComponentInLabel> | APIComponentInLabel;
5
+ /** Shape describing initial label data accepted by the builder. */
6
+ export type LabelBuilderData = {
7
+ label?: string;
8
+ description?: string;
9
+ component?: LabelComponentLike;
10
+ };
11
+ /** Builder for Discord label components used in modals. */
12
+ export declare class LabelBuilder implements JSONEncodable<APILabelComponent> {
13
+ private data;
14
+ /**
15
+ * Creates a new label builder with optional seed data.
16
+ */
17
+ constructor(data?: LabelBuilderData);
18
+ /**
19
+ * Sets the label text (max 45 characters).
20
+ */
21
+ setLabel(label: string): this;
22
+ /**
23
+ * Sets the optional description text (max 100 characters).
24
+ */
25
+ setDescription(description: string): this;
26
+ /**
27
+ * Sets the component within the label.
28
+ * Can be a TextInput, SelectMenu, or FileUpload component.
29
+ */
30
+ setComponent(component: LabelComponentLike): this;
31
+ /**
32
+ * Serialises the builder into an API compatible label component payload.
33
+ */
34
+ toJSON(): APILabelComponent;
35
+ }
@@ -0,0 +1,55 @@
1
+ import { ComponentType, } from "discord-api-types/v10";
2
+ import { resolveJSONEncodable } from "./shared.js";
3
+ /** Builder for Discord label components used in modals. */
4
+ export class LabelBuilder {
5
+ data;
6
+ /**
7
+ * Creates a new label builder with optional seed data.
8
+ */
9
+ constructor(data = {}) {
10
+ this.data = {
11
+ label: data.label,
12
+ description: data.description,
13
+ component: data.component,
14
+ };
15
+ }
16
+ /**
17
+ * Sets the label text (max 45 characters).
18
+ */
19
+ setLabel(label) {
20
+ this.data.label = label;
21
+ return this;
22
+ }
23
+ /**
24
+ * Sets the optional description text (max 100 characters).
25
+ */
26
+ setDescription(description) {
27
+ this.data.description = description;
28
+ return this;
29
+ }
30
+ /**
31
+ * Sets the component within the label.
32
+ * Can be a TextInput, SelectMenu, or FileUpload component.
33
+ */
34
+ setComponent(component) {
35
+ this.data.component = component;
36
+ return this;
37
+ }
38
+ /**
39
+ * Serialises the builder into an API compatible label component payload.
40
+ */
41
+ toJSON() {
42
+ if (!this.data.label) {
43
+ throw new Error("[LabelBuilder] label is required.");
44
+ }
45
+ if (!this.data.component) {
46
+ throw new Error("[LabelBuilder] component is required.");
47
+ }
48
+ return {
49
+ type: ComponentType.Label,
50
+ label: this.data.label,
51
+ description: this.data.description,
52
+ component: resolveJSONEncodable(this.data.component),
53
+ };
54
+ }
55
+ }
@@ -1,7 +1,7 @@
1
- import type { APIModalInteractionResponseCallbackComponent, APIModalInteractionResponseCallbackData } from "discord-api-types/v10";
1
+ import type { APIModalInteractionResponseCallbackComponent, APIModalInteractionResponseCallbackData, APITextInputComponent, APIComponentInLabel, APISelectMenuComponent, APIFileUploadComponent } from "discord-api-types/v10";
2
2
  import type { JSONEncodable } from "./shared.js";
3
3
  /** Values accepted when composing modal component rows. */
4
- export type ModalComponentLike = JSONEncodable<APIModalInteractionResponseCallbackComponent> | APIModalInteractionResponseCallbackComponent;
4
+ export type ModalComponentLike = JSONEncodable<APIModalInteractionResponseCallbackComponent> | APIModalInteractionResponseCallbackComponent | JSONEncodable<APITextInputComponent> | APITextInputComponent | JSONEncodable<APIComponentInLabel> | APIComponentInLabel | JSONEncodable<APISelectMenuComponent> | APISelectMenuComponent | JSONEncodable<APIFileUploadComponent> | APIFileUploadComponent;
5
5
  /** Shape describing initial modal data accepted by the builder. */
6
6
  export type ModalBuilderData = {
7
7
  customId?: string;
@@ -1,3 +1,4 @@
1
+ import { ComponentType } from "discord-api-types/v10";
1
2
  import { resolveJSONEncodable } from "./shared.js";
2
3
  /** Builder for Discord modal interaction responses. */
3
4
  export class ModalBuilder {
@@ -56,10 +57,35 @@ export class ModalBuilder {
56
57
  if (this.components.length > 5) {
57
58
  throw new Error("[ModalBuilder] no more than 5 components can be provided.");
58
59
  }
60
+ // Auto-wrap components that need wrapping
61
+ const normalizedComponents = this.components.map((component) => {
62
+ const resolved = resolveJSONEncodable(component);
63
+ if (!resolved ||
64
+ typeof resolved !== "object" ||
65
+ !("type" in resolved)) {
66
+ return resolved;
67
+ }
68
+ const componentType = resolved.type;
69
+ // If it's a TextInput, SelectMenu, or FileUpload component, wrap it in an ActionRow
70
+ // (ActionRows in modals are deprecated, but still supported for backwards compatibility)
71
+ if (componentType === ComponentType.TextInput ||
72
+ componentType === ComponentType.StringSelect ||
73
+ componentType === ComponentType.UserSelect ||
74
+ componentType === ComponentType.RoleSelect ||
75
+ componentType === ComponentType.MentionableSelect ||
76
+ componentType === ComponentType.ChannelSelect ||
77
+ componentType === ComponentType.FileUpload) {
78
+ return {
79
+ type: ComponentType.ActionRow,
80
+ components: [resolved],
81
+ };
82
+ }
83
+ return resolved;
84
+ });
59
85
  return {
60
86
  custom_id: this.customId,
61
87
  title: this.title,
62
- components: this.components.map((component) => resolveJSONEncodable(component)),
88
+ components: normalizedComponents,
63
89
  };
64
90
  }
65
91
  }
@@ -80,9 +80,8 @@ export class TextInputBuilder {
80
80
  if (!this.data.customId) {
81
81
  throw new Error("[TextInputBuilder] custom_id is required.");
82
82
  }
83
- if (!this.data.label) {
84
- throw new Error("[TextInputBuilder] label is required.");
85
- }
83
+ // Note: label is optional when used inside a LabelComponent
84
+ // but required when used standalone in an ActionRow
86
85
  return {
87
86
  type: ComponentType.TextInput,
88
87
  custom_id: this.data.customId,
@@ -13,6 +13,10 @@ export { ModalBuilder } from "./ModalBuilder.js";
13
13
  export type { ModalBuilderData, ModalComponentLike } from "./ModalBuilder.js";
14
14
  export { TextInputBuilder } from "./TextInputBuilder.js";
15
15
  export type { TextInputBuilderData } from "./TextInputBuilder.js";
16
+ export { LabelBuilder } from "./LabelBuilder.js";
17
+ export type { LabelBuilderData, LabelComponentLike } from "./LabelBuilder.js";
18
+ export { FileUploadBuilder } from "./FileUploadBuilder.js";
19
+ export type { FileUploadBuilderData } from "./FileUploadBuilder.js";
16
20
  export { AutomodRuleBuilder } from "./AutomodRuleBuilder.js";
17
21
  export type { AutomodRuleBuilderData } from "./AutomodRuleBuilder.js";
18
22
  export { EmbedBuilder } from "./EmbedBuilder.js";
@@ -6,6 +6,8 @@ export { RoleSelectMenuBuilder } from "./RoleSelectMenuBuilder.js";
6
6
  export { ChannelSelectMenuBuilder } from "./ChannelSelectMenuBuilder.js";
7
7
  export { ModalBuilder } from "./ModalBuilder.js";
8
8
  export { TextInputBuilder } from "./TextInputBuilder.js";
9
+ export { LabelBuilder } from "./LabelBuilder.js";
10
+ export { FileUploadBuilder } from "./FileUploadBuilder.js";
9
11
  export { AutomodRuleBuilder } from "./AutomodRuleBuilder.js";
10
12
  export { EmbedBuilder } from "./EmbedBuilder.js";
11
13
  export { ContainerBuilder, SectionBuilder, TextDisplayBuilder, SeparatorBuilder, GalleryBuilder, GalleryItemBuilder, ThumbnailBuilder, } from "./MiniContainerBuilder.js";
@@ -185,6 +185,7 @@ export declare class MiniInteraction {
185
185
  private importCommandModule;
186
186
  /**
187
187
  * Dynamically imports and validates a component module from disk.
188
+ * Also handles modal components if they're in a "modals" subdirectory.
188
189
  */
189
190
  private importComponentModule;
190
191
  /**
@@ -545,6 +545,7 @@ export class MiniInteraction {
545
545
  }
546
546
  /**
547
547
  * Dynamically imports and validates a component module from disk.
548
+ * Also handles modal components if they're in a "modals" subdirectory.
548
549
  */
549
550
  async importComponentModule(absolutePath) {
550
551
  try {
@@ -554,11 +555,16 @@ export class MiniInteraction {
554
555
  imported.component ??
555
556
  imported.components ??
556
557
  imported.componentDefinition ??
558
+ imported.modal ??
559
+ imported.modals ??
557
560
  imported;
558
561
  const candidates = Array.isArray(candidate)
559
562
  ? candidate
560
563
  : [candidate];
561
564
  const components = [];
565
+ // Check if this file is in a "modals" subdirectory
566
+ const isModalFile = absolutePath.includes(path.sep + "modals" + path.sep) ||
567
+ absolutePath.includes("/modals/");
562
568
  for (const item of candidates) {
563
569
  if (!item || typeof item !== "object") {
564
570
  continue;
@@ -572,9 +578,18 @@ export class MiniInteraction {
572
578
  console.warn(`[MiniInteraction] Component module "${absolutePath}" is missing a "handler" function. Skipping.`);
573
579
  continue;
574
580
  }
575
- components.push({ customId, handler });
581
+ // If it's in a modals directory, register it as a modal
582
+ if (isModalFile) {
583
+ this.useModal({
584
+ customId,
585
+ handler: handler,
586
+ });
587
+ }
588
+ else {
589
+ components.push({ customId, handler });
590
+ }
576
591
  }
577
- if (components.length === 0) {
592
+ if (components.length === 0 && !isModalFile) {
578
593
  console.warn(`[MiniInteraction] Component module "${absolutePath}" did not export any valid components. Skipping.`);
579
594
  }
580
595
  return components;
@@ -755,6 +770,7 @@ export class MiniInteraction {
755
770
  },
756
771
  };
757
772
  }
773
+ await this.ensureComponentsLoaded();
758
774
  const handler = this.modalHandlers.get(customId);
759
775
  if (!handler) {
760
776
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minesa-org/mini-interaction",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "description": "Mini interaction, connecting your app with Discord via HTTP-interaction (Vercel support).",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",