11<script setup lang="ts">
22import type { DocksContext } from ' @devframes/hub/client'
33import type { CSSProperties } from ' vue'
4+ import type { DevframeDockEntriesGrouped } from ' ../../state/dock-settings'
45import type { DockEdge as DockEdgePosition , DockLayout } from ' ./dock-layout'
56import { useEventListener } from ' @vueuse/core'
6- import { computed , h , onMounted , ref , useTemplateRef } from ' vue'
7- import { getEntryGroup } from ' ../../state/dock-settings'
8- import { setEdgePositionDropdown , setFloatingTooltip , useDocksGroupPanel , useEdgePositionDropdown } from ' ../../state/floating-tooltip'
7+ import { computed , h , onMounted , ref , useTemplateRef , watch } from ' vue'
8+ import { BUILTIN_ENTRY_SETTINGS } from ' ../../constants'
9+ import { docksSplitGroupsBySize , getEntryGroup } from ' ../../state/dock-settings'
10+ import { setEdgePositionDropdown , setFloatingTooltip , useDocksGroupPanel , useDocksOverflowPanel , useEdgePositionDropdown } from ' ../../state/floating-tooltip'
911import { useSettings } from ' ../../state/settings-defaults'
1012import { getEntryPaneKey , useIframePanes } from ' ../../utils/useIframePanes'
1113import BrandMark from ' ../icons/BrandMark.vue'
1214import ViewEntry from ' ../views/ViewEntry.vue'
1315import { resolveDockEdge , resolveDockLayout } from ' ./dock-layout'
16+ import DockEntries from ' ./DockEntries.vue'
1417import DockEntriesWithCategories from ' ./DockEntriesWithCategories.vue'
1518import DockGroupSidebar from ' ./DockGroupSidebar.vue'
19+ import DockOverflowButton from ' ./DockOverflowButton.vue'
1620import DockPanelResizer from ' ./DockPanelResizer.vue'
1721
1822const props = defineProps <{
@@ -31,7 +35,43 @@ const panes = useIframePanes(viewsContainer, context.panel, () => getEntryPaneKe
3135
3236const isVertical = computed (() => store .position === ' left' || store .position === ' right' )
3337
34- const groupedEntries = computed (() => context .docks .groupedEntries )
38+ const settingsEntries = computed (() => context .docks .groupedEntries .flatMap (([, entries ]) => entries .filter (entry => entry .id === BUILTIN_ENTRY_SETTINGS .id )))
39+ const groupedEntries = computed (() => context .docks .groupedEntries
40+ .map (([category , entries ]): DevframeDockEntriesGrouped [number ] => [category , entries .filter (entry => entry .id !== BUILTIN_ENTRY_SETTINGS .id )])
41+ .filter (([, entries ]) => entries .length > 0 ))
42+ const toolbarEntries = useTemplateRef <HTMLElement >(' toolbarEntries' )
43+ const settingsDock = useTemplateRef <HTMLElement >(' settingsDock' )
44+ const toolbarSize = ref ({ width: 0 , height: 0 , item: 32 , gap: 2 })
45+
46+ watch ([toolbarEntries , settingsDock ], ([el , pinned ], _prev , onCleanup ) => {
47+ if (! el || ! pinned )
48+ return
49+ const view = el .ownerDocument .defaultView
50+ const ResizeObserverCtor = view ?.ResizeObserver ?? globalThis .ResizeObserver
51+ if (! ResizeObserverCtor )
52+ return
53+ const observer = new ResizeObserverCtor ((entries ) => {
54+ const bounds = entries .find (entry => entry .target === el )?.contentRect
55+ const style = view ?.getComputedStyle (el )
56+ toolbarSize .value = {
57+ width: bounds ?.width ?? toolbarSize .value .width ,
58+ height: bounds ?.height ?? toolbarSize .value .height ,
59+ item: pinned .querySelector (' button' )?.offsetWidth || 32 ,
60+ gap: Number .parseFloat (style ?.columnGap ?? ' ' ) || 0 ,
61+ }
62+ })
63+ observer .observe (el )
64+ observer .observe (pinned )
65+ onCleanup (() => observer .disconnect ())
66+ }, { immediate: true , flush: ' post' })
67+
68+ const splitEntries = computed (() => {
69+ const { width, height, item, gap } = toolbarSize .value
70+ // Category dividers use m1 (four gap-0.5 units) and a 1.5px border.
71+ const separatorSize = gap * 4 + 1.5
72+ const settingsSize = settingsEntries .value .length ? item + separatorSize + gap * 2 : 0
73+ return docksSplitGroupsBySize (groupedEntries .value , (isVertical .value ? height : width ) - settingsSize , item , gap , separatorSize )
74+ })
3575const selectedEntry = computed (() => context .docks .selected )
3676const activeGroup = computed (() => getEntryGroup (context .docks .entries , selectedEntry .value ))
3777const hasPanelContent = computed (() => {
@@ -59,9 +99,10 @@ function bringUp() {
5999 }, + store .inactiveTimeout || 0 )
60100}
61101
62- // An open group menu popover anchors to a toolbar button, so collapsing the
102+ // An open menu popover anchors to a toolbar button, so collapsing the
63103// toolbar out from under it would leave the menu floating, detached from it.
64104const docksGroupPanel = useDocksGroupPanel ()
105+ const docksOverflowPanel = useDocksOverflowPanel ()
65106
66107const isCollapsed = computed (() => {
67108 if (! settings .value .autoCollapseEdgeToolbar )
@@ -70,7 +111,7 @@ const isCollapsed = computed(() => {
70111 return false
71112 if (context .panel .isDragging )
72113 return false
73- if (docksGroupPanel .value )
114+ if (docksGroupPanel .value || docksOverflowPanel . value )
74115 return false
75116 if (hasPanelContent .value )
76117 return false
@@ -422,21 +463,46 @@ const dragPreviewStyle = computed<CSSProperties | undefined>(() => {
422463 <!-- Toolbar -->
423464 <div class =" relative flex items-center shrink-0 select-none py1" :class =" toolbarClass" >
424465 <div
425- class =" flex items-center flex-1 w-full transition-opacity duration-300"
466+ class =" flex items-center flex-1 min-w-0 min-h-0 w-full transition-opacity duration-300"
426467 :class =" [isVertical ? 'flex-col' : 'flex-row', isCollapsed ? 'opacity-0 pointer-events-none' : 'opacity-100']"
427468 >
428469 <div
429- class =" flex items-center flex-1 flex-wrap gap-0.5 px1"
430- :class =" isVertical ? 'flex-col py1' : 'flex-row px1'"
470+ ref =" toolbarEntries"
471+ class =" flex items-center flex-1 min-w-0 min-h-0 gap-0.5 px1 [& >*]:shrink-0"
472+ :class =" isVertical ? 'flex-col py1 w-full' : 'flex-row'"
431473 >
432474 <DockEntriesWithCategories
433475 :context =" context "
434- :groups =" groupedEntries "
476+ :groups =" splitEntries . visible "
435477 :is-vertical =" isVertical "
436478 :rotate =" false "
437479 :selected =" selectedEntry "
438480 @select =" (e ) => context .docks .switchEntry (e ?.id ) "
439481 />
482+ <DockOverflowButton
483+ v-if =" splitEntries .overflow .length "
484+ :context =" context "
485+ :groups =" splitEntries .overflow "
486+ :is-vertical =" false "
487+ :placement =" positionDropdownPlacement [store .position ]"
488+ :selected =" selectedEntry "
489+ @select =" (e ) => context .docks .switchEntry (e ?.id ) "
490+ @activity =" bringUp "
491+ />
492+ <div
493+ v-if =" settingsEntries.length && groupedEntries.length"
494+ class =" border-base m1"
495+ :class =" isVertical ? 'w-20px h-px border-b-1.5' : 'h-20px w-px border-r-1.5'"
496+ />
497+ <div ref =" settingsDock" class =" shrink-0" >
498+ <DockEntries
499+ :context =" context "
500+ :entries =" settingsEntries "
501+ :is-vertical =" false "
502+ :selected =" selectedEntry "
503+ @select =" (e ) => context .docks .switchEntry (e ?.id ) "
504+ />
505+ </div >
440506 </div >
441507
442508 <!-- Position dropdown & float toggle -->
0 commit comments