Component demo · Instrument readouts and controls
Selector Switch
index.htm as it is written with the smart-industrial package
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<title>Selector Switch Overview Demo</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" type="text/css" href="node_modules/smart-industrial/source/styles/smart.default.css" />
<link rel="stylesheet" type="text/css" href="node_modules/smart-industrial/source/styles/smart.industrial-elements.css" />
<link rel="stylesheet" type="text/css" href="node_modules/smart-industrial/source/styles/smart.industrial.css" />
<link rel="stylesheet" type="text/css" href="node_modules/smart-industrial/source/styles/smart.industrial-dark.css" />
<link rel="stylesheet" type="text/css" href="styles.css" />
</head>
<body class="viewport" theme="industrial">
<div class="smart-demo-container">
<section id="overview">
<h2>Smart.SelectorSwitch is a switch with a set of named positions, such as a Hand-Off-Auto selector. A press on a position is a request, and the position shown is the one confirmed by the plant.</h2>
<p>
Switching a pump from Auto to Hand changes who controls it, so the switch raises the <code>positionRequest</code> event and shows the requested position as pending until the application reports the value the plant accepted. With the <em>confirm</em> property enabled, the position has to be pressed twice. A complete control station with a selector, start, stop and jog buttons is shown on the <a href="../../momentarybutton/overview/index.htm">Momentary Button</a> page.
</p>
</section>
<section>
<h2>A pump control station</h2>
<p>
Press a position. The switch shows it as pending, the simulated plant answers after a moment, and the switch takes the position the plant reports. P-2101B has <code>confirm</code> set, so its rotary selector arms on the first press and requests on the second. The duty selector has three positions with descriptions, which are read by screen readers and shown as tooltips.
</p>
<div class="station">
<div class="station-title">Feed pumps P-2101</div>
<div class="station-body">
<div class="switches">
<smart-selector-switch id="segmented" label="P-2101A control" value="auto"></smart-selector-switch>
<smart-selector-switch id="rotary" label="P-2101B control" value="off" appearance="rotary" confirm></smart-selector-switch>
<smart-selector-switch id="duty" label="Duty pump" value="a"></smart-selector-switch>
</div>
<div class="plant">
<div class="plant-title">Plant</div>
<div class="plant-row"><span>P-2101A</span><b id="plantA">Auto, sequence in control</b></div>
<div class="plant-row"><span>P-2101B</span><b id="plantB">Off</b></div>
<div class="plant-row"><span>Duty</span><b id="plantDuty">P-2101A duty, B standby</b></div>
<div class="demo-actions">
<label><input type="checkbox" id="refuse" /> Plant refuses the next request</label>
<label><input type="checkbox" id="touch" /> Touch density</label>
</div>
</div>
</div>
</div>
<ol id="log" class="log"><li>Press a position to raise a request.</li></ol>
<p class="hint">Keyboard: each switch is a radio group. Tab to it, use the arrow keys to move between positions and Enter or Space to request one. A pending position and a refusal are announced to screen readers.</p>
</section>
<section>
<h2>Toggle, rocker and slide</h2>
<p>
The switch a panel would carry, drawn above the same row of positions: a toggle lever that leans to its position, a rocker whose pressed face is lit, and a slide whose knob sits at its position. The row of positions stays the control that is pressed and read; the drawing follows the position the plant reports, and shows a pending request in the pending colour. These are the toggle switch, rocker and slide switch of a test-and-measurement front panel with the request model of the line.
</p>
<div class="row">
<smart-selector-switch id="toggleSwitch" label="Purge air" value="off" appearance="toggle"></smart-selector-switch>
<smart-selector-switch id="rockerSwitch" label="Lighting" value="on" appearance="rocker"></smart-selector-switch>
<smart-selector-switch id="slideSwitch" label="Range" value="mid" appearance="slide"></smart-selector-switch>
<smart-selector-switch id="toggle3" label="Agitator" value="off" appearance="toggle"></smart-selector-switch>
</div>
<p class="hint" id="note3">Press a position; the plant confirms after a moment.</p>
</section>
<section>
<h2>Interlocked, and a plant that does not answer</h2>
<p>
An interlocked switch stays readable, shows the reason and raises the <code>blocked</code> event when pressed instead of a request. A request that the plant never answers is dropped after <code>pendingTimeout</code>, and the switch returns to the position it had.
</p>
<div class="row">
<smart-selector-switch id="locked" label="Sample valve" value="closed" interlocked interlock-reason="line under pressure"></smart-selector-switch>
<smart-selector-switch id="silent" label="Recirculation" value="off" pending-timeout="2500"></smart-selector-switch>
<smart-selector-switch id="disabledSwitch" label="Spare" value="off" disabled></smart-selector-switch>
</div>
<p class="hint" id="note2">The recirculation selector is wired to nothing: request a position and watch it drop back after 2.5 s.</p>
</section>
</div>
<!-- scripts -->
<script type="module" src="node_modules/smart-industrial/source/modules/smart.selectorswitch.js"></script>
<script type="module" src="index.js"></script>
<script>
/* Shown once every element has rendered, so nothing moves on the way in (styles/demos.css). */
addEventListener('load', function () {
var all = Array.prototype.slice.call(document.querySelectorAll('*')),
ready = function () { document.body.classList.add('ready'); };
Promise.all(all.map(function (e) { return e.localName.indexOf('smart-') === 0 ? customElements.whenDefined(e.localName) : null; })).then(function () {
return Promise.all(all.filter(function (e) { return typeof e.whenRendered === 'function' && !e.isRendered; }).map(function (e) { return e.whenRendered(); }));
}).then(function () { requestAnimationFrame(function () { requestAnimationFrame(ready); }); }, ready);
});
</script>
</body>
</html>
index.js
/// <reference path="../../../source/typescript/smart.elements.d.ts" />
/* Each switch raises a request; a simulated plant answers after a moment, and the switch
shows the position it answers with. */
window.onload = function () {
const log = document.getElementById('log'),
refuse = document.getElementById('refuse'),
touch = document.getElementById('touch'),
plantA = document.getElementById('plantA'),
plantB = document.getElementById('plantB'),
plantDuty = document.getElementById('plantDuty');
function note(text) {
const item = document.createElement('li');
item.textContent = new Date().toLocaleTimeString('en-GB', { hour12: false }) + ' ' + text;
if (log.firstChild && log.firstChild.textContent.indexOf('Press a position') === 0) {
log.removeChild(log.firstChild);
}
log.insertBefore(item, log.firstChild);
while (log.children.length > 8) {
log.removeChild(log.lastChild);
}
}
const duty = document.getElementById('duty');
duty.positions = [
{ id: 'a', label: 'Pump A', description: 'P-2101A duty, B standby' },
{ id: 'b', label: 'Pump B', description: 'P-2101B duty, A standby' },
{ id: 'both', label: 'Both', description: 'Both pumps in service' }
];
/* What the plant shows once it has taken a position. */
const MODE_TEXT = {
hand: 'Hand, operator in control',
off: 'Off',
auto: 'Auto, sequence in control'
};
function reflect(id, position) {
if (id === 'segmented') {
plantA.textContent = MODE_TEXT[position] || position;
}
else if (id === 'rotary') {
plantB.textContent = MODE_TEXT[position] || position;
}
else if (id === 'duty') {
const chosen = duty.positions.filter((p) => p.id === position)[0];
plantDuty.textContent = chosen ? chosen.description : position;
}
}
/* The station: a request goes to the plant, which confirms after 900 ms, or refuses
once when the checkbox is set. A refusal leaves the switch where it was. */
['segmented', 'rotary', 'duty'].forEach(function (id) {
const element = document.getElementById(id);
element.addEventListener('positionRequest', function (event) {
note(element.label + ': requested ' + event.detail.position);
if (refuse.checked) {
refuse.checked = false;
setTimeout(function () {
element.value = event.detail.from;
note(element.label + ': plant refused, stays in ' + event.detail.from);
}, 900);
return;
}
setTimeout(function () {
element.value = event.detail.position;
reflect(id, event.detail.position);
note(element.label + ': plant confirms ' + event.detail.position);
}, 900);
});
});
touch.addEventListener('change', function () {
['segmented', 'rotary', 'duty'].forEach(function (id) {
document.getElementById(id).density = touch.checked ? 'touch' : 'normal';
});
});
/* ── Toggle, rocker and slide ─────────────────────────────────────── */
const note3 = document.getElementById('note3'),
toggleSwitch = document.getElementById('toggleSwitch'),
rockerSwitch = document.getElementById('rockerSwitch'),
slideSwitch = document.getElementById('slideSwitch'),
toggle3 = document.getElementById('toggle3');
toggleSwitch.positions = [{ id: 'off', label: 'Off' }, { id: 'on', label: 'On' }];
rockerSwitch.positions = [{ id: 'off', label: 'O' }, { id: 'on', label: 'I' }];
slideSwitch.positions = [{ id: 'low', label: 'Low' }, { id: 'mid', label: 'Mid' }, { id: 'high', label: 'High' }];
toggle3.positions = [{ id: 'rev', label: 'Rev' }, { id: 'off', label: 'Off' }, { id: 'fwd', label: 'Fwd' }];
[toggleSwitch, rockerSwitch, slideSwitch, toggle3].forEach(function (element) {
element.addEventListener('positionRequest', function (event) {
note3.textContent = element.label + ': ' + event.detail.position + ' requested.';
setTimeout(function () {
element.value = event.detail.position;
note3.textContent = element.label + ': the plant confirms ' + event.detail.position + '.';
}, 700);
});
});
/* ── Interlocked, and a plant that does not answer ─────────────────── */
const locked = document.getElementById('locked'),
silent = document.getElementById('silent'),
note2 = document.getElementById('note2');
locked.positions = [
{ id: 'closed', label: 'Closed' },
{ id: 'open', label: 'Open' }
];
locked.addEventListener('blocked', function (event) {
note2.textContent = 'Sample valve: blocked, ' + event.detail.reason + '.';
});
silent.positions = [
{ id: 'off', label: 'Off' },
{ id: 'on', label: 'On' }
];
silent.addEventListener('positionRequest', function (event) {
note2.textContent = 'Recirculation: ' + event.detail.position + ' requested. Nothing answers, so the request is dropped after 2.5 s.';
setTimeout(function () {
note2.textContent = 'Recirculation: no answer, the request was dropped. The switch still shows ' + silent.value + '.';
}, 2600);
});
};
styles.css
.row { display: flex; flex-wrap: wrap; gap: 30px; align-items: flex-start; }
/* The control station: a surface with the switches on the left and the plant on the right. */
.station {
display: inline-block;
max-width: 100%;
border: 1px solid var(--smart-border);
border-radius: var(--smart-border-radius);
background: var(--smart-industrial-panel-background, var(--smart-surface));
}
.station-title {
padding: 8px 16px;
border-bottom: 1px solid var(--smart-border);
background: var(--smart-industrial-panel-header-background, var(--smart-base-200));
font-size: 11px;
font-weight: 600;
letter-spacing: .08em;
text-transform: uppercase;
color: var(--smart-text-secondary-color);
}
.station-body {
display: flex;
flex-wrap: wrap;
gap: 20px 40px;
padding: 18px 22px 16px;
}
.switches {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
gap: 16px 30px;
}
.plant {
min-width: 280px;
padding-left: 24px;
border-left: 1px solid var(--smart-border);
font-size: 13px;
}
.plant-title {
margin-bottom: 8px;
font-size: 11px;
font-weight: 600;
letter-spacing: .08em;
text-transform: uppercase;
color: var(--smart-text-secondary-color);
}
.plant-row {
display: flex;
justify-content: space-between;
gap: 16px;
padding: 5px 0;
border-bottom: 1px solid var(--smart-border);
}
.plant-row span {
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
color: var(--smart-text-secondary-color);
}
.plant-row b {
font-weight: 600;
}
/* ── Demo chrome, shared by the Industrial demos ─────────────────────────
Headings, prose, buttons and notes are the same on every demo page. This block
is appended by scripts and comes last, so it wins over the rules above. */
.smart-demo-container {
padding: 4px 0 32px;
}
.smart-demo-container section {
margin-bottom: 36px;
}
.smart-demo-container section h2 {
margin: 0 0 8px;
font-size: 17px;
font-weight: 600;
line-height: 1.35;
color: var(--smart-text-color);
}
.smart-demo-container #overview h2 {
margin-bottom: 10px;
font-size: 20px;
max-width: 78ch;
}
.smart-demo-container section > p {
margin: 0 0 14px;
max-width: 84ch;
font-size: 14px;
line-height: 1.55;
color: var(--smart-text-secondary-color);
}
.smart-demo-container code {
padding: 1px 5px;
border-radius: 2px;
background: var(--smart-ui-state-hover);
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
}
.smart-demo-container .demo-actions {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px 10px;
margin-top: 12px;
}
.smart-demo-container .demo-actions label {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 13px;
color: var(--smart-text-color);
}
.smart-demo-container .demo-actions button,
.smart-demo-container .demo-button {
min-height: 30px;
padding: 0 12px;
border: 1px solid var(--smart-border);
border-radius: 3px;
background: var(--smart-industrial-panel-background, var(--smart-surface));
color: var(--smart-text-color);
font: inherit;
font-size: 13px;
font-weight: 500;
letter-spacing: 0;
text-transform: none;
cursor: pointer;
}
.smart-demo-container .demo-actions button:hover,
.smart-demo-container .demo-button:hover {
border-color: var(--smart-ui-state-border-hover);
background: var(--smart-ui-state-hover);
color: var(--smart-ui-state-color-hover);
}
.smart-demo-container .demo-actions button.demo-button-primary,
.smart-demo-container .demo-button-primary {
border-color: var(--smart-primary);
background: var(--smart-primary);
color: var(--smart-primary-color, #fff);
}
.smart-demo-container .demo-actions button[disabled],
.smart-demo-container .demo-button[disabled] {
opacity: 0.5;
cursor: default;
}
.smart-demo-container .demo-actions select,
.smart-demo-container .demo-actions input[type="text"],
.smart-demo-container .demo-actions input[type="number"] {
min-height: 30px;
padding: 2px 6px;
border: 1px solid var(--smart-border);
border-radius: 3px;
background: var(--smart-background);
color: var(--smart-text-color);
font: inherit;
font-size: 13px;
}
.smart-demo-container .hint,
.smart-demo-container .demo-log,
.smart-demo-container .demo-counts {
margin: 10px 0 0;
font-size: 12px;
line-height: 1.5;
color: var(--smart-text-secondary-color);
opacity: 1;
}
.smart-demo-container .demo-log,
.smart-demo-container .demo-counts {
font-family: Consolas, "Courier New", monospace;
}
.smart-demo-container .log {
list-style: none;
margin: 10px 0 0;
padding: 0;
font-size: 12px;
font-variant-numeric: tabular-nums;
color: var(--smart-text-secondary-color);
opacity: 1;
}
.smart-demo-container .log li {
padding: 2px 0;
}
The same demo in Angular, generated from the HTML demo. The frameworks guide has the project setup.
angular/app.component.html
<div class="angular-root" theme="industrial">
<div class="smart-demo-container">
<section id="overview">
<h2>Smart.SelectorSwitch is a switch with a set of named positions, such as a Hand-Off-Auto selector. A press on a position is a request, and the position shown is the one confirmed by the plant.</h2>
<p>
Switching a pump from Auto to Hand changes who controls it, so the switch raises the <code>positionRequest</code> event and shows the requested position as pending until the application reports the value the plant accepted. With the <em>confirm</em> property enabled, the position has to be pressed twice. A complete control station with a selector, start, stop and jog buttons is shown on the <a href="../../momentarybutton/overview/index.htm">Momentary Button</a> page.
</p>
</section>
<section>
<h2>A pump control station</h2>
<p>
Press a position. The switch shows it as pending, the simulated plant answers after a moment, and the switch takes the position the plant reports. P-2101B has <code>confirm</code> set, so its rotary selector arms on the first press and requests on the second. The duty selector has three positions with descriptions, which are read by screen readers and shown as tooltips.
</p>
<div class="station">
<div class="station-title">Feed pumps P-2101</div>
<div class="station-body">
<div class="switches">
<smart-selector-switch id="segmented" label="P-2101A control" value="auto"></smart-selector-switch>
<smart-selector-switch id="rotary" label="P-2101B control" value="off" appearance="rotary" [confirm]="true"></smart-selector-switch>
<smart-selector-switch id="duty" label="Duty pump" value="a"></smart-selector-switch>
</div>
<div class="plant">
<div class="plant-title">Plant</div>
<div class="plant-row"><span>P-2101A</span><b id="plantA">Auto, sequence in control</b></div>
<div class="plant-row"><span>P-2101B</span><b id="plantB">Off</b></div>
<div class="plant-row"><span>Duty</span><b id="plantDuty">P-2101A duty, B standby</b></div>
<div class="demo-actions">
<label><input type="checkbox" id="refuse" /> Plant refuses the next request</label>
<label><input type="checkbox" id="touch" /> Touch density</label>
</div>
</div>
</div>
</div>
<ol id="log" class="log"><li>Press a position to raise a request.</li></ol>
<p class="hint">Keyboard: each switch is a radio group. Tab to it, use the arrow keys to move between positions and Enter or Space to request one. A pending position and a refusal are announced to screen readers.</p>
</section>
<section>
<h2>Toggle, rocker and slide</h2>
<p>
The switch a panel would carry, drawn above the same row of positions: a toggle lever that leans to its position, a rocker whose pressed face is lit, and a slide whose knob sits at its position. The row of positions stays the control that is pressed and read; the drawing follows the position the plant reports, and shows a pending request in the pending colour. These are the toggle switch, rocker and slide switch of a test-and-measurement front panel with the request model of the line.
</p>
<div class="row">
<smart-selector-switch id="toggleSwitch" label="Purge air" value="off" appearance="toggle"></smart-selector-switch>
<smart-selector-switch id="rockerSwitch" label="Lighting" value="on" appearance="rocker"></smart-selector-switch>
<smart-selector-switch id="slideSwitch" label="Range" value="mid" appearance="slide"></smart-selector-switch>
<smart-selector-switch id="toggle3" label="Agitator" value="off" appearance="toggle"></smart-selector-switch>
</div>
<p class="hint" id="note3">Press a position; the plant confirms after a moment.</p>
</section>
<section>
<h2>Interlocked, and a plant that does not answer</h2>
<p>
An interlocked switch stays readable, shows the reason and raises the <code>blocked</code> event when pressed instead of a request. A request that the plant never answers is dropped after <code>pendingTimeout</code>, and the switch returns to the position it had.
</p>
<div class="row">
<smart-selector-switch id="locked" label="Sample valve" value="closed" [interlocked]="true" interlock-reason="line under pressure"></smart-selector-switch>
<smart-selector-switch id="silent" label="Recirculation" value="off" [pendingTimeout]="2500"></smart-selector-switch>
<smart-selector-switch id="disabledSwitch" label="Spare" value="off" [disabled]="true"></smart-selector-switch>
</div>
<p class="hint" id="note2">The recirculation selector is wired to nothing: request a position and watch it drop back after 2.5 s.</p>
</section>
</div>
</div>
angular/app.component.ts
import { Component, AfterViewInit, OnDestroy } from '@angular/core';
import { SelectorSwitchModule } from 'smart-industrial/angular/selectorswitch';
import { runDemo } from './demo';
/**
* Smart.UI Industrial - SelectorSwitch in Angular.
*
* The template is the HTML demo's markup: the elements are the wrappers, so what is
* written on them is checked against their inputs. demo.ts drives them the way the
* plant's data would, once every element in the view has rendered.
*/
@Component({
selector: 'app-root',
standalone: true,
imports: [SelectorSwitchModule],
templateUrl: './app.component.html',
styleUrl: './app.component.css'
})
export class AppComponent implements AfterViewInit, OnDestroy {
private unmounted = false;
ngAfterViewInit(): void {
const root = document.querySelector('.angular-root') as HTMLElement,
pending: Promise<void>[] = [];
root.querySelectorAll('*').forEach((element: any) => {
if (typeof element.whenRendered === 'function' && !element.isRendered) {
pending.push(element.whenRendered());
}
});
Promise.all(pending).then(() => {
if (!this.unmounted) {
runDemo();
}
});
}
ngOnDestroy(): void {
this.unmounted = true;
}
}
angular/demo.ts
// @ts-nocheck
/* The HTML demo's script, as it was written: page code that feeds the elements, not a typed
API. The types are on the wrappers in app.component.ts. */
/* Each switch raises a request; a simulated plant answers after a moment, and the switch
shows the position it answers with. */
export function runDemo(): void {
const log = document.getElementById('log'),
refuse = document.getElementById('refuse'),
touch = document.getElementById('touch'),
plantA = document.getElementById('plantA'),
plantB = document.getElementById('plantB'),
plantDuty = document.getElementById('plantDuty');
function note(text) {
const item = document.createElement('li');
item.textContent = new Date().toLocaleTimeString('en-GB', { hour12: false }) + ' ' + text;
if (log.firstChild && log.firstChild.textContent.indexOf('Press a position') === 0) {
log.removeChild(log.firstChild);
}
log.insertBefore(item, log.firstChild);
while (log.children.length > 8) {
log.removeChild(log.lastChild);
}
}
const duty = document.getElementById('duty');
duty.positions = [
{ id: 'a', label: 'Pump A', description: 'P-2101A duty, B standby' },
{ id: 'b', label: 'Pump B', description: 'P-2101B duty, A standby' },
{ id: 'both', label: 'Both', description: 'Both pumps in service' }
];
/* What the plant shows once it has taken a position. */
const MODE_TEXT = {
hand: 'Hand, operator in control',
off: 'Off',
auto: 'Auto, sequence in control'
};
function reflect(id, position) {
if (id === 'segmented') {
plantA.textContent = MODE_TEXT[position] || position;
}
else if (id === 'rotary') {
plantB.textContent = MODE_TEXT[position] || position;
}
else if (id === 'duty') {
const chosen = duty.positions.filter((p) => p.id === position)[0];
plantDuty.textContent = chosen ? chosen.description : position;
}
}
/* The station: a request goes to the plant, which confirms after 900 ms, or refuses
once when the checkbox is set. A refusal leaves the switch where it was. */
['segmented', 'rotary', 'duty'].forEach(function (id) {
const element = document.getElementById(id);
element.addEventListener('positionRequest', function (event) {
note(element.label + ': requested ' + event.detail.position);
if (refuse.checked) {
refuse.checked = false;
setTimeout(function () {
element.value = event.detail.from;
note(element.label + ': plant refused, stays in ' + event.detail.from);
}, 900);
return;
}
setTimeout(function () {
element.value = event.detail.position;
reflect(id, event.detail.position);
note(element.label + ': plant confirms ' + event.detail.position);
}, 900);
});
});
touch.addEventListener('change', function () {
['segmented', 'rotary', 'duty'].forEach(function (id) {
document.getElementById(id).density = touch.checked ? 'touch' : 'normal';
});
});
/* ── Toggle, rocker and slide ─────────────────────────────────────── */
const note3 = document.getElementById('note3'),
toggleSwitch = document.getElementById('toggleSwitch'),
rockerSwitch = document.getElementById('rockerSwitch'),
slideSwitch = document.getElementById('slideSwitch'),
toggle3 = document.getElementById('toggle3');
toggleSwitch.positions = [{ id: 'off', label: 'Off' }, { id: 'on', label: 'On' }];
rockerSwitch.positions = [{ id: 'off', label: 'O' }, { id: 'on', label: 'I' }];
slideSwitch.positions = [{ id: 'low', label: 'Low' }, { id: 'mid', label: 'Mid' }, { id: 'high', label: 'High' }];
toggle3.positions = [{ id: 'rev', label: 'Rev' }, { id: 'off', label: 'Off' }, { id: 'fwd', label: 'Fwd' }];
[toggleSwitch, rockerSwitch, slideSwitch, toggle3].forEach(function (element) {
element.addEventListener('positionRequest', function (event) {
note3.textContent = element.label + ': ' + event.detail.position + ' requested.';
setTimeout(function () {
element.value = event.detail.position;
note3.textContent = element.label + ': the plant confirms ' + event.detail.position + '.';
}, 700);
});
});
/* ── Interlocked, and a plant that does not answer ─────────────────── */
const locked = document.getElementById('locked'),
silent = document.getElementById('silent'),
note2 = document.getElementById('note2');
locked.positions = [
{ id: 'closed', label: 'Closed' },
{ id: 'open', label: 'Open' }
];
locked.addEventListener('blocked', function (event) {
note2.textContent = 'Sample valve: blocked, ' + event.detail.reason + '.';
});
silent.positions = [
{ id: 'off', label: 'Off' },
{ id: 'on', label: 'On' }
];
silent.addEventListener('positionRequest', function (event) {
note2.textContent = 'Recirculation: ' + event.detail.position + ' requested. Nothing answers, so the request is dropped after 2.5 s.';
setTimeout(function () {
note2.textContent = 'Recirculation: no answer, the request was dropped. The switch still shows ' + silent.value + '.';
}, 2600);
});
}
angular/app.component.css
.row { display: flex; flex-wrap: wrap; gap: 30px; align-items: flex-start; }
/* The control station: a surface with the switches on the left and the plant on the right. */
.station {
display: inline-block;
max-width: 100%;
border: 1px solid var(--smart-border);
border-radius: var(--smart-border-radius);
background: var(--smart-industrial-panel-background, var(--smart-surface));
}
.station-title {
padding: 8px 16px;
border-bottom: 1px solid var(--smart-border);
background: var(--smart-industrial-panel-header-background, var(--smart-base-200));
font-size: 11px;
font-weight: 600;
letter-spacing: .08em;
text-transform: uppercase;
color: var(--smart-text-secondary-color);
}
.station-body {
display: flex;
flex-wrap: wrap;
gap: 20px 40px;
padding: 18px 22px 16px;
}
.switches {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
gap: 16px 30px;
}
.plant {
min-width: 280px;
padding-left: 24px;
border-left: 1px solid var(--smart-border);
font-size: 13px;
}
.plant-title {
margin-bottom: 8px;
font-size: 11px;
font-weight: 600;
letter-spacing: .08em;
text-transform: uppercase;
color: var(--smart-text-secondary-color);
}
.plant-row {
display: flex;
justify-content: space-between;
gap: 16px;
padding: 5px 0;
border-bottom: 1px solid var(--smart-border);
}
.plant-row span {
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
color: var(--smart-text-secondary-color);
}
.plant-row b {
font-weight: 600;
}
/* ── Demo chrome, shared by the Industrial demos ─────────────────────────
Headings, prose, buttons and notes are the same on every demo page. This block
is appended by scripts and comes last, so it wins over the rules above. */
.smart-demo-container {
padding: 4px 0 32px;
}
.smart-demo-container section {
margin-bottom: 36px;
}
.smart-demo-container section h2 {
margin: 0 0 8px;
font-size: 17px;
font-weight: 600;
line-height: 1.35;
color: var(--smart-text-color);
}
.smart-demo-container #overview h2 {
margin-bottom: 10px;
font-size: 20px;
max-width: 78ch;
}
.smart-demo-container section > p {
margin: 0 0 14px;
max-width: 84ch;
font-size: 14px;
line-height: 1.55;
color: var(--smart-text-secondary-color);
}
.smart-demo-container code {
padding: 1px 5px;
border-radius: 2px;
background: var(--smart-ui-state-hover);
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
}
.smart-demo-container .demo-actions {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px 10px;
margin-top: 12px;
}
.smart-demo-container .demo-actions label {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 13px;
color: var(--smart-text-color);
}
.smart-demo-container .demo-actions button,
.smart-demo-container .demo-button {
min-height: 30px;
padding: 0 12px;
border: 1px solid var(--smart-border);
border-radius: 3px;
background: var(--smart-industrial-panel-background, var(--smart-surface));
color: var(--smart-text-color);
font: inherit;
font-size: 13px;
font-weight: 500;
letter-spacing: 0;
text-transform: none;
cursor: pointer;
}
.smart-demo-container .demo-actions button:hover,
.smart-demo-container .demo-button:hover {
border-color: var(--smart-ui-state-border-hover);
background: var(--smart-ui-state-hover);
color: var(--smart-ui-state-color-hover);
}
.smart-demo-container .demo-actions button.demo-button-primary,
.smart-demo-container .demo-button-primary {
border-color: var(--smart-primary);
background: var(--smart-primary);
color: var(--smart-primary-color, #fff);
}
.smart-demo-container .demo-actions button[disabled],
.smart-demo-container .demo-button[disabled] {
opacity: 0.5;
cursor: default;
}
.smart-demo-container .demo-actions select,
.smart-demo-container .demo-actions input[type="text"],
.smart-demo-container .demo-actions input[type="number"] {
min-height: 30px;
padding: 2px 6px;
border: 1px solid var(--smart-border);
border-radius: 3px;
background: var(--smart-background);
color: var(--smart-text-color);
font: inherit;
font-size: 13px;
}
.smart-demo-container .hint,
.smart-demo-container .demo-log,
.smart-demo-container .demo-counts {
margin: 10px 0 0;
font-size: 12px;
line-height: 1.5;
color: var(--smart-text-secondary-color);
opacity: 1;
}
.smart-demo-container .demo-log,
.smart-demo-container .demo-counts {
font-family: Consolas, "Courier New", monospace;
}
.smart-demo-container .log {
list-style: none;
margin: 10px 0 0;
padding: 0;
font-size: 12px;
font-variant-numeric: tabular-nums;
color: var(--smart-text-secondary-color);
opacity: 1;
}
.smart-demo-container .log li {
padding: 2px 0;
}
The same demo in React, generated from the HTML demo. The frameworks guide has the project setup.
react/App.jsx
import 'smart-industrial/source/styles/smart.default.css';
import 'smart-industrial/source/styles/smart.industrial-elements.css';
import 'smart-industrial/source/styles/smart.industrial.css';
import 'smart-industrial/source/styles/smart.industrial-dark.css';
import './App.css';
import React from 'react';
import { SelectorSwitch } from 'smart-industrial/react/selectorswitch';
/* The demo logic is the HTML demo's: it feeds the elements the way a plant's data would. */
/* Each switch raises a request; a simulated plant answers after a moment, and the switch
shows the position it answers with. */
const App = () => {
const root = React.useRef(null);
React.useEffect(() => {
/* The elements render after React has mounted them, and the demo's logic - written
for a page's load event - reads their parts, so it waits for every element in the
tree to have rendered first. */
const pending = Array.from(root.current.querySelectorAll('*'))
.filter((element) => typeof element.whenRendered === 'function' && !element.isRendered)
.map((element) => element.whenRendered());
let unmounted = false;
Promise.all(pending).then(() => {
if (unmounted) {
return;
}
const log = document.getElementById('log'),
refuse = document.getElementById('refuse'),
touch = document.getElementById('touch'),
plantA = document.getElementById('plantA'),
plantB = document.getElementById('plantB'),
plantDuty = document.getElementById('plantDuty');
function note(text) {
const item = document.createElement('li');
item.textContent = new Date().toLocaleTimeString('en-GB', { hour12: false }) + ' ' + text;
if (log.firstChild && log.firstChild.textContent.indexOf('Press a position') === 0) {
log.removeChild(log.firstChild);
}
log.insertBefore(item, log.firstChild);
while (log.children.length > 8) {
log.removeChild(log.lastChild);
}
}
const duty = document.getElementById('duty');
duty.positions = [
{ id: 'a', label: 'Pump A', description: 'P-2101A duty, B standby' },
{ id: 'b', label: 'Pump B', description: 'P-2101B duty, A standby' },
{ id: 'both', label: 'Both', description: 'Both pumps in service' }
];
/* What the plant shows once it has taken a position. */
const MODE_TEXT = {
hand: 'Hand, operator in control',
off: 'Off',
auto: 'Auto, sequence in control'
};
function reflect(id, position) {
if (id === 'segmented') {
plantA.textContent = MODE_TEXT[position] || position;
}
else if (id === 'rotary') {
plantB.textContent = MODE_TEXT[position] || position;
}
else if (id === 'duty') {
const chosen = duty.positions.filter((p) => p.id === position)[0];
plantDuty.textContent = chosen ? chosen.description : position;
}
}
/* The station: a request goes to the plant, which confirms after 900 ms, or refuses
once when the checkbox is set. A refusal leaves the switch where it was. */
['segmented', 'rotary', 'duty'].forEach(function (id) {
const element = document.getElementById(id);
element.addEventListener('positionRequest', function (event) {
note(element.label + ': requested ' + event.detail.position);
if (refuse.checked) {
refuse.checked = false;
setTimeout(function () {
element.value = event.detail.from;
note(element.label + ': plant refused, stays in ' + event.detail.from);
}, 900);
return;
}
setTimeout(function () {
element.value = event.detail.position;
reflect(id, event.detail.position);
note(element.label + ': plant confirms ' + event.detail.position);
}, 900);
});
});
touch.addEventListener('change', function () {
['segmented', 'rotary', 'duty'].forEach(function (id) {
document.getElementById(id).density = touch.checked ? 'touch' : 'normal';
});
});
/* ── Toggle, rocker and slide ─────────────────────────────────────── */
const note3 = document.getElementById('note3'),
toggleSwitch = document.getElementById('toggleSwitch'),
rockerSwitch = document.getElementById('rockerSwitch'),
slideSwitch = document.getElementById('slideSwitch'),
toggle3 = document.getElementById('toggle3');
toggleSwitch.positions = [{ id: 'off', label: 'Off' }, { id: 'on', label: 'On' }];
rockerSwitch.positions = [{ id: 'off', label: 'O' }, { id: 'on', label: 'I' }];
slideSwitch.positions = [{ id: 'low', label: 'Low' }, { id: 'mid', label: 'Mid' }, { id: 'high', label: 'High' }];
toggle3.positions = [{ id: 'rev', label: 'Rev' }, { id: 'off', label: 'Off' }, { id: 'fwd', label: 'Fwd' }];
[toggleSwitch, rockerSwitch, slideSwitch, toggle3].forEach(function (element) {
element.addEventListener('positionRequest', function (event) {
note3.textContent = element.label + ': ' + event.detail.position + ' requested.';
setTimeout(function () {
element.value = event.detail.position;
note3.textContent = element.label + ': the plant confirms ' + event.detail.position + '.';
}, 700);
});
});
/* ── Interlocked, and a plant that does not answer ─────────────────── */
const locked = document.getElementById('locked'),
silent = document.getElementById('silent'),
note2 = document.getElementById('note2');
locked.positions = [
{ id: 'closed', label: 'Closed' },
{ id: 'open', label: 'Open' }
];
locked.addEventListener('blocked', function (event) {
note2.textContent = 'Sample valve: blocked, ' + event.detail.reason + '.';
});
silent.positions = [
{ id: 'off', label: 'Off' },
{ id: 'on', label: 'On' }
];
silent.addEventListener('positionRequest', function (event) {
note2.textContent = 'Recirculation: ' + event.detail.position + ' requested. Nothing answers, so the request is dropped after 2.5 s.';
setTimeout(function () {
note2.textContent = 'Recirculation: no answer, the request was dropped. The switch still shows ' + silent.value + '.';
}, 2600);
});
});
return () => {
unmounted = true;
};
}, []);
return (
<div className="react-root" ref={root} theme="industrial">
<div className="smart-demo-container">
<section id="overview">
<h2>Smart.SelectorSwitch is a switch with a set of named positions, such as a Hand-Off-Auto selector. A press on a position is a request, and the position shown is the one confirmed by the plant.</h2>
<p>Switching a pump from Auto to Hand changes who controls it, so the switch raises the <code>positionRequest</code> event and shows the requested position as pending until the application reports the value the plant accepted. With the <em>confirm</em> property enabled, the position has to be pressed twice. A complete control station with a selector, start, stop and jog buttons is shown on the <a href="../../momentarybutton/overview/index.htm">Momentary Button</a> page.</p>
</section>
<section>
<h2>A pump control station</h2>
<p>Press a position. The switch shows it as pending, the simulated plant answers after a moment, and the switch takes the position the plant reports. P-2101B has <code>confirm</code> set, so its rotary selector arms on the first press and requests on the second. The duty selector has three positions with descriptions, which are read by screen readers and shown as tooltips.</p>
<div className="station">
<div className="station-title">Feed pumps P-2101</div>
<div className="station-body">
<div className="switches">
<SelectorSwitch id="segmented" label="P-2101A control" value="auto"></SelectorSwitch>
<SelectorSwitch id="rotary" label="P-2101B control" value="off" appearance="rotary" confirm></SelectorSwitch>
<SelectorSwitch id="duty" label="Duty pump" value="a"></SelectorSwitch>
</div>
<div className="plant">
<div className="plant-title">Plant</div>
<div className="plant-row"><span>P-2101A</span><b id="plantA">Auto, sequence in control</b></div>
<div className="plant-row"><span>P-2101B</span><b id="plantB">Off</b></div>
<div className="plant-row"><span>Duty</span><b id="plantDuty">P-2101A duty, B standby</b></div>
<div className="demo-actions">
<label><input type="checkbox" id="refuse" /> Plant refuses the next request</label>
<label><input type="checkbox" id="touch" /> Touch density</label>
</div>
</div>
</div>
</div>
<ol id="log" className="log">
<li>Press a position to raise a request.</li>
</ol>
<p className="hint">Keyboard: each switch is a radio group. Tab to it, use the arrow keys to move between positions and Enter or Space to request one. A pending position and a refusal are announced to screen readers.</p>
</section>
<section>
<h2>Toggle, rocker and slide</h2>
<p>The switch a panel would carry, drawn above the same row of positions: a toggle lever that leans to its position, a rocker whose pressed face is lit, and a slide whose knob sits at its position. The row of positions stays the control that is pressed and read; the drawing follows the position the plant reports, and shows a pending request in the pending colour. These are the toggle switch, rocker and slide switch of a test-and-measurement front panel with the request model of the line.</p>
<div className="row">
<SelectorSwitch id="toggleSwitch" label="Purge air" value="off" appearance="toggle"></SelectorSwitch>
<SelectorSwitch id="rockerSwitch" label="Lighting" value="on" appearance="rocker"></SelectorSwitch>
<SelectorSwitch id="slideSwitch" label="Range" value="mid" appearance="slide"></SelectorSwitch>
<SelectorSwitch id="toggle3" label="Agitator" value="off" appearance="toggle"></SelectorSwitch>
</div>
<p className="hint" id="note3">Press a position; the plant confirms after a moment.</p>
</section>
<section>
<h2>Interlocked, and a plant that does not answer</h2>
<p>An interlocked switch stays readable, shows the reason and raises the <code>blocked</code> event when pressed instead of a request. A request that the plant never answers is dropped after <code>pendingTimeout</code>, and the switch returns to the position it had.</p>
<div className="row">
<SelectorSwitch id="locked" label="Sample valve" value="closed" interlocked interlockReason="line under pressure"></SelectorSwitch>
<SelectorSwitch id="silent" label="Recirculation" value="off" pendingTimeout={2500}></SelectorSwitch>
<SelectorSwitch id="disabledSwitch" label="Spare" value="off" disabled></SelectorSwitch>
</div>
<p className="hint" id="note2">The recirculation selector is wired to nothing: request a position and watch it drop back after 2.5 s.</p>
</section>
</div>
</div>
);
};
export default App;
react/App.css
.row { display: flex; flex-wrap: wrap; gap: 30px; align-items: flex-start; }
/* The control station: a surface with the switches on the left and the plant on the right. */
.station {
display: inline-block;
max-width: 100%;
border: 1px solid var(--smart-border);
border-radius: var(--smart-border-radius);
background: var(--smart-industrial-panel-background, var(--smart-surface));
}
.station-title {
padding: 8px 16px;
border-bottom: 1px solid var(--smart-border);
background: var(--smart-industrial-panel-header-background, var(--smart-base-200));
font-size: 11px;
font-weight: 600;
letter-spacing: .08em;
text-transform: uppercase;
color: var(--smart-text-secondary-color);
}
.station-body {
display: flex;
flex-wrap: wrap;
gap: 20px 40px;
padding: 18px 22px 16px;
}
.switches {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
gap: 16px 30px;
}
.plant {
min-width: 280px;
padding-left: 24px;
border-left: 1px solid var(--smart-border);
font-size: 13px;
}
.plant-title {
margin-bottom: 8px;
font-size: 11px;
font-weight: 600;
letter-spacing: .08em;
text-transform: uppercase;
color: var(--smart-text-secondary-color);
}
.plant-row {
display: flex;
justify-content: space-between;
gap: 16px;
padding: 5px 0;
border-bottom: 1px solid var(--smart-border);
}
.plant-row span {
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
color: var(--smart-text-secondary-color);
}
.plant-row b {
font-weight: 600;
}
/* ── Demo chrome, shared by the Industrial demos ─────────────────────────
Headings, prose, buttons and notes are the same on every demo page. This block
is appended by scripts and comes last, so it wins over the rules above. */
.smart-demo-container {
padding: 4px 0 32px;
}
.smart-demo-container section {
margin-bottom: 36px;
}
.smart-demo-container section h2 {
margin: 0 0 8px;
font-size: 17px;
font-weight: 600;
line-height: 1.35;
color: var(--smart-text-color);
}
.smart-demo-container #overview h2 {
margin-bottom: 10px;
font-size: 20px;
max-width: 78ch;
}
.smart-demo-container section > p {
margin: 0 0 14px;
max-width: 84ch;
font-size: 14px;
line-height: 1.55;
color: var(--smart-text-secondary-color);
}
.smart-demo-container code {
padding: 1px 5px;
border-radius: 2px;
background: var(--smart-ui-state-hover);
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
}
.smart-demo-container .demo-actions {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px 10px;
margin-top: 12px;
}
.smart-demo-container .demo-actions label {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 13px;
color: var(--smart-text-color);
}
.smart-demo-container .demo-actions button,
.smart-demo-container .demo-button {
min-height: 30px;
padding: 0 12px;
border: 1px solid var(--smart-border);
border-radius: 3px;
background: var(--smart-industrial-panel-background, var(--smart-surface));
color: var(--smart-text-color);
font: inherit;
font-size: 13px;
font-weight: 500;
letter-spacing: 0;
text-transform: none;
cursor: pointer;
}
.smart-demo-container .demo-actions button:hover,
.smart-demo-container .demo-button:hover {
border-color: var(--smart-ui-state-border-hover);
background: var(--smart-ui-state-hover);
color: var(--smart-ui-state-color-hover);
}
.smart-demo-container .demo-actions button.demo-button-primary,
.smart-demo-container .demo-button-primary {
border-color: var(--smart-primary);
background: var(--smart-primary);
color: var(--smart-primary-color, #fff);
}
.smart-demo-container .demo-actions button[disabled],
.smart-demo-container .demo-button[disabled] {
opacity: 0.5;
cursor: default;
}
.smart-demo-container .demo-actions select,
.smart-demo-container .demo-actions input[type="text"],
.smart-demo-container .demo-actions input[type="number"] {
min-height: 30px;
padding: 2px 6px;
border: 1px solid var(--smart-border);
border-radius: 3px;
background: var(--smart-background);
color: var(--smart-text-color);
font: inherit;
font-size: 13px;
}
.smart-demo-container .hint,
.smart-demo-container .demo-log,
.smart-demo-container .demo-counts {
margin: 10px 0 0;
font-size: 12px;
line-height: 1.5;
color: var(--smart-text-secondary-color);
opacity: 1;
}
.smart-demo-container .demo-log,
.smart-demo-container .demo-counts {
font-family: Consolas, "Courier New", monospace;
}
.smart-demo-container .log {
list-style: none;
margin: 10px 0 0;
padding: 0;
font-size: 12px;
font-variant-numeric: tabular-nums;
color: var(--smart-text-secondary-color);
opacity: 1;
}
.smart-demo-container .log li {
padding: 2px 0;
}
The same demo in Vue, generated from the HTML demo. The frameworks guide has the project setup.
vue/App.vue
<template>
<div class="vue-root" theme="industrial" v-pre>
<div class="smart-demo-container">
<section id="overview">
<h2>Smart.SelectorSwitch is a switch with a set of named positions, such as a Hand-Off-Auto selector. A press on a position is a request, and the position shown is the one confirmed by the plant.</h2>
<p>
Switching a pump from Auto to Hand changes who controls it, so the switch raises the <code>positionRequest</code> event and shows the requested position as pending until the application reports the value the plant accepted. With the <em>confirm</em> property enabled, the position has to be pressed twice. A complete control station with a selector, start, stop and jog buttons is shown on the <a href="../../momentarybutton/overview/index.htm">Momentary Button</a> page.
</p>
</section>
<section>
<h2>A pump control station</h2>
<p>
Press a position. The switch shows it as pending, the simulated plant answers after a moment, and the switch takes the position the plant reports. P-2101B has <code>confirm</code> set, so its rotary selector arms on the first press and requests on the second. The duty selector has three positions with descriptions, which are read by screen readers and shown as tooltips.
</p>
<div class="station">
<div class="station-title">Feed pumps P-2101</div>
<div class="station-body">
<div class="switches">
<smart-selector-switch id="segmented" label="P-2101A control" value="auto"></smart-selector-switch>
<smart-selector-switch id="rotary" label="P-2101B control" value="off" appearance="rotary" confirm></smart-selector-switch>
<smart-selector-switch id="duty" label="Duty pump" value="a"></smart-selector-switch>
</div>
<div class="plant">
<div class="plant-title">Plant</div>
<div class="plant-row"><span>P-2101A</span><b id="plantA">Auto, sequence in control</b></div>
<div class="plant-row"><span>P-2101B</span><b id="plantB">Off</b></div>
<div class="plant-row"><span>Duty</span><b id="plantDuty">P-2101A duty, B standby</b></div>
<div class="demo-actions">
<label><input type="checkbox" id="refuse" /> Plant refuses the next request</label>
<label><input type="checkbox" id="touch" /> Touch density</label>
</div>
</div>
</div>
</div>
<ol id="log" class="log"><li>Press a position to raise a request.</li></ol>
<p class="hint">Keyboard: each switch is a radio group. Tab to it, use the arrow keys to move between positions and Enter or Space to request one. A pending position and a refusal are announced to screen readers.</p>
</section>
<section>
<h2>Toggle, rocker and slide</h2>
<p>
The switch a panel would carry, drawn above the same row of positions: a toggle lever that leans to its position, a rocker whose pressed face is lit, and a slide whose knob sits at its position. The row of positions stays the control that is pressed and read; the drawing follows the position the plant reports, and shows a pending request in the pending colour. These are the toggle switch, rocker and slide switch of a test-and-measurement front panel with the request model of the line.
</p>
<div class="row">
<smart-selector-switch id="toggleSwitch" label="Purge air" value="off" appearance="toggle"></smart-selector-switch>
<smart-selector-switch id="rockerSwitch" label="Lighting" value="on" appearance="rocker"></smart-selector-switch>
<smart-selector-switch id="slideSwitch" label="Range" value="mid" appearance="slide"></smart-selector-switch>
<smart-selector-switch id="toggle3" label="Agitator" value="off" appearance="toggle"></smart-selector-switch>
</div>
<p class="hint" id="note3">Press a position; the plant confirms after a moment.</p>
</section>
<section>
<h2>Interlocked, and a plant that does not answer</h2>
<p>
An interlocked switch stays readable, shows the reason and raises the <code>blocked</code> event when pressed instead of a request. A request that the plant never answers is dropped after <code>pendingTimeout</code>, and the switch returns to the position it had.
</p>
<div class="row">
<smart-selector-switch id="locked" label="Sample valve" value="closed" interlocked interlock-reason="line under pressure"></smart-selector-switch>
<smart-selector-switch id="silent" label="Recirculation" value="off" pending-timeout="2500"></smart-selector-switch>
<smart-selector-switch id="disabledSwitch" label="Spare" value="off" disabled></smart-selector-switch>
</div>
<p class="hint" id="note2">The recirculation selector is wired to nothing: request a position and watch it drop back after 2.5 s.</p>
</section>
</div>
</div>
</template>
<script>
import { onMounted } from "vue";
import "smart-industrial/source/styles/smart.default.css";
import "smart-industrial/source/styles/smart.industrial-elements.css";
import "smart-industrial/source/styles/smart.industrial.css";
import "smart-industrial/source/styles/smart.industrial-dark.css";
import "smart-industrial/source/modules/smart.selectorswitch.js";
/* Each switch raises a request; a simulated plant answers after a moment, and the switch
shows the position it answers with. */
export default {
name: "app",
setup() {
onMounted(() => {
/* The elements render after Vue has mounted them, and the demo's logic - written for
a page's load event - reads their parts, so it waits for every element in the tree
to have rendered first. */
const pending = Array.from(document.querySelector(".vue-root").querySelectorAll("*"))
.filter((element) => typeof element.whenRendered === "function" && !element.isRendered)
.map((element) => element.whenRendered());
Promise.all(pending).then(() => {
const log = document.getElementById('log'),
refuse = document.getElementById('refuse'),
touch = document.getElementById('touch'),
plantA = document.getElementById('plantA'),
plantB = document.getElementById('plantB'),
plantDuty = document.getElementById('plantDuty');
function note(text) {
const item = document.createElement('li');
item.textContent = new Date().toLocaleTimeString('en-GB', { hour12: false }) + ' ' + text;
if (log.firstChild && log.firstChild.textContent.indexOf('Press a position') === 0) {
log.removeChild(log.firstChild);
}
log.insertBefore(item, log.firstChild);
while (log.children.length > 8) {
log.removeChild(log.lastChild);
}
}
const duty = document.getElementById('duty');
duty.positions = [
{ id: 'a', label: 'Pump A', description: 'P-2101A duty, B standby' },
{ id: 'b', label: 'Pump B', description: 'P-2101B duty, A standby' },
{ id: 'both', label: 'Both', description: 'Both pumps in service' }
];
/* What the plant shows once it has taken a position. */
const MODE_TEXT = {
hand: 'Hand, operator in control',
off: 'Off',
auto: 'Auto, sequence in control'
};
function reflect(id, position) {
if (id === 'segmented') {
plantA.textContent = MODE_TEXT[position] || position;
}
else if (id === 'rotary') {
plantB.textContent = MODE_TEXT[position] || position;
}
else if (id === 'duty') {
const chosen = duty.positions.filter((p) => p.id === position)[0];
plantDuty.textContent = chosen ? chosen.description : position;
}
}
/* The station: a request goes to the plant, which confirms after 900 ms, or refuses
once when the checkbox is set. A refusal leaves the switch where it was. */
['segmented', 'rotary', 'duty'].forEach(function (id) {
const element = document.getElementById(id);
element.addEventListener('positionRequest', function (event) {
note(element.label + ': requested ' + event.detail.position);
if (refuse.checked) {
refuse.checked = false;
setTimeout(function () {
element.value = event.detail.from;
note(element.label + ': plant refused, stays in ' + event.detail.from);
}, 900);
return;
}
setTimeout(function () {
element.value = event.detail.position;
reflect(id, event.detail.position);
note(element.label + ': plant confirms ' + event.detail.position);
}, 900);
});
});
touch.addEventListener('change', function () {
['segmented', 'rotary', 'duty'].forEach(function (id) {
document.getElementById(id).density = touch.checked ? 'touch' : 'normal';
});
});
/* ── Toggle, rocker and slide ─────────────────────────────────────── */
const note3 = document.getElementById('note3'),
toggleSwitch = document.getElementById('toggleSwitch'),
rockerSwitch = document.getElementById('rockerSwitch'),
slideSwitch = document.getElementById('slideSwitch'),
toggle3 = document.getElementById('toggle3');
toggleSwitch.positions = [{ id: 'off', label: 'Off' }, { id: 'on', label: 'On' }];
rockerSwitch.positions = [{ id: 'off', label: 'O' }, { id: 'on', label: 'I' }];
slideSwitch.positions = [{ id: 'low', label: 'Low' }, { id: 'mid', label: 'Mid' }, { id: 'high', label: 'High' }];
toggle3.positions = [{ id: 'rev', label: 'Rev' }, { id: 'off', label: 'Off' }, { id: 'fwd', label: 'Fwd' }];
[toggleSwitch, rockerSwitch, slideSwitch, toggle3].forEach(function (element) {
element.addEventListener('positionRequest', function (event) {
note3.textContent = element.label + ': ' + event.detail.position + ' requested.';
setTimeout(function () {
element.value = event.detail.position;
note3.textContent = element.label + ': the plant confirms ' + event.detail.position + '.';
}, 700);
});
});
/* ── Interlocked, and a plant that does not answer ─────────────────── */
const locked = document.getElementById('locked'),
silent = document.getElementById('silent'),
note2 = document.getElementById('note2');
locked.positions = [
{ id: 'closed', label: 'Closed' },
{ id: 'open', label: 'Open' }
];
locked.addEventListener('blocked', function (event) {
note2.textContent = 'Sample valve: blocked, ' + event.detail.reason + '.';
});
silent.positions = [
{ id: 'off', label: 'Off' },
{ id: 'on', label: 'On' }
];
silent.addEventListener('positionRequest', function (event) {
note2.textContent = 'Recirculation: ' + event.detail.position + ' requested. Nothing answers, so the request is dropped after 2.5 s.';
setTimeout(function () {
note2.textContent = 'Recirculation: no answer, the request was dropped. The switch still shows ' + silent.value + '.';
}, 2600);
});
});
});
}
};
</script>
<style>
.row { display: flex; flex-wrap: wrap; gap: 30px; align-items: flex-start; }
/* The control station: a surface with the switches on the left and the plant on the right. */
.station {
display: inline-block;
max-width: 100%;
border: 1px solid var(--smart-border);
border-radius: var(--smart-border-radius);
background: var(--smart-industrial-panel-background, var(--smart-surface));
}
.station-title {
padding: 8px 16px;
border-bottom: 1px solid var(--smart-border);
background: var(--smart-industrial-panel-header-background, var(--smart-base-200));
font-size: 11px;
font-weight: 600;
letter-spacing: .08em;
text-transform: uppercase;
color: var(--smart-text-secondary-color);
}
.station-body {
display: flex;
flex-wrap: wrap;
gap: 20px 40px;
padding: 18px 22px 16px;
}
.switches {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
gap: 16px 30px;
}
.plant {
min-width: 280px;
padding-left: 24px;
border-left: 1px solid var(--smart-border);
font-size: 13px;
}
.plant-title {
margin-bottom: 8px;
font-size: 11px;
font-weight: 600;
letter-spacing: .08em;
text-transform: uppercase;
color: var(--smart-text-secondary-color);
}
.plant-row {
display: flex;
justify-content: space-between;
gap: 16px;
padding: 5px 0;
border-bottom: 1px solid var(--smart-border);
}
.plant-row span {
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
color: var(--smart-text-secondary-color);
}
.plant-row b {
font-weight: 600;
}
/* ── Demo chrome, shared by the Industrial demos ─────────────────────────
Headings, prose, buttons and notes are the same on every demo page. This block
is appended by scripts and comes last, so it wins over the rules above. */
.smart-demo-container {
padding: 4px 0 32px;
}
.smart-demo-container section {
margin-bottom: 36px;
}
.smart-demo-container section h2 {
margin: 0 0 8px;
font-size: 17px;
font-weight: 600;
line-height: 1.35;
color: var(--smart-text-color);
}
.smart-demo-container #overview h2 {
margin-bottom: 10px;
font-size: 20px;
max-width: 78ch;
}
.smart-demo-container section > p {
margin: 0 0 14px;
max-width: 84ch;
font-size: 14px;
line-height: 1.55;
color: var(--smart-text-secondary-color);
}
.smart-demo-container code {
padding: 1px 5px;
border-radius: 2px;
background: var(--smart-ui-state-hover);
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
}
.smart-demo-container .demo-actions {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px 10px;
margin-top: 12px;
}
.smart-demo-container .demo-actions label {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 13px;
color: var(--smart-text-color);
}
.smart-demo-container .demo-actions button,
.smart-demo-container .demo-button {
min-height: 30px;
padding: 0 12px;
border: 1px solid var(--smart-border);
border-radius: 3px;
background: var(--smart-industrial-panel-background, var(--smart-surface));
color: var(--smart-text-color);
font: inherit;
font-size: 13px;
font-weight: 500;
letter-spacing: 0;
text-transform: none;
cursor: pointer;
}
.smart-demo-container .demo-actions button:hover,
.smart-demo-container .demo-button:hover {
border-color: var(--smart-ui-state-border-hover);
background: var(--smart-ui-state-hover);
color: var(--smart-ui-state-color-hover);
}
.smart-demo-container .demo-actions button.demo-button-primary,
.smart-demo-container .demo-button-primary {
border-color: var(--smart-primary);
background: var(--smart-primary);
color: var(--smart-primary-color, #fff);
}
.smart-demo-container .demo-actions button[disabled],
.smart-demo-container .demo-button[disabled] {
opacity: 0.5;
cursor: default;
}
.smart-demo-container .demo-actions select,
.smart-demo-container .demo-actions input[type="text"],
.smart-demo-container .demo-actions input[type="number"] {
min-height: 30px;
padding: 2px 6px;
border: 1px solid var(--smart-border);
border-radius: 3px;
background: var(--smart-background);
color: var(--smart-text-color);
font: inherit;
font-size: 13px;
}
.smart-demo-container .hint,
.smart-demo-container .demo-log,
.smart-demo-container .demo-counts {
margin: 10px 0 0;
font-size: 12px;
line-height: 1.5;
color: var(--smart-text-secondary-color);
opacity: 1;
}
.smart-demo-container .demo-log,
.smart-demo-container .demo-counts {
font-family: Consolas, "Courier New", monospace;
}
.smart-demo-container .log {
list-style: none;
margin: 10px 0 0;
padding: 0;
font-size: 12px;
font-variant-numeric: tabular-nums;
color: var(--smart-text-secondary-color);
opacity: 1;
}
.smart-demo-container .log li {
padding: 2px 0;
}
</style>
What the demo shows
- A pump control station
- Toggle, rocker and slide
- Interlocked, and a plant that does not answer
On the operator screens
Also in instrument readouts and controls
Bar GraphBit FieldJog PanelKnobMomentary ButtonNumeric KeypadPanel MeterSeven SegmentThermometerTouch Keyboard