2026-01-06 00:35:51 +00:00
|
|
|
<template>
|
|
|
|
|
<div data-pyro-telepopover-wrapper class="relative">
|
|
|
|
|
<button
|
|
|
|
|
ref="triggerRef"
|
|
|
|
|
class="teleport-overflow-menu-trigger"
|
feat: content tab rewrite for worlds (#5136)
* feat: base content card component
* fix: tooltips + colors
* feat: fix orgs
* feat: base content tab internals rewrite
* feat: fix invalidmodal
* feat: add ContentModpackCard
* fix: extract types
* draft: layout
* feat: unlink modal
* feat: impl content tab
* fix: lint
* fix: toggling
* temp: disable updating stuff
* feat: selection v-model
* feat: bulk selection
* feat: mods tab rough draft
* feat: use fuse.js
* feat: add project combobox
* clean up project combobox
* feat: start install to play modal
* fix: events
* feat: use v-on
* feat: bulk actions + fix floating action bar width
* feat: figma alignments
* feat: migrate toggle to tailwind
* fix: row borders
* feat: disabled state
* feat: virtual list impl for card table based on window scroll
* fix: lint
* feat: virtualization + smaller contentcard items
* feat: use ContentCardTable + ContentCardItems
* feat: fix gap + border issues on last elm
* feat: cleanup + use proper searching
* fix: use TeleportOverflowMenu
* fix: fallback to svg if src is invalid on avatar component
* fix: storybook
* feat: start on updater modal
* feat: finish content updater modal
* feat: i18n pass
* feat: impl modal
* feat(app): backend changes for content tab refactor (#5237)
* feat: include_changelog=false for updater modal
* fix: hash overrides
* feat: update checking for modpack
* feat: qa
* feat: modpack content modal
* fix: padding in table to match modals + tightness
* fix: lint
* feat: delete modal
* feat: fix toggle bugs
* fix: prepr
* fix: duplicate messages
* qa: full width search
* qa: use bg-surface-1.5
* qa: animation for filter pills
* qa: standardize hover colors
* fix: border-[1px] is border
* qa: mass de-select actually mass selecting
* qa: match figma designs for floating action bar
* qa: modal fixes
* q: modal fixes x2
* fix: table border
* qa: confirm modals
* qa: modal alignment
* qa: re-add stuck heading + dedupe logic
* qa: dedupe virtual scrolling + remove dead components
* qa: responsiveness for content table + link fixes
* qa: version column link, tooltips + lint fixes
* qa: instance busy protections
* fix: installation freeze bug
* chore: remove old mods page
* refactor: deduplicate layout
* chore: delete old content page(s)
* qa
* qa
* qa
* feat: sort btn - to iterate
* fix: ml
* feat: date added
* fix: lint
* fix: formatting.ts removal
* feat: get_dependencies_as_content_items
* qa: final QA changes
* refactor: deduplicate + polish content.rs
* feat: hook up content.vue with v1
* feat: hide v1 content api behind frontend feature flag
* fix: query keys + copy on empty state
* chore: i18n pass
* feat: reimpl unlink + upload endpoint
* feat: use bulk endpoints v1
* fix: lint
* fix: flags
* fix: responsiveness via container queries
* fix: lint
* qa: 1
* qa: fixes
* qa: fix ssr issues with browse content
* qa: header page divider
* qa: modals
* fix: prepr
* fix: issues
* fix: lint
* fix: toggle v1 ff
* qa: 5
* qa: delete modal copy
* feat: creation flow modals (#5383)
* refactor: delete content v0 usages + impl
* feat: qa + fixes
* feat: installing banner using state event
* feat: fix modpack card bugs + filtering issues
* refactor: delete backups v0 api module
* feat: v1 servers GET endpoint
* fix: backups
* feat: swap to kyros upload v1 addon
* fix: use tanstack for loader.vue
* feat: finish install from discovery modal
* qa: bug fixes
* feat: set up installation settings
* fix: lint
* fix: typos
* fix: bugs
* fix: disable inline content
* feat: content tab improvements — upload UX, installation settings, and client-only indicators
Upload cancellation and navigation guard:
- Add ConfirmLeaveModal that prompts when navigating away during upload
- Cancel in-flight XHR uploads when user confirms leaving the page
- Add beforeunload handler to warn on browser/tab close during upload
- Track uploadedBytes/totalBytes in UploadState for progress display
- Replace Collapsible with Transition for upload progress admonition
- Show byte progress and percentage in upload banner
- Clamp upload progress to prevent exceeding 100%
Installation settings (server.properties):
- Add KnownPropertiesFields and PropertiesFields types to Archon types
- Add buildProperties() to creation flow context to collect gamemode,
difficulty, seed, world type, structures, and generator settings
- Pass properties through installContent on onboarding, discovery, and
ServerSetupModal flows
Server setup and discovery flow improvements:
- Migrate ServerSetupModal from servers_v0.reinstall to content_v1.installContent
- Replace loaderApiNames lookup with toApiLoader() helper
- Remove eraseDataOnInstall toggle — always use soft_override: false
- Simplify modpack install on discovery page to use first available version
and route through creation flow modal for both onboarding and non-onboarding
- Differentiate post-install navigation: content page for onboarding,
loader options for existing servers
Modpack update flow:
- Replace updateModpack() call with installContent() using soft_override: true
to support version selection in the content updater modal
Client-only mod indicators:
- Add environment field to AddonVersion (reuses Labrinth.Projects.v3.Environment)
- Add environment to ContentItem and isClientOnly to ContentCardTableItem
- Show orange TriangleAlertIcon with tooltip on client-only mods in content table
- Add "Client-only" filter pill to content filtering (controlled via
showClientOnlyFilter on ContentManagerContext)
- Apply client-only indicators in both ContentPageLayout and ModpackContentModal
Misc:
- Add CLAUDE.md note about using prepr commands for lint checks
- Export ConfirmLeaveModal from instances barrel
* fix: piping
* fix: switch content disable for linked server instances
* feat: client only filter
* fix: prepr
* feat: hasUpdate shape update
* feat: bulk update endpoint impl for content in panel
* feat: websocket state impl again with new phases
* fix: ws
* fix: use timeout fn for sync admon + fix content card layout scroll for browsers with overflow anchor bug
* fix: qa bugs
* fix: lint, a11y and i18n
* refactor: set up layouts folder properly
* fix: linked data cache stuff + lint
* feat: move installationsettings to shared layout
* fix: lint
* fix: issues
* feat: temp fuck staging up
* fix: lockfile
* fix: data sync issues on loader.vue
* fix: lint
* Hide shader configuration files from content list (#5499)
* feat: workaround search problem + split out reset
* fix: qa
* fix: changelog not showing on first open
* fix: qa + optimistic updating improvements
* fix: prepr+lint
* fix: qa
* feat: qa
* fix: lint
* fix: lint
* fix: build
* fix: build
* fix: type errors
* fix: fade and JAVA_HOME passthrough
* feat: qa
* feat: impl diff shit
* fix: qa
* fix: app qa
* feat: update diff modal
* fix: endpoint
* fix: qa
* fix: qa
* fix: use bulk in modpack modal
* feat: abort signal impl + fix issues
* fix: diff modal trunc
* feat: qa
* fix: qa
* feat: tooltip content tab
* fix: prepr
* fix: dismiss on settings btn
* feat: qa
* feat: dont clear handlers on disconnect
* fix: lint
* fix: wrangler + introduce staging-archon env file
---------
Signed-off-by: Calum H. <calum@modrinth.com>
Co-authored-by: tdgao <mr.trumgao@gmail.com>
Co-authored-by: Artyom Ezri <61311568+Artezon@users.noreply.github.com>
2026-03-12 20:24:32 +00:00
|
|
|
:class="btnClass"
|
2026-01-06 00:35:51 +00:00
|
|
|
:aria-expanded="isOpen"
|
|
|
|
|
:aria-haspopup="true"
|
|
|
|
|
@mousedown="handleMouseDown"
|
|
|
|
|
@mouseenter="handleMouseEnter"
|
|
|
|
|
@mouseleave="handleMouseLeave"
|
|
|
|
|
@click="toggleMenu"
|
|
|
|
|
>
|
|
|
|
|
<slot></slot>
|
|
|
|
|
</button>
|
|
|
|
|
<Teleport to="#teleports">
|
|
|
|
|
<Transition
|
|
|
|
|
enter-active-class="transition duration-125 ease-out"
|
|
|
|
|
enter-from-class="transform scale-75 opacity-0"
|
|
|
|
|
enter-to-class="transform scale-100 opacity-100"
|
|
|
|
|
leave-active-class="transition duration-125 ease-in"
|
|
|
|
|
leave-from-class="transform scale-100 opacity-100"
|
|
|
|
|
leave-to-class="transform scale-75 opacity-0"
|
|
|
|
|
>
|
|
|
|
|
<div
|
|
|
|
|
v-if="isOpen"
|
|
|
|
|
ref="menuRef"
|
|
|
|
|
data-pyro-telepopover-root
|
2026-05-03 11:53:06 -07:00
|
|
|
class="fixed isolate z-[9999] flex w-fit flex-col gap-2 overflow-hidden rounded-2xl border-[1px] border-solid border-surface-5 bg-bg-raised p-2 shadow-lg"
|
2026-01-06 00:35:51 +00:00
|
|
|
:style="menuStyle"
|
|
|
|
|
role="menu"
|
|
|
|
|
tabindex="-1"
|
|
|
|
|
@mousedown.stop
|
|
|
|
|
@mouseleave="handleMouseLeave"
|
|
|
|
|
>
|
|
|
|
|
<template
|
|
|
|
|
v-for="(option, index) in filteredOptions"
|
|
|
|
|
:key="isDivider(option) ? `divider-${index}` : option.id"
|
|
|
|
|
>
|
|
|
|
|
<div v-if="isDivider(option)" class="h-px w-full bg-surface-5"></div>
|
|
|
|
|
<ButtonStyled v-else type="transparent" role="menuitem" :color="option.color">
|
|
|
|
|
<button
|
|
|
|
|
v-if="typeof option.action === 'function'"
|
|
|
|
|
:ref="
|
|
|
|
|
(el) => {
|
|
|
|
|
if (el) menuItemsRef[index] = el as HTMLElement
|
|
|
|
|
}
|
|
|
|
|
"
|
feat: content tab rewrite for worlds (#5136)
* feat: base content card component
* fix: tooltips + colors
* feat: fix orgs
* feat: base content tab internals rewrite
* feat: fix invalidmodal
* feat: add ContentModpackCard
* fix: extract types
* draft: layout
* feat: unlink modal
* feat: impl content tab
* fix: lint
* fix: toggling
* temp: disable updating stuff
* feat: selection v-model
* feat: bulk selection
* feat: mods tab rough draft
* feat: use fuse.js
* feat: add project combobox
* clean up project combobox
* feat: start install to play modal
* fix: events
* feat: use v-on
* feat: bulk actions + fix floating action bar width
* feat: figma alignments
* feat: migrate toggle to tailwind
* fix: row borders
* feat: disabled state
* feat: virtual list impl for card table based on window scroll
* fix: lint
* feat: virtualization + smaller contentcard items
* feat: use ContentCardTable + ContentCardItems
* feat: fix gap + border issues on last elm
* feat: cleanup + use proper searching
* fix: use TeleportOverflowMenu
* fix: fallback to svg if src is invalid on avatar component
* fix: storybook
* feat: start on updater modal
* feat: finish content updater modal
* feat: i18n pass
* feat: impl modal
* feat(app): backend changes for content tab refactor (#5237)
* feat: include_changelog=false for updater modal
* fix: hash overrides
* feat: update checking for modpack
* feat: qa
* feat: modpack content modal
* fix: padding in table to match modals + tightness
* fix: lint
* feat: delete modal
* feat: fix toggle bugs
* fix: prepr
* fix: duplicate messages
* qa: full width search
* qa: use bg-surface-1.5
* qa: animation for filter pills
* qa: standardize hover colors
* fix: border-[1px] is border
* qa: mass de-select actually mass selecting
* qa: match figma designs for floating action bar
* qa: modal fixes
* q: modal fixes x2
* fix: table border
* qa: confirm modals
* qa: modal alignment
* qa: re-add stuck heading + dedupe logic
* qa: dedupe virtual scrolling + remove dead components
* qa: responsiveness for content table + link fixes
* qa: version column link, tooltips + lint fixes
* qa: instance busy protections
* fix: installation freeze bug
* chore: remove old mods page
* refactor: deduplicate layout
* chore: delete old content page(s)
* qa
* qa
* qa
* feat: sort btn - to iterate
* fix: ml
* feat: date added
* fix: lint
* fix: formatting.ts removal
* feat: get_dependencies_as_content_items
* qa: final QA changes
* refactor: deduplicate + polish content.rs
* feat: hook up content.vue with v1
* feat: hide v1 content api behind frontend feature flag
* fix: query keys + copy on empty state
* chore: i18n pass
* feat: reimpl unlink + upload endpoint
* feat: use bulk endpoints v1
* fix: lint
* fix: flags
* fix: responsiveness via container queries
* fix: lint
* qa: 1
* qa: fixes
* qa: fix ssr issues with browse content
* qa: header page divider
* qa: modals
* fix: prepr
* fix: issues
* fix: lint
* fix: toggle v1 ff
* qa: 5
* qa: delete modal copy
* feat: creation flow modals (#5383)
* refactor: delete content v0 usages + impl
* feat: qa + fixes
* feat: installing banner using state event
* feat: fix modpack card bugs + filtering issues
* refactor: delete backups v0 api module
* feat: v1 servers GET endpoint
* fix: backups
* feat: swap to kyros upload v1 addon
* fix: use tanstack for loader.vue
* feat: finish install from discovery modal
* qa: bug fixes
* feat: set up installation settings
* fix: lint
* fix: typos
* fix: bugs
* fix: disable inline content
* feat: content tab improvements — upload UX, installation settings, and client-only indicators
Upload cancellation and navigation guard:
- Add ConfirmLeaveModal that prompts when navigating away during upload
- Cancel in-flight XHR uploads when user confirms leaving the page
- Add beforeunload handler to warn on browser/tab close during upload
- Track uploadedBytes/totalBytes in UploadState for progress display
- Replace Collapsible with Transition for upload progress admonition
- Show byte progress and percentage in upload banner
- Clamp upload progress to prevent exceeding 100%
Installation settings (server.properties):
- Add KnownPropertiesFields and PropertiesFields types to Archon types
- Add buildProperties() to creation flow context to collect gamemode,
difficulty, seed, world type, structures, and generator settings
- Pass properties through installContent on onboarding, discovery, and
ServerSetupModal flows
Server setup and discovery flow improvements:
- Migrate ServerSetupModal from servers_v0.reinstall to content_v1.installContent
- Replace loaderApiNames lookup with toApiLoader() helper
- Remove eraseDataOnInstall toggle — always use soft_override: false
- Simplify modpack install on discovery page to use first available version
and route through creation flow modal for both onboarding and non-onboarding
- Differentiate post-install navigation: content page for onboarding,
loader options for existing servers
Modpack update flow:
- Replace updateModpack() call with installContent() using soft_override: true
to support version selection in the content updater modal
Client-only mod indicators:
- Add environment field to AddonVersion (reuses Labrinth.Projects.v3.Environment)
- Add environment to ContentItem and isClientOnly to ContentCardTableItem
- Show orange TriangleAlertIcon with tooltip on client-only mods in content table
- Add "Client-only" filter pill to content filtering (controlled via
showClientOnlyFilter on ContentManagerContext)
- Apply client-only indicators in both ContentPageLayout and ModpackContentModal
Misc:
- Add CLAUDE.md note about using prepr commands for lint checks
- Export ConfirmLeaveModal from instances barrel
* fix: piping
* fix: switch content disable for linked server instances
* feat: client only filter
* fix: prepr
* feat: hasUpdate shape update
* feat: bulk update endpoint impl for content in panel
* feat: websocket state impl again with new phases
* fix: ws
* fix: use timeout fn for sync admon + fix content card layout scroll for browsers with overflow anchor bug
* fix: qa bugs
* fix: lint, a11y and i18n
* refactor: set up layouts folder properly
* fix: linked data cache stuff + lint
* feat: move installationsettings to shared layout
* fix: lint
* fix: issues
* feat: temp fuck staging up
* fix: lockfile
* fix: data sync issues on loader.vue
* fix: lint
* Hide shader configuration files from content list (#5499)
* feat: workaround search problem + split out reset
* fix: qa
* fix: changelog not showing on first open
* fix: qa + optimistic updating improvements
* fix: prepr+lint
* fix: qa
* feat: qa
* fix: lint
* fix: lint
* fix: build
* fix: build
* fix: type errors
* fix: fade and JAVA_HOME passthrough
* feat: qa
* feat: impl diff shit
* fix: qa
* fix: app qa
* feat: update diff modal
* fix: endpoint
* fix: qa
* fix: qa
* fix: use bulk in modpack modal
* feat: abort signal impl + fix issues
* fix: diff modal trunc
* feat: qa
* fix: qa
* feat: tooltip content tab
* fix: prepr
* fix: dismiss on settings btn
* feat: qa
* feat: dont clear handlers on disconnect
* fix: lint
* fix: wrangler + introduce staging-archon env file
---------
Signed-off-by: Calum H. <calum@modrinth.com>
Co-authored-by: tdgao <mr.trumgao@gmail.com>
Co-authored-by: Artyom Ezri <61311568+Artezon@users.noreply.github.com>
2026-03-12 20:24:32 +00:00
|
|
|
v-tooltip="option.tooltip"
|
|
|
|
|
:disabled="option.disabled"
|
2026-01-06 00:35:51 +00:00
|
|
|
class="w-full !justify-start !whitespace-nowrap focus-visible:!outline-none"
|
|
|
|
|
:aria-selected="index === selectedIndex"
|
|
|
|
|
:style="index === selectedIndex ? { background: 'var(--color-button-bg)' } : {}"
|
|
|
|
|
@click="handleItemClick(option, index)"
|
|
|
|
|
@focus="selectedIndex = index"
|
|
|
|
|
@mouseover="handleMouseOver(index)"
|
|
|
|
|
>
|
2026-03-14 22:43:59 +00:00
|
|
|
<slot :name="option.id">
|
|
|
|
|
<component :is="option.icon" v-if="option.icon" class="size-5" />
|
|
|
|
|
{{ option.id }}
|
|
|
|
|
</slot>
|
2026-01-06 00:35:51 +00:00
|
|
|
</button>
|
|
|
|
|
<AutoLink
|
|
|
|
|
v-else-if="typeof option.action === 'string'"
|
|
|
|
|
:ref="
|
|
|
|
|
(el) => {
|
|
|
|
|
if (el) menuItemsRef[index] = el as HTMLElement
|
|
|
|
|
}
|
|
|
|
|
"
|
|
|
|
|
:to="option.action"
|
|
|
|
|
class="w-full !justify-start !whitespace-nowrap focus-visible:!outline-none"
|
|
|
|
|
:aria-selected="index === selectedIndex"
|
|
|
|
|
:style="index === selectedIndex ? { background: 'var(--color-button-bg)' } : {}"
|
|
|
|
|
@click="handleItemClick(option, index)"
|
|
|
|
|
@focus="selectedIndex = index"
|
|
|
|
|
@mouseover="handleMouseOver(index)"
|
|
|
|
|
>
|
2026-03-14 22:43:59 +00:00
|
|
|
<slot :name="option.id">
|
|
|
|
|
<component :is="option.icon" v-if="option.icon" class="size-5" />
|
|
|
|
|
{{ option.id }}
|
|
|
|
|
</slot>
|
2026-01-06 00:35:51 +00:00
|
|
|
</AutoLink>
|
|
|
|
|
<span v-else>
|
2026-03-14 22:43:59 +00:00
|
|
|
<slot :name="option.id">
|
|
|
|
|
<component :is="option.icon" v-if="option.icon" class="size-5" />
|
|
|
|
|
{{ option.id }}
|
|
|
|
|
</slot>
|
2026-01-06 00:35:51 +00:00
|
|
|
</span>
|
|
|
|
|
</ButtonStyled>
|
|
|
|
|
</template>
|
|
|
|
|
</div>
|
|
|
|
|
</Transition>
|
|
|
|
|
</Teleport>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { AutoLink, ButtonStyled } from '@modrinth/ui'
|
|
|
|
|
import { onClickOutside, useElementHover } from '@vueuse/core'
|
2026-03-14 22:43:59 +00:00
|
|
|
import { type Component, computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
|
2026-01-06 00:35:51 +00:00
|
|
|
|
|
|
|
|
interface Option {
|
|
|
|
|
id: string
|
2026-03-14 22:43:59 +00:00
|
|
|
icon?: Component
|
2026-01-06 00:35:51 +00:00
|
|
|
action?: (() => void) | string
|
|
|
|
|
shown?: boolean
|
|
|
|
|
color?: 'standard' | 'brand' | 'red' | 'orange' | 'green' | 'blue' | 'purple'
|
feat: content tab rewrite for worlds (#5136)
* feat: base content card component
* fix: tooltips + colors
* feat: fix orgs
* feat: base content tab internals rewrite
* feat: fix invalidmodal
* feat: add ContentModpackCard
* fix: extract types
* draft: layout
* feat: unlink modal
* feat: impl content tab
* fix: lint
* fix: toggling
* temp: disable updating stuff
* feat: selection v-model
* feat: bulk selection
* feat: mods tab rough draft
* feat: use fuse.js
* feat: add project combobox
* clean up project combobox
* feat: start install to play modal
* fix: events
* feat: use v-on
* feat: bulk actions + fix floating action bar width
* feat: figma alignments
* feat: migrate toggle to tailwind
* fix: row borders
* feat: disabled state
* feat: virtual list impl for card table based on window scroll
* fix: lint
* feat: virtualization + smaller contentcard items
* feat: use ContentCardTable + ContentCardItems
* feat: fix gap + border issues on last elm
* feat: cleanup + use proper searching
* fix: use TeleportOverflowMenu
* fix: fallback to svg if src is invalid on avatar component
* fix: storybook
* feat: start on updater modal
* feat: finish content updater modal
* feat: i18n pass
* feat: impl modal
* feat(app): backend changes for content tab refactor (#5237)
* feat: include_changelog=false for updater modal
* fix: hash overrides
* feat: update checking for modpack
* feat: qa
* feat: modpack content modal
* fix: padding in table to match modals + tightness
* fix: lint
* feat: delete modal
* feat: fix toggle bugs
* fix: prepr
* fix: duplicate messages
* qa: full width search
* qa: use bg-surface-1.5
* qa: animation for filter pills
* qa: standardize hover colors
* fix: border-[1px] is border
* qa: mass de-select actually mass selecting
* qa: match figma designs for floating action bar
* qa: modal fixes
* q: modal fixes x2
* fix: table border
* qa: confirm modals
* qa: modal alignment
* qa: re-add stuck heading + dedupe logic
* qa: dedupe virtual scrolling + remove dead components
* qa: responsiveness for content table + link fixes
* qa: version column link, tooltips + lint fixes
* qa: instance busy protections
* fix: installation freeze bug
* chore: remove old mods page
* refactor: deduplicate layout
* chore: delete old content page(s)
* qa
* qa
* qa
* feat: sort btn - to iterate
* fix: ml
* feat: date added
* fix: lint
* fix: formatting.ts removal
* feat: get_dependencies_as_content_items
* qa: final QA changes
* refactor: deduplicate + polish content.rs
* feat: hook up content.vue with v1
* feat: hide v1 content api behind frontend feature flag
* fix: query keys + copy on empty state
* chore: i18n pass
* feat: reimpl unlink + upload endpoint
* feat: use bulk endpoints v1
* fix: lint
* fix: flags
* fix: responsiveness via container queries
* fix: lint
* qa: 1
* qa: fixes
* qa: fix ssr issues with browse content
* qa: header page divider
* qa: modals
* fix: prepr
* fix: issues
* fix: lint
* fix: toggle v1 ff
* qa: 5
* qa: delete modal copy
* feat: creation flow modals (#5383)
* refactor: delete content v0 usages + impl
* feat: qa + fixes
* feat: installing banner using state event
* feat: fix modpack card bugs + filtering issues
* refactor: delete backups v0 api module
* feat: v1 servers GET endpoint
* fix: backups
* feat: swap to kyros upload v1 addon
* fix: use tanstack for loader.vue
* feat: finish install from discovery modal
* qa: bug fixes
* feat: set up installation settings
* fix: lint
* fix: typos
* fix: bugs
* fix: disable inline content
* feat: content tab improvements — upload UX, installation settings, and client-only indicators
Upload cancellation and navigation guard:
- Add ConfirmLeaveModal that prompts when navigating away during upload
- Cancel in-flight XHR uploads when user confirms leaving the page
- Add beforeunload handler to warn on browser/tab close during upload
- Track uploadedBytes/totalBytes in UploadState for progress display
- Replace Collapsible with Transition for upload progress admonition
- Show byte progress and percentage in upload banner
- Clamp upload progress to prevent exceeding 100%
Installation settings (server.properties):
- Add KnownPropertiesFields and PropertiesFields types to Archon types
- Add buildProperties() to creation flow context to collect gamemode,
difficulty, seed, world type, structures, and generator settings
- Pass properties through installContent on onboarding, discovery, and
ServerSetupModal flows
Server setup and discovery flow improvements:
- Migrate ServerSetupModal from servers_v0.reinstall to content_v1.installContent
- Replace loaderApiNames lookup with toApiLoader() helper
- Remove eraseDataOnInstall toggle — always use soft_override: false
- Simplify modpack install on discovery page to use first available version
and route through creation flow modal for both onboarding and non-onboarding
- Differentiate post-install navigation: content page for onboarding,
loader options for existing servers
Modpack update flow:
- Replace updateModpack() call with installContent() using soft_override: true
to support version selection in the content updater modal
Client-only mod indicators:
- Add environment field to AddonVersion (reuses Labrinth.Projects.v3.Environment)
- Add environment to ContentItem and isClientOnly to ContentCardTableItem
- Show orange TriangleAlertIcon with tooltip on client-only mods in content table
- Add "Client-only" filter pill to content filtering (controlled via
showClientOnlyFilter on ContentManagerContext)
- Apply client-only indicators in both ContentPageLayout and ModpackContentModal
Misc:
- Add CLAUDE.md note about using prepr commands for lint checks
- Export ConfirmLeaveModal from instances barrel
* fix: piping
* fix: switch content disable for linked server instances
* feat: client only filter
* fix: prepr
* feat: hasUpdate shape update
* feat: bulk update endpoint impl for content in panel
* feat: websocket state impl again with new phases
* fix: ws
* fix: use timeout fn for sync admon + fix content card layout scroll for browsers with overflow anchor bug
* fix: qa bugs
* fix: lint, a11y and i18n
* refactor: set up layouts folder properly
* fix: linked data cache stuff + lint
* feat: move installationsettings to shared layout
* fix: lint
* fix: issues
* feat: temp fuck staging up
* fix: lockfile
* fix: data sync issues on loader.vue
* fix: lint
* Hide shader configuration files from content list (#5499)
* feat: workaround search problem + split out reset
* fix: qa
* fix: changelog not showing on first open
* fix: qa + optimistic updating improvements
* fix: prepr+lint
* fix: qa
* feat: qa
* fix: lint
* fix: lint
* fix: build
* fix: build
* fix: type errors
* fix: fade and JAVA_HOME passthrough
* feat: qa
* feat: impl diff shit
* fix: qa
* fix: app qa
* feat: update diff modal
* fix: endpoint
* fix: qa
* fix: qa
* fix: use bulk in modpack modal
* feat: abort signal impl + fix issues
* fix: diff modal trunc
* feat: qa
* fix: qa
* feat: tooltip content tab
* fix: prepr
* fix: dismiss on settings btn
* feat: qa
* feat: dont clear handlers on disconnect
* fix: lint
* fix: wrangler + introduce staging-archon env file
---------
Signed-off-by: Calum H. <calum@modrinth.com>
Co-authored-by: tdgao <mr.trumgao@gmail.com>
Co-authored-by: Artyom Ezri <61311568+Artezon@users.noreply.github.com>
2026-03-12 20:24:32 +00:00
|
|
|
disabled?: boolean
|
|
|
|
|
tooltip?: string
|
2026-01-06 00:35:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type Divider = {
|
2026-01-28 20:09:24 +00:00
|
|
|
divider?: boolean
|
2026-01-06 00:35:51 +00:00
|
|
|
shown?: boolean
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type Item = Option | Divider
|
|
|
|
|
|
|
|
|
|
function isDivider(item: Item): item is Divider {
|
|
|
|
|
return (item as Divider).divider
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const props = withDefaults(
|
|
|
|
|
defineProps<{
|
|
|
|
|
options: Item[]
|
|
|
|
|
hoverable?: boolean
|
feat: content tab rewrite for worlds (#5136)
* feat: base content card component
* fix: tooltips + colors
* feat: fix orgs
* feat: base content tab internals rewrite
* feat: fix invalidmodal
* feat: add ContentModpackCard
* fix: extract types
* draft: layout
* feat: unlink modal
* feat: impl content tab
* fix: lint
* fix: toggling
* temp: disable updating stuff
* feat: selection v-model
* feat: bulk selection
* feat: mods tab rough draft
* feat: use fuse.js
* feat: add project combobox
* clean up project combobox
* feat: start install to play modal
* fix: events
* feat: use v-on
* feat: bulk actions + fix floating action bar width
* feat: figma alignments
* feat: migrate toggle to tailwind
* fix: row borders
* feat: disabled state
* feat: virtual list impl for card table based on window scroll
* fix: lint
* feat: virtualization + smaller contentcard items
* feat: use ContentCardTable + ContentCardItems
* feat: fix gap + border issues on last elm
* feat: cleanup + use proper searching
* fix: use TeleportOverflowMenu
* fix: fallback to svg if src is invalid on avatar component
* fix: storybook
* feat: start on updater modal
* feat: finish content updater modal
* feat: i18n pass
* feat: impl modal
* feat(app): backend changes for content tab refactor (#5237)
* feat: include_changelog=false for updater modal
* fix: hash overrides
* feat: update checking for modpack
* feat: qa
* feat: modpack content modal
* fix: padding in table to match modals + tightness
* fix: lint
* feat: delete modal
* feat: fix toggle bugs
* fix: prepr
* fix: duplicate messages
* qa: full width search
* qa: use bg-surface-1.5
* qa: animation for filter pills
* qa: standardize hover colors
* fix: border-[1px] is border
* qa: mass de-select actually mass selecting
* qa: match figma designs for floating action bar
* qa: modal fixes
* q: modal fixes x2
* fix: table border
* qa: confirm modals
* qa: modal alignment
* qa: re-add stuck heading + dedupe logic
* qa: dedupe virtual scrolling + remove dead components
* qa: responsiveness for content table + link fixes
* qa: version column link, tooltips + lint fixes
* qa: instance busy protections
* fix: installation freeze bug
* chore: remove old mods page
* refactor: deduplicate layout
* chore: delete old content page(s)
* qa
* qa
* qa
* feat: sort btn - to iterate
* fix: ml
* feat: date added
* fix: lint
* fix: formatting.ts removal
* feat: get_dependencies_as_content_items
* qa: final QA changes
* refactor: deduplicate + polish content.rs
* feat: hook up content.vue with v1
* feat: hide v1 content api behind frontend feature flag
* fix: query keys + copy on empty state
* chore: i18n pass
* feat: reimpl unlink + upload endpoint
* feat: use bulk endpoints v1
* fix: lint
* fix: flags
* fix: responsiveness via container queries
* fix: lint
* qa: 1
* qa: fixes
* qa: fix ssr issues with browse content
* qa: header page divider
* qa: modals
* fix: prepr
* fix: issues
* fix: lint
* fix: toggle v1 ff
* qa: 5
* qa: delete modal copy
* feat: creation flow modals (#5383)
* refactor: delete content v0 usages + impl
* feat: qa + fixes
* feat: installing banner using state event
* feat: fix modpack card bugs + filtering issues
* refactor: delete backups v0 api module
* feat: v1 servers GET endpoint
* fix: backups
* feat: swap to kyros upload v1 addon
* fix: use tanstack for loader.vue
* feat: finish install from discovery modal
* qa: bug fixes
* feat: set up installation settings
* fix: lint
* fix: typos
* fix: bugs
* fix: disable inline content
* feat: content tab improvements — upload UX, installation settings, and client-only indicators
Upload cancellation and navigation guard:
- Add ConfirmLeaveModal that prompts when navigating away during upload
- Cancel in-flight XHR uploads when user confirms leaving the page
- Add beforeunload handler to warn on browser/tab close during upload
- Track uploadedBytes/totalBytes in UploadState for progress display
- Replace Collapsible with Transition for upload progress admonition
- Show byte progress and percentage in upload banner
- Clamp upload progress to prevent exceeding 100%
Installation settings (server.properties):
- Add KnownPropertiesFields and PropertiesFields types to Archon types
- Add buildProperties() to creation flow context to collect gamemode,
difficulty, seed, world type, structures, and generator settings
- Pass properties through installContent on onboarding, discovery, and
ServerSetupModal flows
Server setup and discovery flow improvements:
- Migrate ServerSetupModal from servers_v0.reinstall to content_v1.installContent
- Replace loaderApiNames lookup with toApiLoader() helper
- Remove eraseDataOnInstall toggle — always use soft_override: false
- Simplify modpack install on discovery page to use first available version
and route through creation flow modal for both onboarding and non-onboarding
- Differentiate post-install navigation: content page for onboarding,
loader options for existing servers
Modpack update flow:
- Replace updateModpack() call with installContent() using soft_override: true
to support version selection in the content updater modal
Client-only mod indicators:
- Add environment field to AddonVersion (reuses Labrinth.Projects.v3.Environment)
- Add environment to ContentItem and isClientOnly to ContentCardTableItem
- Show orange TriangleAlertIcon with tooltip on client-only mods in content table
- Add "Client-only" filter pill to content filtering (controlled via
showClientOnlyFilter on ContentManagerContext)
- Apply client-only indicators in both ContentPageLayout and ModpackContentModal
Misc:
- Add CLAUDE.md note about using prepr commands for lint checks
- Export ConfirmLeaveModal from instances barrel
* fix: piping
* fix: switch content disable for linked server instances
* feat: client only filter
* fix: prepr
* feat: hasUpdate shape update
* feat: bulk update endpoint impl for content in panel
* feat: websocket state impl again with new phases
* fix: ws
* fix: use timeout fn for sync admon + fix content card layout scroll for browsers with overflow anchor bug
* fix: qa bugs
* fix: lint, a11y and i18n
* refactor: set up layouts folder properly
* fix: linked data cache stuff + lint
* feat: move installationsettings to shared layout
* fix: lint
* fix: issues
* feat: temp fuck staging up
* fix: lockfile
* fix: data sync issues on loader.vue
* fix: lint
* Hide shader configuration files from content list (#5499)
* feat: workaround search problem + split out reset
* fix: qa
* fix: changelog not showing on first open
* fix: qa + optimistic updating improvements
* fix: prepr+lint
* fix: qa
* feat: qa
* fix: lint
* fix: lint
* fix: build
* fix: build
* fix: type errors
* fix: fade and JAVA_HOME passthrough
* feat: qa
* feat: impl diff shit
* fix: qa
* fix: app qa
* feat: update diff modal
* fix: endpoint
* fix: qa
* fix: qa
* fix: use bulk in modpack modal
* feat: abort signal impl + fix issues
* fix: diff modal trunc
* feat: qa
* fix: qa
* feat: tooltip content tab
* fix: prepr
* fix: dismiss on settings btn
* feat: qa
* feat: dont clear handlers on disconnect
* fix: lint
* fix: wrangler + introduce staging-archon env file
---------
Signed-off-by: Calum H. <calum@modrinth.com>
Co-authored-by: tdgao <mr.trumgao@gmail.com>
Co-authored-by: Artyom Ezri <61311568+Artezon@users.noreply.github.com>
2026-03-12 20:24:32 +00:00
|
|
|
btnClass?: string | string[] | Record<string, boolean>
|
2026-01-06 00:35:51 +00:00
|
|
|
}>(),
|
|
|
|
|
{
|
|
|
|
|
hoverable: false,
|
feat: content tab rewrite for worlds (#5136)
* feat: base content card component
* fix: tooltips + colors
* feat: fix orgs
* feat: base content tab internals rewrite
* feat: fix invalidmodal
* feat: add ContentModpackCard
* fix: extract types
* draft: layout
* feat: unlink modal
* feat: impl content tab
* fix: lint
* fix: toggling
* temp: disable updating stuff
* feat: selection v-model
* feat: bulk selection
* feat: mods tab rough draft
* feat: use fuse.js
* feat: add project combobox
* clean up project combobox
* feat: start install to play modal
* fix: events
* feat: use v-on
* feat: bulk actions + fix floating action bar width
* feat: figma alignments
* feat: migrate toggle to tailwind
* fix: row borders
* feat: disabled state
* feat: virtual list impl for card table based on window scroll
* fix: lint
* feat: virtualization + smaller contentcard items
* feat: use ContentCardTable + ContentCardItems
* feat: fix gap + border issues on last elm
* feat: cleanup + use proper searching
* fix: use TeleportOverflowMenu
* fix: fallback to svg if src is invalid on avatar component
* fix: storybook
* feat: start on updater modal
* feat: finish content updater modal
* feat: i18n pass
* feat: impl modal
* feat(app): backend changes for content tab refactor (#5237)
* feat: include_changelog=false for updater modal
* fix: hash overrides
* feat: update checking for modpack
* feat: qa
* feat: modpack content modal
* fix: padding in table to match modals + tightness
* fix: lint
* feat: delete modal
* feat: fix toggle bugs
* fix: prepr
* fix: duplicate messages
* qa: full width search
* qa: use bg-surface-1.5
* qa: animation for filter pills
* qa: standardize hover colors
* fix: border-[1px] is border
* qa: mass de-select actually mass selecting
* qa: match figma designs for floating action bar
* qa: modal fixes
* q: modal fixes x2
* fix: table border
* qa: confirm modals
* qa: modal alignment
* qa: re-add stuck heading + dedupe logic
* qa: dedupe virtual scrolling + remove dead components
* qa: responsiveness for content table + link fixes
* qa: version column link, tooltips + lint fixes
* qa: instance busy protections
* fix: installation freeze bug
* chore: remove old mods page
* refactor: deduplicate layout
* chore: delete old content page(s)
* qa
* qa
* qa
* feat: sort btn - to iterate
* fix: ml
* feat: date added
* fix: lint
* fix: formatting.ts removal
* feat: get_dependencies_as_content_items
* qa: final QA changes
* refactor: deduplicate + polish content.rs
* feat: hook up content.vue with v1
* feat: hide v1 content api behind frontend feature flag
* fix: query keys + copy on empty state
* chore: i18n pass
* feat: reimpl unlink + upload endpoint
* feat: use bulk endpoints v1
* fix: lint
* fix: flags
* fix: responsiveness via container queries
* fix: lint
* qa: 1
* qa: fixes
* qa: fix ssr issues with browse content
* qa: header page divider
* qa: modals
* fix: prepr
* fix: issues
* fix: lint
* fix: toggle v1 ff
* qa: 5
* qa: delete modal copy
* feat: creation flow modals (#5383)
* refactor: delete content v0 usages + impl
* feat: qa + fixes
* feat: installing banner using state event
* feat: fix modpack card bugs + filtering issues
* refactor: delete backups v0 api module
* feat: v1 servers GET endpoint
* fix: backups
* feat: swap to kyros upload v1 addon
* fix: use tanstack for loader.vue
* feat: finish install from discovery modal
* qa: bug fixes
* feat: set up installation settings
* fix: lint
* fix: typos
* fix: bugs
* fix: disable inline content
* feat: content tab improvements — upload UX, installation settings, and client-only indicators
Upload cancellation and navigation guard:
- Add ConfirmLeaveModal that prompts when navigating away during upload
- Cancel in-flight XHR uploads when user confirms leaving the page
- Add beforeunload handler to warn on browser/tab close during upload
- Track uploadedBytes/totalBytes in UploadState for progress display
- Replace Collapsible with Transition for upload progress admonition
- Show byte progress and percentage in upload banner
- Clamp upload progress to prevent exceeding 100%
Installation settings (server.properties):
- Add KnownPropertiesFields and PropertiesFields types to Archon types
- Add buildProperties() to creation flow context to collect gamemode,
difficulty, seed, world type, structures, and generator settings
- Pass properties through installContent on onboarding, discovery, and
ServerSetupModal flows
Server setup and discovery flow improvements:
- Migrate ServerSetupModal from servers_v0.reinstall to content_v1.installContent
- Replace loaderApiNames lookup with toApiLoader() helper
- Remove eraseDataOnInstall toggle — always use soft_override: false
- Simplify modpack install on discovery page to use first available version
and route through creation flow modal for both onboarding and non-onboarding
- Differentiate post-install navigation: content page for onboarding,
loader options for existing servers
Modpack update flow:
- Replace updateModpack() call with installContent() using soft_override: true
to support version selection in the content updater modal
Client-only mod indicators:
- Add environment field to AddonVersion (reuses Labrinth.Projects.v3.Environment)
- Add environment to ContentItem and isClientOnly to ContentCardTableItem
- Show orange TriangleAlertIcon with tooltip on client-only mods in content table
- Add "Client-only" filter pill to content filtering (controlled via
showClientOnlyFilter on ContentManagerContext)
- Apply client-only indicators in both ContentPageLayout and ModpackContentModal
Misc:
- Add CLAUDE.md note about using prepr commands for lint checks
- Export ConfirmLeaveModal from instances barrel
* fix: piping
* fix: switch content disable for linked server instances
* feat: client only filter
* fix: prepr
* feat: hasUpdate shape update
* feat: bulk update endpoint impl for content in panel
* feat: websocket state impl again with new phases
* fix: ws
* fix: use timeout fn for sync admon + fix content card layout scroll for browsers with overflow anchor bug
* fix: qa bugs
* fix: lint, a11y and i18n
* refactor: set up layouts folder properly
* fix: linked data cache stuff + lint
* feat: move installationsettings to shared layout
* fix: lint
* fix: issues
* feat: temp fuck staging up
* fix: lockfile
* fix: data sync issues on loader.vue
* fix: lint
* Hide shader configuration files from content list (#5499)
* feat: workaround search problem + split out reset
* fix: qa
* fix: changelog not showing on first open
* fix: qa + optimistic updating improvements
* fix: prepr+lint
* fix: qa
* feat: qa
* fix: lint
* fix: lint
* fix: build
* fix: build
* fix: type errors
* fix: fade and JAVA_HOME passthrough
* feat: qa
* feat: impl diff shit
* fix: qa
* fix: app qa
* feat: update diff modal
* fix: endpoint
* fix: qa
* fix: qa
* fix: use bulk in modpack modal
* feat: abort signal impl + fix issues
* fix: diff modal trunc
* feat: qa
* fix: qa
* feat: tooltip content tab
* fix: prepr
* fix: dismiss on settings btn
* feat: qa
* feat: dont clear handlers on disconnect
* fix: lint
* fix: wrangler + introduce staging-archon env file
---------
Signed-off-by: Calum H. <calum@modrinth.com>
Co-authored-by: tdgao <mr.trumgao@gmail.com>
Co-authored-by: Artyom Ezri <61311568+Artezon@users.noreply.github.com>
2026-03-12 20:24:32 +00:00
|
|
|
btnClass: undefined,
|
2026-01-06 00:35:51 +00:00
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const emit = defineEmits<{
|
|
|
|
|
select: [option: Option]
|
feat: content tab rewrite for worlds (#5136)
* feat: base content card component
* fix: tooltips + colors
* feat: fix orgs
* feat: base content tab internals rewrite
* feat: fix invalidmodal
* feat: add ContentModpackCard
* fix: extract types
* draft: layout
* feat: unlink modal
* feat: impl content tab
* fix: lint
* fix: toggling
* temp: disable updating stuff
* feat: selection v-model
* feat: bulk selection
* feat: mods tab rough draft
* feat: use fuse.js
* feat: add project combobox
* clean up project combobox
* feat: start install to play modal
* fix: events
* feat: use v-on
* feat: bulk actions + fix floating action bar width
* feat: figma alignments
* feat: migrate toggle to tailwind
* fix: row borders
* feat: disabled state
* feat: virtual list impl for card table based on window scroll
* fix: lint
* feat: virtualization + smaller contentcard items
* feat: use ContentCardTable + ContentCardItems
* feat: fix gap + border issues on last elm
* feat: cleanup + use proper searching
* fix: use TeleportOverflowMenu
* fix: fallback to svg if src is invalid on avatar component
* fix: storybook
* feat: start on updater modal
* feat: finish content updater modal
* feat: i18n pass
* feat: impl modal
* feat(app): backend changes for content tab refactor (#5237)
* feat: include_changelog=false for updater modal
* fix: hash overrides
* feat: update checking for modpack
* feat: qa
* feat: modpack content modal
* fix: padding in table to match modals + tightness
* fix: lint
* feat: delete modal
* feat: fix toggle bugs
* fix: prepr
* fix: duplicate messages
* qa: full width search
* qa: use bg-surface-1.5
* qa: animation for filter pills
* qa: standardize hover colors
* fix: border-[1px] is border
* qa: mass de-select actually mass selecting
* qa: match figma designs for floating action bar
* qa: modal fixes
* q: modal fixes x2
* fix: table border
* qa: confirm modals
* qa: modal alignment
* qa: re-add stuck heading + dedupe logic
* qa: dedupe virtual scrolling + remove dead components
* qa: responsiveness for content table + link fixes
* qa: version column link, tooltips + lint fixes
* qa: instance busy protections
* fix: installation freeze bug
* chore: remove old mods page
* refactor: deduplicate layout
* chore: delete old content page(s)
* qa
* qa
* qa
* feat: sort btn - to iterate
* fix: ml
* feat: date added
* fix: lint
* fix: formatting.ts removal
* feat: get_dependencies_as_content_items
* qa: final QA changes
* refactor: deduplicate + polish content.rs
* feat: hook up content.vue with v1
* feat: hide v1 content api behind frontend feature flag
* fix: query keys + copy on empty state
* chore: i18n pass
* feat: reimpl unlink + upload endpoint
* feat: use bulk endpoints v1
* fix: lint
* fix: flags
* fix: responsiveness via container queries
* fix: lint
* qa: 1
* qa: fixes
* qa: fix ssr issues with browse content
* qa: header page divider
* qa: modals
* fix: prepr
* fix: issues
* fix: lint
* fix: toggle v1 ff
* qa: 5
* qa: delete modal copy
* feat: creation flow modals (#5383)
* refactor: delete content v0 usages + impl
* feat: qa + fixes
* feat: installing banner using state event
* feat: fix modpack card bugs + filtering issues
* refactor: delete backups v0 api module
* feat: v1 servers GET endpoint
* fix: backups
* feat: swap to kyros upload v1 addon
* fix: use tanstack for loader.vue
* feat: finish install from discovery modal
* qa: bug fixes
* feat: set up installation settings
* fix: lint
* fix: typos
* fix: bugs
* fix: disable inline content
* feat: content tab improvements — upload UX, installation settings, and client-only indicators
Upload cancellation and navigation guard:
- Add ConfirmLeaveModal that prompts when navigating away during upload
- Cancel in-flight XHR uploads when user confirms leaving the page
- Add beforeunload handler to warn on browser/tab close during upload
- Track uploadedBytes/totalBytes in UploadState for progress display
- Replace Collapsible with Transition for upload progress admonition
- Show byte progress and percentage in upload banner
- Clamp upload progress to prevent exceeding 100%
Installation settings (server.properties):
- Add KnownPropertiesFields and PropertiesFields types to Archon types
- Add buildProperties() to creation flow context to collect gamemode,
difficulty, seed, world type, structures, and generator settings
- Pass properties through installContent on onboarding, discovery, and
ServerSetupModal flows
Server setup and discovery flow improvements:
- Migrate ServerSetupModal from servers_v0.reinstall to content_v1.installContent
- Replace loaderApiNames lookup with toApiLoader() helper
- Remove eraseDataOnInstall toggle — always use soft_override: false
- Simplify modpack install on discovery page to use first available version
and route through creation flow modal for both onboarding and non-onboarding
- Differentiate post-install navigation: content page for onboarding,
loader options for existing servers
Modpack update flow:
- Replace updateModpack() call with installContent() using soft_override: true
to support version selection in the content updater modal
Client-only mod indicators:
- Add environment field to AddonVersion (reuses Labrinth.Projects.v3.Environment)
- Add environment to ContentItem and isClientOnly to ContentCardTableItem
- Show orange TriangleAlertIcon with tooltip on client-only mods in content table
- Add "Client-only" filter pill to content filtering (controlled via
showClientOnlyFilter on ContentManagerContext)
- Apply client-only indicators in both ContentPageLayout and ModpackContentModal
Misc:
- Add CLAUDE.md note about using prepr commands for lint checks
- Export ConfirmLeaveModal from instances barrel
* fix: piping
* fix: switch content disable for linked server instances
* feat: client only filter
* fix: prepr
* feat: hasUpdate shape update
* feat: bulk update endpoint impl for content in panel
* feat: websocket state impl again with new phases
* fix: ws
* fix: use timeout fn for sync admon + fix content card layout scroll for browsers with overflow anchor bug
* fix: qa bugs
* fix: lint, a11y and i18n
* refactor: set up layouts folder properly
* fix: linked data cache stuff + lint
* feat: move installationsettings to shared layout
* fix: lint
* fix: issues
* feat: temp fuck staging up
* fix: lockfile
* fix: data sync issues on loader.vue
* fix: lint
* Hide shader configuration files from content list (#5499)
* feat: workaround search problem + split out reset
* fix: qa
* fix: changelog not showing on first open
* fix: qa + optimistic updating improvements
* fix: prepr+lint
* fix: qa
* feat: qa
* fix: lint
* fix: lint
* fix: build
* fix: build
* fix: type errors
* fix: fade and JAVA_HOME passthrough
* feat: qa
* feat: impl diff shit
* fix: qa
* fix: app qa
* feat: update diff modal
* fix: endpoint
* fix: qa
* fix: qa
* fix: use bulk in modpack modal
* feat: abort signal impl + fix issues
* fix: diff modal trunc
* feat: qa
* fix: qa
* feat: tooltip content tab
* fix: prepr
* fix: dismiss on settings btn
* feat: qa
* feat: dont clear handlers on disconnect
* fix: lint
* fix: wrangler + introduce staging-archon env file
---------
Signed-off-by: Calum H. <calum@modrinth.com>
Co-authored-by: tdgao <mr.trumgao@gmail.com>
Co-authored-by: Artyom Ezri <61311568+Artezon@users.noreply.github.com>
2026-03-12 20:24:32 +00:00
|
|
|
open: []
|
2026-01-06 00:35:51 +00:00
|
|
|
}>()
|
|
|
|
|
|
|
|
|
|
const isOpen = ref(false)
|
|
|
|
|
const selectedIndex = ref(-1)
|
|
|
|
|
const menuRef = ref<HTMLElement | null>(null)
|
|
|
|
|
const triggerRef = ref<HTMLElement | null>(null)
|
|
|
|
|
const isMouseDown = ref(false)
|
|
|
|
|
const typeAheadBuffer = ref('')
|
|
|
|
|
const typeAheadTimeout = ref<number | null>(null)
|
|
|
|
|
const menuItemsRef = ref<HTMLElement[]>([])
|
|
|
|
|
|
|
|
|
|
const hoveringTrigger = useElementHover(triggerRef)
|
|
|
|
|
const hoveringMenu = useElementHover(menuRef)
|
|
|
|
|
|
|
|
|
|
const hovering = computed(() => hoveringTrigger.value || hoveringMenu.value)
|
|
|
|
|
|
|
|
|
|
const menuStyle = ref({
|
|
|
|
|
top: '0px',
|
|
|
|
|
left: '0px',
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const filteredOptions = computed(() => props.options.filter((option) => option.shown !== false))
|
|
|
|
|
|
|
|
|
|
const calculateMenuPosition = () => {
|
|
|
|
|
if (!triggerRef.value || !menuRef.value) return { top: '0px', left: '0px' }
|
|
|
|
|
|
|
|
|
|
const triggerRect = triggerRef.value.getBoundingClientRect()
|
2026-04-15 03:12:31 -06:00
|
|
|
const menuWidth = menuRef.value.offsetWidth
|
|
|
|
|
const menuHeight = menuRef.value.offsetHeight
|
2026-01-06 00:35:51 +00:00
|
|
|
const margin = 8
|
|
|
|
|
|
|
|
|
|
let top: number
|
|
|
|
|
let left: number
|
|
|
|
|
|
|
|
|
|
if (triggerRect.bottom + menuHeight + margin <= window.innerHeight) {
|
|
|
|
|
top = triggerRect.bottom + margin
|
|
|
|
|
} else if (triggerRect.top - menuHeight - margin >= 0) {
|
|
|
|
|
top = triggerRect.top - menuHeight - margin
|
|
|
|
|
} else {
|
|
|
|
|
top = Math.max(margin, window.innerHeight - menuHeight - margin)
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-18 18:03:55 +00:00
|
|
|
if (triggerRect.right - menuWidth >= margin) {
|
2026-01-06 00:35:51 +00:00
|
|
|
left = triggerRect.right - menuWidth
|
|
|
|
|
} else {
|
2026-04-12 15:38:08 -06:00
|
|
|
left = Math.max(margin, triggerRect.left)
|
2026-01-06 00:35:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
top: `${top}px`,
|
|
|
|
|
left: `${left}px`,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const toggleMenu = (event: MouseEvent) => {
|
|
|
|
|
event.stopPropagation()
|
|
|
|
|
if (!props.hoverable) {
|
|
|
|
|
if (isOpen.value) {
|
|
|
|
|
closeMenu()
|
|
|
|
|
} else {
|
|
|
|
|
openMenu()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const openMenu = () => {
|
|
|
|
|
isOpen.value = true
|
feat: content tab rewrite for worlds (#5136)
* feat: base content card component
* fix: tooltips + colors
* feat: fix orgs
* feat: base content tab internals rewrite
* feat: fix invalidmodal
* feat: add ContentModpackCard
* fix: extract types
* draft: layout
* feat: unlink modal
* feat: impl content tab
* fix: lint
* fix: toggling
* temp: disable updating stuff
* feat: selection v-model
* feat: bulk selection
* feat: mods tab rough draft
* feat: use fuse.js
* feat: add project combobox
* clean up project combobox
* feat: start install to play modal
* fix: events
* feat: use v-on
* feat: bulk actions + fix floating action bar width
* feat: figma alignments
* feat: migrate toggle to tailwind
* fix: row borders
* feat: disabled state
* feat: virtual list impl for card table based on window scroll
* fix: lint
* feat: virtualization + smaller contentcard items
* feat: use ContentCardTable + ContentCardItems
* feat: fix gap + border issues on last elm
* feat: cleanup + use proper searching
* fix: use TeleportOverflowMenu
* fix: fallback to svg if src is invalid on avatar component
* fix: storybook
* feat: start on updater modal
* feat: finish content updater modal
* feat: i18n pass
* feat: impl modal
* feat(app): backend changes for content tab refactor (#5237)
* feat: include_changelog=false for updater modal
* fix: hash overrides
* feat: update checking for modpack
* feat: qa
* feat: modpack content modal
* fix: padding in table to match modals + tightness
* fix: lint
* feat: delete modal
* feat: fix toggle bugs
* fix: prepr
* fix: duplicate messages
* qa: full width search
* qa: use bg-surface-1.5
* qa: animation for filter pills
* qa: standardize hover colors
* fix: border-[1px] is border
* qa: mass de-select actually mass selecting
* qa: match figma designs for floating action bar
* qa: modal fixes
* q: modal fixes x2
* fix: table border
* qa: confirm modals
* qa: modal alignment
* qa: re-add stuck heading + dedupe logic
* qa: dedupe virtual scrolling + remove dead components
* qa: responsiveness for content table + link fixes
* qa: version column link, tooltips + lint fixes
* qa: instance busy protections
* fix: installation freeze bug
* chore: remove old mods page
* refactor: deduplicate layout
* chore: delete old content page(s)
* qa
* qa
* qa
* feat: sort btn - to iterate
* fix: ml
* feat: date added
* fix: lint
* fix: formatting.ts removal
* feat: get_dependencies_as_content_items
* qa: final QA changes
* refactor: deduplicate + polish content.rs
* feat: hook up content.vue with v1
* feat: hide v1 content api behind frontend feature flag
* fix: query keys + copy on empty state
* chore: i18n pass
* feat: reimpl unlink + upload endpoint
* feat: use bulk endpoints v1
* fix: lint
* fix: flags
* fix: responsiveness via container queries
* fix: lint
* qa: 1
* qa: fixes
* qa: fix ssr issues with browse content
* qa: header page divider
* qa: modals
* fix: prepr
* fix: issues
* fix: lint
* fix: toggle v1 ff
* qa: 5
* qa: delete modal copy
* feat: creation flow modals (#5383)
* refactor: delete content v0 usages + impl
* feat: qa + fixes
* feat: installing banner using state event
* feat: fix modpack card bugs + filtering issues
* refactor: delete backups v0 api module
* feat: v1 servers GET endpoint
* fix: backups
* feat: swap to kyros upload v1 addon
* fix: use tanstack for loader.vue
* feat: finish install from discovery modal
* qa: bug fixes
* feat: set up installation settings
* fix: lint
* fix: typos
* fix: bugs
* fix: disable inline content
* feat: content tab improvements — upload UX, installation settings, and client-only indicators
Upload cancellation and navigation guard:
- Add ConfirmLeaveModal that prompts when navigating away during upload
- Cancel in-flight XHR uploads when user confirms leaving the page
- Add beforeunload handler to warn on browser/tab close during upload
- Track uploadedBytes/totalBytes in UploadState for progress display
- Replace Collapsible with Transition for upload progress admonition
- Show byte progress and percentage in upload banner
- Clamp upload progress to prevent exceeding 100%
Installation settings (server.properties):
- Add KnownPropertiesFields and PropertiesFields types to Archon types
- Add buildProperties() to creation flow context to collect gamemode,
difficulty, seed, world type, structures, and generator settings
- Pass properties through installContent on onboarding, discovery, and
ServerSetupModal flows
Server setup and discovery flow improvements:
- Migrate ServerSetupModal from servers_v0.reinstall to content_v1.installContent
- Replace loaderApiNames lookup with toApiLoader() helper
- Remove eraseDataOnInstall toggle — always use soft_override: false
- Simplify modpack install on discovery page to use first available version
and route through creation flow modal for both onboarding and non-onboarding
- Differentiate post-install navigation: content page for onboarding,
loader options for existing servers
Modpack update flow:
- Replace updateModpack() call with installContent() using soft_override: true
to support version selection in the content updater modal
Client-only mod indicators:
- Add environment field to AddonVersion (reuses Labrinth.Projects.v3.Environment)
- Add environment to ContentItem and isClientOnly to ContentCardTableItem
- Show orange TriangleAlertIcon with tooltip on client-only mods in content table
- Add "Client-only" filter pill to content filtering (controlled via
showClientOnlyFilter on ContentManagerContext)
- Apply client-only indicators in both ContentPageLayout and ModpackContentModal
Misc:
- Add CLAUDE.md note about using prepr commands for lint checks
- Export ConfirmLeaveModal from instances barrel
* fix: piping
* fix: switch content disable for linked server instances
* feat: client only filter
* fix: prepr
* feat: hasUpdate shape update
* feat: bulk update endpoint impl for content in panel
* feat: websocket state impl again with new phases
* fix: ws
* fix: use timeout fn for sync admon + fix content card layout scroll for browsers with overflow anchor bug
* fix: qa bugs
* fix: lint, a11y and i18n
* refactor: set up layouts folder properly
* fix: linked data cache stuff + lint
* feat: move installationsettings to shared layout
* fix: lint
* fix: issues
* feat: temp fuck staging up
* fix: lockfile
* fix: data sync issues on loader.vue
* fix: lint
* Hide shader configuration files from content list (#5499)
* feat: workaround search problem + split out reset
* fix: qa
* fix: changelog not showing on first open
* fix: qa + optimistic updating improvements
* fix: prepr+lint
* fix: qa
* feat: qa
* fix: lint
* fix: lint
* fix: build
* fix: build
* fix: type errors
* fix: fade and JAVA_HOME passthrough
* feat: qa
* feat: impl diff shit
* fix: qa
* fix: app qa
* feat: update diff modal
* fix: endpoint
* fix: qa
* fix: qa
* fix: use bulk in modpack modal
* feat: abort signal impl + fix issues
* fix: diff modal trunc
* feat: qa
* fix: qa
* feat: tooltip content tab
* fix: prepr
* fix: dismiss on settings btn
* feat: qa
* feat: dont clear handlers on disconnect
* fix: lint
* fix: wrangler + introduce staging-archon env file
---------
Signed-off-by: Calum H. <calum@modrinth.com>
Co-authored-by: tdgao <mr.trumgao@gmail.com>
Co-authored-by: Artyom Ezri <61311568+Artezon@users.noreply.github.com>
2026-03-12 20:24:32 +00:00
|
|
|
emit('open')
|
2026-01-06 00:35:51 +00:00
|
|
|
disableBodyScroll()
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
menuStyle.value = calculateMenuPosition()
|
|
|
|
|
document.addEventListener('mousemove', handleMouseMove)
|
|
|
|
|
focusFirstMenuItem()
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const closeMenu = () => {
|
|
|
|
|
isOpen.value = false
|
|
|
|
|
selectedIndex.value = -1
|
|
|
|
|
enableBodyScroll()
|
|
|
|
|
document.removeEventListener('mousemove', handleMouseMove)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const selectOption = (option: Option) => {
|
|
|
|
|
emit('select', option)
|
|
|
|
|
if (typeof option.action === 'function') {
|
|
|
|
|
option.action()
|
|
|
|
|
}
|
|
|
|
|
closeMenu()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleMouseDown = (event: MouseEvent) => {
|
|
|
|
|
event.preventDefault()
|
|
|
|
|
isMouseDown.value = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleMouseEnter = () => {
|
|
|
|
|
if (props.hoverable) {
|
|
|
|
|
openMenu()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleMouseLeave = () => {
|
|
|
|
|
if (props.hoverable) {
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
if (!hovering.value) {
|
|
|
|
|
closeMenu()
|
|
|
|
|
}
|
|
|
|
|
}, 250)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleMouseMove = (event: MouseEvent) => {
|
|
|
|
|
if (!isOpen.value || !isMouseDown.value) return
|
|
|
|
|
|
|
|
|
|
const menuRect = menuRef.value?.getBoundingClientRect()
|
|
|
|
|
if (!menuRect) return
|
|
|
|
|
|
|
|
|
|
const menuItems = menuRef.value?.querySelectorAll('[role="menuitem"]')
|
|
|
|
|
if (!menuItems) return
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < menuItems.length; i++) {
|
|
|
|
|
const itemRect = (menuItems[i] as HTMLElement).getBoundingClientRect()
|
|
|
|
|
if (
|
|
|
|
|
event.clientX >= itemRect.left &&
|
|
|
|
|
event.clientX <= itemRect.right &&
|
|
|
|
|
event.clientY >= itemRect.top &&
|
|
|
|
|
event.clientY <= itemRect.bottom
|
|
|
|
|
) {
|
|
|
|
|
selectedIndex.value = i
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleItemClick = (option: Option, index: number) => {
|
feat: content tab rewrite for worlds (#5136)
* feat: base content card component
* fix: tooltips + colors
* feat: fix orgs
* feat: base content tab internals rewrite
* feat: fix invalidmodal
* feat: add ContentModpackCard
* fix: extract types
* draft: layout
* feat: unlink modal
* feat: impl content tab
* fix: lint
* fix: toggling
* temp: disable updating stuff
* feat: selection v-model
* feat: bulk selection
* feat: mods tab rough draft
* feat: use fuse.js
* feat: add project combobox
* clean up project combobox
* feat: start install to play modal
* fix: events
* feat: use v-on
* feat: bulk actions + fix floating action bar width
* feat: figma alignments
* feat: migrate toggle to tailwind
* fix: row borders
* feat: disabled state
* feat: virtual list impl for card table based on window scroll
* fix: lint
* feat: virtualization + smaller contentcard items
* feat: use ContentCardTable + ContentCardItems
* feat: fix gap + border issues on last elm
* feat: cleanup + use proper searching
* fix: use TeleportOverflowMenu
* fix: fallback to svg if src is invalid on avatar component
* fix: storybook
* feat: start on updater modal
* feat: finish content updater modal
* feat: i18n pass
* feat: impl modal
* feat(app): backend changes for content tab refactor (#5237)
* feat: include_changelog=false for updater modal
* fix: hash overrides
* feat: update checking for modpack
* feat: qa
* feat: modpack content modal
* fix: padding in table to match modals + tightness
* fix: lint
* feat: delete modal
* feat: fix toggle bugs
* fix: prepr
* fix: duplicate messages
* qa: full width search
* qa: use bg-surface-1.5
* qa: animation for filter pills
* qa: standardize hover colors
* fix: border-[1px] is border
* qa: mass de-select actually mass selecting
* qa: match figma designs for floating action bar
* qa: modal fixes
* q: modal fixes x2
* fix: table border
* qa: confirm modals
* qa: modal alignment
* qa: re-add stuck heading + dedupe logic
* qa: dedupe virtual scrolling + remove dead components
* qa: responsiveness for content table + link fixes
* qa: version column link, tooltips + lint fixes
* qa: instance busy protections
* fix: installation freeze bug
* chore: remove old mods page
* refactor: deduplicate layout
* chore: delete old content page(s)
* qa
* qa
* qa
* feat: sort btn - to iterate
* fix: ml
* feat: date added
* fix: lint
* fix: formatting.ts removal
* feat: get_dependencies_as_content_items
* qa: final QA changes
* refactor: deduplicate + polish content.rs
* feat: hook up content.vue with v1
* feat: hide v1 content api behind frontend feature flag
* fix: query keys + copy on empty state
* chore: i18n pass
* feat: reimpl unlink + upload endpoint
* feat: use bulk endpoints v1
* fix: lint
* fix: flags
* fix: responsiveness via container queries
* fix: lint
* qa: 1
* qa: fixes
* qa: fix ssr issues with browse content
* qa: header page divider
* qa: modals
* fix: prepr
* fix: issues
* fix: lint
* fix: toggle v1 ff
* qa: 5
* qa: delete modal copy
* feat: creation flow modals (#5383)
* refactor: delete content v0 usages + impl
* feat: qa + fixes
* feat: installing banner using state event
* feat: fix modpack card bugs + filtering issues
* refactor: delete backups v0 api module
* feat: v1 servers GET endpoint
* fix: backups
* feat: swap to kyros upload v1 addon
* fix: use tanstack for loader.vue
* feat: finish install from discovery modal
* qa: bug fixes
* feat: set up installation settings
* fix: lint
* fix: typos
* fix: bugs
* fix: disable inline content
* feat: content tab improvements — upload UX, installation settings, and client-only indicators
Upload cancellation and navigation guard:
- Add ConfirmLeaveModal that prompts when navigating away during upload
- Cancel in-flight XHR uploads when user confirms leaving the page
- Add beforeunload handler to warn on browser/tab close during upload
- Track uploadedBytes/totalBytes in UploadState for progress display
- Replace Collapsible with Transition for upload progress admonition
- Show byte progress and percentage in upload banner
- Clamp upload progress to prevent exceeding 100%
Installation settings (server.properties):
- Add KnownPropertiesFields and PropertiesFields types to Archon types
- Add buildProperties() to creation flow context to collect gamemode,
difficulty, seed, world type, structures, and generator settings
- Pass properties through installContent on onboarding, discovery, and
ServerSetupModal flows
Server setup and discovery flow improvements:
- Migrate ServerSetupModal from servers_v0.reinstall to content_v1.installContent
- Replace loaderApiNames lookup with toApiLoader() helper
- Remove eraseDataOnInstall toggle — always use soft_override: false
- Simplify modpack install on discovery page to use first available version
and route through creation flow modal for both onboarding and non-onboarding
- Differentiate post-install navigation: content page for onboarding,
loader options for existing servers
Modpack update flow:
- Replace updateModpack() call with installContent() using soft_override: true
to support version selection in the content updater modal
Client-only mod indicators:
- Add environment field to AddonVersion (reuses Labrinth.Projects.v3.Environment)
- Add environment to ContentItem and isClientOnly to ContentCardTableItem
- Show orange TriangleAlertIcon with tooltip on client-only mods in content table
- Add "Client-only" filter pill to content filtering (controlled via
showClientOnlyFilter on ContentManagerContext)
- Apply client-only indicators in both ContentPageLayout and ModpackContentModal
Misc:
- Add CLAUDE.md note about using prepr commands for lint checks
- Export ConfirmLeaveModal from instances barrel
* fix: piping
* fix: switch content disable for linked server instances
* feat: client only filter
* fix: prepr
* feat: hasUpdate shape update
* feat: bulk update endpoint impl for content in panel
* feat: websocket state impl again with new phases
* fix: ws
* fix: use timeout fn for sync admon + fix content card layout scroll for browsers with overflow anchor bug
* fix: qa bugs
* fix: lint, a11y and i18n
* refactor: set up layouts folder properly
* fix: linked data cache stuff + lint
* feat: move installationsettings to shared layout
* fix: lint
* fix: issues
* feat: temp fuck staging up
* fix: lockfile
* fix: data sync issues on loader.vue
* fix: lint
* Hide shader configuration files from content list (#5499)
* feat: workaround search problem + split out reset
* fix: qa
* fix: changelog not showing on first open
* fix: qa + optimistic updating improvements
* fix: prepr+lint
* fix: qa
* feat: qa
* fix: lint
* fix: lint
* fix: build
* fix: build
* fix: type errors
* fix: fade and JAVA_HOME passthrough
* feat: qa
* feat: impl diff shit
* fix: qa
* fix: app qa
* feat: update diff modal
* fix: endpoint
* fix: qa
* fix: qa
* fix: use bulk in modpack modal
* feat: abort signal impl + fix issues
* fix: diff modal trunc
* feat: qa
* fix: qa
* feat: tooltip content tab
* fix: prepr
* fix: dismiss on settings btn
* feat: qa
* feat: dont clear handlers on disconnect
* fix: lint
* fix: wrangler + introduce staging-archon env file
---------
Signed-off-by: Calum H. <calum@modrinth.com>
Co-authored-by: tdgao <mr.trumgao@gmail.com>
Co-authored-by: Artyom Ezri <61311568+Artezon@users.noreply.github.com>
2026-03-12 20:24:32 +00:00
|
|
|
if (option.disabled) return
|
2026-01-06 00:35:51 +00:00
|
|
|
selectedIndex.value = index
|
|
|
|
|
selectOption(option)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleMouseOver = (index: number) => {
|
|
|
|
|
selectedIndex.value = index
|
|
|
|
|
menuItemsRef.value[selectedIndex.value]?.focus?.()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const disableBodyScroll = () => {
|
|
|
|
|
document.body.style.overflow = 'hidden'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const enableBodyScroll = () => {
|
|
|
|
|
document.body.style.overflow = ''
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const focusFirstMenuItem = () => {
|
|
|
|
|
if (menuItemsRef.value.length > 0) {
|
|
|
|
|
menuItemsRef.value[0]?.focus?.()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleKeydown = (event: KeyboardEvent) => {
|
|
|
|
|
if (!isOpen.value) {
|
|
|
|
|
if (event.key === 'Enter' || event.key === ' ') {
|
|
|
|
|
event.preventDefault()
|
|
|
|
|
openMenu()
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (event.key) {
|
|
|
|
|
case 'ArrowDown':
|
|
|
|
|
event.preventDefault()
|
|
|
|
|
selectedIndex.value = (selectedIndex.value + 1) % filteredOptions.value.length
|
|
|
|
|
menuItemsRef.value[selectedIndex.value]?.focus?.()
|
|
|
|
|
break
|
|
|
|
|
case 'ArrowUp':
|
|
|
|
|
event.preventDefault()
|
|
|
|
|
selectedIndex.value =
|
|
|
|
|
(selectedIndex.value - 1 + filteredOptions.value.length) % filteredOptions.value.length
|
|
|
|
|
menuItemsRef.value[selectedIndex.value]?.focus?.()
|
|
|
|
|
break
|
|
|
|
|
case 'Home':
|
|
|
|
|
event.preventDefault()
|
|
|
|
|
if (menuItemsRef.value.length > 0) {
|
|
|
|
|
selectedIndex.value = 0
|
|
|
|
|
menuItemsRef.value[selectedIndex.value]?.focus?.()
|
|
|
|
|
}
|
|
|
|
|
break
|
|
|
|
|
case 'End':
|
|
|
|
|
event.preventDefault()
|
|
|
|
|
if (menuItemsRef.value.length > 0) {
|
|
|
|
|
selectedIndex.value = filteredOptions.value.length - 1
|
|
|
|
|
menuItemsRef.value[selectedIndex.value]?.focus?.()
|
|
|
|
|
}
|
|
|
|
|
break
|
|
|
|
|
case 'Enter':
|
|
|
|
|
case ' ':
|
|
|
|
|
event.preventDefault()
|
|
|
|
|
if (selectedIndex.value >= 0) {
|
|
|
|
|
const option = filteredOptions.value[selectedIndex.value]
|
|
|
|
|
if (isDivider(option)) break
|
|
|
|
|
selectOption(option)
|
|
|
|
|
}
|
|
|
|
|
break
|
|
|
|
|
case 'Escape':
|
|
|
|
|
event.preventDefault()
|
|
|
|
|
closeMenu()
|
|
|
|
|
triggerRef.value?.focus?.()
|
|
|
|
|
break
|
|
|
|
|
case 'Tab':
|
|
|
|
|
event.preventDefault()
|
|
|
|
|
if (menuItemsRef.value.length > 0) {
|
|
|
|
|
if (event.shiftKey) {
|
|
|
|
|
selectedIndex.value =
|
|
|
|
|
(selectedIndex.value - 1 + filteredOptions.value.length) % filteredOptions.value.length
|
|
|
|
|
} else {
|
|
|
|
|
selectedIndex.value = (selectedIndex.value + 1) % filteredOptions.value.length
|
|
|
|
|
}
|
|
|
|
|
menuItemsRef.value[selectedIndex.value]?.focus?.()
|
|
|
|
|
}
|
|
|
|
|
break
|
|
|
|
|
default:
|
|
|
|
|
if (event.key.length === 1) {
|
|
|
|
|
typeAheadBuffer.value += event.key.toLowerCase()
|
|
|
|
|
const matchIndex = filteredOptions.value.findIndex(
|
|
|
|
|
(option) =>
|
|
|
|
|
!isDivider(option) && option.id.toLowerCase().startsWith(typeAheadBuffer.value),
|
|
|
|
|
)
|
|
|
|
|
if (matchIndex !== -1) {
|
|
|
|
|
selectedIndex.value = matchIndex
|
|
|
|
|
menuItemsRef.value[selectedIndex.value]?.focus?.()
|
|
|
|
|
}
|
|
|
|
|
if (typeAheadTimeout.value) {
|
|
|
|
|
clearTimeout(typeAheadTimeout.value)
|
|
|
|
|
}
|
|
|
|
|
typeAheadTimeout.value = setTimeout(() => {
|
|
|
|
|
typeAheadBuffer.value = ''
|
|
|
|
|
}, 1000) as unknown as number
|
|
|
|
|
}
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleResizeOrScroll = () => {
|
|
|
|
|
if (isOpen.value) {
|
|
|
|
|
menuStyle.value = calculateMenuPosition()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const throttle = <T extends unknown[]>(
|
|
|
|
|
func: (...args: T) => void,
|
|
|
|
|
limit: number,
|
|
|
|
|
): ((...args: T) => void) => {
|
|
|
|
|
let inThrottle: boolean
|
|
|
|
|
return function (...args: T) {
|
|
|
|
|
if (!inThrottle) {
|
|
|
|
|
func(...args)
|
|
|
|
|
inThrottle = true
|
|
|
|
|
setTimeout(() => (inThrottle = false), limit)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const throttledHandleResizeOrScroll = throttle(handleResizeOrScroll, 100)
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
triggerRef.value?.addEventListener('keydown', handleKeydown)
|
|
|
|
|
window.addEventListener('resize', throttledHandleResizeOrScroll)
|
|
|
|
|
window.addEventListener('scroll', throttledHandleResizeOrScroll)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
|
triggerRef.value?.removeEventListener('keydown', handleKeydown)
|
|
|
|
|
window.removeEventListener('resize', throttledHandleResizeOrScroll)
|
|
|
|
|
window.removeEventListener('scroll', throttledHandleResizeOrScroll)
|
|
|
|
|
document.removeEventListener('mousemove', handleMouseMove)
|
|
|
|
|
if (typeAheadTimeout.value) {
|
|
|
|
|
clearTimeout(typeAheadTimeout.value)
|
|
|
|
|
}
|
|
|
|
|
enableBodyScroll()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
watch(isOpen, (newValue) => {
|
|
|
|
|
if (newValue) {
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
menuRef.value?.addEventListener('keydown', handleKeydown)
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
menuRef.value?.removeEventListener('keydown', handleKeydown)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
onClickOutside(menuRef, (event) => {
|
|
|
|
|
if (!triggerRef.value?.contains(event.target as Node)) {
|
|
|
|
|
closeMenu()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
</script>
|