Commit 293292e0 authored by Matthias Betz's avatar Matthias Betz
Browse files

show the mouse controls as icons with one word each



A small mouse icon with the used part filled (left button, right button,
wheel) and "Rotate", "Pan", "Zoom" replace the sentence-like hints; each
entry keeps the full description as aria-label for screen readers.

Co-Authored-By: default avatarClaude Opus 5.5 (1M context) <noreply@anthropic.com>
parent 4553c4a8
Pipeline #12497 passed with stages
in 14 seconds
......@@ -282,6 +282,22 @@ a {
pointer-events: none;
}
.viewer-controls li {
display: flex;
align-items: center;
gap: 5px;
}
.mouse-icon-outline {
fill: none;
stroke: var(--muted);
stroke-width: 1.2;
}
.mouse-icon-part {
fill: var(--accent);
}
.viewer-message {
padding: var(--space-4);
}
......
......@@ -9,6 +9,41 @@ export function viewerHints(state) {
return { empty: !hasGeometry && !loading, controls: hasGeometry };
}
const SVG = 'http://www.w3.org/2000/svg';
// Mouse controls: a mouse icon with the part that is used, one word, and the
// full sentence for screen readers.
const CONTROLS = [
{ part: 'left', text: 'Rotate', label: 'Rotate: drag with the left mouse button' },
{ part: 'right', text: 'Pan', label: 'Pan: drag with the right mouse button' },
{ part: 'wheel', text: 'Zoom', label: 'Zoom: turn the mouse wheel' },
];
// Shapes of the filled part, in a 12 x 16 mouse outline.
const PARTS = {
left: ['path', { d: 'M6 0.75 A5.25 5.25 0 0 0 0.75 6 V6.5 H6 Z' }],
right: ['path', { d: 'M6 0.75 A5.25 5.25 0 0 1 11.25 6 V6.5 H6 Z' }],
wheel: ['rect', { x: 5, y: 2.25, width: 2, height: 3.5, rx: 1 }],
};
function svgElement(name, attributes) {
const element = document.createElementNS(SVG, name);
for (const [key, value] of Object.entries(attributes)) {
element.setAttribute(key, String(value));
}
return element;
}
function mouseIcon(part) {
const icon = svgElement('svg', { viewBox: '0 0 12 16', width: 12, height: 16, 'aria-hidden': 'true', class: 'mouse-icon' });
const [shape, attributes] = PARTS[part];
icon.append(
svgElement(shape, { ...attributes, class: 'mouse-icon-part' }),
svgElement('rect', { x: 0.75, y: 0.75, width: 10.5, height: 14.5, rx: 5.25, class: 'mouse-icon-outline' }),
svgElement('path', { d: part === 'wheel' ? 'M0.75 6.5 H5 M7 6.5 H11.25' : 'M6 0.75 V6.5 M0.75 6.5 H11.25', class: 'mouse-icon-outline' }));
return icon;
}
export function mountViewerHints(container, store, { onOpen }) {
const empty = document.createElement('button');
empty.type = 'button';
......@@ -24,9 +59,10 @@ export function mountViewerHints(container, store, { onOpen }) {
const controls = document.createElement('ul');
controls.className = 'viewer-controls';
for (const text of ['Left drag rotates', 'Right drag pans', 'Wheel zooms']) {
for (const control of CONTROLS) {
const item = document.createElement('li');
item.textContent = text;
item.setAttribute('aria-label', control.label);
item.append(mouseIcon(control.part), control.text);
controls.append(item);
}
container.append(empty, controls);
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment