Merge pull request #30 from itsMapleLeaf/renderer-bug-fixes
This commit is contained in:
5
.changeset/blue-steaks-explode.md
Normal file
5
.changeset/blue-steaks-explode.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"reacord": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
ensure message is edited from arbitrary component updates
|
||||||
5
.changeset/fifty-vans-fold.md
Normal file
5
.changeset/fifty-vans-fold.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"reacord": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix interaction handling
|
||||||
5
.changeset/fluffy-cycles-beg.md
Normal file
5
.changeset/fluffy-cycles-beg.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"reacord": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix DJS deprecation warning on isStringSelectMenu
|
||||||
5
.changeset/modern-shirts-kneel.md
Normal file
5
.changeset/modern-shirts-kneel.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"reacord": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
ensure action rows handle child interactions
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
"astro-sync": "pnpm --filter website exec astro sync",
|
"astro-sync": "pnpm --filter website exec astro sync",
|
||||||
"format": "run-s --continue-on-error format:*",
|
"format": "run-s --continue-on-error format:*",
|
||||||
"format:eslint": "eslint . --report-unused-disable-directives --fix",
|
"format:eslint": "eslint . --report-unused-disable-directives --fix",
|
||||||
"format:prettier": "prettier --cache --write .",
|
"format:prettier": "prettier --cache --write . \"**/*.astro\"",
|
||||||
"test": "vitest",
|
"test": "vitest",
|
||||||
"build": "pnpm -r run build",
|
"build": "pnpm -r run build",
|
||||||
"build:website": "pnpm --filter website... run build",
|
"build:website": "pnpm --filter website... run build",
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ export function pruneNullishValues<T>(input: T): PruneNullishValues<T> {
|
|||||||
if (Array.isArray(input)) {
|
if (Array.isArray(input)) {
|
||||||
return input
|
return input
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.map((item) => pruneNullishValues(item)) as PruneNullishValues<T>
|
.map(
|
||||||
|
(item) => pruneNullishValues(item) as unknown,
|
||||||
|
) as PruneNullishValues<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
const result: Record<string, unknown> = {}
|
const result: Record<string, unknown> = {}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export function withLoggedMethodCalls<T extends object>(value: T) {
|
|||||||
)
|
)
|
||||||
.join(", ")})`,
|
.join(", ")})`,
|
||||||
)
|
)
|
||||||
return value.apply(target, values)
|
return value.apply(target, values) as unknown
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}) as T
|
}) as T
|
||||||
|
|||||||
@@ -97,6 +97,6 @@ export interface UserInfo {
|
|||||||
username: string
|
username: string
|
||||||
discriminator: string
|
discriminator: string
|
||||||
tag: string
|
tag: string
|
||||||
avatarUrl: string
|
avatarUrl: string | null
|
||||||
accentColor?: number
|
accentColor?: number
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type { ReactNode } from "react"
|
|||||||
import { ReacordElement } from "../../internal/element.js"
|
import { ReacordElement } from "../../internal/element.js"
|
||||||
import type { MessageOptions } from "../../internal/message"
|
import type { MessageOptions } from "../../internal/message"
|
||||||
import { Node } from "../../internal/node.js"
|
import { Node } from "../../internal/node.js"
|
||||||
|
import type { ComponentInteraction } from "../../internal/interaction.js"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Props for an action row
|
* Props for an action row
|
||||||
@@ -44,4 +45,12 @@ class ActionRowNode extends Node<ActionRowProps> {
|
|||||||
child.modifyMessageOptions(options)
|
child.modifyMessageOptions(options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
handleComponentInteraction(interaction: ComponentInteraction) {
|
||||||
|
for (const child of this.children) {
|
||||||
|
if (child.handleComponentInteraction(interaction)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import type {
|
|||||||
import { Node } from "../../internal/node.js"
|
import { Node } from "../../internal/node.js"
|
||||||
import type { ComponentEvent } from "../component-event"
|
import type { ComponentEvent } from "../component-event"
|
||||||
import { OptionNode } from "./option-node"
|
import { OptionNode } from "./option-node"
|
||||||
|
import { omit } from "@reacord/helpers/omit.js"
|
||||||
|
|
||||||
/** @category Select */
|
/** @category Select */
|
||||||
export interface SelectProps {
|
export interface SelectProps {
|
||||||
@@ -102,12 +103,13 @@ class SelectNode extends Node<SelectProps> {
|
|||||||
values,
|
values,
|
||||||
minValues = 0,
|
minValues = 0,
|
||||||
maxValues = 25,
|
maxValues = 25,
|
||||||
children,
|
|
||||||
onChange,
|
|
||||||
onChangeValue,
|
|
||||||
onChangeMultiple,
|
|
||||||
...props
|
...props
|
||||||
} = this.props
|
} = omit(this.props, [
|
||||||
|
"children",
|
||||||
|
"onChange",
|
||||||
|
"onChangeValue",
|
||||||
|
"onChangeMultiple",
|
||||||
|
])
|
||||||
|
|
||||||
const item: ActionRowItem = {
|
const item: ActionRowItem = {
|
||||||
...props,
|
...props,
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export class ReacordDiscordJs extends Reacord {
|
|||||||
super(config)
|
super(config)
|
||||||
|
|
||||||
client.on("interactionCreate", (interaction) => {
|
client.on("interactionCreate", (interaction) => {
|
||||||
if (interaction.isButton() || interaction.isSelectMenu()) {
|
if (interaction.isButton() || interaction.isStringSelectMenu()) {
|
||||||
this.handleComponentInteraction(
|
this.handleComponentInteraction(
|
||||||
this.createReacordComponentInteraction(interaction),
|
this.createReacordComponentInteraction(interaction),
|
||||||
)
|
)
|
||||||
@@ -237,7 +237,7 @@ export class ReacordDiscordJs extends Reacord {
|
|||||||
...pruneNullishValues(
|
...pruneNullishValues(
|
||||||
pick(interaction.user, ["id", "username", "discriminator", "tag"]),
|
pick(interaction.user, ["id", "username", "discriminator", "tag"]),
|
||||||
),
|
),
|
||||||
avatarUrl: interaction.user.avatarURL()!,
|
avatarUrl: interaction.user.avatarURL(),
|
||||||
accentColor: interaction.user.accentColor ?? undefined,
|
accentColor: interaction.user.accentColor ?? undefined,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,7 +245,11 @@ export class ReacordDiscordJs extends Reacord {
|
|||||||
id: interaction.id,
|
id: interaction.id,
|
||||||
customId: interaction.customId,
|
customId: interaction.customId,
|
||||||
update: async (options: MessageOptions) => {
|
update: async (options: MessageOptions) => {
|
||||||
|
if (interaction.deferred || interaction.replied) {
|
||||||
|
await interaction.message.edit(getDiscordMessageOptions(options))
|
||||||
|
} else {
|
||||||
await interaction.update(getDiscordMessageOptions(options))
|
await interaction.update(getDiscordMessageOptions(options))
|
||||||
|
}
|
||||||
},
|
},
|
||||||
deferUpdate: async () => {
|
deferUpdate: async () => {
|
||||||
if (interaction.replied || interaction.deferred) return
|
if (interaction.replied || interaction.deferred) return
|
||||||
@@ -292,7 +296,7 @@ export class ReacordDiscordJs extends Reacord {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (interaction.isSelectMenu()) {
|
if (interaction.isStringSelectMenu()) {
|
||||||
return {
|
return {
|
||||||
...baseProps,
|
...baseProps,
|
||||||
type: "select",
|
type: "select",
|
||||||
|
|||||||
@@ -38,13 +38,13 @@ export abstract class Reacord {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected createInstance(renderer: Renderer, initialContent?: ReactNode) {
|
protected createInstance(renderer: Renderer, initialContent?: ReactNode) {
|
||||||
if (this.renderers.length > this.maxInstances) {
|
if (this.renderers.length > this.maxInstances && this.renderers[0]) {
|
||||||
this.deactivate(this.renderers[0]!)
|
this.deactivate(this.renderers[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
this.renderers.push(renderer)
|
this.renderers.push(renderer)
|
||||||
|
|
||||||
const container = reconciler.createContainer(
|
const container: unknown = reconciler.createContainer(
|
||||||
renderer,
|
renderer,
|
||||||
0,
|
0,
|
||||||
null,
|
null,
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ const config: HostConfig<
|
|||||||
raise(`Missing createNode function`)
|
raise(`Missing createNode function`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const node = props.createNode(props.props)
|
const node: unknown = props.createNode(props.props)
|
||||||
if (!(node instanceof Node)) {
|
if (!(node instanceof Node)) {
|
||||||
raise(`createNode function did not return a Node`)
|
raise(`createNode function did not return a Node`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,14 +47,12 @@ export abstract class Renderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleComponentInteraction(interaction: ComponentInteraction) {
|
handleComponentInteraction(interaction: ComponentInteraction) {
|
||||||
|
for (const node of this.nodes) {
|
||||||
|
if (node.handleComponentInteraction(interaction)) {
|
||||||
this.componentInteraction = interaction
|
this.componentInteraction = interaction
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.updates.next({ action: "deferUpdate", interaction })
|
this.updates.next({ action: "deferUpdate", interaction })
|
||||||
}, 500)
|
}, 500)
|
||||||
|
|
||||||
for (const node of this.nodes) {
|
|
||||||
if (node.handleComponentInteraction(interaction)) {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { raise } from "@reacord/helpers/raise.js"
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Link,
|
Link,
|
||||||
@@ -18,9 +19,13 @@ const reacord = new ReacordDiscordJs(client)
|
|||||||
|
|
||||||
await client.login(process.env.TEST_BOT_TOKEN)
|
await client.login(process.env.TEST_BOT_TOKEN)
|
||||||
|
|
||||||
const guild = await client.guilds.fetch(process.env.TEST_GUILD_ID!)
|
const guild = await client.guilds.fetch(
|
||||||
|
process.env.TEST_GUILD_ID ?? raise("TEST_GUILD_ID not defined"),
|
||||||
|
)
|
||||||
|
|
||||||
const category = await guild.channels.fetch(process.env.TEST_CATEGORY_ID!)
|
const category = await guild.channels.fetch(
|
||||||
|
process.env.TEST_CATEGORY_ID ?? raise("TEST_CATEGORY_ID not defined"),
|
||||||
|
)
|
||||||
if (category?.type !== ChannelType.GuildCategory) {
|
if (category?.type !== ChannelType.GuildCategory) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`channel ${process.env.TEST_CATEGORY_ID} is not a guild category. received ${category?.type}`,
|
`channel ${process.env.TEST_CATEGORY_ID} is not a guild category. received ${category?.type}`,
|
||||||
|
|||||||
@@ -8,5 +8,5 @@ beforeAll(() => {
|
|||||||
|
|
||||||
test("can require commonjs", () => {
|
test("can require commonjs", () => {
|
||||||
const require = createRequire(import.meta.url)
|
const require = createRequire(import.meta.url)
|
||||||
expect(() => require("../dist/main.cjs")).not.toThrow()
|
expect(() => require("../dist/main.cjs") as unknown).not.toThrow()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -128,13 +128,17 @@ test("multiple select", async () => {
|
|||||||
await tester.findSelectByPlaceholder("select").select("1", "3")
|
await tester.findSelectByPlaceholder("select").select("1", "3")
|
||||||
await assertSelect(expect.arrayContaining(["1", "3"]) as unknown as string[])
|
await assertSelect(expect.arrayContaining(["1", "3"]) as unknown as string[])
|
||||||
expect(onSelect).toHaveBeenCalledWith(
|
expect(onSelect).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({ values: expect.arrayContaining(["1", "3"]) }),
|
expect.objectContaining({
|
||||||
|
values: expect.arrayContaining(["1", "3"]) as unknown,
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
await tester.findSelectByPlaceholder("select").select("2")
|
await tester.findSelectByPlaceholder("select").select("2")
|
||||||
await assertSelect(expect.arrayContaining(["2"]) as unknown as string[])
|
await assertSelect(expect.arrayContaining(["2"]) as unknown as string[])
|
||||||
expect(onSelect).toHaveBeenCalledWith(
|
expect(onSelect).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({ values: expect.arrayContaining(["2"]) }),
|
expect.objectContaining({
|
||||||
|
values: expect.arrayContaining(["2"]) as unknown,
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
await tester.findSelectByPlaceholder("select").select()
|
await tester.findSelectByPlaceholder("select").select()
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
"@fontsource/jetbrains-mono": "^4.5.12",
|
"@fontsource/jetbrains-mono": "^4.5.12",
|
||||||
"@fontsource/rubik": "^4.5.14",
|
"@fontsource/rubik": "^4.5.14",
|
||||||
"@heroicons/react": "^2.0.18",
|
"@heroicons/react": "^2.0.18",
|
||||||
|
"@reacord/helpers": "workspace:^",
|
||||||
"@tailwindcss/typography": "^0.5.10",
|
"@tailwindcss/typography": "^0.5.10",
|
||||||
"astro": "^2.10.9",
|
"astro": "^2.10.9",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useEffect, useRef, useState } from "react"
|
|||||||
import blobComfyUrl from "~/assets/blob-comfy.png"
|
import blobComfyUrl from "~/assets/blob-comfy.png"
|
||||||
import cursorIbeamUrl from "~/assets/cursor-ibeam.png"
|
import cursorIbeamUrl from "~/assets/cursor-ibeam.png"
|
||||||
import cursorUrl from "~/assets/cursor.png"
|
import cursorUrl from "~/assets/cursor.png"
|
||||||
|
import { raise } from "@reacord/helpers/raise.ts"
|
||||||
|
|
||||||
const defaultState = {
|
const defaultState = {
|
||||||
chatInputText: "",
|
chatInputText: "",
|
||||||
@@ -70,7 +71,7 @@ export function LandingAnimation() {
|
|||||||
count: state.count + 1,
|
count: state.count + 1,
|
||||||
chatInputCursorVisible: false,
|
chatInputCursorVisible: false,
|
||||||
}))
|
}))
|
||||||
animateClick(addRef.current!)
|
animateClick(addRef.current ?? raise("addRef is null"))
|
||||||
await delay(700)
|
await delay(700)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +83,7 @@ export function LandingAnimation() {
|
|||||||
}))
|
}))
|
||||||
await delay(1000)
|
await delay(1000)
|
||||||
|
|
||||||
animateClick(deleteRef.current!)
|
animateClick(deleteRef.current ?? raise("deleteRef is null"))
|
||||||
setState((state) => ({ ...state, messageVisible: false }))
|
setState((state) => ({ ...state, messageVisible: false }))
|
||||||
await delay(1000)
|
await delay(1000)
|
||||||
|
|
||||||
@@ -105,16 +106,19 @@ export function LandingAnimation() {
|
|||||||
void (async () => {
|
void (async () => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
while (running) {
|
while (running) {
|
||||||
|
const cursor = cursorRef.current ?? raise("cursorRef is null")
|
||||||
|
const chatInput = chatInputRef.current ?? raise("chatInputRef is null")
|
||||||
|
|
||||||
// check if the cursor is in the input
|
// check if the cursor is in the input
|
||||||
const cursorRect = cursorRef.current!.getBoundingClientRect()
|
const cursorRect = cursor.getBoundingClientRect()
|
||||||
const chatInputRect = chatInputRef.current!.getBoundingClientRect()
|
const chatInputRect = chatInput.getBoundingClientRect()
|
||||||
|
|
||||||
const isOverInput =
|
const isOverInput =
|
||||||
cursorRef.current &&
|
cursorRef.current &&
|
||||||
chatInputRef.current &&
|
chatInputRef.current &&
|
||||||
cursorRect.top + cursorRect.height / 2 > chatInputRect.top
|
cursorRect.top + cursorRect.height / 2 > chatInputRect.top
|
||||||
|
|
||||||
cursorRef.current!.src = isOverInput ? cursorIbeamUrl : cursorUrl
|
cursor.src = isOverInput ? cursorIbeamUrl : cursorUrl
|
||||||
|
|
||||||
await animationFrame()
|
await animationFrame()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ const links = [
|
|||||||
href: "https://github.com/itsMapleLeaf/reacord",
|
href: "https://github.com/itsMapleLeaf/reacord",
|
||||||
label: "GitHub",
|
label: "GitHub",
|
||||||
icon: ArrowTopRightOnSquareIcon,
|
icon: ArrowTopRightOnSquareIcon,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
component: ExternalLink,
|
component: ExternalLink,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -36,17 +37,17 @@ const links = [
|
|||||||
const guides = await getCollection("guides")
|
const guides = await getCollection("guides")
|
||||||
---
|
---
|
||||||
|
|
||||||
<nav class="flex justify-between items-center h-16">
|
<nav class="flex h-16 items-center justify-between">
|
||||||
<a href="/">
|
<a href="/">
|
||||||
<AppLogo class="w-32" />
|
<AppLogo class="w-32" />
|
||||||
<span class="sr-only">Home</span>
|
<span class="sr-only">Home</span>
|
||||||
</a>
|
</a>
|
||||||
<div class="hidden md:flex gap-4">
|
<div class="hidden gap-4 md:flex">
|
||||||
{
|
{
|
||||||
links.map((link) => (
|
links.map((link) => (
|
||||||
<link.component
|
<link.component
|
||||||
href={link.href}
|
href={link.href}
|
||||||
class="link inline-flex gap-1 items-center"
|
class="link inline-flex items-center gap-1"
|
||||||
rel={link.prefetch ? "prefetch" : undefined}
|
rel={link.prefetch ? "prefetch" : undefined}
|
||||||
>
|
>
|
||||||
<link.icon className="inline-icon" />
|
<link.icon className="inline-icon" />
|
||||||
|
|||||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@@ -129,6 +129,9 @@ importers:
|
|||||||
'@heroicons/react':
|
'@heroicons/react':
|
||||||
specifier: ^2.0.18
|
specifier: ^2.0.18
|
||||||
version: 2.0.18(react@18.2.0)
|
version: 2.0.18(react@18.2.0)
|
||||||
|
'@reacord/helpers':
|
||||||
|
specifier: workspace:^
|
||||||
|
version: link:../helpers
|
||||||
'@tailwindcss/typography':
|
'@tailwindcss/typography':
|
||||||
specifier: ^0.5.10
|
specifier: ^0.5.10
|
||||||
version: 0.5.10(tailwindcss@3.3.3)
|
version: 0.5.10(tailwindcss@3.3.3)
|
||||||
|
|||||||
Reference in New Issue
Block a user