@oicl/openbridge-webcomponents-full-bundle 2.0.0-next.101 → 2.0.0-next.103

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 (110) hide show
  1. package/bundle/openbridge-webcomponents.bundle.js +580 -143
  2. package/bundle/openbridge-webcomponents.bundle.js.map +1 -1
  3. package/custom-elements.json +629 -407
  4. package/dist/bars-graphs/donut-chart/donut-chart.d.ts +18 -16
  5. package/dist/bars-graphs/donut-chart/donut-chart.d.ts.map +1 -1
  6. package/dist/bars-graphs/donut-chart/donut-chart.js.map +1 -1
  7. package/dist/bars-graphs/pie-chart/pie-chart.d.ts +14 -12
  8. package/dist/bars-graphs/pie-chart/pie-chart.d.ts.map +1 -1
  9. package/dist/bars-graphs/pie-chart/pie-chart.js.map +1 -1
  10. package/dist/bars-graphs/polar-chart/polar-chart.d.ts +19 -16
  11. package/dist/bars-graphs/polar-chart/polar-chart.d.ts.map +1 -1
  12. package/dist/bars-graphs/polar-chart/polar-chart.js.map +1 -1
  13. package/dist/bars-graphs/radial-bar-chart/radial-bar-chart.d.ts +8 -8
  14. package/dist/bars-graphs/radial-bar-chart/radial-bar-chart.d.ts.map +1 -1
  15. package/dist/bars-graphs/radial-bar-chart/radial-bar-chart.js.map +1 -1
  16. package/dist/building-blocks/chart-line/chart-line-base.d.ts +8 -25
  17. package/dist/building-blocks/chart-line/chart-line-base.d.ts.map +1 -1
  18. package/dist/building-blocks/chart-line/chart-line-base.js.map +1 -1
  19. package/dist/building-blocks/instrument-radial/instrument-radial.d.ts +22 -2
  20. package/dist/building-blocks/instrument-radial/instrument-radial.d.ts.map +1 -1
  21. package/dist/building-blocks/instrument-radial/instrument-radial.js +61 -37
  22. package/dist/building-blocks/instrument-radial/instrument-radial.js.map +1 -1
  23. package/dist/integration-systems/integration-bar/integration-bar.d.ts +20 -24
  24. package/dist/integration-systems/integration-bar/integration-bar.d.ts.map +1 -1
  25. package/dist/integration-systems/integration-bar/integration-bar.js.map +1 -1
  26. package/dist/integration-systems/integration-button/integration-button.d.ts +15 -13
  27. package/dist/integration-systems/integration-button/integration-button.d.ts.map +1 -1
  28. package/dist/integration-systems/integration-button/integration-button.js.map +1 -1
  29. package/dist/navigation-instruments/azimuth-thruster/azimuth-thruster.d.ts +14 -0
  30. package/dist/navigation-instruments/azimuth-thruster/azimuth-thruster.d.ts.map +1 -1
  31. package/dist/navigation-instruments/azimuth-thruster/azimuth-thruster.js +30 -11
  32. package/dist/navigation-instruments/azimuth-thruster/azimuth-thruster.js.map +1 -1
  33. package/dist/navigation-instruments/compass/compass.d.ts +68 -31
  34. package/dist/navigation-instruments/compass/compass.d.ts.map +1 -1
  35. package/dist/navigation-instruments/compass/compass.js +36 -16
  36. package/dist/navigation-instruments/compass/compass.js.map +1 -1
  37. package/dist/navigation-instruments/compass-flat/compass-flat.d.ts +8 -9
  38. package/dist/navigation-instruments/compass-flat/compass-flat.d.ts.map +1 -1
  39. package/dist/navigation-instruments/compass-flat/compass-flat.js.map +1 -1
  40. package/dist/navigation-instruments/compass-sector/compass-sector.d.ts.map +1 -1
  41. package/dist/navigation-instruments/compass-sector/compass-sector.js.map +1 -1
  42. package/dist/navigation-instruments/gauge-radial/gauge-radial.css.js +34 -2
  43. package/dist/navigation-instruments/gauge-radial/gauge-radial.css.js.map +1 -1
  44. package/dist/navigation-instruments/gauge-radial/gauge-radial.d.ts +15 -0
  45. package/dist/navigation-instruments/gauge-radial/gauge-radial.d.ts.map +1 -1
  46. package/dist/navigation-instruments/gauge-radial/gauge-radial.js +35 -1
  47. package/dist/navigation-instruments/gauge-radial/gauge-radial.js.map +1 -1
  48. package/dist/navigation-instruments/gauge-trend/gauge-trend.d.ts +0 -5
  49. package/dist/navigation-instruments/gauge-trend/gauge-trend.d.ts.map +1 -1
  50. package/dist/navigation-instruments/gauge-trend/gauge-trend.js.map +1 -1
  51. package/dist/navigation-instruments/graph-mini/graph-mini.d.ts +1 -1
  52. package/dist/navigation-instruments/graph-mini/graph-mini.d.ts.map +1 -1
  53. package/dist/navigation-instruments/graph-mini/graph-mini.js.map +1 -1
  54. package/dist/navigation-instruments/heading/heading.d.ts +19 -1
  55. package/dist/navigation-instruments/heading/heading.d.ts.map +1 -1
  56. package/dist/navigation-instruments/heading/heading.js +34 -16
  57. package/dist/navigation-instruments/heading/heading.js.map +1 -1
  58. package/dist/navigation-instruments/indicator-graph/indicator-graph.d.ts +1 -1
  59. package/dist/navigation-instruments/indicator-graph/indicator-graph.d.ts.map +1 -1
  60. package/dist/navigation-instruments/indicator-graph/indicator-graph.js.map +1 -1
  61. package/dist/navigation-instruments/rudder/rudder.d.ts +15 -2
  62. package/dist/navigation-instruments/rudder/rudder.d.ts.map +1 -1
  63. package/dist/navigation-instruments/rudder/rudder.js +36 -26
  64. package/dist/navigation-instruments/rudder/rudder.js.map +1 -1
  65. package/dist/navigation-instruments/speed-gauge/speed-gauge.d.ts +15 -1
  66. package/dist/navigation-instruments/speed-gauge/speed-gauge.d.ts.map +1 -1
  67. package/dist/navigation-instruments/speed-gauge/speed-gauge.js +31 -3
  68. package/dist/navigation-instruments/speed-gauge/speed-gauge.js.map +1 -1
  69. package/dist/navigation-instruments/watch/watch.d.ts +45 -17
  70. package/dist/navigation-instruments/watch/watch.d.ts.map +1 -1
  71. package/dist/navigation-instruments/watch/watch.js +53 -38
  72. package/dist/navigation-instruments/watch/watch.js.map +1 -1
  73. package/dist/svghelpers/radial-frame.d.ts +122 -0
  74. package/dist/svghelpers/radial-frame.d.ts.map +1 -0
  75. package/dist/svghelpers/radial-frame.js +259 -0
  76. package/dist/svghelpers/radial-frame.js.map +1 -0
  77. package/package.json +1 -1
  78. package/script/check-slot-event-docs.ts +35 -0
  79. package/src/bars-graphs/donut-chart/donut-chart.ts +18 -16
  80. package/src/bars-graphs/pie-chart/pie-chart.ts +14 -12
  81. package/src/bars-graphs/polar-chart/polar-chart.ts +19 -16
  82. package/src/bars-graphs/radial-bar-chart/radial-bar-chart.ts +8 -8
  83. package/src/building-blocks/chart-line/chart-line-base.ts +8 -25
  84. package/src/building-blocks/instrument-radial/instrument-radial.stories.ts +187 -6
  85. package/src/building-blocks/instrument-radial/instrument-radial.ts +98 -47
  86. package/src/integration-systems/integration-bar/integration-bar.ts +20 -24
  87. package/src/integration-systems/integration-button/integration-button.ts +15 -13
  88. package/src/navigation-instruments/azimuth-thruster/azimuth-thruster.stories.ts +78 -1
  89. package/src/navigation-instruments/azimuth-thruster/azimuth-thruster.ts +55 -11
  90. package/src/navigation-instruments/compass/compass.stories.ts +88 -1
  91. package/src/navigation-instruments/compass/compass.ts +99 -46
  92. package/src/navigation-instruments/compass-flat/compass-flat.ts +8 -9
  93. package/src/navigation-instruments/compass-sector/compass-sector.ts +7 -0
  94. package/src/navigation-instruments/gauge-radial/gauge-radial.css +32 -2
  95. package/src/navigation-instruments/gauge-radial/gauge-radial.stories.ts +265 -1
  96. package/src/navigation-instruments/gauge-radial/gauge-radial.ts +59 -1
  97. package/src/navigation-instruments/gauge-trend/gauge-trend.ts +0 -5
  98. package/src/navigation-instruments/graph-mini/graph-mini.ts +1 -1
  99. package/src/navigation-instruments/heading/heading.stories.ts +92 -1
  100. package/src/navigation-instruments/heading/heading.ts +50 -16
  101. package/src/navigation-instruments/indicator-graph/indicator-graph.ts +1 -1
  102. package/src/navigation-instruments/rudder/rudder.stories.ts +83 -1
  103. package/src/navigation-instruments/rudder/rudder.ts +61 -34
  104. package/src/navigation-instruments/speed-gauge/speed-gauge.stories.ts +94 -1
  105. package/src/navigation-instruments/speed-gauge/speed-gauge.ts +57 -4
  106. package/src/navigation-instruments/watch/watch.stories.ts +140 -1
  107. package/src/navigation-instruments/watch/watch.ts +100 -60
  108. package/src/storybook-util.ts +59 -0
  109. package/src/svghelpers/radial-frame.spec.ts +307 -0
  110. package/src/svghelpers/radial-frame.ts +508 -0
@@ -158,27 +158,15 @@ export type PolarChartDataItem = {
158
158
  * </script>
159
159
  * ```
160
160
  *
161
- * @property {Array<{label: string, value: number}>} data - Chart data segments (set via JavaScript)
162
- * @property {string[]} colors - Custom segment colors (set via JavaScript) with fallback to theme palette
163
- * @property {boolean} monochrome - Use single color for all sectors (uses first color from array, default: false)
164
- * @property {boolean} discreteColorStops - Draw sectors as radial color bands from center outward using the colors array as threshold steps (default: false)
165
- * @property {boolean} trimToDiscreteStops - When true, visually trim sectors to the nearest discrete color band boundary. When false, show exact values with partial band fills (default: true)
166
- * @property {boolean} showSectorLabels - When true, display sector labels from data (e.g. "Sector A"). When false, display angle values (0°, 30°, etc.). Default: false
167
- * @property {boolean} showUnit - Whether to show unit in angle or outer labels, default: false
168
- * @property {boolean} showOuterLabels - Show outer labels, default: false
169
- * @property {string} outerLabelUnit - Unit string to append to outer labels, default: ""
170
- * @property {number} outerLabelMaxLength - Maximum character length for labels before trim (0 = no limit), default: 0
171
- * @property {number} outerLabelDecimalPlaces - Number of decimal places in labels, default: 0
172
- * @property {boolean} showDebugOverlay - Show debug overlay for development, default: false
173
- * @property {number} fixedHeight - Fixed height of the chart in pixels (mandatory, determines chart circumference), default: 320. The chart's circumference is always based on this fixed height to match other radial instruments.
174
- * @property {boolean} legend - Whether to display the legend below the chart, default: false
175
161
  * @beta
176
162
  */
177
163
  @customElement('obc-polar-chart')
178
164
  export class ObcPolarChart extends LitElement {
165
+ /** Chart data segments (set via JavaScript) */
179
166
  @property({type: Array, attribute: false})
180
167
  data: PolarChartDataItem[] = [];
181
168
 
169
+ /** Custom segment colors (set via JavaScript) with fallback to theme palette */
182
170
  @property({type: Array, attribute: false}) colors: string[] = [];
183
171
 
184
172
  /** @internal */
@@ -187,30 +175,45 @@ export class ObcPolarChart extends LitElement {
187
175
  @property({type: String})
188
176
  priority: Priority = Priority.regular;
189
177
 
190
- /** @availableWhen discreteColorStops==false */
178
+ /**
179
+ * Use single color for all sectors (uses first color from array, default: false)
180
+ * @availableWhen discreteColorStops==false
181
+ */
191
182
  @property({type: Boolean}) monochrome = false;
183
+ /** Draw sectors as radial color bands from center outward using the colors array as threshold steps (default: false) */
192
184
  @property({type: Boolean})
193
185
  discreteColorStops = false;
186
+ /** When true, display sector labels from data (e.g. "Sector A"). When false, display angle values (0°, 30°, etc.). Default: false */
194
187
  @property({type: Boolean})
195
188
  showSectorLabels = false; // Default: false (angles shown by default)
189
+ /** Whether to show unit in angle or outer labels, default: false */
196
190
  @property({type: Boolean})
197
191
  showUnit = false;
192
+ /** Show outer labels, default: false */
198
193
  @property({type: Boolean})
199
194
  showOuterLabels = false;
195
+ /** Unit string to append to outer labels, default: "°" */
200
196
  @property({type: String})
201
197
  outerLabelUnit = '°';
202
- /** @availableWhen showOuterLabels==true */
198
+ /**
199
+ * Maximum character length for labels before trim (0 = no limit), default: 0
200
+ * @availableWhen showOuterLabels==true
201
+ */
203
202
  @property({type: Number})
204
203
  outerLabelMaxLength = 0;
204
+ /** Number of decimal places in labels, default: 0 */
205
205
  @property({type: Number})
206
206
  outerLabelDecimalPlaces = 0;
207
207
 
208
+ /** Fixed height of the chart in pixels (determines chart circumference), default: 320. The chart's circumference is always based on this fixed height to match other radial instruments. */
208
209
  @property({type: Number, reflect: true})
209
210
  fixedHeight = 320;
210
211
 
212
+ /** Whether to display the legend below the chart, default: false */
211
213
  @property({type: Boolean, reflect: true})
212
214
  legend = false;
213
215
 
216
+ /** Show debug overlay for development, default: false */
214
217
  @property({type: Boolean, reflect: true})
215
218
  showDebugOverlay = false;
216
219
 
@@ -125,32 +125,32 @@ const RADIAL_BAR_WATCHED_PROP_NAMES = [
125
125
  * </script>
126
126
  * ```
127
127
  *
128
- * @property {number[]} data - Array of values for each ring (set via JavaScript)
129
- * @property {string[]} colors - Custom ring colors (set via JavaScript) with fallback to theme palette
130
- * @property {number} max - Maximum value for calculating remaining empty area, default: 100
131
- * @property {number} circumference - Arc span in degrees: 360 for full circle, 270 for 3/4 circle, default: 270
132
- * @property {boolean} showDebugOverlay - Show debug overlay for development, default: false
133
- * @property {number} fixedHeight - Fixed height of the chart in pixels (mandatory, determines chart circumference), default: 320. The chart's circumference is always based on this fixed height to match other radial instruments.
134
- * @property {number} minRingThickness - Minimum thickness of each ring in pixels, excluding borders, default: 16
135
- * @property {boolean} legend - Whether to display the legend below the chart, default: false
136
128
  * @beta
137
129
  */
138
130
  @customElement('obc-radial-bar-chart')
139
131
  export class ObcRadialBarChart extends LitElement {
132
+ /** Array of values for each ring (set via JavaScript) */
140
133
  @property({type: Array, attribute: false}) data: number[] = [];
134
+ /** Custom ring colors (set via JavaScript) with fallback to theme palette */
141
135
  @property({type: Array, attribute: false}) colors: string[] = [];
142
136
  @property({type: String})
143
137
  priority: Priority = Priority.regular;
138
+ /** Maximum value for calculating remaining empty area, default: 100 */
144
139
  @property({type: Number})
145
140
  max = 100;
141
+ /** Arc span in degrees: 360 for full circle, 270 for 3/4 circle, default: 270 */
146
142
  @property({type: Number})
147
143
  circumference = 270;
144
+ /** Whether to display the legend below the chart, default: false */
148
145
  @property({type: Boolean, reflect: true})
149
146
  legend = false;
147
+ /** Show debug overlay for development, default: false */
150
148
  @property({type: Boolean, reflect: true})
151
149
  showDebugOverlay = false;
150
+ /** Fixed height of the chart in pixels (determines chart circumference), default: 320. The chart's circumference is always based on this fixed height to match other radial instruments. */
152
151
  @property({type: Number, reflect: true})
153
152
  fixedHeight = 320;
153
+ /** Minimum thickness of each ring in pixels, excluding borders, default: 16 */
154
154
  @property({
155
155
  type: Number,
156
156
  })
@@ -311,32 +311,15 @@ const LINE_GRAPH_RECREATE_PROP_NAMES = [
311
311
  * </script>
312
312
  * ```
313
313
  *
314
- * @property {Array<{label?: string, x?: number|string|Date|TemporalLike, value: number}>} data - Single-series data array. In `category` mode each item needs `label`; in `time`/`number` mode each item needs `x` (epoch ms, ISO string, Date, or Temporal object — `label` is parsed as a fallback). Used when `datasets` is not provided. Points are drawn in array order (no sorting); Temporal Plain* values are interpreted in the system time zone.
315
- * @property {ChartDataset<'line', (number | {x: number|string|Date|TemporalLike; y: number})[]>[]} datasets - Multi-series Chart.js datasets. Takes precedence over `data`. Each dataset can have `label`, `data` (numeric array or `{x, y}` points), and visual properties like `borderColor`, `backgroundColor`, `fill`, etc. In `time`/`number` mode point x-values are normalized like single-series `x`.
316
- * @property {(string|number)[]} labels - Explicit labels for category x-axis. If omitted, labels are derived from `data` property or dataset x-values.
317
- * @property {string[]} colors - Custom color palette (CSS variable names or color strings). Falls back to theme default colors if not provided.
318
- * @property {'category'|'time'|'number'} xAxisType - X-axis mode. `'category'` for labeled, evenly spaced data points; `'time'` for time-based data positioned proportionally (numbers are always epoch ms — `xStepSize`/`xTicksLimit` operate in ms); `'number'` for plain numeric x-values. Default: `'category'`.
319
- * @property {'minutes'|'date'} timeDisplay - Time axis label format when `xAxisType='time'`. `'date'` shows a locale date, `'minutes'` shows minutes relative to the latest data point. Default: `'date'`.
320
- * @property {'left'|'right'} yAxisPosition - Single y-axis position. Use this for simple charts with one y-axis. For multiple y-axes, use `yAxes` property instead. Default: `'left'`.
321
- * @property {Array<{id?: string; position?: 'left'|'right'; min?: number; max?: number; grid?: boolean}>} yAxes - Multiple y-axis definitions for complex charts. Each axis can specify `id` (referenced by dataset `yAxisID`), `position`, `min`/`max` range, and `grid` visibility.
322
- * @property {boolean} showGrid - Show vertical grid lines (x-axis). When combined with `showGridX` and `showGridY`, controls full grid visibility. Default: `false`.
323
- * @property {boolean} showGridX - Show vertical grid lines (x-axis). Set to `false` to hide only vertical lines while keeping horizontal lines. Default: `false`.
324
- * @property {boolean} showGridY - Show horizontal grid lines (y-axis). Set to `false` to hide only horizontal lines while keeping vertical lines. Default: `false`.
325
- * @property {boolean} showTickMarks - Show axis tick marks and labels. Automatically hidden below 192px height threshold. Default: `false`.
326
- * @property {boolean} showPoints - Show point markers on data points. Default: `false`.
327
- * @property {boolean} fill - Enable area fill under/between lines. Use with `fillMode` to control fill style. Default: `false`.
328
- * @property {'semitransparent'|'solid'|'threshold'} fillMode - Fill rendering mode. `'semitransparent'` uses 50% alpha, `'solid'` uses opaque fill, `'threshold'` (single-series only) fills above/below midpoint with red/blue gradient. Default: `'semitransparent'`.
329
- * @property {'smooth'|'straight'|'stepped'} lineMode - Line drawing style. `'smooth'` applies bezier curve tension, `'straight'` draws straight lines, `'stepped'` creates step-like lines. Default: `'smooth'`.
330
- * @property {boolean} stacked - Stack multi-series datasets vertically on y-axis. Ignored for single-series and threshold fill mode. Default: `false`.
331
- * @property {string} unit - Unit label displayed in tooltips (e.g., 'kW', 'kg', '%'). Default: empty string.
332
- * @property {number} xTicksLimit - Maximum number of x-axis ticks/grid lines. Useful for matching external axes. Optional.
333
- * @property {number} xStepSize - Force specific interval between x-axis ticks (e.g., 1, 2, 5). Useful for matching external axes. Optional.
334
- * @property {number} yTicksLimit - Maximum number of y-axis ticks/grid lines. Useful for matching external axes. Optional.
335
- * @property {number} yStepSize - Force specific interval between y-axis ticks (e.g., 2, 5, 10). Useful for matching external axes. Optional.
336
- * @property {boolean} legend - Show HTML legend below chart with series labels and colors. Default: `false`.
337
- * @property {number} height - Chart height in pixels. Determines chart size with 1.5:1 aspect ratio (width = height × 1.5). Default: `320`.
338
- * @property {boolean} showDebugOverlay - Development mode: show visual debug overlay with dimension guides. Shows blue border around canvas (axis area) and red border around chart grid (data area). Default: `false`.
314
+ * TODO(maintainer): `fill` is documented here as a class-level `@property` tag
315
+ * only because tooling (the manifest, lit-analyzer, story controls) still
316
+ * depends on it, yet no `@property`-decorated field backs it and no render path
317
+ * reads `this.fill` (fill is decided by the abstract `shouldApplyFill()`). Decide
318
+ * whether `fill` is a real public property (add a decorated field) or obsolete
319
+ * (remove this tag and the `.fill=` usages in the stories/wrappers). Kept as-is
320
+ * here to avoid changing the public API surface in a docs-only cleanup.
339
321
  *
322
+ * @property {boolean} fill - Enable area fill under/between lines. Use with `fillMode` to control fill style. Default: `false`.
340
323
  * @ignore This is an abstract base class. Use concrete implementations like ObcLineGraph or ObcAreaGraph instead.
341
324
  * @experimental
342
325
  */
@@ -1,11 +1,27 @@
1
1
  import type {Meta, StoryObj} from '@storybook/web-components-vite';
2
+ import {html} from 'lit';
2
3
  import {ObcInstrumentRadial, ObcGaugeRadialType} from './instrument-radial.js';
3
4
  import './instrument-radial.js';
4
- import {widthDecorator} from '../../storybook-util.js';
5
+ import {
6
+ playgroundColumn,
7
+ resizableStoryBox,
8
+ storyHint,
9
+ widthDecorator,
10
+ } from '../../storybook-util.js';
5
11
  import {AdviceType} from '../../navigation-instruments/watch/advice.js';
6
12
  import {InstrumentState, Priority} from '../../navigation-instruments/types.js';
7
13
 
8
- const meta: Meta<typeof ObcInstrumentRadial> = {
14
+ type InstrumentRadialStoryArgs = Partial<ObcInstrumentRadial> & {
15
+ width?: number;
16
+ height?: number;
17
+ /** Story-only: apply faceDiameter to every playground instance. */
18
+ lockFaceDiameter?: boolean;
19
+ // Manifest-derived entries that only exist to be hidden from the panel.
20
+ minAngle?: unknown;
21
+ maxAngle?: unknown;
22
+ };
23
+
24
+ const meta: Meta<InstrumentRadialStoryArgs> = {
9
25
  title: 'Building Blocks/Instrument Radial',
10
26
  tags: ['6.0'],
11
27
  component: 'obc-instrument-radial',
@@ -19,16 +35,21 @@ const meta: Meta<typeof ObcInstrumentRadial> = {
19
35
  priority: {control: 'select', options: Object.values(Priority)},
20
36
  tickmarksInside: {control: 'boolean'},
21
37
  showLabels: {control: 'boolean'},
22
- minAngle: {control: false, table: {disable: true}},
23
- maxAngle: {control: false, table: {disable: true}},
38
+ faceDiameter: {
39
+ control: {type: 'range', min: 100, max: 600, step: 10},
40
+ description:
41
+ 'Pins the outer-ring diameter in px (fixed intrinsic size, equal circumference across instruments). Clear to return to fill-the-container sizing.',
42
+ },
43
+ minAngle: {table: {disable: true}},
44
+ maxAngle: {table: {disable: true}},
24
45
  // Only acts on labels at the horizontal ends (±90°), i.e. a 180° sweep.
25
46
  // Hidden by default; the MaxMinEndLabels story re-enables it.
26
47
  endLabelsMaxMin: {table: {disable: true}},
27
48
  },
28
- } satisfies Meta<ObcInstrumentRadial>;
49
+ } satisfies Meta<InstrumentRadialStoryArgs>;
29
50
 
30
51
  export default meta;
31
- type Story = StoryObj<ObcInstrumentRadial>;
52
+ type Story = StoryObj<InstrumentRadialStoryArgs>;
32
53
 
33
54
  export const Positive: Story = {
34
55
  args: {
@@ -135,3 +156,163 @@ export const WithAdvices: Story = {
135
156
  ],
136
157
  },
137
158
  };
159
+
160
+ /**
161
+ * The issue #1021 behavior at the building-block level: each dial reserves
162
+ * room for its own labels when squeezed, so the 4-digit dial gets a wider
163
+ * margin (and a smaller ring) than the 2-digit one — nothing is clipped.
164
+ * Shrink further and labels hide entirely instead (the content-aware cap in
165
+ * `svghelpers/radial-frame.ts`). See Instruments/Gauge Radial → *Label Room*
166
+ * stories for the full dashboard scenario.
167
+ */
168
+ export const LabelRoomFlexRow: Story = {
169
+ name: 'Label Room — 2-Digit Vs 4-Digit Flex Row (#1021)',
170
+ args: {
171
+ width: 500,
172
+ height: 220,
173
+ },
174
+ render: () => html`
175
+ <div style="display: flex; gap: 8px; width: 100%; height: 100%;">
176
+ ${[
177
+ {max: 99, interval: 33},
178
+ {max: 3600, interval: 900},
179
+ ].map(
180
+ (g) => html`
181
+ <div style="flex: 1; min-width: 0;">
182
+ <obc-instrument-radial
183
+ .value=${g.max * 0.6}
184
+ .maxValue=${g.max}
185
+ .getAngle=${(v: number) => (v / g.max) * 270 - 135}
186
+ .showLabels=${true}
187
+ .primaryTickmarkInterval=${g.interval}
188
+ ></obc-instrument-radial>
189
+ </div>
190
+ `
191
+ )}
192
+ </div>
193
+ `,
194
+ };
195
+
196
+ /**
197
+ * Same `faceDiameter` on both dials pins their ring circumference: the rings
198
+ * are pixel-identical while the 4-digit dial's box grows wider for its
199
+ * labels. This is the equal-circumference contract shared by all radial
200
+ * instruments (and `obc-donut-chart`'s `fixedHeight`).
201
+ */
202
+ export const EqualCircumferenceMixedDigits: Story = {
203
+ name: 'Equal Circumference — Same FaceDiameter, Different Labels',
204
+ args: {
205
+ width: 600,
206
+ height: 280,
207
+ faceDiameter: 200,
208
+ },
209
+ render: (args) => html`
210
+ <div style="display: flex; gap: 8px; align-items: center;">
211
+ ${[
212
+ {max: 99, interval: 33},
213
+ {max: 3600, interval: 900},
214
+ ].map(
215
+ (g) => html`
216
+ <obc-instrument-radial
217
+ style="display: block;"
218
+ .value=${g.max * 0.6}
219
+ .maxValue=${g.max}
220
+ .getAngle=${(v: number) => (v / g.max) * 270 - 135}
221
+ .faceDiameter=${args.faceDiameter ?? 200}
222
+ .showLabels=${true}
223
+ .primaryTickmarkInterval=${g.interval}
224
+ ></obc-instrument-radial>
225
+ `
226
+ )}
227
+ </div>
228
+ `,
229
+ };
230
+
231
+ /**
232
+ * Interactive sizing playground for the building block: drag the dashed box's
233
+ * bottom-right corner to resize it. The first dial is pinned to a fixed
234
+ * intrinsic size by the `faceDiameter` control, while the 4-digit dial and
235
+ * the 180° sector adapt to the remaining flex space, reserving room for their
236
+ * labels adaptively (issue #1021). Enable `lockFaceDiameter` to pin all three
237
+ * to the same circumference — full circle and partial arcs alike. Related:
238
+ * *Sizing Playground* stories under Building Blocks/Watch and
239
+ * Instruments/Gauge Radial.
240
+ */
241
+ export const SizingPlayground: Story = {
242
+ name: 'Sizing Playground — FaceDiameter + Resizable (Manual)',
243
+ tags: ['skip-test'],
244
+ parameters: {widthDecorator: false},
245
+ args: {
246
+ faceDiameter: 200,
247
+ lockFaceDiameter: false,
248
+ },
249
+ argTypes: {
250
+ lockFaceDiameter: {
251
+ control: 'boolean',
252
+ description:
253
+ 'Apply faceDiameter to every instance (equal circumference) instead of only the first.',
254
+ },
255
+ },
256
+ render: (args) => {
257
+ const instances = [
258
+ {
259
+ label: '2-digit, 270°',
260
+ max: 99,
261
+ interval: 33,
262
+ getAngle: (v: number) => (v / 99) * 270 - 135,
263
+ clipBottom: 0,
264
+ endLabelsMaxMin: false,
265
+ },
266
+ {
267
+ label: '4-digit, 270°',
268
+ max: 3600,
269
+ interval: 900,
270
+ getAngle: (v: number) => (v / 3600) * 270 - 135,
271
+ clipBottom: 0,
272
+ endLabelsMaxMin: false,
273
+ },
274
+ {
275
+ label: '2-digit, 180° sector',
276
+ max: 99,
277
+ interval: 33,
278
+ getAngle: (v: number) => (v / 99) * 180 - 90,
279
+ clipBottom: 44,
280
+ endLabelsMaxMin: true,
281
+ },
282
+ ];
283
+ const fd = (index: number) =>
284
+ index === 0 || args.lockFaceDiameter ? args.faceDiameter : undefined;
285
+ const caption = (index: number, label: string) =>
286
+ fd(index) !== undefined
287
+ ? `${label} — pinned ${fd(index)}px`
288
+ : `${label} — adaptive (flex)`;
289
+ return html`
290
+ ${storyHint(
291
+ 'Drag the bottom-right corner of the dashed box to resize it. The first dial is pinned by the faceDiameter control; the 4-digit dial and the 180° sector adapt to the remaining flex space. Enable lockFaceDiameter to pin all three to the same circumference.'
292
+ )}
293
+ ${resizableStoryBox(
294
+ html`
295
+ ${instances.map((g, index) =>
296
+ playgroundColumn(
297
+ caption(index, g.label),
298
+ html`
299
+ <obc-instrument-radial
300
+ .value=${g.max * 0.6}
301
+ .maxValue=${g.max}
302
+ .getAngle=${g.getAngle}
303
+ .clipBottom=${g.clipBottom}
304
+ .endLabelsMaxMin=${g.endLabelsMaxMin}
305
+ .showLabels=${true}
306
+ .primaryTickmarkInterval=${g.interval}
307
+ .faceDiameter=${fd(index)}
308
+ ></obc-instrument-radial>
309
+ `,
310
+ {pinned: fd(index) !== undefined}
311
+ )
312
+ )}
313
+ `,
314
+ {width: 760, height: 300}
315
+ )}
316
+ `;
317
+ },
318
+ };
@@ -1,5 +1,6 @@
1
- import {css, LitElement, html, svg, nothing} from 'lit';
1
+ import {css, LitElement, PropertyValues, html, svg, nothing} from 'lit';
2
2
  import {property} from 'lit/decorators.js';
3
+ import {ResizeController} from '@lit-labs/observers/resize-controller.js';
3
4
  import {customElement} from '../../decorator.js';
4
5
  import {
5
6
  AdviceState,
@@ -12,18 +13,17 @@ import {TickmarkType} from '../../navigation-instruments/watch/tickmark.js';
12
13
  import {TickmarkStyle} from '../../navigation-instruments/watch/tickmark.js';
13
14
  import {InstrumentState, Priority} from '../../navigation-instruments/types.js';
14
15
  import {SetpointMixin} from '../../svghelpers/setpoint-mixin.js';
16
+ import {innerRingRadiusFor} from '../../navigation-instruments/watch/watch.js';
15
17
  import {
16
- OUTER_RING_RADIUS,
17
- innerRingRadiusFor,
18
- } from '../../navigation-instruments/watch/watch.js';
19
- import {
20
- computeZoomToFitArcFrame,
21
- type ZoomToFitArcFrame,
22
- } from '../../svghelpers/arc-frame.js';
23
-
24
- // Must match obc-watch's un-zoomed viewBox `(176 + getPadding()) * 2`
25
- // (default padding 48 → 448); instrument-radial overlays obc-watch 1:1.
26
- const WATCH_DEFAULT_VIEWBOX = 448;
18
+ applyPinnedHostSize,
19
+ computeRadialFrame,
20
+ END_MAXMIN_LABEL_DROP_PX,
21
+ estimateLabelWidthPx,
22
+ measureContainerPx,
23
+ observeInnerBox,
24
+ SIDE_LABEL_DROP_PX,
25
+ type RadialFrame,
26
+ } from '../../svghelpers/radial-frame.js';
27
27
 
28
28
  export enum ObcGaugeRadialType {
29
29
  filled = 'filled',
@@ -115,6 +115,9 @@ function defaultGaugeAngle(
115
115
  }
116
116
 
117
117
  /**
118
+ * @fires frame-changed {CustomEvent<RadialFrame>} Fired after render when the
119
+ * computed radial frame changed (viewBox, label visibility, or pinned host
120
+ * size). Wrappers use it to align sibling overlays/readouts with the dial.
118
121
  * @experimental
119
122
  */
120
123
  @customElement('obc-instrument-radial')
@@ -174,9 +177,54 @@ export class ObcInstrumentRadial extends SetpointMixin(LitElement) {
174
177
  */
175
178
  @property({type: Boolean}) endLabelsMaxMin: boolean = false;
176
179
  @property({type: Boolean}) zoomToFitArc: boolean = false;
180
+ /**
181
+ * Outer-ring diameter in CSS pixels. When set, the instrument renders at a
182
+ * fixed intrinsic size derived from the ring, arc shape and label reserve —
183
+ * so instruments sharing the same value have identical ring circumference
184
+ * regardless of label width or arc extent (like obc-donut-chart's
185
+ * fixedHeight). When unset (default), the instrument fills its container.
186
+ */
187
+ @property({type: Number, attribute: 'face-diameter'})
188
+ faceDiameter: number | undefined;
177
189
 
178
190
  private _radiusOffset = 0;
179
- private _arcFrame: ZoomToFitArcFrame | undefined;
191
+ private _frame: RadialFrame | undefined;
192
+ private _lastFrameKey = '';
193
+
194
+ private _resizeController = new ResizeController(this, {});
195
+
196
+ override firstUpdated(changed: PropertyValues): void {
197
+ super.firstUpdated(changed);
198
+ observeInnerBox(this._resizeController, this.renderRoot);
199
+ }
200
+
201
+ /** The frame computed for the current render (viewBox, label reserve …). */
202
+ get frame(): RadialFrame | undefined {
203
+ return this._frame;
204
+ }
205
+
206
+ /** Whether the host size styles were set by applyPinnedHostSize. */
207
+ private _hostSizePinned = false;
208
+
209
+ override updated(changed: PropertyValues): void {
210
+ super.updated(changed);
211
+ this._hostSizePinned = applyPinnedHostSize(
212
+ this,
213
+ this._frame,
214
+ this._hostSizePinned
215
+ );
216
+ const frame = this._frame;
217
+ if (!frame) {
218
+ return;
219
+ }
220
+ const key = `${frame.viewBox}|${frame.labelsHidden}|${frame.hostWidthPx ?? ''}|${frame.hostHeightPx ?? ''}`;
221
+ if (key !== this._lastFrameKey) {
222
+ this._lastFrameKey = key;
223
+ this.dispatchEvent(
224
+ new CustomEvent<RadialFrame>('frame-changed', {detail: frame})
225
+ );
226
+ }
227
+ }
180
228
 
181
229
  private get clampedValue(): number {
182
230
  const lowerBound = Math.min(this.minValue, this.maxValue);
@@ -279,31 +327,40 @@ export class ObcInstrumentRadial extends SetpointMixin(LitElement) {
279
327
  ? WatchCircleType.single
280
328
  : WatchCircleType.double;
281
329
 
282
- const clips = this.safeClips;
283
- let viewBox: string;
284
- if (this.zoomToFitArc) {
285
- const ext = 48;
286
- const targetSize = (176 + ext) * 2;
287
- const frame = computeZoomToFitArcFrame({
288
- areas,
289
- outerRadius: OUTER_RING_RADIUS,
290
- innerRadius: innerRingRadiusFor(watchCircleType),
291
- extension: ext,
292
- targetSize,
293
- });
294
- this._radiusOffset = frame.radiusOffset;
295
- viewBox = frame.viewBox;
296
- this._arcFrame = frame;
297
- } else {
298
- this._radiusOffset = 0;
299
- this._arcFrame = undefined;
300
- const full = WATCH_DEFAULT_VIEWBOX;
301
- const w = full * (1 - clips.left / 100 - clips.right / 100);
302
- const h = full * (1 - clips.top / 100 - clips.bottom / 100);
303
- const left = -full / 2 + (full * clips.left) / 100;
304
- const top = -full / 2 + (full * clips.top) / 100;
305
- viewBox = `${left} ${top} ${w} ${h}`;
306
- }
330
+ const tickmarks = this.tickmarks;
331
+ // Labels hang past the ±90° line only when a labeled tick actually sits
332
+ // there (e.g. the 180°/90° sector ends) — a ±60° sector like rot-sector
333
+ // must not reserve a drop it never uses.
334
+ const hasHorizontalEndLabels = tickmarks.some((t) => {
335
+ if (t.text === undefined) {
336
+ return false;
337
+ }
338
+ const angle = ((t.angle % 360) + 360) % 360;
339
+ return Math.abs(angle - 90) < 1 || Math.abs(angle - 270) < 1;
340
+ });
341
+ const frame = computeRadialFrame({
342
+ basePadding: 48,
343
+ labelWidthPx: this.tickmarksInside
344
+ ? 0
345
+ : estimateLabelWidthPx(tickmarks.map((t) => t.text)),
346
+ labelDropPx:
347
+ this.tickmarksInside || !hasHorizontalEndLabels
348
+ ? 0
349
+ : this.endLabelsMaxMin
350
+ ? END_MAXMIN_LABEL_DROP_PX
351
+ : SIDE_LABEL_DROP_PX,
352
+ clips: this.zoomToFitArc ? undefined : this.safeClips,
353
+ containerPx: measureContainerPx(this),
354
+ faceDiameter: this.faceDiameter,
355
+ zoomToFitArc: this.zoomToFitArc,
356
+ areas,
357
+ innerRadius: innerRingRadiusFor(watchCircleType),
358
+ });
359
+ this._radiusOffset = frame.radiusOffset;
360
+ this._frame = frame;
361
+ const shownTickmarks = frame.labelsHidden
362
+ ? tickmarks.map((t) => ({...t, text: undefined}))
363
+ : tickmarks;
307
364
 
308
365
  return html`
309
366
  <div class="container">
@@ -316,23 +373,17 @@ export class ObcInstrumentRadial extends SetpointMixin(LitElement) {
316
373
  .angleSetpointAtZeroDeadband=${this.setpointAtZeroDeadband}
317
374
  .setpointOverride=${this.setpointOverride}
318
375
  .animateSetpoint=${this.animateSetpoint}
319
- .padding=${48}
320
- .tickmarks=${this.tickmarks}
376
+ .tickmarks=${shownTickmarks}
321
377
  .tickmarksInside=${this.tickmarksInside}
322
378
  .tickmarkStyle=${this.tickmarkStyle}
323
379
  .advices=${this._advices}
324
380
  .areas=${areas}
325
381
  .watchCircleType=${watchCircleType}
326
382
  .barAreas=${barAreas}
327
- .clipTop=${this.zoomToFitArc ? 0 : clips.top}
328
- .clipBottom=${this.zoomToFitArc ? 0 : clips.bottom}
329
- .clipLeft=${this.zoomToFitArc ? 0 : clips.left}
330
- .clipRight=${this.zoomToFitArc ? 0 : clips.right}
331
383
  .endLabelsMaxMin=${this.endLabelsMaxMin}
332
- .zoomToFitArc=${this.zoomToFitArc}
333
- .arcFrame=${this._arcFrame}
384
+ .arcFrame=${frame}
334
385
  ></obc-watch>
335
- <svg class="gauge-radial" viewBox=${viewBox}>${this._needle}</svg>
386
+ <svg class="gauge-radial" viewBox=${frame.viewBox}>${this._needle}</svg>
336
387
  </div>
337
388
  `;
338
389
  }
@@ -30,30 +30,6 @@ import {classMap} from 'lit/directives/class-map.js';
30
30
  * @slot clock - Custom clock content, rendered when `showClock` is true
31
31
  * @slot integration-buttons - Regular vessel integration buttons
32
32
  * @slot hug-buttons - Compact vessel integration buttons; slotted integration buttons are forced to hug type
33
- * @property {IntegrationBarType} type - Integration bar mode for fleet/vessel presentation
34
- * @property {boolean} hideHomeButton - Hides the home button when true
35
- * @property {boolean} showClock - Toggles rendering of the clock slot
36
- * @property {boolean} showLinkButton - Toggles visibility of link button
37
- * @property {boolean} linkButtonActivated - Activated state of link button
38
- * @property {boolean} showUserButton - Toggles visibility of user button
39
- * @property {boolean} userButtonActivated - Activated state of user button
40
- * @property {boolean} showDimmingButton - Toggles visibility of dimming button
41
- * @property {boolean} dimmingButtonActivated - Activated state of dimming button
42
- * @property {boolean} showSystemButton - Toggles visibility of system button
43
- * @property {boolean} systemButtonActivated - Activated state of system button
44
- * @property {boolean} showScreenButton - Toggles visibility of screen button
45
- * @property {boolean} screenButtonActivated - Activated state of screen button
46
- * @property {boolean} showNotificationButton - Toggles visibility of notification button
47
- * @property {boolean} notificationButtonActivated - Activated state of notification button
48
- * @property {boolean} showAlertButton - Toggles visibility of alert button
49
- * @property {boolean} alertButtonActivated - Activated state of alert button
50
- * @property {boolean} showFleetButton - Toggles visibility of fleet button
51
- * @property {boolean} fleetButtonSelected - Selected state of fleet button
52
- * @property {boolean} fleetButtonActivated - Active state of fleet button while selection is pending
53
- * @property {string} fleetButtonLabel - Label for the fleet button
54
- * @property {string} selectedVesselValue - Selected vessel value
55
- * @property {string} activeVesselValue - Active vessel value while selection is pending
56
- * @property {{value: string; label: string}[]} vesselSelectorOptions - Available vessel options
57
33
  * @fires fleet-button-click - Fired when the fleet button is clicked
58
34
  * @fires link-button-clicked - Fired when the link button is clicked
59
35
  * @fires alert-button-clicked - Fired when the alert button is clicked
@@ -66,25 +42,45 @@ import {classMap} from 'lit/directives/class-map.js';
66
42
  */
67
43
  @customElement('obc-integration-bar')
68
44
  export class ObcIntegrationBar extends LitElement {
45
+ /** Hides the home button when true */
69
46
  @property({type: Boolean}) hideHomeButton = false;
47
+ /** Toggles rendering of the clock slot */
70
48
  @property({type: Boolean}) showClock = false;
49
+ /** Toggles visibility of link button */
71
50
  @property({type: Boolean}) showLinkButton = false;
51
+ /** Activated state of link button */
72
52
  @property({type: Boolean}) linkButtonActivated = false;
53
+ /** Toggles visibility of user button */
73
54
  @property({type: Boolean}) showUserButton = false;
55
+ /** Activated state of user button */
74
56
  @property({type: Boolean}) userButtonActivated = false;
57
+ /** Toggles visibility of dimming button */
75
58
  @property({type: Boolean}) showDimmingButton = false;
59
+ /** Activated state of dimming button */
76
60
  @property({type: Boolean}) dimmingButtonActivated = false;
61
+ /** Toggles visibility of system button */
77
62
  @property({type: Boolean}) showSystemButton = false;
63
+ /** Activated state of system button */
78
64
  @property({type: Boolean}) systemButtonActivated = false;
65
+ /** Toggles visibility of screen button */
79
66
  @property({type: Boolean}) showScreenButton = false;
67
+ /** Activated state of screen button */
80
68
  @property({type: Boolean}) screenButtonActivated = false;
69
+ /** Toggles visibility of notification button */
81
70
  @property({type: Boolean}) showNotificationButton = false;
71
+ /** Activated state of notification button */
82
72
  @property({type: Boolean}) notificationButtonActivated = false;
73
+ /** Toggles visibility of alert button */
83
74
  @property({type: Boolean}) showAlertButton = false;
75
+ /** Activated state of alert button */
84
76
  @property({type: Boolean}) alertButtonActivated = false;
77
+ /** Toggles visibility of fleet button */
85
78
  @property({type: Boolean}) showFleetButton = false;
79
+ /** Selected state of fleet button */
86
80
  @property({type: Boolean}) fleetButtonSelected = false;
81
+ /** Active state of fleet button while selection is pending */
87
82
  @property({type: Boolean}) fleetButtonActivated = false;
83
+ /** Label for the fleet button */
88
84
  @property({type: String}) fleetButtonLabel = 'Fleet';
89
85
 
90
86
  @state() private buttonsOnBar = false;