feat(panels): render collapse, dock and pin buttons from the store

PanelChrome no longer writes the panels' collapse buttons, the command
dock tray toggles' names or the dock pins' pressed state. It presents each
panel's open and pinned state to the new gev-panel-chrome store, and the
buttons, now views, call it there: PanelCollapseButton for the panel
headers, bound toggles and pins in the command dock. Clicks take effect
once the chrome is initialized; a new owner starts from its own states.

bindPanelDisclosure binds only a panel's Escape and refuses the retired
buttons and onChange arguments. UiButton joins the component kit.
This commit is contained in:
Sameh Khamis committed 2026-10-31 23:30:08 -07:00
1 parent ec4c55e78c
commit dd3a2c87f9
25 files changed
+635 -212

No files matched your search

+6 -18
View File
@@ -1,7 +1,9 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { UiLifetime } from './uiLifetime.js';
import { createPinia } from 'pinia';
import { PanelChrome } from './panelChrome.js';
import { usePanelChromeStore } from './stores/panelChrome.js';
import { PanelLayoutController } from './panelLayoutController.js';
import { ShellFeedback } from './shellFeedback.js';
import { RecordingControls } from './recordingControls.js';
@@ -292,23 +294,13 @@ test('weather rail orders between CCTV and Context, observes and syncs chrome',
panel.id = id;
panel.style = {};
panel.classList.add('collapsed');
const button = f.element();
button.closest = () => panel;
const attributes = new Map();
button.setAttribute = (name, value) => attributes.set(name, value);
button.getAttribute = (name) => attributes.get(name);
panel.button = button;
panel.querySelectorAll = () => [button];
panel.querySelector = (selector) =>
selector.startsWith('.panel-title')
? { textContent: id === 'weather-panel' ? 'WEATHER' : id }
: null;
f.nodes.set(id, panel);
}
const weather = f.nodes.get('weather-panel');
stack.children = [weather, f.nodes.get('global-context-panel')];
const synced = [];
const chrome = {};
const store = usePanelChromeStore(createPinia());
const chrome = { _chromeStore: store, _chromeToken: store.connect({}) };
const owner = new PanelLayoutController({
readHud: () => ({}),
scheduleCockpitLayout() {},
@@ -325,14 +317,10 @@ test('weather rail orders between CCTV and Context, observes and syncs chrome',
);
assert.ok(synced.includes('weather-panel'));
assert.ok(observed.has(weather));
assert.equal(weather.button.textContent, '◀');
assert.equal(weather.button.getAttribute('aria-expanded'), 'false');
assert.equal(weather.button.title, 'Expand WEATHER');
assert.equal(store.panels['weather-panel'].collapsed, true);
weather.classList.remove('collapsed');
PanelChrome.prototype._syncPanelCollapseButton.call(chrome, weather);
assert.equal(weather.button.textContent, '▶');
assert.equal(weather.button.getAttribute('aria-expanded'), 'true');
assert.equal(weather.button.getAttribute('aria-label'), 'Collapse WEATHER');
assert.equal(store.panels['weather-panel'].collapsed, false);
owner.destroy();
assert.equal(observed.size, 0);
assert.equal(f.frames.size, 0);