mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 18:34:02 +00:00
feat(cockpit): render the utility strip's measured layout from the store
The Cockpit layout pass publishes the utility strip's fit to the gev-cockpit store: the HUD's --cockpit-utility-* lengths, whether only the expanded control fits, and whether a collapsed control beside an expanded one is hidden from assistive technology. CockpitView renders the lengths on the HUD and CockpitUtilityControls the strip's class and each control's aria-hidden, which now also follows a popover opened between passes. Focus leaves a control before the strip hides it. Exit publishes no layout, which also clears the expanded height, class and aria-hidden the old exit left behind. The briefing and Contact cards' positions stay imperative: the same pass measures them straight after writing them.
This commit is contained in:
1 parent
c3c69a91cd
commit
4c92f517db
12 files changed
+236
-70
No files matched your search
@@ -177,6 +177,12 @@
|
||||
`signalCollapsed` in its presence, and each card's `data-collapsed` and
|
||||
its toggle's state, words and icon follow it (samehkhamis).
|
||||
|
||||
- The Cockpit utility strip's measured layout renders from the
|
||||
`gev-cockpit` store: the layout pass publishes `utilityLayout` (the HUD's
|
||||
`--cockpit-utility-*` lengths, `primaryOnly` and `hideCollapsed`), and a
|
||||
host constructing `CockpitController` passes `publish.utilityLayout`
|
||||
beside its other publishers (samehkhamis).
|
||||
|
||||
## [0.3.0] — 2026-10-27 — Street Level, On Your Phone, in Your Language
|
||||
|
||||
- Show a Street Level view facing 359.5° or more as 0°. The viewer caption
|
||||
|
||||
@@ -314,7 +314,12 @@ a feature supplying only its accent.
|
||||
that presence too: each card's `data-collapsed` and its toggle's words
|
||||
and icon render from it. The controls keep the controller's listeners,
|
||||
and the cards' layout stays with the controller, which reads their
|
||||
visibility when it lays them out.
|
||||
visibility when it lays them out. Its layout pass publishes the utility
|
||||
strip's measured fit (the HUD's anchor and height limits, whether only the
|
||||
expanded control fits and which control is hidden from assistive
|
||||
technology), which `CockpitView` and `CockpitUtilityControls` render; the
|
||||
briefing and Contact cards' positions stay imperative, because the same
|
||||
pass measures them straight after writing them.
|
||||
`CockpitUtilityControls` renders the Display
|
||||
and Radio launchers, which popover is open and the compact Radio
|
||||
controls from the `gev-radio` store that RadioControls publishes to and
|
||||
|
||||
@@ -527,7 +527,7 @@ test('Cockpit owns a focused shared Display portal and compact Radio controls',
|
||||
);
|
||||
assert.match(
|
||||
syncSignalLayout.toString(),
|
||||
/setAttribute\('aria-hidden', String\(hiddenSibling\)\)/,
|
||||
/primaryOnly: utilityLayout\.primaryOnly,\s*hideCollapsed: utilityLayout\.primaryOnly,/,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -934,7 +934,7 @@ test('Cockpit panel corridors reserve the owned topline readouts', () => {
|
||||
assert.ok(signalLayout, 'Cockpit signal layout method is missing');
|
||||
assert.match(
|
||||
signalLayout,
|
||||
/setProperty\(\s*'--cockpit-utility-top',\s*`\$\{utilityAnchor\.top\.toFixed\(1\)\}px`,?\s*\)/,
|
||||
/publishUtilityLayout\([\s\S]*?top: `\$\{utilityAnchor\.top\.toFixed\(1\)\}px`,/,
|
||||
'Cockpit owns the utility strip anchor and republishes it every layout tick',
|
||||
);
|
||||
assert.match(
|
||||
@@ -970,7 +970,7 @@ test('Cockpit panel corridors reserve the owned topline readouts', () => {
|
||||
);
|
||||
assert.match(
|
||||
signalLayout,
|
||||
/cockpit-utility-controls[\s\S]*?--cockpit-utility-max-height/,
|
||||
/maxHeight: `\$\{availableHeight\.toFixed\(2\)\}px`,/,
|
||||
);
|
||||
assert.match(
|
||||
css,
|
||||
|
||||
@@ -400,6 +400,8 @@ export class StyleManager extends ShellFacade {
|
||||
useCockpitStore(applicationPinia()).publishVision(view),
|
||||
signals: (rows) =>
|
||||
useCockpitStore(applicationPinia()).publishSignals(rows),
|
||||
utilityLayout: (layout) =>
|
||||
useCockpitStore(applicationPinia()).publishUtilityLayout(layout),
|
||||
},
|
||||
_layoutRightPanels: (...args) => this._layoutRightPanels(...args),
|
||||
_setCockpitVision: (...args) => this._setCockpitVision(...args),
|
||||
|
||||
@@ -150,6 +150,7 @@ export class CockpitViewController {
|
||||
this.publishRoute = publish.route ?? (() => {});
|
||||
this.publishVision = publish.vision ?? (() => {});
|
||||
this.publishSignals = publish.signals ?? (() => {});
|
||||
this.publishUtilityLayout = publish.utilityLayout ?? (() => {});
|
||||
this.presence = {
|
||||
active: false,
|
||||
entryAvailable: false,
|
||||
|
||||
@@ -301,7 +301,14 @@ test('re-renders keep the visibility and classes the controller publishes on the
|
||||
const hud = $('#cockpit-hud');
|
||||
store.publishPresence({ active: true, context: false });
|
||||
hud.classList.add('voice-highlight');
|
||||
hud.style.setProperty('--cockpit-utility-top', '120px');
|
||||
hud.style.setProperty('--voice-glow', '1');
|
||||
store.publishUtilityLayout({
|
||||
top: '120px',
|
||||
maxHeight: null,
|
||||
expandedMaxHeight: null,
|
||||
primaryOnly: false,
|
||||
hideCollapsed: false,
|
||||
});
|
||||
$('#cockpit-speed-rim').classList.add('external');
|
||||
updateHud.call(owner, INFO, 1000, true);
|
||||
await nextTick();
|
||||
@@ -310,7 +317,13 @@ test('re-renders keep the visibility and classes the controller publishes on the
|
||||
assert.equal(hud.hidden, false);
|
||||
assert.equal(hud.classList.contains('signals-active'), true);
|
||||
assert.equal(hud.classList.contains('voice-highlight'), true);
|
||||
assert.equal(hud.style.getPropertyValue('--voice-glow'), '1');
|
||||
// The utility strip's measured anchor renders from the store.
|
||||
assert.equal(hud.style.getPropertyValue('--cockpit-utility-top'), '120px');
|
||||
store.publishUtilityLayout(null);
|
||||
await nextTick();
|
||||
assert.equal(hud.style.getPropertyValue('--cockpit-utility-top'), '');
|
||||
assert.equal(hud.style.getPropertyValue('--voice-glow'), '1');
|
||||
assert.equal($('#cockpit-speed-rim').classList.contains('external'), true);
|
||||
});
|
||||
|
||||
|
||||
+39
-56
@@ -133,20 +133,8 @@ export function syncSignalLayout() {
|
||||
const utilityControls = document.getElementById('cockpit-utility-controls');
|
||||
if (window.matchMedia('(max-width: 760px)').matches) {
|
||||
this.signalStream.dataset.layoutMode = 'compact-top';
|
||||
utilityControls?.classList.remove('layout-primary-only');
|
||||
utilityControls
|
||||
?.querySelectorAll('.cockpit-utility-control')
|
||||
.forEach((control) => {
|
||||
const hiddenSibling = Boolean(
|
||||
utilityControls.querySelector(
|
||||
'.cockpit-utility-control.is-expanded',
|
||||
) && !control.classList.contains('is-expanded'),
|
||||
);
|
||||
control.setAttribute('aria-hidden', String(hiddenSibling));
|
||||
});
|
||||
this.hud?.style.removeProperty('--cockpit-utility-top');
|
||||
this.hud?.style.removeProperty('--cockpit-utility-max-height');
|
||||
this.hud?.style.removeProperty('--cockpit-utility-expanded-max-height');
|
||||
// The compact strip shows only an expanded control.
|
||||
this.publishUtilityLayout(utilityLayoutView({ hideCollapsed: true }));
|
||||
this.signalStream.style.removeProperty('--cockpit-signal-right');
|
||||
this.signalStream.style.removeProperty('--cockpit-signal-top');
|
||||
this.signalStream.style.removeProperty('--cockpit-signal-max-height');
|
||||
@@ -231,22 +219,13 @@ export function syncSignalLayout() {
|
||||
'--cyber-cockpit-panel-height',
|
||||
`${lane.panelHeight.toFixed(2)}px`,
|
||||
);
|
||||
this.hud?.style.setProperty(
|
||||
'--cockpit-utility-top',
|
||||
`${alignedTop.toFixed(2)}px`,
|
||||
this.publishUtilityLayout(
|
||||
utilityLayoutView({
|
||||
top: `${alignedTop.toFixed(2)}px`,
|
||||
maxHeight: `${lane.utilityHeight.toFixed(2)}px`,
|
||||
expandedMaxHeight: `${lane.panelHeight.toFixed(2)}px`,
|
||||
}),
|
||||
);
|
||||
this.hud?.style.setProperty(
|
||||
'--cockpit-utility-max-height',
|
||||
`${lane.utilityHeight.toFixed(2)}px`,
|
||||
);
|
||||
this.hud?.style.setProperty(
|
||||
'--cockpit-utility-expanded-max-height',
|
||||
`${lane.panelHeight.toFixed(2)}px`,
|
||||
);
|
||||
utilityControls.classList.remove('layout-primary-only');
|
||||
utilityControls
|
||||
.querySelectorAll('.cockpit-utility-control')
|
||||
.forEach((control) => control.setAttribute('aria-hidden', 'false'));
|
||||
if (expandedControl) {
|
||||
this.signalStream.style.setProperty(
|
||||
'--cockpit-signal-top',
|
||||
@@ -289,14 +268,6 @@ export function syncSignalLayout() {
|
||||
: 0,
|
||||
});
|
||||
const availableHeight = utilityAnchor.maxHeight;
|
||||
this.hud?.style.setProperty(
|
||||
'--cockpit-utility-top',
|
||||
`${utilityAnchor.top.toFixed(1)}px`,
|
||||
);
|
||||
this.hud?.style.setProperty(
|
||||
'--cockpit-utility-max-height',
|
||||
`${availableHeight.toFixed(2)}px`,
|
||||
);
|
||||
const utilityLayout =
|
||||
expandedControl && collapsedControl
|
||||
? resolveCockpitUtilityLayout({
|
||||
@@ -305,25 +276,37 @@ export function syncSignalLayout() {
|
||||
collapsedHeight,
|
||||
})
|
||||
: { primaryOnly: false, expandedMaxHeight: availableHeight };
|
||||
utilityControls?.classList.toggle(
|
||||
'layout-primary-only',
|
||||
utilityLayout.primaryOnly,
|
||||
this.publishUtilityLayout(
|
||||
utilityLayoutView({
|
||||
top: `${utilityAnchor.top.toFixed(1)}px`,
|
||||
maxHeight: `${availableHeight.toFixed(2)}px`,
|
||||
expandedMaxHeight: `${utilityLayout.expandedMaxHeight.toFixed(2)}px`,
|
||||
primaryOnly: utilityLayout.primaryOnly,
|
||||
hideCollapsed: utilityLayout.primaryOnly,
|
||||
}),
|
||||
);
|
||||
this.hud?.style.setProperty(
|
||||
'--cockpit-utility-expanded-max-height',
|
||||
`${utilityLayout.expandedMaxHeight.toFixed(2)}px`,
|
||||
);
|
||||
utilityControls
|
||||
?.querySelectorAll('.cockpit-utility-control')
|
||||
.forEach((control) => {
|
||||
const hiddenSibling =
|
||||
utilityLayout.primaryOnly && control === collapsedControl;
|
||||
control.setAttribute('aria-hidden', String(hiddenSibling));
|
||||
if (hiddenSibling && control.contains(document.activeElement)) {
|
||||
expandedControl
|
||||
?.querySelector('.cockpit-utility-glyph')
|
||||
?.focus({ preventScroll: true });
|
||||
}
|
||||
});
|
||||
// Focus leaves a control the strip is about to hide.
|
||||
if (
|
||||
utilityLayout.primaryOnly &&
|
||||
collapsedControl?.contains(document.activeElement)
|
||||
) {
|
||||
expandedControl
|
||||
?.querySelector('.cockpit-utility-glyph')
|
||||
?.focus({ preventScroll: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The utility strip's layout as CockpitView and CockpitUtilityControls
|
||||
* render it: its anchor and height limits on the HUD, and which controls fit.
|
||||
*/
|
||||
function utilityLayoutView({
|
||||
top = null,
|
||||
maxHeight = null,
|
||||
expandedMaxHeight = null,
|
||||
primaryOnly = false,
|
||||
hideCollapsed = false,
|
||||
} = {}) {
|
||||
return { top, maxHeight, expandedMaxHeight, primaryOnly, hideCollapsed };
|
||||
}
|
||||
@@ -232,8 +232,7 @@ export function exit({ restoreTracking = true } = {}) {
|
||||
this.regionalBriefSubjectId = null;
|
||||
document.body.classList.remove('cockpit-mode');
|
||||
this.onExited?.();
|
||||
this.hud?.style.removeProperty('--cockpit-utility-top');
|
||||
this.hud?.style.removeProperty('--cockpit-utility-max-height');
|
||||
this.publishUtilityLayout(null);
|
||||
this.presence.active = false;
|
||||
this.publishPresenceView();
|
||||
this.routeView.visible = false;
|
||||
|
||||
@@ -10,6 +10,8 @@ import { setLocale } from '../i18n/index.js';
|
||||
import { onKeyDown as cockpitKeyDown } from './cockpitInput.js';
|
||||
import { RadioControls } from './radioControls.js';
|
||||
import { renderRadioState } from './radioPresentation.js';
|
||||
import { syncSignalLayout } from './cockpitLayout.js';
|
||||
import { useCockpitStore } from './stores/cockpit.js';
|
||||
import { useRadioStore } from './stores/radio.js';
|
||||
import CockpitUtilityControls from './views/panels/CockpitUtilityControls.vue';
|
||||
|
||||
@@ -336,3 +338,115 @@ test('Cockpit Escape defers to a popover the moment its disclosure opens', async
|
||||
assert.equal(exits, 1);
|
||||
await nextTick();
|
||||
});
|
||||
|
||||
test('the layout pass’s measured fit hides the collapsed control and marks the strip', async () => {
|
||||
document.body.replaceChildren();
|
||||
const host = document.body.appendChild(document.createElement('div'));
|
||||
const pinia = createPinia();
|
||||
const app = createApp(CockpitUtilityControls).use(pinia);
|
||||
app.mount(host);
|
||||
const radio = useRadioStore(pinia);
|
||||
const cockpit = useCockpitStore(pinia);
|
||||
const strip = document.getElementById('cockpit-utility-controls');
|
||||
const [display, radioControl] = strip.querySelectorAll(
|
||||
'.cockpit-utility-control',
|
||||
);
|
||||
const layout = (fit) => ({
|
||||
top: '96.0px',
|
||||
maxHeight: '300.00px',
|
||||
expandedMaxHeight: '300.00px',
|
||||
primaryOnly: false,
|
||||
hideCollapsed: false,
|
||||
...fit,
|
||||
});
|
||||
// Until the layout pass runs, neither control carries aria-hidden.
|
||||
assert.equal(display.hasAttribute('aria-hidden'), false);
|
||||
radio.publishUtility({ display: false, radio: true });
|
||||
cockpit.publishUtilityLayout(
|
||||
layout({ primaryOnly: true, hideCollapsed: true }),
|
||||
);
|
||||
await nextTick();
|
||||
assert.equal(strip.classList.contains('layout-primary-only'), true);
|
||||
assert.equal(display.getAttribute('aria-hidden'), 'true');
|
||||
assert.equal(radioControl.getAttribute('aria-hidden'), 'false');
|
||||
// With nothing expanded there is no collapsed sibling to hide.
|
||||
radio.publishUtility({ display: false, radio: false });
|
||||
await nextTick();
|
||||
assert.equal(display.getAttribute('aria-hidden'), 'false');
|
||||
cockpit.publishUtilityLayout(layout());
|
||||
await nextTick();
|
||||
assert.equal(strip.classList.contains('layout-primary-only'), false);
|
||||
app.unmount();
|
||||
});
|
||||
|
||||
test('the real layout pass drives the mounted controls: compact hides the collapsed sibling, Cyber shows both', async (t) => {
|
||||
document.body.replaceChildren();
|
||||
const host = document.body.appendChild(document.createElement('div'));
|
||||
const pinia = createPinia();
|
||||
const app = createApp(CockpitUtilityControls).use(pinia);
|
||||
app.mount(host);
|
||||
const radio = useRadioStore(pinia);
|
||||
const cockpit = useCockpitStore(pinia);
|
||||
const signalStream = document.body.appendChild(
|
||||
document.createElement('aside'),
|
||||
);
|
||||
const owner = {
|
||||
signalStream,
|
||||
signalCollapsed: false,
|
||||
contextCollapsed: false,
|
||||
context: null,
|
||||
publishUtilityLayout: (layout) => cockpit.publishUtilityLayout(layout),
|
||||
syncContextLayout() {},
|
||||
};
|
||||
const originalMatchMedia = window.matchMedia;
|
||||
let compact = true;
|
||||
window.matchMedia = (query) => ({ matches: compact, media: query });
|
||||
t.after(() => {
|
||||
window.matchMedia = originalMatchMedia;
|
||||
delete document.documentElement.dataset.uiTheme;
|
||||
document.body.style.removeProperty('opacity');
|
||||
document.documentElement.style.removeProperty('opacity');
|
||||
app.unmount();
|
||||
});
|
||||
const strip = document.getElementById('cockpit-utility-controls');
|
||||
const [display, radioControl] = strip.querySelectorAll(
|
||||
'.cockpit-utility-control',
|
||||
);
|
||||
radio.publishUtility({ display: false, radio: true });
|
||||
await nextTick();
|
||||
syncSignalLayout.call(owner);
|
||||
await nextTick();
|
||||
assert.equal(display.getAttribute('aria-hidden'), 'true');
|
||||
assert.equal(radioControl.getAttribute('aria-hidden'), 'false');
|
||||
assert.equal(cockpit.utilityLayout.top, null);
|
||||
// Compact hides the sibling without marking the strip primary-only.
|
||||
assert.equal(strip.classList.contains('layout-primary-only'), false);
|
||||
// Cyber aligns the strip with the Data panel and keeps both controls.
|
||||
compact = false;
|
||||
document.documentElement.dataset.uiTheme = 'cyber';
|
||||
const stack = document.body.appendChild(document.createElement('div'));
|
||||
stack.id = 'left-panel-stack';
|
||||
const dataPanel = stack.appendChild(document.createElement('section'));
|
||||
dataPanel.id = 'data-panel';
|
||||
for (const node of [
|
||||
dataPanel,
|
||||
stack,
|
||||
document.body,
|
||||
document.documentElement,
|
||||
])
|
||||
node.style.opacity = '1';
|
||||
dataPanel.getBoundingClientRect = () => ({
|
||||
top: 120,
|
||||
bottom: 400,
|
||||
left: 0,
|
||||
right: 200,
|
||||
width: 200,
|
||||
height: 280,
|
||||
});
|
||||
syncSignalLayout.call(owner);
|
||||
await nextTick();
|
||||
assert.equal(display.getAttribute('aria-hidden'), 'false');
|
||||
assert.equal(radioControl.getAttribute('aria-hidden'), 'false');
|
||||
assert.equal(cockpit.utilityLayout.top, '120.00px');
|
||||
assert.equal(strip.classList.contains('layout-primary-only'), false);
|
||||
});
|
||||
@@ -66,6 +66,26 @@ export const useCockpitStore = defineStore('gev-cockpit', () => {
|
||||
signals.value = next && markRaw(next);
|
||||
}
|
||||
|
||||
/**
|
||||
* The utility strip's measured layout: its top and height limits as CSS
|
||||
* lengths (null leaves the stylesheet's), whether only the expanded control
|
||||
* fits, and whether a collapsed control beside an expanded one is hidden
|
||||
* from assistive technology. Null until the first layout pass.
|
||||
*/
|
||||
const utilityLayout = shallowRef(null);
|
||||
|
||||
/** @param {?object} next A fresh layout; one equal to the current is dropped. */
|
||||
function publishUtilityLayout(next) {
|
||||
const current = utilityLayout.value;
|
||||
if (
|
||||
next &&
|
||||
current &&
|
||||
Object.keys(next).every((key) => next[key] === current[key])
|
||||
)
|
||||
return;
|
||||
utilityLayout.value = next && markRaw(next);
|
||||
}
|
||||
|
||||
return {
|
||||
presence,
|
||||
publishPresence,
|
||||
@@ -81,5 +101,7 @@ export const useCockpitStore = defineStore('gev-cockpit', () => {
|
||||
publishVision,
|
||||
signals,
|
||||
publishSignals,
|
||||
utilityLayout,
|
||||
publishUtilityLayout,
|
||||
};
|
||||
});
|
||||
@@ -18,9 +18,10 @@ import CockpitUtilityControls from './panels/CockpitUtilityControls.vue';
|
||||
// The Cockpit HUD. The Cockpit controller presents the instruments, the
|
||||
// destination arrow and the vision selector on every HUD update; until it
|
||||
// does, they show the state they start in. The controller also publishes
|
||||
// which surfaces are shown; the HUD's layout stays with it, and the controls
|
||||
// keep its listeners.
|
||||
const { presence, instruments, route, vision } = storeToRefs(useCockpitStore());
|
||||
// which surfaces are shown and the utility strip's measured layout; the
|
||||
// layout pass stays with it, and the controls keep its listeners.
|
||||
const { presence, instruments, route, vision, utilityLayout } =
|
||||
storeToRefs(useCockpitStore());
|
||||
const active = computed(() => Boolean(presence.value?.active));
|
||||
const globe = useGlobeActionsStore();
|
||||
const { resetBusy } = storeToRefs(globe);
|
||||
@@ -38,6 +39,14 @@ const visionLabel = computed(() =>
|
||||
const visionTitle = computed(() =>
|
||||
t('Current style: {style} — click for next', { style: v.value.name }),
|
||||
);
|
||||
// The utility strip's measured anchor and height limits; a null value leaves
|
||||
// the stylesheet's fallback.
|
||||
const utilityStyle = computed(() => ({
|
||||
'--cockpit-utility-top': utilityLayout.value?.top ?? null,
|
||||
'--cockpit-utility-max-height': utilityLayout.value?.maxHeight ?? null,
|
||||
'--cockpit-utility-expanded-max-height':
|
||||
utilityLayout.value?.expandedMaxHeight ?? null,
|
||||
}));
|
||||
const tickStyle = (tick) =>
|
||||
tick.slot === null
|
||||
? undefined
|
||||
@@ -46,7 +55,7 @@ const tickStyle = (tick) =>
|
||||
|
||||
<template>
|
||||
<!-- Aircraft first-person view. It can be entered from the visible view switcher. -->
|
||||
<section id="cockpit-hud" aria-label="Aircraft cockpit view" :hidden="!active" v-class-toggle="{ 'signals-active': active, 'context-active': Boolean(presence?.context) }" data-i18n-aria-label :data-layer="hud.layer ?? undefined">
|
||||
<section id="cockpit-hud" aria-label="Aircraft cockpit view" :hidden="!active" :style="utilityStyle" v-class-toggle="{ 'signals-active': active, 'context-active': Boolean(presence?.context) }" data-i18n-aria-label :data-layer="hud.layer ?? undefined">
|
||||
<!-- One optical coordinate system: every targeting mark shares this center.
|
||||
Decorative only; telemetry and interactive panels remain separate. -->
|
||||
<svg class="cockpit-frame-lines" viewBox="0 0 1200 720" preserveAspectRatio="none" aria-hidden="true" focusable="false">
|
||||
|
||||
@@ -5,6 +5,7 @@ import { t } from '../../../i18n/index.js';
|
||||
import { vClassToggle } from '../../rail/classToggle.js';
|
||||
import UiSlider from '../../kit/UiSlider.js';
|
||||
import { useRadioStore } from '../../stores/radio.js';
|
||||
import { useCockpitStore } from '../../stores/cockpit.js';
|
||||
import UiButton from '../../kit/UiButton.vue';
|
||||
|
||||
// The Cockpit's Display and Radio launchers and their popovers. RadioControls
|
||||
@@ -18,6 +19,17 @@ const { act } = store;
|
||||
const { view: radio, utility } = storeToRefs(store);
|
||||
const open = computed(() => utility.value ?? { display: false, radio: false });
|
||||
const cockpit = computed(() => radio.value?.cockpit ?? null);
|
||||
// The Cockpit's layout pass measures which controls fit; until it runs, the
|
||||
// controls carry no aria-hidden.
|
||||
const { utilityLayout: layout } = storeToRefs(useCockpitStore());
|
||||
const ariaHidden = (expanded) =>
|
||||
layout.value
|
||||
? String(
|
||||
layout.value.hideCollapsed &&
|
||||
!expanded &&
|
||||
(open.value.display || open.value.radio),
|
||||
)
|
||||
: undefined;
|
||||
const displayLabel = computed(() =>
|
||||
open.value.display
|
||||
? t('Collapse Cockpit display options')
|
||||
@@ -31,8 +43,8 @@ const radioLabel = computed(() =>
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<aside id="cockpit-utility-controls" class="cockpit-utility-controls" aria-label="Cockpit display and Radio controls" data-i18n-aria-label>
|
||||
<div class="cockpit-utility-control ui-scroll" v-class-toggle="{ 'is-expanded': open.display }">
|
||||
<aside id="cockpit-utility-controls" class="cockpit-utility-controls" v-class-toggle="{ 'layout-primary-only': !!layout?.primaryOnly }" aria-label="Cockpit display and Radio controls" data-i18n-aria-label>
|
||||
<div class="cockpit-utility-control ui-scroll" v-class-toggle="{ 'is-expanded': open.display }" :aria-hidden="ariaHidden(open.display)">
|
||||
<div class="cockpit-utility-launcher" data-cockpit-launcher="display">
|
||||
<span data-i18n>DISPLAY</span>
|
||||
<span class="cockpit-utility-divider" aria-hidden="true"></span>
|
||||
@@ -45,7 +57,7 @@ const radioLabel = computed(() =>
|
||||
<div class="cockpit-display-slot" data-cockpit-display-slot="models3d"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cockpit-utility-control ui-scroll" v-class-toggle="{ 'is-expanded': open.radio }">
|
||||
<div class="cockpit-utility-control ui-scroll" v-class-toggle="{ 'is-expanded': open.radio }" :aria-hidden="ariaHidden(open.radio)">
|
||||
<div class="cockpit-utility-launcher" data-cockpit-launcher="radio">
|
||||
<span data-i18n>RADIO</span>
|
||||
<span class="cockpit-utility-divider" aria-hidden="true"></span>
|
||||
|
||||
Reference in new issue
Block a user