@owlmeans/web-panel 0.1.18-rc.9 → 0.1.18

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 (669) hide show
  1. package/README.md +510 -143
  2. package/THIRD-PARTY-NOTICES.txt +24 -0
  3. package/agent-meta/manifest.json +163 -4
  4. package/agent-meta/skills/web-panel/SKILL.md +136 -83
  5. package/agent-meta/skills/web-panel/parts/consent.md +114 -0
  6. package/agent-meta/skills/web-panel/parts/editor.md +47 -0
  7. package/agent-meta/skills/web-panel/parts/forms.md +85 -0
  8. package/agent-meta/skills/web-panel/parts/navigation.md +203 -0
  9. package/agent-meta/skills/web-panel/parts/picker.md +164 -0
  10. package/agent-meta/skills/web-panel/parts/reload.md +46 -0
  11. package/agent-meta/skills/web-panel/parts/sign-in.md +65 -0
  12. package/agent-meta/skills/web-panel/parts/theme.md +74 -0
  13. package/build/@/components/ui/alert-dialog.d.ts +15 -0
  14. package/build/@/components/ui/alert-dialog.d.ts.map +1 -0
  15. package/build/@/components/ui/alert-dialog.js +39 -0
  16. package/build/@/components/ui/alert-dialog.js.map +1 -0
  17. package/build/@/components/ui/alert.js +1 -1
  18. package/build/@/components/ui/alert.js.map +1 -1
  19. package/build/@/components/ui/button.js +1 -1
  20. package/build/@/components/ui/button.js.map +1 -1
  21. package/build/@/components/ui/card.js +1 -1
  22. package/build/@/components/ui/card.js.map +1 -1
  23. package/build/@/components/ui/input.js +1 -1
  24. package/build/@/components/ui/input.js.map +1 -1
  25. package/build/@/components/ui/label.js +1 -1
  26. package/build/@/components/ui/label.js.map +1 -1
  27. package/build/@/components/ui/navigation-menu.js +1 -1
  28. package/build/@/components/ui/navigation-menu.js.map +1 -1
  29. package/build/@/components/ui/popover.d.ts +8 -0
  30. package/build/@/components/ui/popover.d.ts.map +1 -0
  31. package/build/@/components/ui/popover.js +17 -0
  32. package/build/@/components/ui/popover.js.map +1 -0
  33. package/build/@/components/ui/progress.js +1 -1
  34. package/build/@/components/ui/progress.js.map +1 -1
  35. package/build/@/components/ui/separator.js +1 -1
  36. package/build/@/components/ui/separator.js.map +1 -1
  37. package/build/@/components/ui/sheet.d.ts +15 -0
  38. package/build/@/components/ui/sheet.d.ts.map +1 -0
  39. package/build/@/components/ui/sheet.js +40 -0
  40. package/build/@/components/ui/sheet.js.map +1 -0
  41. package/build/auth/context.d.ts.map +1 -1
  42. package/build/auth/context.js +0 -1
  43. package/build/auth/context.js.map +1 -1
  44. package/build/auth/entrypoints.d.ts +10 -0
  45. package/build/auth/entrypoints.d.ts.map +1 -0
  46. package/build/auth/entrypoints.js +4 -0
  47. package/build/auth/entrypoints.js.map +1 -0
  48. package/build/auth/exports.d.ts +4 -4
  49. package/build/auth/exports.d.ts.map +1 -1
  50. package/build/auth/exports.js +3 -3
  51. package/build/auth/exports.js.map +1 -1
  52. package/build/auth/modules.d.ts +1 -1
  53. package/build/auth/modules.d.ts.map +1 -1
  54. package/build/auth/plugins/basic-ed25519.js +4 -4
  55. package/build/auth/plugins/basic-ed25519.js.map +1 -1
  56. package/build/auth/plugins/re-captcha.js +3 -3
  57. package/build/auth/plugins/re-captcha.js.map +1 -1
  58. package/build/auth/plugins/tunnel-consumer.js +1 -1
  59. package/build/auth/plugins/tunnel-consumer.js.map +1 -1
  60. package/build/components/block.js +2 -2
  61. package/build/components/block.js.map +1 -1
  62. package/build/components/consts.local.d.ts +14 -0
  63. package/build/components/consts.local.d.ts.map +1 -0
  64. package/build/components/consts.local.js +26 -0
  65. package/build/components/consts.local.js.map +1 -0
  66. package/build/components/footer/component.d.ts +19 -1
  67. package/build/components/footer/component.d.ts.map +1 -1
  68. package/build/components/footer/component.js +38 -11
  69. package/build/components/footer/component.js.map +1 -1
  70. package/build/components/footer/credit.d.ts +27 -0
  71. package/build/components/footer/credit.d.ts.map +1 -0
  72. package/build/components/footer/credit.js +42 -0
  73. package/build/components/footer/credit.js.map +1 -0
  74. package/build/components/footer/index.d.ts +1 -0
  75. package/build/components/footer/index.d.ts.map +1 -1
  76. package/build/components/footer/index.js +1 -0
  77. package/build/components/footer/index.js.map +1 -1
  78. package/build/components/footer/types.d.ts +22 -1
  79. package/build/components/footer/types.d.ts.map +1 -1
  80. package/build/components/form/button/component.d.ts.map +1 -1
  81. package/build/components/form/button/component.js +5 -3
  82. package/build/components/form/button/component.js.map +1 -1
  83. package/build/components/form/component.js +2 -2
  84. package/build/components/form/component.js.map +1 -1
  85. package/build/components/form/text/component.js +2 -2
  86. package/build/components/form/text/component.js.map +1 -1
  87. package/build/components/helper.d.ts.map +1 -1
  88. package/build/components/helper.js +1 -12
  89. package/build/components/helper.js.map +1 -1
  90. package/build/components/index.d.ts +4 -0
  91. package/build/components/index.d.ts.map +1 -1
  92. package/build/components/index.js +4 -0
  93. package/build/components/index.js.map +1 -1
  94. package/build/components/link.js +3 -3
  95. package/build/components/link.js.map +1 -1
  96. package/build/components/login/append.d.ts +15 -0
  97. package/build/components/login/append.d.ts.map +1 -0
  98. package/build/components/login/append.js +26 -0
  99. package/build/components/login/append.js.map +1 -0
  100. package/build/components/login/consts.d.ts +3 -0
  101. package/build/components/login/consts.d.ts.map +1 -0
  102. package/build/components/login/consts.js +3 -0
  103. package/build/components/login/consts.js.map +1 -0
  104. package/build/components/login/consts.local.d.ts +4 -0
  105. package/build/components/login/consts.local.d.ts.map +1 -0
  106. package/build/components/login/consts.local.js +8 -0
  107. package/build/components/login/consts.local.js.map +1 -0
  108. package/build/components/login/credit.d.ts +12 -0
  109. package/build/components/login/credit.d.ts.map +1 -0
  110. package/build/components/login/credit.js +18 -0
  111. package/build/components/login/credit.js.map +1 -0
  112. package/build/components/login/i18n/be.json +10 -0
  113. package/build/components/login/i18n/de.json +10 -0
  114. package/build/components/login/i18n/en.json +10 -0
  115. package/build/components/login/i18n/es.json +10 -0
  116. package/build/components/login/i18n/fr.json +10 -0
  117. package/build/components/login/i18n/pl.json +10 -0
  118. package/build/components/login/i18n/ru.json +10 -0
  119. package/build/components/login/i18n/uk.json +10 -0
  120. package/build/components/login/i18n.d.ts +2 -0
  121. package/build/components/login/i18n.d.ts.map +1 -0
  122. package/build/components/login/i18n.js +28 -0
  123. package/build/components/login/i18n.js.map +1 -0
  124. package/build/components/login/icons.d.ts +14 -0
  125. package/build/components/login/icons.d.ts.map +1 -0
  126. package/build/components/login/icons.js +29 -0
  127. package/build/components/login/icons.js.map +1 -0
  128. package/build/components/login/index.d.ts +11 -0
  129. package/build/components/login/index.d.ts.map +1 -0
  130. package/build/components/login/index.js +10 -0
  131. package/build/components/login/index.js.map +1 -0
  132. package/build/components/login/notify.d.ts +18 -0
  133. package/build/components/login/notify.d.ts.map +1 -0
  134. package/build/components/login/notify.js +41 -0
  135. package/build/components/login/notify.js.map +1 -0
  136. package/build/components/login/provider.d.ts +20 -0
  137. package/build/components/login/provider.d.ts.map +1 -0
  138. package/build/components/login/provider.js +31 -0
  139. package/build/components/login/provider.js.map +1 -0
  140. package/build/components/login/screen.d.ts +24 -0
  141. package/build/components/login/screen.d.ts.map +1 -0
  142. package/build/components/login/screen.js +92 -0
  143. package/build/components/login/screen.js.map +1 -0
  144. package/build/components/login/terms.d.ts +19 -0
  145. package/build/components/login/terms.d.ts.map +1 -0
  146. package/build/components/login/terms.js +32 -0
  147. package/build/components/login/terms.js.map +1 -0
  148. package/build/components/login/types.d.ts +28 -0
  149. package/build/components/login/types.d.ts.map +1 -0
  150. package/build/components/login/types.js +2 -0
  151. package/build/components/login/types.js.map +1 -0
  152. package/build/components/nav/consts.local.d.ts +41 -0
  153. package/build/components/nav/consts.local.d.ts.map +1 -0
  154. package/build/components/nav/consts.local.js +60 -0
  155. package/build/components/nav/consts.local.js.map +1 -0
  156. package/build/components/nav/index.d.ts +1 -0
  157. package/build/components/nav/index.d.ts.map +1 -1
  158. package/build/components/nav/index.js +1 -0
  159. package/build/components/nav/index.js.map +1 -1
  160. package/build/components/nav/layout.d.ts +9 -2
  161. package/build/components/nav/layout.d.ts.map +1 -1
  162. package/build/components/nav/layout.js +48 -7
  163. package/build/components/nav/layout.js.map +1 -1
  164. package/build/components/nav/mobile.d.ts +21 -0
  165. package/build/components/nav/mobile.d.ts.map +1 -0
  166. package/build/components/nav/mobile.js +55 -0
  167. package/build/components/nav/mobile.js.map +1 -0
  168. package/build/components/nav/side.d.ts.map +1 -1
  169. package/build/components/nav/side.js +10 -10
  170. package/build/components/nav/side.js.map +1 -1
  171. package/build/components/nav/top.d.ts.map +1 -1
  172. package/build/components/nav/top.js +12 -8
  173. package/build/components/nav/top.js.map +1 -1
  174. package/build/components/nav/types.d.ts +64 -1
  175. package/build/components/nav/types.d.ts.map +1 -1
  176. package/build/components/nav/viewport.d.ts +3 -0
  177. package/build/components/nav/viewport.d.ts.map +1 -0
  178. package/build/components/nav/viewport.js +14 -0
  179. package/build/components/nav/viewport.js.map +1 -0
  180. package/build/components/panel-app/component.d.ts.map +1 -1
  181. package/build/components/panel-app/component.js +4 -3
  182. package/build/components/panel-app/component.js.map +1 -1
  183. package/build/components/picker/color-picker.d.ts +9 -0
  184. package/build/components/picker/color-picker.d.ts.map +1 -0
  185. package/build/components/picker/color-picker.js +9 -0
  186. package/build/components/picker/color-picker.js.map +1 -0
  187. package/build/components/picker/consts.local.d.ts +76 -0
  188. package/build/components/picker/consts.local.d.ts.map +1 -0
  189. package/build/components/picker/consts.local.js +214 -0
  190. package/build/components/picker/consts.local.js.map +1 -0
  191. package/build/components/picker/dot.d.ts +8 -0
  192. package/build/components/picker/dot.d.ts.map +1 -0
  193. package/build/components/picker/dot.js +10 -0
  194. package/build/components/picker/dot.js.map +1 -0
  195. package/build/components/picker/grid.d.ts +17 -0
  196. package/build/components/picker/grid.d.ts.map +1 -0
  197. package/build/components/picker/grid.js +73 -0
  198. package/build/components/picker/grid.js.map +1 -0
  199. package/build/components/picker/i18n/be.json +60 -0
  200. package/build/components/picker/i18n/de.json +60 -0
  201. package/build/components/picker/i18n/en.json +60 -0
  202. package/build/components/picker/i18n/es.json +60 -0
  203. package/build/components/picker/i18n/fr.json +60 -0
  204. package/build/components/picker/i18n/pl.json +60 -0
  205. package/build/components/picker/i18n/ru.json +60 -0
  206. package/build/components/picker/i18n/uk.json +60 -0
  207. package/build/components/picker/i18n.d.ts +2 -0
  208. package/build/components/picker/i18n.d.ts.map +1 -0
  209. package/build/components/picker/i18n.js +18 -0
  210. package/build/components/picker/i18n.js.map +1 -0
  211. package/build/components/picker/icon-color-picker.d.ts +14 -0
  212. package/build/components/picker/icon-color-picker.d.ts.map +1 -0
  213. package/build/components/picker/icon-color-picker.js +15 -0
  214. package/build/components/picker/icon-color-picker.js.map +1 -0
  215. package/build/components/picker/icon-picker.d.ts +8 -0
  216. package/build/components/picker/icon-picker.d.ts.map +1 -0
  217. package/build/components/picker/icon-picker.js +8 -0
  218. package/build/components/picker/icon-picker.js.map +1 -0
  219. package/build/components/picker/index.d.ts +11 -0
  220. package/build/components/picker/index.d.ts.map +1 -0
  221. package/build/components/picker/index.js +10 -0
  222. package/build/components/picker/index.js.map +1 -0
  223. package/build/components/picker/panel.d.ts +9 -0
  224. package/build/components/picker/panel.d.ts.map +1 -0
  225. package/build/components/picker/panel.js +23 -0
  226. package/build/components/picker/panel.js.map +1 -0
  227. package/build/components/picker/root.d.ts +13 -0
  228. package/build/components/picker/root.d.ts.map +1 -0
  229. package/build/components/picker/root.js +55 -0
  230. package/build/components/picker/root.js.map +1 -0
  231. package/build/components/picker/roving.d.ts +8 -0
  232. package/build/components/picker/roving.d.ts.map +1 -0
  233. package/build/components/picker/roving.js +62 -0
  234. package/build/components/picker/roving.js.map +1 -0
  235. package/build/components/picker/surface.d.ts +18 -0
  236. package/build/components/picker/surface.d.ts.map +1 -0
  237. package/build/components/picker/surface.js +36 -0
  238. package/build/components/picker/surface.js.map +1 -0
  239. package/build/components/picker/swatches/custom.d.ts +10 -0
  240. package/build/components/picker/swatches/custom.d.ts.map +1 -0
  241. package/build/components/picker/swatches/custom.js +65 -0
  242. package/build/components/picker/swatches/custom.js.map +1 -0
  243. package/build/components/picker/swatches.d.ts +16 -0
  244. package/build/components/picker/swatches.d.ts.map +1 -0
  245. package/build/components/picker/swatches.js +35 -0
  246. package/build/components/picker/swatches.js.map +1 -0
  247. package/build/components/picker/text.d.ts +5 -0
  248. package/build/components/picker/text.d.ts.map +1 -0
  249. package/build/components/picker/text.js +29 -0
  250. package/build/components/picker/text.js.map +1 -0
  251. package/build/components/picker/tile.d.ts +11 -0
  252. package/build/components/picker/tile.d.ts.map +1 -0
  253. package/build/components/picker/tile.js +19 -0
  254. package/build/components/picker/tile.js.map +1 -0
  255. package/build/components/picker/translate.d.ts +9 -0
  256. package/build/components/picker/translate.d.ts.map +1 -0
  257. package/build/components/picker/translate.js +13 -0
  258. package/build/components/picker/translate.js.map +1 -0
  259. package/build/components/picker/trigger.d.ts +12 -0
  260. package/build/components/picker/trigger.d.ts.map +1 -0
  261. package/build/components/picker/trigger.js +29 -0
  262. package/build/components/picker/trigger.js.map +1 -0
  263. package/build/components/picker/types.d.ts +114 -0
  264. package/build/components/picker/types.d.ts.map +1 -0
  265. package/build/components/picker/types.js +2 -0
  266. package/build/components/picker/types.js.map +1 -0
  267. package/build/components/picker/types.local.d.ts +143 -0
  268. package/build/components/picker/types.local.d.ts.map +1 -0
  269. package/build/components/picker/types.local.js +2 -0
  270. package/build/components/picker/types.local.js.map +1 -0
  271. package/build/components/scheme/consts.local.d.ts +8 -0
  272. package/build/components/scheme/consts.local.d.ts.map +1 -0
  273. package/build/components/scheme/consts.local.js +13 -0
  274. package/build/components/scheme/consts.local.js.map +1 -0
  275. package/build/components/scheme/hook.d.ts +16 -0
  276. package/build/components/scheme/hook.d.ts.map +1 -0
  277. package/build/components/scheme/hook.js +52 -0
  278. package/build/components/scheme/hook.js.map +1 -0
  279. package/build/components/scheme/index.d.ts +4 -0
  280. package/build/components/scheme/index.d.ts.map +1 -0
  281. package/build/components/scheme/index.js +3 -0
  282. package/build/components/scheme/index.js.map +1 -0
  283. package/build/components/scheme/toggle.d.ts +14 -0
  284. package/build/components/scheme/toggle.d.ts.map +1 -0
  285. package/build/components/scheme/toggle.js +24 -0
  286. package/build/components/scheme/toggle.js.map +1 -0
  287. package/build/components/scheme/types.d.ts +21 -0
  288. package/build/components/scheme/types.d.ts.map +1 -0
  289. package/build/components/scheme/types.js +2 -0
  290. package/build/components/scheme/types.js.map +1 -0
  291. package/build/components/socket/i18n/be.json +9 -0
  292. package/build/components/socket/i18n/de.json +9 -0
  293. package/build/components/socket/i18n/en.json +9 -0
  294. package/build/components/socket/i18n/es.json +9 -0
  295. package/build/components/socket/i18n/fr.json +9 -0
  296. package/build/components/socket/i18n/pl.json +9 -0
  297. package/build/components/socket/i18n/ru.json +9 -0
  298. package/build/components/socket/i18n/uk.json +9 -0
  299. package/build/components/socket/i18n.d.ts +2 -0
  300. package/build/components/socket/i18n.d.ts.map +1 -0
  301. package/build/components/socket/i18n.js +18 -0
  302. package/build/components/socket/i18n.js.map +1 -0
  303. package/build/components/socket/index.d.ts +3 -0
  304. package/build/components/socket/index.d.ts.map +1 -0
  305. package/build/components/socket/index.js +3 -0
  306. package/build/components/socket/index.js.map +1 -0
  307. package/build/components/socket/reload-dialog.d.ts +22 -0
  308. package/build/components/socket/reload-dialog.d.ts.map +1 -0
  309. package/build/components/socket/reload-dialog.js +34 -0
  310. package/build/components/socket/reload-dialog.js.map +1 -0
  311. package/build/components/status.js +1 -1
  312. package/build/components/status.js.map +1 -1
  313. package/build/components/text.d.ts.map +1 -1
  314. package/build/components/text.js +2 -13
  315. package/build/components/text.js.map +1 -1
  316. package/build/components/toaster.d.ts +16 -0
  317. package/build/components/toaster.d.ts.map +1 -0
  318. package/build/components/toaster.js +52 -0
  319. package/build/components/toaster.js.map +1 -0
  320. package/build/components/uploader/consts.local.d.ts +4 -0
  321. package/build/components/uploader/consts.local.d.ts.map +1 -0
  322. package/build/components/uploader/consts.local.js +6 -0
  323. package/build/components/uploader/consts.local.js.map +1 -0
  324. package/build/components/uploader/image.d.ts.map +1 -1
  325. package/build/components/uploader/image.js +2 -6
  326. package/build/components/uploader/image.js.map +1 -1
  327. package/build/consent/component.d.ts +27 -0
  328. package/build/consent/component.d.ts.map +1 -0
  329. package/build/consent/component.js +60 -0
  330. package/build/consent/component.js.map +1 -0
  331. package/build/consent/consts.d.ts +2 -0
  332. package/build/consent/consts.d.ts.map +1 -0
  333. package/build/consent/consts.js +2 -0
  334. package/build/consent/consts.js.map +1 -0
  335. package/build/consent/consts.local.d.ts +9 -0
  336. package/build/consent/consts.local.d.ts.map +1 -0
  337. package/build/consent/consts.local.js +9 -0
  338. package/build/consent/consts.local.js.map +1 -0
  339. package/build/consent/geo.d.ts +17 -0
  340. package/build/consent/geo.d.ts.map +1 -0
  341. package/build/consent/geo.js +19 -0
  342. package/build/consent/geo.js.map +1 -0
  343. package/build/consent/index.d.ts +11 -0
  344. package/build/consent/index.d.ts.map +1 -0
  345. package/build/consent/index.js +9 -0
  346. package/build/consent/index.js.map +1 -0
  347. package/build/consent/language.d.ts +12 -0
  348. package/build/consent/language.d.ts.map +1 -0
  349. package/build/consent/language.js +11 -0
  350. package/build/consent/language.js.map +1 -0
  351. package/build/consent/menu-widget.d.ts +13 -0
  352. package/build/consent/menu-widget.d.ts.map +1 -0
  353. package/build/consent/menu-widget.js +20 -0
  354. package/build/consent/menu-widget.js.map +1 -0
  355. package/build/consent/presence.d.ts +30 -0
  356. package/build/consent/presence.d.ts.map +1 -0
  357. package/build/consent/presence.js +40 -0
  358. package/build/consent/presence.js.map +1 -0
  359. package/build/consent/service.d.ts +18 -0
  360. package/build/consent/service.d.ts.map +1 -0
  361. package/build/consent/service.js +53 -0
  362. package/build/consent/service.js.map +1 -0
  363. package/build/consent/translate.d.ts +14 -0
  364. package/build/consent/translate.d.ts.map +1 -0
  365. package/build/consent/translate.js +28 -0
  366. package/build/consent/translate.js.map +1 -0
  367. package/build/consent/types.d.ts +39 -0
  368. package/build/consent/types.d.ts.map +1 -0
  369. package/build/consent/types.js +2 -0
  370. package/build/consent/types.js.map +1 -0
  371. package/build/consent/types.local.d.ts +6 -0
  372. package/build/consent/types.local.d.ts.map +1 -0
  373. package/build/consent/types.local.js +2 -0
  374. package/build/consent/types.local.js.map +1 -0
  375. package/build/context.d.ts.map +1 -1
  376. package/build/context.js +10 -0
  377. package/build/context.js.map +1 -1
  378. package/build/editor/component.d.ts +4 -0
  379. package/build/editor/component.d.ts.map +1 -0
  380. package/build/editor/component.js +103 -0
  381. package/build/editor/component.js.map +1 -0
  382. package/build/editor/consts.local.d.ts +6 -0
  383. package/build/editor/consts.local.d.ts.map +1 -0
  384. package/build/editor/consts.local.js +58 -0
  385. package/build/editor/consts.local.js.map +1 -0
  386. package/build/editor/index.d.ts +3 -0
  387. package/build/editor/index.d.ts.map +1 -0
  388. package/build/editor/index.js +2 -0
  389. package/build/editor/index.js.map +1 -0
  390. package/build/editor/types.d.ts +24 -0
  391. package/build/editor/types.d.ts.map +1 -0
  392. package/build/editor/types.js +2 -0
  393. package/build/editor/types.js.map +1 -0
  394. package/build/entrypoints.d.ts +8 -0
  395. package/build/entrypoints.d.ts.map +1 -0
  396. package/build/entrypoints.js +4 -0
  397. package/build/entrypoints.js.map +1 -0
  398. package/build/exports.d.ts +6 -5
  399. package/build/exports.d.ts.map +1 -1
  400. package/build/exports.js +5 -5
  401. package/build/exports.js.map +1 -1
  402. package/build/index.d.ts +1 -1
  403. package/build/index.d.ts.map +1 -1
  404. package/build/index.js +1 -1
  405. package/build/index.js.map +1 -1
  406. package/build/main.d.ts +1 -4
  407. package/build/main.d.ts.map +1 -1
  408. package/build/main.js.map +1 -1
  409. package/build/modules.d.ts +1 -1
  410. package/build/modules.d.ts.map +1 -1
  411. package/build/permission/i18n/be.json +13 -0
  412. package/build/permission/i18n/de.json +13 -0
  413. package/build/permission/i18n/en.json +13 -0
  414. package/build/permission/i18n/es.json +13 -0
  415. package/build/permission/i18n/fr.json +13 -0
  416. package/build/permission/i18n/pl.json +13 -0
  417. package/build/permission/i18n/ru.json +13 -0
  418. package/build/permission/i18n/uk.json +13 -0
  419. package/build/permission/i18n.d.ts +2 -0
  420. package/build/permission/i18n.d.ts.map +1 -0
  421. package/build/permission/i18n.js +13 -0
  422. package/build/permission/i18n.js.map +1 -0
  423. package/build/permission/index.d.ts +6 -0
  424. package/build/permission/index.d.ts.map +1 -0
  425. package/build/permission/index.js +27 -0
  426. package/build/permission/index.js.map +1 -0
  427. package/build/permission/toast.d.ts +5 -0
  428. package/build/permission/toast.d.ts.map +1 -0
  429. package/build/permission/toast.js +8 -0
  430. package/build/permission/toast.js.map +1 -0
  431. package/build/permission/types.d.ts +6 -0
  432. package/build/permission/types.d.ts.map +1 -0
  433. package/build/permission/types.js +2 -0
  434. package/build/permission/types.js.map +1 -0
  435. package/build/picker/icons/consts.d.ts +9 -0
  436. package/build/picker/icons/consts.d.ts.map +1 -0
  437. package/build/picker/icons/consts.js +264 -0
  438. package/build/picker/icons/consts.js.map +1 -0
  439. package/build/picker/icons/types.d.ts +18 -0
  440. package/build/picker/icons/types.d.ts.map +1 -0
  441. package/build/picker/icons/types.js +2 -0
  442. package/build/picker/icons/types.js.map +1 -0
  443. package/build/picker/icons.d.ts +4 -0
  444. package/build/picker/icons.d.ts.map +1 -0
  445. package/build/picker/icons.js +54 -0
  446. package/build/picker/icons.js.map +1 -0
  447. package/build/picker/index.d.ts +11 -0
  448. package/build/picker/index.d.ts.map +1 -0
  449. package/build/picker/index.js +10 -0
  450. package/build/picker/index.js.map +1 -0
  451. package/build/picker/utils/color/types.d.ts +35 -0
  452. package/build/picker/utils/color/types.d.ts.map +1 -0
  453. package/build/picker/utils/color/types.js +2 -0
  454. package/build/picker/utils/color/types.js.map +1 -0
  455. package/build/picker/utils/color.d.ts +4 -0
  456. package/build/picker/utils/color.d.ts.map +1 -0
  457. package/build/picker/utils/color.js +96 -0
  458. package/build/picker/utils/color.js.map +1 -0
  459. package/build/picker/vocabulary/consts.d.ts +26 -0
  460. package/build/picker/vocabulary/consts.d.ts.map +1 -0
  461. package/build/picker/vocabulary/consts.js +336 -0
  462. package/build/picker/vocabulary/consts.js.map +1 -0
  463. package/build/picker/vocabulary/types.d.ts +67 -0
  464. package/build/picker/vocabulary/types.d.ts.map +1 -0
  465. package/build/picker/vocabulary/types.js +2 -0
  466. package/build/picker/vocabulary/types.js.map +1 -0
  467. package/build/picker/vocabulary.d.ts +11 -0
  468. package/build/picker/vocabulary.d.ts.map +1 -0
  469. package/build/picker/vocabulary.js +83 -0
  470. package/build/picker/vocabulary.js.map +1 -0
  471. package/build/scheme/consts.d.ts +8 -0
  472. package/build/scheme/consts.d.ts.map +1 -0
  473. package/build/scheme/consts.js +8 -0
  474. package/build/scheme/consts.js.map +1 -0
  475. package/build/scheme/index.d.ts +4 -0
  476. package/build/scheme/index.d.ts.map +1 -0
  477. package/build/scheme/index.js +3 -0
  478. package/build/scheme/index.js.map +1 -0
  479. package/build/scheme/scheme.d.ts +16 -0
  480. package/build/scheme/scheme.d.ts.map +1 -0
  481. package/build/scheme/scheme.js +58 -0
  482. package/build/scheme/scheme.js.map +1 -0
  483. package/build/scheme/types.d.ts +30 -0
  484. package/build/scheme/types.d.ts.map +1 -0
  485. package/build/scheme/types.js +2 -0
  486. package/build/scheme/types.js.map +1 -0
  487. package/build/types.d.ts +18 -2
  488. package/build/types.d.ts.map +1 -1
  489. package/package.json +107 -52
  490. package/src/@/components/ui/alert-dialog.tsx +156 -0
  491. package/src/@/components/ui/alert.tsx +1 -1
  492. package/src/@/components/ui/button.tsx +1 -1
  493. package/src/@/components/ui/card.tsx +1 -1
  494. package/src/@/components/ui/input.tsx +1 -1
  495. package/src/@/components/ui/label.tsx +1 -1
  496. package/src/@/components/ui/navigation-menu.tsx +1 -1
  497. package/src/@/components/ui/popover.tsx +47 -0
  498. package/src/@/components/ui/progress.tsx +1 -1
  499. package/src/@/components/ui/separator.tsx +1 -1
  500. package/src/@/components/ui/sheet.tsx +142 -0
  501. package/src/auth/context.ts +0 -2
  502. package/src/auth/entrypoints.ts +5 -0
  503. package/src/auth/exports.ts +4 -4
  504. package/src/auth/plugins/basic-ed25519.tsx +4 -4
  505. package/src/auth/plugins/re-captcha.tsx +3 -3
  506. package/src/auth/plugins/tunnel-consumer.tsx +1 -1
  507. package/src/components/block.tsx +2 -2
  508. package/src/components/consts.local.ts +29 -0
  509. package/src/components/footer/component.tsx +58 -16
  510. package/src/components/footer/credit.tsx +61 -0
  511. package/src/components/footer/index.ts +1 -0
  512. package/src/components/footer/types.ts +22 -1
  513. package/src/components/form/button/component.tsx +7 -4
  514. package/src/components/form/component.tsx +2 -2
  515. package/src/components/form/text/component.tsx +2 -2
  516. package/src/components/helper.ts +1 -13
  517. package/src/components/index.ts +4 -0
  518. package/src/components/link.tsx +3 -3
  519. package/src/components/login/append.ts +34 -0
  520. package/src/components/login/consts.local.ts +10 -0
  521. package/src/components/login/consts.ts +2 -0
  522. package/src/components/login/credit.tsx +33 -0
  523. package/src/components/login/i18n/be.json +10 -0
  524. package/src/components/login/i18n/de.json +10 -0
  525. package/src/components/login/i18n/en.json +10 -0
  526. package/src/components/login/i18n/es.json +10 -0
  527. package/src/components/login/i18n/fr.json +10 -0
  528. package/src/components/login/i18n/pl.json +10 -0
  529. package/src/components/login/i18n/ru.json +10 -0
  530. package/src/components/login/i18n/uk.json +10 -0
  531. package/src/components/login/i18n.ts +29 -0
  532. package/src/components/login/icons.tsx +39 -0
  533. package/src/components/login/index.ts +11 -0
  534. package/src/components/login/notify.ts +48 -0
  535. package/src/components/login/provider.tsx +57 -0
  536. package/src/components/login/screen.tsx +178 -0
  537. package/src/components/login/terms.tsx +75 -0
  538. package/src/components/login/types.ts +30 -0
  539. package/src/components/nav/consts.local.ts +64 -0
  540. package/src/components/nav/index.ts +1 -0
  541. package/src/components/nav/layout.tsx +184 -26
  542. package/src/components/nav/mobile.tsx +165 -0
  543. package/src/components/nav/side.tsx +43 -27
  544. package/src/components/nav/top.tsx +44 -22
  545. package/src/components/nav/types.ts +65 -2
  546. package/src/components/nav/viewport.ts +16 -0
  547. package/src/components/panel-app/component.tsx +9 -1
  548. package/src/components/picker/color-picker.tsx +15 -0
  549. package/src/components/picker/consts.local.ts +267 -0
  550. package/src/components/picker/dot.tsx +21 -0
  551. package/src/components/picker/grid.tsx +138 -0
  552. package/src/components/picker/i18n/be.json +60 -0
  553. package/src/components/picker/i18n/de.json +60 -0
  554. package/src/components/picker/i18n/en.json +60 -0
  555. package/src/components/picker/i18n/es.json +60 -0
  556. package/src/components/picker/i18n/fr.json +60 -0
  557. package/src/components/picker/i18n/pl.json +60 -0
  558. package/src/components/picker/i18n/ru.json +60 -0
  559. package/src/components/picker/i18n/uk.json +60 -0
  560. package/src/components/picker/i18n.ts +19 -0
  561. package/src/components/picker/icon-color-picker.tsx +25 -0
  562. package/src/components/picker/icon-picker.tsx +13 -0
  563. package/src/components/picker/index.ts +11 -0
  564. package/src/components/picker/panel.tsx +61 -0
  565. package/src/components/picker/root.tsx +81 -0
  566. package/src/components/picker/roving.ts +70 -0
  567. package/src/components/picker/surface.tsx +62 -0
  568. package/src/components/picker/swatches/custom.tsx +110 -0
  569. package/src/components/picker/swatches.tsx +67 -0
  570. package/src/components/picker/text.ts +46 -0
  571. package/src/components/picker/tile.tsx +35 -0
  572. package/src/components/picker/translate.ts +15 -0
  573. package/src/components/picker/trigger.tsx +54 -0
  574. package/src/components/picker/types.local.ts +154 -0
  575. package/src/components/picker/types.ts +125 -0
  576. package/src/components/scheme/consts.local.ts +13 -0
  577. package/src/components/scheme/hook.ts +62 -0
  578. package/src/components/scheme/index.ts +4 -0
  579. package/src/components/scheme/toggle.tsx +32 -0
  580. package/src/components/scheme/types.ts +23 -0
  581. package/src/components/socket/i18n/be.json +9 -0
  582. package/src/components/socket/i18n/de.json +9 -0
  583. package/src/components/socket/i18n/en.json +9 -0
  584. package/src/components/socket/i18n/es.json +9 -0
  585. package/src/components/socket/i18n/fr.json +9 -0
  586. package/src/components/socket/i18n/pl.json +9 -0
  587. package/src/components/socket/i18n/ru.json +9 -0
  588. package/src/components/socket/i18n/uk.json +9 -0
  589. package/src/components/socket/i18n.ts +19 -0
  590. package/src/components/socket/index.ts +2 -0
  591. package/src/components/socket/reload-dialog.tsx +61 -0
  592. package/src/components/status.tsx +1 -1
  593. package/src/components/text.tsx +2 -14
  594. package/src/components/toaster.tsx +65 -0
  595. package/src/components/uploader/consts.local.ts +7 -0
  596. package/src/components/uploader/image.tsx +2 -6
  597. package/src/consent/component.tsx +84 -0
  598. package/src/consent/consts.local.ts +10 -0
  599. package/src/consent/consts.ts +1 -0
  600. package/src/consent/geo.ts +26 -0
  601. package/src/consent/index.ts +24 -0
  602. package/src/consent/language.ts +12 -0
  603. package/src/consent/menu-widget.tsx +22 -0
  604. package/src/consent/presence.ts +45 -0
  605. package/src/consent/service.ts +75 -0
  606. package/src/consent/translate.ts +31 -0
  607. package/src/consent/types.local.ts +6 -0
  608. package/src/consent/types.ts +43 -0
  609. package/src/context.ts +12 -0
  610. package/src/editor/component.tsx +172 -0
  611. package/src/editor/consts.local.ts +62 -0
  612. package/src/editor/index.ts +2 -0
  613. package/src/editor/types.ts +24 -0
  614. package/src/entrypoints.ts +5 -0
  615. package/src/exports.ts +6 -5
  616. package/src/index.ts +1 -1
  617. package/src/main.tsx +2 -6
  618. package/src/permission/i18n/be.json +13 -0
  619. package/src/permission/i18n/de.json +13 -0
  620. package/src/permission/i18n/en.json +13 -0
  621. package/src/permission/i18n/es.json +13 -0
  622. package/src/permission/i18n/fr.json +13 -0
  623. package/src/permission/i18n/pl.json +13 -0
  624. package/src/permission/i18n/ru.json +13 -0
  625. package/src/permission/i18n/uk.json +13 -0
  626. package/src/permission/i18n.ts +13 -0
  627. package/src/permission/index.ts +27 -0
  628. package/src/permission/toast.tsx +20 -0
  629. package/src/permission/types.ts +5 -0
  630. package/src/picker/icons/consts.ts +309 -0
  631. package/src/picker/icons/types.ts +18 -0
  632. package/src/picker/icons.ts +70 -0
  633. package/src/picker/index.ts +11 -0
  634. package/src/picker/utils/color/types.ts +37 -0
  635. package/src/picker/utils/color.ts +119 -0
  636. package/src/picker/vocabulary/consts.ts +347 -0
  637. package/src/picker/vocabulary/types.ts +72 -0
  638. package/src/picker/vocabulary.ts +111 -0
  639. package/src/scheme/consts.ts +8 -0
  640. package/src/scheme/index.ts +3 -0
  641. package/src/scheme/scheme.ts +66 -0
  642. package/src/scheme/types.ts +30 -0
  643. package/src/types.ts +19 -2
  644. package/tests/consent-language.spec.ts +31 -0
  645. package/tests/consent.spec.ts +112 -0
  646. package/tests/context.ts +4 -0
  647. package/tests/editor.spec.ts +58 -0
  648. package/tests/harness/editor.tsx +32 -0
  649. package/tests/harness/index.html +1 -0
  650. package/tests/harness/lazy-screen.tsx +9 -0
  651. package/tests/harness/mount.tsx +370 -29
  652. package/tests/harness/picker.tsx +110 -0
  653. package/tests/lazy.spec.ts +104 -0
  654. package/tests/login-provider.spec.ts +114 -0
  655. package/tests/login.spec.ts +447 -0
  656. package/tests/nav.spec.ts +449 -3
  657. package/tests/package-boundary.spec.ts +19 -0
  658. package/tests/picker-shots.spec.ts +174 -0
  659. package/tests/picker-vocabulary.spec.ts +217 -0
  660. package/tests/picker.spec.ts +333 -0
  661. package/tests/scheme.spec.ts +149 -0
  662. package/tests/smoke.spec.ts +2 -2
  663. package/tests/socket-dialog.spec.ts +154 -0
  664. package/tests/toaster.spec.ts +59 -0
  665. package/zz-tw.d.mts +2 -0
  666. package/zz-tw.d.mts.map +1 -0
  667. package/build/.gitkeep +0 -0
  668. package/src/auth/modules.ts +0 -5
  669. package/src/modules.ts +0 -5
package/tests/nav.spec.ts CHANGED
@@ -1,17 +1,25 @@
1
1
  import { afterAll, describe, expect, test } from 'bun:test'
2
- import { closeBrowser, mountComponent } from '@owlmeans/test-ui'
2
+ import { mountComponent, browserHelper } from '@owlmeans/test-ui'
3
3
  import { HARNESS_URL } from './context.js'
4
4
 
5
5
  // Browser work does not fit the 5s default: a cold harness compiles the app on first request.
6
6
  const TIMEOUT = 30_000
7
7
 
8
8
  afterAll(async () => {
9
- await closeBrowser()
9
+ await browserHelper.closeBrowser()
10
10
  })
11
11
 
12
12
  const open = async (path: string) => mountComponent({ url: `${HARNESS_URL.replace(/\/$/, '')}${path}` })
13
13
 
14
14
  describe('@owlmeans/web-panel — two-layer navigation', () => {
15
+ test('a sibling detail route keeps the section selected through its pathname', async () => {
16
+ const { page, close } = await open('/reports')
17
+ try {
18
+ await page.locator('#to-sibling').click()
19
+ await page.waitForSelector('#sibling-detail')
20
+ expect(await page.locator('[data-slot="navigation-menu-link"][data-active]').textContent()).toBe('Work')
21
+ } finally { await close() }
22
+ }, TIMEOUT)
15
23
  test('a deep link resolves the active section with no history state', async () => {
16
24
  // The hard-load case: `location.state` is null here, so the section can only come from
17
25
  // the pathname. A menu that highlights nothing on a deep link is the bug this pins.
@@ -84,7 +92,7 @@ describe('@owlmeans/web-panel — two-layer navigation', () => {
84
92
  const { page, close } = await open('/dash')
85
93
  try {
86
94
  await page.waitForSelector('#dash')
87
- const links = page.locator('footer a')
95
+ const links = page.locator('footer a:not([data-login-powered])')
88
96
  expect(await links.count()).toBe(2)
89
97
  expect(await links.nth(0).getAttribute('href')).toBe('/dash')
90
98
  expect(await links.nth(1).getAttribute('href')).toBe('https://owlmeans.com')
@@ -97,3 +105,441 @@ describe('@owlmeans/web-panel — two-layer navigation', () => {
97
105
  }
98
106
  }, TIMEOUT)
99
107
  })
108
+
109
+ /**
110
+ * Perceptual lightness of a computed colour, 0 (black) to 1 (white).
111
+ *
112
+ * The theme is authored in `oklch(L C H)` and Chromium hands that back from
113
+ * `getComputedStyle` unchanged — it does not convert to `rgb()`, and neither does a canvas
114
+ * `fillStyle` round-trip — so L is read straight off. `rgb()` still turns up for the
115
+ * transparent case, and is approximated through its luminance.
116
+ *
117
+ * Comparing lightness rather than colour strings is the point: the failure being pinned is
118
+ * not a wrong colour, it is two colours too CLOSE to tell apart, which equality never sees.
119
+ */
120
+ const lightness = (value: string): number => {
121
+ const oklch = /^oklch\(\s*([\d.]+%?)/i.exec(value.trim())
122
+ if (oklch != null) {
123
+ const raw = oklch[1]
124
+
125
+ return raw.endsWith('%') ? parseFloat(raw) / 100 : parseFloat(raw)
126
+ }
127
+
128
+ const [r, g, b] = (value.match(/[\d.]+/g) ?? []).slice(0, 3).map(Number)
129
+ const channel = (c: number): number => {
130
+ const v = c / 255
131
+
132
+ return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4
133
+ }
134
+
135
+ // The cube root maps luminance onto roughly the same perceptual scale as OKLab's L.
136
+ return (0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b)) ** (1 / 3)
137
+ }
138
+
139
+ /** The gap below which text stops being readable on its surface — as `ensureReadableTheme`. */
140
+ const READABLE = 0.35
141
+
142
+ describe('@owlmeans/web-panel — the header is its own surface', () => {
143
+ // The harness root carries `bg-primary text-primary-foreground`: a dark application shell,
144
+ // both halves of the pair correct. The header paints `bg-background` over it and is
145
+ // therefore a DIFFERENT surface — a near-white foreground inherited from the root lands on
146
+ // a near-white bar, and every child stating no colour of its own goes invisible. Nothing
147
+ // fails at build or at run time, so this is pinned from outside, on rendered pixels.
148
+ test('header content stays legible when the shell around it is dark', async () => {
149
+ const { page, close } = await open('/dash')
150
+ try {
151
+ await page.waitForSelector('#dash')
152
+ const style = async (selector: string, property: 'color' | 'backgroundColor') =>
153
+ page.locator(selector).first().evaluate(
154
+ (el, prop) => window.getComputedStyle(el)[prop as 'color'], property
155
+ )
156
+
157
+ const surface = lightness(await style('header', 'backgroundColor'))
158
+ const legible = async (selector: string) =>
159
+ Math.abs(lightness(await style(selector, 'color')) - surface) >= READABLE
160
+
161
+ // The brand and the action slot state no colour of their own, so they are exactly the
162
+ // elements that inherit across the boundary. The action slot is the reported case: a
163
+ // ghost-variant button rendered white on a white bar.
164
+ expect({
165
+ brand: await legible('a.brand'),
166
+ actions: await legible('#action-slot'),
167
+ section: await legible('[data-slot="navigation-menu-link"]'),
168
+ }).toEqual({ brand: true, actions: true, section: true })
169
+ } finally {
170
+ await close()
171
+ }
172
+ }, TIMEOUT)
173
+
174
+ test('the header keeps an opaque background of its own', async () => {
175
+ // This is what makes it a separate surface, and therefore what makes the pairing above
176
+ // mandatory rather than redundant: it is sticky, so content scrolls beneath it and a
177
+ // transparent bar would show that content through the menu.
178
+ const { page, close } = await open('/dash')
179
+ try {
180
+ await page.waitForSelector('#dash')
181
+ const background = await page.locator('header').first()
182
+ .evaluate(el => window.getComputedStyle(el).backgroundColor)
183
+
184
+ expect(background).not.toMatch(/rgba\(0, 0, 0, 0\)|transparent/)
185
+ } finally {
186
+ await close()
187
+ }
188
+ }, TIMEOUT)
189
+
190
+ test('a broken headerClassName still leaves the backdrop layer opaque', async () => {
191
+ // `?header=broken` hands the header a Tailwind v3 arbitrary-value class v4 never emits, plus
192
+ // a bare `bg-transparent` — exactly what a layout-restyle pass produced once. tailwind-merge
193
+ // drops the header's own `bg-background` for both, so `header` itself legitimately reports a
194
+ // transparent computed background — that is `headerClassName` doing exactly what it says.
195
+ // What must NOT be transparent is the backdrop layer painted behind it: that is the element
196
+ // actually responsible for keeping content from showing through the bar.
197
+ const { page, close } = await open('/dash?header=broken')
198
+ try {
199
+ await page.waitForSelector('#dash')
200
+ const background = await page.locator('[data-nav-backdrop]').first()
201
+ .evaluate(el => window.getComputedStyle(el).backgroundColor)
202
+
203
+ expect(background).not.toMatch(/rgba\(0, 0, 0, 0\)|transparent/)
204
+ } finally {
205
+ await close()
206
+ }
207
+ }, TIMEOUT)
208
+
209
+ test('the backdrop layer never changes which div the shared-rhythm query finds first', async () => {
210
+ // `data-nav-backdrop` sits last in `header`'s DOM but paints behind everything via negative
211
+ // z-index — this pins that a position-based query for the header's own content div
212
+ // (`header > div`, `.first()`) still finds the rhythm div, not the backdrop.
213
+ const { page, close } = await open('/dash')
214
+ try {
215
+ await page.waitForSelector('#dash')
216
+ const first = await page.locator('header > div').first().getAttribute('data-nav-backdrop')
217
+ expect(first).toBeNull()
218
+ } finally {
219
+ await close()
220
+ }
221
+ }, TIMEOUT)
222
+ })
223
+
224
+ describe('@owlmeans/web-panel — the shell footer credit', () => {
225
+ test('the platform credit and the owner notice render, owner notice first', async () => {
226
+ // The harness configures `credit: { poweredBy: true, product: 'Harness', organization: 'Acme' }`
227
+ // with no explicit `line`, so `resolveCredit` composes one from product/organization — the
228
+ // same resolver the sign-in screen uses. Order here is the opposite of the sign-in screen's:
229
+ // the owner's own notice leads, the platform credit follows.
230
+ const { page, close } = await open('/dash')
231
+ try {
232
+ await page.waitForSelector('#dash')
233
+ const credit = page.locator('footer [data-shell-credit]')
234
+ await credit.waitFor()
235
+ const text = await credit.textContent()
236
+ expect(text).toContain('Acme')
237
+ expect(text).toContain('Powered by OwlMeans')
238
+ expect(text?.indexOf('Acme')).toBeLessThan(text!.indexOf('Powered by OwlMeans'))
239
+ const powered = page.locator('footer [data-shell-credit] a[data-login-powered]')
240
+ expect(await powered.getAttribute('href')).toBe('https://owlmeans.com')
241
+ expect(await powered.getAttribute('target')).toBe('_blank')
242
+ } finally {
243
+ await close()
244
+ }
245
+ }, TIMEOUT)
246
+
247
+ test('the footer links and the credit are centred, not left-aligned', async () => {
248
+ const { page, close } = await open('/dash')
249
+ try {
250
+ await page.waitForSelector('#dash')
251
+ const justify = await page.locator('footer > div').first()
252
+ .evaluate(el => window.getComputedStyle(el).alignItems)
253
+ expect(justify).toBe('center')
254
+ } finally {
255
+ await close()
256
+ }
257
+ }, TIMEOUT)
258
+
259
+ test('a layout with no footer prop still shows the credit', async () => {
260
+ // `NavLayout` used to skip `Footer` entirely when no `footer` prop was passed, which made the
261
+ // credit disappear from any area layout that never wired footer links. `?footer=none` omits
262
+ // the prop from the harness's own `NavLayout` call.
263
+ const { page, close } = await open('/dash?footer=none')
264
+ try {
265
+ await page.waitForSelector('#dash')
266
+ expect(await page.locator('footer a:not([data-login-powered])').count()).toBe(0)
267
+ expect(await page.locator('footer [data-shell-credit]').count()).toBe(1)
268
+ } finally {
269
+ await close()
270
+ }
271
+ }, TIMEOUT)
272
+ })
273
+
274
+ describe('@owlmeans/web-panel — layout rhythm and link styling', () => {
275
+ test('header, content and footer share one horizontal rhythm', async () => {
276
+ // A content area with a width of its own sits visibly inset from a full-width header, which
277
+ // is what "the screen padding is much narrower than the header" looks like from outside.
278
+ const { page, close } = await open('/prefs')
279
+ try {
280
+ await page.waitForSelector('#prefs')
281
+ const box = async (sel: string) => {
282
+ const b = await page.locator(sel).first().boundingBox()
283
+ if (b == null) throw new Error(`no box for ${sel}`)
284
+ return { x: Math.round(b.x), w: Math.round(b.width) }
285
+ }
286
+ const header = await box('header > div')
287
+ const content = await box('main > div')
288
+ const footer = await box('footer > div')
289
+
290
+ expect({ ...content, of: 'content' }).toEqual({ ...header, of: 'content' })
291
+ expect({ ...footer, of: 'footer' }).toEqual({ ...header, of: 'footer' })
292
+ } finally {
293
+ await close()
294
+ }
295
+ }, TIMEOUT)
296
+
297
+ test('a width-only rhythm override keeps the side padding', async () => {
298
+ // The harness passes `containerClassName="max-w-[1280px]"` — a width and nothing else.
299
+ // `containerClassName` is merged over the shell's default rhythm rather than substituted
300
+ // for it, so `px-4` and `mx-auto` survive an override that never mentioned them. Replacing
301
+ // instead produced a header, content and footer all flush to the window edge.
302
+ const { page, close } = await open('/prefs')
303
+ try {
304
+ await page.waitForSelector('#prefs')
305
+ const padding = async (selector: string) => page.locator(selector).first().evaluate(el => {
306
+ const style = window.getComputedStyle(el)
307
+
308
+ return [parseFloat(style.paddingLeft), parseFloat(style.paddingRight)]
309
+ })
310
+
311
+ const [header, content, footer] = await Promise.all([
312
+ padding('header > div'), padding('main > div'), padding('footer > div'),
313
+ ])
314
+
315
+ expect({
316
+ header: header.every(value => value > 0),
317
+ content: content.every(value => value > 0),
318
+ footer: footer.every(value => value > 0),
319
+ }).toEqual({ header: true, content: true, footer: true })
320
+ } finally {
321
+ await close()
322
+ }
323
+ }, TIMEOUT)
324
+
325
+ test('section entries render as links, not buttons', async () => {
326
+ const { page, close } = await open('/dash')
327
+ try {
328
+ await page.waitForSelector('#dash')
329
+ const active = page.locator('[data-slot="navigation-menu-link"][data-active]')
330
+ const style = await active.evaluate(el => {
331
+ const s = window.getComputedStyle(el)
332
+ return { bg: s.backgroundColor, radius: s.borderTopLeftRadius, deco: s.textDecorationLine }
333
+ })
334
+ // A filled background plus a radius is the tile look the shadcn primitive ships; a link
335
+ // carries neither, and marks the current section with an underline instead.
336
+ expect(style.bg).toMatch(/rgba\(0, 0, 0, 0\)|transparent/)
337
+ expect(style.radius).toBe('0px')
338
+ expect(style.deco).toBe('underline')
339
+ } finally {
340
+ await close()
341
+ }
342
+ }, TIMEOUT)
343
+ })
344
+
345
+ describe('@owlmeans/web-panel — a node footer', () => {
346
+ test('a node footer spans the container, which stays centred, and the credit follows it', async () => {
347
+ // `?footer=node` hands `NavLayout` an application's own footer layout. It used to land inside
348
+ // the centred link row, shrink-wrapped to its content in the middle of the page; it is a
349
+ // full-width block of the container now, while the container itself keeps `items-center`
350
+ // for the link row and the credit.
351
+ const { page, close } = await open('/prefs?footer=node')
352
+ try {
353
+ await page.waitForSelector('#prefs')
354
+ await page.locator('footer [data-shell-credit]').waitFor()
355
+ const container = page.locator('footer > div').first()
356
+ const layout = await container.evaluate(el => {
357
+ const style = window.getComputedStyle(el)
358
+ const box = el.getBoundingClientRect()
359
+ const block = el.querySelector(':scope > [data-footer-content]')
360
+ const inner = block?.getBoundingClientRect()
361
+
362
+ return {
363
+ align: style.alignItems,
364
+ inner: {
365
+ x: Math.round(box.x + parseFloat(style.paddingLeft)),
366
+ w: Math.round(box.width - parseFloat(style.paddingLeft) - parseFloat(style.paddingRight)),
367
+ },
368
+ block: inner == null ? null : { x: Math.round(inner.x), w: Math.round(inner.width) },
369
+ hasHarnessBlock: block?.querySelector('#footer-block') != null,
370
+ last: el.lastElementChild?.hasAttribute('data-shell-credit') ?? false,
371
+ creditAfterBlock: block != null && el.querySelector(':scope > [data-shell-credit]') != null
372
+ && (block.compareDocumentPosition(el.querySelector(':scope > [data-shell-credit]')!)
373
+ & Node.DOCUMENT_POSITION_FOLLOWING) !== 0,
374
+ }
375
+ })
376
+
377
+ expect(layout.align).toBe('center')
378
+ expect(layout.block).toEqual(layout.inner)
379
+ expect(layout.hasHarnessBlock).toBe(true)
380
+ expect({ last: layout.last, creditAfterBlock: layout.creditAfterBlock })
381
+ .toEqual({ last: true, creditAfterBlock: true })
382
+ } finally {
383
+ await close()
384
+ }
385
+ }, TIMEOUT)
386
+
387
+ test('a node footer keeps the shared rhythm', async () => {
388
+ const { page, close } = await open('/prefs?footer=node')
389
+ try {
390
+ await page.waitForSelector('#prefs')
391
+ const box = async (sel: string) => {
392
+ const b = await page.locator(sel).first().boundingBox()
393
+ if (b == null) throw new Error(`no box for ${sel}`)
394
+ return { x: Math.round(b.x), w: Math.round(b.width) }
395
+ }
396
+
397
+ expect(await box('footer > div')).toEqual(await box('header > div'))
398
+ } finally {
399
+ await close()
400
+ }
401
+ }, TIMEOUT)
402
+ })
403
+
404
+ /** The narrow viewport the menu sheet is for — below Tailwind's `md` (768px). */
405
+ const PHONE = { width: 375, height: 800 }
406
+
407
+ describe('@owlmeans/web-panel — the narrow-viewport menu', () => {
408
+ test('configured selectors identify a single destination in desktop and mobile navigation', async () => {
409
+ const { page, close } = await open('/dash?mobileMenu=1')
410
+ try {
411
+ await page.getByTestId('harness-nav').waitFor()
412
+ expect(await page.getByTestId('settings-nav').count()).toBe(1)
413
+ expect(await page.getByTestId('dashboard-nav').count()).toBe(1)
414
+ await page.setViewportSize(PHONE)
415
+ await page.getByRole('button', { name: 'Menu' }).click()
416
+ expect(await page.getByTestId('settings-nav').count()).toBe(1)
417
+ expect(await page.getByTestId('dashboard-nav').count()).toBe(1)
418
+ await page.getByTestId('settings-nav').click()
419
+ await page.waitForSelector('#prefs')
420
+ await page.getByRole('dialog', { name: 'Menu' }).waitFor({ state: 'detached' })
421
+ } finally { await close() }
422
+ }, TIMEOUT)
423
+ test('with mobileMenu, a phone gets a menu button instead of the section menu', async () => {
424
+ const { page, close } = await open('/dash?mobileMenu=1')
425
+ try {
426
+ await page.setViewportSize(PHONE)
427
+ await page.waitForSelector('#dash')
428
+ const trigger = page.getByRole('button', { name: 'Menu' })
429
+ expect(await trigger.isVisible()).toBe(true)
430
+ const box = await trigger.boundingBox()
431
+ expect((box?.width ?? 0) >= 44 && (box?.height ?? 0) >= 44).toBe(true)
432
+ // The section menu hides, the action slot stays, and the screen strip is not mounted at all:
433
+ // the sheet lists the same screens.
434
+ expect(await page.locator('header nav[aria-label="Sections"]').first().isVisible()).toBe(false)
435
+ expect(await page.locator('#action-slot').isVisible()).toBe(true)
436
+ expect(await page.locator('header nav[aria-label="Screens"]').count()).toBe(0)
437
+ expect(await page.locator('[data-nav-sheet]').count()).toBe(0)
438
+ } finally {
439
+ await close()
440
+ }
441
+ }, TIMEOUT)
442
+
443
+ test('the sheet lists sections and their screens as links, and a press navigates and closes it', async () => {
444
+ const { page, close } = await open('/dash?mobileMenu=1')
445
+ try {
446
+ await page.setViewportSize(PHONE)
447
+ await page.waitForSelector('#dash')
448
+ await page.getByRole('button', { name: 'Menu' }).click()
449
+ const sheet = page.getByRole('dialog', { name: 'Menu' })
450
+ await sheet.waitFor()
451
+
452
+ // A multi-screen section lists its screens under its name; a single-screen section is one
453
+ // link carrying the section's label — no destination twice. Labels resolve as the menus'.
454
+ const links = sheet.getByRole('link')
455
+ expect(await links.allTextContents()).toEqual(['Dashboard', 'Reports', 'Settings', 'Extra'])
456
+ expect(await links.evaluateAll(els => els.map(el => el.getAttribute('href'))))
457
+ .toEqual(['/dash', '/reports', '/prefs', '/'])
458
+ expect(await sheet.getByRole('link', { name: 'Dashboard' }).getAttribute('aria-current')).toBe('page')
459
+ expect(await sheet.getByText('Work').count()).toBe(1)
460
+
461
+ await sheet.getByRole('link', { name: 'Settings' }).click()
462
+ await page.waitForSelector('#prefs')
463
+ expect(new URL(page.url()).pathname).toBe('/prefs')
464
+ await page.locator('[data-nav-sheet]').waitFor({ state: 'detached' })
465
+ } finally {
466
+ await close()
467
+ }
468
+ }, TIMEOUT)
469
+
470
+ test('with mobileMenu, a wide viewport is the ordinary shell', async () => {
471
+ const { page, close } = await open('/dash?mobileMenu=1')
472
+ try {
473
+ await page.waitForSelector('#dash')
474
+ expect(await page.locator('[data-nav-menu-trigger]').isVisible()).toBe(false)
475
+ expect(await page.locator('header nav[aria-label="Sections"]').first().isVisible()).toBe(true)
476
+ expect(await page.locator('aside nav button').count()).toBe(2)
477
+ } finally {
478
+ await close()
479
+ }
480
+ }, TIMEOUT)
481
+
482
+ test('without mobileMenu, a phone keeps the section menu and the screen strip', async () => {
483
+ const { page, close } = await open('/dash')
484
+ try {
485
+ await page.setViewportSize(PHONE)
486
+ await page.waitForSelector('#dash')
487
+ expect(await page.locator('[data-nav-menu-trigger]').count()).toBe(0)
488
+ expect(await page.locator('header nav[aria-label="Sections"]').first().isVisible()).toBe(true)
489
+ const strip = page.locator('header nav[aria-label="Screens"]')
490
+ expect(await strip.isVisible()).toBe(true)
491
+ expect(await strip.locator('button').count()).toBe(2)
492
+ } finally {
493
+ await close()
494
+ }
495
+ }, TIMEOUT)
496
+ })
497
+
498
+ describe('@owlmeans/web-panel — the skip link', () => {
499
+ test('Tab from the page start reaches the skip link first, and it moves focus to main#main', async () => {
500
+ const { page, close } = await open('/dash')
501
+ try {
502
+ await page.waitForSelector('#dash')
503
+ const link = page.locator('[data-skip-link]')
504
+ // First in the shell's root, before the header — never inside it, so `header > div` is
505
+ // still the shared-rhythm row.
506
+ expect(await link.evaluate(el => el.nextElementSibling?.tagName)).toBe('HEADER')
507
+ expect(await page.locator('header [data-skip-link]').count()).toBe(0)
508
+ expect(await link.isVisible()).toBe(true) // `sr-only` is still rendered, just clipped
509
+ const clipped = await link.boundingBox()
510
+ expect((clipped?.width ?? 0) <= 1).toBe(true)
511
+
512
+ await page.keyboard.press('Tab')
513
+ expect(await page.evaluate(() => document.activeElement?.hasAttribute('data-skip-link'))).toBe(true)
514
+ expect(await link.textContent()).toBe('Skip to content')
515
+ const shown = await link.evaluate(el => {
516
+ const style = window.getComputedStyle(el)
517
+ return { position: style.position, width: el.getBoundingClientRect().width }
518
+ })
519
+ expect(shown.position).toBe('fixed')
520
+ expect(shown.width > 1).toBe(true)
521
+
522
+ await page.keyboard.press('Enter')
523
+ expect(await page.evaluate(() => {
524
+ const active = document.activeElement
525
+ return active?.tagName === 'MAIN' ? active.id : null
526
+ })).toBe('main')
527
+ // In-app: the location is left alone, so the router sees no navigation.
528
+ expect(new URL(page.url()).hash).toBe('')
529
+ } finally {
530
+ await close()
531
+ }
532
+ }, TIMEOUT)
533
+
534
+ test('skipLinkLabel={false} renders no link and claims no #main', async () => {
535
+ const { page, close } = await open('/dash?skip=off')
536
+ try {
537
+ await page.waitForSelector('#dash')
538
+ expect(await page.locator('[data-skip-link]').count()).toBe(0)
539
+ expect(await page.locator('#main').count()).toBe(0)
540
+ expect(await page.locator('main').count()).toBe(1)
541
+ } finally {
542
+ await close()
543
+ }
544
+ }, TIMEOUT)
545
+ })
@@ -0,0 +1,19 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+ import { readdirSync, readFileSync } from 'node:fs'
3
+ import { join } from 'node:path'
4
+
5
+ const source = join(import.meta.dir, '../src')
6
+
7
+ const sourceFiles = (dir: string): string[] => readdirSync(dir, { withFileTypes: true }).flatMap(entry => {
8
+ const path = join(dir, entry.name)
9
+
10
+ return entry.isDirectory() ? sourceFiles(path) : [path]
11
+ })
12
+
13
+ describe('@owlmeans/web-panel — package boundary', () => {
14
+ test('does not use consumer-project UI aliases in package source', () => {
15
+ for (const file of sourceFiles(source).filter(file => /\.tsx?$/.test(file))) {
16
+ expect(readFileSync(file, 'utf8')).not.toMatch(/from\s+['"]@\//)
17
+ }
18
+ })
19
+ })
@@ -0,0 +1,174 @@
1
+ import { afterAll, describe, expect, test } from 'bun:test'
2
+ import { mkdirSync } from 'node:fs'
3
+ import { join } from 'node:path'
4
+ import { browserHelper } from '@owlmeans/test-ui'
5
+ import type { Page } from '@owlmeans/test-ui'
6
+ import { HARNESS_URL } from './context.js'
7
+
8
+ /**
9
+ * Visual review material, not an assertion suite: every picker scenario at 1280 and 375, light
10
+ * and dark, written as PNGs for a person to look at. Runs only when `PICKER_SHOTS_DIR` names the
11
+ * directory to write to — `PICKER_SHOTS_DIR=/tmp/shots bun test tests/picker-shots.spec.ts`.
12
+ */
13
+ const dir = process.env.PICKER_SHOTS_DIR
14
+ const TIMEOUT = 120_000
15
+
16
+ afterAll(async () => {
17
+ await browserHelper.closeBrowser()
18
+ })
19
+
20
+ interface Shot {
21
+ name: string
22
+ path: string
23
+ phone?: boolean
24
+ dark?: 'class' | 'os'
25
+ act?: (page: Page) => Promise<void>
26
+ }
27
+
28
+ const openMark = async (page: Page): Promise<void> => {
29
+ await page.locator('[data-picker-trigger]').first().click()
30
+ await page.locator('[data-picker-surface]').waitFor()
31
+ await page.waitForTimeout(250)
32
+ }
33
+
34
+ const shots: Shot[] = [
35
+ { name: 'desktop-closed', path: '/picker' },
36
+ { name: 'desktop-open', path: '/picker', act: openMark },
37
+ { name: 'desktop-open-dark', path: '/picker?dark=1', dark: 'class', act: openMark },
38
+ { name: 'desktop-open-os-dark', path: '/picker', dark: 'os', act: openMark },
39
+ { name: 'desktop-closed-dark', path: '/picker?dark=1', dark: 'class' },
40
+ {
41
+ name: 'desktop-search', path: '/picker', act: async page => {
42
+ await openMark(page)
43
+ await page.locator('[data-picker-search]').fill('user')
44
+ await page.waitForTimeout(150)
45
+ },
46
+ },
47
+ {
48
+ name: 'desktop-empty-search', path: '/picker', act: async page => {
49
+ await openMark(page)
50
+ await page.locator('[data-picker-search]').fill('zzzz')
51
+ await page.waitForTimeout(150)
52
+ },
53
+ },
54
+ {
55
+ name: 'desktop-category', path: '/picker', act: async page => {
56
+ await openMark(page)
57
+ await page.locator('[data-picker-category="commerce"]').click()
58
+ await page.waitForTimeout(150)
59
+ },
60
+ },
61
+ {
62
+ name: 'desktop-keyboard', path: '/picker', act: async page => {
63
+ await openMark(page)
64
+ await page.keyboard.press('ArrowDown')
65
+ await page.keyboard.press('ArrowRight')
66
+ await page.keyboard.press('ArrowDown')
67
+ await page.waitForTimeout(150)
68
+ },
69
+ },
70
+ {
71
+ name: 'desktop-custom', path: '/picker?custom=1', act: async page => {
72
+ await openMark(page)
73
+ await page.locator('[data-picker-color="custom"]').click()
74
+ await page.locator('[data-picker-hex]').fill('#0f766e')
75
+ await page.waitForTimeout(150)
76
+ },
77
+ },
78
+ {
79
+ name: 'desktop-custom-dark', path: '/picker?custom=1&dark=1', dark: 'class', act: async page => {
80
+ await openMark(page)
81
+ await page.locator('[data-picker-color="custom"]').click()
82
+ await page.locator('[data-picker-hex]').fill('#ff8800')
83
+ await page.waitForTimeout(150)
84
+ },
85
+ },
86
+ {
87
+ name: 'desktop-color-picker', path: '/picker', act: async page => {
88
+ await page.locator('[data-picker-trigger]').nth(1).click()
89
+ await page.locator('[data-picker-surface]').waitFor()
90
+ await page.waitForTimeout(250)
91
+ },
92
+ },
93
+ {
94
+ name: 'desktop-icon-picker', path: '/picker', act: async page => {
95
+ await page.locator('[data-picker-trigger]').nth(2).click()
96
+ await page.locator('[data-picker-surface]').waitFor()
97
+ await page.waitForTimeout(250)
98
+ },
99
+ },
100
+ { name: 'desktop-empty', path: '/picker?empty=1' },
101
+ { name: 'desktop-compact', path: '/picker?compact=1' },
102
+ { name: 'desktop-compact-open', path: '/picker?compact=1', act: openMark },
103
+ {
104
+ name: 'desktop-in-sheet', path: '/picker?inSheet=1', act: async page => {
105
+ await page.locator('#open-sheet').click()
106
+ await page.locator('[data-slot="sheet-content"]').waitFor()
107
+ await page.waitForTimeout(400)
108
+ await openMark(page)
109
+ },
110
+ },
111
+ { name: 'desktop-inline', path: '/picker?inline=1&custom=1' },
112
+ { name: 'desktop-bare', path: '/picker?bare=1' },
113
+ { name: 'phone-closed', path: '/picker', phone: true },
114
+ { name: 'phone-open', path: '/picker', phone: true, act: openMark },
115
+ { name: 'phone-open-dark', path: '/picker?dark=1', dark: 'class', phone: true, act: openMark },
116
+ {
117
+ name: 'phone-scrolled', path: '/picker', phone: true, act: async page => {
118
+ await openMark(page)
119
+ await page.locator('[data-picker-surface] [data-picker-scroll]').evaluate(element => { element.scrollTop = 420 })
120
+ await page.waitForTimeout(150)
121
+ },
122
+ },
123
+ {
124
+ name: 'phone-custom', path: '/picker?custom=1', phone: true, act: async page => {
125
+ await openMark(page)
126
+ await page.locator('[data-picker-color="custom"]').click()
127
+ await page.waitForTimeout(150)
128
+ },
129
+ },
130
+ {
131
+ name: 'phone-color-picker', path: '/picker', phone: true, act: async page => {
132
+ await page.locator('[data-picker-trigger]').nth(1).click()
133
+ await page.locator('[data-picker-surface]').waitFor()
134
+ await page.waitForTimeout(250)
135
+ },
136
+ },
137
+ { name: 'phone-inline', path: '/picker?inline=1', phone: true },
138
+ {
139
+ name: 'phone-in-sheet', path: '/picker?inSheet=1', phone: true, act: async page => {
140
+ await page.locator('#open-sheet').click()
141
+ await page.locator('[data-slot="sheet-content"]').waitFor()
142
+ await page.waitForTimeout(400)
143
+ await openMark(page)
144
+ },
145
+ },
146
+ { name: 'phone-empty', path: '/picker?empty=1', phone: true, act: openMark },
147
+ { name: 'phone-compact', path: '/picker?compact=1', phone: true },
148
+ ]
149
+
150
+ describe.skipIf(dir == null)('@owlmeans/web-panel/picker — screenshots', () => {
151
+ test('every scenario', async () => {
152
+ const out = dir ?? ''
153
+ mkdirSync(out, { recursive: true })
154
+ const browser = await browserHelper.launchBrowser()
155
+ for (const shot of shots) {
156
+ const context = await browser.newContext(shot.phone === true
157
+ ? { viewport: { width: 375, height: 812 }, deviceScaleFactor: 2, hasTouch: true, isMobile: true }
158
+ : { viewport: { width: 1280, height: 800 } })
159
+ const page = await context.newPage()
160
+ try {
161
+ if (shot.dark === 'os') {
162
+ await page.emulateMedia({ colorScheme: 'dark' })
163
+ }
164
+ await page.goto(`${HARNESS_URL.replace(/\/$/, '')}${shot.path}`, { waitUntil: 'domcontentloaded' })
165
+ await page.locator('#picker').waitFor()
166
+ await shot.act?.(page)
167
+ await page.screenshot({ path: join(out, `${shot.name}.png`) })
168
+ } finally {
169
+ await context.close()
170
+ }
171
+ }
172
+ expect(shots.length).toBeGreaterThan(0)
173
+ }, TIMEOUT * 3)
174
+ })