@oicl/openbridge-webcomponents-full-bundle 2.0.0-next.110 → 2.0.0-next.111

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.
Files changed (52) hide show
  1. package/bundle/openbridge-webcomponents.bundle.js +2297 -1506
  2. package/bundle/openbridge-webcomponents.bundle.js.map +1 -1
  3. package/custom-elements.json +562 -0
  4. package/dist/automation/indicator-battery/indicator-battery.css.js +52 -0
  5. package/dist/automation/indicator-battery/indicator-battery.css.js.map +1 -0
  6. package/dist/automation/indicator-battery/indicator-battery.d.ts +65 -0
  7. package/dist/automation/indicator-battery/indicator-battery.d.ts.map +1 -0
  8. package/dist/automation/indicator-battery/indicator-battery.js +119 -0
  9. package/dist/automation/indicator-battery/indicator-battery.js.map +1 -0
  10. package/dist/automation/indicator-engine/indicator-engine.css.js +45 -0
  11. package/dist/automation/indicator-engine/indicator-engine.css.js.map +1 -0
  12. package/dist/automation/indicator-engine/indicator-engine.d.ts +34 -0
  13. package/dist/automation/indicator-engine/indicator-engine.d.ts.map +1 -0
  14. package/dist/automation/indicator-engine/indicator-engine.js +73 -0
  15. package/dist/automation/indicator-engine/indicator-engine.js.map +1 -0
  16. package/dist/automation/indicator-generator/indicator-generator-geometry.d.ts +30 -0
  17. package/dist/automation/indicator-generator/indicator-generator-geometry.d.ts.map +1 -0
  18. package/dist/automation/indicator-generator/indicator-generator-geometry.js +61 -0
  19. package/dist/automation/indicator-generator/indicator-generator-geometry.js.map +1 -0
  20. package/dist/automation/indicator-generator/indicator-generator.css.js +121 -0
  21. package/dist/automation/indicator-generator/indicator-generator.css.js.map +1 -0
  22. package/dist/automation/indicator-generator/indicator-generator.d.ts +57 -0
  23. package/dist/automation/indicator-generator/indicator-generator.d.ts.map +1 -0
  24. package/dist/automation/indicator-generator/indicator-generator.js +136 -0
  25. package/dist/automation/indicator-generator/indicator-generator.js.map +1 -0
  26. package/dist/automation/indicator-shared/linear-indicator.d.ts +46 -0
  27. package/dist/automation/indicator-shared/linear-indicator.d.ts.map +1 -0
  28. package/dist/automation/indicator-shared/linear-indicator.js +175 -0
  29. package/dist/automation/indicator-shared/linear-indicator.js.map +1 -0
  30. package/dist/automation/indicator-tank-atmospheric/indicator-tank-atmospheric.css.js +48 -0
  31. package/dist/automation/indicator-tank-atmospheric/indicator-tank-atmospheric.css.js.map +1 -0
  32. package/dist/automation/indicator-tank-atmospheric/indicator-tank-atmospheric.d.ts +65 -0
  33. package/dist/automation/indicator-tank-atmospheric/indicator-tank-atmospheric.d.ts.map +1 -0
  34. package/dist/automation/indicator-tank-atmospheric/indicator-tank-atmospheric.js +105 -0
  35. package/dist/automation/indicator-tank-atmospheric/indicator-tank-atmospheric.js.map +1 -0
  36. package/package.json +1 -1
  37. package/src/automation/indicator-battery/indicator-battery.css +42 -0
  38. package/src/automation/indicator-battery/indicator-battery.stories.ts +94 -0
  39. package/src/automation/indicator-battery/indicator-battery.ts +149 -0
  40. package/src/automation/indicator-engine/indicator-engine.css +35 -0
  41. package/src/automation/indicator-engine/indicator-engine.stories.ts +48 -0
  42. package/src/automation/indicator-engine/indicator-engine.ts +78 -0
  43. package/src/automation/indicator-generator/indicator-generator-geometry.spec.ts +62 -0
  44. package/src/automation/indicator-generator/indicator-generator-geometry.ts +63 -0
  45. package/src/automation/indicator-generator/indicator-generator.css +111 -0
  46. package/src/automation/indicator-generator/indicator-generator.stories.ts +103 -0
  47. package/src/automation/indicator-generator/indicator-generator.ts +184 -0
  48. package/src/automation/indicator-shared/linear-indicator.spec.ts +90 -0
  49. package/src/automation/indicator-shared/linear-indicator.ts +230 -0
  50. package/src/automation/indicator-tank-atmospheric/indicator-tank-atmospheric.css +38 -0
  51. package/src/automation/indicator-tank-atmospheric/indicator-tank-atmospheric.stories.ts +94 -0
  52. package/src/automation/indicator-tank-atmospheric/indicator-tank-atmospheric.ts +138 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"indicator-tank-atmospheric.js","sources":["../../../src/automation/indicator-tank-atmospheric/indicator-tank-atmospheric.ts"],"sourcesContent":["import {LitElement, html, unsafeCSS, nothing} from 'lit';\nimport {property} from 'lit/decorators.js';\nimport componentStyle from './indicator-tank-atmospheric.css?inline';\nimport {customElement} from '../../decorator.js';\nimport '../../icons/icon-tank.js';\nimport {\n IndicatorDirection,\n LinearIndicatorScheme,\n Rect,\n renderLinearBar,\n renderTrendGraph,\n} from '../indicator-shared/linear-indicator.js';\n\nexport enum IndicatorTankAtmosphericVariant {\n bar = 'bar',\n trend = 'trend',\n}\n\nexport enum IndicatorTankAtmosphericValue {\n regular = 'regular',\n enhanced = 'enhanced',\n medium = 'medium',\n}\n\nconst HORIZONTAL_BAR: Rect = {x: 8, y: 11, width: 32, height: 26};\nconst VERTICAL_BAR: Rect = {x: 11, y: 8, width: 26, height: 32};\nconst HORIZONTAL_TREND: Rect = {x: 8, y: 10, width: 32, height: 28};\nconst VERTICAL_TREND: Rect = {x: 10, y: 8, width: 28, height: 32};\n\nconst FRAME_PATH =\n 'M20.5 32.5L4.21259 32.5C3.70663 32.5 3.45364 32.5 3.09178 32.3531C2.84817 32.2542 2.44102 31.9492 2.27758 31.7433C2.0348 31.4373 1.98325 31.2615 1.88017 30.9098C1.29286 28.906 0.5 24.6852 0.5 16.5C0.5 8.31487 1.29286 4.09402 1.88016 2.09025C1.98325 1.73854 2.0348 1.56268 2.27758 1.25676C2.44102 1.0508 2.84817 0.745859 3.09178 0.646944C3.45364 0.500011 3.70662 0.500011 4.21259 0.500011L20.5 0.50001L36.7874 0.500011C37.2934 0.500011 37.5464 0.500011 37.9082 0.646943C38.1518 0.745859 38.559 1.0508 38.7224 1.25675C38.9652 1.56268 39.0167 1.73854 39.1198 2.09025C39.7071 4.09402 40.5 8.31487 40.5 16.5C40.5 24.6851 39.7071 28.906 39.1198 30.9098C39.0167 31.2615 38.9652 31.4373 38.7224 31.7433C38.559 31.9492 38.1518 32.2542 37.9082 32.3531C37.5464 32.5 37.2934 32.5 36.7874 32.5H20.5Z';\n\n/**\n * Compact 48×48 atmospheric tank level indicator.\n *\n * ## Features / Variants\n * - `variant`: `bar` (bottom-up percentage fill) or `trend` (history graph\n * with a current-value micro-bar)\n * - `direction`: `vertical` or `horizontal` tank outline\n * - `value`: color scheme — `regular`, `enhanced` or `medium`\n * - `level`: current tank level 0–100 (%), drives the fill and micro-bar\n * - `data`: history samples 0–100 (%) drawn as the trend graph\n * - `hasIcon`: centered icon overlay (bar variant only)\n *\n * ## Usage Guidelines\n * Use as an at-a-glance level indicator in dashboards and lists. For a\n * full-featured tank cell with readouts, badges and setpoints, use\n * `obc-automation-tank` instead.\n *\n * ## Best Practices / Constraints\n * - `level` and `data` values are clamped to 0–100; values outside that\n * range are pinned to the nearest bound.\n * - The `trend` variant needs at least two `data` samples to draw a line;\n * with fewer it shows only the current-value micro-bar.\n * - The `icon` slot renders only in the `bar` variant; it is ignored in\n * `trend`.\n * - Display-only: the root SVG is `aria-hidden`; convey tank level to\n * assistive tech through adjacent text if it is not otherwise available.\n *\n * ## Slots\n * | Slot | Condition | Purpose |\n * | ------ | ----------------------------- | ------------------------------ |\n * | `icon` | `hasIcon` and `variant='bar'` | Icon overlay (`obi-*` element) |\n *\n * @slot icon - Centered icon overlay, defaults to `obi-tank`\n */\n@customElement('obc-indicator-tank-atmospheric')\nexport class ObcIndicatorTankAtmospheric extends LitElement {\n @property({type: String, reflect: true}) direction: IndicatorDirection =\n IndicatorDirection.vertical;\n @property({type: String, reflect: true})\n variant: IndicatorTankAtmosphericVariant =\n IndicatorTankAtmosphericVariant.bar;\n @property({type: String, reflect: true})\n value: IndicatorTankAtmosphericValue = IndicatorTankAtmosphericValue.regular;\n /** Current level, 0-100 (%) */\n @property({type: Number}) level = 0;\n /** History samples, 0-100 (%) each, oldest first */\n @property({type: Array}) data: number[] = [];\n @property({type: Boolean}) hasIcon = false;\n\n private get scheme(): LinearIndicatorScheme {\n switch (this.value) {\n case IndicatorTankAtmosphericValue.enhanced:\n return LinearIndicatorScheme.enhanced;\n case IndicatorTankAtmosphericValue.medium:\n return LinearIndicatorScheme.categorical;\n default:\n return LinearIndicatorScheme.regular;\n }\n }\n\n override render() {\n const horizontal = this.direction === IndicatorDirection.horizontal;\n const trend = this.variant === IndicatorTankAtmosphericVariant.trend;\n const barRect = horizontal\n ? trend\n ? HORIZONTAL_TREND\n : HORIZONTAL_BAR\n : trend\n ? VERTICAL_TREND\n : VERTICAL_BAR;\n const frameTransform = horizontal\n ? 'translate(3.5 7.5)'\n : 'rotate(90 24 24) translate(3.5 7.5)';\n const showIcon =\n this.hasIcon && this.variant === IndicatorTankAtmosphericVariant.bar;\n return html`\n <svg viewBox=\"0 0 48 48\" aria-hidden=\"true\">\n <g transform=${frameTransform}>\n <path class=\"frame\" d=${FRAME_PATH} />\n </g>\n ${this.variant === IndicatorTankAtmosphericVariant.trend\n ? renderTrendGraph(\n barRect,\n this.data,\n this.level,\n this.scheme,\n 'tank'\n )\n : renderLinearBar(barRect, this.level, this.scheme, 'tank')}\n </svg>\n ${showIcon\n ? html`<div class=\"icon-wrapper\">\n <slot name=\"icon\"><obi-tank usecsscolor></obi-tank></slot>\n </div>`\n : nothing}\n `;\n }\n\n static override styles = unsafeCSS(componentStyle);\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'obc-indicator-tank-atmospheric': ObcIndicatorTankAtmospheric;\n }\n}\n"],"names":["IndicatorTankAtmosphericVariant","IndicatorTankAtmosphericValue"],"mappings":";;;;;;;;;;;;;;;;AAaO,IAAK,oDAAAA,qCAAL;AACLA,mCAAA,KAAA,IAAM;AACNA,mCAAA,OAAA,IAAQ;AAFE,SAAAA;AAAA,GAAA,mCAAA,CAAA,CAAA;AAKL,IAAK,kDAAAC,mCAAL;AACLA,iCAAA,SAAA,IAAU;AACVA,iCAAA,UAAA,IAAW;AACXA,iCAAA,QAAA,IAAS;AAHC,SAAAA;AAAA,GAAA,iCAAA,CAAA,CAAA;AAMZ,MAAM,iBAAuB,EAAC,GAAG,GAAG,GAAG,IAAI,OAAO,IAAI,QAAQ,GAAA;AAC9D,MAAM,eAAqB,EAAC,GAAG,IAAI,GAAG,GAAG,OAAO,IAAI,QAAQ,GAAA;AAC5D,MAAM,mBAAyB,EAAC,GAAG,GAAG,GAAG,IAAI,OAAO,IAAI,QAAQ,GAAA;AAChE,MAAM,iBAAuB,EAAC,GAAG,IAAI,GAAG,GAAG,OAAO,IAAI,QAAQ,GAAA;AAE9D,MAAM,aACJ;AAqCK,IAAM,8BAAN,cAA0C,WAAW;AAAA,EAArD,cAAA;AAAA,UAAA,GAAA,SAAA;AACoC,SAAA,YACvC,mBAAmB;AAErB,SAAA,UACE;AAEF,SAAA,QAAuC;AAEb,SAAA,QAAQ;AAET,SAAA,OAAiB,CAAA;AACf,SAAA,UAAU;AAAA,EAAA;AAAA,EAErC,IAAY,SAAgC;AAC1C,YAAQ,KAAK,OAAA;AAAA,MACX,KAAK;AACH,eAAO,sBAAsB;AAAA,MAC/B,KAAK;AACH,eAAO,sBAAsB;AAAA,MAC/B;AACE,eAAO,sBAAsB;AAAA,IAAA;AAAA,EAEnC;AAAA,EAES,SAAS;AAChB,UAAM,aAAa,KAAK,cAAc,mBAAmB;AACzD,UAAM,QAAQ,KAAK,YAAY;AAC/B,UAAM,UAAU,aACZ,QACE,mBACA,iBACF,QACE,iBACA;AACN,UAAM,iBAAiB,aACnB,uBACA;AACJ,UAAM,WACJ,KAAK,WAAW,KAAK,YAAY;AACnC,WAAO;AAAA;AAAA,uBAEY,cAAc;AAAA,kCACH,UAAU;AAAA;AAAA,UAElC,KAAK,YAAY,UACf;AAAA,MACE;AAAA,MACA,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL;AAAA,IAAA,IAEF,gBAAgB,SAAS,KAAK,OAAO,KAAK,QAAQ,MAAM,CAAC;AAAA;AAAA,QAE7D,WACE;AAAA;AAAA,oBAGA,OAAO;AAAA;AAAA,EAEf;AAGF;AAhEa,4BA+DK,SAAS,UAAU,cAAc;AA9DR,gBAAA;AAAA,EAAxC,SAAS,EAAC,MAAM,QAAQ,SAAS,MAAK;AAAA,GAD5B,4BAC8B,WAAA,aAAA,CAAA;AAGzC,gBAAA;AAAA,EADC,SAAS,EAAC,MAAM,QAAQ,SAAS,MAAK;AAAA,GAH5B,4BAIX,WAAA,WAAA,CAAA;AAGA,gBAAA;AAAA,EADC,SAAS,EAAC,MAAM,QAAQ,SAAS,MAAK;AAAA,GAN5B,4BAOX,WAAA,SAAA,CAAA;AAE0B,gBAAA;AAAA,EAAzB,SAAS,EAAC,MAAM,OAAA,CAAO;AAAA,GATb,4BASe,WAAA,SAAA,CAAA;AAED,gBAAA;AAAA,EAAxB,SAAS,EAAC,MAAM,MAAA,CAAM;AAAA,GAXZ,4BAWc,WAAA,QAAA,CAAA;AACE,gBAAA;AAAA,EAA1B,SAAS,EAAC,MAAM,QAAA,CAAQ;AAAA,GAZd,4BAYgB,WAAA,WAAA,CAAA;AAZhB,8BAAN,gBAAA;AAAA,EADN,cAAc,gCAAgC;AAAA,GAClC,2BAAA;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oicl/openbridge-webcomponents-full-bundle",
3
- "version": "2.0.0-next.110",
3
+ "version": "2.0.0-next.111",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -0,0 +1,42 @@
1
+ :host {
2
+ display: block;
3
+ position: relative;
4
+ width: 48px;
5
+ height: 48px;
6
+ }
7
+
8
+ svg {
9
+ display: block;
10
+ width: 100%;
11
+ height: 100%;
12
+ }
13
+
14
+ .shell {
15
+ fill: var(--instrument-frame-primary-color);
16
+ stroke: var(--instrument-frame-tertiary-color);
17
+ }
18
+
19
+ .tip {
20
+ fill: var(--instrument-frame-tertiary-color);
21
+ }
22
+
23
+ .icon-wrapper {
24
+ position: absolute;
25
+ inset: 12px;
26
+ color: var(--element-neutral-color);
27
+ }
28
+
29
+ :host([value="enhanced"]) .icon-wrapper {
30
+ color: var(--element-neutral-enhanced-color);
31
+ }
32
+
33
+ :host([value="categorical"]) .icon-wrapper {
34
+ color: var(--base-teal-500);
35
+ }
36
+
37
+ .icon-wrapper ::slotted(*),
38
+ .icon-wrapper obi-energy-battery {
39
+ display: block;
40
+ width: 100%;
41
+ height: 100%;
42
+ }
@@ -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
+ }