@smwb/ui-solid 0.13.0 → 0.14.0

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 (411) hide show
  1. package/README.md +564 -0
  2. package/dist/assets/styles/less/components/appBar/appBar.entry.css +1 -1
  3. package/dist/assets/styles/less/components/button/button.entry.css +1 -1
  4. package/dist/assets/styles/less/components/checkbox/checkbox.entry.css +1 -1
  5. package/dist/assets/styles/less/components/chip/chip.entry.css +1 -1
  6. package/dist/assets/styles/less/components/commandPalette/commandPalette.entry.css +1 -0
  7. package/dist/assets/styles/less/components/composer/composer.entry.css +1 -0
  8. package/dist/assets/styles/less/components/diffStat/diffStat.entry.css +1 -0
  9. package/dist/assets/styles/less/components/kbd/kbd.entry.css +1 -0
  10. package/dist/assets/styles/less/components/list/list.entry.css +1 -1
  11. package/dist/assets/styles/less/components/qrCode/qrCode.entry.css +1 -0
  12. package/dist/assets/styles/less/components/radioButton/radioButton.entry.css +1 -1
  13. package/dist/assets/styles/less/components/selectField/selectField.entry.css +1 -1
  14. package/dist/assets/styles/less/components/sidebar/sidebar.entry.css +1 -1
  15. package/dist/assets/styles/less/components/statusDot/statusDot.entry.css +1 -0
  16. package/dist/assets/styles/less/components/swimlanes/swimlanes.entry.css +1 -0
  17. package/dist/assets/styles/less/components/tabGroups/tabGroups.entry.css +1 -0
  18. package/dist/assets/styles/less/components/tabs/tab/tab.entry.css +1 -1
  19. package/dist/assets/styles/less/components/tabs/tabPanel/tabPanel.entry.css +1 -0
  20. package/dist/assets/styles/less/components/tabs/tabs.entry.css +1 -1
  21. package/dist/assets/styles/less/components/textField/textField.entry.css +1 -1
  22. package/dist/assets/styles/less/components/toggle/toggle.entry.css +1 -1
  23. package/dist/assets/styles/less/components/treeView/treeView.entry.css +1 -1
  24. package/dist/assets/styles/less/tokens.entry.css +1 -1
  25. package/dist/components/dataDisplay/chat/chat.d.ts +42 -0
  26. package/dist/components/dataDisplay/chat/chat.js +116 -57
  27. package/dist/components/dataDisplay/chat/chat.js.map +1 -1
  28. package/dist/components/dataDisplay/chat/chatComposer.d.ts +17 -0
  29. package/dist/components/dataDisplay/chat/chatComposer.js +21 -19
  30. package/dist/components/dataDisplay/chat/chatComposer.js.map +1 -1
  31. package/dist/components/dataDisplay/chat/index.d.ts +1 -1
  32. package/dist/components/dataDisplay/chat/utils.d.ts +14 -0
  33. package/dist/components/dataDisplay/chat/utils.js.map +1 -1
  34. package/dist/components/dataDisplay/dagView/dagView.js +238 -223
  35. package/dist/components/dataDisplay/dagView/dagView.js.map +1 -1
  36. package/dist/components/dataDisplay/diffStat/diffStat.d.ts +55 -0
  37. package/dist/components/dataDisplay/diffStat/diffStat.js +70 -0
  38. package/dist/components/dataDisplay/diffStat/diffStat.js.map +1 -0
  39. package/dist/components/dataDisplay/diffStat/index.d.ts +2 -0
  40. package/dist/components/dataDisplay/kbd/index.d.ts +4 -0
  41. package/dist/components/dataDisplay/kbd/kbd.d.ts +41 -0
  42. package/dist/components/dataDisplay/kbd/kbd.js +47 -0
  43. package/dist/components/dataDisplay/kbd/kbd.js.map +1 -0
  44. package/dist/components/dataDisplay/kbd/kbdKeys.d.ts +18 -0
  45. package/dist/components/dataDisplay/kbd/kbdKeys.js +70 -0
  46. package/dist/components/dataDisplay/kbd/kbdKeys.js.map +1 -0
  47. package/dist/components/dataDisplay/list/listItem.d.ts +34 -0
  48. package/dist/components/dataDisplay/list/listItem.js +57 -20
  49. package/dist/components/dataDisplay/list/listItem.js.map +1 -1
  50. package/dist/components/dataDisplay/qrCode/index.d.ts +2 -0
  51. package/dist/components/dataDisplay/qrCode/qrCode.d.ts +30 -0
  52. package/dist/components/dataDisplay/qrCode/qrCode.js +88 -0
  53. package/dist/components/dataDisplay/qrCode/qrCode.js.map +1 -0
  54. package/dist/components/dataDisplay/qrCode/qrCodePath.d.ts +7 -0
  55. package/dist/components/dataDisplay/qrCode/qrCodePath.js +20 -0
  56. package/dist/components/dataDisplay/qrCode/qrCodePath.js.map +1 -0
  57. package/dist/components/dataDisplay/statusDot/index.d.ts +2 -0
  58. package/dist/components/dataDisplay/statusDot/statusDot.d.ts +35 -0
  59. package/dist/components/dataDisplay/statusDot/statusDot.js +31 -0
  60. package/dist/components/dataDisplay/statusDot/statusDot.js.map +1 -0
  61. package/dist/components/dataDisplay/swimlanes/index.d.ts +2 -0
  62. package/dist/components/dataDisplay/swimlanes/swimlanes.d.ts +75 -0
  63. package/dist/components/dataDisplay/swimlanes/swimlanes.js +361 -0
  64. package/dist/components/dataDisplay/swimlanes/swimlanes.js.map +1 -0
  65. package/dist/components/dataDisplay/treeView/treeView.d.ts +20 -0
  66. package/dist/components/dataDisplay/treeView/treeView.js +161 -95
  67. package/dist/components/dataDisplay/treeView/treeView.js.map +1 -1
  68. package/dist/components/feedBack/modal/modal.d.ts +9 -0
  69. package/dist/components/feedBack/modal/modal.js +68 -54
  70. package/dist/components/feedBack/modal/modal.js.map +1 -1
  71. package/dist/components/inputs/button/button.d.ts +12 -0
  72. package/dist/components/inputs/button/button.js +44 -37
  73. package/dist/components/inputs/button/button.js.map +1 -1
  74. package/dist/components/inputs/composer/composer.d.ts +148 -0
  75. package/dist/components/inputs/composer/composer.js +276 -0
  76. package/dist/components/inputs/composer/composer.js.map +1 -0
  77. package/dist/components/inputs/composer/composerFiles.d.ts +26 -0
  78. package/dist/components/inputs/composer/composerFiles.js +18 -0
  79. package/dist/components/inputs/composer/composerFiles.js.map +1 -0
  80. package/dist/components/inputs/composer/composerKeys.d.ts +29 -0
  81. package/dist/components/inputs/composer/composerKeys.js +7 -0
  82. package/dist/components/inputs/composer/composerKeys.js.map +1 -0
  83. package/dist/components/inputs/composer/index.d.ts +3 -0
  84. package/dist/components/layout/sheet/sheet.d.ts +12 -0
  85. package/dist/components/layout/sheet/sheet.js +100 -86
  86. package/dist/components/layout/sheet/sheet.js.map +1 -1
  87. package/dist/components/layout/tabGroups/index.d.ts +4 -0
  88. package/dist/components/layout/tabGroups/tabGroups.d.ts +102 -0
  89. package/dist/components/layout/tabGroups/tabGroups.js +895 -0
  90. package/dist/components/layout/tabGroups/tabGroups.js.map +1 -0
  91. package/dist/components/layout/tabGroups/tabGroupsFocus.d.ts +55 -0
  92. package/dist/components/layout/tabGroups/tabGroupsFocus.js +192 -0
  93. package/dist/components/layout/tabGroups/tabGroupsFocus.js.map +1 -0
  94. package/dist/components/layout/tabGroups/tabGroupsLayout.d.ts +250 -0
  95. package/dist/components/layout/tabGroups/tabGroupsLayout.js +480 -0
  96. package/dist/components/layout/tabGroups/tabGroupsLayout.js.map +1 -0
  97. package/dist/components/navigation/appBar/appBar.d.ts +22 -0
  98. package/dist/components/navigation/appBar/appBar.js +60 -22
  99. package/dist/components/navigation/appBar/appBar.js.map +1 -1
  100. package/dist/components/navigation/commandPalette/commandPalette.d.ts +126 -0
  101. package/dist/components/navigation/commandPalette/commandPalette.js +457 -0
  102. package/dist/components/navigation/commandPalette/commandPalette.js.map +1 -0
  103. package/dist/components/navigation/commandPalette/commandPaletteFilter.d.ts +11 -0
  104. package/dist/components/navigation/commandPalette/commandPaletteFilter.js +15 -0
  105. package/dist/components/navigation/commandPalette/commandPaletteFilter.js.map +1 -0
  106. package/dist/components/navigation/commandPalette/commandPaletteNavigation.d.ts +13 -0
  107. package/dist/components/navigation/commandPalette/commandPaletteNavigation.js +38 -0
  108. package/dist/components/navigation/commandPalette/commandPaletteNavigation.js.map +1 -0
  109. package/dist/components/navigation/commandPalette/commandPaletteTypes.d.ts +39 -0
  110. package/dist/components/navigation/commandPalette/index.d.ts +6 -0
  111. package/dist/components/navigation/sidebar/sidebar.d.ts +10 -0
  112. package/dist/components/navigation/sidebar/sidebar.js +12 -10
  113. package/dist/components/navigation/sidebar/sidebar.js.map +1 -1
  114. package/dist/components/navigation/tabs/index.d.ts +3 -1
  115. package/dist/components/navigation/tabs/tab/tab.d.ts +89 -2
  116. package/dist/components/navigation/tabs/tab/tab.js +164 -44
  117. package/dist/components/navigation/tabs/tab/tab.js.map +1 -1
  118. package/dist/components/navigation/tabs/tabPanel/tabPanel.d.ts +20 -0
  119. package/dist/components/navigation/tabs/tabPanel/tabPanel.js +34 -0
  120. package/dist/components/navigation/tabs/tabPanel/tabPanel.js.map +1 -0
  121. package/dist/components/navigation/tabs/tabs.d.ts +73 -3
  122. package/dist/components/navigation/tabs/tabs.js +387 -160
  123. package/dist/components/navigation/tabs/tabs.js.map +1 -1
  124. package/dist/components/navigation/tabs/tabs.utils.d.ts +20 -0
  125. package/dist/components/navigation/tabs/tabs.utils.js +11 -0
  126. package/dist/components/navigation/tabs/tabs.utils.js.map +1 -0
  127. package/dist/components/navigation/tabs/tabsContext.d.ts +31 -1
  128. package/dist/components/navigation/tabs/tabsContext.js.map +1 -1
  129. package/dist/headless/components/dataDisplay/chat/chat.js +116 -57
  130. package/dist/headless/components/dataDisplay/chat/chat.js.map +1 -1
  131. package/dist/headless/components/dataDisplay/chat/chatComposer.js +21 -19
  132. package/dist/headless/components/dataDisplay/chat/chatComposer.js.map +1 -1
  133. package/dist/headless/components/dataDisplay/chat/utils.js.map +1 -1
  134. package/dist/headless/components/dataDisplay/dagView/dagView.js +238 -223
  135. package/dist/headless/components/dataDisplay/dagView/dagView.js.map +1 -1
  136. package/dist/headless/components/dataDisplay/diffStat/diffStat.js +68 -0
  137. package/dist/headless/components/dataDisplay/diffStat/diffStat.js.map +1 -0
  138. package/dist/headless/components/dataDisplay/kbd/kbd.js +45 -0
  139. package/dist/headless/components/dataDisplay/kbd/kbd.js.map +1 -0
  140. package/dist/headless/components/dataDisplay/kbd/kbdKeys.js +70 -0
  141. package/dist/headless/components/dataDisplay/kbd/kbdKeys.js.map +1 -0
  142. package/dist/headless/components/dataDisplay/list/listItem.js +57 -20
  143. package/dist/headless/components/dataDisplay/list/listItem.js.map +1 -1
  144. package/dist/headless/components/dataDisplay/qrCode/qrCode.js +86 -0
  145. package/dist/headless/components/dataDisplay/qrCode/qrCode.js.map +1 -0
  146. package/dist/headless/components/dataDisplay/qrCode/qrCodePath.js +20 -0
  147. package/dist/headless/components/dataDisplay/qrCode/qrCodePath.js.map +1 -0
  148. package/dist/headless/components/dataDisplay/statusDot/statusDot.js +29 -0
  149. package/dist/headless/components/dataDisplay/statusDot/statusDot.js.map +1 -0
  150. package/dist/headless/components/dataDisplay/swimlanes/swimlanes.js +359 -0
  151. package/dist/headless/components/dataDisplay/swimlanes/swimlanes.js.map +1 -0
  152. package/dist/headless/components/dataDisplay/treeView/treeView.js +161 -95
  153. package/dist/headless/components/dataDisplay/treeView/treeView.js.map +1 -1
  154. package/dist/headless/components/feedBack/modal/modal.js +68 -54
  155. package/dist/headless/components/feedBack/modal/modal.js.map +1 -1
  156. package/dist/headless/components/inputs/button/button.js +42 -35
  157. package/dist/headless/components/inputs/button/button.js.map +1 -1
  158. package/dist/headless/components/inputs/composer/composer.js +274 -0
  159. package/dist/headless/components/inputs/composer/composer.js.map +1 -0
  160. package/dist/headless/components/inputs/composer/composerFiles.js +18 -0
  161. package/dist/headless/components/inputs/composer/composerFiles.js.map +1 -0
  162. package/dist/headless/components/inputs/composer/composerKeys.js +7 -0
  163. package/dist/headless/components/inputs/composer/composerKeys.js.map +1 -0
  164. package/dist/headless/components/layout/sheet/sheet.js +100 -86
  165. package/dist/headless/components/layout/sheet/sheet.js.map +1 -1
  166. package/dist/headless/components/layout/tabGroups/tabGroups.js +893 -0
  167. package/dist/headless/components/layout/tabGroups/tabGroups.js.map +1 -0
  168. package/dist/headless/components/layout/tabGroups/tabGroupsFocus.js +192 -0
  169. package/dist/headless/components/layout/tabGroups/tabGroupsFocus.js.map +1 -0
  170. package/dist/headless/components/layout/tabGroups/tabGroupsLayout.js +480 -0
  171. package/dist/headless/components/layout/tabGroups/tabGroupsLayout.js.map +1 -0
  172. package/dist/headless/components/navigation/appBar/appBar.js +64 -26
  173. package/dist/headless/components/navigation/appBar/appBar.js.map +1 -1
  174. package/dist/headless/components/navigation/commandPalette/commandPalette.js +455 -0
  175. package/dist/headless/components/navigation/commandPalette/commandPalette.js.map +1 -0
  176. package/dist/headless/components/navigation/commandPalette/commandPaletteFilter.js +15 -0
  177. package/dist/headless/components/navigation/commandPalette/commandPaletteFilter.js.map +1 -0
  178. package/dist/headless/components/navigation/commandPalette/commandPaletteNavigation.js +38 -0
  179. package/dist/headless/components/navigation/commandPalette/commandPaletteNavigation.js.map +1 -0
  180. package/dist/headless/components/navigation/sidebar/sidebar.js +28 -26
  181. package/dist/headless/components/navigation/sidebar/sidebar.js.map +1 -1
  182. package/dist/headless/components/navigation/tabs/tab/tab.js +164 -44
  183. package/dist/headless/components/navigation/tabs/tab/tab.js.map +1 -1
  184. package/dist/headless/components/navigation/tabs/tabPanel/tabPanel.js +32 -0
  185. package/dist/headless/components/navigation/tabs/tabPanel/tabPanel.js.map +1 -0
  186. package/dist/headless/components/navigation/tabs/tabs.js +387 -160
  187. package/dist/headless/components/navigation/tabs/tabs.js.map +1 -1
  188. package/dist/headless/components/navigation/tabs/tabs.utils.js +11 -0
  189. package/dist/headless/components/navigation/tabs/tabs.utils.js.map +1 -0
  190. package/dist/headless/components/navigation/tabs/tabsContext.js.map +1 -1
  191. package/dist/headless/index.js +405 -367
  192. package/dist/headless/index.js.map +1 -1
  193. package/dist/headless/primitives/createFocusTrap.js +17 -17
  194. package/dist/headless/primitives/createFocusTrap.js.map +1 -1
  195. package/dist/headless/primitives/createKeyboardShortcut.js +16 -0
  196. package/dist/headless/primitives/createKeyboardShortcut.js.map +1 -0
  197. package/dist/headless/primitives/restoreFocus.js +10 -0
  198. package/dist/headless/primitives/restoreFocus.js.map +1 -0
  199. package/dist/headless/source/components/dataDisplay/chat/chat.jsx +38 -4
  200. package/dist/headless/source/components/dataDisplay/chat/chat.jsx.map +1 -1
  201. package/dist/headless/source/components/dataDisplay/chat/chatComposer.jsx +1 -0
  202. package/dist/headless/source/components/dataDisplay/chat/chatComposer.jsx.map +1 -1
  203. package/dist/headless/source/components/dataDisplay/chat/index.js.map +1 -1
  204. package/dist/headless/source/components/dataDisplay/chat/utils.js.map +2 -2
  205. package/dist/headless/source/components/dataDisplay/dagView/dagView.jsx +30 -21
  206. package/dist/headless/source/components/dataDisplay/dagView/dagView.jsx.map +1 -1
  207. package/dist/headless/source/components/dataDisplay/diffStat/diffStat.jsx +76 -0
  208. package/dist/headless/source/components/dataDisplay/diffStat/diffStat.jsx.map +1 -0
  209. package/dist/headless/source/components/dataDisplay/diffStat/index.js +19 -0
  210. package/dist/headless/source/components/dataDisplay/diffStat/index.js.map +7 -0
  211. package/dist/headless/source/components/dataDisplay/kbd/index.js +9 -0
  212. package/dist/headless/source/components/dataDisplay/kbd/index.js.map +7 -0
  213. package/dist/headless/source/components/dataDisplay/kbd/kbd.jsx +48 -0
  214. package/dist/headless/source/components/dataDisplay/kbd/kbd.jsx.map +1 -0
  215. package/dist/headless/source/components/dataDisplay/kbd/kbdKeys.js +79 -0
  216. package/dist/headless/source/components/dataDisplay/kbd/kbdKeys.js.map +7 -0
  217. package/dist/headless/source/components/dataDisplay/list/listItem.jsx +37 -7
  218. package/dist/headless/source/components/dataDisplay/list/listItem.jsx.map +1 -1
  219. package/dist/headless/source/components/dataDisplay/qrCode/index.js +6 -0
  220. package/dist/headless/source/components/dataDisplay/qrCode/index.js.map +7 -0
  221. package/dist/headless/source/components/dataDisplay/qrCode/qrCode.jsx +94 -0
  222. package/dist/headless/source/components/dataDisplay/qrCode/qrCode.jsx.map +1 -0
  223. package/dist/headless/source/components/dataDisplay/qrCode/qrCodePath.js +24 -0
  224. package/dist/headless/source/components/dataDisplay/qrCode/qrCodePath.js.map +7 -0
  225. package/dist/headless/source/components/dataDisplay/statusDot/index.js +6 -0
  226. package/dist/headless/source/components/dataDisplay/statusDot/index.js.map +7 -0
  227. package/dist/headless/source/components/dataDisplay/statusDot/statusDot.jsx +28 -0
  228. package/dist/headless/source/components/dataDisplay/statusDot/statusDot.jsx.map +1 -0
  229. package/dist/headless/source/components/dataDisplay/swimlanes/index.js +15 -0
  230. package/dist/headless/source/components/dataDisplay/swimlanes/index.js.map +7 -0
  231. package/dist/headless/source/components/dataDisplay/swimlanes/swimlanes.jsx +450 -0
  232. package/dist/headless/source/components/dataDisplay/swimlanes/swimlanes.jsx.map +1 -0
  233. package/dist/headless/source/components/dataDisplay/treeView/treeView.jsx +118 -37
  234. package/dist/headless/source/components/dataDisplay/treeView/treeView.jsx.map +1 -1
  235. package/dist/headless/source/components/feedBack/modal/modal.jsx +34 -4
  236. package/dist/headless/source/components/feedBack/modal/modal.jsx.map +1 -1
  237. package/dist/headless/source/components/inputs/button/button.jsx +6 -2
  238. package/dist/headless/source/components/inputs/button/button.jsx.map +1 -1
  239. package/dist/headless/source/components/inputs/composer/composer.jsx +319 -0
  240. package/dist/headless/source/components/inputs/composer/composer.jsx.map +1 -0
  241. package/dist/headless/source/components/inputs/composer/composerFiles.js +27 -0
  242. package/dist/headless/source/components/inputs/composer/composerFiles.js.map +7 -0
  243. package/dist/headless/source/components/inputs/composer/composerKeys.js +11 -0
  244. package/dist/headless/source/components/inputs/composer/composerKeys.js.map +7 -0
  245. package/dist/headless/source/components/inputs/composer/index.js +5 -0
  246. package/dist/headless/source/components/inputs/composer/index.js.map +7 -0
  247. package/dist/headless/source/components/layout/sheet/sheet.jsx +33 -11
  248. package/dist/headless/source/components/layout/sheet/sheet.jsx.map +1 -1
  249. package/dist/headless/source/components/layout/tabGroups/index.js +8 -0
  250. package/dist/headless/source/components/layout/tabGroups/index.js.map +7 -0
  251. package/dist/headless/source/components/layout/tabGroups/tabGroups.jsx +1117 -0
  252. package/dist/headless/source/components/layout/tabGroups/tabGroups.jsx.map +1 -0
  253. package/dist/headless/source/components/layout/tabGroups/tabGroupsFocus.js +222 -0
  254. package/dist/headless/source/components/layout/tabGroups/tabGroupsFocus.js.map +7 -0
  255. package/dist/headless/source/components/layout/tabGroups/tabGroupsLayout.js +629 -0
  256. package/dist/headless/source/components/layout/tabGroups/tabGroupsLayout.js.map +7 -0
  257. package/dist/headless/source/components/navigation/appBar/appBar.jsx +27 -3
  258. package/dist/headless/source/components/navigation/appBar/appBar.jsx.map +1 -1
  259. package/dist/headless/source/components/navigation/commandPalette/commandPalette.jsx +506 -0
  260. package/dist/headless/source/components/navigation/commandPalette/commandPalette.jsx.map +1 -0
  261. package/dist/headless/source/components/navigation/commandPalette/commandPaletteFilter.js +16 -0
  262. package/dist/headless/source/components/navigation/commandPalette/commandPaletteFilter.js.map +7 -0
  263. package/dist/headless/source/components/navigation/commandPalette/commandPaletteNavigation.js +54 -0
  264. package/dist/headless/source/components/navigation/commandPalette/commandPaletteNavigation.js.map +7 -0
  265. package/dist/headless/source/components/navigation/commandPalette/commandPaletteTypes.js +1 -0
  266. package/dist/headless/source/components/navigation/commandPalette/commandPaletteTypes.js.map +7 -0
  267. package/dist/headless/source/components/navigation/commandPalette/index.js +11 -0
  268. package/dist/headless/source/components/navigation/commandPalette/index.js.map +7 -0
  269. package/dist/headless/source/components/navigation/sidebar/sidebar.jsx +5 -2
  270. package/dist/headless/source/components/navigation/sidebar/sidebar.jsx.map +1 -1
  271. package/dist/headless/source/components/navigation/tabs/index.js +2 -0
  272. package/dist/headless/source/components/navigation/tabs/index.js.map +2 -2
  273. package/dist/headless/source/components/navigation/tabs/tab/tab.jsx +135 -8
  274. package/dist/headless/source/components/navigation/tabs/tab/tab.jsx.map +1 -1
  275. package/dist/headless/source/components/navigation/tabs/tabPanel/tabPanel.jsx +28 -0
  276. package/dist/headless/source/components/navigation/tabs/tabPanel/tabPanel.jsx.map +1 -0
  277. package/dist/headless/source/components/navigation/tabs/tabs.jsx +361 -13
  278. package/dist/headless/source/components/navigation/tabs/tabs.jsx.map +1 -1
  279. package/dist/headless/source/components/navigation/tabs/tabs.utils.js +18 -0
  280. package/dist/headless/source/components/navigation/tabs/tabs.utils.js.map +7 -0
  281. package/dist/headless/source/components/navigation/tabs/tabsContext.js.map +2 -2
  282. package/dist/headless/source/index.js +41 -1
  283. package/dist/headless/source/index.js.map +2 -2
  284. package/dist/headless/source/primitives/createFocusTrap.js +8 -3
  285. package/dist/headless/source/primitives/createFocusTrap.js.map +2 -2
  286. package/dist/headless/source/primitives/createKeyboardShortcut.js +23 -0
  287. package/dist/headless/source/primitives/createKeyboardShortcut.js.map +7 -0
  288. package/dist/headless/source/primitives/restoreFocus.js +14 -0
  289. package/dist/headless/source/primitives/restoreFocus.js.map +7 -0
  290. package/dist/headless/source/utils/keyboardShortcut.js +74 -0
  291. package/dist/headless/source/utils/keyboardShortcut.js.map +7 -0
  292. package/dist/headless/source/utils/linkAttrs.js +16 -0
  293. package/dist/headless/source/utils/linkAttrs.js.map +7 -0
  294. package/dist/headless/utils/keyboardShortcut.js +48 -0
  295. package/dist/headless/utils/keyboardShortcut.js.map +1 -0
  296. package/dist/headless/utils/linkAttrs.js +13 -0
  297. package/dist/headless/utils/linkAttrs.js.map +1 -0
  298. package/dist/index.d.ts +24 -3
  299. package/dist/index.js +405 -367
  300. package/dist/index.js.map +1 -1
  301. package/dist/primitives/createFocusTrap.d.ts +5 -0
  302. package/dist/primitives/createFocusTrap.js +17 -17
  303. package/dist/primitives/createFocusTrap.js.map +1 -1
  304. package/dist/primitives/createKeyboardShortcut.d.ts +23 -0
  305. package/dist/primitives/createKeyboardShortcut.js +16 -0
  306. package/dist/primitives/createKeyboardShortcut.js.map +1 -0
  307. package/dist/primitives/restoreFocus.d.ts +8 -0
  308. package/dist/primitives/restoreFocus.js +10 -0
  309. package/dist/primitives/restoreFocus.js.map +1 -0
  310. package/dist/source/components/dataDisplay/chat/chat.jsx +38 -4
  311. package/dist/source/components/dataDisplay/chat/chat.jsx.map +1 -1
  312. package/dist/source/components/dataDisplay/chat/chatComposer.jsx +1 -0
  313. package/dist/source/components/dataDisplay/chat/chatComposer.jsx.map +1 -1
  314. package/dist/source/components/dataDisplay/chat/index.js.map +1 -1
  315. package/dist/source/components/dataDisplay/chat/utils.js.map +2 -2
  316. package/dist/source/components/dataDisplay/dagView/dagView.jsx +30 -21
  317. package/dist/source/components/dataDisplay/dagView/dagView.jsx.map +1 -1
  318. package/dist/source/components/dataDisplay/diffStat/diffStat.jsx +78 -0
  319. package/dist/source/components/dataDisplay/diffStat/diffStat.jsx.map +1 -0
  320. package/dist/source/components/dataDisplay/diffStat/index.js +19 -0
  321. package/dist/source/components/dataDisplay/diffStat/index.js.map +7 -0
  322. package/dist/source/components/dataDisplay/kbd/index.js +9 -0
  323. package/dist/source/components/dataDisplay/kbd/index.js.map +7 -0
  324. package/dist/source/components/dataDisplay/kbd/kbd.jsx +50 -0
  325. package/dist/source/components/dataDisplay/kbd/kbd.jsx.map +1 -0
  326. package/dist/source/components/dataDisplay/kbd/kbdKeys.js +79 -0
  327. package/dist/source/components/dataDisplay/kbd/kbdKeys.js.map +7 -0
  328. package/dist/source/components/dataDisplay/list/listItem.jsx +37 -7
  329. package/dist/source/components/dataDisplay/list/listItem.jsx.map +1 -1
  330. package/dist/source/components/dataDisplay/qrCode/index.js +6 -0
  331. package/dist/source/components/dataDisplay/qrCode/index.js.map +7 -0
  332. package/dist/source/components/dataDisplay/qrCode/qrCode.jsx +96 -0
  333. package/dist/source/components/dataDisplay/qrCode/qrCode.jsx.map +1 -0
  334. package/dist/source/components/dataDisplay/qrCode/qrCodePath.js +24 -0
  335. package/dist/source/components/dataDisplay/qrCode/qrCodePath.js.map +7 -0
  336. package/dist/source/components/dataDisplay/statusDot/index.js +6 -0
  337. package/dist/source/components/dataDisplay/statusDot/index.js.map +7 -0
  338. package/dist/source/components/dataDisplay/statusDot/statusDot.jsx +30 -0
  339. package/dist/source/components/dataDisplay/statusDot/statusDot.jsx.map +1 -0
  340. package/dist/source/components/dataDisplay/swimlanes/index.js +15 -0
  341. package/dist/source/components/dataDisplay/swimlanes/index.js.map +7 -0
  342. package/dist/source/components/dataDisplay/swimlanes/swimlanes.jsx +452 -0
  343. package/dist/source/components/dataDisplay/swimlanes/swimlanes.jsx.map +1 -0
  344. package/dist/source/components/dataDisplay/treeView/treeView.jsx +118 -37
  345. package/dist/source/components/dataDisplay/treeView/treeView.jsx.map +1 -1
  346. package/dist/source/components/feedBack/modal/modal.jsx +34 -4
  347. package/dist/source/components/feedBack/modal/modal.jsx.map +1 -1
  348. package/dist/source/components/inputs/button/button.jsx +6 -2
  349. package/dist/source/components/inputs/button/button.jsx.map +1 -1
  350. package/dist/source/components/inputs/composer/composer.jsx +321 -0
  351. package/dist/source/components/inputs/composer/composer.jsx.map +1 -0
  352. package/dist/source/components/inputs/composer/composerFiles.js +27 -0
  353. package/dist/source/components/inputs/composer/composerFiles.js.map +7 -0
  354. package/dist/source/components/inputs/composer/composerKeys.js +11 -0
  355. package/dist/source/components/inputs/composer/composerKeys.js.map +7 -0
  356. package/dist/source/components/inputs/composer/index.js +5 -0
  357. package/dist/source/components/inputs/composer/index.js.map +7 -0
  358. package/dist/source/components/layout/sheet/sheet.jsx +33 -11
  359. package/dist/source/components/layout/sheet/sheet.jsx.map +1 -1
  360. package/dist/source/components/layout/tabGroups/index.js +8 -0
  361. package/dist/source/components/layout/tabGroups/index.js.map +7 -0
  362. package/dist/source/components/layout/tabGroups/tabGroups.jsx +1119 -0
  363. package/dist/source/components/layout/tabGroups/tabGroups.jsx.map +1 -0
  364. package/dist/source/components/layout/tabGroups/tabGroupsFocus.js +222 -0
  365. package/dist/source/components/layout/tabGroups/tabGroupsFocus.js.map +7 -0
  366. package/dist/source/components/layout/tabGroups/tabGroupsLayout.js +629 -0
  367. package/dist/source/components/layout/tabGroups/tabGroupsLayout.js.map +7 -0
  368. package/dist/source/components/navigation/appBar/appBar.jsx +27 -3
  369. package/dist/source/components/navigation/appBar/appBar.jsx.map +1 -1
  370. package/dist/source/components/navigation/commandPalette/commandPalette.jsx +508 -0
  371. package/dist/source/components/navigation/commandPalette/commandPalette.jsx.map +1 -0
  372. package/dist/source/components/navigation/commandPalette/commandPaletteFilter.js +16 -0
  373. package/dist/source/components/navigation/commandPalette/commandPaletteFilter.js.map +7 -0
  374. package/dist/source/components/navigation/commandPalette/commandPaletteNavigation.js +54 -0
  375. package/dist/source/components/navigation/commandPalette/commandPaletteNavigation.js.map +7 -0
  376. package/dist/source/components/navigation/commandPalette/commandPaletteTypes.js +1 -0
  377. package/dist/source/components/navigation/commandPalette/commandPaletteTypes.js.map +7 -0
  378. package/dist/source/components/navigation/commandPalette/index.js +11 -0
  379. package/dist/source/components/navigation/commandPalette/index.js.map +7 -0
  380. package/dist/source/components/navigation/sidebar/sidebar.jsx +5 -2
  381. package/dist/source/components/navigation/sidebar/sidebar.jsx.map +1 -1
  382. package/dist/source/components/navigation/tabs/index.js +2 -0
  383. package/dist/source/components/navigation/tabs/index.js.map +2 -2
  384. package/dist/source/components/navigation/tabs/tab/tab.jsx +135 -8
  385. package/dist/source/components/navigation/tabs/tab/tab.jsx.map +1 -1
  386. package/dist/source/components/navigation/tabs/tabPanel/tabPanel.jsx +30 -0
  387. package/dist/source/components/navigation/tabs/tabPanel/tabPanel.jsx.map +1 -0
  388. package/dist/source/components/navigation/tabs/tabs.jsx +361 -13
  389. package/dist/source/components/navigation/tabs/tabs.jsx.map +1 -1
  390. package/dist/source/components/navigation/tabs/tabs.utils.js +18 -0
  391. package/dist/source/components/navigation/tabs/tabs.utils.js.map +7 -0
  392. package/dist/source/components/navigation/tabs/tabsContext.js.map +2 -2
  393. package/dist/source/index.js +41 -1
  394. package/dist/source/index.js.map +2 -2
  395. package/dist/source/primitives/createFocusTrap.js +8 -3
  396. package/dist/source/primitives/createFocusTrap.js.map +2 -2
  397. package/dist/source/primitives/createKeyboardShortcut.js +23 -0
  398. package/dist/source/primitives/createKeyboardShortcut.js.map +7 -0
  399. package/dist/source/primitives/restoreFocus.js +14 -0
  400. package/dist/source/primitives/restoreFocus.js.map +7 -0
  401. package/dist/source/utils/keyboardShortcut.js +74 -0
  402. package/dist/source/utils/keyboardShortcut.js.map +7 -0
  403. package/dist/source/utils/linkAttrs.js +16 -0
  404. package/dist/source/utils/linkAttrs.js.map +7 -0
  405. package/dist/utils/keyboardShortcut.d.ts +12 -0
  406. package/dist/utils/keyboardShortcut.js +48 -0
  407. package/dist/utils/keyboardShortcut.js.map +1 -0
  408. package/dist/utils/linkAttrs.d.ts +13 -0
  409. package/dist/utils/linkAttrs.js +13 -0
  410. package/dist/utils/linkAttrs.js.map +1 -0
  411. package/package.json +66 -3
package/README.md CHANGED
@@ -59,6 +59,380 @@ component sets the same prop there itself, the two meet by these rules, the same
59
59
  `Chip`, `Card` or `MenuItem` (for roving focus, say) replaces the component's default. Passing `undefined` keeps
60
60
  the default.
61
61
 
62
+ ### Composer
63
+
64
+ `Composer` is a task input for chat-style screens: a multi-line field that grows from `minRows` to `maxRows`, a
65
+ `toolbar` slot for chip selectors, a send button that turns into Stop while `busy`, and optional attachments and
66
+ character counter. Ctrl/⌘+Enter sends and Enter adds a line; `sendKey="enter"` swaps that for a reply line. It is in
67
+ the root entry and in the `@smwb/ui-solid/composer` subpath.
68
+
69
+ ```tsx
70
+ import { Composer } from "@smwb/ui-solid/composer";
71
+
72
+ function TaskInput(props: { run: (task: string) => void; cancel: () => void; running: boolean }) {
73
+ return (
74
+ <Composer
75
+ aria-label="Task for the agent"
76
+ placeholder="Describe the task"
77
+ maxLength={2000}
78
+ busy={props.running}
79
+ onSend={props.run}
80
+ onStop={props.cancel}
81
+ toolbar={<span>Agent · Model</span>}
82
+ />
83
+ );
84
+ }
85
+ ```
86
+
87
+ Name the field with `aria-label` or `aria-labelledby`: a placeholder is not a name. `onAttach` turns on the attach
88
+ button and makes the whole composer a drop target for files (dragged text and links still go into the field);
89
+ `accept`, `multiple` and `maxFiles` apply to a pick and to a drop alike, and `onAttach` is never called with an empty
90
+ list. The composer keeps no file state, so show the picked files in the `attachments` slot yourself. The second click
91
+ of a double-click on the action button is ignored, so sending does not stop the run it has just started. The text
92
+ renders on the server as the textarea's content, so a `defaultValue` or `value` survives hydration.
93
+
94
+ `Chat` draws a `Composer` as its footer now, so a chat gets the growing field, the Stop button and the file drop
95
+ target without a separate component. See "Migrating from `ChatComposer`" below for what changed in `Chat`.
96
+
97
+ ### Migrating from `ChatComposer`
98
+
99
+ `Chat` builds its footer out of `Composer` now. `ChatComposer` still works and `Chat` still draws it when you ask for
100
+ it, but both are deprecated and go in the next major.
101
+
102
+ **Stop and the agent's reply** are new props on `Chat`, the same pair `Composer` has: while `busy` the send button
103
+ becomes Stop and calls `onStop`, the keys do not send and the text stays editable. Everything else the composer needs
104
+ goes through one `composerProps` object.
105
+
106
+ ```tsx
107
+ import { Chat } from "@smwb/ui-solid";
108
+
109
+ <Chat
110
+ messages={messages()}
111
+ onSendMessage={send}
112
+ busy={replying()}
113
+ onStop={cancel}
114
+ composerProps={{ maxLength: 2000, counter: true, toolbar: <span>Agent · Model</span> }}
115
+ />;
116
+ ```
117
+
118
+ **Keys.** Enter still sends, but the modifiers swap sides — Ctrl/⌘+Enter now sends instead of adding a line, and
119
+ Alt+Enter no longer sends: it is left to the browser, which adds a line on macOS (`Option+Return`) and does nothing in
120
+ Chromium on Windows and Linux.
121
+
122
+ | Keys in the chat input | `ChatComposer` (before) | `Chat` default `Composer` (after) |
123
+ | --------------------------- | ---------------------------- | --------------------------------- |
124
+ | Enter | send | send |
125
+ | Shift+Enter | newline (`false` → send) | newline, always |
126
+ | Ctrl/⌘+Enter | **newline** (`false` → send) | **send** |
127
+ | Alt+Enter | send | left to the browser |
128
+ | Enter during an IME compose | ignored | ignored |
129
+
130
+ `composerProps={{ sendKey: "modEnter" }}` gives the `Composer` default instead: Enter adds a line, Ctrl/⌘+Enter sends.
131
+ `composerShiftEnterNewline` and `composerCtrlEnterNewline` are deprecated and do nothing unless `legacyComposer` is on.
132
+
133
+ **DOM, classes and test ids.**
134
+
135
+ | Before (`ChatComposer`) | After (`Composer`) |
136
+ | ------------------------------------------------------- | ----------------------------------------------------------------- |
137
+ | `.smwb-chat-composer` | `.smwb-composer` (also `.smwb-chat__composer` on a Chat's footer) |
138
+ | `.smwb-chat-composer__input`, `.smwb-text-field__input` | `.smwb-composer__input` |
139
+ | `chat-composer` | `composer` |
140
+ | `chat-composer-send` | `composer-action` (`data-action="send"` / `"stop"`) |
141
+ | `chat-composer-attach` (a `label`) | `composer-attach` (a focusable `button`) |
142
+ | `chat-file-input` | `composer-file-input` |
143
+
144
+ The textarea is now named "Message" (it had only a placeholder before); override it with
145
+ `composerProps={{ "aria-label": … }}` or `composerProps={{ "aria-labelledby": … }}`. The send button keeps the name
146
+ "Send message".
147
+
148
+ **Keeping the old composer while you migrate:** pass `legacyComposer` and `Chat` draws `ChatComposer` exactly as
149
+ before, with the old DOM, test ids and Enter/Ctrl+Enter semantics. `busy`, `onStop` and `composerProps` are ignored in
150
+ that mode. `composerShiftEnterNewline` and `composerCtrlEnterNewline` work only there. Switching between the two
151
+ composers at runtime is a `<Show>` inside `Chat`, so a signal can flip it.
152
+
153
+ **Porting a standalone `ChatComposer`:**
154
+
155
+ | `ChatComposer` | `Composer` |
156
+ | ------------------------------------------------ | ----------------------------------------- |
157
+ | `onSendMessage` | `onSend` |
158
+ | Enter sends | `sendKey="enter"` |
159
+ | `sendAriaLabel` | `sendLabel` |
160
+ | `attachAriaLabel` | `attachLabel` |
161
+ | `fileUpload` + `attachInputId` / `onAttachClick` | `onAttach` (button, picker and drop zone) |
162
+ | `shiftEnterNewline` | `sendKey="enter"` |
163
+ | `ctrlEnterNewline` | none: Ctrl/⌘+Enter always sends |
164
+ | `class` | `class` |
165
+
166
+ Inside a `Chat` the composer is not a drop target (`dropTarget={false}`): the whole chat shell already is, so a drop
167
+ anywhere — including on the field — uploads once.
168
+
169
+ `ChatComposer`, `shouldComposerNewlineOnEnter`, `ChatComposerNewlineOptions`, `Chat`'s `legacyComposer`,
170
+ `composerShiftEnterNewline` and `composerCtrlEnterNewline` are removed in the next major.
171
+
172
+ ### Tabs, panels and link tabs
173
+
174
+ `Tabs` follows the WAI-ARIA tabs pattern with **manual activation**: the arrow keys along the row's `orientation` (Left
175
+ and Right, Up and Down for `"vertical"`), Home and End move focus between the tabs and skip disabled ones, and Enter or
176
+ Space selects the focused tab (`onChange`, as a click does). Moving focus selects nothing, so a row of links does not
177
+ navigate as the arrows pass it. Only one tab, the focused or else the selected one, is in the page's Tab order; Tab
178
+ leaves the row for the selected panel.
179
+
180
+ The root of `Tabs` has no role. Give the content to `panels` as `TabPanel`s (`index` is the tab's index, counted as for
181
+ `value`): each is `role="tabpanel"`, hidden unless its tab is selected, and linked to its tab by `aria-labelledby`
182
+ and `aria-controls`. Ids derive from `Tabs`' `id` (`{id}-tab-0`, `{id}-panel-0`, ...; generated by default). An `id`
183
+ of your own on a `Tab` wins over the derived one, and its panel is labelled by it; an `id` of your own on a `TabPanel`
184
+ wins too, and then so must your own `aria-controls` on its `Tab`. (On the server the panels render before the tabs, so
185
+ a `Tab`'s own `id` reaches its panel's `aria-labelledby` only in the client: set `aria-labelledby` on that panel
186
+ yourself if the page is server-rendered.) Give a panel to every tab. Without `panels` the tabs
187
+ link to nothing, as before. A `TabPanel` outside `panels` is not linked to any tabs and is always shown. With
188
+ `scrollButtons` the scroll buttons are for the pointer and stay out of the Tab order: the arrow keys reach every tab and
189
+ scroll it into view, and Tab goes on from the row to the panel.
190
+
191
+ A `Tab` with an `href` is a link tab for route navigation: a real `<a href role="tab">` that never has a `target`.
192
+ The router takes the plain click in the tab's `onClick` with `event.preventDefault()`; `Tabs` still calls `onChange`
193
+ for it. A modified or non-primary click (Ctrl, Meta, Shift or Alt held, or a button other than the main one) is the
194
+ browser's, as "open in a new tab": the router leaves it alone, as a router's `Link` does, and `Tabs` does not call
195
+ `onChange` for it. A disabled link tab has no `href`.
196
+
197
+ A `Tab` can be closable and carry actions, for workspace tabs such as terminal sessions. `closable` draws a close button
198
+ after the tab, and `onClose` is called for it, with the click, and for Delete or Backspace on the focused tab (Backspace
199
+ because a Mac has no Delete key), with the key; the tab does not remove itself, your `onClose` removes it. `onClose`
200
+ also gets what asked for the close, `"button"`, `"key"` or `"actions"`. The button is labelled "Close tab", `closeLabel`
201
+ localises it or names the tab. `actions` is content after the tab and before the close button: a trigger with a `Menu`
202
+ ("Rename", "Terminate"), or any control; given as a function it receives `{ close }`.
203
+
204
+ The close button and the actions are the tab's siblings, not inside its `role="tab"` element: a control inside a tab
205
+ would join the tab's accessible name, so a screen reader reads the tab as its label alone. They are not hidden from
206
+ assistive technology, though. The close button keeps its localisable label because a screen reader user who cannot press
207
+ Delete, as on a touch screen, closes the tab with it, and `actions` is yours. The cost: they sit in the tab list beside
208
+ their tab, and ARIA 1.2 lets a `tablist` own only `tab`s, so a linter that applies that strictly (axe's
209
+ `aria-required-children`) can flag them. The tab says which keys it answers to in `aria-keyshortcuts`: "Delete Backspace"
210
+ when closable, "Shift+F10 ContextMenu" with `actions`.
211
+
212
+ A tab given `closable` or `actions` (even `closable={false}`) is wrapped with them in a `.smwb-tab-item`; one given neither
213
+ is the bare element, as before. `ref`, `class` and the handlers stay on the tab. The row keeps one tab stop: the close
214
+ button has `tabindex` -1, and so should the trigger in `actions`.
215
+
216
+ The keyboard reaches the actions with Shift+F10 or the Menu key on the focused tab: it focuses the first control in
217
+ `actions` and clicks it (a control that says `aria-expanded="true"` is already open and is left so). If that control
218
+ names its popup in `aria-controls`, focus then moves to the first enabled `menuitem` in it, as the WAI-ARIA menu button
219
+ pattern asks: ArrowDown moves on, Enter chooses, and Escape hands focus back to the trigger, which still counts as focus
220
+ on its tab. Without `aria-controls` focus stays on the trigger. The tab row clips what overflows it, so draw the menu in a
221
+ `Portal`, anchored to the trigger and with the `id` the trigger names.
222
+
223
+ When the tab that has focus closes, focus moves to the tab that takes its place, the previous one if it was last,
224
+ skipping disabled tabs; a close from a pointer with focus elsewhere takes none. A choice in the menu that removes the tab
225
+ ("Terminate") should call the `close` that `actions` receives, with its event: that runs `onClose` with the reason
226
+ `"actions"` and tells the row that focus, which is in the menu outside it, is to move on. A tab removed some other way
227
+ leaves focus on the page. A link tab can be closable too: the anchor is the tab and the button its sibling.
228
+
229
+ ### Tab groups
230
+
231
+ `TabGroups` is a split area of tab groups: one CSS grid holding any number of tab rows, each with its panels, and a
232
+ handle between neighbours that resizes them. It is the workspace screen — an agent beside two terminals, a log under a
233
+ preview — and the whole arrangement is one JSON value you can store and hand back. It is in the root entry and in the
234
+ `@smwb/ui-solid/tabGroups` subpath.
235
+
236
+ The layout holds **ids and sizes only**: no labels, no content, no state. It stays small, and it survives a rename of
237
+ what a tab is called.
238
+
239
+ ```json
240
+ {
241
+ "version": 1,
242
+ "activeGroup": "g1",
243
+ "root": {
244
+ "type": "split",
245
+ "id": "s1",
246
+ "direction": "row",
247
+ "sizes": [50, 50],
248
+ "children": [
249
+ { "type": "group", "id": "g1", "tabs": ["agent"], "active": "agent" },
250
+ { "type": "group", "id": "g2", "tabs": ["term-1", "term-2"], "active": "term-1" }
251
+ ]
252
+ }
253
+ }
254
+ ```
255
+
256
+ A group is a row of `tabs` with the `active` one selected; a split has a `direction` (`"row"` puts its children side by
257
+ side, `"column"` stacks them), `sizes` in percent and `children`. `version` is 1, and it is what
258
+ `tabGroupsLayout.parseTabGroupsLayout` checks before it trusts anything else: that call drops tab ids that are no longer
259
+ open, repairs sizes and node ids, and returns `null` for JSON that is not a layout, which is all the validation a stored
260
+ or shared layout needs. Reading order is depth-first: it is the Tab order and the order a screen reader reads the area
261
+ in, though the panels keep the DOM order they first appeared in. Generated group and split ids are the next free number
262
+ (`g4`, `s3`), so an id can come back after its group closed: key what the app keeps per pane by tab id, not by group id.
263
+
264
+ Give the component a `layout` and it is controlled: it draws what you hand it and reports through
265
+ `onLayoutChange(layout, change)`, where `change` says which of `select`, `activate`, `move`, `split`, `close` and
266
+ `resize` happened (an `activate` is a focus move into another group, not an edit worth storing). Give it a
267
+ `defaultLayout` instead and it keeps the layout itself. `onLayoutChange` is called in both modes, and a controlled owner
268
+ that ignores a change simply sees nothing move. A change that takes the focus with it — a chord, a menu item, a drop
269
+ of the focused tab, the close of a group's last tab — focuses that tab on the first layout the owner draws after it,
270
+ also when the owner applies `onLayoutChange` later (in a transition or a timeout); a layout drawn without the change
271
+ drops it.
272
+
273
+ `getTab(tabId)` says what a tab shows — `label`, `title` for the announcements, `icon`, `closable`, `closeLabel`,
274
+ `actions`, `disabled` — and an id it returns nothing for is drawn by the id itself. `renderPanel(tabId)` says what the
275
+ panel holds. A panel is rendered the first time its tab is selected and stays mounted from then on: hidden while
276
+ another tab of the group is shown, and kept when the tab moves to another group, so a form keeps what was typed and a
277
+ terminal its scrollback. A move changes only where the panel is drawn — its node stays where it is in the DOM — so a
278
+ scrolled panel keeps its scroll, an iframe in it does not reload, and the focus inside it stays, in the moved tab's
279
+ panel as in every other one.
280
+
281
+ The model behind the component is exported as a namespace, so none of its names collide with yours, and persistence is
282
+ `createPersistedJson` with that namespace's parser as the validator:
283
+
284
+ ```tsx
285
+ import { createSignal } from "solid-js";
286
+ import { TabGroups, tabGroupsLayout, type TabGroupsLayout, type TabGroupsTab } from "@smwb/ui-solid/tabGroups";
287
+ import { createPersistedJson } from "@smwb/ui-solid/createPersistedJson";
288
+
289
+ const TABS: Record<string, TabGroupsTab> = {
290
+ agent: { label: "Agent" },
291
+ "term-1": { label: "Terminal 1" },
292
+ "term-2": { label: "Terminal 2" },
293
+ };
294
+ const openTabs = Object.keys(TABS);
295
+
296
+ // SSR-safe storage: a stale or hand-edited entry is repaired, and JSON that is not a layout falls back.
297
+ const stored = createPersistedJson<TabGroupsLayout>({
298
+ key: "workspace.layout",
299
+ fallback: () => tabGroupsLayout.createTabGroupsLayout(openTabs),
300
+ parse: (raw) =>
301
+ tabGroupsLayout.parseTabGroupsLayout(raw, { tabs: openTabs }) ?? tabGroupsLayout.createTabGroupsLayout(openTabs),
302
+ });
303
+
304
+ export function Workspace() {
305
+ const [layout, setLayout] = createSignal<TabGroupsLayout>(stored.read());
306
+ let nextTerminal = 3;
307
+ return (
308
+ <div style={{ height: "70vh" }}>
309
+ <button
310
+ type="button"
311
+ onClick={() => {
312
+ const tabId = `term-${nextTerminal}`;
313
+ nextTerminal += 1;
314
+ setLayout((current) => tabGroupsLayout.addTab(current, tabId));
315
+ }}
316
+ >
317
+ New terminal
318
+ </button>
319
+ <TabGroups
320
+ layout={layout()}
321
+ getTab={(tabId) => TABS[tabId]}
322
+ renderPanel={(tabId) => <pre>{`$ ${tabId}`}</pre>}
323
+ empty={<p>Nothing open.</p>}
324
+ onLayoutChange={(next) => {
325
+ setLayout(next);
326
+ stored.write(next);
327
+ }}
328
+ />
329
+ </div>
330
+ );
331
+ }
332
+ ```
333
+
334
+ `addTab` is the "New terminal" case: the tab goes into the group the user last worked in, selected. When your own code
335
+ changes which tabs are open — a session loaded, a command closed three of them — `tabGroupsLayout.syncTabs(layout, tabs)`
336
+ makes the layout hold exactly that list: ids that are gone close, and new ones are appended to that same group.
337
+ `removeTab`, `selectTab`, `moveTab`, `resizeSplit` and `setActiveGroup` are the rest of it, and every one returns the
338
+ layout it was given when there is nothing to do.
339
+
340
+ **Keyboard.** Everything is reachable without a mouse, and every move is announced in a `role="status"` region:
341
+
342
+ | Where | Keys | What happens |
343
+ | ------------------------ | --------------------------- | ----------------------------------------------------------------------------------- |
344
+ | A tab row | ← → Home End | move focus between the row's tabs, as `Tabs` does |
345
+ | A tab row | Enter, Space | select the focused tab |
346
+ | The focused tab | Alt+Shift+Arrow | move it into the group that way, or split a new group there when the area ends |
347
+ | The focused tab | Ctrl/⌘+Shift+← → | move it one place along its row, without wrapping |
348
+ | The focused tab | Delete, Backspace, Ctrl/⌘+W | close it, `onTabClose` first |
349
+ | The focused tab | Shift+F10, Menu | open the tab's own `actions` menu |
350
+ | A group's actions button | Enter, Space, ↓ | open the group menu on its first enabled item |
351
+ | The group menu | ↑ ↓ Enter Esc | split right or down, move to the previous or next group, move the tab left or right |
352
+ | A separator | ← → or ↑ ↓ | resize by `keyboardStep` percent of the split (5 by default) |
353
+ | A separator | Home, End | as small as `minGroupSize` (120 px) allows, then as large |
354
+ | Anywhere | Tab | a row, its actions button, its selected panel, the separator after it, the next row |
355
+
356
+ Tab and Shift+Tab follow the reading order from group to group, while inside a row, a panel or a separator the
357
+ browser keeps its own rules: a radio group is one stop (its checked radio), a frame is entered at its first or its last
358
+ stop, and a `TabGroups` nested in a panel is part of that panel, gone through in its own reading order.
359
+
360
+ The group menu acts on that group's selected tab, and an item is disabled when it cannot act: a split needs two tabs to
361
+ leave one behind, and "previous" is grey on the first group in reading order. `groupActions={false}` takes the buttons
362
+ away and leaves the chords and the pointer.
363
+
364
+ **Drag and drop.** Dragging a tab draws a zone over every group's body. The centre of a body drops the tab into that
365
+ group; within a quarter of the body's edge it splits a new group there, and the zone is drawn as that half. The group
366
+ the drag started in refuses its own centre, and its own edge splits only with two tabs or more, so the cursor says "not
367
+ allowed" instead of the drop doing nothing. Dropping on another group's tab row inserts the tab before the one under the
368
+ pointer, or at the end over the row's empty part; a drop on its own row reorders, as `Tabs` does. After a drop the moved
369
+ tab is focused at its new place when it had focus before the drag.
370
+
371
+ **Accessibility.** The rows are `Tabs`, and its caveat applies: a closable tab, or one with `actions`, keeps those
372
+ controls as siblings inside the `tablist`, which a strict `aria-required-children` linter can flag (see above). Each row
373
+ is named "Group 1 of 4" — "Tabs" when there is only one group, so a single group reads as a plain tab row — and the
374
+ group the user is in carries a primary line over its header. Each separator is a `role="separator"` with an
375
+ `aria-orientation` and an `aria-valuenow` of 0–100 (the first of its two neighbours' share of the pair), and each panel
376
+ is a tab stop of its own, so Tab leaves a row for the content it shows. Closing the last tab of a group moves focus to
377
+ the selected tab of the group that takes the space, never to the page. The groups, rows and selected panels render on
378
+ the server; dragging and resizing run in the browser.
379
+
380
+ ### Links in `Button` and `ListItem`
381
+
382
+ A `Button` or a `ListItem` with an `href` is a real link (`<a href>`). By default it opens in a new tab, as `Button`
383
+ always did: `target="_blank"` and `rel="noreferrer"`. For a link inside your app, in a footer list of pages say:
384
+
385
+ - `target={null}` leaves the `target` attribute out, so the link opens in place and has no `rel` of its own. An empty
386
+ string does the same. Any other string is used as is (`"_self"`, or a window name, which gets `rel="noreferrer"`
387
+ like `"_blank"`); `undefined` keeps `"_blank"`.
388
+ - `rel` replaces the default `rel` (`rel=""` drops it), so add `noreferrer` yourself if you still want it.
389
+ - A router takes the plain click in `onClick` with `event.preventDefault()`, and leaves a modified or non-primary click
390
+ (Ctrl, Meta, Shift or Alt held, or a button other than the main one) to the browser, as a router's `Link` does; test
391
+ it before you call the router. Without `preventDefault` the browser follows the `href`.
392
+
393
+ `ListItem` with an `href` puts its `startAdornment` and content in an anchor that fills the row, so the link is the
394
+ row's Tab stop, `Enter` follows it, and the row is not focusable on its own. A disabled link row has no `href`.
395
+
396
+ - The `endAdornment` sits beside the anchor, not in it: a control there (a pin button, a toggle) is not nested in the
397
+ link, and a click on it is not a click of the row, so `onClick` and its `preventDefault` do not see it. Keep the
398
+ `startAdornment` non-interactive.
399
+ - `selected` paints the row and marks the link `aria-current="page"` (a list item cannot be `aria-selected`), which is
400
+ how a list of pages tells assistive technology where the user is.
401
+
402
+ See the `InternalLinks` story of `List`, which stands in for the router with a few lines of state, and `InternalLink`
403
+ of `Button`.
404
+
405
+ ### Sheet: header labels and focus return
406
+
407
+ The icon-only header buttons of `Sheet` are named: `closeAriaLabel` (default `"Close sheet"`) for the close button and,
408
+ for the size toggle, `expandAriaLabel` (`"Expand sheet"`), `collapseAriaLabel` (`"Collapse sheet"`) and
409
+ `restoreAriaLabel` (`"Restore sheet size"`). The toggle cycles normal, expanded, collapsed (header only) and back, and
410
+ its name always says what the next press does, so the name changing is how it exposes its state (it is not an
411
+ `aria-pressed` button). It shows the `open_in_full` icon, and `close_fullscreen` while expanded; both are in the smwb
412
+ icon set (Storybook, **UI/Base/Icon → All Icons**). Pass translated labels for a localized UI.
413
+
414
+ When a `Sheet` opens it remembers the element that had focus (usually the button that opened it) and moves focus into
415
+ the sheet. When it closes (Escape, the close button, the outside click of a modal sheet, `isOpen` turning false, or the
416
+ sheet unmounting while open), focus goes back to that element. Focus the app moved on purpose is left alone: the
417
+ element is focused only while focus is still inside the sheet or has fallen back to the page body, and an element that
418
+ has left the document is skipped.
419
+
420
+ ### Keyboard shortcuts
421
+
422
+ `Kbd` draws a shortcut as key caps, for a `Tooltip` message, beside a `MenuItemText` in a `MenuItem`, or in a command
423
+ palette: `<Kbd keys={["Mod", "K"]} />`. The root and every cap are `<kbd>` elements. `Mod` is the Command symbol on
424
+ macOS and `Ctrl` elsewhere. On macOS `Shift`, `Alt`/`Option`, `Ctrl`/`Control`, `Meta`/`Cmd`/`Command`, `Enter`, `Escape`,
425
+ `Tab`, `Backspace` and `Delete` are drawn as the symbols of the Mac keyboard; elsewhere the same names, in any case, are drawn as
426
+ `Ctrl`, `Alt`, `Shift`, `Meta`, `Enter`, `Esc`, `Tab`, `Backspace` and `Del`. `" "` and `Space` are the Space key, `+` and
427
+ `Plus` the plus key. Arrow names (`ArrowUp` or `Up`, ...) are arrows everywhere, a single letter is
428
+ upper-cased, and any other key is drawn as given. A `+` stands between the caps elsewhere (`separator` changes it), and
429
+ none on macOS.
430
+
431
+ Assistive technology skips the caps and reads one text: "Ctrl+K", or "Command K" on macOS (`label` replaces it). The
432
+ platform is the browser's own, found after the page has mounted: the server, and the first client render, draw the
433
+ non-Mac keys so that hydration matches. Pass `platform="mac"` or `platform="other"` for a fixed look, in stories and tests.
434
+ `detectKbdPlatform()` and `kbdKeyLabel(key, platform)` are exported for your own use.
435
+
62
436
  ## Icons
63
437
 
64
438
  `<Icon>` renders one of two icon sets under the **same name**, so switching sets never touches
@@ -278,6 +652,196 @@ node's `accent` still wins over its status; set runtime properties in the graph'
278
652
  content in `renderNode` with the class `smwb-dag-view__detail` to hide it when zoomed far out. See Storybook → **Data
279
653
  Display / DagView**.
280
654
 
655
+ ### Change size (DiffStat)
656
+
657
+ ```tsx
658
+ import { DiffStat } from "@smwb/ui-solid/diffStat";
659
+
660
+ <DiffStat added={128} removed={37} />
661
+ <DiffStat added={128} removed={37} compact />
662
+ ```
663
+
664
+ How big a change is: `+N −M` (the minus is U+2212, not a hyphen) in the theme's success and error colours, with a
665
+ bar of five cells beside it whose shares match the two figures; the cells a side leaves over are neutral, so `0` and
666
+ `0` leave every cell neutral. Each side takes `floor(5 × its share)` cells, so a side under a fifth of the change reaches
667
+ no cell and shows in its figure only. `compact` is the tab variant: smaller figures and no bar (`bar` turns the bar on
668
+ or off either way). Counts are written with `group`, three digits per group split by U+202F NARROW NO-BREAK SPACE
669
+ (`12 345`) by default; the separator is fixed rather than taken from a locale because the server and the browser
670
+ would group the same figure differently and break hydration, so an app that knows its reader's locale passes
671
+ `group={(n) => n.toLocaleString(locale)}`. Fractions are dropped, a count below zero or one that is not a number (`NaN`, `undefined`, `Infinity`) counts as `0`
672
+ in the figures, the name and the bar alike, and the count is otherwise shown as given, without abbreviating (`1.2k` is
673
+ left to the caller's `group`).
674
+
675
+ The whole is one `role="img"` with one accessible name, `added 12, removed 3`, and the digits and the cells are
676
+ hidden from assistive technology so nothing is read twice. Translate the name with `format={(added, removed) => ...}`
677
+ (it gets the two counts as numbers, so the default name reads plain digits, `added 12345, removed 3`, and a localiser
678
+ can write them as it likes, e.g. `добавлено 12, удалено 3`), or replace it with `aria-label`. The figures and the
679
+ cells use the theme's success and error colours mixed toward the surface ink (`@diffStatColorMix` in your
680
+ `theme.config`, 55% by default), which keeps text contrast at 5.4:1 or better on the page and the surface in both the
681
+ light and the dark theme; a cell nobody owns is an outline of the surface ink (`@diffStatNeutralMix`, 60%), 3.69:1 or
682
+ better against the page in every bundled theme, so it reads as empty next to a filled cell. See Storybook → **Data Display / DiffStat**.
683
+
684
+ ### Status marker (StatusDot)
685
+
686
+ ```tsx
687
+ import { StatusDot } from "@smwb/ui-solid/statusDot";
688
+
689
+ <StatusDot status="active" label="Working" />;
690
+ ```
691
+
692
+ `status` is `active` (info, pulses), `attention` (warning), `idle` (neutral), `done` (success), `error` or `unknown`
693
+ (a ring in the outline colour; also the default). `color` takes the theme colour names `Chip` takes and, with no
694
+ `status`, makes a plain disc. `size` is `md` (12px) or `sm` (8px); `pulse` turns the ring on or off (on for `active`
695
+ only, and never drawn under `prefers-reduced-motion: reduce`). `label` is required: the dot is a `role="img"` named
696
+ by it, so screen readers hear the status. For sighted users the filled statuses differ by colour only (`idle` is
697
+ solid and `unknown` a ring), so show the status in text or a tooltip where it matters. Each status colour reaches 3:1 against the surface in the light and dark
698
+ themes (see `@smwb/ui-styles`).
699
+
700
+ ### Participant lanes (Swimlanes)
701
+
702
+ ```tsx
703
+ import { Swimlanes } from "@smwb/ui-solid/swimlanes";
704
+
705
+ <Swimlanes
706
+ label="Agent team"
707
+ lanes={[
708
+ {
709
+ id: "anna",
710
+ header: () => <Who name="Anna" role="Team lead" />,
711
+ content: () => <Now item="Release notes" done={7} of={12} />,
712
+ },
713
+ {
714
+ id: "ilya",
715
+ header: () => <Who name="Ilya" role="Frontend" />,
716
+ content: () => <Now item="Sticky column" done={3} of={9} />,
717
+ },
718
+ {
719
+ id: "vera",
720
+ header: () => <Who name="Vera" role="Design" />,
721
+ content: () => <Now item="Migration guide" done={0} of={6} />,
722
+ },
723
+ ]}
724
+ onSelectedChange={(id) => openDetails(id)}
725
+ />;
726
+ ```
727
+
728
+ One row per participant: a header on the left (an avatar, a name, a role) and the content on the right (the current
729
+ item, progress, the last signal, the budget). A lane is `{ id, header, content }`, where `id` is stable and unique —
730
+ it keys the row, the selection and the Tab stop. `header` and `content` are **thunks**: a row calls them when it
731
+ mounts and again when it comes back into the window, so 200 lanes never build 200 subtrees up front, and a lane's
732
+ content can read signals. Rows are keyed by `id`, so a `lanes` array rebuilt on every refresh
733
+ (`participants().map(toLane)`) keeps its rows and their cells, and focus on a cell. A cell's content is built again
734
+ for each lane that brings a new thunk, and the arrow functions of such a `toLane` are new on every call: focus on a
735
+ control _inside_ that content (a button, a field) then goes to the page. So keep the thunks stable — make each
736
+ lane once per id and let the data flow through signals. A `disabled` lane stays readable and focusable but cannot be
737
+ selected.
738
+
739
+ The header column stays put while the content scrolls sideways under it. `headerWidth` is its width (240px by
740
+ default; a number of pixels or any CSS length) and `contentMinWidth` the narrowest the content column can be, so a
741
+ grid too narrow for the two together scrolls sideways. Cells clip, so content that needs more room needs a wider
742
+ `contentMinWidth`; an ellipsis inside it is yours.
743
+
744
+ Every lane is `rowHeight` tall (64px by default) — the rows are virtualised, which needs a fixed height — and the
745
+ grid is `height` tall (480px from the stylesheet, so a height in `style` works too). Only the lanes in view and
746
+ `overscan` more above and below them (4 by default, read once when the grid mounts) are in the DOM, so 200 lanes cost about 16 rows, while
747
+ `aria-rowcount` still reports all of them and each row carries its own `aria-rowindex`. On the server, and at the
748
+ first paint before the grid can measure itself, the window is the one `height` implies, so the browser hydrates the
749
+ markup the server sent.
750
+
751
+ A click in a lane, or Enter or Space on its focused cell, selects it: `selectedId` / `defaultSelectedId` /
752
+ `onSelectedChange(id | null)`, controlled while `selectedId` is not `undefined`, where `null` means "controlled,
753
+ nothing selected". Escape clears the selection, and only when there is one, so an enclosing dialog still gets the
754
+ key. A click on a control _inside_ a lane selects nothing — that click belongs to the control. `selectable={false}`
755
+ turns selection off altogether: nothing is painted or exposed, a `selectedId` given anyway is ignored and Escape is
756
+ left alone. A selected lane that leaves `lanes` clears the selection through `onSelectedChange(null)`.
757
+
758
+ For assistive technology the grid is a `role="grid"` named by `label` (your `aria-labelledby` wins), two columns
759
+ wide, each lane's header a `rowheader`. Focus is on cells, and the grid is **one Tab stop**: the cell focused last,
760
+ else the selected lane's header, else the first lane's header. Links and buttons inside a lane stay reachable — Tab
761
+ from the active cell walks that lane's own controls and then leaves the grid — so there is no cell edit mode to
762
+ switch into and no F2 to press. Focus on a control inside a lane (a click on its button) makes that lane the Tab stop,
763
+ and the lane holding focus stays in the DOM when it scrolls out of the window and keeps focus when it scrolls back.
764
+
765
+ | Key | Moves to |
766
+ | ---------------------- | -------------------------------------------------------------------------------- |
767
+ | ArrowDown / ArrowUp | the next or the previous lane, same column; no wrap at the ends |
768
+ | ArrowRight / ArrowLeft | the content cell / the header cell of the same lane |
769
+ | Home / End | the first / the last cell of the row |
770
+ | Ctrl+Home / Ctrl+End | the first / the last lane, same column |
771
+ | PageDown / PageUp | a page of lanes: the rows that fit, less one so a row of context stays on screen |
772
+ | Enter / Space | selects the lane |
773
+ | Escape | clears the selection |
774
+
775
+ A disabled lane takes focus like any other and is not skipped. Keys pressed inside a lane's own control belong to
776
+ that control, and combinations with Ctrl, Cmd or Alt are the browser's, apart from Ctrl+Home and Ctrl+End. With
777
+ `lanes={[]}` the root carries no grid role at all — an empty grid has no rows — and shows `emptyLabel` instead.
778
+ Colours come from the theme's runtime tokens, so the dark theme follows with no extra rules. See Storybook → **Data
779
+ Display / Swimlanes**.
780
+
781
+ ### QR code (QrCode)
782
+
783
+ ```tsx
784
+ import { QrCode } from "@smwb/ui-solid/qrCode";
785
+
786
+ <QrCode value="https://mateme.io/pair" caption="Scan with the phone app" />;
787
+ ```
788
+
789
+ `value` (a URL, a pairing token) is encoded as UTF-8 bytes and drawn as an SVG image, `role="img"` named
790
+ "QR code" (rename with `label`). The modules are dark on a white square — the quiet zone is part of the
791
+ white — in every theme, so a phone camera reads the code in dark mode too; the colours are fixed and never
792
+ follow the theme. `size` is the side in pixels, quiet zone included (192 by default); keep at least ~4px per
793
+ module, so a long payload needs a bigger `size` (or a shorter value). `errorCorrection` is `L`/`M` (default)/
794
+ `Q`/`H` — the share of the code that may be damaged or covered; `quietZone` is the light margin in modules
795
+ (4, the spec minimum). `caption` renders a `<figcaption>` under the code, and the figure is named by it.
796
+ A `value` past the QR capacity does not throw: the code renders an empty white square with
797
+ `data-error="too-long"`, named "QR code: value too long". Any other encode failure (a lone surrogate fails in
798
+ `encodeURI`) renders the same empty square with `data-error="encode-failed"`, named "QR code: value could not be
799
+ encoded". Neither throws, and each cause is reported once — a code bound to a growing text field does not add a
800
+ console line per keystroke. The value itself is not exposed to assistive technology (tokens may be secret), so show
801
+ a copyable link or code next to the QR for users who cannot scan.
802
+ Encoding runs on `uqr`, a new runtime dependency of `@smwb/ui-solid` (external in the bundle, like `clsx`);
803
+ besides the index there is the `@smwb/ui-solid/qrCode` subpath. See Storybook → **Data Display / QR Code**.
804
+
805
+ ### Window title bar (`AppBar variant="window"`)
806
+
807
+ ```tsx
808
+ import { AppBar } from "@smwb/ui-solid/appBar";
809
+
810
+ <AppBar
811
+ variant="window"
812
+ windowControls="left"
813
+ title="Untitled document"
814
+ leftContent={<Button icon={<Icon name="menu" />} variant="text" aria-label="Main menu" onClick={openMenu} />}
815
+ rightContent={<Button label="Login" variant="contained" size="small" />}
816
+ />;
817
+ ```
818
+
819
+ The Solid pair of the React bar: the same classes, the same stylesheet, the same tests. `variant="window"` makes
820
+ the bar the title bar of a desktop app window (Electron, Tauri, an installed PWA with Window Controls Overlay) — it
821
+ becomes a drag region (`-webkit-app-region: drag`), so dragging it moves the window. Interactive content stays
822
+ clickable, which matters because Electron delivers no mouse events at all inside a drag region: the styles take
823
+ `no-drag` on buttons, links, fields, anything focusable or role-tagged, and on the library's tooltip wrapper, and a
824
+ custom control that is neither opts out with the `smwb-appbar__no-drag` class or the `data-no-drag` attribute.
825
+ Overlays are excluded from the drag region too — they portal to `<body>`, out of the bar's subtree, while Electron
826
+ hit-tests drag regions by geometry, not by the DOM tree. That covers the layers carrying `data-smwb-overlay-layer`,
827
+ the `Menu`, `Tooltip` and Snackbar popups, and the library's own full-screen portals (the Chat attachment lightbox,
828
+ the ImagesList lightbox, the ThumbCard preview and the ChatShell panel). Roles that keep `tabindex="-1"` while their
829
+ group holds the tab stop (`menuitemcheckbox`, `menuitemradio`, `treeitem`, `spinbutton`, `gridcell`, `listbox`,
830
+ `scrollbar`) are in the list as well, because the focusable rule alone cannot see them.
831
+
832
+ `windowControls` keeps the content clear of the window buttons the OS draws: `left` for the macOS traffic lights,
833
+ `right` for the Windows/Linux caption buttons, `none` to reserve nothing, `auto` (the default) for the area the
834
+ shell reports through `env(titlebar-area-*)`. Nothing is read from the platform: the variant is a plain class
835
+ choice, so the server markup and the hydrated one agree and the layout never shifts after hydration — pass `left`
836
+ or `right` from the app, which knows its own platform. In Electron, `titleBarStyle: "hiddenInset"` goes with
837
+ `windowControls="left"` and `titleBarOverlay` with `"auto"`; set the overlay's height to the bar's (65px, or 48px
838
+ with `dense`).
839
+
840
+ The reserved width is `@appBarWindowControlsLeft` (64px) and `@appBarWindowControlsRight` (138px), overridable at
841
+ runtime for both sides with `--smui-appBarWindowControlsWidth` (see `@smwb/ui-styles`). A bar without `variant`
842
+ renders exactly as before. See Storybook → **Navigation / AppBar**: `WindowTitleBar`, `WindowTitleBarWindows`,
843
+ `WindowDense`.
844
+
281
845
  ## Customization and theming
282
846
 
283
847
  | Goal | Approach | Rebuild CSS? |
@@ -1 +1 @@
1
- .smwb-appbar{box-sizing:border-box;padding:0 16px;display:flex;align-items:center;inset:0 0 auto;min-width:100%;background-color:var(--smui-appBarBg);color:var(--smui-appBarContentColor);font-size:1rem;height:65px}.smwb-appbar.smwb-bottom{bottom:0;top:auto}.smwb-appbar.smwb-relative{position:relative}.smwb-appbar.smwb-static{position:static}.smwb-appbar.smwb-sticky{position:sticky}.smwb-appbar.smwb-fixed{position:fixed}.smwb-appbar.smwb-absolute{position:absolute}.smwb-appbar.smwb-dense{padding:12px;height:48px}.smwb-appbar__inner{display:flex;align-items:center;width:100%;justify-content:space-between;flex-wrap:nowrap}.smwb-appbar__left{display:flex;align-items:center;gap:10px;white-space:nowrap}.smwb-appbar__right{white-space:nowrap}.smwb-appbar__mid{width:100%;display:flex;flex:1 1 auto;flex-direction:column;flex-wrap:nowrap}.smwb-appbar__mid.smwb-middle{align-items:center}.smwb-appbar__mid.smwb-right{align-items:flex-end}.smwb-appbar__mid.smwb-left{align-items:flex-start}.smwb-appbar_color-primary{background-color:var(--smui-primaryColor);color:var(--smui-onPrimaryColor)}.smwb-appbar_color-secondary{background-color:var(--smui-secondaryColor);color:var(--smui-onSecondaryColor)}.smwb-appbar_color-error{background-color:var(--smui-errorColor);color:var(--smui-onErrorColor)}.smwb-appbar_color-success{background-color:var(--smui-successColor);color:var(--smui-onSuccessColor)}.smwb-appbar_color-warning{background-color:var(--smui-warningColor);color:var(--smui-onWarningColor)}.smwb-appbar_color-info{background-color:var(--smui-infoColor);color:var(--smui-onInfoColor)}
1
+ .smwb-appbar{box-sizing:border-box;padding:0 16px;display:flex;align-items:center;inset:0 0 auto;min-width:100%;background-color:var(--smui-appBarBg);color:var(--smui-appBarContentColor);font-size:1rem;height:65px}.smwb-appbar.smwb-bottom{bottom:0;top:auto}.smwb-appbar.smwb-relative{position:relative}.smwb-appbar.smwb-static{position:static}.smwb-appbar.smwb-sticky{position:sticky}.smwb-appbar.smwb-fixed{position:fixed}.smwb-appbar.smwb-absolute{position:absolute}.smwb-appbar.smwb-dense{padding:12px;height:48px}.smwb-appbar.smwb-appbar_variant-window{-webkit-app-region:drag;app-region:drag;user-select:none;--smwb-appbar-wc-left: env(titlebar-area-x, 0px);--smwb-appbar-wc-right: calc(100vw - env(titlebar-area-x, 0px) - env(titlebar-area-width, 100vw)) ;padding-left:calc(16px + var(--smwb-appbar-wc-left));padding-right:calc(16px + var(--smwb-appbar-wc-right))}.smwb-appbar.smwb-appbar_variant-window.smwb-appbar_window-controls-left{--smwb-appbar-wc-left: env(titlebar-area-x, var(--smui-appBarWindowControlsWidth, 64px))}.smwb-appbar.smwb-appbar_variant-window.smwb-appbar_window-controls-right{--smwb-appbar-wc-right: calc(100vw - env(titlebar-area-x, 0px) - env(titlebar-area-width, calc(100vw - var(--smui-appBarWindowControlsWidth, 138px)))) }.smwb-appbar.smwb-appbar_variant-window.smwb-appbar_window-controls-none{--smwb-appbar-wc-left: 0px;--smwb-appbar-wc-right: 0px}.smwb-appbar.smwb-appbar_variant-window :where(a[href],button,input,select,textarea,label,summary,iframe,video[controls],[contenteditable]:not([contenteditable=false]),[tabindex]:not([tabindex="-1"]),[role=button],[role=link],[role=menuitem],[role=tab],[role=checkbox],[role=switch],[role=radio],[role=combobox],[role=textbox],[role=searchbox],[role=slider],[role=option],[role=menuitemcheckbox],[role=menuitemradio],[role=treeitem],[role=spinbutton],[role=gridcell],[role=listbox],[role=scrollbar],.smwb-tooltip__wrapper,.smwb-appbar__no-drag,[data-no-drag]){-webkit-app-region:no-drag;app-region:no-drag}.smwb-appbar.smwb-appbar_variant-window.smwb-dense{padding:0 calc(16px + var(--smwb-appbar-wc-right)) 0 calc(16px + var(--smwb-appbar-wc-left))}.smwb-appbar__inner{display:flex;align-items:center;width:100%;justify-content:space-between;flex-wrap:nowrap}.smwb-appbar__left{display:flex;align-items:center;gap:10px;white-space:nowrap}.smwb-appbar__left__title{margin:0;font:inherit}.smwb-appbar__right{white-space:nowrap}.smwb-appbar__mid{width:100%;display:flex;flex:1 1 auto;flex-direction:column;flex-wrap:nowrap}.smwb-appbar__mid.smwb-middle{align-items:center}.smwb-appbar__mid.smwb-right{align-items:flex-end}.smwb-appbar__mid.smwb-left{align-items:flex-start}[data-smwb-overlay-layer],.smwb-menu,.smwb-tooltip,.smwb-snackbars,.smwb-chat-attachment-image__overlay,.smwb-image-modal__overlay,.smwb-thumb-card__preview,.smwb-chat-shell{-webkit-app-region:no-drag;app-region:no-drag}.smwb-appbar_color-primary{background-color:var(--smui-primaryColor);color:var(--smui-onPrimaryColor)}.smwb-appbar_color-secondary{background-color:var(--smui-secondaryColor);color:var(--smui-onSecondaryColor)}.smwb-appbar_color-error{background-color:var(--smui-errorColor);color:var(--smui-onErrorColor)}.smwb-appbar_color-success{background-color:var(--smui-successColor);color:var(--smui-onSuccessColor)}.smwb-appbar_color-warning{background-color:var(--smui-warningColor);color:var(--smui-onWarningColor)}.smwb-appbar_color-info{background-color:var(--smui-infoColor);color:var(--smui-onInfoColor)}
@@ -1 +1 @@
1
- .smwb-button{gap:8px;overflow:hidden;font-weight:500;cursor:pointer;transition:all .2s ease-out;display:inline-flex;align-items:center;justify-content:center;border-radius:24px;letter-spacing:1px;vertical-align:middle;word-spacing:0;line-height:1.5;min-width:64px;position:relative;align-self:flex-start}.smwb-button:disabled,.smwb-button.smwb-disabled{color:var(--smui-disabledTextColor);pointer-events:none;border-color:var(--smui-buttonDisabledBg)}.smwb-button:disabled.smwb-contained,.smwb-button.smwb-disabled.smwb-contained{background-color:var(--smui-buttonDisabledBg);color:var(--smui-disabledTextColor)}.smwb-button.smwb-full-width{width:100%}.smwb-button.smwb-small{font-size:13px;padding:4px 10px}.smwb-button.smwb-medium{font-size:14px;padding:6px 14px}.smwb-button.smwb-large{font-size:15px;padding:8px 22px}.smwb-button.smwb-contained{color:var(--smui-onPrimaryColor);box-shadow:0 3px 1px -2px #0003,0 2px 2px #00000024,0 1px 5px #0000001f}.smwb-button.smwb-contained.smwb-primary:not(.smwb-disabled){background-color:var(--smui-primaryColor)}.smwb-button.smwb-contained.smwb-primary:hover{background-color:var(--smui-buttonContainedHoverBg);box-shadow:0 4px 22px -2px #0003,0 2px 2px #00000024,0 1px 5px #0000001f}.smwb-button.smwb-contained.smwb-primary:disabled{background-color:var(--smui-buttonDisabledBg)}.smwb-button.smwb-contained.smwb-primary:focus{background-color:var(--smui-buttonContainedFocusBg)}.smwb-button.smwb-contained.smwb-primary:active{background-color:var(--smui-buttonContainedActiveBg)}.smwb-button.smwb-outlined{background-color:transparent;border:1px solid var(--smui-outlineColor)}.smwb-button.smwb-outlined.smwb-primary:not(.smwb-disabled){color:var(--smui-primaryColor)}.smwb-button.smwb-outlined:hover{background-color:var(--smui-buttonOutlinedHoverBg)}.smwb-button.smwb-outlined:focus{background-color:var(--smui-buttonOutlinedFocusBg)}.smwb-button.smwb-outlined:active{background-color:var(--smui-buttonOutlinedActiveBg);border:1px solid var(--smui-primaryColor)}.smwb-button.smwb-outlined:disabled,.smwb-button.smwb-outlined.smwb-disabled{background-color:transparent;color:var(--smui-disabledTextColor);border-color:var(--smui-buttonDisabledBg)}.smwb-button.smwb-text{background-color:transparent}.smwb-button.smwb-text.smwb-primary:not(.smwb-disabled){color:var(--smui-primaryColor)}.smwb-button.smwb-text.smwb-primary:disabled{background-color:transparent}.smwb-button.smwb-text.smwb-primary:hover{background-color:var(--smui-buttonOutlinedHoverBg)}.smwb-button.smwb-text.smwb-primary:focus{background-color:var(--smui-buttonOutlinedFocusBg)}.smwb-button.smwb-text.smwb-primary:active{background-color:var(--smui-buttonOutlinedActiveBg)}.smwb-button.smwb-text:disabled{background-color:transparent;color:var(--smui-disabledTextColor);border-color:var(--smui-buttonDisabledBg)}.smwb-button *{pointer-events:none}.smwb-button_color-secondary.smwb-contained{background-color:var(--smui-secondaryColor);color:var(--smui-onSecondaryColor)}.smwb-button_color-secondary.smwb-contained:hover:not(.smwb-disabled){background-color:color-mix(in srgb,var(--smui-secondaryColor) 88%,#000)}.smwb-button_color-secondary.smwb-contained:focus:not(.smwb-disabled){background-color:color-mix(in srgb,var(--smui-secondaryColor) 78%,#000)}.smwb-button_color-secondary.smwb-contained:active:not(.smwb-disabled){background-color:color-mix(in srgb,var(--smui-secondaryColor) 78%,#000)}.smwb-button_color-secondary.smwb-outlined{color:var(--smui-secondaryColor);border-color:var(--smui-secondaryColor)}.smwb-button_color-secondary.smwb-outlined:hover:not(.smwb-disabled){background-color:var(--smui-secondaryColorHover)}.smwb-button_color-secondary.smwb-text{color:var(--smui-secondaryColor)}.smwb-button_color-secondary.smwb-text:hover:not(.smwb-disabled){background-color:var(--smui-secondaryColorHover)}.smwb-button_color-error.smwb-contained{background-color:var(--smui-errorColor);color:var(--smui-onErrorColor)}.smwb-button_color-error.smwb-contained:hover:not(.smwb-disabled){background-color:color-mix(in srgb,var(--smui-errorColor) 88%,#000)}.smwb-button_color-error.smwb-contained:focus:not(.smwb-disabled){background-color:color-mix(in srgb,var(--smui-errorColor) 78%,#000)}.smwb-button_color-error.smwb-contained:active:not(.smwb-disabled){background-color:color-mix(in srgb,var(--smui-errorColor) 78%,#000)}.smwb-button_color-error.smwb-outlined{color:var(--smui-errorColor);border-color:var(--smui-errorColor)}.smwb-button_color-error.smwb-outlined:hover:not(.smwb-disabled){background-color:var(--smui-errorColorHover)}.smwb-button_color-error.smwb-text{color:var(--smui-errorColor)}.smwb-button_color-error.smwb-text:hover:not(.smwb-disabled){background-color:var(--smui-errorColorHover)}.smwb-button_color-success.smwb-contained{background-color:var(--smui-successColor);color:var(--smui-onSuccessColor)}.smwb-button_color-success.smwb-contained:hover:not(.smwb-disabled){background-color:color-mix(in srgb,var(--smui-successColor) 88%,#000)}.smwb-button_color-success.smwb-contained:focus:not(.smwb-disabled){background-color:color-mix(in srgb,var(--smui-successColor) 78%,#000)}.smwb-button_color-success.smwb-contained:active:not(.smwb-disabled){background-color:color-mix(in srgb,var(--smui-successColor) 78%,#000)}.smwb-button_color-success.smwb-outlined{color:var(--smui-successColor);border-color:var(--smui-successColor)}.smwb-button_color-success.smwb-outlined:hover:not(.smwb-disabled){background-color:var(--smui-successColorHover)}.smwb-button_color-success.smwb-text{color:var(--smui-successColor)}.smwb-button_color-success.smwb-text:hover:not(.smwb-disabled){background-color:var(--smui-successColorHover)}.smwb-button_color-warning.smwb-contained{background-color:var(--smui-warningColor);color:var(--smui-onWarningColor)}.smwb-button_color-warning.smwb-contained:hover:not(.smwb-disabled){background-color:color-mix(in srgb,var(--smui-warningColor) 88%,#000)}.smwb-button_color-warning.smwb-contained:focus:not(.smwb-disabled){background-color:color-mix(in srgb,var(--smui-warningColor) 78%,#000)}.smwb-button_color-warning.smwb-contained:active:not(.smwb-disabled){background-color:color-mix(in srgb,var(--smui-warningColor) 78%,#000)}.smwb-button_color-warning.smwb-outlined{color:var(--smui-warningColor);border-color:var(--smui-warningColor)}.smwb-button_color-warning.smwb-outlined:hover:not(.smwb-disabled){background-color:var(--smui-warningColorHover)}.smwb-button_color-warning.smwb-text{color:var(--smui-warningColor)}.smwb-button_color-warning.smwb-text:hover:not(.smwb-disabled){background-color:var(--smui-warningColorHover)}.smwb-button_color-info.smwb-contained{background-color:var(--smui-infoColor);color:var(--smui-onInfoColor)}.smwb-button_color-info.smwb-contained:hover:not(.smwb-disabled){background-color:color-mix(in srgb,var(--smui-infoColor) 88%,#000)}.smwb-button_color-info.smwb-contained:focus:not(.smwb-disabled){background-color:color-mix(in srgb,var(--smui-infoColor) 78%,#000)}.smwb-button_color-info.smwb-contained:active:not(.smwb-disabled){background-color:color-mix(in srgb,var(--smui-infoColor) 78%,#000)}.smwb-button_color-info.smwb-outlined{color:var(--smui-infoColor);border-color:var(--smui-infoColor)}.smwb-button_color-info.smwb-outlined:hover:not(.smwb-disabled){background-color:var(--smui-infoColorHover)}.smwb-button_color-info.smwb-text{color:var(--smui-infoColor)}.smwb-button_color-info.smwb-text:hover:not(.smwb-disabled){background-color:var(--smui-infoColorHover)}
1
+ .smwb-button{gap:8px;overflow:hidden;font-weight:500;cursor:pointer;transition:all .2s ease-out;display:inline-flex;align-items:center;justify-content:center;border-radius:24px;letter-spacing:1px;vertical-align:middle;word-spacing:0;line-height:1.5;min-width:64px;position:relative;align-self:flex-start}.smwb-button:disabled,.smwb-button.smwb-disabled{color:var(--smui-disabledTextColor);pointer-events:none;border-color:var(--smui-buttonDisabledBg)}.smwb-button:disabled.smwb-contained,.smwb-button.smwb-disabled.smwb-contained{background-color:var(--smui-buttonDisabledBg);color:var(--smui-disabledTextColor)}.smwb-button.smwb-full-width{width:100%}.smwb-button.smwb-small{font-size:13px;padding:4px 10px;min-height:var(--smui-control-h-sm, 32px)}.smwb-button.smwb-medium{font-size:14px;padding:6px 14px;min-height:var(--smui-control-h-md, 40px)}.smwb-button.smwb-large{font-size:15px;padding:8px 22px;min-height:var(--smui-control-h-lg, 48px)}.smwb-button.smwb-contained{color:var(--smui-onPrimaryColor);box-shadow:0 3px 1px -2px #0003,0 2px 2px #00000024,0 1px 5px #0000001f}.smwb-button.smwb-contained.smwb-primary:not(.smwb-disabled){background-color:var(--smui-primaryColor)}.smwb-button.smwb-contained.smwb-primary:hover{background-color:var(--smui-buttonContainedHoverBg);box-shadow:0 4px 22px -2px #0003,0 2px 2px #00000024,0 1px 5px #0000001f}.smwb-button.smwb-contained.smwb-primary:disabled{background-color:var(--smui-buttonDisabledBg)}.smwb-button.smwb-contained.smwb-primary:focus{background-color:var(--smui-buttonContainedFocusBg)}.smwb-button.smwb-contained.smwb-primary:active{background-color:var(--smui-buttonContainedActiveBg)}.smwb-button.smwb-outlined{background-color:transparent;border:1px solid var(--smui-outlineColor)}.smwb-button.smwb-outlined.smwb-primary:not(.smwb-disabled){color:var(--smui-primaryColor)}.smwb-button.smwb-outlined:hover{background-color:var(--smui-buttonOutlinedHoverBg)}.smwb-button.smwb-outlined:focus{background-color:var(--smui-buttonOutlinedFocusBg)}.smwb-button.smwb-outlined:active{background-color:var(--smui-buttonOutlinedActiveBg);border:1px solid var(--smui-primaryColor)}.smwb-button.smwb-outlined:disabled,.smwb-button.smwb-outlined.smwb-disabled{background-color:transparent;color:var(--smui-disabledTextColor);border-color:var(--smui-buttonDisabledBg)}.smwb-button.smwb-text{background-color:transparent}.smwb-button.smwb-text.smwb-primary:not(.smwb-disabled){color:var(--smui-primaryColor)}.smwb-button.smwb-text.smwb-primary:disabled{background-color:transparent}.smwb-button.smwb-text.smwb-primary:hover{background-color:var(--smui-buttonOutlinedHoverBg)}.smwb-button.smwb-text.smwb-primary:focus{background-color:var(--smui-buttonOutlinedFocusBg)}.smwb-button.smwb-text.smwb-primary:active{background-color:var(--smui-buttonOutlinedActiveBg)}.smwb-button.smwb-text:disabled{background-color:transparent;color:var(--smui-disabledTextColor);border-color:var(--smui-buttonDisabledBg)}.smwb-button *{pointer-events:none}.smwb-button_color-secondary.smwb-contained{background-color:var(--smui-secondaryColor);color:var(--smui-onSecondaryColor)}.smwb-button_color-secondary.smwb-contained:hover:not(.smwb-disabled){background-color:color-mix(in srgb,var(--smui-secondaryColor) 88%,#000)}.smwb-button_color-secondary.smwb-contained:focus:not(.smwb-disabled){background-color:color-mix(in srgb,var(--smui-secondaryColor) 78%,#000)}.smwb-button_color-secondary.smwb-contained:active:not(.smwb-disabled){background-color:color-mix(in srgb,var(--smui-secondaryColor) 78%,#000)}.smwb-button_color-secondary.smwb-outlined{color:var(--smui-secondaryColor);border-color:var(--smui-secondaryColor)}.smwb-button_color-secondary.smwb-outlined:hover:not(.smwb-disabled){background-color:var(--smui-secondaryColorHover)}.smwb-button_color-secondary.smwb-text{color:var(--smui-secondaryColor)}.smwb-button_color-secondary.smwb-text:hover:not(.smwb-disabled){background-color:var(--smui-secondaryColorHover)}.smwb-button_color-error.smwb-contained{background-color:var(--smui-errorColor);color:var(--smui-onErrorColor)}.smwb-button_color-error.smwb-contained:hover:not(.smwb-disabled){background-color:color-mix(in srgb,var(--smui-errorColor) 88%,#000)}.smwb-button_color-error.smwb-contained:focus:not(.smwb-disabled){background-color:color-mix(in srgb,var(--smui-errorColor) 78%,#000)}.smwb-button_color-error.smwb-contained:active:not(.smwb-disabled){background-color:color-mix(in srgb,var(--smui-errorColor) 78%,#000)}.smwb-button_color-error.smwb-outlined{color:var(--smui-errorColor);border-color:var(--smui-errorColor)}.smwb-button_color-error.smwb-outlined:hover:not(.smwb-disabled){background-color:var(--smui-errorColorHover)}.smwb-button_color-error.smwb-text{color:var(--smui-errorColor)}.smwb-button_color-error.smwb-text:hover:not(.smwb-disabled){background-color:var(--smui-errorColorHover)}.smwb-button_color-success.smwb-contained{background-color:var(--smui-successColor);color:var(--smui-onSuccessColor)}.smwb-button_color-success.smwb-contained:hover:not(.smwb-disabled){background-color:color-mix(in srgb,var(--smui-successColor) 88%,#000)}.smwb-button_color-success.smwb-contained:focus:not(.smwb-disabled){background-color:color-mix(in srgb,var(--smui-successColor) 78%,#000)}.smwb-button_color-success.smwb-contained:active:not(.smwb-disabled){background-color:color-mix(in srgb,var(--smui-successColor) 78%,#000)}.smwb-button_color-success.smwb-outlined{color:var(--smui-successColor);border-color:var(--smui-successColor)}.smwb-button_color-success.smwb-outlined:hover:not(.smwb-disabled){background-color:var(--smui-successColorHover)}.smwb-button_color-success.smwb-text{color:var(--smui-successColor)}.smwb-button_color-success.smwb-text:hover:not(.smwb-disabled){background-color:var(--smui-successColorHover)}.smwb-button_color-warning.smwb-contained{background-color:var(--smui-warningColor);color:var(--smui-onWarningColor)}.smwb-button_color-warning.smwb-contained:hover:not(.smwb-disabled){background-color:color-mix(in srgb,var(--smui-warningColor) 88%,#000)}.smwb-button_color-warning.smwb-contained:focus:not(.smwb-disabled){background-color:color-mix(in srgb,var(--smui-warningColor) 78%,#000)}.smwb-button_color-warning.smwb-contained:active:not(.smwb-disabled){background-color:color-mix(in srgb,var(--smui-warningColor) 78%,#000)}.smwb-button_color-warning.smwb-outlined{color:var(--smui-warningColor);border-color:var(--smui-warningColor)}.smwb-button_color-warning.smwb-outlined:hover:not(.smwb-disabled){background-color:var(--smui-warningColorHover)}.smwb-button_color-warning.smwb-text{color:var(--smui-warningColor)}.smwb-button_color-warning.smwb-text:hover:not(.smwb-disabled){background-color:var(--smui-warningColorHover)}.smwb-button_color-info.smwb-contained{background-color:var(--smui-infoColor);color:var(--smui-onInfoColor)}.smwb-button_color-info.smwb-contained:hover:not(.smwb-disabled){background-color:color-mix(in srgb,var(--smui-infoColor) 88%,#000)}.smwb-button_color-info.smwb-contained:focus:not(.smwb-disabled){background-color:color-mix(in srgb,var(--smui-infoColor) 78%,#000)}.smwb-button_color-info.smwb-contained:active:not(.smwb-disabled){background-color:color-mix(in srgb,var(--smui-infoColor) 78%,#000)}.smwb-button_color-info.smwb-outlined{color:var(--smui-infoColor);border-color:var(--smui-infoColor)}.smwb-button_color-info.smwb-outlined:hover:not(.smwb-disabled){background-color:var(--smui-infoColorHover)}.smwb-button_color-info.smwb-text{color:var(--smui-infoColor)}.smwb-button_color-info.smwb-text:hover:not(.smwb-disabled){background-color:var(--smui-infoColorHover)}