@oicl/openbridge-webcomponents-full-bundle 2.0.0-next.111 → 2.0.0-next.113
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 +185 -29
- package/bundle/openbridge-webcomponents.bundle.js.map +1 -1
- package/custom-elements.json +180 -21
- package/dist/building-blocks/readout-block/readout-block.d.ts +29 -5
- package/dist/building-blocks/readout-block/readout-block.d.ts.map +1 -1
- package/dist/building-blocks/readout-block/readout-block.js +21 -7
- package/dist/building-blocks/readout-block/readout-block.js.map +1 -1
- package/dist/navigation-instruments/compass/compass.d.ts +1 -1
- package/dist/navigation-instruments/compass/compass.d.ts.map +1 -1
- package/dist/navigation-instruments/compass/compass.js +1 -1
- package/dist/navigation-instruments/compass/compass.js.map +1 -1
- package/dist/navigation-instruments/current/current.d.ts +25 -0
- package/dist/navigation-instruments/current/current.d.ts.map +1 -1
- package/dist/navigation-instruments/current/current.js +25 -4
- package/dist/navigation-instruments/current/current.js.map +1 -1
- package/dist/navigation-instruments/heading/heading.js +1 -1
- package/dist/navigation-instruments/heading/heading.js.map +1 -1
- package/dist/navigation-instruments/pitch-roll-heave/pitch-roll-heave.d.ts +6 -0
- package/dist/navigation-instruments/pitch-roll-heave/pitch-roll-heave.d.ts.map +1 -1
- package/dist/navigation-instruments/pitch-roll-heave/pitch-roll-heave.js +8 -1
- package/dist/navigation-instruments/pitch-roll-heave/pitch-roll-heave.js.map +1 -1
- package/dist/navigation-instruments/rate-of-turn/rate-of-turn.d.ts +5 -2
- package/dist/navigation-instruments/rate-of-turn/rate-of-turn.d.ts.map +1 -1
- package/dist/navigation-instruments/rate-of-turn/rate-of-turn.js.map +1 -1
- package/dist/navigation-instruments/readout/readout-formatters.d.ts +31 -0
- package/dist/navigation-instruments/readout/readout-formatters.d.ts.map +1 -1
- package/dist/navigation-instruments/readout/readout-formatters.js +55 -1
- package/dist/navigation-instruments/readout/readout-formatters.js.map +1 -1
- package/dist/navigation-instruments/readout/readout.d.ts +26 -1
- package/dist/navigation-instruments/readout/readout.d.ts.map +1 -1
- package/dist/navigation-instruments/readout/readout.js +16 -3
- package/dist/navigation-instruments/readout/readout.js.map +1 -1
- package/dist/navigation-instruments/readout-list/readout-list.d.ts +6 -1
- package/dist/navigation-instruments/readout-list/readout-list.d.ts.map +1 -1
- package/dist/navigation-instruments/readout-list/readout-list.js +21 -4
- package/dist/navigation-instruments/readout-list/readout-list.js.map +1 -1
- package/dist/navigation-instruments/readout-list-item/readout-list-item.d.ts +26 -1
- package/dist/navigation-instruments/readout-list-item/readout-list-item.d.ts.map +1 -1
- package/dist/navigation-instruments/readout-list-item/readout-list-item.js +15 -3
- package/dist/navigation-instruments/readout-list-item/readout-list-item.js.map +1 -1
- package/dist/navigation-instruments/top-view-propulsion/top-view-propulsion.d.ts +6 -0
- package/dist/navigation-instruments/top-view-propulsion/top-view-propulsion.d.ts.map +1 -1
- package/dist/navigation-instruments/top-view-propulsion/top-view-propulsion.js +19 -4
- package/dist/navigation-instruments/top-view-propulsion/top-view-propulsion.js.map +1 -1
- package/dist/navigation-instruments/watch/environment.d.ts +8 -0
- package/dist/navigation-instruments/watch/environment.d.ts.map +1 -1
- package/dist/navigation-instruments/watch/environment.js +3 -0
- package/dist/navigation-instruments/watch/environment.js.map +1 -1
- package/dist/navigation-instruments/watch/force-pattern.d.ts +20 -2
- package/dist/navigation-instruments/watch/force-pattern.d.ts.map +1 -1
- package/dist/navigation-instruments/watch/force-pattern.js +12 -5
- package/dist/navigation-instruments/watch/force-pattern.js.map +1 -1
- package/dist/navigation-instruments/watch/watch.d.ts.map +1 -1
- package/dist/navigation-instruments/watch/watch.js +6 -4
- package/dist/navigation-instruments/watch/watch.js.map +1 -1
- package/dist/navigation-instruments/wind/wind.d.ts +5 -2
- package/dist/navigation-instruments/wind/wind.d.ts.map +1 -1
- package/dist/navigation-instruments/wind/wind.js +5 -1
- package/dist/navigation-instruments/wind/wind.js.map +1 -1
- package/package.json +1 -1
- package/src/building-blocks/readout-block/readout-block.stories.ts +112 -3
- package/src/building-blocks/readout-block/readout-block.ts +64 -10
- package/src/navigation-instruments/compass/compass.ts +2 -2
- package/src/navigation-instruments/current/current.stories.ts +33 -0
- package/src/navigation-instruments/current/current.ts +37 -3
- package/src/navigation-instruments/heading/heading.stories.ts +6 -4
- package/src/navigation-instruments/heading/heading.ts +1 -1
- package/src/navigation-instruments/pitch-roll-heave/pitch-roll-heave.ts +8 -1
- package/src/navigation-instruments/rate-of-turn/rate-of-turn.stories.ts +3 -3
- package/src/navigation-instruments/rate-of-turn/rate-of-turn.ts +5 -2
- package/src/navigation-instruments/readout/readout-formatters.spec.ts +229 -0
- package/src/navigation-instruments/readout/readout-formatters.ts +105 -0
- package/src/navigation-instruments/readout/readout.stories.ts +167 -3
- package/src/navigation-instruments/readout/readout.ts +52 -4
- package/src/navigation-instruments/readout-list/readout-list.stories.ts +122 -1
- package/src/navigation-instruments/readout-list/readout-list.ts +54 -5
- package/src/navigation-instruments/readout-list-item/readout-list-item.stories.ts +124 -4
- package/src/navigation-instruments/readout-list-item/readout-list-item.ts +53 -4
- package/src/navigation-instruments/top-view-propulsion/top-view-propulsion.ts +20 -2
- package/src/navigation-instruments/watch/environment.ts +10 -0
- package/src/navigation-instruments/watch/force-pattern.spec.ts +42 -0
- package/src/navigation-instruments/watch/force-pattern.ts +44 -7
- package/src/navigation-instruments/watch/watch.ts +20 -5
- package/src/navigation-instruments/wind/wind.spec.ts +3 -1
- package/src/navigation-instruments/wind/wind.ts +12 -3
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import type {Meta, StoryObj} from '@storybook/web-components-vite';
|
|
2
2
|
import {html, nothing} from 'lit';
|
|
3
|
+
import {expect} from 'storybook/test';
|
|
3
4
|
import {
|
|
4
5
|
ReadoutBlockVariant,
|
|
5
6
|
ReadoutBlockSize,
|
|
6
7
|
ReadoutBlockDataQuality,
|
|
7
8
|
ObcTextboxFontWeight,
|
|
8
9
|
ObcTextboxAlignment,
|
|
10
|
+
ReadoutValueType,
|
|
9
11
|
} from './readout-block.js';
|
|
10
12
|
import './readout-block.js';
|
|
11
13
|
import '../../icons/icon-placeholder.js';
|
|
@@ -19,7 +21,8 @@ const NONE = 'none';
|
|
|
19
21
|
|
|
20
22
|
type BlockArgs = {
|
|
21
23
|
variant: ReadoutBlockVariant;
|
|
22
|
-
value: number;
|
|
24
|
+
value: number | string | null;
|
|
25
|
+
valueType: ReadoutValueType;
|
|
23
26
|
size: ReadoutBlockSize;
|
|
24
27
|
enhanced: boolean;
|
|
25
28
|
weight: ObcTextboxFontWeight;
|
|
@@ -43,6 +46,7 @@ function renderBlock(args: Partial<BlockArgs>) {
|
|
|
43
46
|
<obc-readout-block
|
|
44
47
|
.variant=${args.variant ?? ReadoutBlockVariant.value}
|
|
45
48
|
.value=${args.value ?? null}
|
|
49
|
+
.valueType=${args.valueType ?? ReadoutValueType.number}
|
|
46
50
|
.size=${args.size ?? ReadoutBlockSize.small}
|
|
47
51
|
.enhanced=${args.enhanced ?? false}
|
|
48
52
|
.weight=${args.weight ?? ObcTextboxFontWeight.regular}
|
|
@@ -128,6 +132,7 @@ const meta = {
|
|
|
128
132
|
args: {
|
|
129
133
|
variant: ReadoutBlockVariant.value,
|
|
130
134
|
value: 123,
|
|
135
|
+
valueType: ReadoutValueType.number,
|
|
131
136
|
size: ReadoutBlockSize.small,
|
|
132
137
|
enhanced: false,
|
|
133
138
|
weight: ObcTextboxFontWeight.regular,
|
|
@@ -143,7 +148,14 @@ const meta = {
|
|
|
143
148
|
dataQuality: NONE,
|
|
144
149
|
},
|
|
145
150
|
argTypes: {
|
|
146
|
-
|
|
151
|
+
// Text control (not number) so both value types are exercisable. Under
|
|
152
|
+
// valueType=number a numeric string resolves back to a number; entering
|
|
153
|
+
// non-numeric text there throws, which is the intended contract.
|
|
154
|
+
value: {control: {type: 'text'}},
|
|
155
|
+
valueType: {
|
|
156
|
+
control: {type: 'inline-radio'},
|
|
157
|
+
options: Object.values(ReadoutValueType),
|
|
158
|
+
},
|
|
147
159
|
variant: {
|
|
148
160
|
control: {type: 'select'},
|
|
149
161
|
options: Object.values(ReadoutBlockVariant),
|
|
@@ -243,6 +255,66 @@ export const OffText: Story = {
|
|
|
243
255
|
]),
|
|
244
256
|
};
|
|
245
257
|
|
|
258
|
+
/**
|
|
259
|
+
* `valueType="text"` renders `value` verbatim instead of formatting it as a
|
|
260
|
+
* number — for readings that are states rather than quantities.
|
|
261
|
+
*
|
|
262
|
+
* The numeric format options (`fractionDigits`, `maxDigits`, `hintedZeros`) are
|
|
263
|
+
* ignored in this mode; an explicit `spaceReserver` still applies. Passing text
|
|
264
|
+
* while `valueType` is `number` throws a `TypeError` rather than rendering
|
|
265
|
+
* `NaN`, while a numeric-looking string such as `"12.4"` is accepted and parsed
|
|
266
|
+
* so plain-HTML `value="12.4"` keeps working.
|
|
267
|
+
*/
|
|
268
|
+
export const TextValue: Story = {
|
|
269
|
+
render: () =>
|
|
270
|
+
renderShowcase([
|
|
271
|
+
{
|
|
272
|
+
title: 'text',
|
|
273
|
+
args: {value: 'Auto', valueType: ReadoutValueType.text},
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
title: 'longer text',
|
|
277
|
+
args: {value: 'Thermo On', valueType: ReadoutValueType.text},
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
title: 'verbatim "1.50"',
|
|
281
|
+
args: {
|
|
282
|
+
value: '1.50',
|
|
283
|
+
valueType: ReadoutValueType.text,
|
|
284
|
+
fractionDigits: 1,
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
title: 'maxDigits ignored',
|
|
289
|
+
args: {value: 'Auto', valueType: ReadoutValueType.text, maxDigits: 4},
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
title: 'spaceReserver honoured',
|
|
293
|
+
args: {
|
|
294
|
+
value: 'Auto',
|
|
295
|
+
valueType: ReadoutValueType.text,
|
|
296
|
+
spaceReserver: 'Thermo On',
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
title: 'null → dash',
|
|
301
|
+
args: {value: null, valueType: ReadoutValueType.text},
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
title: 'numeric string in number mode',
|
|
305
|
+
args: {value: '12.4', fractionDigits: 1},
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
title: 'text + degree',
|
|
309
|
+
args: {
|
|
310
|
+
value: 'Auto',
|
|
311
|
+
valueType: ReadoutValueType.text,
|
|
312
|
+
hasDegree: true,
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
]),
|
|
316
|
+
};
|
|
317
|
+
|
|
246
318
|
/**
|
|
247
319
|
* Hinted zeros pad the integer part up to `maxDigits` as muted leading zeros.
|
|
248
320
|
* When enabled they take priority over `spaceReserver` (they already fill to
|
|
@@ -306,6 +378,43 @@ export const Alignment: Story = {
|
|
|
306
378
|
),
|
|
307
379
|
};
|
|
308
380
|
|
|
381
|
+
/**
|
|
382
|
+
* Regression test for a validation hole, not a visual case.
|
|
383
|
+
*
|
|
384
|
+
* When `willUpdate` throws, Lit's `performUpdate` catch calls `__markUpdated()`,
|
|
385
|
+
* which clears the changed-properties map. Validation used to be gated on
|
|
386
|
+
* `changed.has('value') || changed.has('valueType')`, so the NEXT update —
|
|
387
|
+
* driven by any other property, e.g. `obc-readout-list.align()` writing the
|
|
388
|
+
* shared reservers — saw an empty map, skipped the check, and rendered the
|
|
389
|
+
* invalid value as a plain dash. Loud once, then silent forever.
|
|
390
|
+
*
|
|
391
|
+
* An EMPTY changed map is exactly what Lit leaves behind after a throw, so
|
|
392
|
+
* `willUpdate` is invoked directly with one. The element is deliberately left
|
|
393
|
+
* detached: an unconnected `LitElement` never starts its update cycle, so this
|
|
394
|
+
* exercises the guard without the real throw escaping the scheduler as an
|
|
395
|
+
* unhandled rejection.
|
|
396
|
+
*/
|
|
397
|
+
export const TestValidationSurvivesUnrelatedUpdate: Story = {
|
|
398
|
+
render: () => html`<span>Regression test — see the play function.</span>`,
|
|
399
|
+
play: async () => {
|
|
400
|
+
type Probe = HTMLElement & {
|
|
401
|
+
value: number | string | null;
|
|
402
|
+
willUpdate: (changed: Map<string, unknown>) => void;
|
|
403
|
+
};
|
|
404
|
+
const el = document.createElement('obc-readout-block') as Probe;
|
|
405
|
+
const validateWithNoChanges = () => el.willUpdate(new Map());
|
|
406
|
+
|
|
407
|
+
el.value = 'Auto';
|
|
408
|
+
await expect(validateWithNoChanges).toThrow(/value must be a number/);
|
|
409
|
+
|
|
410
|
+
el.value = 12.4;
|
|
411
|
+
await expect(validateWithNoChanges).not.toThrow();
|
|
412
|
+
|
|
413
|
+
el.value = null;
|
|
414
|
+
await expect(validateWithNoChanges).not.toThrow();
|
|
415
|
+
},
|
|
416
|
+
};
|
|
417
|
+
|
|
309
418
|
export const DataQuality: Story = {
|
|
310
419
|
render: () =>
|
|
311
420
|
renderShowcase([
|
|
@@ -318,7 +427,7 @@ export const DataQuality: Story = {
|
|
|
318
427
|
title: 'invalid',
|
|
319
428
|
args: {value: 123, dataQuality: ReadoutBlockDataQuality.invalid},
|
|
320
429
|
},
|
|
321
|
-
{title: 'null (dash)', args: {value:
|
|
430
|
+
{title: 'null (dash)', args: {value: null}},
|
|
322
431
|
]),
|
|
323
432
|
};
|
|
324
433
|
|
|
@@ -14,6 +14,10 @@ import '../../icons/icon-notification-advice.js';
|
|
|
14
14
|
import {
|
|
15
15
|
formatNumericValue,
|
|
16
16
|
readoutFormattedInteger,
|
|
17
|
+
assertReadoutValueType,
|
|
18
|
+
resolveReadoutNumericValue,
|
|
19
|
+
resolveReadoutTextValue,
|
|
20
|
+
ReadoutValueType,
|
|
17
21
|
type ReadoutNumericFormatOptions,
|
|
18
22
|
} from '../../navigation-instruments/readout/readout-formatters.js';
|
|
19
23
|
import {
|
|
@@ -27,6 +31,7 @@ export {
|
|
|
27
31
|
ObcTextboxFontWeight,
|
|
28
32
|
ObcTextboxAlignment,
|
|
29
33
|
} from '../../components/textbox/textbox.js';
|
|
34
|
+
export {ReadoutValueType} from '../../navigation-instruments/readout/readout-formatters.js';
|
|
30
35
|
|
|
31
36
|
/**
|
|
32
37
|
* Semantic variant of the block. Drives the default marker icon and the
|
|
@@ -83,6 +88,9 @@ export enum ReadoutBlockHidePhase {
|
|
|
83
88
|
* role default), an optional trailing degree glyph, an `off`/unavailable text
|
|
84
89
|
* state, per-block data-quality and an optional per-block alert frame.
|
|
85
90
|
*
|
|
91
|
+
* Setting `valueType` to `text` renders `value` verbatim (e.g.
|
|
92
|
+
* `"Auto"`) instead of a formatted number.
|
|
93
|
+
*
|
|
86
94
|
* This is a building block used inside `obc-readout-list-item` (and, in a future
|
|
87
95
|
* refactor, inside `obc-readout`); it is not normally used on its own. Colour is
|
|
88
96
|
* inherited from the host context (the parent sets the role colour), so the
|
|
@@ -105,8 +113,19 @@ export class ObcReadoutBlock extends LitElement {
|
|
|
105
113
|
@property({type: String}) variant: ReadoutBlockVariant =
|
|
106
114
|
ReadoutBlockVariant.value;
|
|
107
115
|
|
|
108
|
-
/**
|
|
109
|
-
|
|
116
|
+
/**
|
|
117
|
+
* The value; `null`/`undefined` renders a dash. A number by default, or text
|
|
118
|
+
* when {@link valueType} is `text`.
|
|
119
|
+
*/
|
|
120
|
+
@property({type: String}) value: number | string | null = null;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* How {@link value} is interpreted. `number` (default) formats it via
|
|
124
|
+
* `fractionDigits`; `text` renders it verbatim and ignores the numeric
|
|
125
|
+
* format options. Passing text while this is `number` throws.
|
|
126
|
+
*/
|
|
127
|
+
@property({type: String}) valueType: ReadoutValueType =
|
|
128
|
+
ReadoutValueType.number;
|
|
110
129
|
|
|
111
130
|
/** Density tier — icon size, gap, degree tier. */
|
|
112
131
|
@property({type: String}) size: ReadoutBlockSize = ReadoutBlockSize.small;
|
|
@@ -131,13 +150,22 @@ export class ObcReadoutBlock extends LitElement {
|
|
|
131
150
|
/** Show the leading marker-icon container (always on for setpoint/advice). */
|
|
132
151
|
@property({type: Boolean}) hasIcon = false;
|
|
133
152
|
|
|
134
|
-
/**
|
|
153
|
+
/**
|
|
154
|
+
* Number of fraction digits.
|
|
155
|
+
* @availableWhen valueType==number
|
|
156
|
+
*/
|
|
135
157
|
@property({type: Number}) fractionDigits = 0;
|
|
136
158
|
|
|
137
|
-
/**
|
|
159
|
+
/**
|
|
160
|
+
* Integer digits to reserve / hint (independent of `fractionDigits`).
|
|
161
|
+
* @availableWhen valueType==number
|
|
162
|
+
*/
|
|
138
163
|
@property({type: Number}) maxDigits = 0;
|
|
139
164
|
|
|
140
|
-
/**
|
|
165
|
+
/**
|
|
166
|
+
* Render muted leading zeros filling the integer part to `maxDigits`.
|
|
167
|
+
* @availableWhen valueType==number
|
|
168
|
+
*/
|
|
141
169
|
@property({type: Boolean}) hintedZeros = false;
|
|
142
170
|
|
|
143
171
|
/** Explicit longest string to reserve width for (e.g. `"0000.0"`). */
|
|
@@ -280,19 +308,41 @@ export class ObcReadoutBlock extends LitElement {
|
|
|
280
308
|
`;
|
|
281
309
|
}
|
|
282
310
|
|
|
311
|
+
protected override willUpdate(changed: Map<string, unknown>): void {
|
|
312
|
+
super.willUpdate(changed);
|
|
313
|
+
// Validated on EVERY update, deliberately NOT gated on `value`/`valueType`
|
|
314
|
+
// appearing in `changed`. When this assertion throws, Lit's `performUpdate`
|
|
315
|
+
// catch calls `__markUpdated()`, which clears the changed-properties map. A
|
|
316
|
+
// later update driven by any OTHER property — inside `obc-readout-list`,
|
|
317
|
+
// `align()` writing the shared reservers — would then see no `value` in
|
|
318
|
+
// `changed`, skip the check, and render the invalid value as a plain dash:
|
|
319
|
+
// exactly the silent failure this assertion exists to prevent.
|
|
320
|
+
assertReadoutValueType('obc-readout-block', this.value, this.valueType);
|
|
321
|
+
}
|
|
322
|
+
|
|
283
323
|
override render() {
|
|
284
324
|
const valueSize = this.resolvedValueSize;
|
|
285
325
|
const formatOptions = this.numericFormatOptions;
|
|
286
|
-
const
|
|
326
|
+
const isTextMode = this.valueType === ReadoutValueType.text;
|
|
327
|
+
const valueForFormat = resolveReadoutNumericValue(
|
|
328
|
+
this.value,
|
|
329
|
+
this.valueType
|
|
330
|
+
);
|
|
331
|
+
const textValue = resolveReadoutTextValue(this.value, this.valueType);
|
|
332
|
+
// Text mode renders verbatim and ignores the numeric format options; a
|
|
333
|
+
// blank / missing text value still falls back to the unavailable dash.
|
|
287
334
|
const text = this.off
|
|
288
335
|
? this.offText
|
|
289
|
-
:
|
|
336
|
+
: isTextMode
|
|
337
|
+
? (textValue ?? '-')
|
|
338
|
+
: formatNumericValue(valueForFormat, formatOptions);
|
|
290
339
|
// Hinted zeros pad the INTEGER part up to `maxDigits`, independent of
|
|
291
340
|
// `fractionDigits` (the decimal point and fraction digits never count toward
|
|
292
341
|
// `maxDigits`). Negative / dashed values are not padded. Example: value 1.2,
|
|
293
342
|
// maxDigits 3, fractionDigits 1 → "001.2".
|
|
294
343
|
const hintCount =
|
|
295
344
|
this.off ||
|
|
345
|
+
isTextMode ||
|
|
296
346
|
!this.hintedZeros ||
|
|
297
347
|
valueForFormat === undefined ||
|
|
298
348
|
valueForFormat < 0
|
|
@@ -303,9 +353,13 @@ export class ObcReadoutBlock extends LitElement {
|
|
|
303
353
|
// `hintedZeros` is enabled an explicit `spaceReserver` is ignored (it has
|
|
304
354
|
// higher priority). Otherwise the wider of the explicit reserver and the
|
|
305
355
|
// `maxDigits`-derived reserve wins.
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
356
|
+
// Text mode ignores the `maxDigits`-derived numeric reserve — only an
|
|
357
|
+
// explicit `spaceReserver` still applies.
|
|
358
|
+
const reserver = isTextMode
|
|
359
|
+
? (this.spaceReserver ?? '')
|
|
360
|
+
: this.hintedZeros
|
|
361
|
+
? this.reserverText
|
|
362
|
+
: this.widerReserver(this.spaceReserver, this.reserverText);
|
|
309
363
|
|
|
310
364
|
const block = html`
|
|
311
365
|
<div
|
|
@@ -262,7 +262,7 @@ export class ObcCompass extends LitElement {
|
|
|
262
262
|
/** @availableWhen priority==enhanced */
|
|
263
263
|
@property({type: Array, attribute: false})
|
|
264
264
|
priorityElements: CompassPriorityElement[] = [CompassPriorityElement.hdg];
|
|
265
|
-
/** Show compass NSEW labels
|
|
265
|
+
/** Show compass NSEW labels. The north arrow is always shown, independent of this flag. */
|
|
266
266
|
@property({type: Boolean}) showLabels: boolean = false;
|
|
267
267
|
/** When true, labels and north arrow are placed inside the outer ring. */
|
|
268
268
|
@property({type: Boolean}) tickmarksInside: boolean = false;
|
|
@@ -424,7 +424,7 @@ export class ObcCompass extends LitElement {
|
|
|
424
424
|
.tickmarksInside=${this.tickmarksInside}
|
|
425
425
|
.crosshairEnabled=${true}
|
|
426
426
|
.crosshairCenterCutout=${this.hasCenterReadouts}
|
|
427
|
-
.northArrow=${
|
|
427
|
+
.northArrow=${true}
|
|
428
428
|
.angleSetpoint=${this.headingSetpoint ?? undefined}
|
|
429
429
|
.newAngleSetpoint=${this.newHeadingSetpoint}
|
|
430
430
|
.atAngleSetpoint=${this._headingSp.computeAtSetpoint(this.heading)}
|
|
@@ -38,6 +38,21 @@ const meta: Meta<typeof ObcCurrent> = {
|
|
|
38
38
|
control: 'select',
|
|
39
39
|
options: topVessels,
|
|
40
40
|
},
|
|
41
|
+
hasPattern: {control: 'boolean'},
|
|
42
|
+
waveLength: {
|
|
43
|
+
control: {type: 'range', min: 0.25, max: 3, step: 0.05},
|
|
44
|
+
description:
|
|
45
|
+
'Wavelength multiplier on the design band spacing (1 = design).',
|
|
46
|
+
},
|
|
47
|
+
waveHeight: {
|
|
48
|
+
control: {type: 'range', min: 0, max: 1, step: 0.05},
|
|
49
|
+
description: 'Peak opacity the pattern bands fade up to.',
|
|
50
|
+
},
|
|
51
|
+
waveSpeed: {
|
|
52
|
+
control: {type: 'range', min: -2, max: 2, step: 0.1},
|
|
53
|
+
description:
|
|
54
|
+
'Pattern drift in wavelengths per second, with the flow (0 = static).',
|
|
55
|
+
},
|
|
41
56
|
},
|
|
42
57
|
} satisfies Meta<ObcCurrent>;
|
|
43
58
|
|
|
@@ -59,3 +74,21 @@ export const VesselEnhanced: Story = {
|
|
|
59
74
|
export const DirectionEnhanced: Story = {
|
|
60
75
|
args: {type: CurrentType.direction, priority: Priority.enhanced},
|
|
61
76
|
};
|
|
77
|
+
|
|
78
|
+
export const WithoutPattern: Story = {
|
|
79
|
+
args: {hasPattern: false},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/** Denser, softer waves: half the design wavelength at reduced intensity. */
|
|
83
|
+
export const WaveTuned: Story = {
|
|
84
|
+
args: {waveLength: 0.5, waveHeight: 0.6},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The pattern drifts with the flow at `waveSpeed` wavelengths per second
|
|
89
|
+
* (excluded from visual tests because it animates continuously).
|
|
90
|
+
*/
|
|
91
|
+
export const AnimatedWaves: Story = {
|
|
92
|
+
tags: ['skip-test'],
|
|
93
|
+
args: {waveSpeed: 0.4},
|
|
94
|
+
};
|
|
@@ -18,8 +18,14 @@ export enum CurrentType {
|
|
|
18
18
|
|
|
19
19
|
const CURRENT_PATTERN_SCALE_VESSEL = 1.39;
|
|
20
20
|
const CURRENT_PATTERN_SCALE_DIRECTION = 2.0;
|
|
21
|
-
/**
|
|
22
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Chevron-tip anchor radius for the peripheral icon (vessel type): the tip
|
|
23
|
+
* sits on the track band's inner edge (112), so the 48-unit glyph box fills
|
|
24
|
+
* the band (112–160) exactly, per the OpenBridge 6.1 design.
|
|
25
|
+
*/
|
|
26
|
+
const CURRENT_ICON_PERIPHERY_RADIUS = innerRingRadiusFor(
|
|
27
|
+
WatchCircleType.double
|
|
28
|
+
);
|
|
23
29
|
/** Scale of the centered 24-unit chevron glyph (direction type), from Figma (168/24). */
|
|
24
30
|
const CURRENT_DIRECTION_ICON_SCALE = 7;
|
|
25
31
|
|
|
@@ -56,6 +62,9 @@ export function clampCurrentSpeed(value: number | null): number | null {
|
|
|
56
62
|
* the enhanced (blue) palette.
|
|
57
63
|
* - **Vessel image**: configurable silhouette rotating with
|
|
58
64
|
* `vesselHeadingDeg` (vessel type).
|
|
65
|
+
* - **Pattern toggle & wave tuning**: `hasPattern` switches the band pattern
|
|
66
|
+
* off; `waveLength`, `waveHeight` and `waveSpeed` tune the band spacing,
|
|
67
|
+
* intensity and drift motion.
|
|
59
68
|
*
|
|
60
69
|
* ## Usage Guidelines
|
|
61
70
|
*
|
|
@@ -89,6 +98,28 @@ export class ObcCurrent extends LitElement {
|
|
|
89
98
|
@property({type: Number}) currentFromDirection: number | null = null;
|
|
90
99
|
/** Color priority: `Priority.enhanced` uses the blue/enhanced palette (default: `Priority.regular`). */
|
|
91
100
|
@property({type: String}) priority: Priority = Priority.regular;
|
|
101
|
+
/**
|
|
102
|
+
* Show the force-graphics band pattern behind the watch face.
|
|
103
|
+
* @availableWhen currentFromDirection!=null
|
|
104
|
+
*/
|
|
105
|
+
@property({type: Boolean, attribute: false}) hasPattern = true;
|
|
106
|
+
/**
|
|
107
|
+
* Wavelength of the pattern bands as a multiplier on the design spacing
|
|
108
|
+
* (1 = design, 0.5 = twice as dense).
|
|
109
|
+
* @availableWhen hasPattern==true && currentFromDirection!=null
|
|
110
|
+
*/
|
|
111
|
+
@property({type: Number}) waveLength = 1;
|
|
112
|
+
/**
|
|
113
|
+
* Wave intensity: the peak opacity the pattern bands fade up to (0–1).
|
|
114
|
+
* @availableWhen hasPattern==true && currentFromDirection!=null
|
|
115
|
+
*/
|
|
116
|
+
@property({type: Number}) waveHeight = 1;
|
|
117
|
+
/**
|
|
118
|
+
* Drift speed of the pattern in wavelengths per second, moving with the
|
|
119
|
+
* flow (negative drifts against it); 0 keeps the pattern static.
|
|
120
|
+
* @availableWhen hasPattern==true && currentFromDirection!=null
|
|
121
|
+
*/
|
|
122
|
+
@property({type: Number}) waveSpeed = 0;
|
|
92
123
|
/**
|
|
93
124
|
* The image of the vessel.
|
|
94
125
|
* @availableWhen type==vessel
|
|
@@ -148,7 +179,7 @@ export class ObcCurrent extends LitElement {
|
|
|
148
179
|
}
|
|
149
180
|
|
|
150
181
|
private renderForcePattern(watchCircleType: WatchCircleType) {
|
|
151
|
-
if (this.currentFromDirection == null) {
|
|
182
|
+
if (!this.hasPattern || this.currentFromDirection == null) {
|
|
152
183
|
return nothing;
|
|
153
184
|
}
|
|
154
185
|
return html`
|
|
@@ -162,6 +193,9 @@ export class ObcCurrent extends LitElement {
|
|
|
162
193
|
color: this.enhanced
|
|
163
194
|
? 'var(--instrument-enhanced-tertiary-color)'
|
|
164
195
|
: undefined,
|
|
196
|
+
waveLength: this.waveLength,
|
|
197
|
+
waveHeight: this.waveHeight,
|
|
198
|
+
waveSpeed: this.waveSpeed,
|
|
165
199
|
})}
|
|
166
200
|
</svg>
|
|
167
201
|
`;
|
|
@@ -128,12 +128,14 @@ export const WithVectorArrows: Story = {
|
|
|
128
128
|
|
|
129
129
|
/**
|
|
130
130
|
* Below the label-reserve cap (~200px for the NSWE + north-arrow decor) the
|
|
131
|
-
* frame reports `labelsHidden` and the instrument drops the labels
|
|
132
|
-
*
|
|
133
|
-
*
|
|
131
|
+
* frame reports `labelsHidden` and the instrument drops the labels instead
|
|
132
|
+
* of letting them clip — the same graceful degradation the tick-label
|
|
133
|
+
* instruments use (issue #1021). The north arrow stays visible on even the
|
|
134
|
+
* smallest face: below the small-scale threshold it renders as a compact
|
|
135
|
+
* triangle at the ring that scales with the face, so it never clips.
|
|
134
136
|
*/
|
|
135
137
|
export const SmallContainerDegraded: Story = {
|
|
136
|
-
name: 'Small Container (160px, Labels
|
|
138
|
+
name: 'Small Container (160px, Labels Hidden Past Cap)',
|
|
137
139
|
args: {
|
|
138
140
|
width: 160,
|
|
139
141
|
},
|
|
@@ -309,7 +309,7 @@ export class ObcHeading extends LitElement {
|
|
|
309
309
|
.tickmarksInside=${this.tickmarksInside}
|
|
310
310
|
.crosshairEnabled=${true}
|
|
311
311
|
.crosshairCenterCutout=${this.hasCenterReadouts}
|
|
312
|
-
.northArrow=${
|
|
312
|
+
.northArrow=${true}
|
|
313
313
|
.angleSetpoint=${this.headingSetpoint ?? undefined}
|
|
314
314
|
.newAngleSetpoint=${this.newHeadingSetpoint}
|
|
315
315
|
.atAngleSetpoint=${this._headingSp.computeAtSetpoint(this.heading)}
|
|
@@ -351,9 +351,16 @@ export class ObcPitchRollHeave extends LitElement {
|
|
|
351
351
|
* `obc-watch` renders its crosshair after the bands, i.e. on top of them.
|
|
352
352
|
* Drawing it here puts it under the watch layer, so the bands and the heave
|
|
353
353
|
* column mask it naturally.
|
|
354
|
+
*
|
|
355
|
+
* In `single-scale` nothing frames the segment above the vessel (the other
|
|
356
|
+
* three arms end under the pitch band, roll band and heave column), so the
|
|
357
|
+
* vertical arm stops at the centre instead of continuing upward. In
|
|
358
|
+
* `dual-scale` the top roll band gives it a natural endpoint, so the full
|
|
359
|
+
* arm is kept.
|
|
354
360
|
*/
|
|
355
361
|
private renderCrosshair(): SVGTemplateResult {
|
|
356
362
|
const r = OUTER_RING_RADIUS;
|
|
363
|
+
const yTop = this.isDualScale ? -r : 0;
|
|
357
364
|
return svg`
|
|
358
365
|
<line
|
|
359
366
|
x1=${-r} y1="0" x2=${r} y2="0"
|
|
@@ -361,7 +368,7 @@ export class ObcPitchRollHeave extends LitElement {
|
|
|
361
368
|
vector-effect="non-scaling-stroke"
|
|
362
369
|
/>
|
|
363
370
|
<line
|
|
364
|
-
x1="0" y1=${
|
|
371
|
+
x1="0" y1=${yTop} x2="0" y2=${r}
|
|
365
372
|
stroke="var(--instrument-frame-tertiary-color)"
|
|
366
373
|
vector-effect="non-scaling-stroke"
|
|
367
374
|
/>
|
|
@@ -131,7 +131,7 @@ export const WithTrackBar: Story = {
|
|
|
131
131
|
hasReadout: true,
|
|
132
132
|
rateOfTurnDegreesPerMinute: 45,
|
|
133
133
|
rotPosition: RotPosition.innerCircle,
|
|
134
|
-
watchCircleType: WatchCircleType.
|
|
134
|
+
watchCircleType: WatchCircleType.triple,
|
|
135
135
|
},
|
|
136
136
|
};
|
|
137
137
|
|
|
@@ -142,7 +142,7 @@ export const WithTrackBarEnhanced: Story = {
|
|
|
142
142
|
hasReadout: true,
|
|
143
143
|
rateOfTurnDegreesPerMinute: 45,
|
|
144
144
|
rotPosition: RotPosition.innerCircle,
|
|
145
|
-
watchCircleType: WatchCircleType.
|
|
145
|
+
watchCircleType: WatchCircleType.triple,
|
|
146
146
|
priority: Priority.enhanced,
|
|
147
147
|
},
|
|
148
148
|
};
|
|
@@ -154,7 +154,7 @@ export const WithTrackBarPortStarboard: Story = {
|
|
|
154
154
|
hasReadout: true,
|
|
155
155
|
rateOfTurnDegreesPerMinute: 45,
|
|
156
156
|
rotPosition: RotPosition.innerCircle,
|
|
157
|
-
watchCircleType: WatchCircleType.
|
|
157
|
+
watchCircleType: WatchCircleType.triple,
|
|
158
158
|
rotPortStarboard: true,
|
|
159
159
|
},
|
|
160
160
|
};
|
|
@@ -35,7 +35,8 @@ export {RotType, RotPosition};
|
|
|
35
35
|
* - **Track bar** (`hasTrackBar`): A bar in the ring band growing from the
|
|
36
36
|
* twelve o'clock position to the measured rate of turn, with a needle
|
|
37
37
|
* marker at its end and sector tickmarks (pairs with
|
|
38
|
-
* `watchCircleType="double"` for the banded face
|
|
38
|
+
* `watchCircleType="double"` for the banded face, or `"triple"` when the
|
|
39
|
+
* ROT dots sit on the inner circle).
|
|
39
40
|
* - **Center readout** (`hasReadout`): A centered readout showing the
|
|
40
41
|
* measured rate of turn (label `ROT`, unit `DEG/min` by default).
|
|
41
42
|
* - **Color priority**: Uses `priority` to select regular or enhanced color
|
|
@@ -91,7 +92,9 @@ export class ObcRateOfTurn extends LitElement {
|
|
|
91
92
|
* When `true`, shows a bar in the ring band from the twelve o'clock
|
|
92
93
|
* position to the measured rate of turn, with a needle marker at its end
|
|
93
94
|
* and sector tickmarks. Pairs with `watchCircleType="double"` for the
|
|
94
|
-
* banded face
|
|
95
|
+
* banded face — or `"triple"` when `rotPosition` is `innerCircle`, so the
|
|
96
|
+
* dots get their inner track too. Driven by `rateOfTurnDegreesPerMinute`
|
|
97
|
+
* only.
|
|
95
98
|
*/
|
|
96
99
|
@property({type: Boolean}) hasTrackBar: boolean = false;
|
|
97
100
|
/**
|