@oicl/openbridge-webcomponents-full-bundle 2.0.0-next.102 → 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.
- package/bundle/openbridge-webcomponents.bundle.js +579 -143
- package/bundle/openbridge-webcomponents.bundle.js.map +1 -1
- package/custom-elements.json +338 -8
- package/dist/building-blocks/instrument-radial/instrument-radial.d.ts +22 -2
- package/dist/building-blocks/instrument-radial/instrument-radial.d.ts.map +1 -1
- package/dist/building-blocks/instrument-radial/instrument-radial.js +61 -37
- package/dist/building-blocks/instrument-radial/instrument-radial.js.map +1 -1
- package/dist/navigation-instruments/azimuth-thruster/azimuth-thruster.d.ts +14 -0
- package/dist/navigation-instruments/azimuth-thruster/azimuth-thruster.d.ts.map +1 -1
- package/dist/navigation-instruments/azimuth-thruster/azimuth-thruster.js +30 -11
- package/dist/navigation-instruments/azimuth-thruster/azimuth-thruster.js.map +1 -1
- package/dist/navigation-instruments/compass/compass.d.ts +21 -1
- package/dist/navigation-instruments/compass/compass.d.ts.map +1 -1
- package/dist/navigation-instruments/compass/compass.js +36 -16
- package/dist/navigation-instruments/compass/compass.js.map +1 -1
- package/dist/navigation-instruments/compass-sector/compass-sector.d.ts.map +1 -1
- package/dist/navigation-instruments/compass-sector/compass-sector.js.map +1 -1
- package/dist/navigation-instruments/gauge-radial/gauge-radial.css.js +34 -2
- package/dist/navigation-instruments/gauge-radial/gauge-radial.css.js.map +1 -1
- package/dist/navigation-instruments/gauge-radial/gauge-radial.d.ts +15 -0
- package/dist/navigation-instruments/gauge-radial/gauge-radial.d.ts.map +1 -1
- package/dist/navigation-instruments/gauge-radial/gauge-radial.js +35 -1
- package/dist/navigation-instruments/gauge-radial/gauge-radial.js.map +1 -1
- package/dist/navigation-instruments/heading/heading.d.ts +19 -1
- package/dist/navigation-instruments/heading/heading.d.ts.map +1 -1
- package/dist/navigation-instruments/heading/heading.js +34 -16
- package/dist/navigation-instruments/heading/heading.js.map +1 -1
- package/dist/navigation-instruments/rudder/rudder.d.ts +15 -2
- package/dist/navigation-instruments/rudder/rudder.d.ts.map +1 -1
- package/dist/navigation-instruments/rudder/rudder.js +36 -26
- package/dist/navigation-instruments/rudder/rudder.js.map +1 -1
- package/dist/navigation-instruments/speed-gauge/speed-gauge.d.ts +15 -1
- package/dist/navigation-instruments/speed-gauge/speed-gauge.d.ts.map +1 -1
- package/dist/navigation-instruments/speed-gauge/speed-gauge.js +31 -3
- package/dist/navigation-instruments/speed-gauge/speed-gauge.js.map +1 -1
- package/dist/navigation-instruments/watch/watch.d.ts +29 -1
- package/dist/navigation-instruments/watch/watch.d.ts.map +1 -1
- package/dist/navigation-instruments/watch/watch.js +52 -38
- package/dist/navigation-instruments/watch/watch.js.map +1 -1
- package/dist/svghelpers/radial-frame.d.ts +122 -0
- package/dist/svghelpers/radial-frame.d.ts.map +1 -0
- package/dist/svghelpers/radial-frame.js +259 -0
- package/dist/svghelpers/radial-frame.js.map +1 -0
- package/package.json +1 -1
- package/src/building-blocks/instrument-radial/instrument-radial.stories.ts +187 -6
- package/src/building-blocks/instrument-radial/instrument-radial.ts +98 -47
- package/src/navigation-instruments/azimuth-thruster/azimuth-thruster.stories.ts +78 -1
- package/src/navigation-instruments/azimuth-thruster/azimuth-thruster.ts +55 -11
- package/src/navigation-instruments/compass/compass.stories.ts +88 -1
- package/src/navigation-instruments/compass/compass.ts +52 -16
- package/src/navigation-instruments/compass-sector/compass-sector.ts +7 -0
- package/src/navigation-instruments/gauge-radial/gauge-radial.css +32 -2
- package/src/navigation-instruments/gauge-radial/gauge-radial.stories.ts +265 -1
- package/src/navigation-instruments/gauge-radial/gauge-radial.ts +59 -1
- package/src/navigation-instruments/heading/heading.stories.ts +92 -1
- package/src/navigation-instruments/heading/heading.ts +50 -16
- package/src/navigation-instruments/rudder/rudder.stories.ts +83 -1
- package/src/navigation-instruments/rudder/rudder.ts +61 -34
- package/src/navigation-instruments/speed-gauge/speed-gauge.stories.ts +94 -1
- package/src/navigation-instruments/speed-gauge/speed-gauge.ts +57 -4
- package/src/navigation-instruments/watch/watch.stories.ts +140 -1
- package/src/navigation-instruments/watch/watch.ts +84 -44
- package/src/storybook-util.ts +59 -0
- package/src/svghelpers/radial-frame.spec.ts +307 -0
- package/src/svghelpers/radial-frame.ts +508 -0
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import type {Meta, StoryObj} from '@storybook/web-components-vite';
|
|
2
2
|
import {ObcRudder, ObcRudderVariant} from './rudder.js';
|
|
3
3
|
import './rudder.js';
|
|
4
|
-
import {
|
|
4
|
+
import {html} from 'lit';
|
|
5
|
+
import {
|
|
6
|
+
playgroundColumn,
|
|
7
|
+
resizableStoryBox,
|
|
8
|
+
storyHint,
|
|
9
|
+
widthDecorator,
|
|
10
|
+
} from '../../storybook-util.js';
|
|
5
11
|
import {TickmarkStyle} from '../watch/tickmark.js';
|
|
6
12
|
import {InstrumentState, Priority} from '../types.js';
|
|
7
13
|
const meta: Meta<typeof ObcRudder> = {
|
|
@@ -20,6 +26,11 @@ const meta: Meta<typeof ObcRudder> = {
|
|
|
20
26
|
},
|
|
21
27
|
argTypes: {
|
|
22
28
|
width: {control: {type: 'range', min: 32, max: 1028, step: 1}},
|
|
29
|
+
faceDiameter: {
|
|
30
|
+
control: {type: 'range', min: 100, max: 600, step: 10},
|
|
31
|
+
description:
|
|
32
|
+
'Pins the outer-ring diameter in px (fixed intrinsic size, equal circumference across instruments). Clear to return to fill-the-container sizing.',
|
|
33
|
+
},
|
|
23
34
|
angle: {control: {type: 'range', min: -90, max: 90, step: 1}},
|
|
24
35
|
maxAngle: {control: {type: 'range', min: 2, max: 90, step: 1}},
|
|
25
36
|
setpoint: {control: {type: 'range', min: -90, max: 90, step: 1}},
|
|
@@ -72,3 +83,74 @@ export const ZoomedInNarrow: Story = {
|
|
|
72
83
|
showLabels: true,
|
|
73
84
|
},
|
|
74
85
|
};
|
|
86
|
+
|
|
87
|
+
type SizingPlaygroundArgs = Partial<ObcRudder> & {
|
|
88
|
+
lockFaceDiameter?: boolean;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Interactive sizing playground: drag the dashed box's bottom-right corner to
|
|
93
|
+
* resize it. The first rudder is pinned to a fixed intrinsic size by the
|
|
94
|
+
* `faceDiameter` control (its box is wider than tall, matching the 40% top
|
|
95
|
+
* clip), the second adapts to the remaining flex space, and the third is a
|
|
96
|
+
* zoomed narrow arc (`zoomToFitArc`) showing that the reserve composes with
|
|
97
|
+
* zoom (issue #1021). Enable `lockFaceDiameter` to pin all three to the same
|
|
98
|
+
* circumference. Related: *Sizing Playground* stories under Building
|
|
99
|
+
* Blocks/Watch, Building Blocks/Instrument Radial and Instruments/Gauge
|
|
100
|
+
* Radial.
|
|
101
|
+
*/
|
|
102
|
+
export const SizingPlayground: StoryObj<SizingPlaygroundArgs> = {
|
|
103
|
+
name: 'Sizing Playground — FaceDiameter + Resizable (Manual)',
|
|
104
|
+
tags: ['skip-test'],
|
|
105
|
+
parameters: {widthDecorator: false},
|
|
106
|
+
args: {
|
|
107
|
+
faceDiameter: 260,
|
|
108
|
+
lockFaceDiameter: false,
|
|
109
|
+
},
|
|
110
|
+
argTypes: {
|
|
111
|
+
lockFaceDiameter: {
|
|
112
|
+
control: 'boolean',
|
|
113
|
+
description:
|
|
114
|
+
'Apply faceDiameter to every instance (equal circumference) instead of only the first.',
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
render: (args) => {
|
|
118
|
+
const instances = [
|
|
119
|
+
{label: 'half circle', maxAngle: 45, zoom: false},
|
|
120
|
+
{label: 'half circle', maxAngle: 45, zoom: false},
|
|
121
|
+
{label: 'zoomed ±20°', maxAngle: 20, zoom: true},
|
|
122
|
+
];
|
|
123
|
+
const fd = (index: number) =>
|
|
124
|
+
index === 0 || args.lockFaceDiameter ? args.faceDiameter : undefined;
|
|
125
|
+
const caption = (index: number, label: string) =>
|
|
126
|
+
fd(index) !== undefined
|
|
127
|
+
? `${label} — pinned ${fd(index)}px`
|
|
128
|
+
: `${label} — adaptive (flex)`;
|
|
129
|
+
return html`
|
|
130
|
+
${storyHint(
|
|
131
|
+
'Drag the bottom-right corner of the dashed box to resize it. The first rudder is pinned by the faceDiameter control; the second and the zoomed narrow arc adapt to the remaining flex space. Enable lockFaceDiameter to pin all three to the same circumference.'
|
|
132
|
+
)}
|
|
133
|
+
${resizableStoryBox(
|
|
134
|
+
html`
|
|
135
|
+
${instances.map((g, index) =>
|
|
136
|
+
playgroundColumn(
|
|
137
|
+
caption(index, g.label),
|
|
138
|
+
html`
|
|
139
|
+
<obc-rudder
|
|
140
|
+
.angle=${15}
|
|
141
|
+
.setpoint=${30}
|
|
142
|
+
.maxAngle=${g.maxAngle}
|
|
143
|
+
.zoomToFitArc=${g.zoom}
|
|
144
|
+
.showLabels=${true}
|
|
145
|
+
.faceDiameter=${fd(index)}
|
|
146
|
+
></obc-rudder>
|
|
147
|
+
`,
|
|
148
|
+
{pinned: fd(index) !== undefined}
|
|
149
|
+
)
|
|
150
|
+
)}
|
|
151
|
+
`,
|
|
152
|
+
{width: 760, height: 280}
|
|
153
|
+
)}
|
|
154
|
+
`;
|
|
155
|
+
},
|
|
156
|
+
};
|
|
@@ -1,20 +1,21 @@
|
|
|
1
|
-
import {LitElement, css, html, nothing, svg} from 'lit';
|
|
1
|
+
import {LitElement, PropertyValues, css, html, nothing, svg} from 'lit';
|
|
2
2
|
import {property} from 'lit/decorators.js';
|
|
3
|
+
import {ResizeController} from '@lit-labs/observers/resize-controller.js';
|
|
3
4
|
import '../watch/watch.js';
|
|
4
5
|
import {Tickmark, TickmarkStyle, TickmarkType} from '../watch/tickmark.js';
|
|
5
|
-
import {
|
|
6
|
-
OUTER_RING_RADIUS,
|
|
7
|
-
WatchCircleType,
|
|
8
|
-
innerRingRadiusFor,
|
|
9
|
-
} from '../watch/watch.js';
|
|
6
|
+
import {WatchCircleType, innerRingRadiusFor} from '../watch/watch.js';
|
|
10
7
|
import {InstrumentState, Priority} from '../types.js';
|
|
11
8
|
import {SetpointMixin} from '../../svghelpers/setpoint-mixin.js';
|
|
12
9
|
import {AdviceState, AngleAdvice, AngleAdviceRaw} from '../watch/advice.js';
|
|
13
10
|
import {customElement} from '../../decorator.js';
|
|
14
11
|
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
applyPinnedHostSize,
|
|
13
|
+
computeRadialFrame,
|
|
14
|
+
estimateLabelWidthPx,
|
|
15
|
+
measureContainerPx,
|
|
16
|
+
observeInnerBox,
|
|
17
|
+
type RadialFrame,
|
|
18
|
+
} from '../../svghelpers/radial-frame.js';
|
|
18
19
|
|
|
19
20
|
export enum ObcRudderVariant {
|
|
20
21
|
Bar = 'bar',
|
|
@@ -95,9 +96,37 @@ export class ObcRudder extends SetpointMixin(LitElement) {
|
|
|
95
96
|
TickmarkStyle.regular;
|
|
96
97
|
@property({type: Array, attribute: false}) advices: AngleAdvice[] = [];
|
|
97
98
|
@property({type: Boolean}) zoomToFitArc: boolean = false;
|
|
99
|
+
/**
|
|
100
|
+
* Outer-ring diameter in CSS pixels. When set, the instrument renders at a
|
|
101
|
+
* fixed intrinsic size derived from the ring, arc shape and label reserve —
|
|
102
|
+
* so instruments sharing the same value have identical ring circumference
|
|
103
|
+
* regardless of label width or arc extent (like obc-donut-chart's
|
|
104
|
+
* fixedHeight). When unset (default), the instrument fills its container.
|
|
105
|
+
*/
|
|
106
|
+
@property({type: Number, attribute: 'face-diameter'})
|
|
107
|
+
faceDiameter: number | undefined;
|
|
98
108
|
|
|
99
109
|
private _radiusOffset = 0;
|
|
100
|
-
private
|
|
110
|
+
private _frame: RadialFrame | undefined;
|
|
111
|
+
|
|
112
|
+
/** Whether the host size styles were set by applyPinnedHostSize. */
|
|
113
|
+
private _hostSizePinned = false;
|
|
114
|
+
|
|
115
|
+
private _resizeController = new ResizeController(this, {});
|
|
116
|
+
|
|
117
|
+
override firstUpdated(changed: PropertyValues): void {
|
|
118
|
+
super.firstUpdated(changed);
|
|
119
|
+
observeInnerBox(this._resizeController, this.renderRoot);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
override updated(changed: PropertyValues): void {
|
|
123
|
+
super.updated(changed);
|
|
124
|
+
this._hostSizePinned = applyPinnedHostSize(
|
|
125
|
+
this,
|
|
126
|
+
this._frame,
|
|
127
|
+
this._hostSizePinned
|
|
128
|
+
);
|
|
129
|
+
}
|
|
101
130
|
|
|
102
131
|
private get _needleTransform(): string {
|
|
103
132
|
const rOff = this._radiusOffset;
|
|
@@ -243,33 +272,32 @@ export class ObcRudder extends SetpointMixin(LitElement) {
|
|
|
243
272
|
};
|
|
244
273
|
});
|
|
245
274
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
275
|
+
const frame = computeRadialFrame({
|
|
276
|
+
basePadding: 48,
|
|
277
|
+
labelWidthPx: this.tickmarksInside
|
|
278
|
+
? 0
|
|
279
|
+
: estimateLabelWidthPx(tickmarks.map((t) => t.text)),
|
|
280
|
+
clips: this.zoomToFitArc
|
|
281
|
+
? undefined
|
|
282
|
+
: {top: 40, bottom: 0, left: 0, right: 0},
|
|
283
|
+
containerPx: measureContainerPx(this),
|
|
284
|
+
faceDiameter: this.faceDiameter,
|
|
285
|
+
zoomToFitArc: this.zoomToFitArc,
|
|
286
|
+
areas,
|
|
287
|
+
innerRadius: innerRingRadiusFor(WatchCircleType.double),
|
|
288
|
+
});
|
|
289
|
+
this._radiusOffset = frame.radiusOffset;
|
|
290
|
+
this._frame = frame;
|
|
291
|
+
const shownTickmarks = frame.labelsHidden
|
|
292
|
+
? tickmarks.map((t) => ({...t, text: undefined}))
|
|
293
|
+
: tickmarks;
|
|
294
|
+
const overlayViewBox = frame.viewBox;
|
|
265
295
|
|
|
266
296
|
return html`
|
|
267
297
|
<div class="container">
|
|
268
298
|
<obc-watch
|
|
269
299
|
.touching=${this.touching}
|
|
270
|
-
.
|
|
271
|
-
.zoomToFitArc=${this.zoomToFitArc}
|
|
272
|
-
.arcFrame=${this._arcFrame}
|
|
300
|
+
.arcFrame=${frame}
|
|
273
301
|
.areas=${areas}
|
|
274
302
|
.angleSetpoint=${setpointAngle}
|
|
275
303
|
.newAngleSetpoint=${this.newSetpoint !== undefined
|
|
@@ -279,8 +307,7 @@ export class ObcRudder extends SetpointMixin(LitElement) {
|
|
|
279
307
|
.angleSetpointAtZeroDeadband=${this.setpointAtZeroDeadband}
|
|
280
308
|
.setpointOverride=${this.setpointOverride}
|
|
281
309
|
.animateSetpoint=${this.animateSetpoint}
|
|
282
|
-
.
|
|
283
|
-
.tickmarks=${tickmarks}
|
|
310
|
+
.tickmarks=${shownTickmarks}
|
|
284
311
|
.tickmarksInside=${this.tickmarksInside}
|
|
285
312
|
.tickmarkStyle=${this.tickmarkStyle}
|
|
286
313
|
.watchCircleType=${WatchCircleType.double}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import type {Meta, StoryObj} from '@storybook/web-components-vite';
|
|
2
2
|
import {ObcSpeedGauge, ObcSpeedGaugeNeedleType} from './speed-gauge.js';
|
|
3
3
|
import './speed-gauge.js';
|
|
4
|
-
import {
|
|
4
|
+
import {html} from 'lit';
|
|
5
|
+
import {
|
|
6
|
+
playgroundColumn,
|
|
7
|
+
resizableStoryBox,
|
|
8
|
+
storyHint,
|
|
9
|
+
widthDecorator,
|
|
10
|
+
} from '../../storybook-util.js';
|
|
5
11
|
import {AdviceType} from '../watch/advice.js';
|
|
6
12
|
import {TickmarkStyle} from '../watch/tickmark.js';
|
|
7
13
|
import {Priority} from '../types.js';
|
|
@@ -33,6 +39,11 @@ const meta: Meta<typeof ObcSpeedGauge> = {
|
|
|
33
39
|
options: Object.values(TickmarkStyle),
|
|
34
40
|
},
|
|
35
41
|
hasReadout: {control: 'boolean'},
|
|
42
|
+
faceDiameter: {
|
|
43
|
+
control: {type: 'range', min: 100, max: 600, step: 10},
|
|
44
|
+
description:
|
|
45
|
+
'Pins the outer-ring diameter in px (fixed intrinsic size, equal circumference across instruments). Clear to return to fill-the-container sizing.',
|
|
46
|
+
},
|
|
36
47
|
label: {control: 'text'},
|
|
37
48
|
unit: {control: 'text'},
|
|
38
49
|
fractionDigits: {control: 'number'},
|
|
@@ -73,3 +84,85 @@ export const Readout: Story = {
|
|
|
73
84
|
hasReadout: true,
|
|
74
85
|
},
|
|
75
86
|
};
|
|
87
|
+
|
|
88
|
+
export const LabelRoomHighSpeed: Story = {
|
|
89
|
+
name: 'Label Room — 4-Digit Scale in A Short Cell (#1021)',
|
|
90
|
+
args: {
|
|
91
|
+
minSpeed: 0,
|
|
92
|
+
maxSpeed: 3600,
|
|
93
|
+
speed: 2400,
|
|
94
|
+
tickmarkInterval: 900,
|
|
95
|
+
showLabels: true,
|
|
96
|
+
hasReadout: true,
|
|
97
|
+
width: 600,
|
|
98
|
+
height: 220,
|
|
99
|
+
} as never,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
type SizingPlaygroundArgs = Partial<ObcSpeedGauge> & {
|
|
103
|
+
lockFaceDiameter?: boolean;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Interactive sizing playground: drag the dashed box's bottom-right corner to
|
|
108
|
+
* resize it. The first gauge is pinned to a fixed intrinsic size by the
|
|
109
|
+
* `faceDiameter` control, while the second adapts to the remaining flex
|
|
110
|
+
* space, reserving room for its 4-digit labels adaptively (issue #1021).
|
|
111
|
+
* Enable `lockFaceDiameter` to pin both to the same circumference regardless
|
|
112
|
+
* of their label widths. Related: *Sizing Playground* stories under Building
|
|
113
|
+
* Blocks/Watch, Building Blocks/Instrument Radial and Instruments/Gauge
|
|
114
|
+
* Radial.
|
|
115
|
+
*/
|
|
116
|
+
export const SizingPlayground: StoryObj<SizingPlaygroundArgs> = {
|
|
117
|
+
name: 'Sizing Playground — FaceDiameter + Resizable (Manual)',
|
|
118
|
+
tags: ['skip-test'],
|
|
119
|
+
parameters: {widthDecorator: false},
|
|
120
|
+
args: {
|
|
121
|
+
faceDiameter: 220,
|
|
122
|
+
lockFaceDiameter: false,
|
|
123
|
+
},
|
|
124
|
+
argTypes: {
|
|
125
|
+
lockFaceDiameter: {
|
|
126
|
+
control: 'boolean',
|
|
127
|
+
description:
|
|
128
|
+
'Apply faceDiameter to every instance (equal circumference) instead of only the first.',
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
render: (args) => {
|
|
132
|
+
const instances = [
|
|
133
|
+
{label: '2-digit scale', max: 40, interval: 10},
|
|
134
|
+
{label: '4-digit scale', max: 3600, interval: 900},
|
|
135
|
+
];
|
|
136
|
+
const fd = (index: number) =>
|
|
137
|
+
index === 0 || args.lockFaceDiameter ? args.faceDiameter : undefined;
|
|
138
|
+
const caption = (index: number, label: string) =>
|
|
139
|
+
fd(index) !== undefined
|
|
140
|
+
? `${label} — pinned ${fd(index)}px`
|
|
141
|
+
: `${label} — adaptive (flex)`;
|
|
142
|
+
return html`
|
|
143
|
+
${storyHint(
|
|
144
|
+
'Drag the bottom-right corner of the dashed box to resize it. The first gauge is pinned by the faceDiameter control; the second adapts to the remaining flex space. Enable lockFaceDiameter to pin both to the same circumference.'
|
|
145
|
+
)}
|
|
146
|
+
${resizableStoryBox(
|
|
147
|
+
html`
|
|
148
|
+
${instances.map((g, index) =>
|
|
149
|
+
playgroundColumn(
|
|
150
|
+
caption(index, g.label),
|
|
151
|
+
html`
|
|
152
|
+
<obc-speed-gauge
|
|
153
|
+
.speed=${g.max * 0.6}
|
|
154
|
+
.maxSpeed=${g.max}
|
|
155
|
+
.tickmarkInterval=${g.interval}
|
|
156
|
+
.showLabels=${true}
|
|
157
|
+
.faceDiameter=${fd(index)}
|
|
158
|
+
></obc-speed-gauge>
|
|
159
|
+
`,
|
|
160
|
+
{pinned: fd(index) !== undefined}
|
|
161
|
+
)
|
|
162
|
+
)}
|
|
163
|
+
`,
|
|
164
|
+
{width: 680, height: 340}
|
|
165
|
+
)}
|
|
166
|
+
`;
|
|
167
|
+
},
|
|
168
|
+
};
|
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
import {LitElement, css, html, nothing, svg} from 'lit';
|
|
1
|
+
import {LitElement, PropertyValues, css, html, nothing, svg} from 'lit';
|
|
2
2
|
import {property} from 'lit/decorators.js';
|
|
3
|
+
import {ResizeController} from '@lit-labs/observers/resize-controller.js';
|
|
4
|
+
import {
|
|
5
|
+
applyPinnedHostSize,
|
|
6
|
+
computeRadialFrame,
|
|
7
|
+
estimateLabelWidthPx,
|
|
8
|
+
measureContainerPx,
|
|
9
|
+
observeInnerBox,
|
|
10
|
+
type RadialFrame,
|
|
11
|
+
} from '../../svghelpers/radial-frame.js';
|
|
3
12
|
import {Tickmark, TickmarkStyle, TickmarkType} from '../watch/tickmark.js';
|
|
4
13
|
import {WatchCircleType} from '../watch/watch.js';
|
|
5
14
|
import {AdviceType, AngleAdviceRaw, AdviceState} from '../watch/advice.js';
|
|
@@ -113,6 +122,36 @@ export class ObcSpeedGauge extends SetpointMixin(LitElement) {
|
|
|
113
122
|
@property({type: String}) unit = 'KN';
|
|
114
123
|
/** Number of fraction digits shown in the readout. Default `1`. */
|
|
115
124
|
@property({type: Number}) fractionDigits = 1;
|
|
125
|
+
/**
|
|
126
|
+
* Outer-ring diameter in CSS pixels. When set, the instrument renders at a
|
|
127
|
+
* fixed intrinsic size derived from the ring, arc shape and label reserve —
|
|
128
|
+
* so instruments sharing the same value have identical ring circumference
|
|
129
|
+
* regardless of label width or arc extent (like obc-donut-chart's
|
|
130
|
+
* fixedHeight). When unset (default), the instrument fills its container.
|
|
131
|
+
*/
|
|
132
|
+
@property({type: Number, attribute: 'face-diameter'})
|
|
133
|
+
faceDiameter: number | undefined;
|
|
134
|
+
|
|
135
|
+
private _frame: RadialFrame | undefined;
|
|
136
|
+
|
|
137
|
+
/** Whether the host size styles were set by applyPinnedHostSize. */
|
|
138
|
+
private _hostSizePinned = false;
|
|
139
|
+
|
|
140
|
+
private _resizeController = new ResizeController(this, {});
|
|
141
|
+
|
|
142
|
+
override firstUpdated(changed: PropertyValues): void {
|
|
143
|
+
super.firstUpdated(changed);
|
|
144
|
+
observeInnerBox(this._resizeController, this.renderRoot);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
override updated(changed: PropertyValues): void {
|
|
148
|
+
super.updated(changed);
|
|
149
|
+
this._hostSizePinned = applyPinnedHostSize(
|
|
150
|
+
this,
|
|
151
|
+
this._frame,
|
|
152
|
+
this._hostSizePinned
|
|
153
|
+
);
|
|
154
|
+
}
|
|
116
155
|
|
|
117
156
|
getAngle(v: number): number {
|
|
118
157
|
return (v / this.maxSpeed) * (180 + 45) - 90;
|
|
@@ -134,6 +173,20 @@ export class ObcSpeedGauge extends SetpointMixin(LitElement) {
|
|
|
134
173
|
|
|
135
174
|
const maxDigits = 1;
|
|
136
175
|
|
|
176
|
+
const tickmarks = this.tickmarks;
|
|
177
|
+
const frame = computeRadialFrame({
|
|
178
|
+
basePadding: 48,
|
|
179
|
+
labelWidthPx: this.tickmarksInside
|
|
180
|
+
? 0
|
|
181
|
+
: estimateLabelWidthPx(tickmarks.map((t) => t.text)),
|
|
182
|
+
containerPx: measureContainerPx(this),
|
|
183
|
+
faceDiameter: this.faceDiameter,
|
|
184
|
+
});
|
|
185
|
+
this._frame = frame;
|
|
186
|
+
const shownTickmarks = frame.labelsHidden
|
|
187
|
+
? tickmarks.map((t) => ({...t, text: undefined}))
|
|
188
|
+
: tickmarks;
|
|
189
|
+
|
|
137
190
|
return html`
|
|
138
191
|
<div class="container">
|
|
139
192
|
<obc-watch
|
|
@@ -146,8 +199,8 @@ export class ObcSpeedGauge extends SetpointMixin(LitElement) {
|
|
|
146
199
|
.angleSetpointAtZeroDeadband=${this.setpointAtZeroDeadband}
|
|
147
200
|
.setpointOverride=${this.setpointOverride}
|
|
148
201
|
.animateSetpoint=${this.animateSetpoint}
|
|
149
|
-
.
|
|
150
|
-
.tickmarks=${
|
|
202
|
+
.arcFrame=${frame}
|
|
203
|
+
.tickmarks=${shownTickmarks}
|
|
151
204
|
.tickmarksInside=${this.tickmarksInside}
|
|
152
205
|
.tickmarkStyle=${this.tickmarkStyle}
|
|
153
206
|
.advices=${this._advices}
|
|
@@ -168,7 +221,7 @@ export class ObcSpeedGauge extends SetpointMixin(LitElement) {
|
|
|
168
221
|
},
|
|
169
222
|
]}
|
|
170
223
|
></obc-watch>
|
|
171
|
-
<svg class="rudder" viewBox
|
|
224
|
+
<svg class="rudder" viewBox=${frame.viewBox}>${this.needle}</svg>
|
|
172
225
|
${this.hasReadout
|
|
173
226
|
? html`
|
|
174
227
|
${renderInstrumentReadout({
|
|
@@ -6,7 +6,12 @@ import {
|
|
|
6
6
|
WatchCircleType,
|
|
7
7
|
} from './watch.js';
|
|
8
8
|
import './watch.js';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
playgroundColumn,
|
|
11
|
+
resizableStoryBox,
|
|
12
|
+
storyHint,
|
|
13
|
+
widthDecorator,
|
|
14
|
+
} from '../../storybook-util.js';
|
|
10
15
|
import {AdviceState, AdviceType} from './advice.js';
|
|
11
16
|
import {InstrumentState, Priority} from '../types.js';
|
|
12
17
|
import {TickmarkType} from './tickmark.js';
|
|
@@ -70,6 +75,11 @@ Source of truth: \`packages/openbridge-webcomponents/src/navigation-instruments/
|
|
|
70
75
|
},
|
|
71
76
|
areas: {control: {type: 'object'}},
|
|
72
77
|
padding: {control: {type: 'range', min: 0, max: 100, step: 1}},
|
|
78
|
+
faceDiameter: {
|
|
79
|
+
control: {type: 'range', min: 100, max: 600, step: 10},
|
|
80
|
+
description:
|
|
81
|
+
'Pins the outer-ring diameter in px (fixed intrinsic size, equal circumference across instruments). Clear to return to fill-the-container sizing.',
|
|
82
|
+
},
|
|
73
83
|
vessels: {control: {type: 'object'}},
|
|
74
84
|
windKnots: {control: {type: 'range', min: 0, max: 100, step: 1}},
|
|
75
85
|
windFromDirectionDeg: {control: {type: 'range', min: 0, max: 360, step: 1}},
|
|
@@ -729,6 +739,135 @@ export const TickmarksTestInsideRotation: Story = {
|
|
|
729
739
|
},
|
|
730
740
|
};
|
|
731
741
|
|
|
742
|
+
/**
|
|
743
|
+
* Regression test for label counter-scaling after a container resize: the
|
|
744
|
+
* wrapper starts at 400px and `play` shrinks it to 240px BEFORE the snapshot.
|
|
745
|
+
* The standalone watch host renders inline (no box), so its ResizeObserver
|
|
746
|
+
* only fires via the internal `<svg>` (`observeInnerBox`) — if that
|
|
747
|
+
* observation is lost, `--scale` stays at the 400px value and the labels
|
|
748
|
+
* shrink with the box (~7px on screen) instead of holding ~12px.
|
|
749
|
+
*/
|
|
750
|
+
export const CounterScaleAfterResize: Story = {
|
|
751
|
+
args: {
|
|
752
|
+
tickmarks: [
|
|
753
|
+
...Array.from({length: 24}, (_, i) => ({
|
|
754
|
+
angle: i * 15,
|
|
755
|
+
type: TickmarkType.secondary,
|
|
756
|
+
text: `${i * 15}`,
|
|
757
|
+
})),
|
|
758
|
+
],
|
|
759
|
+
},
|
|
760
|
+
parameters: {widthDecorator: false},
|
|
761
|
+
render: (args) => html`
|
|
762
|
+
<div id="counter-scale-wrap" style="width: 400px; height: 400px;">
|
|
763
|
+
<obc-watch .tickmarks=${args.tickmarks}></obc-watch>
|
|
764
|
+
</div>
|
|
765
|
+
`,
|
|
766
|
+
play: async ({canvasElement}) => {
|
|
767
|
+
const wrap = canvasElement.querySelector(
|
|
768
|
+
'#counter-scale-wrap'
|
|
769
|
+
) as HTMLElement;
|
|
770
|
+
wrap.style.width = '240px';
|
|
771
|
+
wrap.style.height = '240px';
|
|
772
|
+
await new Promise((resolve) =>
|
|
773
|
+
requestAnimationFrame(() => requestAnimationFrame(resolve))
|
|
774
|
+
);
|
|
775
|
+
await wrap.querySelector('obc-watch')?.updateComplete;
|
|
776
|
+
},
|
|
777
|
+
};
|
|
778
|
+
|
|
779
|
+
type SizingPlaygroundArgs = Partial<ObcWatch> & {
|
|
780
|
+
lockFaceDiameter?: boolean;
|
|
781
|
+
};
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* Interactive sizing playground for the core renderer: drag the dashed box's
|
|
785
|
+
* bottom-right corner to resize it. The first watch (2-digit tick labels) is
|
|
786
|
+
* pinned to a fixed intrinsic size by the `faceDiameter` control, while the
|
|
787
|
+
* 4-digit watch and the 180° arc adapt to the remaining flex space, reserving
|
|
788
|
+
* label room adaptively (and hiding labels past the reserve cap in
|
|
789
|
+
* `svghelpers/radial-frame.ts`, issue #1021). Enable `lockFaceDiameter` to
|
|
790
|
+
* pin all three to the same circumference. Related: *Sizing Playground*
|
|
791
|
+
* stories under Building Blocks/Instrument Radial and Instruments/Gauge
|
|
792
|
+
* Radial.
|
|
793
|
+
*/
|
|
794
|
+
export const SizingPlayground: StoryObj<SizingPlaygroundArgs> = {
|
|
795
|
+
name: 'Sizing Playground — FaceDiameter + Resizable (Manual)',
|
|
796
|
+
tags: ['skip-test'],
|
|
797
|
+
parameters: {widthDecorator: false},
|
|
798
|
+
args: {
|
|
799
|
+
faceDiameter: 200,
|
|
800
|
+
lockFaceDiameter: false,
|
|
801
|
+
},
|
|
802
|
+
argTypes: {
|
|
803
|
+
lockFaceDiameter: {
|
|
804
|
+
control: 'boolean',
|
|
805
|
+
description:
|
|
806
|
+
'Apply faceDiameter to every instance (equal circumference) instead of only the first.',
|
|
807
|
+
},
|
|
808
|
+
},
|
|
809
|
+
render: (args) => {
|
|
810
|
+
const fullCircleTicks = (factor: number) =>
|
|
811
|
+
Array.from({length: 12}, (_, i) => ({
|
|
812
|
+
angle: i * 30,
|
|
813
|
+
type: TickmarkType.secondary,
|
|
814
|
+
text: `${i * 30 * factor}`,
|
|
815
|
+
}));
|
|
816
|
+
const instances = [
|
|
817
|
+
{label: '2-digit ticks', ticks: fullCircleTicks(1), areas: undefined},
|
|
818
|
+
{label: '4-digit ticks', ticks: fullCircleTicks(10), areas: undefined},
|
|
819
|
+
{
|
|
820
|
+
label: '180° arc',
|
|
821
|
+
ticks: Array.from({length: 7}, (_, i) => ({
|
|
822
|
+
angle: i * 30 - 90,
|
|
823
|
+
type: TickmarkType.secondary,
|
|
824
|
+
text: `${i * 30}`,
|
|
825
|
+
})),
|
|
826
|
+
areas: [
|
|
827
|
+
{
|
|
828
|
+
startAngle: -90,
|
|
829
|
+
endAngle: 90,
|
|
830
|
+
roundInsideCut: true,
|
|
831
|
+
roundOutsideCut: true,
|
|
832
|
+
},
|
|
833
|
+
],
|
|
834
|
+
},
|
|
835
|
+
];
|
|
836
|
+
const fd = (index: number) =>
|
|
837
|
+
index === 0 || args.lockFaceDiameter ? args.faceDiameter : undefined;
|
|
838
|
+
const caption = (index: number, label: string) =>
|
|
839
|
+
fd(index) !== undefined
|
|
840
|
+
? `${label} — pinned ${fd(index)}px`
|
|
841
|
+
: `${label} — adaptive (flex)`;
|
|
842
|
+
return html`
|
|
843
|
+
${storyHint(
|
|
844
|
+
'Drag the bottom-right corner of the dashed box to resize it. The first watch is pinned by the faceDiameter control; the 4-digit watch and the 180° arc adapt to the remaining flex space. Enable lockFaceDiameter to pin all three to the same circumference.'
|
|
845
|
+
)}
|
|
846
|
+
${resizableStoryBox(
|
|
847
|
+
html`
|
|
848
|
+
${instances.map((g, index) =>
|
|
849
|
+
playgroundColumn(
|
|
850
|
+
caption(index, g.label),
|
|
851
|
+
html`
|
|
852
|
+
<obc-watch
|
|
853
|
+
.faceDiameter=${fd(index)}
|
|
854
|
+
.tickmarks=${g.ticks}
|
|
855
|
+
.areas=${g.areas ?? []}
|
|
856
|
+
.watchCircleType=${g.areas
|
|
857
|
+
? WatchCircleType.double
|
|
858
|
+
: WatchCircleType.single}
|
|
859
|
+
></obc-watch>
|
|
860
|
+
`,
|
|
861
|
+
{pinned: fd(index) !== undefined}
|
|
862
|
+
)
|
|
863
|
+
)}
|
|
864
|
+
`,
|
|
865
|
+
{width: 760, height: 320}
|
|
866
|
+
)}
|
|
867
|
+
`;
|
|
868
|
+
},
|
|
869
|
+
};
|
|
870
|
+
|
|
732
871
|
/**
|
|
733
872
|
* Side-by-side comparison of setpoint visual states for radial instruments.
|
|
734
873
|
*
|