@oicl/openbridge-webcomponents-full-bundle 2.0.0-next.155 → 2.0.0-next.156
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/LICENSE-AGPL.txt +661 -0
- package/LICENSE-APACHE.txt +177 -0
- package/LICENSE.txt +35 -0
- package/bundle/openbridge-webcomponents.bundle.js +3500 -3275
- package/bundle/openbridge-webcomponents.bundle.js.map +1 -1
- package/custom-elements.json +193 -31
- package/dist/automation/transmitter/transmitter-shared.d.ts +11 -0
- package/dist/automation/transmitter/transmitter-shared.d.ts.map +1 -0
- package/dist/automation/transmitter/transmitter-shared.js +16 -0
- package/dist/automation/transmitter/transmitter-shared.js.map +1 -0
- package/dist/automation/transmitter/transmitter.css.js +12 -1
- package/dist/automation/transmitter/transmitter.css.js.map +1 -1
- package/dist/automation/transmitter/transmitter.d.ts +9 -10
- package/dist/automation/transmitter/transmitter.d.ts.map +1 -1
- package/dist/automation/transmitter/transmitter.js +12 -15
- package/dist/automation/transmitter/transmitter.js.map +1 -1
- package/dist/automation/transmitter-button/transmitter-button.css.js +18 -42
- package/dist/automation/transmitter-button/transmitter-button.css.js.map +1 -1
- package/dist/automation/transmitter-button/transmitter-button.d.ts +31 -21
- package/dist/automation/transmitter-button/transmitter-button.d.ts.map +1 -1
- package/dist/automation/transmitter-button/transmitter-button.js +37 -39
- package/dist/automation/transmitter-button/transmitter-button.js.map +1 -1
- package/dist/automation/transmitter-stack/transmitter-stack.css.js +144 -0
- package/dist/automation/transmitter-stack/transmitter-stack.css.js.map +1 -0
- package/dist/automation/transmitter-stack/transmitter-stack.d.ts +93 -0
- package/dist/automation/transmitter-stack/transmitter-stack.d.ts.map +1 -0
- package/dist/automation/transmitter-stack/transmitter-stack.js +97 -0
- package/dist/automation/transmitter-stack/transmitter-stack.js.map +1 -0
- package/dist/building-blocks/readout-block/readout-block.d.ts +1 -1
- package/dist/building-blocks/readout-block/readout-block.js.map +1 -1
- package/dist/components/textarea-field/textarea-field.css.js +0 -2
- package/dist/components/textarea-field/textarea-field.css.js.map +1 -1
- package/dist/components/textbox/textbox.d.ts +1 -1
- package/dist/components/textbox/textbox.js.map +1 -1
- package/dist/navigation-instruments/readout/readout.d.ts +1 -1
- package/dist/navigation-instruments/readout/readout.js.map +1 -1
- package/dist/navigation-instruments/readout-list-item/readout-list-item.css.js +1 -0
- package/dist/navigation-instruments/readout-list-item/readout-list-item.css.js.map +1 -1
- package/dist/navigation-instruments/readout-list-item/readout-list-item.d.ts +1 -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 +3 -0
- package/dist/navigation-instruments/readout-list-item/readout-list-item.js.map +1 -1
- package/package.json +6 -3
- package/script/prepare-full-bundle.ts +5 -0
- package/src/automation/transmitter/transmitter-shared.ts +16 -0
- package/src/automation/transmitter/transmitter.css +3 -71
- package/src/automation/transmitter/transmitter.stories.ts +13 -1
- package/src/automation/transmitter/transmitter.ts +19 -16
- package/src/automation/transmitter-button/transmitter-button.css +11 -36
- package/src/automation/transmitter-button/transmitter-button.stories.ts +3 -1
- package/src/automation/transmitter-button/transmitter-button.ts +62 -66
- package/src/automation/transmitter-stack/transmitter-stack.css +56 -0
- package/src/automation/transmitter-stack/transmitter-stack.spec.ts +210 -0
- package/src/automation/transmitter-stack/transmitter-stack.stories.ts +149 -0
- package/src/automation/transmitter-stack/transmitter-stack.ts +172 -0
- package/src/building-blocks/readout-block/readout-block.stories.ts +1 -1
- package/src/building-blocks/readout-block/readout-block.ts +1 -1
- package/src/components/textarea-field/textarea-field.css +1 -8
- package/src/components/textbox/textbox.stories.ts +1 -1
- package/src/components/textbox/textbox.ts +1 -1
- package/src/mixins/transmitter.css +90 -0
- package/src/mixins/visually-hidden.css +10 -0
- package/src/navigation-instruments/readout/readout.stories.ts +1 -1
- package/src/navigation-instruments/readout/readout.ts +1 -1
- package/src/navigation-instruments/readout-list-item/readout-list-item.css +1 -0
- package/src/navigation-instruments/readout-list-item/readout-list-item.stories.ts +1 -1
- package/src/navigation-instruments/readout-list-item/readout-list-item.ts +12 -2
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
display: inline-flex;
|
|
12
12
|
align-items: center;
|
|
13
13
|
justify-content: center;
|
|
14
|
-
width:
|
|
14
|
+
width: 100%; /* follows the host: hugs by default, fills a stretched grid cell */
|
|
15
15
|
padding: 0;
|
|
16
16
|
appearance: none;
|
|
17
17
|
background: none;
|
|
@@ -21,52 +21,23 @@
|
|
|
21
21
|
@mixin style style=normal visibleWrapperClass=.visible-wrapper;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/* Figma draws the 1 px stroke inside its 4 / 2 px padding, so the padding
|
|
25
|
+
gives the border back and the chip stays 24 px high. */
|
|
24
26
|
.visible-wrapper {
|
|
25
27
|
display: inline-flex;
|
|
26
28
|
align-items: center;
|
|
27
|
-
|
|
28
|
-
padding: 2px 4px;
|
|
29
|
+
padding: 1px 3px;
|
|
29
30
|
border: 1px solid var(--normal-enabled-border-color);
|
|
30
31
|
border-radius: 4px;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
align-items: center;
|
|
36
|
-
gap: 2px;
|
|
37
|
-
padding: 0 2px;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
.icon {
|
|
41
|
-
display: inline-flex;
|
|
42
|
-
align-items: center;
|
|
43
|
-
justify-content: center;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
.unit {
|
|
47
|
-
@mixin font-instrument-unit;
|
|
48
|
-
align-self: flex-end;
|
|
49
|
-
color: var(--element-neutral-color);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
.size-regular .icon ::slotted(*) {
|
|
53
|
-
width: 16px;
|
|
54
|
-
height: 16px;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
.size-medium .icon ::slotted(*) {
|
|
58
|
-
width: 20px;
|
|
59
|
-
height: 20px;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
.size-large .icon ::slotted(*) {
|
|
63
|
-
width: 24px;
|
|
64
|
-
height: 24px;
|
|
34
|
+
obc-readout-list-item {
|
|
35
|
+
width: auto; /* the list item fills a row by default; hugging lets a stretched chip centre the reading */
|
|
65
36
|
}
|
|
66
37
|
|
|
67
38
|
.size-medium .visible-wrapper:not(.tag),
|
|
68
39
|
.size-large .visible-wrapper:not(.tag) {
|
|
69
|
-
padding:
|
|
40
|
+
padding: 3px 7px;
|
|
70
41
|
border-radius: 6px;
|
|
71
42
|
}
|
|
72
43
|
|
|
@@ -81,3 +52,7 @@
|
|
|
81
52
|
@mixin font-instrument-value-regular-neutral;
|
|
82
53
|
color: var(--element-neutral-color);
|
|
83
54
|
}
|
|
55
|
+
|
|
56
|
+
.visually-hidden {
|
|
57
|
+
@mixin visually-hidden;
|
|
58
|
+
}
|
|
@@ -16,11 +16,12 @@ const meta: Meta<typeof ObcTransmitterButton> = {
|
|
|
16
16
|
variant: TransmitterButtonVariant.value,
|
|
17
17
|
size: TransmitterButtonSize.regular,
|
|
18
18
|
value: 12.3,
|
|
19
|
-
unit: '
|
|
19
|
+
unit: 'C',
|
|
20
20
|
fractionDigits: 1,
|
|
21
21
|
maxDigits: 0,
|
|
22
22
|
hintedZeros: false,
|
|
23
23
|
hasSignSpacer: false,
|
|
24
|
+
hasDegree: true,
|
|
24
25
|
hasIcon: false,
|
|
25
26
|
hasAdvice: false,
|
|
26
27
|
adviceValue: 123,
|
|
@@ -81,6 +82,7 @@ function renderComponent(args: ObcTransmitterButton) {
|
|
|
81
82
|
.maxDigits=${args.maxDigits}
|
|
82
83
|
.hintedZeros=${args.hintedZeros}
|
|
83
84
|
.hasSignSpacer=${args.hasSignSpacer}
|
|
85
|
+
.hasDegree=${args.hasDegree}
|
|
84
86
|
.hasIcon=${args.hasIcon}
|
|
85
87
|
.hasAdvice=${args.hasAdvice}
|
|
86
88
|
.adviceValue=${args.adviceValue}
|
|
@@ -3,11 +3,8 @@ import {property} from 'lit/decorators.js';
|
|
|
3
3
|
import {classMap} from 'lit/directives/class-map.js';
|
|
4
4
|
import componentStyle from './transmitter-button.css?inline';
|
|
5
5
|
import {customElement} from '../../decorator.js';
|
|
6
|
-
import '../../
|
|
7
|
-
import {
|
|
8
|
-
ReadoutBlockSize,
|
|
9
|
-
ReadoutBlockVariant,
|
|
10
|
-
} from '../../building-blocks/readout-block/readout-block.js';
|
|
6
|
+
import '../../navigation-instruments/readout-list-item/readout-list-item.js';
|
|
7
|
+
import {ReadoutListItemSize} from '../../navigation-instruments/readout-list-item/readout-list-item.js';
|
|
11
8
|
|
|
12
9
|
export enum TransmitterButtonVariant {
|
|
13
10
|
value = 'value',
|
|
@@ -20,31 +17,31 @@ export enum TransmitterButtonSize {
|
|
|
20
17
|
large = 'large',
|
|
21
18
|
}
|
|
22
19
|
|
|
23
|
-
const readoutSizeBySize: Record<TransmitterButtonSize,
|
|
24
|
-
[TransmitterButtonSize.regular]:
|
|
25
|
-
[TransmitterButtonSize.medium]:
|
|
26
|
-
[TransmitterButtonSize.large]:
|
|
20
|
+
const readoutSizeBySize: Record<TransmitterButtonSize, ReadoutListItemSize> = {
|
|
21
|
+
[TransmitterButtonSize.regular]: ReadoutListItemSize.small,
|
|
22
|
+
[TransmitterButtonSize.medium]: ReadoutListItemSize.medium,
|
|
23
|
+
[TransmitterButtonSize.large]: ReadoutListItemSize.large,
|
|
27
24
|
};
|
|
28
25
|
|
|
29
26
|
/**
|
|
30
|
-
* `<obc-transmitter-button>` – The pressable readout chip
|
|
31
|
-
* transmitter
|
|
27
|
+
* `<obc-transmitter-button>` – The pressable readout chip at the core of a
|
|
28
|
+
* transmitter: a bordered button around one reading.
|
|
32
29
|
*
|
|
33
|
-
* The
|
|
34
|
-
* value
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* provides the type-specific content.
|
|
30
|
+
* The reading is a label-less `<obc-readout-list-item>` (the Figma "automation
|
|
31
|
+
* value"): leading icon, optional advice and setpoint, value, optional degree
|
|
32
|
+
* and unit, all rendered by the readout family. The leading icon is slotted so
|
|
33
|
+
* the consumer provides the type-specific content.
|
|
38
34
|
*
|
|
39
35
|
* ### Features / Variants
|
|
40
|
-
* - **`value`** –
|
|
41
|
-
* leading advice segment with `hasAdvice`/`adviceValue
|
|
42
|
-
* with `hasSetPoint`/`setpointValue
|
|
36
|
+
* - **`value`** – bordered chip showing an icon, value and unit. Opt into a
|
|
37
|
+
* leading advice segment with `hasAdvice`/`adviceValue`, a setpoint segment
|
|
38
|
+
* with `hasSetPoint`/`setpointValue`, and a degree column before the unit
|
|
39
|
+
* with `hasDegree`.
|
|
43
40
|
* - **`tag`** – a static rounded pill showing a short identifier (e.g. `TT`)
|
|
44
41
|
* from the `label` property, with no live value.
|
|
45
|
-
* - **`size`** – `regular`, `medium` or `large`,
|
|
46
|
-
*
|
|
47
|
-
*
|
|
42
|
+
* - **`size`** – `regular`, `medium` or `large`, the list item's `small`,
|
|
43
|
+
* `medium` and `large` tiers: the value text, the icon and the advice/setpoint
|
|
44
|
+
* segments scale, the unit stays extra small.
|
|
48
45
|
* - **Formatting** – `fractionDigits` sets the decimal precision, `maxDigits`
|
|
49
46
|
* reserves a number of integer digits, and `hintedZeros` renders the reserved
|
|
50
47
|
* leading positions as muted zeros (e.g. `0012.3`). The sign never consumes
|
|
@@ -57,9 +54,15 @@ const readoutSizeBySize: Record<TransmitterButtonSize, ReadoutBlockSize> = {
|
|
|
57
54
|
* dashes fill the whole reserved width (e.g. `---.-`).
|
|
58
55
|
*
|
|
59
56
|
* ### Usage Guidelines
|
|
60
|
-
* Use as a building block for `<obc-transmitter
|
|
61
|
-
* the measured value or the tag identifier. Slot in
|
|
62
|
-
* advice and setpoint segments are value-driven and
|
|
57
|
+
* Use as a building block for `<obc-transmitter>` and `<obc-transmitter-stack>`;
|
|
58
|
+
* it is the part that carries the measured value or the tag identifier. Slot in
|
|
59
|
+
* the type-specific icon; the advice and setpoint segments are value-driven and
|
|
60
|
+
* read-only.
|
|
61
|
+
*
|
|
62
|
+
* **TODO(designer):** the hit area is the visible chip, 24 px high at `regular`:
|
|
63
|
+
* at the WCAG 2.5.8 floor of 24 px, below the 48 px touch-target token. A
|
|
64
|
+
* 48 px target would overlap the id tag, the leader line and neighbouring
|
|
65
|
+
* `<obc-transmitter-stack>` segments, so the size needs a design decision.
|
|
63
66
|
*
|
|
64
67
|
* ### Slots
|
|
65
68
|
* | Slot Name | Conditions | Purpose |
|
|
@@ -69,9 +72,12 @@ const readoutSizeBySize: Record<TransmitterButtonSize, ReadoutBlockSize> = {
|
|
|
69
72
|
* @property maxDigits - Integer digits to reserve / hint (independent of `fractionDigits`).
|
|
70
73
|
* @property hasSignSpacer - Reserve a minus-sign column on every segment, filled by the real sign
|
|
71
74
|
* only while a value is negative, so the width does not change across zero.
|
|
75
|
+
* @property hasDegree - Show a degree column between the value and the unit (e.g. `12.3°` then `C`).
|
|
72
76
|
* @property adviceValue - Advisory value shown in the leading advice segment when `hasAdvice`.
|
|
73
77
|
* @property setpointValue - Target value shown in the setpoint segment when `hasSetPoint`.
|
|
74
78
|
* @property label - Short tag identifier shown in the `tag` variant (e.g. `TT`).
|
|
79
|
+
* @property idTag - Identifier appended to the button's accessible name only (e.g. `#0001`); the
|
|
80
|
+
* parent transmitter draws the visible tag outside the button, where it cannot join the name.
|
|
75
81
|
* @slot icon - Leading icon beside the value.
|
|
76
82
|
*
|
|
77
83
|
* @experimental
|
|
@@ -90,6 +96,7 @@ export class ObcTransmitterButton extends LitElement {
|
|
|
90
96
|
|
|
91
97
|
@property({type: Boolean}) hintedZeros = false;
|
|
92
98
|
@property({type: Boolean}) hasSignSpacer = false;
|
|
99
|
+
@property({type: Boolean}) hasDegree = false;
|
|
93
100
|
@property({type: Boolean}) hasIcon = false;
|
|
94
101
|
@property({type: Boolean}) hasAdvice = false;
|
|
95
102
|
|
|
@@ -101,53 +108,37 @@ export class ObcTransmitterButton extends LitElement {
|
|
|
101
108
|
|
|
102
109
|
@property({type: String}) label = '';
|
|
103
110
|
|
|
111
|
+
@property({type: String}) idTag = '';
|
|
112
|
+
|
|
104
113
|
private get isTag() {
|
|
105
114
|
return this.variant === TransmitterButtonVariant.tag;
|
|
106
115
|
}
|
|
107
116
|
|
|
108
|
-
private
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
.variant=${variant}
|
|
114
|
-
.value=${value ?? null}
|
|
115
|
-
.size=${readoutSizeBySize[this.size]}
|
|
116
|
-
.fractionDigits=${this.fractionDigits}
|
|
117
|
-
.maxDigits=${this.maxDigits}
|
|
118
|
-
.hintedZeros=${this.hintedZeros}
|
|
119
|
-
.hasSignSpacer=${this.hasSignSpacer}
|
|
120
|
-
></obc-readout-block>`;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
private renderAdvice() {
|
|
124
|
-
if (!this.hasAdvice) {
|
|
125
|
-
return nothing;
|
|
126
|
-
}
|
|
127
|
-
return this.renderBlock(ReadoutBlockVariant.advice, this.adviceValue);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
private renderSetpoint() {
|
|
131
|
-
if (!this.hasSetPoint) {
|
|
132
|
-
return nothing;
|
|
133
|
-
}
|
|
134
|
-
return this.renderBlock(ReadoutBlockVariant.setpoint, this.setpointValue);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
private renderContent() {
|
|
138
|
-
if (this.isTag) {
|
|
139
|
-
return html`<span class="label">${this.label}</span>`;
|
|
140
|
-
}
|
|
141
|
-
|
|
117
|
+
private renderReading() {
|
|
118
|
+
const format = {
|
|
119
|
+
hintedZeros: this.hintedZeros,
|
|
120
|
+
hasSignSpacer: this.hasSignSpacer,
|
|
121
|
+
};
|
|
142
122
|
return html`
|
|
143
|
-
|
|
144
|
-
|
|
123
|
+
<obc-readout-list-item
|
|
124
|
+
.size=${readoutSizeBySize[this.size]}
|
|
125
|
+
.value=${this.value ?? null}
|
|
126
|
+
.unit=${this.unit}
|
|
127
|
+
.hasDegree=${this.hasDegree}
|
|
128
|
+
.fractionDigits=${this.fractionDigits}
|
|
129
|
+
.maxDigits=${this.maxDigits}
|
|
130
|
+
.valueOptions=${{...format, hasIcon: this.hasIcon}}
|
|
131
|
+
.hasSetpoint=${this.hasSetPoint}
|
|
132
|
+
.setpoint=${this.setpointValue ?? undefined}
|
|
133
|
+
.setpointOptions=${format}
|
|
134
|
+
.hasAdvice=${this.hasAdvice}
|
|
135
|
+
.advice=${this.adviceValue ?? undefined}
|
|
136
|
+
.adviceOptions=${format}
|
|
137
|
+
>
|
|
145
138
|
${this.hasIcon
|
|
146
|
-
? html`<
|
|
139
|
+
? html`<slot name="icon" slot="value-icon"></slot>`
|
|
147
140
|
: nothing}
|
|
148
|
-
|
|
149
|
-
${this.unit ? html`<span class="unit">${this.unit}</span>` : nothing}
|
|
150
|
-
</div>
|
|
141
|
+
</obc-readout-list-item>
|
|
151
142
|
`;
|
|
152
143
|
}
|
|
153
144
|
|
|
@@ -161,8 +152,13 @@ export class ObcTransmitterButton extends LitElement {
|
|
|
161
152
|
tag: this.isTag,
|
|
162
153
|
})}
|
|
163
154
|
>
|
|
164
|
-
${this.
|
|
155
|
+
${this.isTag
|
|
156
|
+
? html`<span class="label">${this.label}</span>`
|
|
157
|
+
: this.renderReading()}
|
|
165
158
|
</div>
|
|
159
|
+
${this.idTag
|
|
160
|
+
? html`<span class="visually-hidden">${this.idTag}</span>`
|
|
161
|
+
: nothing}
|
|
166
162
|
</button>
|
|
167
163
|
`;
|
|
168
164
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
* {
|
|
2
|
+
box-sizing: border-box;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
:host {
|
|
6
|
+
display: block;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.transmitter {
|
|
10
|
+
@mixin transmitter-leader;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.values {
|
|
14
|
+
display: grid;
|
|
15
|
+
grid-auto-flow: column;
|
|
16
|
+
grid-auto-columns: 1fr; /* equal segments, sized by the widest reading (Figma 25656:45218) */
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.cell {
|
|
20
|
+
position: relative; /* containing block for the left/right id tag (transmitter-leader) */
|
|
21
|
+
display: flex;
|
|
22
|
+
flex-direction: column;
|
|
23
|
+
align-items: center;
|
|
24
|
+
gap: 2px;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.orientation-top .cell {
|
|
28
|
+
flex-direction: column-reverse;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.segment {
|
|
32
|
+
width: 100%;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.segment::part(button) {
|
|
36
|
+
flex: 1;
|
|
37
|
+
justify-content: center;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* Neighbours share one border line; only the chip's outer corners round. */
|
|
41
|
+
.cell:not(:last-child) .segment::part(button) {
|
|
42
|
+
border-right: none;
|
|
43
|
+
border-top-right-radius: 0;
|
|
44
|
+
border-bottom-right-radius: 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.cell:not(:first-child) .segment::part(button) {
|
|
48
|
+
border-top-left-radius: 0;
|
|
49
|
+
border-bottom-left-radius: 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.id-tag {
|
|
53
|
+
@mixin transmitter-id-tag;
|
|
54
|
+
/* an empty tag keeps the chip row aligned */
|
|
55
|
+
min-height: var(--global-typography-instrument-label-line-height);
|
|
56
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import {describe, expect, it} from 'vitest';
|
|
2
|
+
import {render} from 'vitest-browser-lit';
|
|
3
|
+
import {page, userEvent} from '@vitest/browser/context';
|
|
4
|
+
import {html} from 'lit';
|
|
5
|
+
import '../../main.css';
|
|
6
|
+
import './transmitter-stack.js';
|
|
7
|
+
import type {
|
|
8
|
+
ObcTransmitterStack,
|
|
9
|
+
TransmitterStackValue,
|
|
10
|
+
TransmitterStackValueClickDetail,
|
|
11
|
+
} from './transmitter-stack.js';
|
|
12
|
+
import type {ObcTransmitterButton} from '../transmitter-button/transmitter-button.js';
|
|
13
|
+
import type {ObcReadoutListItem} from '../../navigation-instruments/readout-list-item/readout-list-item.js';
|
|
14
|
+
|
|
15
|
+
async function setup(values: TransmitterStackValue[]) {
|
|
16
|
+
const screen = render(
|
|
17
|
+
html`<obc-transmitter-stack .values=${values}>
|
|
18
|
+
<span slot="temperature" id="icon"></span>
|
|
19
|
+
</obc-transmitter-stack>`
|
|
20
|
+
);
|
|
21
|
+
const el = screen.container.querySelector(
|
|
22
|
+
'obc-transmitter-stack'
|
|
23
|
+
) as ObcTransmitterStack;
|
|
24
|
+
await el.updateComplete;
|
|
25
|
+
return el;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function segments(el: ObcTransmitterStack): ObcTransmitterButton[] {
|
|
29
|
+
return Array.from(el.shadowRoot!.querySelectorAll('obc-transmitter-button'));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function idTags(el: ObcTransmitterStack): string[] {
|
|
33
|
+
return Array.from(el.shadowRoot!.querySelectorAll('.id-tag')).map(
|
|
34
|
+
(tag) => tag.textContent?.trim() ?? ''
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
describe('obc-transmitter-stack', () => {
|
|
39
|
+
it('renders one segment per value, in order', async () => {
|
|
40
|
+
const el = await setup([
|
|
41
|
+
{value: 12.3, unit: '°C'},
|
|
42
|
+
{value: 1.23, unit: 'm', fractionDigits: 2},
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
expect(
|
|
46
|
+
segments(el).map((s) => [s.value, s.unit, s.fractionDigits])
|
|
47
|
+
).toEqual([
|
|
48
|
+
[12.3, '°C', 1],
|
|
49
|
+
[1.23, 'm', 2],
|
|
50
|
+
]);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('renders each reading through a label-less readout list item', async () => {
|
|
54
|
+
const el = await setup([
|
|
55
|
+
{value: 12.3, unit: 'C', hasDegree: true},
|
|
56
|
+
{value: 1, unit: 'm'},
|
|
57
|
+
]);
|
|
58
|
+
const items = await Promise.all(
|
|
59
|
+
segments(el).map(async (segment) => {
|
|
60
|
+
await segment.updateComplete;
|
|
61
|
+
return segment.shadowRoot!.querySelector(
|
|
62
|
+
'obc-readout-list-item'
|
|
63
|
+
) as ObcReadoutListItem;
|
|
64
|
+
})
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
expect(
|
|
68
|
+
items.map((item) => [item.value, item.unit, item.hasDegree, item.label])
|
|
69
|
+
).toEqual([
|
|
70
|
+
[12.3, 'C', true, undefined],
|
|
71
|
+
[1, 'm', false, undefined],
|
|
72
|
+
]);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('defaults an omitted digit key but forwards one that never arrived', async () => {
|
|
76
|
+
const el = await setup([
|
|
77
|
+
{value: 1},
|
|
78
|
+
{value: 1, fractionDigits: undefined, maxDigits: NaN},
|
|
79
|
+
]);
|
|
80
|
+
const [omitted, missing] = segments(el);
|
|
81
|
+
|
|
82
|
+
expect([omitted.fractionDigits, omitted.maxDigits]).toEqual([1, 0]);
|
|
83
|
+
expect(missing.fractionDigits).toBeNaN();
|
|
84
|
+
expect(missing.maxDigits).toBeNaN();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('forwards the named icon slot into the segment', async () => {
|
|
88
|
+
const el = await setup([
|
|
89
|
+
{value: 1, iconSlotName: 'temperature'},
|
|
90
|
+
{value: 2},
|
|
91
|
+
]);
|
|
92
|
+
const [withIcon, withoutIcon] = segments(el);
|
|
93
|
+
const forwarded = withIcon.querySelector('slot') as HTMLSlotElement;
|
|
94
|
+
|
|
95
|
+
expect(withIcon.hasIcon).toBe(true);
|
|
96
|
+
expect(withoutIcon.hasIcon).toBe(false);
|
|
97
|
+
expect(forwarded.name).toBe('temperature');
|
|
98
|
+
expect(forwarded.getAttribute('slot')).toBe('icon');
|
|
99
|
+
expect(forwarded.assignedElements()[0]?.id).toBe('icon');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('fires value-click with the pressed segment', async () => {
|
|
103
|
+
const values = [{value: 1}, {value: 2}];
|
|
104
|
+
const el = await setup(values);
|
|
105
|
+
const details: TransmitterStackValueClickDetail[] = [];
|
|
106
|
+
el.addEventListener('value-click', (event) =>
|
|
107
|
+
details.push(
|
|
108
|
+
(event as CustomEvent<TransmitterStackValueClickDetail>).detail
|
|
109
|
+
)
|
|
110
|
+
);
|
|
111
|
+
const second = segments(el)[1];
|
|
112
|
+
await second.updateComplete;
|
|
113
|
+
|
|
114
|
+
second.shadowRoot!.querySelector('button')!.click();
|
|
115
|
+
|
|
116
|
+
expect(details).toEqual([{index: 1, value: values[1]}]);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('reports the rendered value when values change before the next render', async () => {
|
|
120
|
+
const rendered = [{value: 1}, {value: 2}];
|
|
121
|
+
const el = await setup(rendered);
|
|
122
|
+
const details: TransmitterStackValueClickDetail[] = [];
|
|
123
|
+
el.addEventListener('value-click', (event) =>
|
|
124
|
+
details.push(
|
|
125
|
+
(event as CustomEvent<TransmitterStackValueClickDetail>).detail
|
|
126
|
+
)
|
|
127
|
+
);
|
|
128
|
+
const second = segments(el)[1];
|
|
129
|
+
await second.updateComplete;
|
|
130
|
+
|
|
131
|
+
el.values = [{value: 3}, {value: 4}];
|
|
132
|
+
second.shadowRoot!.querySelector('button')!.click();
|
|
133
|
+
|
|
134
|
+
expect(details).toEqual([{index: 1, value: rendered[1]}]);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('reaches each segment with Tab and activates it with Enter and Space', async () => {
|
|
138
|
+
const el = await setup([{value: 1}, {value: 2}]);
|
|
139
|
+
const indexes: number[] = [];
|
|
140
|
+
el.addEventListener('value-click', (event) =>
|
|
141
|
+
indexes.push(
|
|
142
|
+
(event as CustomEvent<TransmitterStackValueClickDetail>).detail.index
|
|
143
|
+
)
|
|
144
|
+
);
|
|
145
|
+
await Promise.all(segments(el).map((segment) => segment.updateComplete));
|
|
146
|
+
|
|
147
|
+
await userEvent.tab();
|
|
148
|
+
await userEvent.keyboard('{Enter}');
|
|
149
|
+
await userEvent.tab();
|
|
150
|
+
await userEvent.keyboard(' ');
|
|
151
|
+
|
|
152
|
+
expect(indexes).toEqual([0, 1]);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('names each segment by its reading and id tag, and hides the visible tag', async () => {
|
|
156
|
+
const el = await setup([
|
|
157
|
+
{value: 1, unit: 'm', idTag: '#0001'},
|
|
158
|
+
{value: 1, unit: 'm', idTag: '#0002'},
|
|
159
|
+
]);
|
|
160
|
+
await Promise.all(segments(el).map((segment) => segment.updateComplete));
|
|
161
|
+
|
|
162
|
+
await expect
|
|
163
|
+
.element(page.getByRole('button', {name: /#0002/}))
|
|
164
|
+
.toBeInTheDocument();
|
|
165
|
+
expect(
|
|
166
|
+
Array.from(el.shadowRoot!.querySelectorAll('.id-tag')).map((tag) =>
|
|
167
|
+
tag.getAttribute('aria-hidden')
|
|
168
|
+
)
|
|
169
|
+
).toEqual(['true', 'true']);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('renders nothing, leader line included, without values', async () => {
|
|
173
|
+
const el = await setup([]);
|
|
174
|
+
|
|
175
|
+
expect(el.shadowRoot!.querySelector('.transmitter')).toBeNull();
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('gives every segment the width of the widest reading', async () => {
|
|
179
|
+
const el = await setup([
|
|
180
|
+
{value: 1, unit: 'm'},
|
|
181
|
+
{value: 12345.6, unit: 'mVs', maxDigits: 5},
|
|
182
|
+
]);
|
|
183
|
+
await Promise.all(segments(el).map((segment) => segment.updateComplete));
|
|
184
|
+
const [narrow, wide] = segments(el).map(
|
|
185
|
+
(segment) => segment.getBoundingClientRect().width
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
expect(narrow).toBeCloseTo(wide, 0);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('keeps each segment at or above the 24 px pointer-target floor', async () => {
|
|
192
|
+
const el = await setup([{value: 1, unit: 'm'}]);
|
|
193
|
+
const [segment] = segments(el);
|
|
194
|
+
await segment.updateComplete;
|
|
195
|
+
const target = segment
|
|
196
|
+
.shadowRoot!.querySelector('button')!
|
|
197
|
+
.getBoundingClientRect();
|
|
198
|
+
|
|
199
|
+
expect(target.height).toBeGreaterThanOrEqual(24);
|
|
200
|
+
expect(target.width).toBeGreaterThanOrEqual(24);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('renders an id-tag row in every cell once any value has one', async () => {
|
|
204
|
+
expect(idTags(await setup([{value: 1, idTag: '#1'}, {value: 2}]))).toEqual([
|
|
205
|
+
'#1',
|
|
206
|
+
'',
|
|
207
|
+
]);
|
|
208
|
+
expect(idTags(await setup([{value: 1}, {value: 2}]))).toEqual([]);
|
|
209
|
+
});
|
|
210
|
+
});
|