@oicl/openbridge-webcomponents-full-bundle 2.0.0-next.136 → 2.0.0-next.137

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.
Files changed (39) hide show
  1. package/bundle/openbridge-webcomponents.bundle.js +11434 -10949
  2. package/bundle/openbridge-webcomponents.bundle.js.map +1 -1
  3. package/custom-elements.json +784 -0
  4. package/dist/automation/hydraulic-check-valve/hydraulic-check-valve.css.js +12 -0
  5. package/dist/automation/hydraulic-check-valve/hydraulic-check-valve.css.js.map +1 -0
  6. package/dist/automation/hydraulic-check-valve/hydraulic-check-valve.d.ts +33 -0
  7. package/dist/automation/hydraulic-check-valve/hydraulic-check-valve.d.ts.map +1 -0
  8. package/dist/automation/hydraulic-check-valve/hydraulic-check-valve.js +58 -0
  9. package/dist/automation/hydraulic-check-valve/hydraulic-check-valve.js.map +1 -0
  10. package/dist/automation/hydraulic-valve-4-3/hydraulic-valve-4-3.d.ts +77 -0
  11. package/dist/automation/hydraulic-valve-4-3/hydraulic-valve-4-3.d.ts.map +1 -0
  12. package/dist/automation/hydraulic-valve-4-3/hydraulic-valve-4-3.js +74 -0
  13. package/dist/automation/hydraulic-valve-4-3/hydraulic-valve-4-3.js.map +1 -0
  14. package/dist/automation/hydraulic-valve-x-2/hydraulic-valve-x-2.d.ts +74 -0
  15. package/dist/automation/hydraulic-valve-x-2/hydraulic-valve-x-2.d.ts.map +1 -0
  16. package/dist/automation/hydraulic-valve-x-2/hydraulic-valve-x-2.js +60 -0
  17. package/dist/automation/hydraulic-valve-x-2/hydraulic-valve-x-2.js.map +1 -0
  18. package/dist/automation/shuffle-button/shuffle-button-base.d.ts +67 -0
  19. package/dist/automation/shuffle-button/shuffle-button-base.d.ts.map +1 -0
  20. package/dist/automation/shuffle-button/shuffle-button-base.js +126 -0
  21. package/dist/automation/shuffle-button/shuffle-button-base.js.map +1 -0
  22. package/dist/automation/shuffle-button/shuffle-button.css.js +219 -0
  23. package/dist/automation/shuffle-button/shuffle-button.css.js.map +1 -0
  24. package/dist/automation/shuffle-button/shuffle-layout.d.ts +9 -0
  25. package/dist/automation/shuffle-button/shuffle-layout.d.ts.map +1 -0
  26. package/dist/automation/shuffle-button/shuffle-layout.js +16 -0
  27. package/dist/automation/shuffle-button/shuffle-layout.js.map +1 -0
  28. package/package.json +1 -1
  29. package/src/automation/hydraulic-check-valve/hydraulic-check-valve.css +3 -0
  30. package/src/automation/hydraulic-check-valve/hydraulic-check-valve.stories.ts +18 -0
  31. package/src/automation/hydraulic-check-valve/hydraulic-check-valve.ts +60 -0
  32. package/src/automation/hydraulic-valve-4-3/hydraulic-valve-4-3.stories.ts +72 -0
  33. package/src/automation/hydraulic-valve-4-3/hydraulic-valve-4-3.ts +108 -0
  34. package/src/automation/hydraulic-valve-x-2/hydraulic-valve-x-2.stories.ts +60 -0
  35. package/src/automation/hydraulic-valve-x-2/hydraulic-valve-x-2.ts +101 -0
  36. package/src/automation/shuffle-button/shuffle-button-base.ts +170 -0
  37. package/src/automation/shuffle-button/shuffle-button.css +136 -0
  38. package/src/automation/shuffle-button/shuffle-layout.spec.ts +44 -0
  39. package/src/automation/shuffle-button/shuffle-layout.ts +21 -0
@@ -0,0 +1,101 @@
1
+ import {html} from 'lit';
2
+ import type {TemplateResult} from 'lit';
3
+ import {property} from 'lit/decorators.js';
4
+ import {customElement} from '../../decorator.js';
5
+ import {ObcShuffleButtonBase} from '../shuffle-button/shuffle-button-base.js';
6
+ import '../../icons/icon-hydraulic-01.js';
7
+ import '../../icons/icon-hydraulic-02.js';
8
+ import '../../icons/icon-hydraulic-07.js';
9
+ import '../../icons/icon-hydraulic-10.js';
10
+ import '../../icons/icon-hydraulic-11.js';
11
+
12
+ /**
13
+ * Detail payload for the `position-selected` event: the zero-based position
14
+ * the user requested (0 = first, 1 = second).
15
+ *
16
+ * Declared here rather than imported from the base: the wrapper generators
17
+ * resolve event types in the component's own module only.
18
+ */
19
+ export interface PositionSelectedDetail {
20
+ position: number;
21
+ }
22
+
23
+ /**
24
+ * Port/position configuration of `<obc-hydraulic-valve-x-2>`:
25
+ * `2/2`, `3/2` or `4/2` (ports/positions). Each value maps to its own pair of
26
+ * position symbols.
27
+ */
28
+ export enum HydraulicValveX2Type {
29
+ TwoTwo = '2/2',
30
+ ThreeTwo = '3/2',
31
+ FourTwo = '4/2',
32
+ }
33
+
34
+ /**
35
+ * `<obc-hydraulic-valve-x-2>` – Position selector symbol for a two-position valve.
36
+ *
37
+ * Shows the valve's two positions as icon thumbs on a shared track. The
38
+ * selected position always occupies the fixed center slot; the option thumb
39
+ * sits to its left or right depending on logical order, and an empty spacer
40
+ * slot keeps the total width constant (three 48px slots at regular size).
41
+ *
42
+ * ## Features / Variants
43
+ * - `type` selects the port/position configuration: `2/2`, `3/2` or `4/2`.
44
+ * - `vertical` stacks the positions top to bottom and rotates the symbols to
45
+ * match a vertical flow path.
46
+ * - Controlled selection: clicking the option or pressing arrow keys fires
47
+ * `position-selected`; the application updates `selectedPosition` once the
48
+ * change is confirmed.
49
+ * - Scales with the component size classes (`obc-component-size-*`).
50
+ *
51
+ * ## Usage Guidelines
52
+ * Use to display and command the position of a two-position valve. For 4/3
53
+ * directional valves use `<obc-hydraulic-valve-4-3>`; for a static check
54
+ * valve symbol use `<obc-hydraulic-check-valve>`. Bind `selectedPosition` to
55
+ * the position the device reports, not to the last request, so the symbol
56
+ * never shows a position the valve has not reached.
57
+ *
58
+ * ## Events
59
+ * - `position-selected` – Fired when the user requests a position (click or
60
+ * arrow key). Detail: `{position: number}` (0 = first, 1 = second).
61
+ *
62
+ * @property type - Port/position configuration.
63
+ * @property ariaLabel - Accessible name for the control.
64
+ * @fires {CustomEvent<PositionSelectedDetail>} position-selected - Fired when the user requests a position (click or arrow key).
65
+ * @experimental
66
+ */
67
+ @customElement('obc-hydraulic-valve-x-2')
68
+ export class ObcHydraulicValveX2 extends ObcShuffleButtonBase {
69
+ @property({type: String}) type: HydraulicValveX2Type =
70
+ HydraulicValveX2Type.TwoTwo;
71
+
72
+ @property({type: String}) override ariaLabel = 'Hydraulic valve';
73
+
74
+ protected override get positionCount(): number {
75
+ return 2;
76
+ }
77
+
78
+ protected override renderPositionIcon(position: number): TemplateResult {
79
+ switch (this.type) {
80
+ case HydraulicValveX2Type.ThreeTwo:
81
+ return position === 0
82
+ ? html`<obi-hydraulic-11></obi-hydraulic-11>`
83
+ : html`<obi-hydraulic-10></obi-hydraulic-10>`;
84
+ case HydraulicValveX2Type.FourTwo:
85
+ return position === 0
86
+ ? html`<obi-hydraulic-02></obi-hydraulic-02>`
87
+ : html`<obi-hydraulic-07></obi-hydraulic-07>`;
88
+ case HydraulicValveX2Type.TwoTwo:
89
+ default:
90
+ return position === 0
91
+ ? html`<obi-hydraulic-02></obi-hydraulic-02>`
92
+ : html`<obi-hydraulic-01></obi-hydraulic-01>`;
93
+ }
94
+ }
95
+ }
96
+
97
+ declare global {
98
+ interface HTMLElementTagNameMap {
99
+ 'obc-hydraulic-valve-x-2': ObcHydraulicValveX2;
100
+ }
101
+ }
@@ -0,0 +1,170 @@
1
+ import {LitElement, html, unsafeCSS} from 'lit';
2
+ import type {PropertyValues, TemplateResult} from 'lit';
3
+ import {property, state} from 'lit/decorators.js';
4
+ import {classMap} from 'lit/directives/class-map.js';
5
+ import componentStyle from './shuffle-button.css?inline';
6
+ import {
7
+ clampPosition,
8
+ shuffleSlotCount,
9
+ shuffleWindowOffset,
10
+ } from './shuffle-layout.js';
11
+
12
+ /**
13
+ * Detail payload for the `position-selected` event dispatched by
14
+ * shuffle-button components. `position` is the zero-based position the user
15
+ * requested.
16
+ */
17
+ export interface PositionSelectedDetail {
18
+ position: number;
19
+ }
20
+
21
+ /**
22
+ * The `position-selected` event dispatched by shuffle-button components.
23
+ */
24
+ export type PositionSelectedEvent = CustomEvent<PositionSelectedDetail>;
25
+
26
+ /**
27
+ * Base class for shuffle-button selectors: a control whose selected thumb
28
+ * always occupies the fixed center slot of a (2·n−1)-slot row (or column when
29
+ * `vertical`). Option thumbs keep their logical order and redistribute to
30
+ * either side of the selected thumb; empty spacer slots absorb the remaining
31
+ * length so the total size never changes.
32
+ *
33
+ * ## Controlled selection
34
+ * Clicking an option or pressing an arrow key only fires `position-selected`.
35
+ * The host application sets `selectedPosition` once the device confirms the
36
+ * change, so the control never shows a position the device has not reached.
37
+ *
38
+ * ## Keyboard
39
+ * Follows the WAI-ARIA radio group pattern
40
+ * (https://www.w3.org/WAI/ARIA/apg/patterns/radio/): `role="radiogroup"` with
41
+ * one `role="radio"` thumb per position, a single tab stop on the selected
42
+ * thumb, and Left/Up and Right/Down moving to the previous/next position with
43
+ * wrap-around. Departures: arrow keys request the position rather than moving
44
+ * the checked state (controlled selection above), and focus follows the thumb
45
+ * only once the host confirms it. Space/Enter on a focused thumb request it
46
+ * through the native button click. Home/End are out of scope.
47
+ *
48
+ * Subclasses define `positionCount` and `renderPositionIcon()` and carry the
49
+ * `@fires position-selected` tag: the manifest and the framework wrappers read
50
+ * events from the registered element, not from this base.
51
+ *
52
+ * Not declared `abstract`: the wrapper generators wrap every `LitElement`
53
+ * subclass and pass its constructor as a concrete `Constructor<T>`, so the
54
+ * default implementations below stand in for abstract members (same as
55
+ * `ObcAbstractAutomationButton`).
56
+ *
57
+ * @property selectedPosition - Zero-based index of the currently selected position. Out-of-range values are clamped.
58
+ * @property vertical - Lays the control out vertically: positions stack top to bottom and the position symbols rotate 90° counter-clockwise to match a vertical flow path.
59
+ * @property ariaLabel - Accessible name for the radio group (the control is icon-only). Concrete components override the default with a device-specific name.
60
+ */
61
+ export class ObcShuffleButtonBase extends LitElement {
62
+ @property({type: Number}) selectedPosition = 1;
63
+
64
+ @property({type: Boolean}) vertical = false;
65
+
66
+ @property({type: String}) override ariaLabel = 'Position selector';
67
+
68
+ @state() private suppressHover = false;
69
+
70
+ protected get positionCount(): number {
71
+ return 1;
72
+ }
73
+
74
+ protected renderPositionIcon(_position: number): TemplateResult {
75
+ return html``;
76
+ }
77
+
78
+ override render() {
79
+ const count = this.positionCount;
80
+ const selected = clampPosition(count, this.selectedPosition);
81
+ return html`
82
+ <div
83
+ class=${classMap({shuffle: true, vertical: this.vertical})}
84
+ role="radiogroup"
85
+ aria-label=${this.ariaLabel}
86
+ aria-orientation=${this.vertical ? 'vertical' : 'horizontal'}
87
+ style="--_shuffle-slot-count: ${shuffleSlotCount(
88
+ count
89
+ )}; --_shuffle-window-offset: ${shuffleWindowOffset(
90
+ count,
91
+ selected
92
+ )};${this.suppressHover ? ' --obc-can-hover: 0;' : ''}"
93
+ @keydown=${this.handleKeydown}
94
+ @pointermove=${this.restoreHover}
95
+ >
96
+ <div class="window">
97
+ <div class="track"></div>
98
+ ${Array.from({length: count}, (_, position) =>
99
+ this.renderThumb(position, selected)
100
+ )}
101
+ </div>
102
+ </div>
103
+ `;
104
+ }
105
+
106
+ private renderThumb(position: number, selected: number) {
107
+ const isSelected = position === selected;
108
+ return html`
109
+ <button
110
+ class=${classMap({thumb: true, selected: isSelected})}
111
+ type="button"
112
+ role="radio"
113
+ aria-checked=${isSelected}
114
+ tabindex=${isSelected ? 0 : -1}
115
+ @click=${() => this.requestPosition(position)}
116
+ >
117
+ <div class="visible-wrapper">
118
+ <div class="icon-container">${this.renderPositionIcon(position)}</div>
119
+ </div>
120
+ </button>
121
+ `;
122
+ }
123
+
124
+ private requestPosition(position: number) {
125
+ if (position === clampPosition(this.positionCount, this.selectedPosition)) {
126
+ return;
127
+ }
128
+ // Browsers keep :hover on the thumb that shuffles away from under a
129
+ // stationary pointer; mute hover feedback until the pointer really moves.
130
+ this.suppressHover = true;
131
+ const event: PositionSelectedEvent = new CustomEvent('position-selected', {
132
+ detail: {position},
133
+ bubbles: true,
134
+ composed: true,
135
+ });
136
+ this.dispatchEvent(event);
137
+ }
138
+
139
+ private restoreHover() {
140
+ this.suppressHover = false;
141
+ }
142
+
143
+ private handleKeydown(event: KeyboardEvent) {
144
+ let delta: number;
145
+ if (event.key === 'ArrowRight' || event.key === 'ArrowDown') {
146
+ delta = 1;
147
+ } else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') {
148
+ delta = -1;
149
+ } else {
150
+ return;
151
+ }
152
+ event.preventDefault();
153
+ const count = this.positionCount;
154
+ const selected = clampPosition(count, this.selectedPosition);
155
+ this.requestPosition((selected + delta + count) % count);
156
+ }
157
+
158
+ protected override updated(changed: PropertyValues) {
159
+ if (
160
+ changed.has('selectedPosition') &&
161
+ this.shadowRoot?.activeElement instanceof HTMLElement
162
+ ) {
163
+ this.shadowRoot
164
+ .querySelector<HTMLButtonElement>('button.thumb.selected')
165
+ ?.focus();
166
+ }
167
+ }
168
+
169
+ static override styles = unsafeCSS(componentStyle);
170
+ }
@@ -0,0 +1,136 @@
1
+ :host {
2
+ display: inline-block;
3
+ }
4
+
5
+ /* ---- Slot grid ----
6
+ * The box is (2n-1) touch-target slots; the selected thumb always sits in the
7
+ * center slot, so the box never changes size with the selection. */
8
+ .shuffle {
9
+ --shuffle-slot-size: var(--global-size-spacing-touch-target-min);
10
+ --_shuffle-slot-count: 1;
11
+ --_shuffle-window-offset: 0;
12
+ position: relative; /* containing block for the sliding .window */
13
+ width: calc(var(--_shuffle-slot-count) * var(--shuffle-slot-size));
14
+ height: var(--shuffle-slot-size);
15
+ }
16
+
17
+ .shuffle.vertical {
18
+ width: var(--shuffle-slot-size);
19
+ height: calc(var(--_shuffle-slot-count) * var(--shuffle-slot-size));
20
+ }
21
+
22
+ /* The window holds the n thumbs in logical order and slides by whole slots so
23
+ * the selected one lands in the center; the box above stays put. */
24
+ .window {
25
+ position: absolute; /* slides inside the fixed-size .shuffle box */
26
+ top: 0;
27
+ left: 0;
28
+ display: flex;
29
+ transform: translateX(
30
+ calc(var(--_shuffle-window-offset) * var(--shuffle-slot-size))
31
+ );
32
+ transition: transform 100ms ease-out; /* TODO(designer): Figma is WIP and specifies no motion */
33
+ }
34
+
35
+ .shuffle.vertical .window {
36
+ flex-direction: column;
37
+ transform: translateY(
38
+ calc(var(--_shuffle-window-offset) * var(--shuffle-slot-size))
39
+ );
40
+ }
41
+
42
+ @media (prefers-reduced-motion: reduce) {
43
+ .window {
44
+ transition: none;
45
+ }
46
+ }
47
+
48
+ /* The track is inset to the visual target height so it aligns with the thumbs'
49
+ * visible wrappers, not with the touch slots. */
50
+ .track {
51
+ @mixin style style=indent noClick=true;
52
+ position: absolute;
53
+ inset: calc(
54
+ (
55
+ var(--shuffle-slot-size) -
56
+ var(--global-size-spacing-visual-target-min)
57
+ ) /
58
+ 2
59
+ )
60
+ 0;
61
+ border-radius: var(--global-border-radius-border-radius-base);
62
+ box-sizing: border-box; /* the mixin border must not grow the track past the slots */
63
+ }
64
+
65
+ .shuffle.vertical .track {
66
+ inset: 0
67
+ calc(
68
+ (
69
+ var(--shuffle-slot-size) -
70
+ var(--global-size-spacing-visual-target-min)
71
+ ) /
72
+ 2
73
+ );
74
+ }
75
+
76
+ /* ---- Thumbs ----
77
+ * Two-layer pattern: the button is the touch target and focus-ring layer, the
78
+ * .visible-wrapper is the visual target. */
79
+ .thumb {
80
+ position: relative;
81
+ width: var(--shuffle-slot-size);
82
+ height: var(--shuffle-slot-size);
83
+ display: flex;
84
+ align-items: center;
85
+ justify-content: center;
86
+ flex-shrink: 0; /* a slot never shrinks below the touch target */
87
+ padding: 0;
88
+ margin: 0;
89
+ border: none;
90
+ background: none;
91
+ cursor: pointer;
92
+ }
93
+
94
+ button.thumb {
95
+ @mixin style style=flat visibleWrapperClass=.visible-wrapper;
96
+ }
97
+
98
+ .visible-wrapper {
99
+ display: flex;
100
+ align-items: center;
101
+ justify-content: center;
102
+ width: 100%;
103
+ height: var(--global-size-spacing-visual-target-min);
104
+ box-sizing: border-box;
105
+ border-radius: var(--global-border-radius-border-radius-base);
106
+ color: var(--automation-device-secondary-color);
107
+ }
108
+
109
+ .thumb.selected {
110
+ cursor: default; /* clicking the selected position is a no-op */
111
+ }
112
+
113
+ .thumb.selected .visible-wrapper {
114
+ background-color: var(--automation-pipe-primary-color);
115
+ border: 1px solid var(--automation-pipe-tertiary-color);
116
+ color: var(--automation-device-tertiary-color);
117
+ }
118
+
119
+ .shuffle.vertical .visible-wrapper {
120
+ width: var(--global-size-spacing-visual-target-min);
121
+ height: 100%;
122
+ }
123
+
124
+ .icon-container {
125
+ width: var(--global-size-spacing-icon-icon-size-regular);
126
+ height: var(--global-size-spacing-icon-icon-size-regular);
127
+ }
128
+
129
+ .shuffle.vertical .icon-container {
130
+ transform: rotate(-90deg); /* symbols are drawn for horizontal flow */
131
+ }
132
+
133
+ .icon-container > * {
134
+ width: 100%;
135
+ height: 100%;
136
+ }
@@ -0,0 +1,44 @@
1
+ import {describe, expect, it} from 'vitest';
2
+ import {
3
+ clampPosition,
4
+ shuffleSlotCount,
5
+ shuffleWindowOffset,
6
+ } from './shuffle-layout.js';
7
+
8
+ describe('shuffleSlotCount', () => {
9
+ it('is 2n-1 so the selected thumb can sit center with all options on one side', () => {
10
+ expect(shuffleSlotCount(1)).toBe(1);
11
+ expect(shuffleSlotCount(2)).toBe(3);
12
+ expect(shuffleSlotCount(3)).toBe(5);
13
+ });
14
+ });
15
+
16
+ describe('clampPosition', () => {
17
+ it('passes through valid positions', () => {
18
+ expect(clampPosition(3, 0)).toBe(0);
19
+ expect(clampPosition(3, 2)).toBe(2);
20
+ });
21
+ it('clamps out-of-range and rounds non-integers', () => {
22
+ expect(clampPosition(3, -1)).toBe(0);
23
+ expect(clampPosition(3, 7)).toBe(2);
24
+ expect(clampPosition(3, 1.4)).toBe(1);
25
+ });
26
+ it('falls back to position 0 for NaN', () => {
27
+ expect(clampPosition(3, Number.NaN)).toBe(0);
28
+ });
29
+ });
30
+
31
+ describe('shuffleWindowOffset', () => {
32
+ it('offsets the window so the selected thumb lands in the center slot', () => {
33
+ expect(shuffleWindowOffset(3, 0)).toBe(2);
34
+ expect(shuffleWindowOffset(3, 1)).toBe(1);
35
+ expect(shuffleWindowOffset(3, 2)).toBe(0);
36
+ expect(shuffleWindowOffset(2, 0)).toBe(1);
37
+ expect(shuffleWindowOffset(2, 1)).toBe(0);
38
+ expect(shuffleWindowOffset(1, 0)).toBe(0);
39
+ });
40
+ it('clamps invalid selections', () => {
41
+ expect(shuffleWindowOffset(3, 9)).toBe(0);
42
+ expect(shuffleWindowOffset(3, -4)).toBe(2);
43
+ });
44
+ });
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Pure layout math for the shuffle-button mechanism: a selector whose selected
3
+ * thumb always occupies the fixed center slot of a (2n-1)-slot row while the
4
+ * remaining option thumbs keep their logical order on either side.
5
+ */
6
+
7
+ export function clampPosition(positionCount: number, position: number): number {
8
+ const rounded = Number.isNaN(position) ? 0 : Math.round(position);
9
+ return Math.min(Math.max(rounded, 0), positionCount - 1);
10
+ }
11
+
12
+ export function shuffleSlotCount(positionCount: number): number {
13
+ return 2 * positionCount - 1;
14
+ }
15
+
16
+ export function shuffleWindowOffset(
17
+ positionCount: number,
18
+ selectedPosition: number
19
+ ): number {
20
+ return positionCount - 1 - clampPosition(positionCount, selectedPosition);
21
+ }