@oicl/openbridge-webcomponents-full-bundle 2.0.0-next.110 → 2.0.0-next.112
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundle/openbridge-webcomponents.bundle.js +2370 -1522
- package/bundle/openbridge-webcomponents.bundle.js.map +1 -1
- package/custom-elements.json +641 -7
- package/dist/automation/indicator-battery/indicator-battery.css.js +52 -0
- package/dist/automation/indicator-battery/indicator-battery.css.js.map +1 -0
- package/dist/automation/indicator-battery/indicator-battery.d.ts +65 -0
- package/dist/automation/indicator-battery/indicator-battery.d.ts.map +1 -0
- package/dist/automation/indicator-battery/indicator-battery.js +119 -0
- package/dist/automation/indicator-battery/indicator-battery.js.map +1 -0
- package/dist/automation/indicator-engine/indicator-engine.css.js +45 -0
- package/dist/automation/indicator-engine/indicator-engine.css.js.map +1 -0
- package/dist/automation/indicator-engine/indicator-engine.d.ts +34 -0
- package/dist/automation/indicator-engine/indicator-engine.d.ts.map +1 -0
- package/dist/automation/indicator-engine/indicator-engine.js +73 -0
- package/dist/automation/indicator-engine/indicator-engine.js.map +1 -0
- package/dist/automation/indicator-generator/indicator-generator-geometry.d.ts +30 -0
- package/dist/automation/indicator-generator/indicator-generator-geometry.d.ts.map +1 -0
- package/dist/automation/indicator-generator/indicator-generator-geometry.js +61 -0
- package/dist/automation/indicator-generator/indicator-generator-geometry.js.map +1 -0
- package/dist/automation/indicator-generator/indicator-generator.css.js +121 -0
- package/dist/automation/indicator-generator/indicator-generator.css.js.map +1 -0
- package/dist/automation/indicator-generator/indicator-generator.d.ts +57 -0
- package/dist/automation/indicator-generator/indicator-generator.d.ts.map +1 -0
- package/dist/automation/indicator-generator/indicator-generator.js +136 -0
- package/dist/automation/indicator-generator/indicator-generator.js.map +1 -0
- package/dist/automation/indicator-shared/linear-indicator.d.ts +46 -0
- package/dist/automation/indicator-shared/linear-indicator.d.ts.map +1 -0
- package/dist/automation/indicator-shared/linear-indicator.js +175 -0
- package/dist/automation/indicator-shared/linear-indicator.js.map +1 -0
- package/dist/automation/indicator-tank-atmospheric/indicator-tank-atmospheric.css.js +48 -0
- package/dist/automation/indicator-tank-atmospheric/indicator-tank-atmospheric.css.js.map +1 -0
- package/dist/automation/indicator-tank-atmospheric/indicator-tank-atmospheric.d.ts +65 -0
- package/dist/automation/indicator-tank-atmospheric/indicator-tank-atmospheric.d.ts.map +1 -0
- package/dist/automation/indicator-tank-atmospheric/indicator-tank-atmospheric.js +105 -0
- package/dist/automation/indicator-tank-atmospheric/indicator-tank-atmospheric.js.map +1 -0
- package/dist/navigation-instruments/compass/compass.d.ts +1 -1
- package/dist/navigation-instruments/compass/compass.d.ts.map +1 -1
- package/dist/navigation-instruments/compass/compass.js +1 -1
- package/dist/navigation-instruments/compass/compass.js.map +1 -1
- package/dist/navigation-instruments/current/current.d.ts +25 -0
- package/dist/navigation-instruments/current/current.d.ts.map +1 -1
- package/dist/navigation-instruments/current/current.js +25 -4
- package/dist/navigation-instruments/current/current.js.map +1 -1
- package/dist/navigation-instruments/heading/heading.js +1 -1
- package/dist/navigation-instruments/heading/heading.js.map +1 -1
- package/dist/navigation-instruments/pitch-roll-heave/pitch-roll-heave.d.ts +6 -0
- package/dist/navigation-instruments/pitch-roll-heave/pitch-roll-heave.d.ts.map +1 -1
- package/dist/navigation-instruments/pitch-roll-heave/pitch-roll-heave.js +8 -1
- package/dist/navigation-instruments/pitch-roll-heave/pitch-roll-heave.js.map +1 -1
- package/dist/navigation-instruments/rate-of-turn/rate-of-turn.d.ts +5 -2
- package/dist/navigation-instruments/rate-of-turn/rate-of-turn.d.ts.map +1 -1
- package/dist/navigation-instruments/rate-of-turn/rate-of-turn.js.map +1 -1
- package/dist/navigation-instruments/top-view-propulsion/top-view-propulsion.d.ts +6 -0
- package/dist/navigation-instruments/top-view-propulsion/top-view-propulsion.d.ts.map +1 -1
- package/dist/navigation-instruments/top-view-propulsion/top-view-propulsion.js +19 -4
- package/dist/navigation-instruments/top-view-propulsion/top-view-propulsion.js.map +1 -1
- package/dist/navigation-instruments/watch/environment.d.ts +8 -0
- package/dist/navigation-instruments/watch/environment.d.ts.map +1 -1
- package/dist/navigation-instruments/watch/environment.js +3 -0
- package/dist/navigation-instruments/watch/environment.js.map +1 -1
- package/dist/navigation-instruments/watch/force-pattern.d.ts +20 -2
- package/dist/navigation-instruments/watch/force-pattern.d.ts.map +1 -1
- package/dist/navigation-instruments/watch/force-pattern.js +12 -5
- package/dist/navigation-instruments/watch/force-pattern.js.map +1 -1
- package/dist/navigation-instruments/watch/watch.d.ts.map +1 -1
- package/dist/navigation-instruments/watch/watch.js +6 -4
- package/dist/navigation-instruments/watch/watch.js.map +1 -1
- package/dist/navigation-instruments/wind/wind.d.ts +5 -2
- package/dist/navigation-instruments/wind/wind.d.ts.map +1 -1
- package/dist/navigation-instruments/wind/wind.js +5 -1
- package/dist/navigation-instruments/wind/wind.js.map +1 -1
- package/package.json +1 -1
- package/src/automation/indicator-battery/indicator-battery.css +42 -0
- package/src/automation/indicator-battery/indicator-battery.stories.ts +94 -0
- package/src/automation/indicator-battery/indicator-battery.ts +149 -0
- package/src/automation/indicator-engine/indicator-engine.css +35 -0
- package/src/automation/indicator-engine/indicator-engine.stories.ts +48 -0
- package/src/automation/indicator-engine/indicator-engine.ts +78 -0
- package/src/automation/indicator-generator/indicator-generator-geometry.spec.ts +62 -0
- package/src/automation/indicator-generator/indicator-generator-geometry.ts +63 -0
- package/src/automation/indicator-generator/indicator-generator.css +111 -0
- package/src/automation/indicator-generator/indicator-generator.stories.ts +103 -0
- package/src/automation/indicator-generator/indicator-generator.ts +184 -0
- package/src/automation/indicator-shared/linear-indicator.spec.ts +90 -0
- package/src/automation/indicator-shared/linear-indicator.ts +230 -0
- package/src/automation/indicator-tank-atmospheric/indicator-tank-atmospheric.css +38 -0
- package/src/automation/indicator-tank-atmospheric/indicator-tank-atmospheric.stories.ts +94 -0
- package/src/automation/indicator-tank-atmospheric/indicator-tank-atmospheric.ts +138 -0
- package/src/navigation-instruments/compass/compass.ts +2 -2
- package/src/navigation-instruments/current/current.stories.ts +33 -0
- package/src/navigation-instruments/current/current.ts +37 -3
- package/src/navigation-instruments/heading/heading.stories.ts +6 -4
- package/src/navigation-instruments/heading/heading.ts +1 -1
- package/src/navigation-instruments/pitch-roll-heave/pitch-roll-heave.ts +8 -1
- package/src/navigation-instruments/rate-of-turn/rate-of-turn.stories.ts +3 -3
- package/src/navigation-instruments/rate-of-turn/rate-of-turn.ts +5 -2
- package/src/navigation-instruments/top-view-propulsion/top-view-propulsion.ts +20 -2
- package/src/navigation-instruments/watch/environment.ts +10 -0
- package/src/navigation-instruments/watch/force-pattern.spec.ts +42 -0
- package/src/navigation-instruments/watch/force-pattern.ts +44 -7
- package/src/navigation-instruments/watch/watch.ts +20 -5
- package/src/navigation-instruments/wind/wind.spec.ts +3 -1
- package/src/navigation-instruments/wind/wind.ts +12 -3
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type {Meta, StoryObj} from '@storybook/web-components-vite';
|
|
2
|
+
import {html} from 'lit';
|
|
3
|
+
import {
|
|
4
|
+
ObcIndicatorBattery,
|
|
5
|
+
IndicatorBatteryVariant,
|
|
6
|
+
IndicatorBatteryValue,
|
|
7
|
+
} from './indicator-battery.js';
|
|
8
|
+
import './indicator-battery.js';
|
|
9
|
+
import {IndicatorDirection} from '../indicator-shared/linear-indicator.js';
|
|
10
|
+
|
|
11
|
+
const meta = {
|
|
12
|
+
title: 'Automation/Indicators/Indicator Battery',
|
|
13
|
+
component: 'obc-indicator-battery',
|
|
14
|
+
tags: ['autodocs', '6.0'],
|
|
15
|
+
args: {
|
|
16
|
+
direction: IndicatorDirection.vertical,
|
|
17
|
+
variant: IndicatorBatteryVariant.bar,
|
|
18
|
+
value: IndicatorBatteryValue.regular,
|
|
19
|
+
level: 40,
|
|
20
|
+
data: [60, 52, 58, 45, 50, 42, 44, 40],
|
|
21
|
+
hasIcon: false,
|
|
22
|
+
},
|
|
23
|
+
argTypes: {
|
|
24
|
+
direction: {
|
|
25
|
+
control: 'radio',
|
|
26
|
+
options: Object.values(IndicatorDirection),
|
|
27
|
+
},
|
|
28
|
+
variant: {
|
|
29
|
+
control: 'radio',
|
|
30
|
+
options: Object.values(IndicatorBatteryVariant),
|
|
31
|
+
},
|
|
32
|
+
value: {
|
|
33
|
+
control: 'radio',
|
|
34
|
+
options: Object.values(IndicatorBatteryValue),
|
|
35
|
+
},
|
|
36
|
+
level: {control: {type: 'range', min: 0, max: 100}},
|
|
37
|
+
},
|
|
38
|
+
render: (args) => html`
|
|
39
|
+
<obc-indicator-battery
|
|
40
|
+
.direction=${args.direction}
|
|
41
|
+
.variant=${args.variant}
|
|
42
|
+
.value=${args.value}
|
|
43
|
+
.level=${args.level}
|
|
44
|
+
.data=${args.data}
|
|
45
|
+
.hasIcon=${args.hasIcon}
|
|
46
|
+
></obc-indicator-battery>
|
|
47
|
+
`,
|
|
48
|
+
} satisfies Meta<Partial<ObcIndicatorBattery>>;
|
|
49
|
+
|
|
50
|
+
export default meta;
|
|
51
|
+
type Story = StoryObj<Partial<ObcIndicatorBattery>>;
|
|
52
|
+
|
|
53
|
+
export const Default: Story = {};
|
|
54
|
+
|
|
55
|
+
export const BarHorizontal: Story = {
|
|
56
|
+
args: {direction: IndicatorDirection.horizontal},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const TrendVertical: Story = {
|
|
60
|
+
args: {variant: IndicatorBatteryVariant.trend},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const TrendHorizontal: Story = {
|
|
64
|
+
args: {
|
|
65
|
+
variant: IndicatorBatteryVariant.trend,
|
|
66
|
+
direction: IndicatorDirection.horizontal,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const Enhanced: Story = {
|
|
71
|
+
args: {value: IndicatorBatteryValue.enhanced},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const Categorical: Story = {
|
|
75
|
+
args: {value: IndicatorBatteryValue.categorical},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const TrendEnhanced: Story = {
|
|
79
|
+
args: {
|
|
80
|
+
variant: IndicatorBatteryVariant.trend,
|
|
81
|
+
value: IndicatorBatteryValue.enhanced,
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const TrendCategorical: Story = {
|
|
86
|
+
args: {
|
|
87
|
+
variant: IndicatorBatteryVariant.trend,
|
|
88
|
+
value: IndicatorBatteryValue.categorical,
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export const WithIcon: Story = {
|
|
93
|
+
args: {hasIcon: true},
|
|
94
|
+
};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import {LitElement, html, unsafeCSS, nothing} from 'lit';
|
|
2
|
+
import {property} from 'lit/decorators.js';
|
|
3
|
+
import componentStyle from './indicator-battery.css?inline';
|
|
4
|
+
import {customElement} from '../../decorator.js';
|
|
5
|
+
import '../../icons/icon-energy-battery.js';
|
|
6
|
+
import {
|
|
7
|
+
IndicatorDirection,
|
|
8
|
+
LinearIndicatorScheme,
|
|
9
|
+
Rect,
|
|
10
|
+
renderLinearBar,
|
|
11
|
+
renderTrendGraph,
|
|
12
|
+
} from '../indicator-shared/linear-indicator.js';
|
|
13
|
+
|
|
14
|
+
export enum IndicatorBatteryVariant {
|
|
15
|
+
bar = 'bar',
|
|
16
|
+
trend = 'trend',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export enum IndicatorBatteryValue {
|
|
20
|
+
regular = 'regular',
|
|
21
|
+
enhanced = 'enhanced',
|
|
22
|
+
categorical = 'categorical',
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const HORIZONTAL_SHELL: Rect = {x: 6, y: 8, width: 36, height: 32};
|
|
26
|
+
const VERTICAL_SHELL: Rect = {x: 8, y: 6, width: 32, height: 36};
|
|
27
|
+
const HORIZONTAL_BAR: Rect = {x: 9, y: 11, width: 30, height: 26};
|
|
28
|
+
const VERTICAL_BAR: Rect = {x: 11, y: 9, width: 26, height: 30};
|
|
29
|
+
const HORIZONTAL_TREND: Rect = {x: 8, y: 10, width: 32, height: 28};
|
|
30
|
+
const VERTICAL_TREND: Rect = {x: 10, y: 8, width: 28, height: 32};
|
|
31
|
+
|
|
32
|
+
const HORIZONTAL_TIP_PATH =
|
|
33
|
+
'M42 18 H44 A2 2 0 0 1 46 20 V28 A2 2 0 0 1 44 30 H42 Z';
|
|
34
|
+
const VERTICAL_TIP_PATH = 'M18 6 V4 A2 2 0 0 1 20 2 H28 A2 2 0 0 1 30 4 V6 Z';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Compact 48×48 battery charge indicator.
|
|
38
|
+
*
|
|
39
|
+
* ## Features / Variants
|
|
40
|
+
* - `variant`: `bar` (bottom-up percentage fill) or `trend` (history graph
|
|
41
|
+
* with a current-value micro-bar)
|
|
42
|
+
* - `direction`: `vertical` or `horizontal` battery shell
|
|
43
|
+
* - `value`: color scheme — `regular`, `enhanced` or `categorical`
|
|
44
|
+
* - `level`: current charge 0–100 (%), drives the fill and micro-bar
|
|
45
|
+
* - `data`: history samples 0–100 (%) drawn as the trend graph
|
|
46
|
+
* - `hasIcon`: centered icon overlay (bar variant only)
|
|
47
|
+
*
|
|
48
|
+
* ## Usage Guidelines
|
|
49
|
+
* Use as an at-a-glance charge indicator in dashboards and lists. For a
|
|
50
|
+
* full-featured tank/battery cell with readouts, badges and setpoints, use
|
|
51
|
+
* `obc-automation-tank` (type `battery`) instead.
|
|
52
|
+
*
|
|
53
|
+
* ## Best Practices / Constraints
|
|
54
|
+
* - `level` and `data` values are clamped to 0–100; values outside that
|
|
55
|
+
* range are pinned to the nearest bound.
|
|
56
|
+
* - The `trend` variant needs at least two `data` samples to draw a line;
|
|
57
|
+
* with fewer it shows only the current-value micro-bar.
|
|
58
|
+
* - The `icon` slot renders only in the `bar` variant; it is ignored in
|
|
59
|
+
* `trend`.
|
|
60
|
+
* - Display-only: the root SVG is `aria-hidden`; convey battery state to
|
|
61
|
+
* assistive tech through adjacent text if it is not otherwise available.
|
|
62
|
+
*
|
|
63
|
+
* ## Slots
|
|
64
|
+
* | Slot | Condition | Purpose |
|
|
65
|
+
* | ------ | ----------------------------- | ------------------------------ |
|
|
66
|
+
* | `icon` | `hasIcon` and `variant='bar'` | Icon overlay (`obi-*` element) |
|
|
67
|
+
*
|
|
68
|
+
* @slot icon - Centered icon overlay, defaults to `obi-energy-battery`
|
|
69
|
+
*/
|
|
70
|
+
@customElement('obc-indicator-battery')
|
|
71
|
+
export class ObcIndicatorBattery extends LitElement {
|
|
72
|
+
@property({type: String, reflect: true}) direction: IndicatorDirection =
|
|
73
|
+
IndicatorDirection.vertical;
|
|
74
|
+
@property({type: String, reflect: true}) variant: IndicatorBatteryVariant =
|
|
75
|
+
IndicatorBatteryVariant.bar;
|
|
76
|
+
@property({type: String, reflect: true}) value: IndicatorBatteryValue =
|
|
77
|
+
IndicatorBatteryValue.regular;
|
|
78
|
+
/** Current charge, 0-100 (%) */
|
|
79
|
+
@property({type: Number}) level = 0;
|
|
80
|
+
/** History samples, 0-100 (%) each, oldest first */
|
|
81
|
+
@property({type: Array}) data: number[] = [];
|
|
82
|
+
@property({type: Boolean}) hasIcon = false;
|
|
83
|
+
|
|
84
|
+
private get scheme(): LinearIndicatorScheme {
|
|
85
|
+
switch (this.value) {
|
|
86
|
+
case IndicatorBatteryValue.enhanced:
|
|
87
|
+
return LinearIndicatorScheme.enhanced;
|
|
88
|
+
case IndicatorBatteryValue.categorical:
|
|
89
|
+
return LinearIndicatorScheme.categorical;
|
|
90
|
+
default:
|
|
91
|
+
return LinearIndicatorScheme.regular;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
override render() {
|
|
96
|
+
const horizontal = this.direction === IndicatorDirection.horizontal;
|
|
97
|
+
const trend = this.variant === IndicatorBatteryVariant.trend;
|
|
98
|
+
const shell = horizontal ? HORIZONTAL_SHELL : VERTICAL_SHELL;
|
|
99
|
+
const barRect = horizontal
|
|
100
|
+
? trend
|
|
101
|
+
? HORIZONTAL_TREND
|
|
102
|
+
: HORIZONTAL_BAR
|
|
103
|
+
: trend
|
|
104
|
+
? VERTICAL_TREND
|
|
105
|
+
: VERTICAL_BAR;
|
|
106
|
+
const showIcon =
|
|
107
|
+
this.hasIcon && this.variant === IndicatorBatteryVariant.bar;
|
|
108
|
+
return html`
|
|
109
|
+
<svg viewBox="0 0 48 48" aria-hidden="true">
|
|
110
|
+
<path
|
|
111
|
+
class="tip"
|
|
112
|
+
d=${horizontal ? HORIZONTAL_TIP_PATH : VERTICAL_TIP_PATH}
|
|
113
|
+
/>
|
|
114
|
+
<rect
|
|
115
|
+
class="shell"
|
|
116
|
+
x=${shell.x}
|
|
117
|
+
y=${shell.y}
|
|
118
|
+
width=${shell.width}
|
|
119
|
+
height=${shell.height}
|
|
120
|
+
rx="4"
|
|
121
|
+
/>
|
|
122
|
+
${this.variant === IndicatorBatteryVariant.trend
|
|
123
|
+
? renderTrendGraph(
|
|
124
|
+
barRect,
|
|
125
|
+
this.data,
|
|
126
|
+
this.level,
|
|
127
|
+
this.scheme,
|
|
128
|
+
'battery'
|
|
129
|
+
)
|
|
130
|
+
: renderLinearBar(barRect, this.level, this.scheme, 'battery')}
|
|
131
|
+
</svg>
|
|
132
|
+
${showIcon
|
|
133
|
+
? html`<div class="icon-wrapper">
|
|
134
|
+
<slot name="icon">
|
|
135
|
+
<obi-energy-battery usecsscolor></obi-energy-battery>
|
|
136
|
+
</slot>
|
|
137
|
+
</div>`
|
|
138
|
+
: nothing}
|
|
139
|
+
`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
static override styles = unsafeCSS(componentStyle);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
declare global {
|
|
146
|
+
interface HTMLElementTagNameMap {
|
|
147
|
+
'obc-indicator-battery': ObcIndicatorBattery;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
:host {
|
|
2
|
+
display: block;
|
|
3
|
+
width: 48px;
|
|
4
|
+
height: 48px;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
svg {
|
|
8
|
+
display: block;
|
|
9
|
+
width: 100%;
|
|
10
|
+
height: 100%;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.frame {
|
|
14
|
+
fill: var(--instrument-frame-primary-color);
|
|
15
|
+
stroke: var(--instrument-frame-tertiary-color);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.rail {
|
|
19
|
+
fill: var(--instrument-frame-tertiary-color);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.cylinder {
|
|
23
|
+
fill: var(--instrument-frame-tertiary-color);
|
|
24
|
+
stroke: var(--instrument-frame-tertiary-color);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
:host([value="regular"]) .cylinder {
|
|
28
|
+
fill: var(--automation-device-secondary-color);
|
|
29
|
+
stroke: var(--element-neutral-color);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
:host([value="enhanced"]) .cylinder {
|
|
33
|
+
fill: var(--base-blue-300);
|
|
34
|
+
stroke: var(--element-neutral-enhanced-color);
|
|
35
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type {Meta, StoryObj} from '@storybook/web-components-vite';
|
|
2
|
+
import {html} from 'lit';
|
|
3
|
+
import {ObcIndicatorEngine, IndicatorEngineValue} from './indicator-engine.js';
|
|
4
|
+
import './indicator-engine.js';
|
|
5
|
+
import {IndicatorDirection} from '../indicator-shared/linear-indicator.js';
|
|
6
|
+
|
|
7
|
+
const meta = {
|
|
8
|
+
title: 'Automation/Indicators/Indicator Engine',
|
|
9
|
+
component: 'obc-indicator-engine',
|
|
10
|
+
tags: ['autodocs', '6.0'],
|
|
11
|
+
args: {
|
|
12
|
+
value: IndicatorEngineValue.static,
|
|
13
|
+
direction: IndicatorDirection.vertical,
|
|
14
|
+
},
|
|
15
|
+
argTypes: {
|
|
16
|
+
value: {
|
|
17
|
+
control: 'radio',
|
|
18
|
+
options: Object.values(IndicatorEngineValue),
|
|
19
|
+
},
|
|
20
|
+
direction: {
|
|
21
|
+
control: 'radio',
|
|
22
|
+
options: Object.values(IndicatorDirection),
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
render: (args) => html`
|
|
26
|
+
<obc-indicator-engine
|
|
27
|
+
.value=${args.value}
|
|
28
|
+
.direction=${args.direction}
|
|
29
|
+
></obc-indicator-engine>
|
|
30
|
+
`,
|
|
31
|
+
} satisfies Meta<Partial<ObcIndicatorEngine>>;
|
|
32
|
+
|
|
33
|
+
export default meta;
|
|
34
|
+
type Story = StoryObj<Partial<ObcIndicatorEngine>>;
|
|
35
|
+
|
|
36
|
+
export const Default: Story = {};
|
|
37
|
+
|
|
38
|
+
export const Horizontal: Story = {
|
|
39
|
+
args: {direction: IndicatorDirection.horizontal},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const Regular: Story = {
|
|
43
|
+
args: {value: IndicatorEngineValue.regular},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const Enhanced: Story = {
|
|
47
|
+
args: {value: IndicatorEngineValue.enhanced},
|
|
48
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import {LitElement, html, svg, unsafeCSS} from 'lit';
|
|
2
|
+
import {property} from 'lit/decorators.js';
|
|
3
|
+
import componentStyle from './indicator-engine.css?inline';
|
|
4
|
+
import {customElement} from '../../decorator.js';
|
|
5
|
+
import {IndicatorDirection} from '../indicator-shared/linear-indicator.js';
|
|
6
|
+
|
|
7
|
+
export enum IndicatorEngineValue {
|
|
8
|
+
static = 'static',
|
|
9
|
+
regular = 'regular',
|
|
10
|
+
enhanced = 'enhanced',
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const CYLINDER_RADIUS = 3;
|
|
14
|
+
const HORIZONTAL_CYLINDER_CENTERS = [12, 20, 28, 36];
|
|
15
|
+
const VERTICAL_CYLINDER_CENTERS = [12, 20, 28, 36];
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Compact 48×48 engine indicator glyph.
|
|
19
|
+
*
|
|
20
|
+
* ## Features / Variants
|
|
21
|
+
* - `direction`: `vertical` (upright block with side rails) or `horizontal`
|
|
22
|
+
* (lying block on a base rail)
|
|
23
|
+
* - `value`: cylinder color — `static`, `regular` or `enhanced`
|
|
24
|
+
*
|
|
25
|
+
* ## Usage Guidelines
|
|
26
|
+
* Use as a static engine presence/state glyph in dashboards and lists.
|
|
27
|
+
* It carries no numeric value; pair it with readouts or other indicators
|
|
28
|
+
* when quantities must be shown.
|
|
29
|
+
*/
|
|
30
|
+
@customElement('obc-indicator-engine')
|
|
31
|
+
export class ObcIndicatorEngine extends LitElement {
|
|
32
|
+
@property({type: String, reflect: true}) value: IndicatorEngineValue =
|
|
33
|
+
IndicatorEngineValue.static;
|
|
34
|
+
@property({type: String, reflect: true}) direction: IndicatorDirection =
|
|
35
|
+
IndicatorDirection.vertical;
|
|
36
|
+
|
|
37
|
+
private renderHorizontal() {
|
|
38
|
+
return svg`
|
|
39
|
+
<rect class="rail" x="6" y="38" width="36" height="4" rx="2" />
|
|
40
|
+
<rect class="frame" x="6" y="12" width="36" height="24" rx="4" />
|
|
41
|
+
<rect class="rail" x="9" y="24" width="30" height="1" rx="0.5" />
|
|
42
|
+
${HORIZONTAL_CYLINDER_CENTERS.map(
|
|
43
|
+
(cx) =>
|
|
44
|
+
svg`<circle class="cylinder" cx=${cx} cy="18" r=${CYLINDER_RADIUS} />`
|
|
45
|
+
)}
|
|
46
|
+
`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private renderVertical() {
|
|
50
|
+
return svg`
|
|
51
|
+
<rect class="frame" x="12" y="6" width="24" height="36" rx="4" />
|
|
52
|
+
<rect class="rail" x="16" y="10" width="1" height="28" rx="0.5" />
|
|
53
|
+
<rect class="rail" x="31" y="10" width="1" height="28" rx="0.5" />
|
|
54
|
+
${VERTICAL_CYLINDER_CENTERS.map(
|
|
55
|
+
(cy) =>
|
|
56
|
+
svg`<circle class="cylinder" cx="24" cy=${cy} r=${CYLINDER_RADIUS} />`
|
|
57
|
+
)}
|
|
58
|
+
`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
override render() {
|
|
62
|
+
return html`
|
|
63
|
+
<svg viewBox="0 0 48 48" aria-hidden="true">
|
|
64
|
+
${this.direction === IndicatorDirection.horizontal
|
|
65
|
+
? this.renderHorizontal()
|
|
66
|
+
: this.renderVertical()}
|
|
67
|
+
</svg>
|
|
68
|
+
`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
static override styles = unsafeCSS(componentStyle);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
declare global {
|
|
75
|
+
interface HTMLElementTagNameMap {
|
|
76
|
+
'obc-indicator-engine': ObcIndicatorEngine;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import {describe, it, expect} from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
arcPath,
|
|
4
|
+
progressSweep,
|
|
5
|
+
polarPoint,
|
|
6
|
+
} from './indicator-generator-geometry.js';
|
|
7
|
+
|
|
8
|
+
describe('progressSweep', () => {
|
|
9
|
+
it('is 0 at level 0', () => {
|
|
10
|
+
expect(progressSweep(0)).toBe(0);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('is 360 at level 100', () => {
|
|
14
|
+
expect(progressSweep(100)).toBe(360);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('interpolates linearly', () => {
|
|
18
|
+
expect(progressSweep(50)).toBeCloseTo(180);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('clamps out-of-range levels', () => {
|
|
22
|
+
expect(progressSweep(150)).toBe(360);
|
|
23
|
+
expect(progressSweep(-10)).toBe(0);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('polarPoint', () => {
|
|
28
|
+
it('maps angle 0 to the top of the circle', () => {
|
|
29
|
+
expect(polarPoint(24, 24, 18, 0)).toEqual({x: 24, y: 6});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('maps angle 90 to the right of the circle', () => {
|
|
33
|
+
expect(polarPoint(24, 24, 18, 90)).toEqual({x: 42, y: 24});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('maps angle 180 to the bottom of the circle', () => {
|
|
37
|
+
expect(polarPoint(24, 24, 18, 180)).toEqual({x: 24, y: 42});
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('arcPath', () => {
|
|
42
|
+
it('returns empty string for zero or negative sweep', () => {
|
|
43
|
+
expect(arcPath(24, 24, 18, 0, 0)).toBe('');
|
|
44
|
+
expect(arcPath(24, 24, 18, 90, 45)).toBe('');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('uses a single arc segment for sweeps up to 180', () => {
|
|
48
|
+
const d = arcPath(24, 24, 18, 0, 90);
|
|
49
|
+
expect(d.startsWith('M')).toBe(true);
|
|
50
|
+
expect((d.match(/A/g) ?? []).length).toBe(1);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('splits sweeps above 180 into two arc segments', () => {
|
|
54
|
+
const d = arcPath(24, 24, 18, 0, 270);
|
|
55
|
+
expect((d.match(/A/g) ?? []).length).toBe(2);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('starts at the start angle', () => {
|
|
59
|
+
const d = arcPath(24, 24, 18, 0, 90);
|
|
60
|
+
expect(d.startsWith('M 24 6')).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export const BUTTON_SILHOUETTE_RADIUS = 18.5;
|
|
2
|
+
export const DISC_RADIUS = 17.5;
|
|
3
|
+
export const FILLED_DISC_RADIUS = 18.5;
|
|
4
|
+
export const BAR_SILHOUETTE_RADIUS = 19.5;
|
|
5
|
+
export const BAR_SILHOUETTE_STROKE_WIDTH = 3;
|
|
6
|
+
export const BAR_OUTLINE_RADIUS = 16.5;
|
|
7
|
+
export const DOUBLE_BAR_OUTLINE_RADIUS = 17.5;
|
|
8
|
+
export const OUTLINE_STROKE_WIDTH = 3;
|
|
9
|
+
export const BAR_TRACK_RADIUS = 18;
|
|
10
|
+
export const BAR_TRACK_STROKE_WIDTH = 4;
|
|
11
|
+
export const DOUBLE_BAR_OUTER_RADIUS = 22;
|
|
12
|
+
export const DOUBLE_BAR_OUTER_STROKE_WIDTH = 4;
|
|
13
|
+
export const DOUBLE_BAR_INNER_RADIUS = 18;
|
|
14
|
+
export const DOUBLE_BAR_INNER_STROKE_WIDTH = 2;
|
|
15
|
+
|
|
16
|
+
export function clampPercent(value: number): number {
|
|
17
|
+
if (Number.isNaN(value)) return 0;
|
|
18
|
+
return Math.min(100, Math.max(0, value));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Sweep (deg) of the progress arc, clockwise from 12 o'clock. */
|
|
22
|
+
export function progressSweep(level: number): number {
|
|
23
|
+
return (360 * clampPercent(level)) / 100;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Point on a circle; angle in degrees clockwise from 12 o'clock. */
|
|
27
|
+
export function polarPoint(
|
|
28
|
+
cx: number,
|
|
29
|
+
cy: number,
|
|
30
|
+
radius: number,
|
|
31
|
+
angleDeg: number
|
|
32
|
+
): {x: number; y: number} {
|
|
33
|
+
const rad = (angleDeg * Math.PI) / 180;
|
|
34
|
+
return {
|
|
35
|
+
x: Math.round((cx + radius * Math.sin(rad)) * 1000) / 1000,
|
|
36
|
+
y: Math.round((cy - radius * Math.cos(rad)) * 1000) / 1000,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Circular arc path from startAngle to endAngle (deg, clockwise from
|
|
42
|
+
* 12 o'clock), meant to be stroked. Sweeps above 180° are split into two
|
|
43
|
+
* arc segments; non-positive sweeps yield an empty string. Full circles
|
|
44
|
+
* (sweep 360) should be drawn with a `<circle>` element instead.
|
|
45
|
+
*/
|
|
46
|
+
export function arcPath(
|
|
47
|
+
cx: number,
|
|
48
|
+
cy: number,
|
|
49
|
+
radius: number,
|
|
50
|
+
startAngleDeg: number,
|
|
51
|
+
endAngleDeg: number
|
|
52
|
+
): string {
|
|
53
|
+
const sweep = endAngleDeg - startAngleDeg;
|
|
54
|
+
if (sweep <= 0) return '';
|
|
55
|
+
const start = polarPoint(cx, cy, radius, startAngleDeg);
|
|
56
|
+
if (sweep > 180) {
|
|
57
|
+
const mid = polarPoint(cx, cy, radius, startAngleDeg + sweep / 2);
|
|
58
|
+
const end = polarPoint(cx, cy, radius, endAngleDeg);
|
|
59
|
+
return `M ${start.x} ${start.y} A ${radius} ${radius} 0 0 1 ${mid.x} ${mid.y} A ${radius} ${radius} 0 0 1 ${end.x} ${end.y}`;
|
|
60
|
+
}
|
|
61
|
+
const end = polarPoint(cx, cy, radius, endAngleDeg);
|
|
62
|
+
return `M ${start.x} ${start.y} A ${radius} ${radius} 0 0 1 ${end.x} ${end.y}`;
|
|
63
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
:host {
|
|
2
|
+
display: block;
|
|
3
|
+
position: relative;
|
|
4
|
+
width: 48px;
|
|
5
|
+
height: 48px;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
:host([variant="double-bar"]) {
|
|
9
|
+
width: 56px;
|
|
10
|
+
height: 56px;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
svg {
|
|
14
|
+
display: block;
|
|
15
|
+
width: 100%;
|
|
16
|
+
height: 100%;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.silhouette {
|
|
20
|
+
fill: var(--instrument-frame-primary-color);
|
|
21
|
+
stroke: var(--border-silhouette-color);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.silhouette-ring {
|
|
25
|
+
fill: none;
|
|
26
|
+
stroke: var(--border-silhouette-color);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.disc {
|
|
30
|
+
fill: var(--instrument-frame-primary-color);
|
|
31
|
+
stroke: var(--instrument-frame-tertiary-color);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
:host([value="off"]) .disc {
|
|
35
|
+
fill: var(--automation-button-off-background-color);
|
|
36
|
+
stroke: var(--automation-button-off-border-color);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
:host([value="filled"]) .disc {
|
|
40
|
+
stroke: var(--element-neutral-color);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
:host([value="filled"][variant="double-bar"]) .disc {
|
|
44
|
+
fill: var(--element-neutral-color);
|
|
45
|
+
stroke: var(--element-neutral-color);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
:host([value="enhanced"]) .disc {
|
|
49
|
+
fill: var(--element-neutral-enhanced-color);
|
|
50
|
+
stroke: var(--base-blue-600);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
:host([value="categorical"]) .disc {
|
|
54
|
+
fill: var(--base-teal-400);
|
|
55
|
+
stroke: var(--base-teal-500);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.outline {
|
|
59
|
+
fill: none;
|
|
60
|
+
stroke: var(--border-silhouette-color);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.ring-track {
|
|
64
|
+
fill: none;
|
|
65
|
+
stroke: var(--border-divider-color);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.ring-progress {
|
|
69
|
+
fill: none;
|
|
70
|
+
stroke: var(--element-neutral-color);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
:host([value="enhanced"]) .ring-progress {
|
|
74
|
+
stroke: var(--element-neutral-enhanced-color);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
:host([value="categorical"]) .ring-progress {
|
|
78
|
+
stroke: var(--base-teal-400);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.icon-wrapper {
|
|
82
|
+
position: absolute;
|
|
83
|
+
inset: 12px;
|
|
84
|
+
color: var(--element-neutral-color);
|
|
85
|
+
pointer-events: none;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
:host([variant="double-bar"]) .icon-wrapper {
|
|
89
|
+
inset: 16px;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
:host([value="off"]) .icon-wrapper {
|
|
93
|
+
color: var(--instrument-frame-tertiary-color);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
:host([value="filled"]) .icon-wrapper,
|
|
97
|
+
:host([value="enhanced"]) .icon-wrapper,
|
|
98
|
+
:host([value="categorical"]) .icon-wrapper {
|
|
99
|
+
color: var(--instrument-frame-primary-color);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.icon-wrapper ::slotted(*),
|
|
103
|
+
.default-icon {
|
|
104
|
+
display: block;
|
|
105
|
+
width: 100%;
|
|
106
|
+
height: 100%;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.default-icon path {
|
|
110
|
+
fill: currentColor;
|
|
111
|
+
}
|