diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6f276b151..d32717dbd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -65,7 +65,10 @@
what the answering action returned. The Display, Radio, RTL-SDR, Cockpit,
mission replay and style parameter sliders are the kit's `UiSlider`, a
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
deprecated: the Display view and the rail now own what they did. `./ui/input` imports Vue
(samehkhamis).
diff --git a/docs/VUE-MIGRATION.md b/docs/VUE-MIGRATION.md
index 0aeec0ac2..d5fbc63fa 100644
--- a/docs/VUE-MIGRATION.md
+++ b/docs/VUE-MIGRATION.md
@@ -61,7 +61,10 @@ adapter is gone.
`active`); its pressed classes toggle through `classList`, so a class other
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
-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
`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
diff --git a/scripts/format-scope.json b/scripts/format-scope.json
index d8814419c..57f479111 100644
--- a/scripts/format-scope.json
+++ b/scripts/format-scope.json
@@ -1132,6 +1132,9 @@
"src/ui/kit/UiSlider.test.mjs",
"src/ui/kit/UiButton.vue",
"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/testSupport/vueDom.mjs",
"build/vueLoader.js",
diff --git a/scripts/package-boundaries.json b/scripts/package-boundaries.json
index 8d5c468db..fb68740ae 100644
--- a/scripts/package-boundaries.json
+++ b/scripts/package-boundaries.json
@@ -134,7 +134,8 @@
"src/ui/rail/PanelCollapseButton.vue",
"src/ui/rail/panelState.js",
"src/sdr/gain.js",
- "src/ui/kit/UiSlider.js"
+ "src/ui/kit/UiSlider.js",
+ "src/ui/kit/UiChip.vue"
],
"external": ["vue", "pinia"]
},
@@ -667,7 +668,8 @@
"src/ui/rail/utcClock.js",
"src/ui/rail/RailTimeline.vue",
"src/ui/rail/rangeValue.js",
- "src/ui/weather/WeatherReadout.vue"
+ "src/ui/weather/WeatherReadout.vue",
+ "src/ui/kit/UiButton.vue"
],
"external": ["vue"]
},
@@ -1066,7 +1068,8 @@
"src/ui/stores/panelChrome.js",
"src/ui/rail/panelState.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"]
},
@@ -3076,7 +3079,8 @@
"src/ui/stores/panelChrome.js",
"src/ui/rail/panelState.js",
"src/ui/rail/fieldValue.js",
- "src/ui/kit/UiSlider.js"
+ "src/ui/kit/UiSlider.js",
+ "src/ui/kit/UiButton.vue"
],
"external": [
"hls.js",
diff --git a/src/data/managerPanel.test.mjs b/src/data/managerPanel.test.mjs
index 51cfd3d23..d8b089255 100644
--- a/src/data/managerPanel.test.mjs
+++ b/src/data/managerPanel.test.mjs
@@ -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' });
manager._refreshTogglePanel();
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',
diff --git a/src/ui/kit/UiChip.test.mjs b/src/ui/kit/UiChip.test.mjs
new file mode 100644
index 000000000..68253212e
--- /dev/null
+++ b/src/ui/kit/UiChip.test.mjs
@@ -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();
+});
diff --git a/src/ui/kit/UiChip.vue b/src/ui/kit/UiChip.vue
new file mode 100644
index 000000000..f2d590a7e
--- /dev/null
+++ b/src/ui/kit/UiChip.vue
@@ -0,0 +1,19 @@
+
+
+
+
+