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
25 changes: 24 additions & 1 deletion docs/content/1.guide/17.client-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,30 @@ The exported function (`DockClientScriptContext`) receives the client context an
- **`current`** holds this entry's state: `entryMeta`, `isActive`, `domElements`, `events` (`entry:activated`, `entry:deactivated`, `entry:updated`, `dom:panel:mounted`, `dom:iframe:mounted`).
- **`messages`**: an entry-scoped messages client (`category` defaults to the entry id; `info`/`warn`/`error`/`success`/`debug` shortcuts for `add()`).

Failed setup retries on the next activation, or on a dock update for eager scripts. Setup is cached per RPC connection, dock and import descriptor. Action clicks always execute again.
Failed setup retries on the next activation, or on a dock update for eager scripts. Setup is cached per RPC connection, dock and import descriptor. Action clicks always execute again, except for a [toggle action](#toggle-actions).

### Toggle actions

Set `toggle: true` on an `action` entry to make it an on/off tool, such as an in-page element picker. A click turns it on and a second click turns it off. While it is on:

- the dock button shows as active (`current.isActive` is `true`);
- the selected dock stays selected, and an open panel is hidden until the tool turns off;
- `Escape` turns it off in the reference hub UI.

The script sets up once and drives the tool from `entry:activated` / `entry:deactivated`. Call `docks.setActionActive(id, false)` when the tool is done, which brings the hidden panel back. Selecting another dock, or `switchEntry(null)`, also turns the tool off, and then the panel stays as the new selection leaves it.

```ts
// Dock entry: { type: 'action', toggle: true, action: { importFrom } }
import type { DockClientScriptContext } from '@devframes/hub/client'

export default function setup(ctx: DockClientScriptContext) {
const id = ctx.current.entryMeta.id
ctx.current.events.on('entry:activated', () => {
startPicker({ onPick: () => ctx.docks.setActionActive(id, false) })
})
ctx.current.events.on('entry:deactivated', () => stopPicker())
}
```

### Shipping a client script

Expand Down
3 changes: 2 additions & 1 deletion docs/content/1.guide/22.build-your-own-hub-ui.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ and loads dock client scripts. Honor:
Render the built-in variants of the open dock union
(`DevframeDockEntryRegistry`, `@devframes/hub/types`): `iframe` (the entry's
`url` in a kept-alive iframe, honoring `subTabs` soft nav), `action` (a
dock-rail button running its client script), `custom-render` (a container its
dock-rail button running its client script, shown as active while a
`toggle: true` action is on), `custom-render` (a container its
client script mounts into), `launcher` (a launch call-to-action reflecting
`launcher.status`), `group` (one dock-rail button collapsing its members), and
`~builtin` (your native views for reserved ids). What to render per type is in
Expand Down
10 changes: 5 additions & 5 deletions docs/content/8.references/6.hub-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ The properties of `DevframeClientContext`: [The client context](/guide/client-co
|----------|-------------|
| `rpc` | The [RPC client](/guide/client): server/client functions, shared state. |
| `clientType` | `'embedded'` (inside the user app) or `'standalone'` (independent hub page). |
| `docks` | `entries`, `selected`, `groupedEntries`, `switchEntry()`, `toggleEntry()`, `getStateById()`, `register()` / `update()` for [client-only docks](/guide/client-context#client-only-docks). |
| `docks` | `entries`, `selected`, `groupedEntries`, `switchEntry()`, `toggleEntry()`, `setActionActive(id, active)` for [toggle actions](/guide/client-context#toggle-actions), `getStateById()`, `register()` / `update()` for [client-only docks](/guide/client-context#client-only-docks). |
| `panel` | Current `state`, local `events`, session, position, size, and drag/resize state for the dock panel. |
| `commands` | Command palette: `register()`, `execute()`, `getKeybindings()`, `paletteOpen`, `paletteScopeId`, `openPalette(atCommandId?)`. Passing an id opens the palette drilled into that command's children ([Activating a group](/guide/hub#activating-a-group)). |
| `renderers` | Dock-renderer registry: `register()`, `get()`, `has()`, `mount(entry, container)`. Routes a dock `type` to a renderer (local boot or the hub's [manifest](/guide/hub-initiate#renderer-modules); local wins). `mount()` resolves a `status`: `mounted` (with `dispose`), `missing-renderer`, or `load-error` (with `error`). |
Expand All @@ -161,11 +161,11 @@ Which `ClientScriptEntry` field carries an entry's client script, and when it ru

| Entry kind | Field | Runs |
|---|---|---|
| `action` | `action` | when the dock button is activated |
| `action` | `action` | on each click; once for a `toggle: true` action |
| `custom-render` | `renderer` | to render the entry's panel |
| `iframe` | `clientScript` (optional) | inside the host page on first activation |

`ClientScriptEntry.eager` defaults to `false`. Set it to `true` on an `iframe` `clientScript` or an `action` to initialize it after RPC trust, before dock activation. A `custom-render` `renderer` needs its mounted panel, so it always initializes on activation regardless of `eager`. Setup is cached per RPC connection and dock; action clicks execute on every activation.
`ClientScriptEntry.eager` defaults to `false`. Set it to `true` on an `iframe` `clientScript` or an `action` to initialize it after RPC trust, before dock activation. A `custom-render` `renderer` needs its mounted panel, so it always initializes on activation regardless of `eager`. Setup is cached per RPC connection and dock; action clicks execute on every activation, except a `toggle: true` action, which sets up once.

## Frame-nav messages

Expand All @@ -179,12 +179,12 @@ The origin-locked `postMessage` protocol on `devframe:frame-nav`: [Shared-iframe

## Dock entry types

The built-in variants of the open dock union (`DevframeDockEntryRegistry`, `@devframes/hub/types`) a hub UI provider renders: [Build Your Own Hub UI](/guide/build-your-own-hub-ui). Each entry's `icon` takes any of the [icon values](/references/node-api#icon-values). Each entry's `title` has an optional `titleLocales` map of translations, resolved with `resolveTitle(entry, locale)` from `@devframes/hub/client`; commands and launchers carry the same pair.
The built-in variants of the open dock union (`DevframeDockEntryRegistry`, `@devframes/hub/types`) a hub UI provider renders: [Build Your Own Hub UI](/guide/build-your-own-hub-ui). Each entry's `icon` takes any of the [icon values](/references/node-api#icon-values). Each entry's `title` has an optional `titleLocales` map of translations, resolved with `resolveTitle(entry, locale)` from `@devframes/hub/client`; commands and launchers carry the same pair. An optional one-line `description` explains the entry; the reference hub UI shows it under the title in the dock button tooltip.

| Type | The hub UI provider renders |
|---|---|
| `iframe` | the entry's `url` in a kept-alive iframe (per `frameId` when shared); honor `subTabs` soft nav; show the existing address bar with `addressBar: true`, or configure Back, Reload, and Open externally by passing an `addressBar` object |
| `action` | a dock-rail button; activating runs its client script |
| `action` | a dock-rail button; activating runs its client script. With `toggle: true`, render it as active while `getStateById(id).isActive` is `true` |
| `custom-render` | a container its client script mounts into |
| `launcher` | a launch call-to-action reflecting `launcher.status` |
| `group` | one dock-rail button collapsing its member entries |
Expand Down
3 changes: 3 additions & 0 deletions packages/hub-ui/src/client/components/dock/Dock.vue
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,9 @@ const isMinimized = computed(() => {
return false
if (docksGroupPanel.value)
return false
// Keep an active tool's button in sight while it hides the panel.
if (context.when.context.dockActionActive)
return false
if (context.panel.store.inactiveTimeout === 0)
return true
// @ts-expect-error compatibility
Expand Down
3 changes: 3 additions & 0 deletions packages/hub-ui/src/client/components/dock/DockEdge.vue
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ const isCollapsed = computed(() => {
return false
if (hasPanelContent.value)
return false
// Keep an active tool's button in sight while it hides the panel.
if (context.when.context.dockActionActive)
return false
if (store.inactiveTimeout === 0)
return true
// @ts-expect-error compatibility (mirrors Dock.vue's own touch-device check)
Expand Down
9 changes: 7 additions & 2 deletions packages/hub-ui/src/client/components/dock/DockEntries.vue
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ function isDockVisible(dock: DevframeDockEntry): boolean {
return true
}

// Selected, or a toggle action that is on (it never takes the selection).
function isDockActive(dock: DevframeDockEntry): boolean {
return selected.value?.id === dock.id || !!props.context.docks.getStateById(dock.id)?.isActive
}

function toggleDockEntry(dock: DevframeDockEntry) {
if (selected.value?.id === dock.id)
emit('select', undefined!)
Expand All @@ -73,8 +78,8 @@ function toggleDockEntry(dock: DevframeDockEntry) {
:context="context"
:dock
:is-action="dock.type === 'action'"
:is-selected="selected?.id === dock.id"
:is-dimmed="dimInactive && selected ? (selected.id !== dock.id) : false"
:is-selected="isDockActive(dock)"
:is-dimmed="dimInactive && selected ? !isDockActive(dock) : false"
:is-vertical="isVertical"
:badge="dock.badge"
:badge-variant="dock.badgeVariant"
Expand Down
4 changes: 2 additions & 2 deletions packages/hub-ui/src/client/components/dock/DockEntry.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { DevframeDockBadgeVariant, DevframeDockEntryBase } from '@devframes
import type { DocksContext } from '@devframes/hub/client'
import { useEventListener } from '@vueuse/core'
import { computed, useTemplateRef } from 'vue'
import { setFloatingTooltip } from '../../state/floating-tooltip'
import { dockTooltipContent, setFloatingTooltip } from '../../state/floating-tooltip'
import { openDockContextMenu } from './DockContextMenu'
import DockIcon from './DockIcon.vue'

Expand Down Expand Up @@ -51,7 +51,7 @@ function updateTooltip() {
if (!button.value)
return
setFloatingTooltip({
content: props.dock.title,
content: dockTooltipContent(props.dock),
el: button.value,
})
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ const isActive = computed(() => {
return !!id && members.value.some(m => m.id === id)
})

// A member toggle action that is on lights the button up too, without owning the panel.
const hasActiveTool = computed(() => members.value.some(m => m.type === 'action' && props.context.docks.getStateById(m.id)?.isActive))

const groupButton = useTemplateRef<HTMLElement>('groupButton')
const isPanelVisible = ref(false)
const docksGroupPanel = useDocksGroupPanel()
Expand Down Expand Up @@ -131,8 +134,8 @@ function onClick() {
:context="context"
:dock="group"
:is-vertical="isVertical"
:is-selected="isActive || isPanelVisible"
:is-dimmed="dimInactive && selected ? !isActive : false"
:is-selected="isActive || isPanelVisible || hasActiveTool"
:is-dimmed="dimInactive && selected ? !isActive && !hasActiveTool : false"
:badge="group.badge"
:badge-variant="group.badgeVariant"
@click="onClick"
Expand Down
19 changes: 16 additions & 3 deletions packages/hub-ui/src/client/components/dock/DockGroupPopover.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { DevframeDockEntriesGrouped, DevframeDockEntry, DevframeViewGroup }
import type { DocksContext } from '@devframes/hub/client'
import { computed } from 'vue'
import { t } from '../../i18n'
import { dockTooltipContent, setFloatingTooltip } from '../../state/floating-tooltip'
import { accentVarStyle } from '../../utils/accent-color'
import { openDockContextMenu } from './DockContextMenu'
import DockIcon from './DockIcon.vue'
Expand All @@ -19,6 +20,16 @@ const emit = defineEmits<{
(e: 'select', entry: DevframeDockEntry): void
}>()

// A row already shows its title, so only a `description` is worth a tooltip.
function showTooltip(event: PointerEvent, member: DevframeDockEntry) {
if (member.description)
setFloatingTooltip({ content: dockTooltipContent(member), el: event.currentTarget as HTMLElement, placement: 'right' })
}

function hideTooltip() {
setFloatingTooltip(null)
}

const isEmpty = computed(() => props.members.every(([, items]) => items.length === 0))

function openContextMenu(e: MouseEvent, member: DevframeDockEntry) {
Expand Down Expand Up @@ -55,9 +66,11 @@ function openContextMenu(e: MouseEvent, member: DevframeDockEntry) {
v-for="member of items"
:key="member.id"
class="flex items-center gap-2 w-full px2 py1.5 rounded text-sm text-left transition"
:class="selectedId === member.id ? 'text-primary bg-active' : 'op80 hover:op100 hover:bg-active'"
@click="emit('select', member)"
@contextmenu="openContextMenu($event, member)"
:class="selectedId === member.id || context.docks.getStateById(member.id)?.isActive ? 'text-primary bg-active' : 'op80 hover:op100 hover:bg-active'"
@pointerenter="showTooltip($event, member)"
@pointerleave="hideTooltip"
@click="hideTooltip(); emit('select', member)"
@contextmenu="hideTooltip(); openContextMenu($event, member)"
>
<DockIcon :icon="member.icon" class="w-4.5 h-4.5 flex-none" />
<span class="truncate flex-1">{{ member.title }}</span>
Expand Down
10 changes: 5 additions & 5 deletions packages/hub-ui/src/client/components/dock/DockGroupSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { watchDebounced } from '@vueuse/core'
import { computed, h, ref, useTemplateRef, watch } from 'vue'
import { t } from '../../i18n'
import { deriveSidebarCapacity, docksSplitGroupsWithCapacity, getGroupMembersGrouped } from '../../state/dock-settings'
import { setDocksSidebarOverflowPanel, setFloatingTooltip, useDocksSidebarOverflowPanel } from '../../state/floating-tooltip'
import { dockTooltipContent, setDocksSidebarOverflowPanel, setFloatingTooltip, useDocksSidebarOverflowPanel } from '../../state/floating-tooltip'
import { useSettings } from '../../state/settings-defaults'
import { accentVarStyle } from '../../utils/accent-color'
import { openDockContextMenu } from './DockContextMenu'
Expand Down Expand Up @@ -92,8 +92,8 @@ function select(id: string) {
props.context.docks.switchEntry(id)
}

function showTooltip(event: PointerEvent, title: string) {
setFloatingTooltip({ content: title, el: event.currentTarget as HTMLElement })
function showTooltip(event: PointerEvent, entry: { title: string, description?: string }) {
setFloatingTooltip({ content: dockTooltipContent(entry), el: event.currentTarget as HTMLElement })
}

function hideTooltip() {
Expand Down Expand Up @@ -175,7 +175,7 @@ const moreButtonActive = computed(() => selectedInOverflow.value || isOverflowPa
<div
class="flex items-center justify-center w-8 h-8 op60"
:class="group.accentColor ? 'color-active' : ''"
@pointerenter="showTooltip($event, group.title)"
@pointerenter="showTooltip($event, group)"
@pointerleave="hideTooltip"
>
<DockIcon :icon="group.icon" class="w-5 h-5 flex-none" />
Expand All @@ -192,7 +192,7 @@ const moreButtonActive = computed(() => selectedInOverflow.value || isOverflowPa
:aria-label="member.title"
class="relative flex items-center justify-center w-8 h-8 rounded-lg transition"
:class="selectedId === member.id ? 'text-primary bg-active' : 'op60 hover:op100 hover:bg-active'"
@pointerenter="showTooltip($event, member.title)"
@pointerenter="showTooltip($event, member)"
@pointerleave="hideTooltip"
@pointerdown="hideTooltip"
@click="select(member.id)"
Expand Down
1 change: 1 addition & 0 deletions packages/hub-ui/src/client/i18n/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

"command.togglePalette": "Befehlspalette umschalten",
"command.closePanel": "Panel schließen",
"command.stopActiveTool": "Aktives Werkzeug beenden",
"command.openSettings": "Einstellungen öffnen",
"command.hide": "{productName} ausblenden",
"command.dockMode": "Dock-Modus",
Expand Down
1 change: 1 addition & 0 deletions packages/hub-ui/src/client/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

"command.togglePalette": "Toggle Command Palette",
"command.closePanel": "Close Panel",
"command.stopActiveTool": "Stop Active Tool",
"command.openSettings": "Open Settings",
"command.hide": "Hide {productName}",
"command.dockMode": "Dock Mode",
Expand Down
1 change: 1 addition & 0 deletions packages/hub-ui/src/client/i18n/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

"command.togglePalette": "Alternar paleta de comandos",
"command.closePanel": "Cerrar panel",
"command.stopActiveTool": "Detener herramienta activa",
"command.openSettings": "Abrir ajustes",
"command.hide": "Ocultar {productName}",
"command.dockMode": "Modo de anclaje",
Expand Down
1 change: 1 addition & 0 deletions packages/hub-ui/src/client/i18n/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

"command.togglePalette": "Basculer la palette de commandes",
"command.closePanel": "Fermer le panneau",
"command.stopActiveTool": "Arrêter l'outil actif",
"command.openSettings": "Ouvrir les paramètres",
"command.hide": "Masquer {productName}",
"command.dockMode": "Mode d'ancrage",
Expand Down
1 change: 1 addition & 0 deletions packages/hub-ui/src/client/i18n/locales/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

"command.togglePalette": "コマンドパレットの切り替え",
"command.closePanel": "パネルを閉じる",
"command.stopActiveTool": "アクティブなツールを停止",
"command.openSettings": "設定を開く",
"command.hide": "{productName} を隠す",
"command.dockMode": "ドックモード",
Expand Down
1 change: 1 addition & 0 deletions packages/hub-ui/src/client/i18n/locales/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

"command.togglePalette": "명령 팔레트 전환",
"command.closePanel": "패널 닫기",
"command.stopActiveTool": "활성 도구 중지",
"command.openSettings": "설정 열기",
"command.hide": "{productName} 숨기기",
"command.dockMode": "도킹 모드",
Expand Down
1 change: 1 addition & 0 deletions packages/hub-ui/src/client/i18n/locales/pt-BR.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

"command.togglePalette": "Alternar paleta de comandos",
"command.closePanel": "Fechar painel",
"command.stopActiveTool": "Parar ferramenta ativa",
"command.openSettings": "Abrir configurações",
"command.hide": "Ocultar {productName}",
"command.dockMode": "Modo de encaixe",
Expand Down
1 change: 1 addition & 0 deletions packages/hub-ui/src/client/i18n/locales/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

"command.togglePalette": "Переключить палитру команд",
"command.closePanel": "Закрыть панель",
"command.stopActiveTool": "Остановить активный инструмент",
"command.openSettings": "Открыть настройки",
"command.hide": "Скрыть {productName}",
"command.dockMode": "Режим панели",
Expand Down
1 change: 1 addition & 0 deletions packages/hub-ui/src/client/i18n/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

"command.togglePalette": "切换命令面板",
"command.closePanel": "关闭面板",
"command.stopActiveTool": "停止当前工具",
"command.openSettings": "打开设置",
"command.hide": "隐藏 {productName}",
"command.dockMode": "停靠模式",
Expand Down
1 change: 1 addition & 0 deletions packages/hub-ui/src/client/i18n/locales/zh-TW.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

"command.togglePalette": "切換命令面板",
"command.closePanel": "關閉面板",
"command.stopActiveTool": "停止使用中的工具",
"command.openSettings": "開啟設定",
"command.hide": "隱藏 {productName}",
"command.dockMode": "停靠模式",
Expand Down
Loading
Loading