@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,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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
|
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
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
}
|
|
@@ -2,7 +2,13 @@ import type {Meta, StoryObj} from '@storybook/web-components-vite';
|
|
|
2
2
|
import {ObcAzimuthThruster} from './azimuth-thruster.js';
|
|
3
3
|
import './azimuth-thruster.js';
|
|
4
4
|
import {InstrumentState, Priority, Size} from '../types.js';
|
|
5
|
-
import {
|
|
5
|
+
import {html} from 'lit';
|
|
6
|
+
import {
|
|
7
|
+
playgroundColumn,
|
|
8
|
+
resizableStoryBox,
|
|
9
|
+
storyHint,
|
|
10
|
+
widthDecorator,
|
|
11
|
+
} from '../../storybook-util.js';
|
|
6
12
|
import {AdviceType} from '../watch/advice.js';
|
|
7
13
|
import {TickmarkStyle} from '../watch/tickmark.js';
|
|
8
14
|
import {PropellerType} from '../thruster/propeller.js';
|
|
@@ -12,6 +18,11 @@ const meta: Meta<typeof ObcAzimuthThruster> = {
|
|
|
12
18
|
tags: ['autodocs', '6.0'],
|
|
13
19
|
component: 'obc-azimuth-thruster',
|
|
14
20
|
argTypes: {
|
|
21
|
+
faceDiameter: {
|
|
22
|
+
control: {type: 'range', min: 100, max: 600, step: 10},
|
|
23
|
+
description:
|
|
24
|
+
'Pins the outer-ring diameter in px (fixed intrinsic size, equal circumference across instruments). Clear to return to fill-the-container sizing.',
|
|
25
|
+
},
|
|
15
26
|
thrust: {control: {type: 'range', min: -100, max: 100, step: 1}},
|
|
16
27
|
thrustSetpoint: {control: {type: 'range', min: -100, max: 100, step: 1}},
|
|
17
28
|
angle: {control: {type: 'range', min: -180, max: 180, step: 1}},
|
|
@@ -256,3 +267,69 @@ export const OffWithAngleSetpointOverride: Story = {
|
|
|
256
267
|
priority: Priority.enhanced,
|
|
257
268
|
},
|
|
258
269
|
};
|
|
270
|
+
|
|
271
|
+
type SizingPlaygroundArgs = Partial<ObcAzimuthThruster> & {
|
|
272
|
+
lockFaceDiameter?: boolean;
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Interactive sizing playground: drag the dashed box's bottom-right corner to
|
|
277
|
+
* resize it. The first thruster is pinned to a fixed intrinsic size by the
|
|
278
|
+
* `faceDiameter` control, while the second adapts to the remaining flex
|
|
279
|
+
* space, reserving room for its degree labels adaptively (issue #1021).
|
|
280
|
+
* Enable `lockFaceDiameter` to pin both to the same circumference. Related:
|
|
281
|
+
* *Sizing Playground* stories under Building Blocks/Watch, Building
|
|
282
|
+
* Blocks/Instrument Radial and Instruments/Gauge Radial.
|
|
283
|
+
*/
|
|
284
|
+
export const SizingPlayground: StoryObj<SizingPlaygroundArgs> = {
|
|
285
|
+
name: 'Sizing Playground — FaceDiameter + Resizable (Manual)',
|
|
286
|
+
tags: ['skip-test'],
|
|
287
|
+
parameters: {widthDecorator: false},
|
|
288
|
+
args: {
|
|
289
|
+
faceDiameter: 240,
|
|
290
|
+
lockFaceDiameter: false,
|
|
291
|
+
},
|
|
292
|
+
argTypes: {
|
|
293
|
+
lockFaceDiameter: {
|
|
294
|
+
control: 'boolean',
|
|
295
|
+
description:
|
|
296
|
+
'Apply faceDiameter to every instance (equal circumference) instead of only the first.',
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
render: (args) => {
|
|
300
|
+
const instances = [
|
|
301
|
+
{label: 'thruster A', angle: 45, thrust: 60},
|
|
302
|
+
{label: 'thruster B', angle: -120, thrust: 35},
|
|
303
|
+
];
|
|
304
|
+
const fd = (index: number) =>
|
|
305
|
+
index === 0 || args.lockFaceDiameter ? args.faceDiameter : undefined;
|
|
306
|
+
const caption = (index: number, label: string) =>
|
|
307
|
+
fd(index) !== undefined
|
|
308
|
+
? `${label} — pinned ${fd(index)}px`
|
|
309
|
+
: `${label} — adaptive (flex)`;
|
|
310
|
+
return html`
|
|
311
|
+
${storyHint(
|
|
312
|
+
'Drag the bottom-right corner of the dashed box to resize it. The first thruster is pinned by the faceDiameter control; the second adapts to the remaining flex space. Enable lockFaceDiameter to pin both to the same circumference.'
|
|
313
|
+
)}
|
|
314
|
+
${resizableStoryBox(
|
|
315
|
+
html`
|
|
316
|
+
${instances.map((g, index) =>
|
|
317
|
+
playgroundColumn(
|
|
318
|
+
caption(index, g.label),
|
|
319
|
+
html`
|
|
320
|
+
<obc-azimuth-thruster
|
|
321
|
+
.angle=${g.angle}
|
|
322
|
+
.thrust=${g.thrust}
|
|
323
|
+
.showLabels=${true}
|
|
324
|
+
.faceDiameter=${fd(index)}
|
|
325
|
+
></obc-azimuth-thruster>
|
|
326
|
+
`,
|
|
327
|
+
{pinned: fd(index) !== undefined}
|
|
328
|
+
)
|
|
329
|
+
)}
|
|
330
|
+
`,
|
|
331
|
+
{width: 680, height: 400}
|
|
332
|
+
)}
|
|
333
|
+
`;
|
|
334
|
+
},
|
|
335
|
+
};
|
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import {html, LitElement, unsafeCSS} from 'lit';
|
|
2
2
|
import {property} from 'lit/decorators.js';
|
|
3
3
|
import type {PropertyValues} from 'lit';
|
|
4
|
+
import {ResizeController} from '@lit-labs/observers/resize-controller.js';
|
|
5
|
+
import {
|
|
6
|
+
applyPinnedHostSize,
|
|
7
|
+
computeRadialFrame,
|
|
8
|
+
estimateLabelWidthPx,
|
|
9
|
+
measureContainerPx,
|
|
10
|
+
observeInnerBox,
|
|
11
|
+
type RadialFrame,
|
|
12
|
+
} from '../../svghelpers/radial-frame.js';
|
|
4
13
|
import {InstrumentState, Priority} from '../types.js';
|
|
5
14
|
import {SetpointBundle} from '../../svghelpers/setpoint-bundle.js';
|
|
6
15
|
import {thruster} from '../thruster/thruster.js';
|
|
7
16
|
import '../watch/watch.js';
|
|
8
17
|
import componentStyle from './azimuth-thruster.css?inline';
|
|
9
|
-
import {ifDefined} from 'lit/directives/if-defined.js';
|
|
10
18
|
import {AdviceState, AngleAdvice, AngleAdviceRaw} from '../watch/advice.js';
|
|
11
19
|
import {Tickmark, TickmarkStyle, TickmarkType} from '../watch/tickmark.js';
|
|
12
20
|
import {LinearAdvice} from '../thruster/advice.js';
|
|
@@ -134,6 +142,36 @@ export class ObcAzimuthThruster extends LitElement {
|
|
|
134
142
|
@property({type: String}) tickmarkStyle: TickmarkStyle =
|
|
135
143
|
TickmarkStyle.regular;
|
|
136
144
|
@property({type: Boolean}) starboardPortIndicator: boolean = false;
|
|
145
|
+
/**
|
|
146
|
+
* Outer-ring diameter in CSS pixels. When set, the instrument renders at a
|
|
147
|
+
* fixed intrinsic size derived from the ring, arc shape and label reserve —
|
|
148
|
+
* so instruments sharing the same value have identical ring circumference
|
|
149
|
+
* regardless of label width or arc extent (like obc-donut-chart's
|
|
150
|
+
* fixedHeight). When unset (default), the instrument fills its container.
|
|
151
|
+
*/
|
|
152
|
+
@property({type: Number, attribute: 'face-diameter'})
|
|
153
|
+
faceDiameter: number | undefined;
|
|
154
|
+
|
|
155
|
+
private _frame: RadialFrame | undefined;
|
|
156
|
+
|
|
157
|
+
/** Whether the host size styles were set by applyPinnedHostSize. */
|
|
158
|
+
private _hostSizePinned = false;
|
|
159
|
+
|
|
160
|
+
private _resizeController = new ResizeController(this, {});
|
|
161
|
+
|
|
162
|
+
override firstUpdated(changed: PropertyValues): void {
|
|
163
|
+
super.firstUpdated(changed);
|
|
164
|
+
observeInnerBox(this._resizeController, this.renderRoot);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
override updated(changed: PropertyValues): void {
|
|
168
|
+
super.updated(changed);
|
|
169
|
+
this._hostSizePinned = applyPinnedHostSize(
|
|
170
|
+
this,
|
|
171
|
+
this._frame,
|
|
172
|
+
this._hostSizePinned
|
|
173
|
+
);
|
|
174
|
+
}
|
|
137
175
|
|
|
138
176
|
private get angleAdviceRaw(): AngleAdviceRaw[] {
|
|
139
177
|
return this.angleAdvices.map((advice) => {
|
|
@@ -236,20 +274,27 @@ export class ObcAzimuthThruster extends LitElement {
|
|
|
236
274
|
|
|
237
275
|
const tickmarks = this.getTickmarks();
|
|
238
276
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
277
|
+
const frame = computeRadialFrame({
|
|
278
|
+
basePadding: this.hasLabelSpacer ? 24 : 16,
|
|
279
|
+
labelWidthPx:
|
|
280
|
+
this.hasLabelSpacer && !this.tickmarksInside
|
|
281
|
+
? estimateLabelWidthPx(tickmarks.map((t) => t.text))
|
|
282
|
+
: 0,
|
|
283
|
+
containerPx: measureContainerPx(this),
|
|
284
|
+
faceDiameter: this.faceDiameter,
|
|
285
|
+
});
|
|
286
|
+
this._frame = frame;
|
|
287
|
+
const shownTickmarks = frame.labelsHidden
|
|
288
|
+
? tickmarks.map((t) => ({...t, text: undefined}))
|
|
289
|
+
: tickmarks;
|
|
290
|
+
const viewBox = frame.viewBox;
|
|
247
291
|
|
|
248
292
|
return html`
|
|
249
293
|
<div class="container">
|
|
250
294
|
<obc-watch
|
|
251
295
|
.touching=${this.touching}
|
|
252
|
-
.
|
|
296
|
+
.arcFrame=${frame}
|
|
297
|
+
.tickmarks=${shownTickmarks}
|
|
253
298
|
.state=${this.state}
|
|
254
299
|
.priority=${this.priority}
|
|
255
300
|
.angleSetpoint=${this.angleSetpoint}
|
|
@@ -260,7 +305,6 @@ export class ObcAzimuthThruster extends LitElement {
|
|
|
260
305
|
.animateSetpoint=${this.animateSetpoint}
|
|
261
306
|
.tickmarksInside=${this.tickmarksInside}
|
|
262
307
|
.tickmarkStyle=${this.tickmarkStyle}
|
|
263
|
-
padding=${ifDefined(!this.hasLabelSpacer ? 16 : undefined)}
|
|
264
308
|
.advices=${this.angleAdviceRaw}
|
|
265
309
|
.starboardPortIndicator=${this.starboardPortIndicator}
|
|
266
310
|
></obc-watch>
|
|
@@ -6,7 +6,13 @@ import {
|
|
|
6
6
|
RotType,
|
|
7
7
|
} from './compass.js';
|
|
8
8
|
import './compass.js';
|
|
9
|
-
import {
|
|
9
|
+
import {html} from 'lit';
|
|
10
|
+
import {
|
|
11
|
+
playgroundColumn,
|
|
12
|
+
resizableStoryBox,
|
|
13
|
+
storyHint,
|
|
14
|
+
widthDecorator,
|
|
15
|
+
} from '../../storybook-util.js';
|
|
10
16
|
import {AdviceType} from '../watch/advice.js';
|
|
11
17
|
import {VesselImage} from '../watch/watch.js';
|
|
12
18
|
import {topVessels} from '../watch/vessels/storybook-helper.js';
|
|
@@ -48,6 +54,11 @@ const meta: Meta<typeof ObcCompass> = {
|
|
|
48
54
|
},
|
|
49
55
|
argTypes: {
|
|
50
56
|
width: {control: {type: 'range', min: 32, max: 1028, step: 1}},
|
|
57
|
+
faceDiameter: {
|
|
58
|
+
control: {type: 'range', min: 100, max: 600, step: 10},
|
|
59
|
+
description:
|
|
60
|
+
'Pins the outer-ring diameter in px (fixed intrinsic size, equal circumference across instruments). Clear to return to fill-the-container sizing.',
|
|
61
|
+
},
|
|
51
62
|
heading: {control: {type: 'range', min: 0, max: 360, step: 1}},
|
|
52
63
|
courseOverGround: {control: {type: 'range', min: 0, max: 360, step: 1}},
|
|
53
64
|
headingSetpoint: {control: {type: 'range', min: 0, max: 360, step: 1}},
|
|
@@ -171,3 +182,79 @@ export const WithRateOfTurnDegreesPerMinute: Story = {
|
|
|
171
182
|
priorityElements: [CompassPriorityElement.hdg, CompassPriorityElement.rot],
|
|
172
183
|
},
|
|
173
184
|
};
|
|
185
|
+
|
|
186
|
+
export const SmallContainer: Story = {
|
|
187
|
+
name: 'Small Container (250px, Labels + Decor Reserve)',
|
|
188
|
+
args: {
|
|
189
|
+
width: 250,
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
type SizingPlaygroundArgs = Partial<ObcCompass> & {
|
|
194
|
+
lockFaceDiameter?: boolean;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Interactive sizing playground: drag the dashed box's bottom-right corner to
|
|
199
|
+
* resize it. The first compass is pinned to a fixed intrinsic size by the
|
|
200
|
+
* `faceDiameter` control, while the second adapts to the remaining flex
|
|
201
|
+
* space, reserving room for the NSWE labels, north arrow and wind symbols
|
|
202
|
+
* adaptively (issue #1021). Enable `lockFaceDiameter` to pin both to the same
|
|
203
|
+
* circumference. Related: *Sizing Playground* stories under Building
|
|
204
|
+
* Blocks/Watch, Building Blocks/Instrument Radial and Instruments/Gauge
|
|
205
|
+
* Radial.
|
|
206
|
+
*/
|
|
207
|
+
export const SizingPlayground: StoryObj<SizingPlaygroundArgs> = {
|
|
208
|
+
name: 'Sizing Playground — FaceDiameter + Resizable (Manual)',
|
|
209
|
+
tags: ['skip-test'],
|
|
210
|
+
parameters: {widthDecorator: false},
|
|
211
|
+
args: {
|
|
212
|
+
faceDiameter: 240,
|
|
213
|
+
lockFaceDiameter: false,
|
|
214
|
+
},
|
|
215
|
+
argTypes: {
|
|
216
|
+
lockFaceDiameter: {
|
|
217
|
+
control: 'boolean',
|
|
218
|
+
description:
|
|
219
|
+
'Apply faceDiameter to every instance (equal circumference) instead of only the first.',
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
render: (args) => {
|
|
223
|
+
const instances = [
|
|
224
|
+
{label: 'compass A', heading: 311},
|
|
225
|
+
{label: 'compass B', heading: 45},
|
|
226
|
+
];
|
|
227
|
+
const fd = (index: number) =>
|
|
228
|
+
index === 0 || args.lockFaceDiameter ? args.faceDiameter : undefined;
|
|
229
|
+
const caption = (index: number, label: string) =>
|
|
230
|
+
fd(index) !== undefined
|
|
231
|
+
? `${label} — pinned ${fd(index)}px`
|
|
232
|
+
: `${label} — adaptive (flex)`;
|
|
233
|
+
return html`
|
|
234
|
+
${storyHint(
|
|
235
|
+
'Drag the bottom-right corner of the dashed box to resize it. The first compass is pinned by the faceDiameter control; the second adapts to the remaining flex space. Enable lockFaceDiameter to pin both to the same circumference.'
|
|
236
|
+
)}
|
|
237
|
+
${resizableStoryBox(
|
|
238
|
+
html`
|
|
239
|
+
${instances.map((g, index) =>
|
|
240
|
+
playgroundColumn(
|
|
241
|
+
caption(index, g.label),
|
|
242
|
+
html`
|
|
243
|
+
<obc-compass
|
|
244
|
+
.heading=${g.heading}
|
|
245
|
+
.courseOverGround=${g.heading + 27}
|
|
246
|
+
.currentWindSpeedKnots=${20}
|
|
247
|
+
.windFromDirection=${45}
|
|
248
|
+
.showLabels=${true}
|
|
249
|
+
.faceDiameter=${fd(index)}
|
|
250
|
+
></obc-compass>
|
|
251
|
+
`,
|
|
252
|
+
{pinned: fd(index) !== undefined}
|
|
253
|
+
)
|
|
254
|
+
)}
|
|
255
|
+
`,
|
|
256
|
+
{width: 680, height: 400}
|
|
257
|
+
)}
|
|
258
|
+
`;
|
|
259
|
+
},
|
|
260
|
+
};
|
|
@@ -5,6 +5,14 @@ import {Tickmark, TickmarkType} from '../watch/tickmark.js';
|
|
|
5
5
|
import {arrow, ArrowStyle} from './arrow.js';
|
|
6
6
|
import {AdviceState, AngleAdvice, AngleAdviceRaw} from '../watch/advice.js';
|
|
7
7
|
import {ResizeController} from '@lit-labs/observers/resize-controller.js';
|
|
8
|
+
import {
|
|
9
|
+
applyPinnedHostSize,
|
|
10
|
+
computeRadialFrame,
|
|
11
|
+
measureContainerPx,
|
|
12
|
+
NORTH_ARROW_WIDTH_PX,
|
|
13
|
+
NSWE_LABEL_WIDTH_PX,
|
|
14
|
+
type RadialFrame,
|
|
15
|
+
} from '../../svghelpers/radial-frame.js';
|
|
8
16
|
import {
|
|
9
17
|
VesselImage,
|
|
10
18
|
VesselImageSize,
|
|
@@ -217,6 +225,15 @@ export class ObcCompass extends LitElement {
|
|
|
217
225
|
@property({type: Boolean}) showLabels: boolean = false;
|
|
218
226
|
/** When true, labels and north arrow are placed inside the outer ring. */
|
|
219
227
|
@property({type: Boolean}) tickmarksInside: boolean = false;
|
|
228
|
+
/**
|
|
229
|
+
* Outer-ring diameter in CSS pixels. When set, the instrument renders at a
|
|
230
|
+
* fixed intrinsic size derived from the ring, arc shape and label reserve —
|
|
231
|
+
* so instruments sharing the same value have identical ring circumference
|
|
232
|
+
* regardless of label width or arc extent (like obc-donut-chart's
|
|
233
|
+
* fixedHeight). When unset (default), the instrument fills its container.
|
|
234
|
+
*/
|
|
235
|
+
@property({type: Number, attribute: 'face-diameter'})
|
|
236
|
+
faceDiameter: number | undefined;
|
|
220
237
|
|
|
221
238
|
private _headingSp = new SetpointBundle({
|
|
222
239
|
angularWraparound: true,
|
|
@@ -257,18 +274,32 @@ export class ObcCompass extends LitElement {
|
|
|
257
274
|
return this.rateOfTurnDegreesPerMinute ?? this.rotationsPerMinute;
|
|
258
275
|
}
|
|
259
276
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
277
|
+
/**
|
|
278
|
+
* Pixel cost of the outside decor: the always-rendered north-arrow glyph
|
|
279
|
+
* plus, only while shown, the NSWE labels (both keep a constant on-screen
|
|
280
|
+
* size via `1/scale` terms). Feeds the frame's width-aware reserve,
|
|
281
|
+
* replacing the former empirical `72 + delta(clientSize)` padding.
|
|
282
|
+
* Wind/current symbols at their default radius are covered by the base
|
|
283
|
+
* padding of 72.
|
|
284
|
+
*/
|
|
285
|
+
private getOutsideDecorPx(): number {
|
|
286
|
+
if (this.tickmarksInside) {
|
|
287
|
+
return 0;
|
|
269
288
|
}
|
|
289
|
+
return NORTH_ARROW_WIDTH_PX + (this.showLabels ? NSWE_LABEL_WIDTH_PX : 0);
|
|
290
|
+
}
|
|
270
291
|
|
|
271
|
-
|
|
292
|
+
/** Whether the host size styles were set by applyPinnedHostSize. */
|
|
293
|
+
private _hostSizePinned = false;
|
|
294
|
+
private _frame: RadialFrame | undefined;
|
|
295
|
+
|
|
296
|
+
override updated(changed: PropertyValues): void {
|
|
297
|
+
super.updated(changed);
|
|
298
|
+
this._hostSizePinned = applyPinnedHostSize(
|
|
299
|
+
this,
|
|
300
|
+
this._frame,
|
|
301
|
+
this._hostSizePinned
|
|
302
|
+
);
|
|
272
303
|
}
|
|
273
304
|
|
|
274
305
|
private get angleAdviceRaw(): AngleAdviceRaw[] {
|
|
@@ -315,23 +346,28 @@ export class ObcCompass extends LitElement {
|
|
|
315
346
|
{angle: 270, type: TickmarkType.main},
|
|
316
347
|
];
|
|
317
348
|
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
349
|
+
const frame = computeRadialFrame({
|
|
350
|
+
basePadding: 72,
|
|
351
|
+
labelWidthPx: this.getOutsideDecorPx(),
|
|
352
|
+
containerPx: measureContainerPx(this),
|
|
353
|
+
faceDiameter: this.faceDiameter,
|
|
354
|
+
});
|
|
355
|
+
this._frame = frame;
|
|
356
|
+
const viewBox = frame.viewBox;
|
|
321
357
|
|
|
322
358
|
return html`
|
|
323
359
|
<div class="container">
|
|
324
360
|
<obc-watch
|
|
325
361
|
.touching=${this.touching}
|
|
326
|
-
.
|
|
362
|
+
.arcFrame=${frame}
|
|
327
363
|
.advices=${this.angleAdviceRaw}
|
|
328
364
|
.tickmarks=${tickmarks}
|
|
329
365
|
.state=${this.state}
|
|
330
366
|
.watchCircleType=${WatchCircleType.triple}
|
|
331
|
-
.showLabels=${this.showLabels}
|
|
367
|
+
.showLabels=${this.showLabels && !frame.labelsHidden}
|
|
332
368
|
.tickmarksInside=${this.tickmarksInside}
|
|
333
369
|
.crosshairEnabled=${true}
|
|
334
|
-
.northArrow=${
|
|
370
|
+
.northArrow=${!frame.labelsHidden}
|
|
335
371
|
.angleSetpoint=${this.headingSetpoint ?? undefined}
|
|
336
372
|
.newAngleSetpoint=${this.newHeadingSetpoint}
|
|
337
373
|
.atAngleSetpoint=${this._headingSp.computeAtSetpoint(this.heading)}
|
|
@@ -31,6 +31,13 @@ export enum CompassSectorPriorityElement {
|
|
|
31
31
|
rot = 'rot',
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
// Fixed frame padding for both the zoomed and un-zoomed paths. This component
|
|
35
|
+
// deliberately keeps its bespoke FOV-compression geometry and does NOT use
|
|
36
|
+
// svghelpers/radial-frame.ts: the viewBox is cached per FOV (a
|
|
37
|
+
// container-size-dependent label reserve would invalidate that), and the
|
|
38
|
+
// 72-unit padding covers the 3-char degree labels at typical sizes.
|
|
39
|
+
// TODO(#1021): adopt computeRadialFrame if degree labels ever clip when the
|
|
40
|
+
// component is shrunk far below its design size.
|
|
34
41
|
const PADDING = 72;
|
|
35
42
|
const WATCH_TYPE = WatchCircleType.triple;
|
|
36
43
|
const INNER_RADIUS = innerRingRadiusFor(WATCH_TYPE);
|