Component demo · Instrument readouts and controls
Knob
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>Knob 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.Knob is a rotary control for setting a value. It extends Tank.</h2>
<p>
Because the component extends <code>Smart.Tank</code>, clamping, coercion to an interval, logarithmic scales, alarm ranges, units, inversion, right-to-left layout and the keyboard behaviour of the slider are inherited. This includes the <code>mechanicalAction</code> property, which defines when the control commits its value.
</p>
</section>
<section>
<h2>A controller front panel</h2>
<p>
Four settings of a temperature controller. Drag around a dial or up and down over it to change the value; the arrow keys, Home and End also work. Each change is a <code>change</code> event that the application writes to the controller, listed under the panel. The setpoint knob carries the alarm ranges of the loop, so the operator sees where the setting sits before it is written.
</p>
<div class="panel">
<div class="panel-title">TIC-2108 Reactor temperature</div>
<div class="stations">
<div class="station">
<span class="caption">Setpoint</span>
<smart-knob id="setpoint" class="large"></smart-knob>
<span class="range">0 to 150 °C</span>
</div>
<div class="station">
<span class="caption">Output bias</span>
<smart-knob id="bias" class="large"></smart-knob>
<span class="range">0 to 100 %</span>
</div>
<div class="station">
<span class="caption">Ramp rate</span>
<smart-knob id="ramp" class="large" knob-style="arc"></smart-knob>
<span class="range">0.5 to 20 °C/min</span>
</div>
<div class="station">
<span class="caption">High alarm</span>
<smart-knob id="alarm" class="large" knob-style="dot"></smart-knob>
<span class="range">100 to 150 °C, steps of 5</span>
</div>
</div>
</div>
<ol id="log" class="log"><li>Nothing written yet. Turn a knob.</li></ol>
</section>
<section>
<h2>Styles</h2>
<p>The <code>knobStyle</code> property selects a pointer, a dot on the dial or an arc that fills from the start of the scale.</p>
<div class="row">
<div class="cell"><smart-knob id="k1"></smart-knob><span>pointer</span></div>
<div class="cell"><smart-knob id="k2" knob-style="dot"></smart-knob><span>dot</span></div>
<div class="cell"><smart-knob id="k3" knob-style="arc"></smart-knob><span>arc</span></div>
</div>
</section>
<section>
<h2>Scales and ranges</h2>
<p>Ranges are inherited from Tank, so a range has the same meaning as on a gauge or a tank. A logarithmic scale keeps the bottom decades usable, and <code>coerce</code> with an <code>interval</code> snaps the value to steps.</p>
<div class="row">
<div class="cell"><smart-knob id="pressure"></smart-knob><span>Setpoint, bar</span></div>
<div class="cell"><smart-knob id="logScale"></smart-knob><span>Logarithmic, 1 to 1000</span></div>
<div class="cell"><smart-knob id="coerced"></smart-knob><span>Coerced to 10</span></div>
<div class="cell"><smart-knob id="disabledKnob" disabled></smart-knob><span>Disabled</span></div>
</div>
<p class="hint">Keyboard: Tab to a knob, then use the arrow keys to step, Page Up and Page Down for larger steps, and Home and End for the limits.</p>
</section>
</div>
<script type="module" src="node_modules/smart-industrial/source/modules/smart.knob.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" />
window.onload = function () {
const log = document.getElementById('log');
/* ── The controller front panel ────────────────────────────────────── */
const setpoint = document.getElementById('setpoint'),
bias = document.getElementById('bias'),
ramp = document.getElementById('ramp'),
alarm = document.getElementById('alarm');
setpoint.min = 0;
setpoint.max = 150;
setpoint.value = 118;
setpoint.unit = '°C';
setpoint.showUnit = true;
setpoint.precisionDigits = 1;
setpoint.showRanges = true;
setpoint.ranges = [
{ startValue: 0, endValue: 60, className: 'warning' },
{ startValue: 60, endValue: 125, className: 'ok' },
{ startValue: 125, endValue: 150, className: 'critical' }
];
bias.min = 0;
bias.max = 100;
bias.value = 42;
bias.unit = '%';
bias.showUnit = true;
bias.precisionDigits = 0;
ramp.min = 0.5;
ramp.max = 20;
ramp.value = 4;
ramp.unit = '°C/min';
ramp.showUnit = true;
ramp.precisionDigits = 1;
alarm.min = 100;
alarm.max = 150;
alarm.interval = 5;
alarm.coerce = true;
alarm.value = 125;
alarm.unit = '°C';
alarm.showUnit = true;
alarm.precisionDigits = 0;
/* Every knob is a control: its change event is what the application writes to the
controller. The list keeps the last few writes, newest first. */
const TAGS = { setpoint: 'TIC-2108.SP', bias: 'TIC-2108.BIAS', ramp: 'TIC-2108.RAMP', alarm: 'TAH-2108.LIMIT' };
[setpoint, bias, ramp, alarm].forEach(function (knob) {
knob.addEventListener('change', function (event) {
const item = document.createElement('li'),
value = Number(event.detail.value);
item.textContent = new Date().toLocaleTimeString() + ' ' + TAGS[knob.id] + ' = ' +
value.toFixed(knob.precisionDigits) + ' ' + knob.unit;
if (log.firstChild && log.firstChild.textContent.indexOf('Nothing written') === 0) {
log.removeChild(log.firstChild);
}
log.insertBefore(item, log.firstChild);
while (log.children.length > 5) {
log.removeChild(log.lastChild);
}
});
});
/* ── Styles ─────────────────────────────────────────────────────────── */
['k1', 'k2', 'k3'].forEach(function (id) {
const knob = document.getElementById(id);
knob.min = 0;
knob.max = 100;
knob.value = 62;
knob.precisionDigits = 0;
});
/* ── Scales and ranges ──────────────────────────────────────────────── */
const pressure = document.getElementById('pressure');
pressure.min = 0;
pressure.max = 10;
pressure.value = 6.2;
pressure.unit = 'bar';
pressure.showUnit = true;
pressure.precisionDigits = 1;
pressure.showRanges = true;
pressure.ranges = [
{ startValue: 0, endValue: 2, className: 'warning' },
{ startValue: 2, endValue: 8, className: 'ok' },
{ startValue: 8, endValue: 10, className: 'critical' }
];
/* A log knob is what a decade-ranged instrument setting needs: the top of the scale
must not swallow the bottom three decades. */
const logScale = document.getElementById('logScale');
logScale.min = 1;
logScale.max = 1000;
logScale.logarithmicScale = true;
logScale.value = 100;
logScale.precisionDigits = 0;
logScale.knobStyle = 'arc';
const coerced = document.getElementById('coerced');
coerced.min = 0;
coerced.max = 100;
coerced.interval = 10;
coerced.coerce = true;
coerced.value = 40;
coerced.precisionDigits = 0;
const disabledKnob = document.getElementById('disabledKnob');
disabledKnob.min = 0;
disabledKnob.max = 100;
disabledKnob.value = 75;
disabledKnob.precisionDigits = 0;
};
styles.css
.row { display: flex; flex-wrap: wrap; gap: 28px; align-items: flex-start; }
.cell { display: flex; flex-direction: column; align-items: center; gap: 6px; font-size: 12px; color: var(--smart-text-secondary-color); }
/* The front panel: a surface with a title strip and a station per setting. */
.panel {
display: inline-block;
min-width: 320px;
max-width: 100%;
border: 1px solid var(--smart-border);
border-radius: var(--smart-border-radius);
background: var(--smart-industrial-panel-background, var(--smart-surface));
}
.panel-title {
padding: 8px 16px;
border-bottom: 1px solid var(--smart-border);
background: var(--smart-industrial-panel-header-background, var(--smart-base-200));
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
letter-spacing: .04em;
color: var(--smart-text-secondary-color);
}
.stations {
display: flex;
flex-wrap: wrap;
gap: 8px 36px;
padding: 18px 24px 16px;
}
.station {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
min-width: 150px;
}
.station .caption {
font-size: 11px;
font-weight: 600;
letter-spacing: .08em;
text-transform: uppercase;
color: var(--smart-text-secondary-color);
}
.station .range {
font-size: 11px;
color: var(--smart-text-tertiary-color);
}
smart-knob.large {
--smart-knob-size: 128px;
}
smart-knob.large .smart-knob-readout {
font-size: 15px;
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.Knob is a rotary control for setting a value. It extends Tank.</h2>
<p>
Because the component extends <code>Smart.Tank</code>, clamping, coercion to an interval, logarithmic scales, alarm ranges, units, inversion, right-to-left layout and the keyboard behaviour of the slider are inherited. This includes the <code>mechanicalAction</code> property, which defines when the control commits its value.
</p>
</section>
<section>
<h2>A controller front panel</h2>
<p>
Four settings of a temperature controller. Drag around a dial or up and down over it to change the value; the arrow keys, Home and End also work. Each change is a <code>change</code> event that the application writes to the controller, listed under the panel. The setpoint knob carries the alarm ranges of the loop, so the operator sees where the setting sits before it is written.
</p>
<div class="panel">
<div class="panel-title">TIC-2108 Reactor temperature</div>
<div class="stations">
<div class="station">
<span class="caption">Setpoint</span>
<smart-knob id="setpoint" class="large"></smart-knob>
<span class="range">0 to 150 °C</span>
</div>
<div class="station">
<span class="caption">Output bias</span>
<smart-knob id="bias" class="large"></smart-knob>
<span class="range">0 to 100 %</span>
</div>
<div class="station">
<span class="caption">Ramp rate</span>
<smart-knob id="ramp" class="large" knob-style="arc"></smart-knob>
<span class="range">0.5 to 20 °C/min</span>
</div>
<div class="station">
<span class="caption">High alarm</span>
<smart-knob id="alarm" class="large" knob-style="dot"></smart-knob>
<span class="range">100 to 150 °C, steps of 5</span>
</div>
</div>
</div>
<ol id="log" class="log"><li>Nothing written yet. Turn a knob.</li></ol>
</section>
<section>
<h2>Styles</h2>
<p>The <code>knobStyle</code> property selects a pointer, a dot on the dial or an arc that fills from the start of the scale.</p>
<div class="row">
<div class="cell"><smart-knob id="k1"></smart-knob><span>pointer</span></div>
<div class="cell"><smart-knob id="k2" knob-style="dot"></smart-knob><span>dot</span></div>
<div class="cell"><smart-knob id="k3" knob-style="arc"></smart-knob><span>arc</span></div>
</div>
</section>
<section>
<h2>Scales and ranges</h2>
<p>Ranges are inherited from Tank, so a range has the same meaning as on a gauge or a tank. A logarithmic scale keeps the bottom decades usable, and <code>coerce</code> with an <code>interval</code> snaps the value to steps.</p>
<div class="row">
<div class="cell"><smart-knob id="pressure"></smart-knob><span>Setpoint, bar</span></div>
<div class="cell"><smart-knob id="logScale"></smart-knob><span>Logarithmic, 1 to 1000</span></div>
<div class="cell"><smart-knob id="coerced"></smart-knob><span>Coerced to 10</span></div>
<div class="cell"><smart-knob id="disabledKnob" [disabled]="true"></smart-knob><span>Disabled</span></div>
</div>
<p class="hint">Keyboard: Tab to a knob, then use the arrow keys to step, Page Up and Page Down for larger steps, and Home and End for the limits.</p>
</section>
</div>
</div>
angular/app.component.ts
import { Component, AfterViewInit, OnDestroy } from '@angular/core';
import { KnobModule } from 'smart-industrial/angular/knob';
import { runDemo } from './demo';
/**
* Smart.UI Industrial - Knob 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: [KnobModule],
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. */
export function runDemo(): void {
const log = document.getElementById('log');
/* ── The controller front panel ────────────────────────────────────── */
const setpoint = document.getElementById('setpoint'),
bias = document.getElementById('bias'),
ramp = document.getElementById('ramp'),
alarm = document.getElementById('alarm');
setpoint.min = 0;
setpoint.max = 150;
setpoint.value = 118;
setpoint.unit = '°C';
setpoint.showUnit = true;
setpoint.precisionDigits = 1;
setpoint.showRanges = true;
setpoint.ranges = [
{ startValue: 0, endValue: 60, className: 'warning' },
{ startValue: 60, endValue: 125, className: 'ok' },
{ startValue: 125, endValue: 150, className: 'critical' }
];
bias.min = 0;
bias.max = 100;
bias.value = 42;
bias.unit = '%';
bias.showUnit = true;
bias.precisionDigits = 0;
ramp.min = 0.5;
ramp.max = 20;
ramp.value = 4;
ramp.unit = '°C/min';
ramp.showUnit = true;
ramp.precisionDigits = 1;
alarm.min = 100;
alarm.max = 150;
alarm.interval = 5;
alarm.coerce = true;
alarm.value = 125;
alarm.unit = '°C';
alarm.showUnit = true;
alarm.precisionDigits = 0;
/* Every knob is a control: its change event is what the application writes to the
controller. The list keeps the last few writes, newest first. */
const TAGS = { setpoint: 'TIC-2108.SP', bias: 'TIC-2108.BIAS', ramp: 'TIC-2108.RAMP', alarm: 'TAH-2108.LIMIT' };
[setpoint, bias, ramp, alarm].forEach(function (knob) {
knob.addEventListener('change', function (event) {
const item = document.createElement('li'),
value = Number(event.detail.value);
item.textContent = new Date().toLocaleTimeString() + ' ' + TAGS[knob.id] + ' = ' +
value.toFixed(knob.precisionDigits) + ' ' + knob.unit;
if (log.firstChild && log.firstChild.textContent.indexOf('Nothing written') === 0) {
log.removeChild(log.firstChild);
}
log.insertBefore(item, log.firstChild);
while (log.children.length > 5) {
log.removeChild(log.lastChild);
}
});
});
/* ── Styles ─────────────────────────────────────────────────────────── */
['k1', 'k2', 'k3'].forEach(function (id) {
const knob = document.getElementById(id);
knob.min = 0;
knob.max = 100;
knob.value = 62;
knob.precisionDigits = 0;
});
/* ── Scales and ranges ──────────────────────────────────────────────── */
const pressure = document.getElementById('pressure');
pressure.min = 0;
pressure.max = 10;
pressure.value = 6.2;
pressure.unit = 'bar';
pressure.showUnit = true;
pressure.precisionDigits = 1;
pressure.showRanges = true;
pressure.ranges = [
{ startValue: 0, endValue: 2, className: 'warning' },
{ startValue: 2, endValue: 8, className: 'ok' },
{ startValue: 8, endValue: 10, className: 'critical' }
];
/* A log knob is what a decade-ranged instrument setting needs: the top of the scale
must not swallow the bottom three decades. */
const logScale = document.getElementById('logScale');
logScale.min = 1;
logScale.max = 1000;
logScale.logarithmicScale = true;
logScale.value = 100;
logScale.precisionDigits = 0;
logScale.knobStyle = 'arc';
const coerced = document.getElementById('coerced');
coerced.min = 0;
coerced.max = 100;
coerced.interval = 10;
coerced.coerce = true;
coerced.value = 40;
coerced.precisionDigits = 0;
const disabledKnob = document.getElementById('disabledKnob');
disabledKnob.min = 0;
disabledKnob.max = 100;
disabledKnob.value = 75;
disabledKnob.precisionDigits = 0;
}
angular/app.component.css
.row { display: flex; flex-wrap: wrap; gap: 28px; align-items: flex-start; }
.cell { display: flex; flex-direction: column; align-items: center; gap: 6px; font-size: 12px; color: var(--smart-text-secondary-color); }
/* The front panel: a surface with a title strip and a station per setting. */
.panel {
display: inline-block;
min-width: 320px;
max-width: 100%;
border: 1px solid var(--smart-border);
border-radius: var(--smart-border-radius);
background: var(--smart-industrial-panel-background, var(--smart-surface));
}
.panel-title {
padding: 8px 16px;
border-bottom: 1px solid var(--smart-border);
background: var(--smart-industrial-panel-header-background, var(--smart-base-200));
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
letter-spacing: .04em;
color: var(--smart-text-secondary-color);
}
.stations {
display: flex;
flex-wrap: wrap;
gap: 8px 36px;
padding: 18px 24px 16px;
}
.station {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
min-width: 150px;
}
.station .caption {
font-size: 11px;
font-weight: 600;
letter-spacing: .08em;
text-transform: uppercase;
color: var(--smart-text-secondary-color);
}
.station .range {
font-size: 11px;
color: var(--smart-text-tertiary-color);
}
smart-knob.large {
--smart-knob-size: 128px;
}
smart-knob.large .smart-knob-readout {
font-size: 15px;
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 { Knob } from 'smart-industrial/react/knob';
/* The demo logic is the HTML demo's: it feeds the elements the way a plant's data would. */
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');
/* ── The controller front panel ────────────────────────────────────── */
const setpoint = document.getElementById('setpoint'),
bias = document.getElementById('bias'),
ramp = document.getElementById('ramp'),
alarm = document.getElementById('alarm');
setpoint.min = 0;
setpoint.max = 150;
setpoint.value = 118;
setpoint.unit = '°C';
setpoint.showUnit = true;
setpoint.precisionDigits = 1;
setpoint.showRanges = true;
setpoint.ranges = [
{ startValue: 0, endValue: 60, className: 'warning' },
{ startValue: 60, endValue: 125, className: 'ok' },
{ startValue: 125, endValue: 150, className: 'critical' }
];
bias.min = 0;
bias.max = 100;
bias.value = 42;
bias.unit = '%';
bias.showUnit = true;
bias.precisionDigits = 0;
ramp.min = 0.5;
ramp.max = 20;
ramp.value = 4;
ramp.unit = '°C/min';
ramp.showUnit = true;
ramp.precisionDigits = 1;
alarm.min = 100;
alarm.max = 150;
alarm.interval = 5;
alarm.coerce = true;
alarm.value = 125;
alarm.unit = '°C';
alarm.showUnit = true;
alarm.precisionDigits = 0;
/* Every knob is a control: its change event is what the application writes to the
controller. The list keeps the last few writes, newest first. */
const TAGS = { setpoint: 'TIC-2108.SP', bias: 'TIC-2108.BIAS', ramp: 'TIC-2108.RAMP', alarm: 'TAH-2108.LIMIT' };
[setpoint, bias, ramp, alarm].forEach(function (knob) {
knob.addEventListener('change', function (event) {
const item = document.createElement('li'),
value = Number(event.detail.value);
item.textContent = new Date().toLocaleTimeString() + ' ' + TAGS[knob.id] + ' = ' +
value.toFixed(knob.precisionDigits) + ' ' + knob.unit;
if (log.firstChild && log.firstChild.textContent.indexOf('Nothing written') === 0) {
log.removeChild(log.firstChild);
}
log.insertBefore(item, log.firstChild);
while (log.children.length > 5) {
log.removeChild(log.lastChild);
}
});
});
/* ── Styles ─────────────────────────────────────────────────────────── */
['k1', 'k2', 'k3'].forEach(function (id) {
const knob = document.getElementById(id);
knob.min = 0;
knob.max = 100;
knob.value = 62;
knob.precisionDigits = 0;
});
/* ── Scales and ranges ──────────────────────────────────────────────── */
const pressure = document.getElementById('pressure');
pressure.min = 0;
pressure.max = 10;
pressure.value = 6.2;
pressure.unit = 'bar';
pressure.showUnit = true;
pressure.precisionDigits = 1;
pressure.showRanges = true;
pressure.ranges = [
{ startValue: 0, endValue: 2, className: 'warning' },
{ startValue: 2, endValue: 8, className: 'ok' },
{ startValue: 8, endValue: 10, className: 'critical' }
];
/* A log knob is what a decade-ranged instrument setting needs: the top of the scale
must not swallow the bottom three decades. */
const logScale = document.getElementById('logScale');
logScale.min = 1;
logScale.max = 1000;
logScale.logarithmicScale = true;
logScale.value = 100;
logScale.precisionDigits = 0;
logScale.knobStyle = 'arc';
const coerced = document.getElementById('coerced');
coerced.min = 0;
coerced.max = 100;
coerced.interval = 10;
coerced.coerce = true;
coerced.value = 40;
coerced.precisionDigits = 0;
const disabledKnob = document.getElementById('disabledKnob');
disabledKnob.min = 0;
disabledKnob.max = 100;
disabledKnob.value = 75;
disabledKnob.precisionDigits = 0;
});
return () => {
unmounted = true;
};
}, []);
return (
<div className="react-root" ref={root} theme="industrial">
<div className="smart-demo-container">
<section id="overview">
<h2>Smart.Knob is a rotary control for setting a value. It extends Tank.</h2>
<p>Because the component extends <code>Smart.Tank</code>, clamping, coercion to an interval, logarithmic scales, alarm ranges, units, inversion, right-to-left layout and the keyboard behaviour of the slider are inherited. This includes the <code>mechanicalAction</code> property, which defines when the control commits its value.</p>
</section>
<section>
<h2>A controller front panel</h2>
<p>Four settings of a temperature controller. Drag around a dial or up and down over it to change the value; the arrow keys, Home and End also work. Each change is a <code>change</code> event that the application writes to the controller, listed under the panel. The setpoint knob carries the alarm ranges of the loop, so the operator sees where the setting sits before it is written.</p>
<div className="panel">
<div className="panel-title">TIC-2108 Reactor temperature</div>
<div className="stations">
<div className="station">
<span className="caption">Setpoint</span>
<Knob id="setpoint" className="large"></Knob>
<span className="range">0 to 150 °C</span>
</div>
<div className="station">
<span className="caption">Output bias</span>
<Knob id="bias" className="large"></Knob>
<span className="range">0 to 100 %</span>
</div>
<div className="station">
<span className="caption">Ramp rate</span>
<Knob id="ramp" className="large" knobStyle="arc"></Knob>
<span className="range">0.5 to 20 °C/min</span>
</div>
<div className="station">
<span className="caption">High alarm</span>
<Knob id="alarm" className="large" knobStyle="dot"></Knob>
<span className="range">100 to 150 °C, steps of 5</span>
</div>
</div>
</div>
<ol id="log" className="log">
<li>Nothing written yet. Turn a knob.</li>
</ol>
</section>
<section>
<h2>Styles</h2>
<p>The <code>knobStyle</code> property selects a pointer, a dot on the dial or an arc that fills from the start of the scale.</p>
<div className="row">
<div className="cell">
<Knob id="k1"></Knob>
<span>pointer</span>
</div>
<div className="cell">
<Knob id="k2" knobStyle="dot"></Knob>
<span>dot</span>
</div>
<div className="cell">
<Knob id="k3" knobStyle="arc"></Knob>
<span>arc</span>
</div>
</div>
</section>
<section>
<h2>Scales and ranges</h2>
<p>Ranges are inherited from Tank, so a range has the same meaning as on a gauge or a tank. A logarithmic scale keeps the bottom decades usable, and <code>coerce</code> with an <code>interval</code> snaps the value to steps.</p>
<div className="row">
<div className="cell">
<Knob id="pressure"></Knob>
<span>Setpoint, bar</span>
</div>
<div className="cell">
<Knob id="logScale"></Knob>
<span>Logarithmic, 1 to 1000</span>
</div>
<div className="cell">
<Knob id="coerced"></Knob>
<span>Coerced to 10</span>
</div>
<div className="cell">
<Knob id="disabledKnob" disabled></Knob>
<span>Disabled</span>
</div>
</div>
<p className="hint">Keyboard: Tab to a knob, then use the arrow keys to step, Page Up and Page Down for larger steps, and Home and End for the limits.</p>
</section>
</div>
</div>
);
};
export default App;
react/App.css
.row { display: flex; flex-wrap: wrap; gap: 28px; align-items: flex-start; }
.cell { display: flex; flex-direction: column; align-items: center; gap: 6px; font-size: 12px; color: var(--smart-text-secondary-color); }
/* The front panel: a surface with a title strip and a station per setting. */
.panel {
display: inline-block;
min-width: 320px;
max-width: 100%;
border: 1px solid var(--smart-border);
border-radius: var(--smart-border-radius);
background: var(--smart-industrial-panel-background, var(--smart-surface));
}
.panel-title {
padding: 8px 16px;
border-bottom: 1px solid var(--smart-border);
background: var(--smart-industrial-panel-header-background, var(--smart-base-200));
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
letter-spacing: .04em;
color: var(--smart-text-secondary-color);
}
.stations {
display: flex;
flex-wrap: wrap;
gap: 8px 36px;
padding: 18px 24px 16px;
}
.station {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
min-width: 150px;
}
.station .caption {
font-size: 11px;
font-weight: 600;
letter-spacing: .08em;
text-transform: uppercase;
color: var(--smart-text-secondary-color);
}
.station .range {
font-size: 11px;
color: var(--smart-text-tertiary-color);
}
smart-knob.large {
--smart-knob-size: 128px;
}
smart-knob.large .smart-knob-readout {
font-size: 15px;
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.Knob is a rotary control for setting a value. It extends Tank.</h2>
<p>
Because the component extends <code>Smart.Tank</code>, clamping, coercion to an interval, logarithmic scales, alarm ranges, units, inversion, right-to-left layout and the keyboard behaviour of the slider are inherited. This includes the <code>mechanicalAction</code> property, which defines when the control commits its value.
</p>
</section>
<section>
<h2>A controller front panel</h2>
<p>
Four settings of a temperature controller. Drag around a dial or up and down over it to change the value; the arrow keys, Home and End also work. Each change is a <code>change</code> event that the application writes to the controller, listed under the panel. The setpoint knob carries the alarm ranges of the loop, so the operator sees where the setting sits before it is written.
</p>
<div class="panel">
<div class="panel-title">TIC-2108 Reactor temperature</div>
<div class="stations">
<div class="station">
<span class="caption">Setpoint</span>
<smart-knob id="setpoint" class="large"></smart-knob>
<span class="range">0 to 150 °C</span>
</div>
<div class="station">
<span class="caption">Output bias</span>
<smart-knob id="bias" class="large"></smart-knob>
<span class="range">0 to 100 %</span>
</div>
<div class="station">
<span class="caption">Ramp rate</span>
<smart-knob id="ramp" class="large" knob-style="arc"></smart-knob>
<span class="range">0.5 to 20 °C/min</span>
</div>
<div class="station">
<span class="caption">High alarm</span>
<smart-knob id="alarm" class="large" knob-style="dot"></smart-knob>
<span class="range">100 to 150 °C, steps of 5</span>
</div>
</div>
</div>
<ol id="log" class="log"><li>Nothing written yet. Turn a knob.</li></ol>
</section>
<section>
<h2>Styles</h2>
<p>The <code>knobStyle</code> property selects a pointer, a dot on the dial or an arc that fills from the start of the scale.</p>
<div class="row">
<div class="cell"><smart-knob id="k1"></smart-knob><span>pointer</span></div>
<div class="cell"><smart-knob id="k2" knob-style="dot"></smart-knob><span>dot</span></div>
<div class="cell"><smart-knob id="k3" knob-style="arc"></smart-knob><span>arc</span></div>
</div>
</section>
<section>
<h2>Scales and ranges</h2>
<p>Ranges are inherited from Tank, so a range has the same meaning as on a gauge or a tank. A logarithmic scale keeps the bottom decades usable, and <code>coerce</code> with an <code>interval</code> snaps the value to steps.</p>
<div class="row">
<div class="cell"><smart-knob id="pressure"></smart-knob><span>Setpoint, bar</span></div>
<div class="cell"><smart-knob id="logScale"></smart-knob><span>Logarithmic, 1 to 1000</span></div>
<div class="cell"><smart-knob id="coerced"></smart-knob><span>Coerced to 10</span></div>
<div class="cell"><smart-knob id="disabledKnob" disabled></smart-knob><span>Disabled</span></div>
</div>
<p class="hint">Keyboard: Tab to a knob, then use the arrow keys to step, Page Up and Page Down for larger steps, and Home and End for the limits.</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.knob.js";
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');
/* ── The controller front panel ────────────────────────────────────── */
const setpoint = document.getElementById('setpoint'),
bias = document.getElementById('bias'),
ramp = document.getElementById('ramp'),
alarm = document.getElementById('alarm');
setpoint.min = 0;
setpoint.max = 150;
setpoint.value = 118;
setpoint.unit = '°C';
setpoint.showUnit = true;
setpoint.precisionDigits = 1;
setpoint.showRanges = true;
setpoint.ranges = [
{ startValue: 0, endValue: 60, className: 'warning' },
{ startValue: 60, endValue: 125, className: 'ok' },
{ startValue: 125, endValue: 150, className: 'critical' }
];
bias.min = 0;
bias.max = 100;
bias.value = 42;
bias.unit = '%';
bias.showUnit = true;
bias.precisionDigits = 0;
ramp.min = 0.5;
ramp.max = 20;
ramp.value = 4;
ramp.unit = '°C/min';
ramp.showUnit = true;
ramp.precisionDigits = 1;
alarm.min = 100;
alarm.max = 150;
alarm.interval = 5;
alarm.coerce = true;
alarm.value = 125;
alarm.unit = '°C';
alarm.showUnit = true;
alarm.precisionDigits = 0;
/* Every knob is a control: its change event is what the application writes to the
controller. The list keeps the last few writes, newest first. */
const TAGS = { setpoint: 'TIC-2108.SP', bias: 'TIC-2108.BIAS', ramp: 'TIC-2108.RAMP', alarm: 'TAH-2108.LIMIT' };
[setpoint, bias, ramp, alarm].forEach(function (knob) {
knob.addEventListener('change', function (event) {
const item = document.createElement('li'),
value = Number(event.detail.value);
item.textContent = new Date().toLocaleTimeString() + ' ' + TAGS[knob.id] + ' = ' +
value.toFixed(knob.precisionDigits) + ' ' + knob.unit;
if (log.firstChild && log.firstChild.textContent.indexOf('Nothing written') === 0) {
log.removeChild(log.firstChild);
}
log.insertBefore(item, log.firstChild);
while (log.children.length > 5) {
log.removeChild(log.lastChild);
}
});
});
/* ── Styles ─────────────────────────────────────────────────────────── */
['k1', 'k2', 'k3'].forEach(function (id) {
const knob = document.getElementById(id);
knob.min = 0;
knob.max = 100;
knob.value = 62;
knob.precisionDigits = 0;
});
/* ── Scales and ranges ──────────────────────────────────────────────── */
const pressure = document.getElementById('pressure');
pressure.min = 0;
pressure.max = 10;
pressure.value = 6.2;
pressure.unit = 'bar';
pressure.showUnit = true;
pressure.precisionDigits = 1;
pressure.showRanges = true;
pressure.ranges = [
{ startValue: 0, endValue: 2, className: 'warning' },
{ startValue: 2, endValue: 8, className: 'ok' },
{ startValue: 8, endValue: 10, className: 'critical' }
];
/* A log knob is what a decade-ranged instrument setting needs: the top of the scale
must not swallow the bottom three decades. */
const logScale = document.getElementById('logScale');
logScale.min = 1;
logScale.max = 1000;
logScale.logarithmicScale = true;
logScale.value = 100;
logScale.precisionDigits = 0;
logScale.knobStyle = 'arc';
const coerced = document.getElementById('coerced');
coerced.min = 0;
coerced.max = 100;
coerced.interval = 10;
coerced.coerce = true;
coerced.value = 40;
coerced.precisionDigits = 0;
const disabledKnob = document.getElementById('disabledKnob');
disabledKnob.min = 0;
disabledKnob.max = 100;
disabledKnob.value = 75;
disabledKnob.precisionDigits = 0;
});
});
}
};
</script>
<style>
.row { display: flex; flex-wrap: wrap; gap: 28px; align-items: flex-start; }
.cell { display: flex; flex-direction: column; align-items: center; gap: 6px; font-size: 12px; color: var(--smart-text-secondary-color); }
/* The front panel: a surface with a title strip and a station per setting. */
.panel {
display: inline-block;
min-width: 320px;
max-width: 100%;
border: 1px solid var(--smart-border);
border-radius: var(--smart-border-radius);
background: var(--smart-industrial-panel-background, var(--smart-surface));
}
.panel-title {
padding: 8px 16px;
border-bottom: 1px solid var(--smart-border);
background: var(--smart-industrial-panel-header-background, var(--smart-base-200));
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
letter-spacing: .04em;
color: var(--smart-text-secondary-color);
}
.stations {
display: flex;
flex-wrap: wrap;
gap: 8px 36px;
padding: 18px 24px 16px;
}
.station {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
min-width: 150px;
}
.station .caption {
font-size: 11px;
font-weight: 600;
letter-spacing: .08em;
text-transform: uppercase;
color: var(--smart-text-secondary-color);
}
.station .range {
font-size: 11px;
color: var(--smart-text-tertiary-color);
}
smart-knob.large {
--smart-knob-size: 128px;
}
smart-knob.large .smart-knob-readout {
font-size: 15px;
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 controller front panel
- Styles
- Scales and ranges
Also in instrument readouts and controls
Bar GraphBit FieldJog PanelMomentary ButtonNumeric KeypadPanel MeterSelector SwitchSeven SegmentThermometerTouch Keyboard