@aquera/nile-elements 1.6.6 → 1.6.8

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 (231) hide show
  1. package/README.md +7 -0
  2. package/dist/index.cjs.js +1 -1
  3. package/dist/index.esm.js +1 -1
  4. package/dist/index.js +1735 -489
  5. package/dist/internal/enum.cjs.js +1 -1
  6. package/dist/internal/enum.cjs.js.map +1 -1
  7. package/dist/internal/enum.esm.js +1 -1
  8. package/dist/nile-inline-sidebar/index.cjs.js +1 -1
  9. package/dist/nile-inline-sidebar/index.esm.js +1 -1
  10. package/dist/nile-inline-sidebar/nile-inline-sidebar.cjs.js +1 -1
  11. package/dist/nile-inline-sidebar/nile-inline-sidebar.cjs.js.map +1 -1
  12. package/dist/nile-inline-sidebar/nile-inline-sidebar.css.cjs.js +1 -1
  13. package/dist/nile-inline-sidebar/nile-inline-sidebar.css.cjs.js.map +1 -1
  14. package/dist/nile-inline-sidebar/nile-inline-sidebar.css.esm.js +76 -19
  15. package/dist/nile-inline-sidebar/nile-inline-sidebar.esm.js +42 -23
  16. package/dist/nile-inline-sidebar-group/nile-inline-sidebar-group.css.cjs.js +1 -1
  17. package/dist/nile-inline-sidebar-group/nile-inline-sidebar-group.css.cjs.js.map +1 -1
  18. package/dist/nile-inline-sidebar-group/nile-inline-sidebar-group.css.esm.js +8 -9
  19. package/dist/nile-inline-sidebar-item/index.cjs.js +1 -1
  20. package/dist/nile-inline-sidebar-item/index.esm.js +1 -1
  21. package/dist/nile-inline-sidebar-item/nile-inline-sidebar-item.cjs.js +1 -1
  22. package/dist/nile-inline-sidebar-item/nile-inline-sidebar-item.cjs.js.map +1 -1
  23. package/dist/nile-inline-sidebar-item/nile-inline-sidebar-item.css.cjs.js +1 -1
  24. package/dist/nile-inline-sidebar-item/nile-inline-sidebar-item.css.cjs.js.map +1 -1
  25. package/dist/nile-inline-sidebar-item/nile-inline-sidebar-item.css.esm.js +16 -18
  26. package/dist/nile-inline-sidebar-item/nile-inline-sidebar-item.esm.js +22 -4
  27. package/dist/nile-inline-sidebar-item-body/index.cjs.js +2 -0
  28. package/dist/nile-inline-sidebar-item-body/index.cjs.js.map +1 -0
  29. package/dist/nile-inline-sidebar-item-body/index.esm.js +1 -0
  30. package/dist/nile-inline-sidebar-item-body/nile-inline-sidebar-item-body.cjs.js +2 -0
  31. package/dist/nile-inline-sidebar-item-body/nile-inline-sidebar-item-body.cjs.js.map +1 -0
  32. package/dist/nile-inline-sidebar-item-body/nile-inline-sidebar-item-body.css.cjs.js +2 -0
  33. package/dist/nile-inline-sidebar-item-body/nile-inline-sidebar-item-body.css.cjs.js.map +1 -0
  34. package/dist/nile-inline-sidebar-item-body/nile-inline-sidebar-item-body.css.esm.js +53 -0
  35. package/dist/nile-inline-sidebar-item-body/nile-inline-sidebar-item-body.esm.js +13 -0
  36. package/dist/nile-inline-sidebar-item-header/index.cjs.js +2 -0
  37. package/dist/nile-inline-sidebar-item-header/index.cjs.js.map +1 -0
  38. package/dist/nile-inline-sidebar-item-header/index.esm.js +1 -0
  39. package/dist/nile-inline-sidebar-item-header/nile-inline-sidebar-item-header.cjs.js +2 -0
  40. package/dist/nile-inline-sidebar-item-header/nile-inline-sidebar-item-header.cjs.js.map +1 -0
  41. package/dist/nile-inline-sidebar-item-header/nile-inline-sidebar-item-header.css.cjs.js +2 -0
  42. package/dist/nile-inline-sidebar-item-header/nile-inline-sidebar-item-header.css.cjs.js.map +1 -0
  43. package/dist/nile-inline-sidebar-item-header/nile-inline-sidebar-item-header.css.esm.js +27 -0
  44. package/dist/nile-inline-sidebar-item-header/nile-inline-sidebar-item-header.esm.js +16 -0
  45. package/dist/nile-inline-sidebar-panel/index.cjs.js +2 -0
  46. package/dist/nile-inline-sidebar-panel/index.cjs.js.map +1 -0
  47. package/dist/nile-inline-sidebar-panel/index.esm.js +1 -0
  48. package/dist/nile-inline-sidebar-panel/nile-inline-sidebar-panel.cjs.js +2 -0
  49. package/dist/nile-inline-sidebar-panel/nile-inline-sidebar-panel.cjs.js.map +1 -0
  50. package/dist/nile-inline-sidebar-panel/nile-inline-sidebar-panel.css.cjs.js +2 -0
  51. package/dist/nile-inline-sidebar-panel/nile-inline-sidebar-panel.css.cjs.js.map +1 -0
  52. package/dist/nile-inline-sidebar-panel/nile-inline-sidebar-panel.css.esm.js +19 -0
  53. package/dist/nile-inline-sidebar-panel/nile-inline-sidebar-panel.esm.js +5 -0
  54. package/dist/nile-inline-sidebar-panel-group/index.cjs.js +2 -0
  55. package/dist/nile-inline-sidebar-panel-group/index.cjs.js.map +1 -0
  56. package/dist/nile-inline-sidebar-panel-group/index.esm.js +1 -0
  57. package/dist/nile-inline-sidebar-panel-group/nile-inline-sidebar-panel-group.cjs.js +2 -0
  58. package/dist/nile-inline-sidebar-panel-group/nile-inline-sidebar-panel-group.cjs.js.map +1 -0
  59. package/dist/nile-inline-sidebar-panel-group/nile-inline-sidebar-panel-group.css.cjs.js +2 -0
  60. package/dist/nile-inline-sidebar-panel-group/nile-inline-sidebar-panel-group.css.cjs.js.map +1 -0
  61. package/dist/nile-inline-sidebar-panel-group/nile-inline-sidebar-panel-group.css.esm.js +30 -0
  62. package/dist/nile-inline-sidebar-panel-group/nile-inline-sidebar-panel-group.esm.js +12 -0
  63. package/dist/nile-nav-tab/index.cjs.js +2 -0
  64. package/dist/nile-nav-tab/index.cjs.js.map +1 -0
  65. package/dist/nile-nav-tab/index.esm.js +1 -0
  66. package/dist/nile-nav-tab/nile-nav-tab.cjs.js +2 -0
  67. package/dist/nile-nav-tab/nile-nav-tab.cjs.js.map +1 -0
  68. package/dist/nile-nav-tab/nile-nav-tab.css.cjs.js +2 -0
  69. package/dist/nile-nav-tab/nile-nav-tab.css.cjs.js.map +1 -0
  70. package/dist/nile-nav-tab/nile-nav-tab.css.esm.js +190 -0
  71. package/dist/nile-nav-tab/nile-nav-tab.esm.js +36 -0
  72. package/dist/nile-nav-tab-group/index.cjs.js +2 -0
  73. package/dist/nile-nav-tab-group/index.cjs.js.map +1 -0
  74. package/dist/nile-nav-tab-group/index.esm.js +1 -0
  75. package/dist/nile-nav-tab-group/nile-nav-tab-group.cjs.js +4 -0
  76. package/dist/nile-nav-tab-group/nile-nav-tab-group.cjs.js.map +1 -0
  77. package/dist/nile-nav-tab-group/nile-nav-tab-group.css.cjs.js +2 -0
  78. package/dist/nile-nav-tab-group/nile-nav-tab-group.css.cjs.js.map +1 -0
  79. package/dist/nile-nav-tab-group/nile-nav-tab-group.css.esm.js +668 -0
  80. package/dist/nile-nav-tab-group/nile-nav-tab-group.esm.js +61 -0
  81. package/dist/nile-nav-tab-panel/index.cjs.js +2 -0
  82. package/dist/nile-nav-tab-panel/index.cjs.js.map +1 -0
  83. package/dist/nile-nav-tab-panel/index.esm.js +1 -0
  84. package/dist/nile-nav-tab-panel/nile-nav-tab-panel.cjs.js +2 -0
  85. package/dist/nile-nav-tab-panel/nile-nav-tab-panel.cjs.js.map +1 -0
  86. package/dist/nile-nav-tab-panel/nile-nav-tab-panel.css.cjs.js +2 -0
  87. package/dist/nile-nav-tab-panel/nile-nav-tab-panel.css.cjs.js.map +1 -0
  88. package/dist/nile-nav-tab-panel/nile-nav-tab-panel.css.esm.js +22 -0
  89. package/dist/nile-nav-tab-panel/nile-nav-tab-panel.esm.js +8 -0
  90. package/dist/nile-rich-text-editor/nile-rich-text-editor.cjs.js +1 -1
  91. package/dist/nile-rich-text-editor/nile-rich-text-editor.cjs.js.map +1 -1
  92. package/dist/nile-rich-text-editor/nile-rich-text-editor.css.cjs.js +1 -1
  93. package/dist/nile-rich-text-editor/nile-rich-text-editor.css.cjs.js.map +1 -1
  94. package/dist/nile-rich-text-editor/nile-rich-text-editor.css.esm.js +5 -2
  95. package/dist/nile-rich-text-editor/nile-rich-text-editor.esm.js +1 -1
  96. package/dist/src/index.d.ts +7 -0
  97. package/dist/src/index.js +7 -0
  98. package/dist/src/index.js.map +1 -1
  99. package/dist/src/internal/enum.d.ts +6 -0
  100. package/dist/src/internal/enum.js +7 -0
  101. package/dist/src/internal/enum.js.map +1 -1
  102. package/dist/src/nile-inline-sidebar/nile-inline-sidebar.css.d.ts +1 -0
  103. package/dist/src/nile-inline-sidebar/nile-inline-sidebar.css.js +75 -17
  104. package/dist/src/nile-inline-sidebar/nile-inline-sidebar.css.js.map +1 -1
  105. package/dist/src/nile-inline-sidebar/nile-inline-sidebar.d.ts +17 -1
  106. package/dist/src/nile-inline-sidebar/nile-inline-sidebar.js +147 -18
  107. package/dist/src/nile-inline-sidebar/nile-inline-sidebar.js.map +1 -1
  108. package/dist/src/nile-inline-sidebar-group/nile-inline-sidebar-group.css.d.ts +1 -0
  109. package/dist/src/nile-inline-sidebar-group/nile-inline-sidebar-group.css.js +7 -7
  110. package/dist/src/nile-inline-sidebar-group/nile-inline-sidebar-group.css.js.map +1 -1
  111. package/dist/src/nile-inline-sidebar-item/nile-inline-sidebar-item.css.d.ts +1 -1
  112. package/dist/src/nile-inline-sidebar-item/nile-inline-sidebar-item.css.js +15 -17
  113. package/dist/src/nile-inline-sidebar-item/nile-inline-sidebar-item.css.js.map +1 -1
  114. package/dist/src/nile-inline-sidebar-item/nile-inline-sidebar-item.d.ts +7 -1
  115. package/dist/src/nile-inline-sidebar-item/nile-inline-sidebar-item.js +63 -4
  116. package/dist/src/nile-inline-sidebar-item/nile-inline-sidebar-item.js.map +1 -1
  117. package/dist/src/nile-inline-sidebar-item-body/index.d.ts +1 -0
  118. package/dist/src/nile-inline-sidebar-item-body/index.js +2 -0
  119. package/dist/src/nile-inline-sidebar-item-body/index.js.map +1 -0
  120. package/dist/src/nile-inline-sidebar-item-body/nile-inline-sidebar-item-body.css.d.ts +9 -0
  121. package/dist/src/nile-inline-sidebar-item-body/nile-inline-sidebar-item-body.css.js +62 -0
  122. package/dist/src/nile-inline-sidebar-item-body/nile-inline-sidebar-item-body.css.js.map +1 -0
  123. package/dist/src/nile-inline-sidebar-item-body/nile-inline-sidebar-item-body.d.ts +45 -0
  124. package/dist/src/nile-inline-sidebar-item-body/nile-inline-sidebar-item-body.js +110 -0
  125. package/dist/src/nile-inline-sidebar-item-body/nile-inline-sidebar-item-body.js.map +1 -0
  126. package/dist/src/nile-inline-sidebar-item-header/index.d.ts +1 -0
  127. package/dist/src/nile-inline-sidebar-item-header/index.js +2 -0
  128. package/dist/src/nile-inline-sidebar-item-header/index.js.map +1 -0
  129. package/dist/src/nile-inline-sidebar-item-header/nile-inline-sidebar-item-header.css.d.ts +9 -0
  130. package/dist/src/nile-inline-sidebar-item-header/nile-inline-sidebar-item-header.css.js +36 -0
  131. package/dist/src/nile-inline-sidebar-item-header/nile-inline-sidebar-item-header.css.js.map +1 -0
  132. package/dist/src/nile-inline-sidebar-item-header/nile-inline-sidebar-item-header.d.ts +34 -0
  133. package/dist/src/nile-inline-sidebar-item-header/nile-inline-sidebar-item-header.js +68 -0
  134. package/dist/src/nile-inline-sidebar-item-header/nile-inline-sidebar-item-header.js.map +1 -0
  135. package/dist/src/nile-inline-sidebar-panel/index.d.ts +1 -0
  136. package/dist/src/nile-inline-sidebar-panel/index.js +2 -0
  137. package/dist/src/nile-inline-sidebar-panel/index.js.map +1 -0
  138. package/dist/src/nile-inline-sidebar-panel/nile-inline-sidebar-panel.css.d.ts +9 -0
  139. package/dist/src/nile-inline-sidebar-panel/nile-inline-sidebar-panel.css.js +28 -0
  140. package/dist/src/nile-inline-sidebar-panel/nile-inline-sidebar-panel.css.js.map +1 -0
  141. package/dist/src/nile-inline-sidebar-panel/nile-inline-sidebar-panel.d.ts +35 -0
  142. package/dist/src/nile-inline-sidebar-panel/nile-inline-sidebar-panel.js +55 -0
  143. package/dist/src/nile-inline-sidebar-panel/nile-inline-sidebar-panel.js.map +1 -0
  144. package/dist/src/nile-inline-sidebar-panel-group/index.d.ts +1 -0
  145. package/dist/src/nile-inline-sidebar-panel-group/index.js +2 -0
  146. package/dist/src/nile-inline-sidebar-panel-group/index.js.map +1 -0
  147. package/dist/src/nile-inline-sidebar-panel-group/nile-inline-sidebar-panel-group.css.d.ts +9 -0
  148. package/dist/src/nile-inline-sidebar-panel-group/nile-inline-sidebar-panel-group.css.js +39 -0
  149. package/dist/src/nile-inline-sidebar-panel-group/nile-inline-sidebar-panel-group.css.js.map +1 -0
  150. package/dist/src/nile-inline-sidebar-panel-group/nile-inline-sidebar-panel-group.d.ts +43 -0
  151. package/dist/src/nile-inline-sidebar-panel-group/nile-inline-sidebar-panel-group.js +93 -0
  152. package/dist/src/nile-inline-sidebar-panel-group/nile-inline-sidebar-panel-group.js.map +1 -0
  153. package/dist/src/nile-nav-tab/index.d.ts +1 -0
  154. package/dist/src/nile-nav-tab/index.js +2 -0
  155. package/dist/src/nile-nav-tab/index.js.map +1 -0
  156. package/dist/src/nile-nav-tab/nile-nav-tab.css.d.ts +9 -0
  157. package/dist/src/nile-nav-tab/nile-nav-tab.css.js +199 -0
  158. package/dist/src/nile-nav-tab/nile-nav-tab.css.js.map +1 -0
  159. package/dist/src/nile-nav-tab/nile-nav-tab.d.ts +50 -0
  160. package/dist/src/nile-nav-tab/nile-nav-tab.js +190 -0
  161. package/dist/src/nile-nav-tab/nile-nav-tab.js.map +1 -0
  162. package/dist/src/nile-nav-tab/nile-nav-tab.test.d.ts +1 -0
  163. package/dist/src/nile-nav-tab/nile-nav-tab.test.js +656 -0
  164. package/dist/src/nile-nav-tab/nile-nav-tab.test.js.map +1 -0
  165. package/dist/src/nile-nav-tab-group/index.d.ts +1 -0
  166. package/dist/src/nile-nav-tab-group/index.js +2 -0
  167. package/dist/src/nile-nav-tab-group/index.js.map +1 -0
  168. package/dist/src/nile-nav-tab-group/nile-nav-tab-group.css.d.ts +12 -0
  169. package/dist/src/nile-nav-tab-group/nile-nav-tab-group.css.js +680 -0
  170. package/dist/src/nile-nav-tab-group/nile-nav-tab-group.css.js.map +1 -0
  171. package/dist/src/nile-nav-tab-group/nile-nav-tab-group.d.ts +119 -0
  172. package/dist/src/nile-nav-tab-group/nile-nav-tab-group.js +765 -0
  173. package/dist/src/nile-nav-tab-group/nile-nav-tab-group.js.map +1 -0
  174. package/dist/src/nile-nav-tab-group/nile-nav-tab-group.test.d.ts +3 -0
  175. package/dist/src/nile-nav-tab-group/nile-nav-tab-group.test.js +838 -0
  176. package/dist/src/nile-nav-tab-group/nile-nav-tab-group.test.js.map +1 -0
  177. package/dist/src/nile-nav-tab-panel/index.d.ts +1 -0
  178. package/dist/src/nile-nav-tab-panel/index.js +2 -0
  179. package/dist/src/nile-nav-tab-panel/index.js.map +1 -0
  180. package/dist/src/nile-nav-tab-panel/nile-nav-tab-panel.css.d.ts +15 -0
  181. package/dist/src/nile-nav-tab-panel/nile-nav-tab-panel.css.js +37 -0
  182. package/dist/src/nile-nav-tab-panel/nile-nav-tab-panel.css.js.map +1 -0
  183. package/dist/src/nile-nav-tab-panel/nile-nav-tab-panel.d.ts +37 -0
  184. package/dist/src/nile-nav-tab-panel/nile-nav-tab-panel.js +75 -0
  185. package/dist/src/nile-nav-tab-panel/nile-nav-tab-panel.js.map +1 -0
  186. package/dist/src/nile-nav-tab-panel/nile-nav-tab-panel.test.d.ts +1 -0
  187. package/dist/src/nile-nav-tab-panel/nile-nav-tab-panel.test.js +534 -0
  188. package/dist/src/nile-nav-tab-panel/nile-nav-tab-panel.test.js.map +1 -0
  189. package/dist/src/nile-rich-text-editor/nile-rich-text-editor.css.js +5 -2
  190. package/dist/src/nile-rich-text-editor/nile-rich-text-editor.css.js.map +1 -1
  191. package/dist/src/nile-rich-text-editor/nile-rich-text-editor.d.ts +2 -1
  192. package/dist/src/nile-rich-text-editor/nile-rich-text-editor.js +9 -0
  193. package/dist/src/nile-rich-text-editor/nile-rich-text-editor.js.map +1 -1
  194. package/dist/src/version.js +1 -1
  195. package/dist/src/version.js.map +1 -1
  196. package/dist/tsconfig.tsbuildinfo +1 -1
  197. package/package.json +8 -3
  198. package/src/index.ts +7 -0
  199. package/src/internal/enum.ts +8 -1
  200. package/src/nile-inline-sidebar/nile-inline-sidebar.css.ts +75 -17
  201. package/src/nile-inline-sidebar/nile-inline-sidebar.ts +148 -18
  202. package/src/nile-inline-sidebar-group/nile-inline-sidebar-group.css.ts +7 -7
  203. package/src/nile-inline-sidebar-item/nile-inline-sidebar-item.css.ts +15 -17
  204. package/src/nile-inline-sidebar-item/nile-inline-sidebar-item.ts +74 -9
  205. package/src/nile-inline-sidebar-item-body/index.ts +1 -0
  206. package/src/nile-inline-sidebar-item-body/nile-inline-sidebar-item-body.css.ts +64 -0
  207. package/src/nile-inline-sidebar-item-body/nile-inline-sidebar-item-body.ts +110 -0
  208. package/src/nile-inline-sidebar-item-header/index.ts +1 -0
  209. package/src/nile-inline-sidebar-item-header/nile-inline-sidebar-item-header.css.ts +38 -0
  210. package/src/nile-inline-sidebar-item-header/nile-inline-sidebar-item-header.ts +69 -0
  211. package/src/nile-inline-sidebar-panel/index.ts +1 -0
  212. package/src/nile-inline-sidebar-panel/nile-inline-sidebar-panel.css.ts +30 -0
  213. package/src/nile-inline-sidebar-panel/nile-inline-sidebar-panel.ts +53 -0
  214. package/src/nile-inline-sidebar-panel-group/index.ts +1 -0
  215. package/src/nile-inline-sidebar-panel-group/nile-inline-sidebar-panel-group.css.ts +41 -0
  216. package/src/nile-inline-sidebar-panel-group/nile-inline-sidebar-panel-group.ts +101 -0
  217. package/src/nile-nav-tab/index.ts +1 -0
  218. package/src/nile-nav-tab/nile-nav-tab.css.ts +201 -0
  219. package/src/nile-nav-tab/nile-nav-tab.test.ts +768 -0
  220. package/src/nile-nav-tab/nile-nav-tab.ts +198 -0
  221. package/src/nile-nav-tab-group/index.ts +1 -0
  222. package/src/nile-nav-tab-group/nile-nav-tab-group.css.ts +682 -0
  223. package/src/nile-nav-tab-group/nile-nav-tab-group.test.ts +1009 -0
  224. package/src/nile-nav-tab-group/nile-nav-tab-group.ts +845 -0
  225. package/src/nile-nav-tab-panel/index.ts +1 -0
  226. package/src/nile-nav-tab-panel/nile-nav-tab-panel.css.ts +39 -0
  227. package/src/nile-nav-tab-panel/nile-nav-tab-panel.test.ts +797 -0
  228. package/src/nile-nav-tab-panel/nile-nav-tab-panel.ts +78 -0
  229. package/src/nile-rich-text-editor/nile-rich-text-editor.css.ts +5 -2
  230. package/src/nile-rich-text-editor/nile-rich-text-editor.ts +12 -1
  231. package/vscode-html-custom-data.json +272 -2
@@ -0,0 +1,845 @@
1
+ /**
2
+ * Copyright Aquera Inc 2023
3
+ *
4
+ * This source code is licensed under the BSD-3-Clause license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ import { html, nothing } from 'lit';
9
+ import { customElement, property, query, state } from 'lit/decorators.js';
10
+ import { classMap } from 'lit/directives/class-map.js';
11
+ import { styleMap } from 'lit/directives/style-map.js';
12
+ import { scrollIntoView } from '../internal/scroll';
13
+ import { watch } from '../internal/watch';
14
+ import NileElement from '../internal/nile-element';
15
+ import type { CSSResultGroup, PropertyValueMap } from 'lit';
16
+ import type NileNavTab from '../nile-nav-tab/nile-nav-tab';
17
+ import type NileNavTabPanel from '../nile-nav-tab-panel/nile-nav-tab-panel';
18
+ import { styles } from './nile-nav-tab-group.css';
19
+ import { MouseKey } from '../internal/enum';
20
+ import '../nile-icon-button/nile-icon-button';
21
+
22
+ /**
23
+ * Tab group navigation component.
24
+ *
25
+ * @tag nile-nav-tab-group
26
+ *
27
+ * @slot - Used for grouping tab panels in the tab group. Must be `<nile-nav-tab-panel>` elements.
28
+ * @slot nav - Used for grouping tabs in the tab group. Must be `<nile-nav-tab>` elements.
29
+ *
30
+ * @csspart base - The component's base wrapper.
31
+ * @csspart nav - The tab group's navigation container where tabs are slotted in.
32
+ * @csspart tabs - The container that wraps the tabs.
33
+ * @csspart active-tab-indicator - The line that highlights the currently selected tab.
34
+ * @csspart toggle-frame - Toggle variant: absolutely positioned ring that draws the group chrome (tabs paint above it).
35
+ * @csspart body - The tab group's body where tab panels are slotted in.
36
+ * @csspart scroll-button - The previous/next scroll buttons that show when tabs are scrollable, an `<nile-icon-button>`.
37
+ * @csspart scroll-button--start - The starting scroll button.
38
+ * @csspart scroll-button--end - The ending scroll button.
39
+ * @csspart scroll-button__base - The scroll button's exported `base` part.
40
+ *
41
+ * @cssproperty --indicator-color - The color of the active tab indicator.
42
+ * @cssproperty --track-color - The color of the indicator's track (the line that separates tabs from panels).
43
+ * @cssproperty --track-width - The width of the indicator's track (the line that separates tabs from panels).
44
+ * @cssproperty --show-indicator-on-hover - Whether to show the indicator on hover.
45
+ *
46
+ * @event nile-close - Bubbled from a closable `<nile-nav-tab>`; re-emitted so parents can listen on this element.
47
+ */
48
+ @customElement('nile-nav-tab-group')
49
+ export class NileNavTabGroup extends NileElement {
50
+
51
+ static styles: CSSResultGroup = styles;
52
+
53
+ /**
54
+ * Selection model (for reviewers):
55
+ * - `activeTabName` is the internal source of truth; clicks and keyboard update it.
56
+ * - `activeTabProp` / attribute `value` is the host-controlled API; changes in `updated()` copy into `activeTabName`.
57
+ * - `setActiveTab()` syncs each tab’s `active` flag, each panel’s `active`, `activeTabProp` / `value`, indicator/pill geometry, and optionally emits `nile-tab-change`.
58
+ * - `this.tabs` only includes non-disabled tabs (see `syncTabsAndPanels`); keyboard roving focus uses that list.
59
+ */
60
+ private activeTab?: NileNavTab;
61
+ private mutationObserver: MutationObserver;
62
+ private resizeObserver: ResizeObserver;
63
+ private visibilityObserver: IntersectionObserver;
64
+ private pillRepositionTimer: ReturnType<typeof setTimeout> | null = null;
65
+ private pillTransitionClassTimer: ReturnType<typeof setTimeout> | null = null;
66
+ private pillReady = false;
67
+ private observedTabs = new Set<NileNavTab>();
68
+ private tabs: NileNavTab[] = [];
69
+ private panels: NileNavTabPanel[] = [];
70
+
71
+ /** Tabs emit `nile-close` on the host; bubble is stopped so we re-emit from the group with `{ panel }` for consumers. */
72
+ private readonly handleCloseEvent = (e: Event) => {
73
+ if (e.target === this) return;
74
+
75
+ const tab = e.target as NileNavTab | null;
76
+ const panel = tab?.getAttribute('panel');
77
+
78
+ if (!panel) return;
79
+
80
+ // Other listeners on this host must not see the child event (no `detail`) after we synthesize one.
81
+ e.stopImmediatePropagation();
82
+ e.stopPropagation();
83
+
84
+ this.syncIndicator();
85
+ this.emit('nile-close', { panel });
86
+ };
87
+
88
+ /** Tracks visibility for snapping the indicator when a hidden parent (e.g. dropdown) is reopened. */
89
+ private tabGroupWasIntersecting = false;
90
+
91
+ /** Last measured nav width; used to detect layout resume after display:none / collapse. */
92
+ private lastNavClientWidth = 0;
93
+
94
+ @query('.nav-tab-group') tabGroup: HTMLElement;
95
+ @query('.nav-tab-group__body') body: HTMLSlotElement;
96
+ @query('.nav-tab-group__nav') nav: HTMLElement;
97
+ @query('.nav-tab-group__indicator') indicator: HTMLElement;
98
+ @query('.nav-tab-group__hover-indicator') hoverIndicator: HTMLElement;
99
+
100
+ @property({ type: Boolean, reflect: true, attribute: true }) hasScrollControls = false;
101
+
102
+ /** The placement of the tabs. */
103
+ @property() placement: 'top' | 'bottom' | 'start' | 'end' = 'top';
104
+
105
+ /** The currently active tab value. */
106
+ @property({ reflect: true, attribute: 'value', type: String }) activeTabProp: string = '';
107
+
108
+ /** Track for showing Indicators and Background. */
109
+ @property({ type: Boolean, reflect: true, attribute: 'no-track' }) noTrack = false;
110
+
111
+
112
+ /** Disables the scroll arrows that appear when tabs overflow. */
113
+ @property({ attribute: 'no-scroll-controls', type: Boolean }) noScrollControls = false;
114
+
115
+ /** Controls whether tabs are centered and have equal width. */
116
+ @property({ type: Boolean, reflect: true }) centered = false;
117
+
118
+ @property({ type: String, reflect: true }) variant: 'underline' | 'filled' | 'toggle' | 'neutral-filled' | 'toggle-button' = 'underline';
119
+
120
+ @property({ type: String, reflect: true }) indicatorPlacement = '';
121
+
122
+ @property({ type: Boolean, reflect: true, attribute: true }) fullWidth = false;
123
+
124
+ @property({ type: String, reflect: true }) width = '';
125
+
126
+ @property({ type: Boolean, reflect: true, attribute: true }) showIndicatorOnHover = false;
127
+
128
+ @state() activeTabName: string = '';
129
+
130
+ private get showScrollControlButtons(): boolean {
131
+ return !this.noScrollControls && this.hasScrollControls;
132
+ }
133
+
134
+ connectedCallback() {
135
+ super.connectedCallback();
136
+
137
+ this.resizeObserver = new ResizeObserver(() => {
138
+ this.setScrollControls();
139
+ const nw = this.nav?.clientWidth ?? 0;
140
+ const layoutResumed = this.lastNavClientWidth === 0 && nw > 0;
141
+ this.lastNavClientWidth = nw;
142
+ this.repositionIndicator({ skipTransition: layoutResumed });
143
+ this.debouncedPositionPill();
144
+ });
145
+
146
+ this.mutationObserver = new MutationObserver(mutations => {
147
+ if (mutations.some(m => !['aria-labelledby', 'aria-controls'].includes(m.attributeName!))) {
148
+ setTimeout(() => this.setAriaLabels());
149
+ }
150
+
151
+ if (mutations.some(m => m.attributeName === 'disabled')) {
152
+ this.syncTabsAndPanels();
153
+ }
154
+
155
+ if (mutations.some(m => m.attributeName === 'active')) {
156
+ const activeTab = this.getAllTabs({ includeDisabled: false }).find(t => t.active);
157
+ if (activeTab && activeTab.panel !== this.activeTabName) {
158
+ this.activeTabName = activeTab.panel;
159
+ }
160
+ }
161
+
162
+ if (mutations.some(m => m.type === 'childList' || m.type === 'characterData')) {
163
+ this.repositionIndicator();
164
+ this.debouncedPositionPill();
165
+ requestAnimationFrame(() => this.setScrollControls());
166
+ }
167
+ });
168
+
169
+ this.updateComplete.then(async () => {
170
+ this.setScrollControls();
171
+ await Promise.all(this.getAllTabs({ includeDisabled: true }).map(tab => tab.updateComplete));
172
+ await Promise.all(this.getAllPanels().map(panel => panel.updateComplete));
173
+ this.syncTabsAndPanels();
174
+
175
+ if (this.activeTabProp) {
176
+ this.activeTabName = this.activeTabProp;
177
+ } else {
178
+ const activeTab = this.getActiveTab();
179
+ this.activeTabName = activeTab ? activeTab.panel : '';
180
+ }
181
+
182
+ // First paint: disable indicator transition so it jumps to the correct tab without sliding from (0,0).
183
+ requestAnimationFrame(() => {
184
+ if (this.indicator) {
185
+ this.indicator.style.transition = 'none';
186
+ }
187
+
188
+ this.syncIndicator();
189
+ this.positionPill();
190
+
191
+ requestAnimationFrame(() => {
192
+ if (this.indicator) {
193
+ this.indicator.style.transition = '';
194
+ }
195
+ });
196
+ });
197
+
198
+ this.mutationObserver.observe(this, { attributes: true, childList: true, subtree: true, characterData: true });
199
+ this.resizeObserver.observe(this.nav);
200
+
201
+ this.visibilityObserver = new IntersectionObserver((entries) => {
202
+ const entry = entries[0];
203
+ const nowVisible = entry.isIntersecting && entry.intersectionRatio > 0;
204
+ if (nowVisible) {
205
+ this.setAriaLabels();
206
+ this.debouncedPositionPill();
207
+ const becameVisible = !this.tabGroupWasIntersecting;
208
+ this.tabGroupWasIntersecting = true;
209
+ this.repositionIndicator({ skipTransition: becameVisible });
210
+ } else {
211
+ this.tabGroupWasIntersecting = false;
212
+ }
213
+ });
214
+ this.visibilityObserver.observe(this.tabGroup);
215
+ });
216
+
217
+ this.addEventListener('nile-close', this.handleCloseEvent);
218
+ this.addEventListener('click', this.handleNavClick);
219
+ this.addEventListener('keydown', this.handleKeyDown);
220
+ }
221
+
222
+ disconnectedCallback() {
223
+ this.mutationObserver.disconnect();
224
+ this.resizeObserver.disconnect();
225
+ this.observedTabs.clear();
226
+ this.visibilityObserver?.disconnect();
227
+ this.removeEventListener('nile-close', this.handleCloseEvent);
228
+ this.removeEventListener('click', this.handleNavClick);
229
+ this.removeEventListener('keydown', this.handleKeyDown);
230
+ }
231
+
232
+ protected updated(_changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void {
233
+ let nameChanged = _changedProperties.has('activeTabName');
234
+
235
+ if (_changedProperties.has('activeTabProp') && this.activeTabName !== this.activeTabProp) {
236
+ this.activeTabName = this.activeTabProp;
237
+ nameChanged = true;
238
+ }
239
+
240
+ if (nameChanged) {
241
+ const tab = this.getActiveTab();
242
+ if (tab) {
243
+ this.setActiveTab(tab, { scrollBehavior: 'smooth' });
244
+ }
245
+ }
246
+ if (_changedProperties.has('noScrollControls')) {
247
+ requestAnimationFrame(() => this.setScrollControls());
248
+ }
249
+ }
250
+
251
+ @watch('placement', { waitUntilFirstUpdate: true })
252
+ syncIndicator() {
253
+ requestAnimationFrame(() => this.setScrollControls());
254
+ if (!this.indicator) return;
255
+ const tab = this.getActiveTab();
256
+
257
+ if (tab && !tab.disabled) {
258
+ this.indicator.style.display = 'block';
259
+ this.repositionIndicator();
260
+ } else {
261
+ this.indicator.style.display = 'none';
262
+ }
263
+ }
264
+
265
+ private handleNavClick(event: MouseEvent) {
266
+ const tab = event.composedPath().find(
267
+ (el): el is NileNavTab =>
268
+ el instanceof HTMLElement && el.tagName.toLowerCase() === 'nile-nav-tab'
269
+ );
270
+
271
+ if (!tab) return;
272
+
273
+ const tabsNow = this.getAllTabs({ includeDisabled: true });
274
+ if (!tabsNow.includes(tab)) return;
275
+
276
+ if (tab.hasAttribute('disabled') || tab.disabled) return;
277
+
278
+ const isModifiedClick =
279
+ Object.values(MouseKey).some(key => event[key as keyof MouseEvent]) || event.button !== 0;
280
+
281
+ if (isModifiedClick) return;
282
+
283
+ this.activeTabProp = tab.panel;
284
+ this.activeTabName = tab.panel;
285
+ tab.focus();
286
+ }
287
+
288
+ private positionPill(animate = false) {
289
+ const pill = this.shadowRoot?.querySelector<HTMLElement>('.nav-tab-group__pill');
290
+ if (!pill) return;
291
+
292
+ if (this.pillTransitionClassTimer) {
293
+ clearTimeout(this.pillTransitionClassTimer);
294
+ this.pillTransitionClassTimer = null;
295
+ }
296
+
297
+ const tab = this.getActiveTab();
298
+ if (!tab || tab.disabled) {
299
+ pill.classList.add('nav-tab-group__pill--inactive');
300
+ pill.classList.remove('nav-tab-group__pill--transitioning');
301
+ return;
302
+ }
303
+
304
+ pill.classList.remove('nav-tab-group__pill--inactive');
305
+
306
+ const target = tab.shadowRoot?.querySelector<HTMLElement>('.nav-tab-container');
307
+ if (!target) return;
308
+
309
+ const container = this.shadowRoot?.querySelector<HTMLElement>('.nav-tab-group__tabs');
310
+ if (!container) return;
311
+
312
+ if (target.offsetWidth === 0 || target.offsetHeight === 0) return;
313
+
314
+ const box = this.getLayoutBoxRelativeToContainer(target, container);
315
+ if (!box) return;
316
+
317
+ const x = box.x;
318
+ const y = box.y;
319
+ const w = box.width;
320
+ const h = box.height;
321
+
322
+ const cw = container.clientWidth;
323
+ const rightInset = Math.max(0, cw - x - w);
324
+
325
+ if (animate) {
326
+ const ease = 'cubic-bezier(0.4, 0, 0.2, 1)';
327
+ pill.style.transition = `left 400ms ${ease}, right 400ms ${ease}, top 400ms ${ease}, height 400ms ${ease}`;
328
+ if (this.variant === 'toggle') {
329
+ pill.classList.add('nav-tab-group__pill--transitioning');
330
+ }
331
+ } else {
332
+ pill.style.transition = '';
333
+ if (this.variant === 'toggle') {
334
+ pill.classList.remove('nav-tab-group__pill--transitioning');
335
+ }
336
+ }
337
+
338
+ pill.style.transform = '';
339
+ pill.style.left = `${x}px`;
340
+ pill.style.right = `${rightInset}px`;
341
+ pill.style.width = 'auto';
342
+ pill.style.top = `${y}px`;
343
+ pill.style.height = `${h}px`;
344
+ pill.style.opacity = '1';
345
+
346
+ if (!this.pillReady) {
347
+ this.pillReady = true;
348
+ }
349
+
350
+ if (animate) {
351
+ const onEnd = () => {
352
+ pill.style.transition = '';
353
+ pill.removeEventListener('transitionend', onEnd);
354
+ };
355
+ pill.addEventListener('transitionend', onEnd);
356
+ if (this.variant === 'toggle') {
357
+ this.pillTransitionClassTimer = setTimeout(() => {
358
+ this.pillTransitionClassTimer = null;
359
+ pill.classList.remove('nav-tab-group__pill--transitioning');
360
+ }, 420);
361
+ }
362
+ }
363
+ }
364
+
365
+ private debouncedPositionPill() {
366
+ if (this.pillRepositionTimer) {
367
+ clearTimeout(this.pillRepositionTimer);
368
+ }
369
+ this.pillRepositionTimer = setTimeout(() => {
370
+ this.pillRepositionTimer = null;
371
+ this.positionPill();
372
+ }, 150);
373
+ }
374
+
375
+ private handleTabHover(event: MouseEvent) {
376
+ if (!this.showIndicatorOnHover) return;
377
+
378
+ const tab = event.composedPath().find(
379
+ (el): el is NileNavTab =>
380
+ el instanceof HTMLElement && el.tagName.toLowerCase() === 'nile-nav-tab'
381
+ );
382
+
383
+ if (!tab || tab.disabled || tab.hasAttribute('disabled')) return;
384
+
385
+ const tabsNow = this.getAllTabs({ includeDisabled: true });
386
+ if (!tabsNow.includes(tab)) return;
387
+
388
+ this.positionHoverIndicator(tab);
389
+ }
390
+
391
+ private handleTabHoverLeave() {
392
+ if (!this.showIndicatorOnHover || !this.hoverIndicator) return;
393
+ this.hoverIndicator.style.opacity = '0';
394
+ }
395
+
396
+ private positionHoverIndicator(tab: NileNavTab) {
397
+ if (!this.hoverIndicator) return;
398
+
399
+ const container = this.shadowRoot?.querySelector<HTMLElement>('.nav-tab-group__tabs');
400
+ if (!container) return;
401
+
402
+ const target = tab.shadowRoot?.querySelector<HTMLElement>('.nav-tab-container');
403
+ if (!target) return;
404
+
405
+ const layoutBox = this.getLayoutBoxRelativeToContainer(target, container);
406
+ if (!layoutBox) return;
407
+
408
+ this.hoverIndicator.style.transition = 'none';
409
+
410
+ switch (this.placement) {
411
+ case 'top':
412
+ case 'bottom':
413
+ this.hoverIndicator.style.width = `${target.offsetWidth}px`;
414
+ this.hoverIndicator.style.height = 'auto';
415
+ this.hoverIndicator.style.translate = `${layoutBox.x}px 0px`;
416
+ break;
417
+
418
+ case 'start':
419
+ case 'end':
420
+ this.hoverIndicator.style.height = `${target.offsetHeight}px`;
421
+ this.hoverIndicator.style.width = 'auto';
422
+ this.hoverIndicator.style.translate = `0px ${layoutBox.y}px`;
423
+ break;
424
+ }
425
+
426
+ this.hoverIndicator.style.opacity = '1';
427
+ }
428
+
429
+ render() {
430
+ const usesPill =
431
+ this.variant === 'filled' ||
432
+ this.variant === 'neutral-filled' ||
433
+ this.variant === 'toggle' ||
434
+ this.variant === 'toggle-button';
435
+
436
+ const showsIndicator =
437
+ this.variant !== 'filled' &&
438
+ this.variant !== 'neutral-filled' &&
439
+ this.variant !== 'toggle-button' &&
440
+ this.variant !== 'toggle';
441
+
442
+ const toggleVariant = this.variant === 'toggle';
443
+
444
+ return html`
445
+ <div
446
+ part="base"
447
+ class=${classMap({
448
+ 'nav-tab-group': true,
449
+ 'nav-tab-group--top': this.placement === 'top',
450
+ 'nav-tab-group--bottom': this.placement === 'bottom',
451
+ 'nav-tab-group--start': this.placement === 'start',
452
+ 'nav-tab-group--end': this.placement === 'end',
453
+ 'nav-tab-group--rtl': true,
454
+ 'nav-tab-group--has-scroll-controls': this.showScrollControlButtons,
455
+ 'hide__track': this.noTrack,
456
+ })}
457
+ style=${styleMap(this.width ? { '--nav-tab-item-width': this.width } : {})}
458
+ >
459
+ <nav class="nav-tab-group__nav-container" part="nav">
460
+ ${toggleVariant
461
+ ? html`<div class="nav-tab-group__toggle-frame" part="toggle-frame"></div>`
462
+ : nothing}
463
+ ${this.showScrollControlButtons
464
+ ? html`
465
+ <nile-icon-button
466
+ part="scroll-button scroll-button--start"
467
+ exportparts="base:scroll-button__base"
468
+ class="nav-tab-group__scroll-button nav-tab-group__scroll-button--start"
469
+ name='var(--nile-icon-arrow-right, var(--ng-icon-chevron-right))'
470
+ library="system"
471
+ label="scrollToStart"
472
+ @click=${this.handleScrollToEnd}
473
+ ></nile-icon-button>
474
+ `
475
+ : nothing}
476
+
477
+ <div part="tab-container" class="nav-tab-group__nav">
478
+ <ul part="tabs" class="nav-tab-group__tabs" role="tablist">
479
+ ${showsIndicator ? html`
480
+ <div part="active-tab-indicator-path" class="nav-tab-group__indicator__path"></div>
481
+ <div
482
+ part="active-tab-indicator"
483
+ class=${classMap({
484
+ 'nav-tab-group__indicator': !this.noTrack,
485
+ })}
486
+ ></div>
487
+ ` : nothing}
488
+
489
+ ${!this.noTrack && showsIndicator && this.showIndicatorOnHover ? html`
490
+ <div class="nav-tab-group__hover-indicator" part="hover-tab-indicator"></div>
491
+ ` : nothing}
492
+
493
+ ${usesPill ? html`<div class="nav-tab-group__pill" part="active-tab-pill"></div>` : nothing}
494
+
495
+ <slot
496
+ name="nav"
497
+ @mouseover=${this.handleTabHover}
498
+ @mouseleave=${this.handleTabHoverLeave}
499
+ @slotchange=${this.syncTabsAndPanels}
500
+ ></slot>
501
+ </ul>
502
+ </div>
503
+
504
+ ${this.showScrollControlButtons
505
+ ? html`
506
+ <nile-icon-button
507
+ part="scroll-button scroll-button--end"
508
+ exportparts="base:scroll-button__base"
509
+ class="nav-tab-group__scroll-button nav-tab-group__scroll-button--end"
510
+ name='var(--nile-icon-arrow-left, var(--ng-icon-chevron-left))'
511
+ library="system"
512
+ label="scrollToEnd"
513
+ @click=${this.handleScrollToStart}
514
+ ></nile-icon-button>
515
+ `
516
+ : nothing}
517
+ </nav>
518
+
519
+ <slot part="body" class="nav-tab-group__body" @slotchange=${this.syncTabsAndPanels}></slot>
520
+ </div>
521
+ `;
522
+ }
523
+
524
+ private getAllTabs(options: { includeDisabled: boolean } = { includeDisabled: true }) {
525
+ const slot = this.shadowRoot!.querySelector<HTMLSlotElement>('slot[name="nav"]')!;
526
+ const navTabs = slot.assignedElements().filter(
527
+ (el): el is NileNavTab => el.tagName.toLowerCase() === 'nile-nav-tab'
528
+ );
529
+ return options.includeDisabled ? navTabs : navTabs.filter(tab => !tab.disabled);
530
+ }
531
+
532
+ private getAllPanels() {
533
+ return [...this.body.assignedElements()].filter(
534
+ el => el.tagName.toLowerCase() === 'nile-nav-tab-panel'
535
+ ) as NileNavTabPanel[];
536
+ }
537
+
538
+ /**
539
+ * Resolves the tab to treat as selected for indicator/pill wiring.
540
+ * Prefer `activeTabName` match; if empty (before init), fall back to `.active` or first usable tab.
541
+ */
542
+ private getActiveTab() {
543
+ return this.tabs.find(el =>
544
+ this.activeTabName ? el.panel === this.activeTabName : el.active
545
+ ) ?? this.tabs.find(el => !el.disabled) ?? this.tabs[0];
546
+ }
547
+
548
+ /** Reflects horizontal overflow into `hasScrollControls` (top/bottom only; vertical stacks do not scroll this strip). */
549
+ setScrollControls() {
550
+ if (!this.nav) return;
551
+ if (this.noScrollControls) {
552
+ this.hasScrollControls = false;
553
+ return;
554
+ }
555
+ this.hasScrollControls =
556
+ ['top', 'bottom'].includes(this.placement) &&
557
+ this.nav.scrollWidth > this.nav.clientWidth;
558
+ }
559
+
560
+ private handleKeyDown(event: KeyboardEvent) {
561
+ const tab = event.composedPath().find(
562
+ (el): el is NileNavTab =>
563
+ el instanceof HTMLElement && el.tagName.toLowerCase() === 'nile-nav-tab'
564
+ );
565
+
566
+ if (tab?.disabled) {
567
+ event.preventDefault();
568
+ return;
569
+ }
570
+
571
+ if (!tab || !this.tabs.includes(tab)) {
572
+ return;
573
+ }
574
+
575
+ if (event.key === 'Enter' || event.key === ' ') {
576
+ event.preventDefault();
577
+ this.activeTabProp = tab.panel;
578
+ this.activeTabName = tab.panel;
579
+ return;
580
+ }
581
+
582
+ const navKeys = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End'];
583
+ if (!navKeys.includes(event.key)) return;
584
+
585
+ let index = this.tabs.indexOf(tab);
586
+ const isHorizontal = ['top', 'bottom'].includes(this.placement);
587
+ const isRtl = getComputedStyle(this).direction === 'rtl';
588
+
589
+ if (event.key === 'Home') {
590
+ index = 0;
591
+ } else if (event.key === 'End') {
592
+ index = this.tabs.length - 1;
593
+ } else if (
594
+ (isHorizontal &&
595
+ ((isRtl && event.key === 'ArrowRight') || (!isRtl && event.key === 'ArrowLeft'))) ||
596
+ (['start', 'end'].includes(this.placement) && event.key === 'ArrowUp')
597
+ ) {
598
+ index--;
599
+ } else if (
600
+ (isHorizontal &&
601
+ ((isRtl && event.key === 'ArrowLeft') || (!isRtl && event.key === 'ArrowRight'))) ||
602
+ (['start', 'end'].includes(this.placement) && event.key === 'ArrowDown')
603
+ ) {
604
+ index++;
605
+ }
606
+
607
+ if (index < 0) index = this.tabs.length - 1;
608
+ if (index > this.tabs.length - 1) index = 0;
609
+
610
+ this.tabs[index].focus({ preventScroll: true });
611
+
612
+ if (isHorizontal) {
613
+ scrollIntoView(this.tabs[index], this.nav, 'horizontal');
614
+ }
615
+
616
+ event.preventDefault();
617
+ }
618
+
619
+ private handleScrollToStart() {
620
+ this.nav.scroll({
621
+ left: -this.nav.scrollWidth,
622
+ behavior: 'smooth'
623
+ });
624
+ }
625
+
626
+ private handleScrollToEnd() {
627
+ this.nav.scroll({
628
+ left: this.nav.scrollWidth,
629
+ behavior: 'smooth'
630
+ });
631
+ }
632
+
633
+ private setActiveTab(tab: NileNavTab, options?: { emitEvents?: boolean; scrollBehavior?: 'auto' | 'smooth' }) {
634
+ options = {
635
+ emitEvents: true,
636
+ scrollBehavior: 'auto',
637
+ ...options
638
+ };
639
+
640
+ if (tab !== this.activeTab && !tab.disabled) {
641
+ const previousTab = this.activeTab;
642
+ this.activeTab = tab;
643
+ if (this.activeTabProp !== this.activeTab.panel) {
644
+ this.activeTabProp = this.activeTab.panel;
645
+ }
646
+
647
+ this.tabs.forEach(el => (el.active = el === this.activeTab));
648
+ this.panels.forEach(el => (el.active = el.name === this.activeTab?.panel));
649
+ this.syncIndicator();
650
+ this.positionPill(this.pillReady);
651
+
652
+ if (['top', 'bottom'].includes(this.placement)) {
653
+ scrollIntoView(this.activeTab, this.nav, 'horizontal', options.scrollBehavior);
654
+ }
655
+
656
+ if (options.emitEvents) {
657
+ this.emit('nile-tab-change', {
658
+ value: this.activeTab.panel,
659
+ previousValue: previousTab?.panel ?? null,
660
+ index: this.tabs.indexOf(this.activeTab),
661
+ previousIndex: previousTab ? this.tabs.indexOf(previousTab) : -1,
662
+ link: this.activeTab.link
663
+ });
664
+ }
665
+ }
666
+ }
667
+
668
+ private setAriaLabels() {
669
+ const allTabs = this.getAllTabs({ includeDisabled: true });
670
+ for (const tab of allTabs) {
671
+ const panel = this.panels.find(el => el.name === tab.panel);
672
+ if (tab.disabled) {
673
+ tab.removeAttribute('aria-controls');
674
+ if (panel) panel.removeAttribute('aria-labelledby');
675
+ continue;
676
+ }
677
+ if (!panel) continue;
678
+ const panelId = panel.getAttribute('id');
679
+ const tabId = tab.getAttribute('id');
680
+ if (!panelId || !tabId) continue;
681
+
682
+ tab.setAttribute('aria-controls', panelId);
683
+ panel.setAttribute('aria-labelledby', tabId);
684
+ }
685
+ }
686
+
687
+
688
+ private getRelativeOffsetToAncestor(element: HTMLElement, ancestor: HTMLElement): { x: number; y: number } | null {
689
+ let x = 0;
690
+ let y = 0;
691
+ let el: HTMLElement | null = element;
692
+ while (el && el !== ancestor) {
693
+ x += el.offsetLeft;
694
+ y += el.offsetTop;
695
+ el = el.offsetParent as HTMLElement | null;
696
+ }
697
+ return el === ancestor ? { x, y } : null;
698
+ }
699
+
700
+ private getLayoutBoxRelativeToContainer(
701
+ target: HTMLElement,
702
+ container: HTMLElement
703
+ ): { x: number; y: number; width: number; height: number } | null {
704
+ const c = container.getBoundingClientRect();
705
+ const r = target.getBoundingClientRect();
706
+ if (r.width === 0 || r.height === 0) return null;
707
+
708
+ const scaleX = container.offsetWidth > 0 ? c.width / container.offsetWidth : 1;
709
+ const scaleY = container.offsetHeight > 0 ? c.height / container.offsetHeight : 1;
710
+
711
+ if (scaleX > 0 && scaleY > 0 && Number.isFinite(scaleX) && Number.isFinite(scaleY)) {
712
+ const scaleIsUnity =
713
+ Math.abs(scaleX - 1) < 0.001 && Math.abs(scaleY - 1) < 0.001;
714
+ if (scaleIsUnity) {
715
+ const off = this.getRelativeOffsetToAncestor(target, container);
716
+ if (off) {
717
+ return {
718
+ x: off.x,
719
+ y: off.y,
720
+ width: target.offsetWidth,
721
+ height: target.offsetHeight,
722
+ };
723
+ }
724
+ }
725
+
726
+ const x = (r.left - c.left) / scaleX;
727
+ const y = (r.top - c.top) / scaleY;
728
+ let width = r.width / scaleX;
729
+ let height = r.height / scaleY;
730
+ const ow = target.offsetWidth;
731
+ const oh = target.offsetHeight;
732
+ if (ow > 0) width = Math.min(width, ow);
733
+ if (oh > 0) height = Math.min(height, oh);
734
+ return { x, y, width, height };
735
+ }
736
+
737
+ const offset = this.getRelativeOffsetToAncestor(target, container);
738
+ if (!offset) return null;
739
+ return {
740
+ x: offset.x,
741
+ y: offset.y,
742
+ width: target.offsetWidth,
743
+ height: target.offsetHeight,
744
+ };
745
+ }
746
+
747
+ private repositionIndicator(options?: { skipTransition?: boolean }) {
748
+ const currentTab = this.getActiveTab();
749
+ if (!currentTab || !this.indicator) return;
750
+
751
+ const container = this.shadowRoot?.querySelector<HTMLElement>('.nav-tab-group__tabs');
752
+ if (!container) return;
753
+
754
+ const target = currentTab.shadowRoot?.querySelector<HTMLElement>('.nav-tab-container');
755
+ if (!target) return;
756
+
757
+ const skipTransition = options?.skipTransition === true;
758
+ if (skipTransition) {
759
+ this.indicator.style.transition = 'none';
760
+ }
761
+
762
+ const layoutBox = this.getLayoutBoxRelativeToContainer(target, container);
763
+ if (!layoutBox) {
764
+ if (skipTransition) {
765
+ requestAnimationFrame(() => {
766
+ if (this.indicator) this.indicator.style.transition = '';
767
+ });
768
+ }
769
+ return;
770
+ }
771
+
772
+ const x = layoutBox.x;
773
+ const y = layoutBox.y;
774
+
775
+ switch (this.placement) {
776
+ case 'top':
777
+ case 'bottom':
778
+ this.indicator.style.width = `${layoutBox.width}px`;
779
+ this.indicator.style.height = 'auto';
780
+ this.indicator.style.translate = `${x}px 0px`;
781
+ break;
782
+
783
+ case 'start':
784
+ case 'end':
785
+ this.indicator.style.width = 'auto';
786
+ this.indicator.style.height = `${layoutBox.height}px`;
787
+ this.indicator.style.translate = `0px ${y}px`;
788
+ break;
789
+ }
790
+
791
+ if (skipTransition) {
792
+ requestAnimationFrame(() => {
793
+ requestAnimationFrame(() => {
794
+ if (this.indicator) this.indicator.style.transition = '';
795
+ });
796
+ });
797
+ }
798
+ }
799
+
800
+ private syncTabsAndPanels() {
801
+ this.tabs = this.getAllTabs({ includeDisabled: false });
802
+ this.panels = this.getAllPanels();
803
+
804
+ const allTabs = this.getAllTabs({ includeDisabled: true });
805
+ const enabledTabs = this.getAllTabs({ includeDisabled: false });
806
+ const size = enabledTabs.length;
807
+
808
+ const currentTabs = new Set(allTabs);
809
+
810
+ for (const tab of this.observedTabs) {
811
+ if (!currentTabs.has(tab)) {
812
+ this.resizeObserver.unobserve(tab);
813
+ this.observedTabs.delete(tab);
814
+ }
815
+ }
816
+
817
+ allTabs.forEach(tab => {
818
+ tab.centered = this.centered;
819
+ if (tab.disabled) {
820
+ tab.removeAttribute('aria-posinset');
821
+ tab.removeAttribute('aria-setsize');
822
+ } else {
823
+ const i = enabledTabs.indexOf(tab);
824
+ tab.setAttribute('aria-posinset', String(i + 1));
825
+ tab.setAttribute('aria-setsize', String(size));
826
+ }
827
+
828
+ if (!this.observedTabs.has(tab)) {
829
+ this.resizeObserver.observe(tab);
830
+ this.observedTabs.add(tab);
831
+ }
832
+ });
833
+
834
+ this.setScrollControls();
835
+ this.setAriaLabels();
836
+ }
837
+ }
838
+
839
+ export default NileNavTabGroup;
840
+
841
+ declare global {
842
+ interface HTMLElementTagNameMap {
843
+ 'nile-nav-tab-group': NileNavTabGroup;
844
+ }
845
+ }