diff --git a/docs/content/1.guide/17.client-context.md b/docs/content/1.guide/17.client-context.md index 7a73b7cb..fb8fd51e 100644 --- a/docs/content/1.guide/17.client-context.md +++ b/docs/content/1.guide/17.client-context.md @@ -36,6 +36,29 @@ The context carries the [RPC client](/guide/client) (`rpc`) and the page's `clie `getDevframeClientContext()` returns the context anywhere; `undefined` before boot. +### Tracking panel state + +`ctx.panel.state` is the current dock panel snapshot. It contains `state: 'open' | 'closed' | 'hidden'` and includes `selectedDockId` while a dock is selected. Subscribe to [`ctx.panel.events`](/references/events#client-context-events) for later changes: + +```ts +import type { DockClientScriptContext } from '@devframes/hub/client' +import { HUB_EVENTS } from '@devframes/hub/constants' + +export default function setup(context: DockClientScriptContext) { + const reportPanelState = (panelState: typeof context.panel.state) => { + void context.rpc.call('my-devframe:panel-state', panelState).catch(error => console.error(error)) + } + + reportPanelState(context.panel.state) + context.panel.events.on( + HUB_EVENTS.client.docksPanelStateChanged, + reportPanelState, + ) +} +``` + +The custom RPC keeps node-side reporting opt-in. + ### Client-only docks A client runtime can register a dock local to the host page (unlike [node hub context](/guide/hub) docks synced via `devframe:docks`). `ctx.docks.register(entry)` — e.g. `type: 'custom-render'` with `renderer: { importFrom }` — returns a handle whose `update({ badge })` patches in place (id immutable) and `dispose()` removes it. One sharing a server dock's id overrides it locally; re-registering an owned id throws unless you pass `register(entry, true)`. diff --git a/docs/content/8.references/3.events.md b/docs/content/8.references/3.events.md index 473bc115..e776cb9d 100644 --- a/docs/content/8.references/3.events.md +++ b/docs/content/8.references/3.events.md @@ -2,10 +2,10 @@ title: 'Events Reference' navigation: icon: i-lucide-radio-tower -description: 'Devframe carries change notifications across channels of differing direction and reach: a node event bus, server RPC, and server-pushed broadcasts and shared state.' +description: 'Devframe carries change notifications through client contexts, node event buses, RPC, broadcasts, and shared state.' --- -Devframe carries change notifications across channels of differing **direction and reach**: a node event bus, server RPC, and server-pushed broadcasts and shared state. +Devframe carries change notifications through client contexts, node event buses, RPC, broadcasts, and shared state. Two prefixes mark the wire protocol: `hub:` for hub-layer server RPC (client → server), `devframe:` for the client-facing protocol (server → client). The internal event bus mirrors the subsystem vocabulary (`docks`, `terminals`, `messages`, `commands`) — `docks:activate` fans out to `devframe:docks:activate`. @@ -13,6 +13,16 @@ Each name lives in code: [`HUB_EVENTS`](https://github.com/devframes/devframe/bl ## Hub events +### Client-context events + +Client scripts subscribe to these events on the client context inside the host page. + +| Event | Emitter | Payload | +|---|---|---| +| `panel:state:changed` | `ctx.panel.events` | `DevframeDockPanelState` | + +`ctx.panel.state` supplies the current snapshot when a client script loads. Later open, close, dock selection, and hub UI provider visibility changes emit `panel:state:changed`. The snapshot contains `state: 'open' | 'closed' | 'hidden'` and an optional `selectedDockId`. + ### Internal node event bus Each subsystem emits on `ctx..events`, consumed **inside the same node process** by `createHubContext`, which fans them onto the wire. diff --git a/docs/content/8.references/6.hub-api.md b/docs/content/8.references/6.hub-api.md index 397c4941..849ee7a3 100644 --- a/docs/content/8.references/6.hub-api.md +++ b/docs/content/8.references/6.hub-api.md @@ -105,7 +105,7 @@ The properties of `DevframeClientContext` — [The client context](/guide/client | `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). | -| `panel` | Dock panel state: position, size, drag/resize. | +| `panel` | Current `state`, local `events`, session, position, size, and drag/resize state for the dock panel. | | `commands` | Command palette: `register()`, `execute()`, `getKeybindings()`. | | `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`). | | `when` | The [when-clause](/references/when-clauses) context. | diff --git a/packages/hub-ui/src/client/embedded/index.ts b/packages/hub-ui/src/client/embedded/index.ts index 26802d36..4f3551cb 100644 --- a/packages/hub-ui/src/client/embedded/index.ts +++ b/packages/hub-ui/src/client/embedded/index.ts @@ -1,9 +1,10 @@ import type { DockPanelStorage, DockSessionStorage } from '@devframes/hub/client' import { getDevframeRpcClient, setDevframeClientContext } from '@devframes/hub/client' import { useLocalStorage, useSessionStorage } from '@vueuse/core' +import { ref } from 'vue' import { applyPrimaryColor, setBranding } from '../state/branding' import { DEFAULT_DOCK_PANEL_STORE, DEFAULT_DOCK_SESSION_STORE } from '../state/docks' -import { setupEmbeddedVisibility } from './visibility' +import { isEmbeddedDockInitiallyVisible, setupEmbeddedVisibility } from './visibility' /** * The floating-dock bootstrap the hub serves at `embedded.js` — load @@ -74,8 +75,10 @@ async function mountDock(): Promise { // carried by the connection we just established above. const branding = setBranding(rpc.connectionMeta.configs?.ui?.branding || {}) + const embeddedVisibility = rpc.connectionMeta.configs?.ui?.embeddedVisibility ?? 'normal' + const panelVisible = ref(isEmbeddedDockInitiallyVisible(embeddedVisibility)) const { createDocksContext } = await import('../state/context') - const context = await createDocksContext('embedded', rpc, state, session) + const context = await createDocksContext('embedded', rpc, state, session, panelVisible) setDevframeClientContext(context) const { DockEmbedded } = await import('../components/DockEmbedded') @@ -91,14 +94,18 @@ async function mountDock(): Promise { // Reveal policy: `normal` appends now; `passive`/`hidden` wait for the // Shift+Alt+D reveal (the element is built and ready, just detached). setupEmbeddedVisibility( - rpc.connectionMeta.configs?.ui?.embeddedVisibility ?? 'normal', + embeddedVisibility, branding.productName, { show: () => { if (dockEl && !dockEl.isConnected) document.body.appendChild(dockEl) + panelVisible.value = true + }, + hide: () => { + dockEl?.remove() + panelVisible.value = false }, - hide: () => dockEl?.remove(), }, ) } diff --git a/packages/hub-ui/src/client/embedded/visibility.ts b/packages/hub-ui/src/client/embedded/visibility.ts index 7dd01403..a16dc10f 100644 --- a/packages/hub-ui/src/client/embedded/visibility.ts +++ b/packages/hub-ui/src/client/embedded/visibility.ts @@ -60,6 +60,10 @@ export interface EmbeddedVisibilityHandlers { hide: () => void } +export function isEmbeddedDockInitiallyVisible(mode: EmbeddedVisibility): boolean { + return mode === 'normal' || (mode === 'passive' && readPersistedReveal()) +} + /** * Drive the embedded dock's reveal lifecycle for the resolved * {@link EmbeddedVisibility} mode: decide whether to show on boot, wire the @@ -73,7 +77,7 @@ export function setupEmbeddedVisibility( label: string, handlers: EmbeddedVisibilityHandlers, ): void { - let shown = mode === 'normal' || (mode === 'passive' && readPersistedReveal()) + let shown = isEmbeddedDockInitiallyVisible(mode) function reveal(): void { if (shown) diff --git a/packages/hub-ui/src/client/state/context.test.ts b/packages/hub-ui/src/client/state/context.test.ts index f22e5b7d..63b2fcfa 100644 --- a/packages/hub-ui/src/client/state/context.test.ts +++ b/packages/hub-ui/src/client/state/context.test.ts @@ -1,6 +1,7 @@ -import type { DevframeDockEntry } from '@devframes/hub' +import type { DevframeDockEntry, DevframeDockPanelState } from '@devframes/hub' import type { DevframeRpcClient, DockSessionStorage } from '@devframes/hub/client' import type { SharedState } from 'devframe/utils/shared-state' +import { HUB_EVENTS } from '@devframes/hub/constants' import { DEVFRAME_EVENTS } from 'devframe/constants' import { createEventEmitter } from 'devframe/utils/events' import { createSharedState } from 'devframe/utils/shared-state' @@ -74,12 +75,74 @@ async function flushRestore(): Promise { } describe('createDocksContext', () => { + it('exposes restored panel state and emits selected, hidden, and closed changes', async () => { + expect.assertions(9) + + const { rpc, sharedStates, trust } = createStubRpc() + const panelVisible = ref(false) + const session = ref({ + open: true, + selectedDockId: 'git', + selectedDockRoute: null, + }) + const context = await createDocksContext('embedded', rpc, undefined, session, panelVisible) + const panelStates: DevframeDockPanelState[] = [] + context.panel.events.on( + HUB_EVENTS.client.docksPanelStateChanged, + panelState => panelStates.push(panelState), + ) + + panelVisible.value = true + await nextTick() + expect(panelStates).toEqual([{ state: 'open', selectedDockId: 'git' }]) + panelVisible.value = false + await nextTick() + panelStates.length = 0 + + trust() + sharedStates.get('devframe:docks')!.push([gitEntry]) + sharedStates.get('devframe:dock-renderers')!.push({}) + await flushRestore() + expect(context.panel.state).toEqual({ state: 'hidden', selectedDockId: 'git' }) + expect(panelStates).toEqual([]) + + panelVisible.value = true + await nextTick() + expect(panelStates.at(-1)).toEqual({ state: 'open', selectedDockId: 'git' }) + + session.value.selectedDockId = '~settings' + await nextTick() + expect(panelStates.at(-1)).toEqual({ state: 'open', selectedDockId: '~settings' }) + + panelVisible.value = false + await nextTick() + expect(panelStates.at(-1)).toEqual({ state: 'hidden', selectedDockId: '~settings' }) + + session.value.open = false + session.value.selectedDockId = null + await nextTick() + expect(panelStates.at(-1)).toEqual({ state: 'hidden' }) + + panelVisible.value = true + await nextTick() + expect(panelStates.at(-1)).toEqual({ state: 'closed' }) + + panelVisible.value = true + session.value.open = false + await nextTick() + expect(panelStates).toHaveLength(5) + }) + it('mounts a restored dock once after all initial server state arrives', async () => { - expect.assertions(7) + expect.assertions(8) const { rpc, sharedStates, trust } = createStubRpc() const executeSetupScriptMock = vi.mocked(executeSetupScript) executeSetupScriptMock.mockClear() + let setupPanelState: DevframeDockPanelState | undefined + executeSetupScriptMock.mockImplementationOnce(async (_dockEntry, scriptContext) => { + setupPanelState = scriptContext.panel.state + }) const session = ref({ open: true, selectedDockId: 'git', @@ -105,6 +168,7 @@ describe('createDocksContext', () => { expect(context.docks.selected?.id).toBe('git') expect(session.value.open).toBe(true) + expect(setupPanelState).toEqual({ state: 'open', selectedDockId: 'git' }) expect(executeSetupScriptMock).toHaveBeenCalledOnce() }) diff --git a/packages/hub-ui/src/client/state/context.ts b/packages/hub-ui/src/client/state/context.ts index 6ac2b64d..0fb29bba 100644 --- a/packages/hub-ui/src/client/state/context.ts +++ b/packages/hub-ui/src/client/state/context.ts @@ -1,5 +1,5 @@ -import type { DevframeClientCommand, DevframeDockEntry, DevframeDockUserEntry, DevframeRpcClientFunctions, DevframeViewIframe } from '@devframes/hub' -import type { CommandsContext, DevframeClientContext, DevframeRpcClient, DockClientScriptContext, DockEntryState, DockPanelStorage, DockRegistration, DockRendererManifest, DocksContext, DockSessionStorage } from '@devframes/hub/client' +import type { DevframeClientCommand, DevframeDockEntry, DevframeDockPanelState, DevframeDockUserEntry, DevframeRpcClientFunctions, DevframeViewIframe } from '@devframes/hub' +import type { CommandsContext, DevframeClientContext, DevframeRpcClient, DockClientScriptContext, DockEntryState, DockPanelStorage, DockRegistration, DockRendererManifest, DocksContext, DockSessionStorage, DocksPanelEvents } from '@devframes/hub/client' import type { SharedState } from 'devframe/utils/shared-state' import type { WhenContext } from 'devframe/utils/when' import type { Ref } from 'vue' @@ -7,6 +7,7 @@ import type { DevframeDocksUserSettings } from './dock-settings' import { attachFrameNavClient, createDockRenderersContext } from '@devframes/hub/client' import { DEFAULT_STATE_USER_SETTINGS, DOCK_RENDERERS_STATE_KEY, HUB_EVENTS } from '@devframes/hub/constants' import { DEVFRAME_EVENTS } from 'devframe/constants' +import { createEventEmitter } from 'devframe/utils/events' import { computed, markRaw, reactive, ref, toRefs, watch, watchEffect } from 'vue' import { BUILTIN_ENTRIES, BUILTIN_ENTRY_SETTINGS, DEFAULT_CATEGORIES_ORDER, HUB_UI_HIDE_EVENT } from '../constants' import { useBranding } from './branding' @@ -23,6 +24,7 @@ export async function createDocksContext( rpc: DevframeRpcClient, panelStore?: Ref, sessionStore?: Ref, + panelVisible: Ref = ref(true), ): Promise { if (docksContextByRpc.has(rpc)) { return docksContextByRpc.get(rpc)! @@ -172,6 +174,7 @@ export async function createDocksContext( } panelStore ||= ref(DEFAULT_DOCK_PANEL_STORE()) + const panelEvents = createEventEmitter() let docksContext: DocksContext let _settingsStorePromise: Promise> | undefined @@ -269,6 +272,10 @@ export async function createDocksContext( return false } + initialRestorePending.value = false + selectedDockId.value = entry.id + sessionStore.value.open = true + // If has import script, run it if ( (entry.type === 'action') @@ -299,9 +306,6 @@ export async function createDocksContext( if (entry.groupId) (sessionStore.value.groupLastChildIds ??= {})[entry.groupId] = entry.id - initialRestorePending.value = false - selectedDockId.value = entry.id - sessionStore.value.open = true // Only an iframe dock owns an address-bar route; ViewIframe keeps // `session.selectedDockRoute` current for it. Clear it for anything else so a stale // route from a previous iframe isn't persisted against a non-iframe dock. @@ -577,6 +581,21 @@ export async function createDocksContext( docksContext = reactive({ panel: { + get state() { + let state: DevframeDockPanelState['state'] + if (!panelVisible.value) + state = 'hidden' + else if (sessionStore.value.open) + state = 'open' + else + state = 'closed' + + const panelState: DevframeDockPanelState = { state } + if (selectedDockId.value !== null) + panelState.selectedDockId = selectedDockId.value + return panelState + }, + events: markRaw(panelEvents), store: panelStore, session: sessionStore, isDragging: false, @@ -669,6 +688,23 @@ export async function createDocksContext( initialRestorePending.value = false await switchEntry(restoreDockId) } + let previousPanelState = docksContext.panel.state + watch( + [panelVisible, () => sessionStore.value.open, selectedDockId], + () => { + const panelState = docksContext.panel.state + if ( + panelState.state === previousPanelState.state + && panelState.selectedDockId === previousPanelState.selectedDockId + ) { + return + } + + previousPanelState = panelState + panelEvents.emit(HUB_EVENTS.client.docksPanelStateChanged, panelState) + }, + { flush: 'post' }, + ) void restoreAfterInitialization() docksContextByRpc.set(rpc, docksContext) diff --git a/packages/hub/src/client/__tests__/host.test.ts b/packages/hub/src/client/__tests__/host.test.ts index ff4c9aea..2c10a9ce 100644 --- a/packages/hub/src/client/__tests__/host.test.ts +++ b/packages/hub/src/client/__tests__/host.test.ts @@ -1,8 +1,9 @@ import type { DevframeRpcClient } from 'devframe/client' import type { SharedState } from 'devframe/utils/shared-state' -import type { DevframeDockEntry } from '../../types/docks' +import type { DevframeDockEntry, DevframeDockPanelState } from '../../types/docks' import { createEventEmitter } from 'devframe/utils/events' import { describe, expect, it, vi } from 'vitest' +import { HUB_EVENTS } from '../../events' import { getDevframeClientContext } from '../context' import { createDevframeClientRuntime } from '../host' @@ -67,6 +68,41 @@ function groupEntry(id: string, extra?: Record): DevframeDockEn } describe('createDevframeClientRuntime', () => { + it('exposes panel state and emits coalesced changes', async () => { + expect.assertions(6) + + const { rpc, states } = createStubRpc() + const host = await createDevframeClientRuntime({ rpc, clientType: 'embedded' }) + const panelStates: DevframeDockPanelState[] = [] + + expect(host.context.panel.state).toEqual({ state: 'closed' }) + host.context.panel.events.on( + HUB_EVENTS.client.docksPanelStateChanged, + panelState => panelStates.push(panelState), + ) + + states.get('devframe:docks')!.push([iframeEntry('one'), iframeEntry('two')]) + host.context.panel.session.open = true + const switched = host.context.docks.switchEntry('one') + await switched + expect(panelStates).toEqual([{ state: 'open', selectedDockId: 'one' }]) + expect(host.context.panel.state).toEqual({ state: 'open', selectedDockId: 'one' }) + + host.context.panel.session.open = true + host.context.panel.session.selectedDockId = 'one' + await Promise.resolve() + expect(panelStates).toHaveLength(1) + + await host.context.docks.switchEntry('two') + expect(panelStates.at(-1)).toEqual({ state: 'open', selectedDockId: 'two' }) + + host.context.panel.session.open = false + const cleared = host.context.docks.switchEntry(null) + await cleared + expect(panelStates.at(-1)).toEqual({ state: 'closed' }) + host.dispose() + }) + it('publishes the global client context with the full surface', async () => { const { rpc } = createStubRpc() const host = await createDevframeClientRuntime({ rpc }) diff --git a/packages/hub/src/client/docks.ts b/packages/hub/src/client/docks.ts index eefc0943..fe5a919d 100644 --- a/packages/hub/src/client/docks.ts +++ b/packages/hub/src/client/docks.ts @@ -3,7 +3,7 @@ import type { EventEmitter } from 'devframe/types' import type { SharedState } from 'devframe/utils/shared-state' import type { WhenContext } from 'devframe/utils/when' import type { DevframeClientCommand, DevframeCommandEntry, DevframeCommandKeybinding } from '../types/commands' -import type { DevframeDockEntriesGrouped, DevframeDockEntry, DevframeDockUserEntry } from '../types/docks' +import type { DevframeDockEntriesGrouped, DevframeDockEntry, DevframeDockPanelState, DevframeDockUserEntry } from '../types/docks' import type { DevframeDocksUserSettings } from '../types/settings' import type { DockRenderersContext } from './renderers' @@ -125,7 +125,15 @@ export interface WhenClauseContext { export type DevframeClientContext = DocksContext +export interface DocksPanelEvents { + 'panel:state:changed': (panelState: DevframeDockPanelState) => void +} + export interface DocksPanelContext { + /** The current panel state snapshot. */ + readonly state: DevframeDockPanelState + /** Subscribe to panel state changes after the current snapshot. */ + readonly events: EventEmitter store: DockPanelStorage /** * Per-tab session UI state — whether the panel is open, which dock is diff --git a/packages/hub/src/client/host.ts b/packages/hub/src/client/host.ts index 1bc8b685..7a0ecaff 100644 --- a/packages/hub/src/client/host.ts +++ b/packages/hub/src/client/host.ts @@ -10,6 +10,7 @@ import type { ClientScriptEntry, DevframeDockEntriesGrouped, DevframeDockEntry, + DevframeDockPanelState, DevframeViewIframe, } from '../types/docks' import type { DevframeDocksUserSettings } from '../types/settings' @@ -21,6 +22,7 @@ import type { DockEntryState, DocksEntriesContext, DocksPanelContext, + DocksPanelEvents, WhenClauseContext, } from './docks' import type { DockRenderer, DockRendererManifest, DockRenderersContext } from './renderers' @@ -559,13 +561,67 @@ function createPanelContext(clientType: DockClientType): DocksPanelContext { position: 'right', inactiveTimeout: 0, } + let open = clientType === 'standalone' + let selectedDockId: string | null = null + const events = createEventEmitter() + let panelStateChangePending = false + + function createDockPanelState(): DevframeDockPanelState { + const panelState: DevframeDockPanelState = { + state: open ? 'open' : 'closed', + } + if (selectedDockId !== null) + panelState.selectedDockId = selectedDockId + return panelState + } + + let previousPanelState = createDockPanelState() + + function schedulePanelStateChange(): void { + if (panelStateChangePending) + return + panelStateChangePending = true + queueMicrotask(() => { + panelStateChangePending = false + const panelState = createDockPanelState() + if ( + panelState.state === previousPanelState.state + && panelState.selectedDockId === previousPanelState.selectedDockId + ) { + return + } + + previousPanelState = panelState + events.emit(HUB_EVENTS.client.docksPanelStateChanged, panelState) + }) + } + const session: DocksPanelContext['session'] = { - // A standalone runtime owns the page, so its "panel" is always open. - open: clientType === 'standalone', - selectedDockId: null, + get open() { + return open + }, + set open(nextOpen) { + if (nextOpen === open) + return + open = nextOpen + schedulePanelStateChange() + }, + get selectedDockId() { + return selectedDockId + }, + set selectedDockId(nextSelectedDockId) { + if (nextSelectedDockId === selectedDockId) + return + selectedDockId = nextSelectedDockId + schedulePanelStateChange() + }, selectedDockRoute: null, } return { + get state() { + return createDockPanelState() + }, + events, store, session, isDragging: false, diff --git a/packages/hub/src/events.ts b/packages/hub/src/events.ts index a9fce268..3d3e3e60 100644 --- a/packages/hub/src/events.ts +++ b/packages/hub/src/events.ts @@ -9,10 +9,9 @@ * both places in the same change, and reference `HUB_EVENTS.*` from call sites * instead of re-typing a literal. * - * The `.events` EventEmitter maps in `types/{docks,terminals,messages,commands}.ts` - * and the RPC augmentation interfaces in `node/context.ts` declare these same - * names as type-level keys (a literal is unavoidable in a type position); those - * declarations mirror this map and move with it. + * The `.events` EventEmitter maps and RPC augmentation interfaces declare these + * same names as type-level keys (a literal is unavoidable in a type position); + * those declarations mirror this map and move with it. */ export const HUB_EVENTS = { /** @@ -45,6 +44,10 @@ export const HUB_EVENTS = { terminalsRestart: 'hub:terminals:restart', terminalsRemove: 'hub:terminals:remove', }, + /** Client-context events emitted inside the host page. */ + client: { + docksPanelStateChanged: 'panel:state:changed', + }, /** Broadcast notifications the server pushes to clients (server → client), `devframe:` prefix. */ broadcast: { docksActivate: 'devframe:docks:activate', diff --git a/packages/hub/src/types/docks.ts b/packages/hub/src/types/docks.ts index 93992520..f21c575e 100644 --- a/packages/hub/src/types/docks.ts +++ b/packages/hub/src/types/docks.ts @@ -29,6 +29,12 @@ export interface DevframeDocksHost { activate: (dockId: string, params?: Record) => void } +/** Current state of one dock panel in a host page. */ +export interface DevframeDockPanelState { + state: 'open' | 'closed' | 'hidden' + selectedDockId?: string +} + /** * A request to switch the active dock. `params` is an opaque, serializable * bag the target dock interprets — the terminals dock reads `params.sessionId` diff --git a/tests/__snapshots__/tsnapi/@devframes/hub/client.snapshot.d.ts b/tests/__snapshots__/tsnapi/@devframes/hub/client.snapshot.d.ts index 2bbbb5b6..6acdec65 100644 --- a/tests/__snapshots__/tsnapi/@devframes/hub/client.snapshot.d.ts +++ b/tests/__snapshots__/tsnapi/@devframes/hub/client.snapshot.d.ts @@ -114,6 +114,8 @@ export interface DockSessionStorage { groupLastChildIds?: Record; } export interface DocksPanelContext { + readonly state: DevframeDockPanelState; + readonly events: EventEmitter; store: DockPanelStorage; session: DockSessionStorage; isDragging: boolean; @@ -121,6 +123,9 @@ export interface DocksPanelContext { readonly isVertical: boolean; consumeBootRoute?: (_: string) => string | null; } +export interface DocksPanelEvents { + 'panel:state:changed': (_: DevframeDockPanelState) => void; +} export interface FrameLocationHistory { pushState: (..._: any[]) => void; replaceState: (..._: any[]) => void; diff --git a/tests/__snapshots__/tsnapi/@devframes/hub/constants.snapshot.d.ts b/tests/__snapshots__/tsnapi/@devframes/hub/constants.snapshot.d.ts index 4d2876e2..bddf45b0 100644 --- a/tests/__snapshots__/tsnapi/@devframes/hub/constants.snapshot.d.ts +++ b/tests/__snapshots__/tsnapi/@devframes/hub/constants.snapshot.d.ts @@ -35,6 +35,9 @@ export declare const HUB_EVENTS: { readonly terminalsRestart: "hub:terminals:restart"; readonly terminalsRemove: "hub:terminals:remove"; }; + readonly client: { + readonly docksPanelStateChanged: "panel:state:changed"; + }; readonly broadcast: { readonly docksActivate: "devframe:docks:activate"; readonly terminalsUpdated: "devframe:terminals:updated"; diff --git a/tests/__snapshots__/tsnapi/@devframes/hub/index.snapshot.d.ts b/tests/__snapshots__/tsnapi/@devframes/hub/index.snapshot.d.ts index 96010d69..6fec7e31 100644 --- a/tests/__snapshots__/tsnapi/@devframes/hub/index.snapshot.d.ts +++ b/tests/__snapshots__/tsnapi/@devframes/hub/index.snapshot.d.ts @@ -101,6 +101,10 @@ export interface DevframeDockEntryRegistry { 'group': DevframeViewGroup; '~builtin': DevframeViewBuiltin; } +export interface DevframeDockPanelState { + state: 'open' | 'closed' | 'hidden'; + selectedDockId?: string; +} export interface DevframeDocksActiveState { activation: DevframeDockActivation | null; } diff --git a/tests/__snapshots__/tsnapi/@devframes/hub/types.snapshot.d.ts b/tests/__snapshots__/tsnapi/@devframes/hub/types.snapshot.d.ts index 2eb67965..ca4778e5 100644 --- a/tests/__snapshots__/tsnapi/@devframes/hub/types.snapshot.d.ts +++ b/tests/__snapshots__/tsnapi/@devframes/hub/types.snapshot.d.ts @@ -29,6 +29,7 @@ export { DevframeDockEntryBase } export { DevframeDockEntryCategory } export { DevframeDockEntryIcon } export { DevframeDockEntryRegistry } +export { DevframeDockPanelState } export { DevframeDocksActiveState } export { DevframeDocksHost } export { DevframeDocksUserSettings }