@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
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import {describe, it, expect} from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
assertReadoutValueType,
|
|
4
|
+
resolveReadoutNumericValue,
|
|
5
|
+
resolveReadoutTextValue,
|
|
6
|
+
isReadoutValueType,
|
|
7
|
+
ReadoutValueType,
|
|
8
|
+
} from './readout-formatters.js';
|
|
9
|
+
|
|
10
|
+
describe('assertReadoutValueType', () => {
|
|
11
|
+
it('accepts a number when valueType is number', () => {
|
|
12
|
+
expect(() =>
|
|
13
|
+
assertReadoutValueType('obc-readout', 12.4, ReadoutValueType.number)
|
|
14
|
+
).not.toThrow();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('accepts null when valueType is number', () => {
|
|
18
|
+
expect(() =>
|
|
19
|
+
assertReadoutValueType('obc-readout', null, ReadoutValueType.number)
|
|
20
|
+
).not.toThrow();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// HTML attributes are always strings, so `<obc-readout value="12.4">` must
|
|
24
|
+
// keep working under the default valueType.
|
|
25
|
+
it('accepts a numeric-looking string when valueType is number', () => {
|
|
26
|
+
expect(() =>
|
|
27
|
+
assertReadoutValueType('obc-readout', '12.4', ReadoutValueType.number)
|
|
28
|
+
).not.toThrow();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('accepts a negative numeric string when valueType is number', () => {
|
|
32
|
+
expect(() =>
|
|
33
|
+
assertReadoutValueType('obc-readout', '-12.4', ReadoutValueType.number)
|
|
34
|
+
).not.toThrow();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// `value="${maybeUndefined}"` is a common template shape, and Number('') is
|
|
38
|
+
// 0 — a silently wrong reading, which is worse than a dash.
|
|
39
|
+
it('accepts a blank string when valueType is number', () => {
|
|
40
|
+
expect(() =>
|
|
41
|
+
assertReadoutValueType('obc-readout', '', ReadoutValueType.number)
|
|
42
|
+
).not.toThrow();
|
|
43
|
+
expect(() =>
|
|
44
|
+
assertReadoutValueType('obc-readout', ' ', ReadoutValueType.number)
|
|
45
|
+
).not.toThrow();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('throws for text when valueType is number', () => {
|
|
49
|
+
expect(() =>
|
|
50
|
+
assertReadoutValueType('obc-readout', 'Auto', ReadoutValueType.number)
|
|
51
|
+
).toThrow(TypeError);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('names the offending tag and value in the message', () => {
|
|
55
|
+
expect(() =>
|
|
56
|
+
assertReadoutValueType(
|
|
57
|
+
'obc-readout-list-item',
|
|
58
|
+
'Thermo On',
|
|
59
|
+
ReadoutValueType.number
|
|
60
|
+
)
|
|
61
|
+
).toThrow(/obc-readout-list-item.*Thermo On.*valueType="text"/s);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('throws for a partially numeric string when valueType is number', () => {
|
|
65
|
+
expect(() =>
|
|
66
|
+
assertReadoutValueType('obc-readout', '12.4 kn', ReadoutValueType.number)
|
|
67
|
+
).toThrow(TypeError);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('never throws when valueType is text', () => {
|
|
71
|
+
expect(() =>
|
|
72
|
+
assertReadoutValueType('obc-readout', 'Auto', ReadoutValueType.text)
|
|
73
|
+
).not.toThrow();
|
|
74
|
+
expect(() =>
|
|
75
|
+
assertReadoutValueType('obc-readout', 12.4, ReadoutValueType.text)
|
|
76
|
+
).not.toThrow();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// An attribute carries an unchecked string, so a typo matches neither mode.
|
|
80
|
+
// Without this it falls through every check and silently renders a dash.
|
|
81
|
+
it('throws for an unrecognised valueType', () => {
|
|
82
|
+
expect(() =>
|
|
83
|
+
assertReadoutValueType(
|
|
84
|
+
'obc-readout',
|
|
85
|
+
'Auto',
|
|
86
|
+
'strng' as unknown as ReadoutValueType
|
|
87
|
+
)
|
|
88
|
+
).toThrow(/valueType must be "number" or "text".*"strng"/s);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('throws for an unrecognised valueType even with a numeric value', () => {
|
|
92
|
+
expect(() =>
|
|
93
|
+
assertReadoutValueType(
|
|
94
|
+
'obc-readout',
|
|
95
|
+
12.4,
|
|
96
|
+
'STRING' as unknown as ReadoutValueType
|
|
97
|
+
)
|
|
98
|
+
).toThrow(TypeError);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// undefined/null mean "use the default", so they must still enforce the
|
|
102
|
+
// number contract rather than skipping validation entirely.
|
|
103
|
+
it('treats an unset valueType as number', () => {
|
|
104
|
+
expect(() =>
|
|
105
|
+
assertReadoutValueType(
|
|
106
|
+
'obc-readout',
|
|
107
|
+
'Auto',
|
|
108
|
+
undefined as unknown as ReadoutValueType
|
|
109
|
+
)
|
|
110
|
+
).toThrow(TypeError);
|
|
111
|
+
expect(() =>
|
|
112
|
+
assertReadoutValueType(
|
|
113
|
+
'obc-readout',
|
|
114
|
+
12.4,
|
|
115
|
+
undefined as unknown as ReadoutValueType
|
|
116
|
+
)
|
|
117
|
+
).not.toThrow();
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe('isReadoutValueType', () => {
|
|
122
|
+
it('accepts the supported values', () => {
|
|
123
|
+
expect(isReadoutValueType('number')).toBe(true);
|
|
124
|
+
expect(isReadoutValueType('text')).toBe(true);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('rejects anything else', () => {
|
|
128
|
+
// `string` names the JS type rather than the mode, and is the likely
|
|
129
|
+
// mistake for anyone reaching for the TypeScript word.
|
|
130
|
+
expect(isReadoutValueType('string')).toBe(false);
|
|
131
|
+
expect(isReadoutValueType('strng')).toBe(false);
|
|
132
|
+
expect(isReadoutValueType('Number')).toBe(false);
|
|
133
|
+
expect(isReadoutValueType('')).toBe(false);
|
|
134
|
+
expect(isReadoutValueType(undefined)).toBe(false);
|
|
135
|
+
expect(isReadoutValueType(null)).toBe(false);
|
|
136
|
+
expect(isReadoutValueType(0)).toBe(false);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe('resolveReadoutNumericValue', () => {
|
|
141
|
+
it('passes a number through unchanged', () => {
|
|
142
|
+
expect(resolveReadoutNumericValue(12.4, ReadoutValueType.number)).toBe(
|
|
143
|
+
12.4
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('parses a numeric string', () => {
|
|
148
|
+
expect(resolveReadoutNumericValue('12.4', ReadoutValueType.number)).toBe(
|
|
149
|
+
12.4
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('is undefined for null, undefined and blank strings', () => {
|
|
154
|
+
expect(
|
|
155
|
+
resolveReadoutNumericValue(null, ReadoutValueType.number)
|
|
156
|
+
).toBeUndefined();
|
|
157
|
+
expect(
|
|
158
|
+
resolveReadoutNumericValue(undefined, ReadoutValueType.number)
|
|
159
|
+
).toBeUndefined();
|
|
160
|
+
expect(
|
|
161
|
+
resolveReadoutNumericValue('', ReadoutValueType.number)
|
|
162
|
+
).toBeUndefined();
|
|
163
|
+
expect(
|
|
164
|
+
resolveReadoutNumericValue(' ', ReadoutValueType.number)
|
|
165
|
+
).toBeUndefined();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('is undefined for non-numeric text', () => {
|
|
169
|
+
expect(
|
|
170
|
+
resolveReadoutNumericValue('Auto', ReadoutValueType.number)
|
|
171
|
+
).toBeUndefined();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Text rows must contribute nothing to obc-readout-list's shared reserver.
|
|
175
|
+
it('is undefined in text mode even for a numeric value', () => {
|
|
176
|
+
expect(
|
|
177
|
+
resolveReadoutNumericValue(12.4, ReadoutValueType.text)
|
|
178
|
+
).toBeUndefined();
|
|
179
|
+
expect(
|
|
180
|
+
resolveReadoutNumericValue('12.4', ReadoutValueType.text)
|
|
181
|
+
).toBeUndefined();
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Pre-existing behaviour, deliberately preserved: NaN reaches the formatter.
|
|
185
|
+
it('passes NaN through in number mode', () => {
|
|
186
|
+
expect(
|
|
187
|
+
resolveReadoutNumericValue(Number.NaN, ReadoutValueType.number)
|
|
188
|
+
).toBeNaN();
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
describe('resolveReadoutTextValue', () => {
|
|
193
|
+
it('returns the text verbatim in text mode', () => {
|
|
194
|
+
expect(resolveReadoutTextValue('Thermo On', ReadoutValueType.text)).toBe(
|
|
195
|
+
'Thermo On'
|
|
196
|
+
);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// The reason a converter was rejected: it would have parsed this to 1.5.
|
|
200
|
+
it('preserves trailing zeros in text mode', () => {
|
|
201
|
+
expect(resolveReadoutTextValue('1.50', ReadoutValueType.text)).toBe('1.50');
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('coerces a number to text in text mode', () => {
|
|
205
|
+
expect(resolveReadoutTextValue(12.4, ReadoutValueType.text)).toBe('12.4');
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('is undefined for null, undefined and blank strings', () => {
|
|
209
|
+
expect(
|
|
210
|
+
resolveReadoutTextValue(null, ReadoutValueType.text)
|
|
211
|
+
).toBeUndefined();
|
|
212
|
+
expect(
|
|
213
|
+
resolveReadoutTextValue(undefined, ReadoutValueType.text)
|
|
214
|
+
).toBeUndefined();
|
|
215
|
+
expect(resolveReadoutTextValue('', ReadoutValueType.text)).toBeUndefined();
|
|
216
|
+
expect(
|
|
217
|
+
resolveReadoutTextValue(' ', ReadoutValueType.text)
|
|
218
|
+
).toBeUndefined();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('is undefined in number mode', () => {
|
|
222
|
+
expect(
|
|
223
|
+
resolveReadoutTextValue('12.4', ReadoutValueType.number)
|
|
224
|
+
).toBeUndefined();
|
|
225
|
+
expect(
|
|
226
|
+
resolveReadoutTextValue(12.4, ReadoutValueType.number)
|
|
227
|
+
).toBeUndefined();
|
|
228
|
+
});
|
|
229
|
+
});
|
|
@@ -4,6 +4,111 @@ export type ReadoutNumericFormatOptions = {
|
|
|
4
4
|
fractionDigits: number;
|
|
5
5
|
};
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* How a readout's `value` is interpreted.
|
|
9
|
+
* - `number`: formatted via `fractionDigits` / `maxDigits`.
|
|
10
|
+
* - `text`: rendered verbatim, with the numeric format options ignored.
|
|
11
|
+
*/
|
|
12
|
+
export enum ReadoutValueType {
|
|
13
|
+
number = 'number',
|
|
14
|
+
text = 'text',
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const READOUT_VALUE_TYPES: readonly string[] = Object.values(ReadoutValueType);
|
|
18
|
+
|
|
19
|
+
/** Whether `value` is one of the supported {@link ReadoutValueType} values. */
|
|
20
|
+
export function isReadoutValueType(value: unknown): value is ReadoutValueType {
|
|
21
|
+
return typeof value === 'string' && READOUT_VALUE_TYPES.includes(value);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function isBlank(value: string): boolean {
|
|
25
|
+
return value.trim() === '';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Throws when `valueType` is not a supported value, or when `value` is text but
|
|
30
|
+
* `valueType` is `number`.
|
|
31
|
+
*
|
|
32
|
+
* `valueType` is validated first because an attribute carries an unchecked
|
|
33
|
+
* string: a typo such as `valuetype="strng"` matches neither mode, so every
|
|
34
|
+
* mode check falls through and the readout silently renders the unavailable
|
|
35
|
+
* dash — the opposite of the loud failure this contract exists to give.
|
|
36
|
+
* `undefined`/`null` are allowed and mean "use the default".
|
|
37
|
+
*
|
|
38
|
+
* Attributes are always strings, so a numeric-looking string (`value="10.12"`)
|
|
39
|
+
* is accepted and parsed. Blank strings resolve to the unavailable dash rather
|
|
40
|
+
* than throwing — `value="${maybeUndefined}"` is a common template shape, and
|
|
41
|
+
* `Number('')` is `0`, a silently wrong reading.
|
|
42
|
+
*/
|
|
43
|
+
export function assertReadoutValueType(
|
|
44
|
+
tagName: string,
|
|
45
|
+
value: number | string | null | undefined,
|
|
46
|
+
valueType: ReadoutValueType
|
|
47
|
+
): void {
|
|
48
|
+
// `undefined`/`null` mean "use the default", matching how the components and
|
|
49
|
+
// the resolvers treat an unset `valueType`.
|
|
50
|
+
const resolved = valueType ?? ReadoutValueType.number;
|
|
51
|
+
if (!isReadoutValueType(resolved)) {
|
|
52
|
+
throw new TypeError(
|
|
53
|
+
`<${tagName}>: valueType must be ` +
|
|
54
|
+
`${READOUT_VALUE_TYPES.map((t) => `"${t}"`).join(' or ')} ` +
|
|
55
|
+
`(got ${JSON.stringify(valueType)}).`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
if (
|
|
59
|
+
resolved !== ReadoutValueType.number ||
|
|
60
|
+
typeof value !== 'string' ||
|
|
61
|
+
isBlank(value)
|
|
62
|
+
) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (Number.isFinite(Number(value))) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
throw new TypeError(
|
|
69
|
+
`<${tagName}>: value must be a number when valueType is "number" ` +
|
|
70
|
+
`(got ${JSON.stringify(value)}). Set valueType="text" to render text.`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** The value as a number, or `undefined` when it is text / unavailable. */
|
|
75
|
+
export function resolveReadoutNumericValue(
|
|
76
|
+
value: number | string | null | undefined,
|
|
77
|
+
valueType: ReadoutValueType
|
|
78
|
+
): number | undefined {
|
|
79
|
+
if (
|
|
80
|
+
valueType === ReadoutValueType.text ||
|
|
81
|
+
value === null ||
|
|
82
|
+
value === undefined
|
|
83
|
+
) {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
if (typeof value === 'number') {
|
|
87
|
+
return value;
|
|
88
|
+
}
|
|
89
|
+
if (isBlank(value)) {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
const parsed = Number(value);
|
|
93
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** The value as display text, or `undefined` when not in text mode / blank. */
|
|
97
|
+
export function resolveReadoutTextValue(
|
|
98
|
+
value: number | string | null | undefined,
|
|
99
|
+
valueType: ReadoutValueType
|
|
100
|
+
): string | undefined {
|
|
101
|
+
if (
|
|
102
|
+
valueType !== ReadoutValueType.text ||
|
|
103
|
+
value === null ||
|
|
104
|
+
value === undefined
|
|
105
|
+
) {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
const text = typeof value === 'number' ? String(value) : value;
|
|
109
|
+
return isBlank(text) ? undefined : text;
|
|
110
|
+
}
|
|
111
|
+
|
|
7
112
|
function dashedGenerator({
|
|
8
113
|
showZeroPadding,
|
|
9
114
|
minValueLength,
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
type ReadoutAdviceOptions,
|
|
21
21
|
type ReadoutReserverOptions,
|
|
22
22
|
type ReadoutSourceOptions,
|
|
23
|
+
ReadoutValueType,
|
|
23
24
|
} from './readout.js';
|
|
24
25
|
import {
|
|
25
26
|
type AlertFrameConfig,
|
|
@@ -36,7 +37,8 @@ type ReadoutStoryArgs = {
|
|
|
36
37
|
unit: string;
|
|
37
38
|
src: string;
|
|
38
39
|
hasValue: boolean;
|
|
39
|
-
value: number;
|
|
40
|
+
value: number | string;
|
|
41
|
+
valueType: ReadoutValueType;
|
|
40
42
|
off: boolean;
|
|
41
43
|
hasSetpoint: boolean;
|
|
42
44
|
setpoint: number;
|
|
@@ -90,7 +92,8 @@ type ReadoutConfig = {
|
|
|
90
92
|
unit?: string;
|
|
91
93
|
src?: string;
|
|
92
94
|
hasValue?: boolean;
|
|
93
|
-
value?: number | null;
|
|
95
|
+
value?: number | string | null;
|
|
96
|
+
valueType?: ReadoutValueType;
|
|
94
97
|
off?: boolean;
|
|
95
98
|
hasSetpoint?: boolean;
|
|
96
99
|
setpoint?: number;
|
|
@@ -159,6 +162,7 @@ function renderReadout(config: ReadoutConfig) {
|
|
|
159
162
|
.src=${config.src}
|
|
160
163
|
.hasValue=${config.hasValue ?? true}
|
|
161
164
|
.value=${config.value ?? null}
|
|
165
|
+
.valueType=${config.valueType ?? ReadoutValueType.number}
|
|
162
166
|
.off=${config.off ?? false}
|
|
163
167
|
.hasSetpoint=${config.hasSetpoint ?? false}
|
|
164
168
|
.setpoint=${config.setpoint}
|
|
@@ -241,6 +245,7 @@ const defaultArgs: ReadoutStoryArgs = {
|
|
|
241
245
|
src: '',
|
|
242
246
|
hasValue: true,
|
|
243
247
|
value: 123,
|
|
248
|
+
valueType: ReadoutValueType.number,
|
|
244
249
|
off: false,
|
|
245
250
|
hasSetpoint: false,
|
|
246
251
|
setpoint: 120,
|
|
@@ -306,6 +311,7 @@ const meta = {
|
|
|
306
311
|
src: args.src,
|
|
307
312
|
hasValue: args.hasValue,
|
|
308
313
|
value: args.value,
|
|
314
|
+
valueType: args.valueType,
|
|
309
315
|
off: args.off,
|
|
310
316
|
hasSetpoint: args.hasSetpoint,
|
|
311
317
|
setpoint: args.setpoint,
|
|
@@ -322,7 +328,16 @@ const meta = {
|
|
|
322
328
|
hasValue: {name: 'Has Value', table: {category: 'Data'}},
|
|
323
329
|
value: {
|
|
324
330
|
name: 'Value',
|
|
325
|
-
control
|
|
331
|
+
// Text control (not number) so both value types are exercisable. Under
|
|
332
|
+
// valueType=number a numeric string resolves back to a number; entering
|
|
333
|
+
// non-numeric text there throws, which is the intended contract.
|
|
334
|
+
control: {type: 'text'},
|
|
335
|
+
table: {category: 'Data'},
|
|
336
|
+
},
|
|
337
|
+
valueType: {
|
|
338
|
+
name: 'Value Type',
|
|
339
|
+
control: {type: 'inline-radio'},
|
|
340
|
+
options: Object.values(ReadoutValueType),
|
|
326
341
|
table: {category: 'Data'},
|
|
327
342
|
},
|
|
328
343
|
off: {name: 'Off', table: {category: 'Data'}},
|
|
@@ -1153,6 +1168,141 @@ export const DebugOverlay: StoryObj<
|
|
|
1153
1168
|
`,
|
|
1154
1169
|
};
|
|
1155
1170
|
|
|
1171
|
+
/**
|
|
1172
|
+
* **Text values** — `valueType="text"` renders `value` verbatim instead of
|
|
1173
|
+
* formatting it as a number, for readings that are states rather than
|
|
1174
|
+
* quantities ("Auto", "Thermo On", "Standby").
|
|
1175
|
+
*
|
|
1176
|
+
* The numeric format options (`fractionDigits`, `maxDigits`, `hintedZeros`) are
|
|
1177
|
+
* ignored in this mode; an explicit `spaceReserver` still applies. Passing text
|
|
1178
|
+
* while `valueType` is `number` throws a `TypeError` rather than rendering
|
|
1179
|
+
* `NaN` — a numeric-looking string such as `"12.4"` is still accepted and
|
|
1180
|
+
* parsed, so plain-HTML `value="12.4"` keeps working.
|
|
1181
|
+
*/
|
|
1182
|
+
export const TextValue: Story = {
|
|
1183
|
+
render: () =>
|
|
1184
|
+
renderShowcase([
|
|
1185
|
+
{
|
|
1186
|
+
title: 'Text Value — Vertical',
|
|
1187
|
+
columns: 3,
|
|
1188
|
+
cases: [
|
|
1189
|
+
{
|
|
1190
|
+
label: 'text',
|
|
1191
|
+
config: {
|
|
1192
|
+
label: 'Mode',
|
|
1193
|
+
unit: '',
|
|
1194
|
+
value: 'Auto',
|
|
1195
|
+
valueType: ReadoutValueType.text,
|
|
1196
|
+
},
|
|
1197
|
+
},
|
|
1198
|
+
{
|
|
1199
|
+
label: 'text + source',
|
|
1200
|
+
config: {
|
|
1201
|
+
label: 'Thruster',
|
|
1202
|
+
unit: '',
|
|
1203
|
+
value: 'Standby',
|
|
1204
|
+
valueType: ReadoutValueType.text,
|
|
1205
|
+
src: 'CTRL',
|
|
1206
|
+
},
|
|
1207
|
+
},
|
|
1208
|
+
{
|
|
1209
|
+
label: 'text + stacked meta',
|
|
1210
|
+
config: {
|
|
1211
|
+
label: 'Status',
|
|
1212
|
+
unit: '',
|
|
1213
|
+
value: 'Normal',
|
|
1214
|
+
valueType: ReadoutValueType.text,
|
|
1215
|
+
options: {stacking: ReadoutStacking.stacked},
|
|
1216
|
+
},
|
|
1217
|
+
},
|
|
1218
|
+
],
|
|
1219
|
+
},
|
|
1220
|
+
{
|
|
1221
|
+
title: 'Text Value — Horizontal',
|
|
1222
|
+
columns: 3,
|
|
1223
|
+
cases: [
|
|
1224
|
+
{
|
|
1225
|
+
label: 'text',
|
|
1226
|
+
config: {
|
|
1227
|
+
label: 'Mode',
|
|
1228
|
+
unit: '',
|
|
1229
|
+
value: 'Auto',
|
|
1230
|
+
valueType: ReadoutValueType.text,
|
|
1231
|
+
options: {direction: ReadoutDirection.horizontal},
|
|
1232
|
+
},
|
|
1233
|
+
},
|
|
1234
|
+
{
|
|
1235
|
+
label: 'text + source',
|
|
1236
|
+
config: {
|
|
1237
|
+
label: 'Thruster',
|
|
1238
|
+
unit: '',
|
|
1239
|
+
value: 'Standby',
|
|
1240
|
+
valueType: ReadoutValueType.text,
|
|
1241
|
+
src: 'CTRL',
|
|
1242
|
+
options: {direction: ReadoutDirection.horizontal},
|
|
1243
|
+
},
|
|
1244
|
+
},
|
|
1245
|
+
{
|
|
1246
|
+
label: 'numeric (contrast)',
|
|
1247
|
+
config: {
|
|
1248
|
+
label: 'SOG',
|
|
1249
|
+
unit: 'kn',
|
|
1250
|
+
value: 12.4,
|
|
1251
|
+
options: {
|
|
1252
|
+
direction: ReadoutDirection.horizontal,
|
|
1253
|
+
fractionDigits: 1,
|
|
1254
|
+
},
|
|
1255
|
+
},
|
|
1256
|
+
},
|
|
1257
|
+
],
|
|
1258
|
+
},
|
|
1259
|
+
{
|
|
1260
|
+
title: 'Value Resolution — Sizes and Edge Cases',
|
|
1261
|
+
columns: 4,
|
|
1262
|
+
cases: [
|
|
1263
|
+
{
|
|
1264
|
+
label: 'verbatim "1.50" (trailing zero kept)',
|
|
1265
|
+
config: {
|
|
1266
|
+
label: 'Bearing',
|
|
1267
|
+
unit: '',
|
|
1268
|
+
value: '1.50',
|
|
1269
|
+
valueType: ReadoutValueType.text,
|
|
1270
|
+
options: {fractionDigits: 1},
|
|
1271
|
+
},
|
|
1272
|
+
},
|
|
1273
|
+
{
|
|
1274
|
+
label: 'null in text mode (dash)',
|
|
1275
|
+
config: {
|
|
1276
|
+
label: 'Mode',
|
|
1277
|
+
unit: '',
|
|
1278
|
+
value: null,
|
|
1279
|
+
valueType: ReadoutValueType.text,
|
|
1280
|
+
},
|
|
1281
|
+
},
|
|
1282
|
+
{
|
|
1283
|
+
label: 'numeric string in number mode → 12.4',
|
|
1284
|
+
config: {
|
|
1285
|
+
label: 'SOG',
|
|
1286
|
+
unit: 'kn',
|
|
1287
|
+
value: '12.4',
|
|
1288
|
+
options: {fractionDigits: 1},
|
|
1289
|
+
},
|
|
1290
|
+
},
|
|
1291
|
+
{
|
|
1292
|
+
label: 'text + degree (hasDegree honoured)',
|
|
1293
|
+
config: {
|
|
1294
|
+
label: 'Mode',
|
|
1295
|
+
unit: '',
|
|
1296
|
+
value: 'Auto',
|
|
1297
|
+
valueType: ReadoutValueType.text,
|
|
1298
|
+
options: {hasDegree: true},
|
|
1299
|
+
},
|
|
1300
|
+
},
|
|
1301
|
+
],
|
|
1302
|
+
},
|
|
1303
|
+
]),
|
|
1304
|
+
};
|
|
1305
|
+
|
|
1156
1306
|
export const TestCases: Story = {
|
|
1157
1307
|
render: () => {
|
|
1158
1308
|
return html` <style>
|
|
@@ -1174,6 +1324,20 @@ export const TestCases: Story = {
|
|
|
1174
1324
|
.value=${123}
|
|
1175
1325
|
.unit=${'kn'}
|
|
1176
1326
|
></obc-readout>
|
|
1327
|
+
<obc-readout
|
|
1328
|
+
.size=${ReadoutSize.small}
|
|
1329
|
+
.direction=${ReadoutDirection.horizontal}
|
|
1330
|
+
.value=${'Thermo On'}
|
|
1331
|
+
.valueType=${ReadoutValueType.text}
|
|
1332
|
+
.label=${'Operating mode'}
|
|
1333
|
+
></obc-readout>
|
|
1334
|
+
<obc-readout
|
|
1335
|
+
.size=${ReadoutSize.small}
|
|
1336
|
+
.direction=${ReadoutDirection.horizontal}
|
|
1337
|
+
.value=${'Normal'}
|
|
1338
|
+
.valueType=${ReadoutValueType.text}
|
|
1339
|
+
.label=${'Status'}
|
|
1340
|
+
></obc-readout>
|
|
1177
1341
|
</div>`;
|
|
1178
1342
|
},
|
|
1179
1343
|
};
|
|
@@ -25,7 +25,12 @@ import {
|
|
|
25
25
|
type ReadoutSrcOptions,
|
|
26
26
|
} from '../readout-list-item/readout-list-item.js';
|
|
27
27
|
import {Priority} from '../types.js';
|
|
28
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
assertReadoutValueType,
|
|
30
|
+
resolveReadoutNumericValue,
|
|
31
|
+
ReadoutValueType,
|
|
32
|
+
type ReadoutNumericFormatOptions,
|
|
33
|
+
} from './readout-formatters.js';
|
|
29
34
|
import {
|
|
30
35
|
isDisplayedAtSetpoint,
|
|
31
36
|
readoutNumericFormatOptions,
|
|
@@ -58,6 +63,7 @@ import '../../icons/icon-drop-down-google.js';
|
|
|
58
63
|
// purpose: the two components are layout variants of the same primitives +
|
|
59
64
|
// per-block options API and may merge in a future release.
|
|
60
65
|
export {ObcTextboxFontWeight} from '../../components/textbox/textbox.js';
|
|
66
|
+
export {ReadoutValueType} from './readout-formatters.js';
|
|
61
67
|
export type {
|
|
62
68
|
ReadoutBlockState,
|
|
63
69
|
ReadoutValueOptions,
|
|
@@ -202,6 +208,8 @@ export interface ReadoutSourceOptions extends ReadoutSrcOptions {
|
|
|
202
208
|
* `flyout` interactivity via `srcOptions.interaction`.
|
|
203
209
|
* - **Formatting:** shared `fractionDigits`, width reservation via
|
|
204
210
|
* `maxDigits` and per-segment `hintedZeros`; a `null` value renders a dash.
|
|
211
|
+
* - **Text values:** `valueType="text"` renders `value` verbatim (e.g.
|
|
212
|
+
* `"Auto"`) instead of formatting it as a number.
|
|
205
213
|
*
|
|
206
214
|
* ### Usage Guidelines
|
|
207
215
|
* Use for stand-alone instrument readouts and readouts embedded in
|
|
@@ -242,7 +250,18 @@ export class ObcReadout extends LitElement {
|
|
|
242
250
|
* value block at full size, so the layout does not shift when data arrives.
|
|
243
251
|
*/
|
|
244
252
|
@property({type: Boolean, attribute: false}) hasValue = true;
|
|
245
|
-
|
|
253
|
+
/**
|
|
254
|
+
* The value; `null` renders a dash. A number by default, or text when
|
|
255
|
+
* {@link valueType} is `text`.
|
|
256
|
+
*/
|
|
257
|
+
@property({type: String}) value: number | string | null = null;
|
|
258
|
+
/**
|
|
259
|
+
* How {@link value} is interpreted. `number` (default) formats it via
|
|
260
|
+
* `fractionDigits`; `text` renders it verbatim and ignores the numeric
|
|
261
|
+
* format options. Passing text while this is `number` throws.
|
|
262
|
+
*/
|
|
263
|
+
@property({type: String}) valueType: ReadoutValueType =
|
|
264
|
+
ReadoutValueType.number;
|
|
246
265
|
/** Render the value as `offText` (e.g. equipment powered down). Affects the value only. */
|
|
247
266
|
@property({type: Boolean}) off = false;
|
|
248
267
|
/** Text shown in place of the value when `off` is true. @availableWhen off==true */
|
|
@@ -267,7 +286,17 @@ export class ObcReadout extends LitElement {
|
|
|
267
286
|
@property({type: Boolean}) hasDegree = false;
|
|
268
287
|
/** @availableWhen hasDegree==false */
|
|
269
288
|
@property({type: Boolean}) hasDegreeSpacer = false;
|
|
289
|
+
/**
|
|
290
|
+
* Also formats the numeric setpoint / advice blocks, which stay numeric
|
|
291
|
+
* even when the value is text.
|
|
292
|
+
* @availableWhen valueType==number || hasSetpoint==true || hasAdvice==true
|
|
293
|
+
*/
|
|
270
294
|
@property({type: Number}) fractionDigits = 0;
|
|
295
|
+
/**
|
|
296
|
+
* Also formats the numeric setpoint / advice blocks, which stay numeric
|
|
297
|
+
* even when the value is text.
|
|
298
|
+
* @availableWhen valueType==number || hasSetpoint==true || hasAdvice==true
|
|
299
|
+
*/
|
|
271
300
|
@property({type: Number}) maxDigits = 0;
|
|
272
301
|
@property({type: String}) dataQuality?: ReadoutDataQuality;
|
|
273
302
|
// `boolean | …` (not `false | …`): the generated Angular wrapper widens a
|
|
@@ -361,8 +390,10 @@ export class ObcReadout extends LitElement {
|
|
|
361
390
|
if (!this.hasSetpoint) {
|
|
362
391
|
return false;
|
|
363
392
|
}
|
|
393
|
+
// A text value never compares equal to a setpoint, so flip-flop / pop-up
|
|
394
|
+
// stay dormant for `valueType="text"`.
|
|
364
395
|
return isDisplayedAtSetpoint(
|
|
365
|
-
this.value,
|
|
396
|
+
resolveReadoutNumericValue(this.value, this.valueType) ?? null,
|
|
366
397
|
this.setpoint,
|
|
367
398
|
this.numericFormatOptions(this.resolvedMaxDigits)
|
|
368
399
|
);
|
|
@@ -489,7 +520,9 @@ export class ObcReadout extends LitElement {
|
|
|
489
520
|
|
|
490
521
|
private renderBlock(config: {
|
|
491
522
|
variant: ReadoutBlockVariant;
|
|
492
|
-
value: number | null | undefined;
|
|
523
|
+
value: number | string | null | undefined;
|
|
524
|
+
/** Only the value block is ever text; setpoint / advice stay numeric. */
|
|
525
|
+
valueType?: ReadoutValueType;
|
|
493
526
|
valueSize: ObcTextboxSize;
|
|
494
527
|
enhanced: boolean;
|
|
495
528
|
weight: ObcTextboxFontWeight;
|
|
@@ -518,6 +551,7 @@ export class ObcReadout extends LitElement {
|
|
|
518
551
|
exportparts="block, block-content, block-text, block-icon, degree"
|
|
519
552
|
.variant=${config.variant}
|
|
520
553
|
.value=${config.value ?? null}
|
|
554
|
+
.valueType=${config.valueType ?? ReadoutValueType.number}
|
|
521
555
|
.size=${this.resolvedSize}
|
|
522
556
|
.valueSize=${config.valueSize}
|
|
523
557
|
.enhanced=${config.enhanced}
|
|
@@ -676,6 +710,7 @@ export class ObcReadout extends LitElement {
|
|
|
676
710
|
${this.renderBlock({
|
|
677
711
|
variant: ReadoutBlockVariant.value,
|
|
678
712
|
value: this.value,
|
|
713
|
+
valueType: this.valueType,
|
|
679
714
|
valueSize: this.valueSize,
|
|
680
715
|
enhanced: this.rowEnhanced,
|
|
681
716
|
weight: this.valueWeight,
|
|
@@ -1017,6 +1052,7 @@ export class ObcReadout extends LitElement {
|
|
|
1017
1052
|
: html`${this.renderBlock({
|
|
1018
1053
|
variant: ReadoutBlockVariant.value,
|
|
1019
1054
|
value: this.value,
|
|
1055
|
+
valueType: this.valueType,
|
|
1020
1056
|
valueSize: this.primarySize,
|
|
1021
1057
|
enhanced: false,
|
|
1022
1058
|
weight: this.valueWeight,
|
|
@@ -1089,6 +1125,18 @@ export class ObcReadout extends LitElement {
|
|
|
1089
1125
|
`;
|
|
1090
1126
|
}
|
|
1091
1127
|
|
|
1128
|
+
protected override willUpdate(changed: Map<string, unknown>): void {
|
|
1129
|
+
super.willUpdate(changed);
|
|
1130
|
+
// Validated on EVERY update, deliberately NOT gated on `value`/`valueType`
|
|
1131
|
+
// appearing in `changed`. When this assertion throws, Lit's `performUpdate`
|
|
1132
|
+
// catch calls `__markUpdated()`, which clears the changed-properties map. A
|
|
1133
|
+
// later update driven by any OTHER property — inside `obc-readout-list`,
|
|
1134
|
+
// `align()` writing the shared reservers — would then see no `value` in
|
|
1135
|
+
// `changed`, skip the check, and render the invalid value as a plain dash:
|
|
1136
|
+
// exactly the silent failure this assertion exists to prevent.
|
|
1137
|
+
assertReadoutValueType('obc-readout', this.value, this.valueType);
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1092
1140
|
override updated(changed: Map<string, unknown>): void {
|
|
1093
1141
|
super.updated(changed);
|
|
1094
1142
|
|