mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
feat(kit): render the chips with UiChip and UiButton
UiChip is a non-interactive label chip and renders the voice card's chips. The pressable chips and pills are pressed toggles, so the rail card chips, map source chips and city and place pills render through UiButton with pressedClass, keeping their classes, attributes and the classes other code adds. The pills now report aria-pressed.
This commit is contained in:
1 parent
d98a7c81be
commit
28ca784d49
10 files changed
+81
-16
No files matched your search
+4
-1
@@ -65,7 +65,10 @@
|
|||||||
what the answering action returned. The Display, Radio, RTL-SDR, Cockpit,
|
what the answering action returned. The Display, Radio, RTL-SDR, Cockpit,
|
||||||
mission replay and style parameter sliders are the kit's `UiSlider`, a
|
mission replay and style parameter sliders are the kit's `UiSlider`, a
|
||||||
native range input that emits numbers, and the pressed toggles are the
|
native range input that emits numbers, and the pressed toggles are the
|
||||||
kit's `UiButton`, which gains `pressedClass`. `syncScopeControls`
|
kit's `UiButton`, which gains `pressedClass`; so are the rail card
|
||||||
|
chips, map source chips and location pills (which now report
|
||||||
|
`aria-pressed`), and the voice card's chips
|
||||||
|
are the kit's new `UiChip`. `syncScopeControls`
|
||||||
(`./ui/display`) and `shouldHideCollapsedRightPanels` (`./ui/layout`) are
|
(`./ui/display`) and `shouldHideCollapsedRightPanels` (`./ui/layout`) are
|
||||||
deprecated: the Display view and the rail now own what they did. `./ui/input` imports Vue
|
deprecated: the Display view and the rail now own what they did. `./ui/input` imports Vue
|
||||||
(samehkhamis).
|
(samehkhamis).
|
||||||
|
|||||||
@@ -61,7 +61,10 @@ adapter is gone.
|
|||||||
`active`); its pressed classes toggle through `classList`, so a class other
|
`active`); its pressed classes toggle through `classList`, so a class other
|
||||||
code adds (a voice highlight) survives a press. Every pressed toggle in the
|
code adds (a voice highlight) survives a press. Every pressed toggle in the
|
||||||
views is a `UiButton` except Bhote Koshi's cinematic button and the
|
views is a `UiButton` except Bhote Koshi's cinematic button and the
|
||||||
tilt-map button, whose owners still write their state into the DOM.
|
tilt-map button, whose owners still write their state into the DOM. Core's
|
||||||
|
pressable chips and pills (rail card chips, map sources, cities and places)
|
||||||
|
are `UiButton`s too; `UiChip` is ui-vue's non-interactive label chip, used by
|
||||||
|
the voice card.
|
||||||
`UiSlider` keeps the native range input where ui-vue draws a
|
`UiSlider` keeps the native range input where ui-vue draws a
|
||||||
`role="slider"` track, so Core's keyboard, form behaviour and styling stay;
|
`role="slider"` track, so Core's keyboard, form behaviour and styling stay;
|
||||||
it takes ui-vue's `v-model`, `min`, `max`, `step` and `disabled`, emits a
|
it takes ui-vue's `v-model`, `min`, `max`, `step` and `disabled`, emits a
|
||||||
|
|||||||
@@ -1132,6 +1132,9 @@
|
|||||||
"src/ui/kit/UiSlider.test.mjs",
|
"src/ui/kit/UiSlider.test.mjs",
|
||||||
"src/ui/kit/UiButton.vue",
|
"src/ui/kit/UiButton.vue",
|
||||||
"src/ui/kit/UiButton.test.mjs",
|
"src/ui/kit/UiButton.test.mjs",
|
||||||
|
"src/ui/kit/UiChip.vue",
|
||||||
|
"src/ui/kit/UiChip.test.mjs",
|
||||||
|
"src/ui/rail/RailChips.vue",
|
||||||
"src/ui/displayPanelActions.test.mjs",
|
"src/ui/displayPanelActions.test.mjs",
|
||||||
"src/testSupport/vueDom.mjs",
|
"src/testSupport/vueDom.mjs",
|
||||||
"build/vueLoader.js",
|
"build/vueLoader.js",
|
||||||
|
|||||||
@@ -134,7 +134,8 @@
|
|||||||
"src/ui/rail/PanelCollapseButton.vue",
|
"src/ui/rail/PanelCollapseButton.vue",
|
||||||
"src/ui/rail/panelState.js",
|
"src/ui/rail/panelState.js",
|
||||||
"src/sdr/gain.js",
|
"src/sdr/gain.js",
|
||||||
"src/ui/kit/UiSlider.js"
|
"src/ui/kit/UiSlider.js",
|
||||||
|
"src/ui/kit/UiChip.vue"
|
||||||
],
|
],
|
||||||
"external": ["vue", "pinia"]
|
"external": ["vue", "pinia"]
|
||||||
},
|
},
|
||||||
@@ -667,7 +668,8 @@
|
|||||||
"src/ui/rail/utcClock.js",
|
"src/ui/rail/utcClock.js",
|
||||||
"src/ui/rail/RailTimeline.vue",
|
"src/ui/rail/RailTimeline.vue",
|
||||||
"src/ui/rail/rangeValue.js",
|
"src/ui/rail/rangeValue.js",
|
||||||
"src/ui/weather/WeatherReadout.vue"
|
"src/ui/weather/WeatherReadout.vue",
|
||||||
|
"src/ui/kit/UiButton.vue"
|
||||||
],
|
],
|
||||||
"external": ["vue"]
|
"external": ["vue"]
|
||||||
},
|
},
|
||||||
@@ -1066,7 +1068,8 @@
|
|||||||
"src/ui/stores/panelChrome.js",
|
"src/ui/stores/panelChrome.js",
|
||||||
"src/ui/rail/panelState.js",
|
"src/ui/rail/panelState.js",
|
||||||
"src/ui/rail/fieldValue.js",
|
"src/ui/rail/fieldValue.js",
|
||||||
"src/ui/kit/UiSlider.js"
|
"src/ui/kit/UiSlider.js",
|
||||||
|
"src/ui/kit/UiButton.vue"
|
||||||
],
|
],
|
||||||
"external": ["cesium", "vue", "pinia"]
|
"external": ["cesium", "vue", "pinia"]
|
||||||
},
|
},
|
||||||
@@ -3076,7 +3079,8 @@
|
|||||||
"src/ui/stores/panelChrome.js",
|
"src/ui/stores/panelChrome.js",
|
||||||
"src/ui/rail/panelState.js",
|
"src/ui/rail/panelState.js",
|
||||||
"src/ui/rail/fieldValue.js",
|
"src/ui/rail/fieldValue.js",
|
||||||
"src/ui/kit/UiSlider.js"
|
"src/ui/kit/UiSlider.js",
|
||||||
|
"src/ui/kit/UiButton.vue"
|
||||||
],
|
],
|
||||||
"external": [
|
"external": [
|
||||||
"hls.js",
|
"hls.js",
|
||||||
|
|||||||
@@ -898,7 +898,9 @@ test('row info and legends reconcile in place while chips retain focus and order
|
|||||||
controls.chips.push({ id: 'two', label: 'Two' });
|
controls.chips.push({ id: 'two', label: 'Two' });
|
||||||
manager._refreshTogglePanel();
|
manager._refreshTogglePanel();
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
[...block.children].map((node) => node.className),
|
[...block.children].map((node) =>
|
||||||
|
[...node.classList].filter((name) => !name.startsWith('ui-')).join(' '),
|
||||||
|
),
|
||||||
[
|
[
|
||||||
'data-toggle-chip chip-idle',
|
'data-toggle-chip chip-idle',
|
||||||
'data-toggle-chip chip-idle',
|
'data-toggle-chip chip-idle',
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { after, before, test } from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { installVueDom } from '../../testSupport/vueDom.mjs';
|
||||||
|
|
||||||
|
let mount;
|
||||||
|
let UiChip;
|
||||||
|
let uninstall;
|
||||||
|
before(async () => {
|
||||||
|
uninstall = installVueDom();
|
||||||
|
({ mount } = await import('@vue/test-utils'));
|
||||||
|
UiChip = (await import('./UiChip.vue')).default;
|
||||||
|
});
|
||||||
|
after(() => uninstall());
|
||||||
|
|
||||||
|
test('a chip is a span with the kit classes and Core’s', async () => {
|
||||||
|
const wrapper = mount(UiChip, {
|
||||||
|
attrs: { class: 'gev-voice-card-chip', 'data-tone': 'ok' },
|
||||||
|
slots: { default: 'ok' },
|
||||||
|
});
|
||||||
|
const chip = wrapper.get('span');
|
||||||
|
assert.deepEqual(chip.classes(), ['ui-chip', 'gev-voice-card-chip']);
|
||||||
|
assert.equal(chip.attributes('data-tone'), 'ok');
|
||||||
|
assert.equal(chip.text(), 'ok');
|
||||||
|
await wrapper.setProps({ selected: true, mono: true });
|
||||||
|
assert.ok(chip.classes().includes('ui-chip--selected'));
|
||||||
|
assert.ok(chip.classes().includes('ui-mono'));
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<script setup>
|
||||||
|
// Core's chip, with @halfpixel/ui-vue UiChip's props and classes so the two
|
||||||
|
// kits can merge: a label that says what something is, never a control.
|
||||||
|
// Core styles it through the classes its callers pass; the kit classes carry
|
||||||
|
// no Core styles yet. A chip that presses is a UiButton with pressedClass.
|
||||||
|
defineProps({
|
||||||
|
selected: { type: Boolean, default: false },
|
||||||
|
mono: { type: Boolean, default: false },
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<span
|
||||||
|
class="ui-chip"
|
||||||
|
:class="{ 'ui-chip--selected': selected, 'ui-mono': mono }"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
import UiButton from '../kit/UiButton.vue';
|
||||||
|
|
||||||
// Keyed chips shared by rail cards; the owner decides what a click does.
|
// Keyed chips shared by rail cards; the owner decides what a click does.
|
||||||
defineProps({
|
defineProps({
|
||||||
chips: { type: Array, default: () => [] },
|
chips: { type: Array, default: () => [] },
|
||||||
@@ -6,23 +8,23 @@ defineProps({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const chipClass = (chip) =>
|
const chipClass = (chip) =>
|
||||||
`data-toggle-chip chip-${chip.state || (chip.active ? 'active' : 'idle')}${chip.active ? ' active' : ''}`;
|
`data-toggle-chip chip-${chip.state || (chip.active ? 'active' : 'idle')}`;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button
|
<UiButton
|
||||||
v-for="chip in chips"
|
v-for="chip in chips"
|
||||||
:key="chip.id"
|
:key="chip.id"
|
||||||
type="button"
|
|
||||||
:data-chip-id="chip.id"
|
:data-chip-id="chip.id"
|
||||||
:class="chipClass(chip)"
|
:class="chipClass(chip)"
|
||||||
|
:pressed="Boolean(chip.active)"
|
||||||
|
pressed-class="active"
|
||||||
:style="chip.color ? `--chip-color: ${chip.color}` : undefined"
|
:style="chip.color ? `--chip-color: ${chip.color}` : undefined"
|
||||||
:data-chip-color="chip.color || undefined"
|
:data-chip-color="chip.color || undefined"
|
||||||
:title="chip.title || undefined"
|
:title="chip.title || undefined"
|
||||||
:disabled="Boolean(chip.disabled)"
|
:disabled="Boolean(chip.disabled)"
|
||||||
:aria-pressed="String(Boolean(chip.active))"
|
|
||||||
:aria-busy="String(Boolean(chip.busy))"
|
:aria-busy="String(Boolean(chip.busy))"
|
||||||
@click="onSelect(chip)"
|
@click="onSelect(chip)"
|
||||||
>{{ chip.label }}</button
|
>{{ chip.label }}</UiButton
|
||||||
>
|
>
|
||||||
</template>
|
</template>
|
||||||
@@ -117,7 +117,7 @@ function selectMapSource(chip) {
|
|||||||
<span data-i18n id="map-source-label">MAP SOURCE</span>
|
<span data-i18n id="map-source-label">MAP SOURCE</span>
|
||||||
<span id="map-stack-status" class="pp-stack-status" v-class-toggle="{ warn: !!mapSource?.status.warn }" aria-live="polite">{{ mapSource ? mapSource.status.text : '3D' }}</span>
|
<span id="map-stack-status" class="pp-stack-status" v-class-toggle="{ warn: !!mapSource?.status.warn }" aria-live="polite">{{ mapSource ? mapSource.status.text : '3D' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="map-stack-chips" class="map-stack-chip-row" role="group" aria-label="Map source" data-i18n-aria-label><button v-for="chip in mapSource?.chips" :key="chip.id" type="button" class="map-stack-chip" v-class-toggle="{ active: chip.active, unavailable: !chip.available }" :data-stack-id="chip.id" :title="chip.title" :aria-pressed="String(chip.active)" :aria-disabled="String(!chip.available)" :aria-label="chip.ariaLabel" @click="selectMapSource(chip)"><span class="map-stack-chip-label">{{ chip.label }}</span><span v-if="chip.requirement" class="map-stack-chip-req">{{ chip.requirement }}</span></button></div>
|
<div id="map-stack-chips" class="map-stack-chip-row" role="group" aria-label="Map source" data-i18n-aria-label><UiButton v-for="chip in mapSource?.chips" :key="chip.id" class="map-stack-chip" :pressed="Boolean(chip.active)" pressed-class="active" v-class-toggle="{ unavailable: !chip.available }" :data-stack-id="chip.id" :title="chip.title" :aria-disabled="String(!chip.available)" :aria-label="chip.ariaLabel" @click="selectMapSource(chip)"><span class="map-stack-chip-label">{{ chip.label }}</span><span v-if="chip.requirement" class="map-stack-chip-req">{{ chip.requirement }}</span></UiButton></div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div id="style-mini-status" class="panel-mini-status" aria-live="polite">
|
<div id="style-mini-status" class="panel-mini-status" aria-live="polite">
|
||||||
@@ -145,10 +145,10 @@ function selectMapSource(chip) {
|
|||||||
</div>
|
</div>
|
||||||
<div id="location-bar-popover" class="dock-popover-content">
|
<div id="location-bar-popover" class="dock-popover-content">
|
||||||
<UiButton variant="icon" class="dock-pin-btn" data-pin-target="location-bar" :label="t('Pin location tray')" :title="t('Keep location tray open')" :pressed="!!panels['location-bar']?.pinned" @click="pin($event, 'location-bar')"><img src="/pin.svg" alt="" /></UiButton>
|
<UiButton variant="icon" class="dock-pin-btn" data-pin-target="location-bar" :label="t('Pin location tray')" :title="t('Keep location tray open')" :pressed="!!panels['location-bar']?.pinned" @click="pin($event, 'location-bar')"><img src="/pin.svg" alt="" /></UiButton>
|
||||||
<div id="poi-row" class="poi-row-container" v-class-toggle="{ expanded: !!loc?.poisExpanded }"><button v-for="poi in loc?.pois" :key="`${loc.poiCity}:${poi.index}`" type="button" class="poi-pill" v-class-toggle="{ active: poi.active }" :data-poi-index="poi.index" @click="locationControls?.choosePoi(poi.index)"><span class="poi-pill-key">{{ poi.key }}</span><span class="poi-pill-name">{{ poi.name }}</span></button></div>
|
<div id="poi-row" class="poi-row-container" v-class-toggle="{ expanded: !!loc?.poisExpanded }"><UiButton v-for="poi in loc?.pois" :key="`${loc.poiCity}:${poi.index}`" class="poi-pill" :pressed="Boolean(poi.active)" pressed-class="active" :data-poi-index="poi.index" @click="locationControls?.choosePoi(poi.index)"><span class="poi-pill-key">{{ poi.key }}</span><span class="poi-pill-name">{{ poi.name }}</span></UiButton></div>
|
||||||
<div class="location-bar-divider" id="location-bar-divider" v-class-toggle="{ visible: !!loc?.poisExpanded }"></div>
|
<div class="location-bar-divider" id="location-bar-divider" v-class-toggle="{ visible: !!loc?.poisExpanded }"></div>
|
||||||
<div class="location-city-row">
|
<div class="location-city-row">
|
||||||
<div id="location-pills" class="location-pills"><button v-for="city in loc?.cities" :key="city.id" type="button" class="location-pill" v-class-toggle="{ active: city.active }" :data-location-id="city.id" @click="locationControls?.chooseCity(city.id)">{{ city.label }}</button></div>
|
<div id="location-pills" class="location-pills"><UiButton v-for="city in loc?.cities" :key="city.id" class="location-pill" :pressed="Boolean(city.active)" pressed-class="active" :data-location-id="city.id" @click="locationControls?.chooseCity(city.id)">{{ city.label }}</UiButton></div>
|
||||||
<div class="location-search-wrap">
|
<div class="location-search-wrap">
|
||||||
<button id="search-toggle" class="search-toggle-btn" title="Search any location" data-i18n-title>
|
<button id="search-toggle" class="search-toggle-btn" title="Search any location" data-i18n-title>
|
||||||
<span>🔎</span>
|
<span>🔎</span>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { t } from '../../../i18n/index.js';
|
import { t } from '../../../i18n/index.js';
|
||||||
import { ASSISTANT_NAME } from '../../../voice/control.js';
|
import { ASSISTANT_NAME } from '../../../voice/control.js';
|
||||||
|
import UiChip from '../../kit/UiChip.vue';
|
||||||
|
|
||||||
// Captions, plan and result, as VoiceCardControls (src/voice/voiceCard.js)
|
// Captions, plan and result, as VoiceCardControls (src/voice/voiceCard.js)
|
||||||
// presents them. Captions update silently; the polite live region carries
|
// presents them. Captions update silently; the polite live region carries
|
||||||
@@ -22,7 +23,7 @@ const emit = defineEmits(['dismiss']);
|
|||||||
<ol id="gev-voice-card-plan" class="gev-voice-card-plan" :aria-label="t('Plan')" :hidden="card.steps.length === 0"><li v-for="(step, index) in card.steps" :key="step.key ?? index" class="gev-voice-card-step" :data-status="step.status"><span class="gev-voice-card-step-label">{{ step.label }}</span><span class="gev-voice-card-sr">{{ step.statusText ? `, ${step.statusText}` : '' }}</span><span v-if="step.detail" class="gev-voice-card-step-detail">{{ step.detail }}</span></li></ol>
|
<ol id="gev-voice-card-plan" class="gev-voice-card-plan" :aria-label="t('Plan')" :hidden="card.steps.length === 0"><li v-for="(step, index) in card.steps" :key="step.key ?? index" class="gev-voice-card-step" :data-status="step.status"><span class="gev-voice-card-step-label">{{ step.label }}</span><span class="gev-voice-card-sr">{{ step.statusText ? `, ${step.statusText}` : '' }}</span><span v-if="step.detail" class="gev-voice-card-step-detail">{{ step.detail }}</span></li></ol>
|
||||||
<div id="gev-voice-card-result" class="gev-voice-card-result" :hidden="!card.result.visible">
|
<div id="gev-voice-card-result" class="gev-voice-card-result" :hidden="!card.result.visible">
|
||||||
<div id="gev-voice-card-result-title" class="gev-voice-card-result-title">{{ card.result.title }}</div>
|
<div id="gev-voice-card-result-title" class="gev-voice-card-result-title">{{ card.result.title }}</div>
|
||||||
<div id="gev-voice-card-chips" class="gev-voice-card-chips" :hidden="card.result.chips.length === 0"><span v-for="(chip, index) in card.result.chips" :key="index" class="gev-voice-card-chip" :data-tone="chip">{{ chip }}</span></div>
|
<div id="gev-voice-card-chips" class="gev-voice-card-chips" :hidden="card.result.chips.length === 0"><UiChip v-for="(chip, index) in card.result.chips" :key="index" class="gev-voice-card-chip" :data-tone="chip">{{ chip }}</UiChip></div>
|
||||||
<ul id="gev-voice-card-lines" class="gev-voice-card-lines"><li v-for="(line, index) in card.result.lines" :key="index">{{ line }}</li></ul>
|
<ul id="gev-voice-card-lines" class="gev-voice-card-lines"><li v-for="(line, index) in card.result.lines" :key="index">{{ line }}</li></ul>
|
||||||
<ol id="gev-voice-card-referents" class="gev-voice-card-referents" :aria-label="t('Referenced items')" :hidden="card.result.referents.length === 0"><li v-for="(ref, index) in card.result.referents" :key="index" :value="ref.n">{{ ref.label }}</li></ol>
|
<ol id="gev-voice-card-referents" class="gev-voice-card-referents" :aria-label="t('Referenced items')" :hidden="card.result.referents.length === 0"><li v-for="(ref, index) in card.result.referents" :key="index" :value="ref.n">{{ ref.label }}</li></ol>
|
||||||
<details id="gev-voice-card-notes" class="gev-voice-card-notes" :hidden="card.result.notes.length === 0">
|
<details id="gev-voice-card-notes" class="gev-voice-card-notes" :hidden="card.result.notes.length === 0">
|
||||||
|
|||||||
Reference in new issue
Block a user