Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions docs/kit/dock-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ interface DockEntry {
groupId?: string
/** Member opened when a group button is activated (for type: 'group') */
defaultChildId?: string
/** Per-group override of in-group sub-category order (for type: 'group') — see Categories inside a group */
categoryOrder?: Record<string, number>
/** URL to load in the iframe (for type: 'iframe') */
url?: string
/** Action configuration (for type: 'action') */
Expand Down Expand Up @@ -504,6 +506,22 @@ ctx.docks.register({ id: 'nuxt:graph', title: 'Graph', icon: 'ph:graph-duotone',

An orphan member (its `groupId` matches no registered group) has no group to supply an outer bucket, so it falls back to its own `category`.

A group can reshuffle its own sub-category order with `categoryOrder`, a `Record<category, weight>` that overrides `DEFAULT_CATEGORIES_ORDER` for that group's members only — every other group and the outer dock-bar order are untouched:

```ts
// 'advanced' now leads 'app' inside this group, reversing the shared default.
ctx.docks.register({
id: 'nuxt',
title: 'Nuxt',
icon: 'logos:nuxt-icon',
type: 'group',
category: 'framework',
categoryOrder: { advanced: -1, app: 1 },
})
```

A sub-category the map omits keeps its weight from the shared table.

### The built-in Vite+ group

Vite DevTools seeds a built-in **Vite+** group that collects Vite ecosystem integrations under one button. Join it with the exported id:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,49 @@ describe('in-group sub-categories (dual role of `category`)', () => {
})
})

describe('per-group sub-category order override (group.categoryOrder)', () => {
// Same shape as the sub-category fixture above ('app' before 'advanced' by
// default), but the 'nuxt' group flips that with its own `categoryOrder`.
const entries: DevToolsDockEntry[] = [
group('nuxt', { category: 'framework', categoryOrder: { advanced: -1, app: 1 } }),
iframe('nuxt:overview', { groupId: 'nuxt', category: 'app' }),
iframe('nuxt:graph', { groupId: 'nuxt', category: 'advanced' }),
group('other', { category: 'framework' }),
iframe('other:page', { groupId: 'other', category: 'app' }),
iframe('other:tools', { groupId: 'other', category: 'advanced' }),
]

it('reorders sub-categories inside the overriding group only', () => {
const sub = getGroupMembersGrouped(entries, 'nuxt', settings)
// 'advanced' (-1) now sorts ahead of 'app' (1) — the reverse of the default table
expect(sub.map(([c]) => c)).toEqual(['advanced', 'app'])
})

it('leaves other groups on the shared DEFAULT_CATEGORIES_ORDER table', () => {
const sub = getGroupMembersGrouped(entries, 'other', settings)
// unaffected by nuxt's override: default order is 'app' (100) before 'advanced' (400)
expect(sub.map(([c]) => c)).toEqual(['app', 'advanced'])
})

it('leaves the outer dock bar unaffected', () => {
const grouped = docksGroupByCategories(entries, settings, { collapseGroups: true })
// both group buttons still sort by their own outer category ('framework'), untouched
expect(grouped.map(([c]) => c)).toEqual(['framework'])
})

it('falls back to the shared table for sub-categories the override omits', () => {
const partial: DevToolsDockEntry[] = [
group('g', { category: 'framework', categoryOrder: { advanced: -1 } }),
iframe('g:a', { groupId: 'g', category: 'app' }),
iframe('g:b', { groupId: 'g', category: 'advanced' }),
iframe('g:c', { groupId: 'g', category: 'web' }),
]
const sub = getGroupMembersGrouped(partial, 'g', settings)
// 'advanced' (-1, overridden) leads; 'app' (100) and 'web' (300) keep the shared order
expect(sub.map(([c]) => c)).toEqual(['advanced', 'app', 'web'])
})
})

describe('pinning re-buckets into the ~pinned category', () => {
function categoryOf(grouped: ReturnType<typeof docksGroupByCategories>, id: string): string | undefined {
return grouped.find(([, items]) => items.some(i => i.id === id))?.[0]
Expand Down
35 changes: 25 additions & 10 deletions packages/core/src/client/webcomponents/state/dock-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,16 @@ export const PINNED_CATEGORY_ORDER = -100000

/**
* Resolve a category's sort weight, layering the local {@link PINNED_CATEGORY}
* override on top of the upstream {@link DEFAULT_CATEGORIES_ORDER} table.
* override, then a caller-supplied `overrides` map (a group's own
* {@link DevToolsViewGroup.categoryOrder}), on top of the upstream
* {@link DEFAULT_CATEGORIES_ORDER} table. `overrides` is per-call — passing a
* group's map only reweights that group's in-group sub-categories, never the
* outer bar or any other group.
*/
function categoryOrder(category: string): number {
function categoryOrder(category: string, overrides?: Record<string, number>): number {
if (category === PINNED_CATEGORY)
return PINNED_CATEGORY_ORDER
return DEFAULT_CATEGORIES_ORDER[category] || 0
return overrides?.[category] ?? DEFAULT_CATEGORIES_ORDER[category] ?? 0
}

export interface SplitGroupsResult {
Expand Down Expand Up @@ -127,8 +131,11 @@ export function getEntryGroup(
* lives in, so it never bleeds into the sub-category split here. A pinned member
* moves to a `~pinned` sub-category (leading the group, via
* {@link PINNED_CATEGORY_ORDER}). Sub-categories are ordered by the same
* {@link DEFAULT_CATEGORIES_ORDER} table as top-level categories, but they are
* not independently hideable (the outer category-hide toggle does not apply
* {@link DEFAULT_CATEGORIES_ORDER} table as top-level categories — unless the
* group entry itself sets {@link DevToolsViewGroup.categoryOrder}, whose
* weights take precedence for this group's sub-categories only, leaving every
* other group and the outer bar on the shared table. Sub-categories are not
* independently hideable (the outer category-hide toggle does not apply
* inside a group).
*/
export function getGroupMembersGrouped(
Expand All @@ -140,9 +147,11 @@ export function getGroupMembersGrouped(
const members = entries.filter(e => e.type !== 'group' && e.groupId === groupId)
if (!settings)
return members.length ? [['default', members]] : []
const group = entries.find((e): e is DevToolsViewGroup => e.type === 'group' && e.id === groupId)
// Group by the members' own `category` (the in-group sub-category), never the
// group's category. Category-hide is an outer-bar concern, so it is ignored.
return docksGroupByCategories(members, settings, { ...options, ignoreCategoryHidden: true })
// The group's own `categoryOrder`, if set, reweights only this split.
return docksGroupByCategories(members, settings, { ...options, ignoreCategoryHidden: true, categoryOrderOverride: group?.categoryOrder })
}

/**
Expand Down Expand Up @@ -186,14 +195,20 @@ export function getGroupMembers(
* Because the pinned bucket is chosen before the category-hide check and is
* itself never hideable, a pinned entry stays visible even when its original
* category is hidden.
*
* `categoryOrderOverride` reweights the categories produced by *this call*
* (used by {@link getGroupMembersGrouped} to apply a group's own
* {@link DevToolsViewGroup.categoryOrder} to its in-group sub-category split)
* — it never touches the shared {@link DEFAULT_CATEGORIES_ORDER} table, so it
* has no effect on any other call, group, or the outer bar.
*/
export function docksGroupByCategories(
entries: DevToolsDockEntry[],
settings: Immutable<DevToolsDocksUserSettings>,
options?: { includeHidden?: boolean, whenContext?: WhenContext, collapseGroups?: boolean, ignoreCategoryHidden?: boolean },
options?: { includeHidden?: boolean, whenContext?: WhenContext, collapseGroups?: boolean, ignoreCategoryHidden?: boolean, categoryOrderOverride?: Record<string, number> },
): DevToolsDockEntriesGrouped {
const { docksHidden, docksCategoriesHidden, docksCustomOrder, docksPinned } = settings
const { includeHidden = false, whenContext, collapseGroups = false, ignoreCategoryHidden = false } = options ?? {}
const { includeHidden = false, whenContext, collapseGroups = false, ignoreCategoryHidden = false, categoryOrderOverride } = options ?? {}

// Map every registered group id to its resolved outer category. A grouped
// member's OUTER bucket is its group's category (the member's own `category`
Expand Down Expand Up @@ -258,8 +273,8 @@ export function docksGroupByCategories(
const grouped = Array
.from(map.entries())
.sort(([a], [b]) => {
const ia = categoryOrder(a)
const ib = categoryOrder(b)
const ia = categoryOrder(a, categoryOrderOverride)
const ib = categoryOrder(b, categoryOrderOverride)
return ib === ia ? b.localeCompare(a) : ia - ib
})

Expand Down
Loading
Loading