diff --git a/scripts/package-boundaries.json b/scripts/package-boundaries.json
index 98e7a716b..84cec6de3 100644
--- a/scripts/package-boundaries.json
+++ b/scripts/package-boundaries.json
@@ -552,6 +552,7 @@
"src/ui/railCardBlocks.js",
"src/ui/layerRowText.js",
"src/ui/layerPanel/LayerRows.vue",
+ "src/ui/rail/classToggle.js",
"src/ui/layerPanel/RowText.vue",
"src/ui/railTimeline.js",
"src/ui/clearLayersControl.js",
@@ -857,6 +858,7 @@
"src/ui/railCardBlocks.js",
"src/ui/layerRowText.js",
"src/ui/layerPanel/LayerRows.vue",
+ "src/ui/rail/classToggle.js",
"src/ui/layerPanel/RowText.vue",
"src/ui/railTimeline.js",
"src/ui/layers.js",
@@ -2553,6 +2555,7 @@
"src/ui/railCardBlocks.js",
"src/ui/layerRowText.js",
"src/ui/layerPanel/LayerRows.vue",
+ "src/ui/rail/classToggle.js",
"src/ui/layerPanel/RowText.vue",
"src/ui/railTimeline.js",
"src/ui/layers.js",
diff --git a/src/ui/layerPanel/LayerRows.vue b/src/ui/layerPanel/LayerRows.vue
index e3897ce30..f404938b6 100644
--- a/src/ui/layerPanel/LayerRows.vue
+++ b/src/ui/layerPanel/LayerRows.vue
@@ -2,6 +2,7 @@
import RailChips from '../rail/RailChips.vue';
import RailRowList from '../rail/RailRowList.vue';
import RowText from './RowText.vue';
+import { vClassToggle } from '../rail/classToggle.js';
// The data layer rows under their group headings. LayerPanel presents every
// value; clicks reach it with the layer id, and it re-reads the live
@@ -21,10 +22,10 @@ defineProps({
}}
({
+ key: 'flights',
+ id: 'flights',
+ englishName: 'Flights',
+ icon: 'flight',
+ name: 'Flights',
+ meta: '',
+ toggle: {
+ classes: [],
+ text: 'OFF',
+ ariaLabel: 'Flights',
+ title: '',
+ ariaBusy: 'false',
+ ariaDisabled: 'false',
+ feedState: '',
+ },
+ count: { countLine, text: '12', empty: false },
+ controls: null,
+});
+
+test('the count-line class toggles without dropping the voice highlight', () => {
+ document.body.replaceChildren();
+ const container = document.body.appendChild(document.createElement('div'));
+ render(h(LayerRows, { items: [row(false)] }), container);
+ const element = container.querySelector('[data-layer-id="flights"]');
+ assert.equal(element.className, 'data-toggle-row');
+ element.classList.add('gev-voice-focus');
+
+ render(h(LayerRows, { items: [row(true)] }), container);
+ assert.equal(element.classList.contains('data-toggle-row--count-line'), true);
+ assert.equal(element.classList.contains('gev-voice-focus'), true);
+
+ render(h(LayerRows, { items: [row(false)] }), container);
+ assert.equal(element.classList.contains('data-toggle-row--count-line'), false);
+ assert.equal(element.classList.contains('gev-voice-focus'), true);
+ assert.equal(element.classList.contains('data-toggle-row'), true);
+ render(null, container);
+});
diff --git a/src/ui/rail/classToggle.js b/src/ui/rail/classToggle.js
new file mode 100644
index 000000000..53383691f
--- /dev/null
+++ b/src/ui/rail/classToggle.js
@@ -0,0 +1,11 @@
+/**
+ * `v-class-toggle`: toggles each named class with `classList`, so classes other
+ * code adds to the element (a voice highlight) survive a re-render. A `:class`
+ * binding rewrites the whole `className` instead.
+ */
+const apply = (el, { value }) => {
+ for (const [name, on] of Object.entries(value || {}))
+ if (el.classList.contains(name) !== Boolean(on))
+ el.classList.toggle(name, Boolean(on));
+};
+export const vClassToggle = { mounted: apply, updated: apply };