refactor: ♻️ Redesign post composer

This commit is contained in:
Jesse Wierzbinski 2025-12-09 22:12:23 +01:00
parent ef7475aead
commit 7ff9d2302a
No known key found for this signature in database
17 changed files with 327 additions and 203 deletions

View file

@ -1,18 +0,0 @@
<template>
<Tooltip>
<TooltipTrigger as="div">
<slot />
</TooltipTrigger>
<TooltipContent>
<p>{{ tooltip }}</p>
</TooltipContent>
</Tooltip>
</template>
<script lang="ts" setup>
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
const { tooltip } = defineProps<{
tooltip: string;
}>();
</script>

View file

@ -1,85 +0,0 @@
<template>
<ComposerButton :tooltip="m.game_tough_seal_adore()">
<Button variant="ghost" size="icon">
<AtSign class="!size-5" />
</Button>
</ComposerButton>
<ComposerButton :tooltip="m.plane_born_koala_hope()">
<Toggle variant="default" size="sm" :model-value="contentType === 'text/html'" @update:model-value="
(i) =>
(contentType = i ? 'text/html' : 'text/plain')
">
<LetterText class="!size-5" />
</Toggle>
</ComposerButton>
<VisibilityPicker v-model:visibility="visibility">
<Button variant="ghost" size="icon" :disabled="relation?.type === 'edit'">
<component :is="visibilities[visibility].icon" />
</Button>
</VisibilityPicker>
<ComposerButton :tooltip="m.blue_ornate_coyote_tickle()">
<Button variant="ghost" size="icon">
<Smile class="!size-5" />
</Button>
</ComposerButton>
<ComposerButton :tooltip="m.top_patchy_earthworm_vent()">
<Button variant="ghost" size="icon" @click="emit('pickFile')">
<FilePlus2 class="!size-5" />
</Button>
</ComposerButton>
<ComposerButton :tooltip="m.frail_broad_mallard_dart()">
<Toggle variant="default" size="sm" v-model="sensitive">
<TriangleAlert class="!size-5" />
</Toggle>
</ComposerButton>
<CharacterCounter class="ml-auto" :max="authStore.instance?.configuration.statuses.max_characters ?? 0" :current="rawContent.length" />
<Button type="submit" size="lg" :disabled="sending || !canSend" @click="emit('submit')">
<Loader v-if="sending" class="!size-5 animate-spin" />
{{
relation?.type === "edit"
? m.gaudy_strong_puma_slide()
: m.free_teal_bulldog_learn()
}}
</Button>
</template>
<script lang="ts" setup>
import {
AtSign,
FilePlus2,
LetterText,
Loader,
Smile,
TriangleAlert,
} from "lucide-vue-next";
import * as m from "~~/paraglide/messages.js";
import { Button } from "../ui/button";
import { Toggle } from "../ui/toggle";
import ComposerButton from "./button.vue";
import CharacterCounter from "./character-counter.vue";
import { visibilities } from "./visibilities";
import VisibilityPicker from "./visibility-picker.vue";
const { relation, sending, canSend, rawContent } = defineProps<{
relation?: ComposerState["relation"];
sending: boolean;
canSend: boolean;
rawContent: string;
}>();
const authStore = useAuthStore();
const contentType = defineModel<ComposerState["contentType"]>("contentType", {
required: true,
});
const visibility = defineModel<ComposerState["visibility"]>("visibility", {
required: true,
});
const sensitive = defineModel<ComposerState["sensitive"]>("sensitive", {
required: true,
});
const emit = defineEmits<{
submit: [];
pickFile: [];
}>();
</script>

View file

@ -1,39 +0,0 @@
<template>
<Tooltip>
<TooltipTrigger as-child>
<div v-bind="$attrs" class="m-1">
<TriangleAlert v-if="isOverflowing" class="text-destructive-foreground size-6" />
<svg v-else viewBox="0 0 100 100" class="transform rotate-[-90deg] size-6">
<!-- Background Circle -->
<circle cx="50" cy="50" r="46" stroke="currentColor" class="text-muted" stroke-width="8"
fill="none" />
<!-- Progress Circle -->
<circle cx="50" cy="50" r="46" stroke="currentColor" stroke-width="8" fill="none"
stroke-dasharray="100" :stroke-dashoffset="100 - percentage" pathLength="100"
stroke-linecap="round" class="text-accent-foreground transition-all duration-500" />
</svg>
</div>
</TooltipTrigger>
<TooltipContent class="text-center">
<p>{{ current }} / {{ max }}</p>
<p v-if="isOverflowing">Too long!</p>
</TooltipContent>
</Tooltip>
</template>
<script lang="ts" setup>
import { TriangleAlert } from "lucide-vue-next";
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
const { max, current } = defineProps<{
max: number;
current: number;
}>();
const percentage = computed(() => {
return Math.min((current / max) * 100, 100);
});
const isOverflowing = computed(() => {
return current > max;
});
</script>

View file

@ -3,29 +3,94 @@
<Note :note="relation.note" :hide-actions="true" :small-layout="true" /> <Note :note="relation.note" :hide-actions="true" :small-layout="true" />
</div> </div>
<ContentWarning v-if="store.sensitive" v-model="store.contentWarning" />
<EditorContent @paste-files="uploadFiles" v-model:content="store.content" v-model:raw-content="store.rawContent" :placeholder="getRandomSplash()" <InputGroup class="p-1">
class="[&>.tiptap]:!border-none [&>.tiptap]:!ring-0 [&>.tiptap]:!outline-none [&>.tiptap]:rounded-none p-0 [&>.tiptap]:max-h-[50dvh] [&>.tiptap]:overflow-y-auto [&>.tiptap]:min-h-48 [&>.tiptap]:!ring-offset-0 [&>.tiptap]:h-full" <InputGroupAddon v-if="store.sensitive" align="block-start" class="pt-3">
<Input v-model:model-value="store.contentWarning" placeholder="Put your content warning here" />
</InputGroupAddon>
<EditorContent data-slot="input-group-control" @paste-files="uploadFiles" v-model:content="store.content"
v-model:raw-content="store.rawContent" :placeholder="getRandomSplash()"
class=" placeholder:text-muted-foreground flex field-sizing-content min-h-58 w-full px-4 text-base disabled:opacity-50 md:text-sm flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none"
:disabled="store.sending" :mode="store.contentType === 'text/html' ? 'rich' : 'plain'" /> :disabled="store.sending" :mode="store.contentType === 'text/html' ? 'rich' : 'plain'" />
<div class="w-full flex flex-row gap-2 overflow-x-auto *:shrink-0 pb-2"> <InputGroupAddon v-if="store.files.length > 0" align="block-end" class="overflow-x-auto *:shrink-0">
<input type="file" ref="fileInput" @change="uploadFileFromEvent" class="hidden" multiple />
<Files v-model:files="store.files" :composer-key="composerKey" /> <Files v-model:files="store.files" :composer-key="composerKey" />
</div> </InputGroupAddon>
<DialogFooter class="items-center flex-row overflow-x-auto"> <InputGroupAddon align="block-end">
<ComposerButtons @submit="send" @pick-file="fileInput?.click()" v-model:content-type="store.contentType" v-model:sensitive="store.sensitive" v-model:visibility="store.visibility" :relation="store.relation" :sending="store.sending" :can-send="store.canSend" :raw-content="store.rawContent" /> <Select v-model:model-value="store.contentType">
</DialogFooter> <SelectTrigger as-child disable-default-classes disable-select-icon>
<InputGroupButton variant="ghost" size="icon-sm">
<LetterText v-if="store.contentType === 'text/html'" />
<Type v-else />
</InputGroupButton>
</SelectTrigger>
<SelectContent>
<SelectItem value="text/plain">
Plain text
</SelectItem>
<SelectItem value="text/html">
Rich text
</SelectItem>
</SelectContent>
</Select>
<VisibilityPicker v-model:visibility="store.visibility">
<InputGroupButton variant="ghost" size="icon-sm" :disabled="store.relation?.type === 'edit'">
<component :is="visibilities[store.visibility].icon" />
</InputGroupButton>
</VisibilityPicker>
<InputGroupButton variant="ghost" size="icon-sm" @click="fileInput?.click()">
<FilePlus2 />
</InputGroupButton>
<Toggle size="sm" v-model="store.sensitive">
<TriangleAlert />
</Toggle>
<InputGroupText :class="['ml-auto', charactersLeft < 0 && 'text-destructive']">
{{ charactersLeft.toLocaleString(getLocale(), {
maximumFractionDigits: 2,
notation: 'compact',
compactDisplay: 'short',
}) }}
</InputGroupText>
<Separator orientation="vertical" class="h-4!" />
<InputGroupButton variant="default" size="icon-sm" :disabled="store.sending || !store.canSend"
@click="send">
<Spinner v-if="store.sending" />
<ArrowUp v-else />
<span class="sr-only">Send</span>
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<input type="file" ref="fileInput" @change="uploadFileFromEvent" class="hidden" multiple />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {
ArrowUp,
FilePlus2,
LetterText,
TriangleAlert,
Type,
} from "lucide-vue-next";
import { Separator } from "reka-ui";
import Note from "~/components/notes/note.vue"; import Note from "~/components/notes/note.vue";
import { getLocale } from "~~/paraglide/runtime";
import EditorContent from "../editor/content.vue"; import EditorContent from "../editor/content.vue";
import { DialogFooter } from "../ui/dialog"; import { Input } from "../ui/input";
import ComposerButtons from "./buttons.vue"; import {
import ContentWarning from "./content-warning.vue"; InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupText,
} from "../ui/input-group";
import { Select, SelectContent, SelectItem, SelectTrigger } from "../ui/select";
import { Spinner } from "../ui/spinner";
import { Toggle } from "../ui/toggle";
import Files from "./files.vue"; import Files from "./files.vue";
import { visibilities } from "./visibilities";
import VisibilityPicker from "./visibility-picker.vue";
const props = defineProps<{ const props = defineProps<{
relation?: ComposerState["relation"]; relation?: ComposerState["relation"];
@ -38,6 +103,12 @@ const composerKey = props.relation
? (`${props.relation.type}-${props.relation.note.id}` as const) ? (`${props.relation.type}-${props.relation.note.id}` as const)
: "blank"; : "blank";
const store = useComposerStore(composerKey)(); const store = useComposerStore(composerKey)();
const authStore = useAuthStore();
const charactersLeft = computed(() => {
const max = authStore.instance?.configuration.statuses.max_characters ?? 0;
return max - store.rawContent.length;
});
watch([Control_Enter, Command_Enter], () => { watch([Control_Enter, Command_Enter], () => {
if (store.sending || !preferences.ctrl_enter_send.value) { if (store.sending || !preferences.ctrl_enter_send.value) {

View file

@ -1,9 +0,0 @@
<template>
<Input v-model:model-value="contentWarning" placeholder="Put your content warning here" />
</template>
<script lang="ts" setup>
import { Input } from "../ui/input";
const contentWarning = defineModel<string>();
</script>

View file

@ -81,7 +81,7 @@ const relation = ref(
> >
<DialogContent <DialogContent
:hide-close="true" :hide-close="true"
class="sm:max-w-xl max-w-full w-[calc(100%-2*0.5rem)] grid-cols-1 max-h-[90dvh] p-5 pt-6 top-2 sm:top-1/2 translate-y-0 sm:-translate-y-1/2" class="sm:max-w-xl max-w-full w-[calc(100%-2*0.5rem)] grid-cols-1 max-h-[90dvh] p-0 top-2 sm:top-1/2 translate-y-0 sm:-translate-y-1/2 border-none bg-transparent shadow-none"
> >
<DialogTitle class="sr-only"> <DialogTitle class="sr-only">
{{ {{

View file

@ -2,7 +2,7 @@
<BubbleMenu :editor="editor" /> <BubbleMenu :editor="editor" />
<EditorContent :editor="editor" <EditorContent :editor="editor"
v-bind="$attrs" v-bind="$attrs"
:class="[$style.content, 'relative prose prose-sm dark:prose-invert break-words prose-a:no-underline prose-a:hover:underline prose-p:first-of-type:mt-0']" /> :class="$style.content" />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -36,6 +36,11 @@ const emit = defineEmits<{
}>(); }>();
const editor = new Editor({ const editor = new Editor({
editorProps: {
attributes: {
class: "relative prose prose-sm dark:prose-invert wrap-break-word prose-a:no-underline prose-a:hover:underline prose-p:first-of-type:mt-0 focus:outline-none w-full",
},
},
extensions: [ extensions: [
StarterKit, StarterKit,
Placeholder.configure({ Placeholder.configure({
@ -118,6 +123,6 @@ onUnmounted(() => {
} }
.tiptap .emoji>img { .tiptap .emoji>img {
@apply h-[1lh] align-middle inline hover:scale-110 transition-transform duration-75 ease-in-out; @apply h-lh align-middle inline hover:scale-110 transition-transform duration-75 ease-in-out;
} }
</style> </style>

View file

@ -0,0 +1,32 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue";
import { cn } from "@/lib/utils";
const props = defineProps<{
class?: HTMLAttributes["class"];
}>();
</script>
<template>
<div
data-slot="input-group"
role="group"
:class="cn(
'group/input-group border-input bg-background relative flex w-full items-center rounded-md border outline-none',
'h-9 min-w-0 has-[>textarea]:h-auto',
// Variants based on alignment.
'has-[>[data-align=inline-start]]:[&>input]:pl-2',
'has-[>[data-align=inline-end]]:[&>input]:pr-2',
'has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3',
'has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3',
// Focus state.
'has-[[data-slot=input-group-control]:focus-visible]:ring-ring has-[[data-slot=input-group-control]:focus-visible]:ring-1',
props.class,
)"
>
<slot />
</div>
</template>

View file

@ -0,0 +1,39 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue";
import { cn } from "@/lib/utils";
import type { InputGroupVariants } from ".";
import { inputGroupAddonVariants } from ".";
const props = withDefaults(
defineProps<{
align?: InputGroupVariants["align"];
class?: HTMLAttributes["class"];
}>(),
{
align: "inline-start",
},
);
function handleInputGroupAddonClick(e: MouseEvent) {
const currentTarget = e.currentTarget as HTMLElement | null;
const target = e.target as HTMLElement | null;
if (target?.closest("button")) {
return;
}
if (currentTarget?.parentElement) {
currentTarget.parentElement?.querySelector("input")?.focus();
}
}
</script>
<template>
<div
role="group"
data-slot="input-group-addon"
:data-align="props.align"
:class="cn(inputGroupAddonVariants({ align: props.align }), props.class)"
@click="handleInputGroupAddonClick"
>
<slot />
</div>
</template>

View file

@ -0,0 +1,21 @@
<script setup lang="ts">
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import type { InputGroupButtonProps } from ".";
import { inputGroupButtonVariants } from ".";
const props = withDefaults(defineProps<InputGroupButtonProps>(), {
size: "xs",
variant: "ghost",
});
</script>
<template>
<Button
:data-size="props.size"
:variant="props.variant"
:class="cn(inputGroupButtonVariants({ size: props.size }), props.class)"
>
<slot />
</Button>
</template>

View file

@ -0,0 +1,19 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue";
import { Input } from "@/components/ui/input";
import { cn } from "@/lib/utils";
const props = defineProps<{
class?: HTMLAttributes["class"];
}>();
</script>
<template>
<Input
data-slot="input-group-control"
:class="cn(
'flex-1 rounded-none border-0 bg-transparent focus-visible:ring-0 focus-visible:ring-transparent ring-offset-transparent dark:bg-transparent',
props.class,
)"
/>
</template>

View file

@ -0,0 +1,19 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue";
import { cn } from "@/lib/utils";
const props = defineProps<{
class?: HTMLAttributes["class"];
}>();
</script>
<template>
<span
:class="cn(
'text-muted-foreground flex items-center gap-2 text-sm [&_svg]:pointer-events-none [&_svg:not([class*=\'size-\'])]:size-4',
props.class,
)"
>
<slot />
</span>
</template>

View file

@ -0,0 +1,19 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue";
import { Textarea } from "@/components/ui/textarea";
import { cn } from "@/lib/utils";
const props = defineProps<{
class?: HTMLAttributes["class"];
}>();
</script>
<template>
<Textarea
data-slot="input-group-control"
:class="cn(
'flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 focus-visible:ring-transparent ring-offset-transparent dark:bg-transparent',
props.class,
)"
/>
</template>

View file

@ -0,0 +1,62 @@
import type { VariantProps } from "class-variance-authority";
import { cva } from "class-variance-authority";
import type { HTMLAttributes } from "vue";
import type { ButtonVariants } from "@/components/ui/button";
export { default as InputGroup } from "./InputGroup.vue";
export { default as InputGroupAddon } from "./InputGroupAddon.vue";
export { default as InputGroupButton } from "./InputGroupButton.vue";
export { default as InputGroupInput } from "./InputGroupInput.vue";
export { default as InputGroupText } from "./InputGroupText.vue";
export { default as InputGroupTextarea } from "./InputGroupTextarea.vue";
export const inputGroupAddonVariants = cva(
"text-muted-foreground flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium select-none [&>svg:not([class*='size-'])]:size-4 [&>kbd]:rounded-[calc(var(--radius)-5px)] group-data-[disabled=true]/input-group:opacity-50",
{
variants: {
align: {
"inline-start":
"order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]",
"inline-end":
"order-last pr-3 has-[>button]:mr-[-0.45rem] has-[>kbd]:mr-[-0.35rem]",
"block-start":
"order-first w-full justify-start px-3 pt-3 [.border-b]:pb-3 group-has-[>input]/input-group:pt-2.5",
"block-end":
"order-last w-full justify-start px-3 pb-3 [.border-t]:pt-3 group-has-[>input]/input-group:pb-2.5",
},
},
defaultVariants: {
align: "inline-start",
},
},
);
export type InputGroupVariants = VariantProps<typeof inputGroupAddonVariants>;
export const inputGroupButtonVariants = cva(
"text-sm shadow-none flex gap-2 items-center",
{
variants: {
size: {
xs: "h-6 gap-1 px-2 rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-3.5 has-[>svg]:px-2",
sm: "h-8 px-2.5 gap-1.5 rounded-md has-[>svg]:px-2.5",
"icon-xs":
"size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0",
"icon-sm": "size-8 p-0 has-[>svg]:p-0",
},
},
defaultVariants: {
size: "xs",
},
},
);
export type InputGroupButtonVariants = VariantProps<
typeof inputGroupButtonVariants
>;
export interface InputGroupButtonProps {
variant?: ButtonVariants["variant"];
size?: InputGroupButtonVariants["size"];
class?: HTMLAttributes["class"];
}

View file

@ -0,0 +1,17 @@
<script setup lang="ts">
import { Loader2Icon } from "lucide-vue-next";
import type { HTMLAttributes } from "vue";
import { cn } from "@/lib/utils";
const props = defineProps<{
class?: HTMLAttributes["class"];
}>();
</script>
<template>
<Loader2Icon
role="status"
aria-label="Loading"
:class="cn('size-4 animate-spin', props.class)"
/>
</template>

View file

@ -0,0 +1 @@
export { default as Spinner } from "./Spinner.vue";

View file

@ -252,12 +252,6 @@ packages:
resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
'@babel/helper-create-class-features-plugin@7.27.1':
resolution: {integrity: sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0
'@babel/helper-create-class-features-plugin@7.28.5': '@babel/helper-create-class-features-plugin@7.28.5':
resolution: {integrity: sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==} resolution: {integrity: sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
@ -279,10 +273,6 @@ packages:
resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
'@babel/helper-member-expression-to-functions@7.27.1':
resolution: {integrity: sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==}
engines: {node: '>=6.9.0'}
'@babel/helper-member-expression-to-functions@7.28.5': '@babel/helper-member-expression-to-functions@7.28.5':
resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==} resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
@ -6149,19 +6139,6 @@ snapshots:
lru-cache: 5.1.1 lru-cache: 5.1.1
semver: 6.3.1 semver: 6.3.1
'@babel/helper-create-class-features-plugin@7.27.1(@babel/core@7.28.5)':
dependencies:
'@babel/core': 7.28.5
'@babel/helper-annotate-as-pure': 7.27.3
'@babel/helper-member-expression-to-functions': 7.27.1
'@babel/helper-optimise-call-expression': 7.27.1
'@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.5)
'@babel/helper-skip-transparent-expression-wrappers': 7.27.1
'@babel/traverse': 7.28.5
semver: 6.3.1
transitivePeerDependencies:
- supports-color
'@babel/helper-create-class-features-plugin@7.28.5(@babel/core@7.28.5)': '@babel/helper-create-class-features-plugin@7.28.5(@babel/core@7.28.5)':
dependencies: dependencies:
'@babel/core': 7.28.5 '@babel/core': 7.28.5
@ -6187,7 +6164,7 @@ snapshots:
'@babel/core': 7.28.5 '@babel/core': 7.28.5
'@babel/helper-compilation-targets': 7.27.2 '@babel/helper-compilation-targets': 7.27.2
'@babel/helper-plugin-utils': 7.27.1 '@babel/helper-plugin-utils': 7.27.1
debug: 4.4.1 debug: 4.4.3
lodash.debounce: 4.0.8 lodash.debounce: 4.0.8
resolve: 1.22.10 resolve: 1.22.10
transitivePeerDependencies: transitivePeerDependencies:
@ -6195,13 +6172,6 @@ snapshots:
'@babel/helper-globals@7.28.0': {} '@babel/helper-globals@7.28.0': {}
'@babel/helper-member-expression-to-functions@7.27.1':
dependencies:
'@babel/traverse': 7.28.5
'@babel/types': 7.28.5
transitivePeerDependencies:
- supports-color
'@babel/helper-member-expression-to-functions@7.28.5': '@babel/helper-member-expression-to-functions@7.28.5':
dependencies: dependencies:
'@babel/traverse': 7.28.5 '@babel/traverse': 7.28.5
@ -6380,7 +6350,7 @@ snapshots:
'@babel/plugin-transform-class-properties@7.27.1(@babel/core@7.28.5)': '@babel/plugin-transform-class-properties@7.27.1(@babel/core@7.28.5)':
dependencies: dependencies:
'@babel/core': 7.28.5 '@babel/core': 7.28.5
'@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.28.5) '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5)
'@babel/helper-plugin-utils': 7.27.1 '@babel/helper-plugin-utils': 7.27.1
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@ -6388,7 +6358,7 @@ snapshots:
'@babel/plugin-transform-class-static-block@7.27.1(@babel/core@7.28.5)': '@babel/plugin-transform-class-static-block@7.27.1(@babel/core@7.28.5)':
dependencies: dependencies:
'@babel/core': 7.28.5 '@babel/core': 7.28.5
'@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.28.5) '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5)
'@babel/helper-plugin-utils': 7.27.1 '@babel/helper-plugin-utils': 7.27.1
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@ -6591,7 +6561,7 @@ snapshots:
'@babel/plugin-transform-private-methods@7.27.1(@babel/core@7.28.5)': '@babel/plugin-transform-private-methods@7.27.1(@babel/core@7.28.5)':
dependencies: dependencies:
'@babel/core': 7.28.5 '@babel/core': 7.28.5
'@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.28.5) '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5)
'@babel/helper-plugin-utils': 7.27.1 '@babel/helper-plugin-utils': 7.27.1
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@ -6600,7 +6570,7 @@ snapshots:
dependencies: dependencies:
'@babel/core': 7.28.5 '@babel/core': 7.28.5
'@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-annotate-as-pure': 7.27.3
'@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.28.5) '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5)
'@babel/helper-plugin-utils': 7.27.1 '@babel/helper-plugin-utils': 7.27.1
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color