@oicl/openbridge-webcomponents-full-bundle 2.0.0-next.58 → 2.0.0-next.60

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 (213) hide show
  1. package/bundle/openbridge-webcomponents.bundle.js +8144 -5241
  2. package/bundle/openbridge-webcomponents.bundle.js.map +1 -1
  3. package/custom-elements.json +1937 -78
  4. package/dist/alert-severity.d.ts +38 -0
  5. package/dist/alert-severity.d.ts.map +1 -0
  6. package/dist/alert-severity.js +176 -0
  7. package/dist/alert-severity.js.map +1 -0
  8. package/dist/ar/building-blocks/poi-header/poi-header.css.js +38 -7
  9. package/dist/ar/building-blocks/poi-header/poi-header.css.js.map +1 -1
  10. package/dist/ar/building-blocks/poi-header/poi-header.d.ts +7 -2
  11. package/dist/ar/building-blocks/poi-header/poi-header.d.ts.map +1 -1
  12. package/dist/ar/building-blocks/poi-header/poi-header.js +5 -0
  13. package/dist/ar/building-blocks/poi-header/poi-header.js.map +1 -1
  14. package/dist/ar/poi-button/poi-button.css.js +110 -32
  15. package/dist/ar/poi-button/poi-button.css.js.map +1 -1
  16. package/dist/ar/poi-button/poi-button.d.ts +7 -2
  17. package/dist/ar/poi-button/poi-button.d.ts.map +1 -1
  18. package/dist/ar/poi-button/poi-button.js +15 -0
  19. package/dist/ar/poi-button/poi-button.js.map +1 -1
  20. package/dist/ar/types.d.ts +6 -1
  21. package/dist/ar/types.d.ts.map +1 -1
  22. package/dist/ar/types.js +5 -0
  23. package/dist/ar/types.js.map +1 -1
  24. package/dist/automation/automation-badge/automation-badge.d.ts +9 -1
  25. package/dist/automation/automation-badge/automation-badge.d.ts.map +1 -1
  26. package/dist/automation/automation-badge/automation-badge.js +119 -12
  27. package/dist/automation/automation-badge/automation-badge.js.map +1 -1
  28. package/dist/automation/automation-button/abstract-automation-button-storybook-helpers.d.ts +25 -1
  29. package/dist/automation/automation-button/abstract-automation-button-storybook-helpers.d.ts.map +1 -1
  30. package/dist/automation/automation-button/abstract-automation-button-storybook-helpers.js +7 -2
  31. package/dist/automation/automation-button/abstract-automation-button-storybook-helpers.js.map +1 -1
  32. package/dist/automation/automation-button/abstract-automation-button.d.ts +10 -3
  33. package/dist/automation/automation-button/abstract-automation-button.d.ts.map +1 -1
  34. package/dist/automation/automation-button/abstract-automation-button.js +23 -2
  35. package/dist/automation/automation-button/abstract-automation-button.js.map +1 -1
  36. package/dist/automation/automation-button/automation-button.css.js +9 -4
  37. package/dist/automation/automation-button/automation-button.css.js.map +1 -1
  38. package/dist/automation/automation-button/automation-button.d.ts +4 -2
  39. package/dist/automation/automation-button/automation-button.d.ts.map +1 -1
  40. package/dist/automation/automation-button/automation-button.js +24 -6
  41. package/dist/automation/automation-button/automation-button.js.map +1 -1
  42. package/dist/automation/automation-tank/automation-tank.d.ts +3 -2
  43. package/dist/automation/automation-tank/automation-tank.d.ts.map +1 -1
  44. package/dist/automation/automation-tank/automation-tank.js +13 -2
  45. package/dist/automation/automation-tank/automation-tank.js.map +1 -1
  46. package/dist/components/accordion-card/accordion-card.css.js +34 -0
  47. package/dist/components/accordion-card/accordion-card.css.js.map +1 -1
  48. package/dist/components/accordion-card/accordion-card.d.ts +3 -2
  49. package/dist/components/accordion-card/accordion-card.d.ts.map +1 -1
  50. package/dist/components/accordion-card/accordion-card.js +4 -3
  51. package/dist/components/accordion-card/accordion-card.js.map +1 -1
  52. package/dist/components/alert-button/alert-button.css.js +744 -7
  53. package/dist/components/alert-button/alert-button.css.js.map +1 -1
  54. package/dist/components/alert-button/alert-button.d.ts +3 -1
  55. package/dist/components/alert-button/alert-button.d.ts.map +1 -1
  56. package/dist/components/alert-button/alert-button.js +33 -31
  57. package/dist/components/alert-button/alert-button.js.map +1 -1
  58. package/dist/components/alert-frame/alert-frame.css.js +116 -29
  59. package/dist/components/alert-frame/alert-frame.css.js.map +1 -1
  60. package/dist/components/alert-frame/alert-frame.d.ts +42 -15
  61. package/dist/components/alert-frame/alert-frame.d.ts.map +1 -1
  62. package/dist/components/alert-frame/alert-frame.js +68 -17
  63. package/dist/components/alert-frame/alert-frame.js.map +1 -1
  64. package/dist/components/alert-frame/critical-badge.d.ts +12 -0
  65. package/dist/components/alert-frame/critical-badge.d.ts.map +1 -0
  66. package/dist/components/alert-frame/critical-badge.js +47 -0
  67. package/dist/components/alert-frame/critical-badge.js.map +1 -0
  68. package/dist/components/alert-frame/diagnostic-badge.d.ts +12 -0
  69. package/dist/components/alert-frame/diagnostic-badge.d.ts.map +1 -0
  70. package/dist/components/alert-frame/diagnostic-badge.js +60 -0
  71. package/dist/components/alert-frame/diagnostic-badge.js.map +1 -0
  72. package/dist/components/alert-icon/alert-icon.d.ts +6 -1
  73. package/dist/components/alert-icon/alert-icon.d.ts.map +1 -1
  74. package/dist/components/alert-icon/alert-icon.js +96 -38
  75. package/dist/components/alert-icon/alert-icon.js.map +1 -1
  76. package/dist/components/alert-icon/icons/icon-critical.d.ts +3 -0
  77. package/dist/components/alert-icon/icons/icon-critical.d.ts.map +1 -0
  78. package/dist/components/alert-icon/icons/icon-critical.js +26 -0
  79. package/dist/components/alert-icon/icons/icon-critical.js.map +1 -0
  80. package/dist/components/alert-list-details/alert-list-details.d.ts.map +1 -1
  81. package/dist/components/alert-list-details/alert-list-details.js +6 -7
  82. package/dist/components/alert-list-details/alert-list-details.js.map +1 -1
  83. package/dist/components/badge/badge.css.js +50 -2
  84. package/dist/components/badge/badge.css.js.map +1 -1
  85. package/dist/components/badge/badge.d.ts +13 -2
  86. package/dist/components/badge/badge.d.ts.map +1 -1
  87. package/dist/components/badge/badge.js +54 -0
  88. package/dist/components/badge/badge.js.map +1 -1
  89. package/dist/components/navigation-item/navigation-item.css.js +5 -0
  90. package/dist/components/navigation-item/navigation-item.css.js.map +1 -1
  91. package/dist/components/navigation-item/navigation-item.d.ts +18 -0
  92. package/dist/components/navigation-item/navigation-item.d.ts.map +1 -1
  93. package/dist/components/navigation-item/navigation-item.js +49 -1
  94. package/dist/components/navigation-item/navigation-item.js.map +1 -1
  95. package/dist/components/navigation-item-group/navigation-item-group.css.js +8 -0
  96. package/dist/components/navigation-item-group/navigation-item-group.css.js.map +1 -1
  97. package/dist/components/navigation-item-group/navigation-item-group.d.ts +22 -0
  98. package/dist/components/navigation-item-group/navigation-item-group.d.ts.map +1 -1
  99. package/dist/components/navigation-item-group/navigation-item-group.js +63 -1
  100. package/dist/components/navigation-item-group/navigation-item-group.js.map +1 -1
  101. package/dist/components/navigation-menu/navigation-menu.d.ts +29 -1
  102. package/dist/components/navigation-menu/navigation-menu.d.ts.map +1 -1
  103. package/dist/components/navigation-menu/navigation-menu.js +105 -0
  104. package/dist/components/navigation-menu/navigation-menu.js.map +1 -1
  105. package/dist/components/tree-navigation/tree-navigation.css.js +18 -0
  106. package/dist/components/tree-navigation/tree-navigation.css.js.map +1 -0
  107. package/dist/components/tree-navigation/tree-navigation.d.ts +74 -0
  108. package/dist/components/tree-navigation/tree-navigation.d.ts.map +1 -0
  109. package/dist/components/tree-navigation/tree-navigation.js +120 -0
  110. package/dist/components/tree-navigation/tree-navigation.js.map +1 -0
  111. package/dist/components/tree-navigation-group/tree-navigation-group.css.js +22 -0
  112. package/dist/components/tree-navigation-group/tree-navigation-group.css.js.map +1 -0
  113. package/dist/components/tree-navigation-group/tree-navigation-group.d.ts +94 -0
  114. package/dist/components/tree-navigation-group/tree-navigation-group.d.ts.map +1 -0
  115. package/dist/components/tree-navigation-group/tree-navigation-group.js +116 -0
  116. package/dist/components/tree-navigation-group/tree-navigation-group.js.map +1 -0
  117. package/dist/components/tree-navigation-item/tree-navigation-item.css.js +429 -0
  118. package/dist/components/tree-navigation-item/tree-navigation-item.css.js.map +1 -0
  119. package/dist/components/tree-navigation-item/tree-navigation-item.d.ts +160 -0
  120. package/dist/components/tree-navigation-item/tree-navigation-item.d.ts.map +1 -0
  121. package/dist/components/tree-navigation-item/tree-navigation-item.js +208 -0
  122. package/dist/components/tree-navigation-item/tree-navigation-item.js.map +1 -0
  123. package/dist/index.js +2 -1
  124. package/dist/internal/tree-roving-navigator.d.ts +71 -0
  125. package/dist/internal/tree-roving-navigator.d.ts.map +1 -0
  126. package/dist/internal/tree-roving-navigator.js +172 -0
  127. package/dist/internal/tree-roving-navigator.js.map +1 -0
  128. package/dist/manual-icon/icon-alerts-critical-twotone.d.ts +30 -0
  129. package/dist/manual-icon/icon-alerts-critical-twotone.d.ts.map +1 -0
  130. package/dist/manual-icon/icon-alerts-critical-twotone.js +55 -0
  131. package/dist/manual-icon/icon-alerts-critical-twotone.js.map +1 -0
  132. package/dist/manual-icon/icon-alerts-diagnostic-twotone.d.ts +30 -0
  133. package/dist/manual-icon/icon-alerts-diagnostic-twotone.d.ts.map +1 -0
  134. package/dist/manual-icon/icon-alerts-diagnostic-twotone.js +55 -0
  135. package/dist/manual-icon/icon-alerts-diagnostic-twotone.js.map +1 -0
  136. package/dist/navigation-instruments/readout/readout.css.js +3 -18
  137. package/dist/navigation-instruments/readout/readout.css.js.map +1 -1
  138. package/dist/navigation-instruments/readout/readout.d.ts +6 -7
  139. package/dist/navigation-instruments/readout/readout.d.ts.map +1 -1
  140. package/dist/navigation-instruments/readout/readout.js +54 -52
  141. package/dist/navigation-instruments/readout/readout.js.map +1 -1
  142. package/dist/navigation-instruments/readout-list-item/readout-list-item.css.js +9 -36
  143. package/dist/navigation-instruments/readout-list-item/readout-list-item.css.js.map +1 -1
  144. package/dist/navigation-instruments/readout-list-item/readout-list-item.d.ts +8 -8
  145. package/dist/navigation-instruments/readout-list-item/readout-list-item.d.ts.map +1 -1
  146. package/dist/navigation-instruments/readout-list-item/readout-list-item.js +49 -42
  147. package/dist/navigation-instruments/readout-list-item/readout-list-item.js.map +1 -1
  148. package/dist/openbridge.css +155 -0
  149. package/dist/types.d.ts +7 -1
  150. package/dist/types.d.ts.map +1 -1
  151. package/dist/types.js +18 -7
  152. package/dist/types.js.map +1 -1
  153. package/package.json +1 -1
  154. package/src/alert-severity.ts +225 -0
  155. package/src/ar/building-blocks/poi-header/poi-header.css +38 -7
  156. package/src/ar/building-blocks/poi-header/poi-header.ts +6 -1
  157. package/src/ar/poi-button/poi-button.css +126 -32
  158. package/src/ar/poi-button/poi-button.ts +16 -1
  159. package/src/ar/types.ts +5 -0
  160. package/src/automation/automation-badge/automation-badge.ts +124 -17
  161. package/src/automation/automation-button/abstract-automation-button-storybook-helpers.ts +5 -0
  162. package/src/automation/automation-button/abstract-automation-button.ts +21 -3
  163. package/src/automation/automation-button/automation-button.css +9 -4
  164. package/src/automation/automation-button/automation-button.ts +25 -7
  165. package/src/automation/automation-tank/automation-tank.stories.ts +22 -0
  166. package/src/automation/automation-tank/automation-tank.ts +12 -3
  167. package/src/components/accordion-card/accordion-card.css +33 -0
  168. package/src/components/accordion-card/accordion-card.stories.ts +2 -1
  169. package/src/components/accordion-card/accordion-card.ts +3 -4
  170. package/src/components/alert-button/alert-button.css +89 -5
  171. package/src/components/alert-button/alert-button.stories.ts +43 -0
  172. package/src/components/alert-button/alert-button.ts +43 -32
  173. package/src/components/alert-frame/alert-frame.css +116 -29
  174. package/src/components/alert-frame/alert-frame.stories.ts +63 -3
  175. package/src/components/alert-frame/alert-frame.ts +114 -23
  176. package/src/components/alert-frame/critical-badge.ts +39 -0
  177. package/src/components/alert-frame/diagnostic-badge.ts +52 -0
  178. package/src/components/alert-icon/alert-icon.stories.ts +41 -0
  179. package/src/components/alert-icon/alert-icon.ts +101 -38
  180. package/src/components/alert-icon/icons/icon-critical.ts +23 -0
  181. package/src/components/alert-list-details/alert-list-details.stories.ts +67 -0
  182. package/src/components/alert-list-details/alert-list-details.ts +11 -10
  183. package/src/components/badge/badge.css +52 -2
  184. package/src/components/badge/badge.stories.ts +75 -0
  185. package/src/components/badge/badge.ts +70 -2
  186. package/src/components/navigation-item/navigation-item.css +5 -0
  187. package/src/components/navigation-item/navigation-item.ts +56 -1
  188. package/src/components/navigation-item-group/navigation-item-group.css +8 -0
  189. package/src/components/navigation-item-group/navigation-item-group.ts +71 -1
  190. package/src/components/navigation-menu/navigation-menu.stories.ts +54 -0
  191. package/src/components/navigation-menu/navigation-menu.ts +151 -0
  192. package/src/components/tree-navigation/tree-navigation.css +7 -0
  193. package/src/components/tree-navigation/tree-navigation.stories.ts +162 -0
  194. package/src/components/tree-navigation/tree-navigation.ts +188 -0
  195. package/src/components/tree-navigation-group/tree-navigation-group.css +11 -0
  196. package/src/components/tree-navigation-group/tree-navigation-group.ts +157 -0
  197. package/src/components/tree-navigation-item/tree-navigation-item.css +258 -0
  198. package/src/components/tree-navigation-item/tree-navigation-item.stories.ts +119 -0
  199. package/src/components/tree-navigation-item/tree-navigation-item.ts +307 -0
  200. package/src/internal/tree-roving-navigator.ts +231 -0
  201. package/src/main.css +1 -0
  202. package/src/manual-icon/icon-alerts-critical-twotone.ts +60 -0
  203. package/src/manual-icon/icon-alerts-diagnostic-twotone.ts +60 -0
  204. package/src/mixins/alert.css +29 -0
  205. package/src/navigation-instruments/readout/readout.css +3 -24
  206. package/src/navigation-instruments/readout/readout.stories.ts +57 -4
  207. package/src/navigation-instruments/readout/readout.ts +69 -63
  208. package/src/navigation-instruments/readout-list-item/readout-list-item.css +9 -42
  209. package/src/navigation-instruments/readout-list-item/readout-list-item.stories.ts +57 -14
  210. package/src/navigation-instruments/readout-list-item/readout-list-item.ts +54 -44
  211. package/src/palettes/critical.md +95 -0
  212. package/src/palettes/manual.css +165 -0
  213. package/src/types.ts +20 -3
@@ -185,3 +185,8 @@
185
185
  min-width: 0;
186
186
  }
187
187
  }
188
+
189
+ obc-tree-navigation-item.tree {
190
+ display: block;
191
+ width: 100%;
192
+ }
@@ -6,6 +6,12 @@ import {ifDefined} from 'lit/directives/if-defined.js';
6
6
  import '../../icons/icon-arrow-flyout-google.js';
7
7
  import {ObcNavigationMenuVariant} from '../navigation-menu/navigation-menu.js';
8
8
  import {customElement} from '../../decorator.js';
9
+ import '../tree-navigation-item/tree-navigation-item.js';
10
+ import {
11
+ TreeBranchType,
12
+ TreeTerminalType,
13
+ } from '../tree-navigation-item/tree-navigation-item.js';
14
+ import {BadgeType} from '../badge/badge.js';
9
15
 
10
16
  enum NavigationItemRole {
11
17
  Button = 'button',
@@ -142,8 +148,32 @@ export class ObcNavigationItem extends LitElement {
142
148
 
143
149
  @property({type: Boolean}) hasTrailingIcon = false;
144
150
 
151
+ /** Set by `obc-navigation-menu` in its Tree variant — renders the row as a tree item. */
152
+ @property({type: Boolean}) treeMode = false;
153
+
154
+ /** Indentation columns for tree mode, assigned by `obc-navigation-menu`. */
155
+ @property({type: Array}) treeBranches: TreeBranchType[] = [];
156
+
157
+ /**
158
+ * Terminal type for the row in the Tree variant — one of `regular` (default),
159
+ * `aggregated-header`, or `group-header`. Has no effect in the flat variants.
160
+ */
161
+ @property({type: String}) terminalType: string = TreeTerminalType.regular;
162
+
163
+ /** Whether a trailing alert counter badge is shown (Tree variant only). */
164
+ @property({type: Boolean}) hasAlertBadge = false;
165
+
166
+ /** The number shown in the alert badge when `hasAlertBadge` is true (Tree variant only). */
167
+ @property({type: Number}) alertCount = 0;
168
+
169
+ /** The severity/type of the alert badge — one of the `obc-badge` types (Tree variant only). */
170
+ @property({type: String}) alertType: string = BadgeType.alarm;
171
+
145
172
  @query('a') private anchorElement?: HTMLAnchorElement;
146
173
 
174
+ // In tree mode the row renders an `obc-tree-navigation-item` instead of an `<a>`.
175
+ @query('obc-tree-navigation-item') private treeItemElement?: HTMLElement;
176
+
147
177
  /**
148
178
  * Fired when the navigation item is clicked (either as a link or button).
149
179
  * @fires click {CustomEvent<void>}
@@ -165,7 +195,7 @@ export class ObcNavigationItem extends LitElement {
165
195
  }
166
196
 
167
197
  public override focus(options?: FocusOptions): void {
168
- this.anchorElement?.focus(options);
198
+ (this.treeItemElement ?? this.anchorElement)?.focus(options);
169
199
  }
170
200
 
171
201
  private getItemRole(): NavigationItemRole | undefined {
@@ -188,6 +218,31 @@ export class ObcNavigationItem extends LitElement {
188
218
  }
189
219
 
190
220
  override render() {
221
+ if (this.treeMode) {
222
+ // Delegate the whole row to the tree item: it owns focus, keyboard
223
+ // activation, the checked-inert behavior, and href navigation. Forward its
224
+ // activation as this item's own `click` so selection wiring is unchanged.
225
+ return html`
226
+ <obc-tree-navigation-item
227
+ class="tree"
228
+ .label=${this.label}
229
+ .branches=${this.treeBranches}
230
+ ?checked=${this.checked}
231
+ .hasLeadingIcon=${this.hasIcon}
232
+ .href=${this.href}
233
+ .terminalType=${this.terminalType}
234
+ ?hasAlertBadge=${this.hasAlertBadge}
235
+ .alertCount=${this.alertCount}
236
+ .alertType=${this.alertType}
237
+ @click=${this.onClick}
238
+ >
239
+ ${this.hasIcon
240
+ ? html`<slot name="icon" slot="icon"></slot>`
241
+ : nothing}
242
+ </obc-tree-navigation-item>
243
+ `;
244
+ }
245
+
191
246
  const showFlyout =
192
247
  this.group && this.variant !== ObcNavigationMenuVariant.IconOnly;
193
248
  const isCompact = this.variant === ObcNavigationMenuVariant.Compact;
@@ -100,3 +100,11 @@
100
100
  display: flex;
101
101
  flex-direction: column;
102
102
  }
103
+
104
+ [part="children"] {
105
+ display: block;
106
+ }
107
+
108
+ [part="children"][hidden] {
109
+ display: none;
110
+ }
@@ -4,6 +4,12 @@ import compentStyle from './navigation-item-group.css?inline';
4
4
  import {ObcNavigationMenuVariant} from '../navigation-menu/navigation-menu.js';
5
5
  import {classMap} from 'lit/directives/class-map.js';
6
6
  import {customElement} from '../../decorator.js';
7
+ import '../tree-navigation-item/tree-navigation-item.js';
8
+ import {
9
+ TreeBranchType,
10
+ TreeTerminalType,
11
+ } from '../tree-navigation-item/tree-navigation-item.js';
12
+ import {BadgeType} from '../badge/badge.js';
7
13
 
8
14
  /**
9
15
  * `<obc-navigation-item-group>` – A collapsible navigation group component for organizing related navigation items under a single expandable label.
@@ -90,9 +96,47 @@ export class ObcNavigationItemGroup extends LitElement {
90
96
 
91
97
  @property({type: Boolean}) hasIcon = false;
92
98
 
99
+ /** Set by `obc-navigation-menu` in its Tree variant — renders the group as a tree row. */
100
+ @property({type: Boolean}) treeMode = false;
101
+
102
+ /** Indentation columns for tree mode, assigned by `obc-navigation-menu`. */
103
+ @property({type: Array}) treeBranches: TreeBranchType[] = [];
104
+
105
+ /**
106
+ * Terminal type for the group header in the Tree variant — one of `regular`
107
+ * (default), `aggregated-header`, or `group-header`. No effect in flat variants.
108
+ */
109
+ @property({type: String}) terminalType: string = TreeTerminalType.regular;
110
+
111
+ /** Whether a trailing alert counter badge is shown on the header (Tree variant only). */
112
+ @property({type: Boolean}) hasAlertBadge = false;
113
+
114
+ /** The number shown in the header's alert badge when `hasAlertBadge` is true (Tree variant only). */
115
+ @property({type: Number}) alertCount = 0;
116
+
117
+ /** The severity/type of the header's alert badge — one of the `obc-badge` types (Tree variant only). */
118
+ @property({type: String}) alertType: string = BadgeType.alarm;
119
+
120
+ /** Whether the group starts expanded. Useful for trees that open by default. */
121
+ @property({type: Boolean}) defaultOpen = false;
122
+
93
123
  @state() private openContainer = false;
94
124
 
95
- @query('obc-navigation-item') private groupItem?: HTMLElement;
125
+ // Flat mode renders an `obc-navigation-item` header; tree mode renders an
126
+ // `obc-tree-navigation-item`. Match whichever is present so `focus()` works in both.
127
+ @query('obc-navigation-item, obc-tree-navigation-item')
128
+ private groupItem?: HTMLElement;
129
+
130
+ override firstUpdated() {
131
+ if (this.defaultOpen) {
132
+ this.openContainer = true;
133
+ }
134
+ }
135
+
136
+ /** Whether the group is currently open (its children are disclosed). */
137
+ public get expanded(): boolean {
138
+ return this.openContainer;
139
+ }
96
140
 
97
141
  private onClickGroup() {
98
142
  if (this.openContainer) {
@@ -123,6 +167,32 @@ export class ObcNavigationItemGroup extends LitElement {
123
167
  }
124
168
 
125
169
  override render() {
170
+ if (this.treeMode) {
171
+ return html`
172
+ <obc-tree-navigation-item
173
+ part="header"
174
+ .label=${this.label}
175
+ .branches=${this.treeBranches}
176
+ expandable
177
+ ?expanded=${this.openContainer}
178
+ ?checked=${this.checked}
179
+ .hasLeadingIcon=${this.hasIcon}
180
+ .terminalType=${this.terminalType}
181
+ ?hasAlertBadge=${this.hasAlertBadge}
182
+ .alertCount=${this.alertCount}
183
+ .alertType=${this.alertType}
184
+ @expand-toggle=${this.onClickGroup}
185
+ >
186
+ ${this.hasIcon
187
+ ? html`<slot name="icon" slot="icon"></slot>`
188
+ : nothing}
189
+ </obc-tree-navigation-item>
190
+ <div part="children" role="group" ?hidden=${!this.openContainer}>
191
+ <slot></slot>
192
+ </div>
193
+ `;
194
+ }
195
+
126
196
  return html`
127
197
  <obc-navigation-item
128
198
  @click=${this.onClickGroup}
@@ -252,3 +252,57 @@ export const TestDynamicElementsInSpan: Story = {
252
252
  await expect(newItem.variant).toBe(ObcNavigationMenuVariant.Compact);
253
253
  },
254
254
  };
255
+
256
+ export const Tree: Story = {
257
+ args: {
258
+ variant: ObcNavigationMenuVariant.Tree,
259
+ },
260
+ render: (args) => {
261
+ return html`
262
+ <obc-navigation-menu
263
+ .variant=${args.variant}
264
+ style="position: fixed; top: 0; bottom: 0; left: 0;"
265
+ >
266
+ <obc-navigation-item-group
267
+ slot="main"
268
+ label="Vessel"
269
+ hasIcon
270
+ defaultOpen
271
+ >
272
+ <obi-placeholder slot="icon"></obi-placeholder>
273
+ <obc-navigation-item-group
274
+ label="Engine room"
275
+ hasIcon
276
+ defaultOpen
277
+ terminalType="aggregated-header"
278
+ >
279
+ <obi-placeholder slot="icon"></obi-placeholder>
280
+ <obc-navigation-item label="Main engine" hasIcon checked>
281
+ <obi-placeholder slot="icon"></obi-placeholder>
282
+ </obc-navigation-item>
283
+ <obc-navigation-item
284
+ label="Cooling system"
285
+ hasIcon
286
+ hasAlertBadge
287
+ .alertCount=${3}
288
+ >
289
+ <obi-placeholder slot="icon"></obi-placeholder>
290
+ </obc-navigation-item>
291
+ </obc-navigation-item-group>
292
+ <obc-navigation-item-group label="Bridge" hasIcon>
293
+ <obi-placeholder slot="icon"></obi-placeholder>
294
+ <obc-navigation-item label="Radar" hasIcon>
295
+ <obi-placeholder slot="icon"></obi-placeholder>
296
+ </obc-navigation-item>
297
+ <obc-navigation-item label="Autopilot" hasIcon>
298
+ <obi-placeholder slot="icon"></obi-placeholder>
299
+ </obc-navigation-item>
300
+ </obc-navigation-item-group>
301
+ <obc-navigation-item label="Deck" hasIcon>
302
+ <obi-placeholder slot="icon"></obi-placeholder>
303
+ </obc-navigation-item>
304
+ </obc-navigation-item-group>
305
+ </obc-navigation-menu>
306
+ `;
307
+ },
308
+ };
@@ -4,6 +4,29 @@ import compentStyle from './navigation-menu.css?inline';
4
4
  import {ObcNavigationItemGroup} from '../navigation-item-group/navigation-item-group.js';
5
5
  import {ObcNavigationItem} from '../navigation-item/navigation-item.js';
6
6
  import {customElement} from '../../decorator.js';
7
+ import {
8
+ ObcTreeNavigationItem,
9
+ TreeBranchType,
10
+ } from '../tree-navigation-item/tree-navigation-item.js';
11
+ import {
12
+ TreeRovingNavigator,
13
+ TreeRovingAdapter,
14
+ } from '../../internal/tree-roving-navigator.js';
15
+
16
+ /** A nav-menu tree row is either a navigation item or a navigation item group. */
17
+ type NavTreeRow = ObcNavigationItem | ObcNavigationItemGroup;
18
+
19
+ const NAV_ITEM_TAG = 'obc-navigation-item';
20
+ const NAV_GROUP_TAG = 'obc-navigation-item-group';
21
+
22
+ function isNavGroup(el: Element): el is ObcNavigationItemGroup {
23
+ return el.tagName.toLowerCase() === NAV_GROUP_TAG;
24
+ }
25
+
26
+ function isNavRow(el: Element): el is NavTreeRow {
27
+ const tag = el.tagName.toLowerCase();
28
+ return tag === NAV_ITEM_TAG || tag === NAV_GROUP_TAG;
29
+ }
7
30
 
8
31
  /**
9
32
  * `ObcNavigationMenuVariant` – Enumerates the available visual and behavioral variants for `<obc-navigation-menu>`.
@@ -12,6 +35,7 @@ import {customElement} from '../../decorator.js';
12
35
  * - `IconOnly`: Compact menu showing only icons (should only be used when no flyouts/submenus are present).
13
36
  * - `IconOnlyLarge`: Icon-only menu variant designed for use when flyouts/submenus are present.
14
37
  * - `Compact`: Space-saving menu with reduced padding and layout.
38
+ * - `Tree`: Hierarchical tree — groups expand inline and rows are indented by depth.
15
39
  *
16
40
  * Use these variants to adapt the navigation menu to different layouts or device sizes.
17
41
  */
@@ -20,6 +44,7 @@ export enum ObcNavigationMenuVariant {
20
44
  IconOnly = 'icon-only', // Should only be used when no flyouts are present in the navigation menu
21
45
  IconOnlyLarge = 'icon-only-large', // Should be used when flyouts are present in the navigation menu
22
46
  Compact = 'compact',
47
+ Tree = 'tree',
23
48
  }
24
49
 
25
50
  /**
@@ -49,6 +74,11 @@ export enum ObcNavigationMenuFlyoutVariant {
49
74
  * - **IconOnly:** Shows only icons for a compact appearance. *Should only be used when no navigation items have sub-items or flyouts.*
50
75
  * - **IconOnlyLarge:** Icon-only mode that supports flyouts/submenus. Use when navigation contains groups or nested items.
51
76
  * - **Compact:** Reduces padding and overall width for a space-saving layout.
77
+ * - **Tree:** Renders the same `obc-navigation-item` / `obc-navigation-item-group`
78
+ * markup as a hierarchical tree. Groups expand inline (instead of as flyouts),
79
+ * rows are indented by depth, multiple branches can stay open at once, and the
80
+ * tree-row presentation (alert badge, terminal marker) is available via the
81
+ * items' `hasAlertBadge`/`alertCount`/`alertType`/`terminalType` properties.
52
82
  * - **Responsive Layout:**
53
83
  * - `smallScreen` property adapts the footer and logo layout for smaller viewports.
54
84
  * - **Slot-based Content:**
@@ -71,6 +101,10 @@ export enum ObcNavigationMenuFlyoutVariant {
71
101
  * - Use the `Full` variant for standard navigation with both icons and labels.
72
102
  * - Use `IconOnly` only when there are no nested groups or flyouts; otherwise, use `IconOnlyLarge` for icon-only navigation with flyout support.
73
103
  * - The `Compact` variant is suitable for layouts with limited space or when a minimal navigation appearance is desired.
104
+ * - Use the `Tree` variant for hierarchical navigation (file trees, nested
105
+ * sections). The existing item/group markup is reused unchanged — only the
106
+ * `variant` changes. Footer and logo slots remain flat. Mark a group with
107
+ * `defaultOpen` to have it start expanded.
74
108
  * - Set `smallScreen` to `true` to optimize the layout for smaller devices or responsive breakpoints.
75
109
  *
76
110
  * **TODO(designer):** Provide additional guidance on when to use each variant and recommended slot content for best usability.
@@ -129,6 +163,7 @@ export class ObcNavigationMenu extends LitElement {
129
163
  * - `icon-only`: Compact, icon-only menu (use only when no flyouts/groups are present).
130
164
  * - `icon-only-large`: Icon-only menu supporting flyouts/groups.
131
165
  * - `compact`: Minimal, space-saving menu.
166
+ * - `tree`: Hierarchical tree — groups expand inline and rows are indented by depth.
132
167
  */
133
168
  @property({type: String}) variant: ObcNavigationMenuVariant =
134
169
  ObcNavigationMenuVariant.Full;
@@ -148,6 +183,59 @@ export class ObcNavigationMenu extends LitElement {
148
183
  private slotObservers: MutationObserver[] = [];
149
184
  @state() private hasFooter = false;
150
185
 
186
+ /**
187
+ * Roving-tabindex + arrow-key navigation for the Tree variant, sharing the
188
+ * navigator that drives `obc-tree-navigation`. Engaged only while
189
+ * `variant === Tree` (see `onTreeKeydown`).
190
+ */
191
+ private readonly treeNavigator = new TreeRovingNavigator<NavTreeRow>(this, {
192
+ getRows: () => this.treeRootRows(),
193
+ childRows: (row) => this.treeChildRows(row),
194
+ isGroup: (row) => isNavGroup(row),
195
+ // Read the group's own synchronous open state, not the shadow header's
196
+ // attribute (which lags a render tick behind a keyboard expand/collapse).
197
+ isExpanded: (row) => isNavGroup(row) && row.expanded,
198
+ setExpanded: (row, expanded) => {
199
+ if (!isNavGroup(row)) return;
200
+ if (expanded) row.open();
201
+ else row.close();
202
+ },
203
+ innerItem: (row) => this.treeInnerItem(row),
204
+ } satisfies TreeRovingAdapter<NavTreeRow>);
205
+
206
+ /** Top-level tree rows of the main slot (mirrors `assignTreeBranches`'s filter). */
207
+ private treeRootRows(): NavTreeRow[] {
208
+ return Array.from(this.children).filter((child): child is NavTreeRow => {
209
+ if (!isNavRow(child)) return false;
210
+ const slot = child.getAttribute('slot');
211
+ return slot === null || slot === 'main';
212
+ });
213
+ }
214
+
215
+ /** Direct tree-row children of a row, in document order. */
216
+ private treeChildRows(row: NavTreeRow): NavTreeRow[] {
217
+ return Array.from(row.children).filter(isNavRow);
218
+ }
219
+
220
+ /** The inline `obc-tree-navigation-item` a tree-mode row renders in its shadow root. */
221
+ private treeInnerItem(row: NavTreeRow): ObcTreeNavigationItem | null {
222
+ return (
223
+ row.shadowRoot?.querySelector<ObcTreeNavigationItem>(
224
+ 'obc-tree-navigation-item'
225
+ ) ?? null
226
+ );
227
+ }
228
+
229
+ private onTreeKeydown = (event: KeyboardEvent): void => {
230
+ if (this.variant !== ObcNavigationMenuVariant.Tree) return;
231
+ if (this.treeNavigator.handleKeydown(event)) event.preventDefault();
232
+ };
233
+
234
+ override connectedCallback(): void {
235
+ super.connectedCallback();
236
+ this.addEventListener('keydown', this.onTreeKeydown);
237
+ }
238
+
151
239
  findAllElements<T extends Element>(
152
240
  el: Element,
153
241
  tag: string,
@@ -212,6 +300,7 @@ export class ObcNavigationMenu extends LitElement {
212
300
  registerGroup(groups: ObcNavigationItemGroup[]) {
213
301
  groups.forEach((group) => {
214
302
  group.addEventListener('open', () => {
303
+ if (this.variant === ObcNavigationMenuVariant.Tree) return;
215
304
  groups.forEach((g) => {
216
305
  if (g !== group) {
217
306
  g.close();
@@ -296,6 +385,7 @@ export class ObcNavigationMenu extends LitElement {
296
385
  override disconnectedCallback(): void {
297
386
  super.disconnectedCallback();
298
387
  this.cleanupSlotObservers();
388
+ this.removeEventListener('keydown', this.onTreeKeydown);
299
389
  }
300
390
 
301
391
  private handleSlotChange() {
@@ -303,7 +393,68 @@ export class ObcNavigationMenu extends LitElement {
303
393
  this.setupSlotObservers();
304
394
  }
305
395
 
396
+ private assignTreeBranches(el: Element, depth: number): void {
397
+ for (const child of el.children) {
398
+ const tag = child.tagName.toLowerCase();
399
+ const isItem = tag === 'obc-navigation-item';
400
+ const isGroup = tag === 'obc-navigation-item-group';
401
+ if (!isItem && !isGroup) continue;
402
+ // Top-level rows are slotted; only treeify the main slot. Nested rows have
403
+ // no slot attribute and always belong to their group's tree.
404
+ if (depth === 0) {
405
+ const slot = child.getAttribute('slot');
406
+ if (slot !== null && slot !== 'main') continue;
407
+ }
408
+ const branches: TreeBranchType[] = Array.from(
409
+ {length: depth},
410
+ () => TreeBranchType.blank
411
+ );
412
+ const row = child as ObcNavigationItem | ObcNavigationItemGroup;
413
+ row.treeMode = true;
414
+ row.treeBranches = branches;
415
+ if (isGroup) {
416
+ this.assignTreeBranches(child, depth + 1);
417
+ }
418
+ }
419
+ }
420
+
421
+ // Reset tree mode on every descendant item/group so switching away from the
422
+ // Tree variant restores the flat rendering.
423
+ private clearTreeMode(el: Element): void {
424
+ for (const child of el.children) {
425
+ const tag = child.tagName.toLowerCase();
426
+ const isItem = tag === 'obc-navigation-item';
427
+ const isGroup = tag === 'obc-navigation-item-group';
428
+ if (!isItem && !isGroup) continue;
429
+ const row = child as ObcNavigationItem | ObcNavigationItemGroup;
430
+ row.treeMode = false;
431
+ row.treeBranches = [];
432
+ if (isGroup) {
433
+ this.clearTreeMode(child);
434
+ }
435
+ }
436
+ }
437
+
306
438
  private setupItems() {
439
+ if (this.variant === ObcNavigationMenuVariant.Tree) {
440
+ this.assignTreeBranches(this, 0);
441
+ // Footer and logo are not part of the tree — keep them as flat full-variant
442
+ // items (and clear any tree mode left over from the main slot's walk).
443
+ (['footer', 'logo'] as const).forEach((slot) => {
444
+ this.findAllItems(this, slot).forEach((item) => {
445
+ item.treeMode = false;
446
+ item.treeBranches = [];
447
+ item.variant = ObcNavigationMenuVariant.Full;
448
+ });
449
+ });
450
+ // Defer so the rows' inline tree-item headers exist before the roving
451
+ // tabindex is assigned to them.
452
+ queueMicrotask(() => this.treeNavigator.refresh());
453
+ return;
454
+ }
455
+
456
+ this.clearTreeMode(this);
457
+
307
458
  const hug =
308
459
  this.variant !== ObcNavigationMenuVariant.Full ||
309
460
  this.flyoutVariant === ObcNavigationMenuFlyoutVariant.Compact;
@@ -0,0 +1,7 @@
1
+ :host {
2
+ display: block;
3
+ }
4
+
5
+ [role="tree"] {
6
+ display: block;
7
+ }
@@ -0,0 +1,162 @@
1
+ import type {Meta, StoryObj} from '@storybook/web-components-vite';
2
+ import {ObcTreeNavigation} from './tree-navigation.js';
3
+ import './tree-navigation.js';
4
+ import '../tree-navigation-group/tree-navigation-group.js';
5
+ import '../tree-navigation-item/tree-navigation-item.js';
6
+ import {TreeTerminalType} from '../tree-navigation-item/tree-navigation-item.js';
7
+ import {iconIdToIconHtml} from '../../storybook-util.js';
8
+ import {LitElement, html} from 'lit';
9
+ import {state} from 'lit/decorators.js';
10
+ import {customElement} from '../../decorator.js';
11
+
12
+ /**
13
+ * `<obc-tree-navigation>` builds the guide lines automatically from the nested
14
+ * markup. Write groups and items as real nested elements; the container assigns
15
+ * each row's `branches` from its position. Click a group header to expand or
16
+ * collapse — the guides recompute to match.
17
+ */
18
+ const meta: Meta<typeof ObcTreeNavigation> = {
19
+ title: 'UI Components/Menus and Navigation/Tree Navigation',
20
+ tags: ['autodocs', '6.0'],
21
+ component: 'obc-tree-navigation',
22
+ render: () => html`
23
+ <obc-tree-navigation>
24
+ <obc-tree-navigation-group label="Vessel" expanded>
25
+ ${iconIdToIconHtml('placeholder', {slot: 'icon'})}
26
+ <obc-tree-navigation-group
27
+ label="Engine room"
28
+ expanded
29
+ .terminalType=${TreeTerminalType.aggregatedHeader}
30
+ >
31
+ ${iconIdToIconHtml('placeholder', {slot: 'icon'})}
32
+ <obc-tree-navigation-item label="Main engine" checked>
33
+ ${iconIdToIconHtml('placeholder', {slot: 'icon'})}
34
+ </obc-tree-navigation-item>
35
+ <obc-tree-navigation-group
36
+ label="Auxiliary engine"
37
+ expanded
38
+ hasAlertBadge
39
+ .alertCount=${3}
40
+ >
41
+ ${iconIdToIconHtml('placeholder', {slot: 'icon'})}
42
+ <obc-tree-navigation-item label="Fuel pump">
43
+ ${iconIdToIconHtml('placeholder', {slot: 'icon'})}
44
+ </obc-tree-navigation-item>
45
+ <obc-tree-navigation-item
46
+ label="Starter"
47
+ hasAlertBadge
48
+ .alertCount=${1}
49
+ >
50
+ ${iconIdToIconHtml('placeholder', {slot: 'icon'})}
51
+ </obc-tree-navigation-item>
52
+ <obc-tree-navigation-item label="Oil filter">
53
+ ${iconIdToIconHtml('placeholder', {slot: 'icon'})}
54
+ </obc-tree-navigation-item>
55
+ </obc-tree-navigation-group>
56
+ <obc-tree-navigation-group label="Cooling system" expanded>
57
+ ${iconIdToIconHtml('placeholder', {slot: 'icon'})}
58
+ <obc-tree-navigation-item label="Seawater pump">
59
+ ${iconIdToIconHtml('placeholder', {slot: 'icon'})}
60
+ </obc-tree-navigation-item>
61
+ </obc-tree-navigation-group>
62
+ </obc-tree-navigation-group>
63
+ <obc-tree-navigation-group label="Bridge" expanded>
64
+ ${iconIdToIconHtml('placeholder', {slot: 'icon'})}
65
+ <obc-tree-navigation-item label="Radar">
66
+ ${iconIdToIconHtml('placeholder', {slot: 'icon'})}
67
+ </obc-tree-navigation-item>
68
+ <obc-tree-navigation-item label="Autopilot" disabled>
69
+ ${iconIdToIconHtml('placeholder', {slot: 'icon'})}
70
+ </obc-tree-navigation-item>
71
+ <obc-tree-navigation-item label="ECDIS">
72
+ ${iconIdToIconHtml('placeholder', {slot: 'icon'})}
73
+ </obc-tree-navigation-item>
74
+ </obc-tree-navigation-group>
75
+ <obc-tree-navigation-item label="Deck">
76
+ ${iconIdToIconHtml('placeholder', {slot: 'icon'})}
77
+ </obc-tree-navigation-item>
78
+ </obc-tree-navigation-group>
79
+ <obc-tree-navigation-item label="Shore power">
80
+ ${iconIdToIconHtml('placeholder', {slot: 'icon'})}
81
+ </obc-tree-navigation-item>
82
+ </obc-tree-navigation>
83
+ `,
84
+ } satisfies Meta<ObcTreeNavigation>;
85
+
86
+ export default meta;
87
+ type Story = StoryObj<ObcTreeNavigation>;
88
+
89
+ /**
90
+ * A fully expanded tree. The guide lines — pass-through verticals, intersections,
91
+ * and last-child corners — are all computed by the container from the nested
92
+ * markup. The header rows are groups; the leaves are items.
93
+ */
94
+ export const Default: Story = {};
95
+
96
+ /**
97
+ * `<tree-navigation-selection-demo>` – Wraps the tree to track which leaf is
98
+ * selected. Selection is purely consumer state: it sets `checked` on the chosen
99
+ * item; the container still owns all guide-line logic.
100
+ *
101
+ * Not exported: Storybook treats every module export as a story, so the demo
102
+ * element is kept file-local and registered only via the `@customElement` side
103
+ * effect.
104
+ */
105
+ @customElement('tree-navigation-selection-demo')
106
+ class TreeNavigationSelectionDemo extends LitElement {
107
+ @state() private selectedLabel = 'Main engine';
108
+
109
+ override createRenderRoot() {
110
+ return this;
111
+ }
112
+
113
+ private select(label: string) {
114
+ this.selectedLabel = label;
115
+ }
116
+
117
+ private leaf(label: string) {
118
+ return html`<obc-tree-navigation-item
119
+ label=${label}
120
+ ?checked=${this.selectedLabel === label}
121
+ @click=${() => this.select(label)}
122
+ >
123
+ ${iconIdToIconHtml('placeholder', {slot: 'icon'})}
124
+ </obc-tree-navigation-item>`;
125
+ }
126
+
127
+ override render() {
128
+ return html`
129
+ <obc-tree-navigation>
130
+ <obc-tree-navigation-group label="Vessel" expanded>
131
+ ${iconIdToIconHtml('placeholder', {slot: 'icon'})}
132
+ <obc-tree-navigation-group label="Engine room" expanded>
133
+ ${iconIdToIconHtml('placeholder', {slot: 'icon'})}
134
+ ${this.leaf('Main engine')} ${this.leaf('Auxiliary engine')}
135
+ ${this.leaf('Cooling system')}
136
+ </obc-tree-navigation-group>
137
+ <obc-tree-navigation-group label="Bridge" expanded>
138
+ ${iconIdToIconHtml('placeholder', {slot: 'icon'})}
139
+ ${this.leaf('Radar')} ${this.leaf('Autopilot')}
140
+ </obc-tree-navigation-group>
141
+ ${this.leaf('Deck')}
142
+ </obc-tree-navigation-group>
143
+ </obc-tree-navigation>
144
+ `;
145
+ }
146
+ }
147
+
148
+ declare global {
149
+ interface HTMLElementTagNameMap {
150
+ 'tree-navigation-selection-demo': TreeNavigationSelectionDemo;
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Click a leaf to select it; click a group chevron to expand or collapse. The
156
+ * container keeps the guide lines correct through every change — the consumer
157
+ * only tracks the selected item.
158
+ */
159
+ export const Interactive: Story = {
160
+ render: () =>
161
+ html`<tree-navigation-selection-demo></tree-navigation-selection-demo>`,
162
+ };