updraft / components /view-mode-controls.js
Nicholas Celestin
Build update — 2026-05-22T18:34:00.912Z
3f22414
Raw
History Blame Contribute Delete
3.2 kB
/**
* <view-mode-controls> — segmented icon-button radio for the canvas view mode.
*
* Renders one button per mode; the currently-selected mode is marked with
* `aria-pressed="true"`. Clicking a different button switches and emits
* `mode-change` { detail: { mode } }.
*
* Mode keys: 'fit-width' | 'fit-height' | 'one-to-one'.
*/
const VIEW_MODES = [
{ key: 'fit-width', label: 'Fit Width', icon: 'fa-arrows-left-right' },
{ key: 'fit-height', label: 'Fit Height', icon: 'fa-arrows-up-down' },
{ key: 'one-to-one', label: '1:1', icon: 'fa-vector-square' },
];
class ViewModeControls extends HTMLElement {
#mode = 'fit-width';
connectedCallback() {
this.#render();
this.addEventListener('click', this.#onClick);
}
get mode() { return this.#mode; }
set mode(value) {
if (!VIEW_MODES.find(m => m.key === value)) return;
if (this.#mode === value) return;
this.#mode = value;
this.#render();
}
#onClick = (e) => {
const btn = e.target.closest('button[data-mode]');
if (!btn) return;
e.stopPropagation();
if (btn.dataset.mode === this.#mode) return;
this.#mode = btn.dataset.mode;
this.#render();
this.dispatchEvent(new CustomEvent('mode-change', { detail: { mode: this.#mode } }));
};
#render() {
const buttons = VIEW_MODES.map(m => `
<button type="button" class="secondary outline" data-mode="${m.key}"
aria-pressed="${m.key === this.#mode}"
title="${m.label}" aria-label="${m.label}">
<i class="fas ${m.icon}"></i>
</button>
`).join('');
this.innerHTML = `
<style>
view-mode-controls {
display: inline-flex;
vertical-align: middle;
}
view-mode-controls .vm-row {
display: inline-flex;
gap: 0;
}
view-mode-controls .vm-row > button {
border-radius: 0;
}
view-mode-controls .vm-row > button:first-child {
border-top-left-radius: var(--pico-border-radius);
border-bottom-left-radius: var(--pico-border-radius);
}
view-mode-controls .vm-row > button:last-child {
border-top-right-radius: var(--pico-border-radius);
border-bottom-right-radius: var(--pico-border-radius);
}
view-mode-controls .vm-row > button:not(:first-child) {
margin-left: -1px;
}
view-mode-controls button .fas {
margin-right: 0 !important;
}
/* Pressed state: disable the host toolbar's mix-blend-mode trick and
paint a solid filled background so the active mode is unambiguous
against the surrounding outline buttons. */
view-mode-controls button[aria-pressed="true"] {
mix-blend-mode: normal !important;
background: rgba(255, 255, 255, 0.22) !important;
opacity: 1 !important;
border-color: #fff !important;
color: #fff !important;
z-index: 1;
}
</style>
<div class="vm-row" role="radiogroup" aria-label="View mode">
${buttons}
</div>
`;
}
}
customElements.define('view-mode-controls', ViewModeControls);