@skewedaspect/sleekspace-ui 0.8.0 → 0.9.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 (341) hide show
  1. package/dist/{src/components → components}/Accordion/SkAccordion.vue.d.ts +1 -1
  2. package/dist/{src/components → components}/Accordion/types.d.ts +1 -1
  3. package/dist/{src/components → components}/Alert/SkAlert.vue.d.ts +1 -1
  4. package/dist/{src/components → components}/Alert/types.d.ts +1 -1
  5. package/dist/{src/components → components}/Avatar/SkAvatar.vue.d.ts +1 -1
  6. package/dist/{src/components → components}/Avatar/types.d.ts +1 -1
  7. package/dist/{src/components → components}/Breadcrumbs/SkBreadcrumbs.vue.d.ts +2 -2
  8. package/dist/{src/components → components}/Breadcrumbs/types.d.ts +1 -1
  9. package/dist/{src/components → components}/Button/SkButton.vue.d.ts +1 -1
  10. package/dist/{src/components → components}/Button/types.d.ts +1 -1
  11. package/dist/{src/components → components}/Card/SkCard.vue.d.ts +1 -1
  12. package/dist/components/Card/types.d.ts +2 -0
  13. package/dist/{src/components → components}/Checkbox/SkCheckbox.vue.d.ts +1 -1
  14. package/dist/{src/components → components}/Checkbox/types.d.ts +1 -1
  15. package/dist/{src/components → components}/Collapsible/SkCollapsible.vue.d.ts +1 -1
  16. package/dist/components/Collapsible/types.d.ts +2 -0
  17. package/dist/{src/components → components}/ColorPicker/SkColorPicker.vue.d.ts +1 -1
  18. package/dist/{src/components → components}/ColorPicker/types.d.ts +1 -1
  19. package/dist/{src/components → components}/ContextMenu/SkContextMenu.vue.d.ts +1 -1
  20. package/dist/components/ContextMenu/types.d.ts +2 -0
  21. package/dist/{src/components → components}/Divider/SkDivider.vue.d.ts +1 -1
  22. package/dist/{src/components → components}/Dropdown/SkDropdown.vue.d.ts +10 -2
  23. package/dist/{src/components → components}/Dropdown/types.d.ts +2 -1
  24. package/dist/{src/components → components}/Input/SkInput.vue.d.ts +1 -1
  25. package/dist/{src/components → components}/Input/types.d.ts +1 -1
  26. package/dist/{src/components → components}/Listbox/SkListbox.vue.d.ts +1 -1
  27. package/dist/{src/components → components}/Listbox/types.d.ts +1 -1
  28. package/dist/{src/components → components}/Modal/SkModal.vue.d.ts +1 -1
  29. package/dist/{src/components → components}/Modal/types.d.ts +1 -1
  30. package/dist/{src/components → components}/NavBar/SkNavBar.vue.d.ts +10 -2
  31. package/dist/{src/components → components}/NavBar/context.d.ts +2 -0
  32. package/dist/components/NavBar/types.d.ts +10 -0
  33. package/dist/{src/components → components}/NumberInput/SkNumberInput.vue.d.ts +1 -1
  34. package/dist/{src/components → components}/NumberInput/types.d.ts +1 -1
  35. package/dist/{src/components → components}/Page/SkPage.vue.d.ts +9 -0
  36. package/dist/{src/components → components}/Pagination/SkPagination.vue.d.ts +3 -3
  37. package/dist/{src/components → components}/Pagination/types.d.ts +1 -1
  38. package/dist/{src/components → components}/Panel/SkPanel.vue.d.ts +1 -1
  39. package/dist/{src/components → components}/Panel/types.d.ts +1 -1
  40. package/dist/{src/components → components}/Popover/SkPopover.vue.d.ts +1 -1
  41. package/dist/{src/components → components}/Progress/SkProgress.vue.d.ts +1 -1
  42. package/dist/{src/components → components}/Progress/types.d.ts +1 -1
  43. package/dist/{src/components → components}/Radio/SkRadio.vue.d.ts +1 -1
  44. package/dist/{src/components → components}/Radio/types.d.ts +1 -1
  45. package/dist/components/ScrollArea/SkScrollArea.vue.d.ts +141 -0
  46. package/dist/{src/components → components}/ScrollArea/types.d.ts +1 -1
  47. package/dist/{src/components → components}/Select/SkSelect.vue.d.ts +1 -1
  48. package/dist/{src/components → components}/Select/types.d.ts +1 -1
  49. package/dist/{src/components → components}/Sidebar/SkSidebar.vue.d.ts +1 -1
  50. package/dist/{src/components → components}/Sidebar/types.d.ts +1 -1
  51. package/dist/{src/components → components}/Slider/SkSlider.vue.d.ts +1 -1
  52. package/dist/{src/components → components}/Slider/types.d.ts +1 -1
  53. package/dist/{src/components → components}/Spinner/SkSpinner.vue.d.ts +1 -1
  54. package/dist/{src/components → components}/Spinner/types.d.ts +1 -1
  55. package/dist/{src/components → components}/Splitter/types.d.ts +1 -1
  56. package/dist/{src/components → components}/Switch/SkSwitch.vue.d.ts +1 -1
  57. package/dist/{src/components → components}/Switch/types.d.ts +1 -1
  58. package/dist/{src/components → components}/Table/SkTable.vue.d.ts +1 -1
  59. package/dist/{src/components → components}/Table/types.d.ts +1 -1
  60. package/dist/{src/components → components}/Tabs/SkTab.vue.d.ts +1 -1
  61. package/dist/{src/components → components}/Tabs/SkTabs.vue.d.ts +1 -1
  62. package/dist/{src/components → components}/Tag/SkTag.vue.d.ts +1 -1
  63. package/dist/{src/components → components}/TagsInput/SkTagsInput.vue.d.ts +1 -1
  64. package/dist/{src/components → components}/TagsInput/types.d.ts +1 -1
  65. package/dist/{src/components → components}/Textarea/SkTextarea.vue.d.ts +1 -1
  66. package/dist/{src/components → components}/Textarea/types.d.ts +1 -1
  67. package/dist/{src/components → components}/Toolbar/types.d.ts +1 -1
  68. package/dist/{src/components → components}/Tooltip/SkTooltip.vue.d.ts +1 -1
  69. package/dist/{src/components → components}/Tooltip/types.d.ts +1 -1
  70. package/dist/{src/components → components}/TreeView/types.d.ts +1 -1
  71. package/dist/composables/useCustomColors.d.ts +36 -0
  72. package/{src → dist}/global.d.ts +6 -2
  73. package/dist/sleekspace-ui.css +4253 -1251
  74. package/dist/sleekspace-ui.es.js +204 -109
  75. package/dist/sleekspace-ui.umd.js +204 -109
  76. package/dist/static/classes.d.ts +18 -0
  77. package/dist/static/components/alert.d.ts +12 -0
  78. package/dist/static/components/avatar.d.ts +9 -0
  79. package/dist/static/components/breadcrumbs.d.ts +6 -0
  80. package/dist/static/components/button.d.ts +13 -0
  81. package/dist/static/components/card.d.ts +5 -0
  82. package/dist/static/components/checkbox.d.ts +10 -0
  83. package/dist/static/components/colorPicker.d.ts +8 -0
  84. package/dist/static/components/divider.d.ts +8 -0
  85. package/dist/static/components/dropdown.d.ts +8 -0
  86. package/dist/static/components/field.d.ts +15 -0
  87. package/dist/static/components/group.d.ts +5 -0
  88. package/dist/static/components/input.d.ts +14 -0
  89. package/dist/static/components/navBar.d.ts +16 -0
  90. package/dist/static/components/numberInput.d.ts +15 -0
  91. package/dist/static/components/page.d.ts +9 -0
  92. package/dist/static/components/pagination.d.ts +5 -0
  93. package/dist/static/components/panel.d.ts +11 -0
  94. package/dist/static/components/progress.d.ts +9 -0
  95. package/dist/static/components/radio.d.ts +11 -0
  96. package/dist/static/components/select.d.ts +10 -0
  97. package/dist/static/components/sidebar.d.ts +9 -0
  98. package/dist/static/components/skeleton.d.ts +11 -0
  99. package/dist/static/components/slider.d.ts +12 -0
  100. package/dist/static/components/spinner.d.ts +12 -0
  101. package/dist/static/components/switchInput.d.ts +10 -0
  102. package/dist/static/components/table.d.ts +12 -0
  103. package/dist/static/components/tag.d.ts +8 -0
  104. package/dist/static/components/tagsInput.d.ts +7 -0
  105. package/dist/static/components/textarea.d.ts +12 -0
  106. package/dist/static/components/toolbar.d.ts +12 -0
  107. package/dist/static/components/tooltip.d.ts +7 -0
  108. package/dist/static/escape.d.ts +2 -0
  109. package/dist/static/index.cjs.js +1 -0
  110. package/dist/static/index.d.ts +68 -0
  111. package/dist/static/index.es.js +732 -0
  112. package/dist/static/render.d.ts +12 -0
  113. package/dist/static/specs.d.ts +2 -0
  114. package/dist/static/types.d.ts +43 -0
  115. package/dist/tokens.css +322 -0
  116. package/dist/types/index.d.ts +36 -0
  117. package/docs/guides/installation.md +8 -2
  118. package/docs/guides/pure-css/_meta.yaml +8 -0
  119. package/docs/guides/pure-css/class-api.md +1070 -0
  120. package/docs/guides/pure-css/custom-elements.md +574 -0
  121. package/docs/guides/pure-css/index.md +86 -0
  122. package/docs/guides/pure-css/limitations.md +152 -0
  123. package/docs/guides/pure-css/static-helpers.md +1203 -0
  124. package/llms-full.txt +3736 -261
  125. package/package.json +16 -5
  126. package/src/components/Card/SkCard.vue +1 -0
  127. package/src/components/ContextMenu/SkContextMenuRadioGroup.vue +4 -1
  128. package/src/components/Dropdown/SkDropdown.vue +20 -3
  129. package/src/components/Dropdown/SkDropdownRadioGroup.vue +4 -1
  130. package/src/components/Dropdown/types.ts +2 -1
  131. package/src/components/NavBar/SkNavBar.vue +14 -4
  132. package/src/components/NavBar/context.ts +4 -2
  133. package/src/components/NavBar/types.ts +6 -1
  134. package/src/components/Page/SkPage.vue +11 -0
  135. package/src/components/Panel/SkPanel.vue +2 -1
  136. package/src/components/ScrollArea/SkScrollArea.vue +78 -5
  137. package/src/components/TreeView/SkTreeView.vue +7 -2
  138. package/src/composables/useCustomColors.ts +86 -77
  139. package/src/composables/usePortalContext.test.ts +0 -2
  140. package/src/shims.d.ts +10 -0
  141. package/src/static/__tests__/parity.test.ts +717 -0
  142. package/src/static/__tests__/parityHarness.test.ts +98 -0
  143. package/src/static/__tests__/parityHarness.ts +260 -0
  144. package/src/static/classes.test.ts +82 -0
  145. package/src/static/classes.ts +111 -0
  146. package/src/static/components/__tests__/helpers.test.ts +837 -0
  147. package/src/static/components/alert.ts +117 -0
  148. package/src/static/components/avatar.ts +86 -0
  149. package/src/static/components/breadcrumbs.ts +28 -0
  150. package/src/static/components/button.ts +75 -0
  151. package/src/static/components/card.ts +27 -0
  152. package/src/static/components/checkbox.ts +48 -0
  153. package/src/static/components/colorPicker.ts +45 -0
  154. package/src/static/components/divider.ts +39 -0
  155. package/src/static/components/dropdown.ts +36 -0
  156. package/src/static/components/field.ts +86 -0
  157. package/src/static/components/group.ts +27 -0
  158. package/src/static/components/input.ts +55 -0
  159. package/src/static/components/navBar.ts +94 -0
  160. package/src/static/components/numberInput.ts +64 -0
  161. package/src/static/components/page.ts +31 -0
  162. package/src/static/components/pagination.ts +27 -0
  163. package/src/static/components/panel.ts +33 -0
  164. package/src/static/components/progress.ts +31 -0
  165. package/src/static/components/radio.ts +53 -0
  166. package/src/static/components/select.ts +51 -0
  167. package/src/static/components/sidebar.ts +85 -0
  168. package/src/static/components/skeleton.ts +66 -0
  169. package/src/static/components/slider.ts +50 -0
  170. package/src/static/components/spinner.ts +94 -0
  171. package/src/static/components/switchInput.ts +49 -0
  172. package/src/static/components/table.ts +88 -0
  173. package/src/static/components/tag.ts +76 -0
  174. package/src/static/components/tagsInput.ts +35 -0
  175. package/src/static/components/textarea.ts +53 -0
  176. package/src/static/components/toolbar.ts +74 -0
  177. package/src/static/components/tooltip.ts +29 -0
  178. package/src/static/escape.test.ts +53 -0
  179. package/src/static/escape.ts +28 -0
  180. package/src/static/generated/defaults.ts +378 -0
  181. package/src/static/generated/propTypes.ts +425 -0
  182. package/src/static/index.ts +116 -0
  183. package/src/static/render.test.ts +83 -0
  184. package/src/static/render.ts +76 -0
  185. package/src/static/specs.test.ts +58 -0
  186. package/src/static/specs.ts +230 -0
  187. package/src/static/types.ts +176 -0
  188. package/src/styles/__tests__/testHelpers.ts +97 -0
  189. package/src/styles/base/_custom-elements.scss +51 -0
  190. package/src/styles/base/_index.scss +4 -0
  191. package/src/styles/components/__tests__/componentSelectors.test.ts +2575 -0
  192. package/src/styles/components/_alert.scss +82 -39
  193. package/src/styles/components/_avatar.scss +102 -47
  194. package/src/styles/components/_breadcrumbs.scss +39 -37
  195. package/src/styles/components/_button.scss +58 -5
  196. package/src/styles/components/_card.scss +64 -2
  197. package/src/styles/components/_checkbox.scss +35 -5
  198. package/src/styles/components/_color-picker.scss +48 -13
  199. package/src/styles/components/_divider.scss +86 -52
  200. package/src/styles/components/_dropdown.scss +214 -0
  201. package/src/styles/components/_field.scss +76 -23
  202. package/src/styles/components/_group.scss +190 -79
  203. package/src/styles/components/_index.scss +1 -0
  204. package/src/styles/components/_input.scss +81 -5
  205. package/src/styles/components/_menu.scss +1 -1
  206. package/src/styles/components/_navbar.scss +76 -45
  207. package/src/styles/components/_number-input.scss +88 -83
  208. package/src/styles/components/_page.scss +82 -23
  209. package/src/styles/components/_pagination.scss +240 -212
  210. package/src/styles/components/_panel.scss +268 -122
  211. package/src/styles/components/_progress.scss +120 -70
  212. package/src/styles/components/_radio.scss +35 -5
  213. package/src/styles/components/_scroll-area.scss +50 -22
  214. package/src/styles/components/_select.scss +40 -9
  215. package/src/styles/components/_sidebar.scss +59 -34
  216. package/src/styles/components/_skeleton.scss +111 -65
  217. package/src/styles/components/_slider.scss +34 -10
  218. package/src/styles/components/_spinner.scss +107 -56
  219. package/src/styles/components/_switch.scss +36 -5
  220. package/src/styles/components/_table.scss +150 -166
  221. package/src/styles/components/_tag.scss +244 -154
  222. package/src/styles/components/_tags-input.scss +46 -12
  223. package/src/styles/components/_textarea.scss +36 -5
  224. package/src/styles/components/_toolbar.scss +85 -31
  225. package/src/styles/components/_tooltip.scss +172 -3
  226. package/src/styles/mixins/_cut-border.scss +18 -4
  227. package/src/styles/mixins/_dual-selector.scss +192 -0
  228. package/src/styles/mixins/_index.scss +1 -0
  229. package/src/styles/mixins/dualSelector.test.ts +151 -0
  230. package/src/styles/themes/_colorful.scss +25 -0
  231. package/src/styles/themes/_greyscale.scss +25 -0
  232. package/src/styles/themes/_shade-scale.scss +39 -0
  233. package/src/styles/tokens/_semantic-color-kinds.scss +66 -0
  234. package/src/{types.ts → types/index.ts} +19 -11
  235. package/web-types.json +970 -137
  236. package/dist/src/components/Card/types.d.ts +0 -2
  237. package/dist/src/components/Collapsible/types.d.ts +0 -2
  238. package/dist/src/components/ContextMenu/types.d.ts +0 -2
  239. package/dist/src/components/NavBar/types.d.ts +0 -6
  240. package/dist/src/components/ScrollArea/SkScrollArea.vue.d.ts +0 -40
  241. package/dist/src/composables/useCustomColors.d.ts +0 -74
  242. package/dist/src/composables/useCustomColors.test.d.ts +0 -1
  243. package/dist/src/composables/useFocusTrap.test.d.ts +0 -1
  244. package/dist/src/composables/usePortalContext.test.d.ts +0 -1
  245. package/dist/src/styles/mixins/fluidSize.test.d.ts +0 -1
  246. package/dist/src/types.d.ts +0 -29
  247. /package/dist/{src/components → components}/Accordion/SkAccordionItem.vue.d.ts +0 -0
  248. /package/dist/{src/components → components}/Accordion/index.d.ts +0 -0
  249. /package/dist/{src/components → components}/Avatar/index.d.ts +0 -0
  250. /package/dist/{src/components → components}/Breadcrumbs/SkBreadcrumbItem.vue.d.ts +0 -0
  251. /package/dist/{src/components → components}/Breadcrumbs/SkBreadcrumbSeparator.vue.d.ts +0 -0
  252. /package/dist/{src/components → components}/Breadcrumbs/index.d.ts +0 -0
  253. /package/dist/{src/components → components}/Checkbox/index.d.ts +0 -0
  254. /package/dist/{src/components → components}/Collapsible/index.d.ts +0 -0
  255. /package/dist/{src/components → components}/ColorPicker/index.d.ts +0 -0
  256. /package/dist/{src/components → components}/ContextMenu/SkContextMenuCheckboxItem.vue.d.ts +0 -0
  257. /package/dist/{src/components → components}/ContextMenu/SkContextMenuItem.vue.d.ts +0 -0
  258. /package/dist/{src/components → components}/ContextMenu/SkContextMenuLabel.vue.d.ts +0 -0
  259. /package/dist/{src/components → components}/ContextMenu/SkContextMenuRadioGroup.vue.d.ts +0 -0
  260. /package/dist/{src/components → components}/ContextMenu/SkContextMenuRadioItem.vue.d.ts +0 -0
  261. /package/dist/{src/components → components}/ContextMenu/SkContextMenuSeparator.vue.d.ts +0 -0
  262. /package/dist/{src/components → components}/ContextMenu/SkContextMenuSubmenu.vue.d.ts +0 -0
  263. /package/dist/{src/components → components}/ContextMenu/index.d.ts +0 -0
  264. /package/dist/{src/components → components}/Divider/types.d.ts +0 -0
  265. /package/dist/{src/components → components}/Dropdown/SkDropdownCheckboxItem.vue.d.ts +0 -0
  266. /package/dist/{src/components → components}/Dropdown/SkDropdownMenuItem.vue.d.ts +0 -0
  267. /package/dist/{src/components → components}/Dropdown/SkDropdownMenuLabel.vue.d.ts +0 -0
  268. /package/dist/{src/components → components}/Dropdown/SkDropdownMenuSeparator.vue.d.ts +0 -0
  269. /package/dist/{src/components → components}/Dropdown/SkDropdownRadioGroup.vue.d.ts +0 -0
  270. /package/dist/{src/components → components}/Dropdown/SkDropdownRadioItem.vue.d.ts +0 -0
  271. /package/dist/{src/components → components}/Dropdown/SkDropdownSubmenu.vue.d.ts +0 -0
  272. /package/dist/{src/components → components}/Dropdown/index.d.ts +0 -0
  273. /package/dist/{src/components → components}/Field/SkField.vue.d.ts +0 -0
  274. /package/dist/{src/components → components}/Field/index.d.ts +0 -0
  275. /package/dist/{src/components → components}/Field/types.d.ts +0 -0
  276. /package/dist/{src/components → components}/Group/SkGroup.vue.d.ts +0 -0
  277. /package/dist/{src/components → components}/Group/types.d.ts +0 -0
  278. /package/dist/{src/components → components}/Input/index.d.ts +0 -0
  279. /package/dist/{src/components → components}/Listbox/SkListboxItem.vue.d.ts +0 -0
  280. /package/dist/{src/components → components}/Listbox/SkListboxSeparator.vue.d.ts +0 -0
  281. /package/dist/{src/components → components}/Listbox/index.d.ts +0 -0
  282. /package/dist/{src/components → components}/Modal/index.d.ts +0 -0
  283. /package/dist/{src/components → components}/NavBar/index.d.ts +0 -0
  284. /package/dist/{src/components → components}/NumberInput/index.d.ts +0 -0
  285. /package/dist/{src/components → components}/Page/SkPageSidebarToggle.vue.d.ts +0 -0
  286. /package/dist/{src/components → components}/Page/index.d.ts +0 -0
  287. /package/dist/{src/components → components}/Page/types.d.ts +0 -0
  288. /package/dist/{src/components → components}/Pagination/SkPaginationItem.vue.d.ts +0 -0
  289. /package/dist/{src/components → components}/Pagination/index.d.ts +0 -0
  290. /package/dist/{src/components → components}/Popover/index.d.ts +0 -0
  291. /package/dist/{src/components → components}/Popover/types.d.ts +0 -0
  292. /package/dist/{src/components → components}/Progress/index.d.ts +0 -0
  293. /package/dist/{src/components → components}/Radio/SkRadioGroup.vue.d.ts +0 -0
  294. /package/dist/{src/components → components}/Radio/index.d.ts +0 -0
  295. /package/dist/{src/components → components}/ScrollArea/index.d.ts +0 -0
  296. /package/dist/{src/components → components}/Select/SkSelectItem.vue.d.ts +0 -0
  297. /package/dist/{src/components → components}/Select/SkSelectSeparator.vue.d.ts +0 -0
  298. /package/dist/{src/components → components}/Select/index.d.ts +0 -0
  299. /package/dist/{src/components → components}/Sidebar/SkSidebarItem.vue.d.ts +0 -0
  300. /package/dist/{src/components → components}/Sidebar/SkSidebarSection.vue.d.ts +0 -0
  301. /package/dist/{src/components → components}/Skeleton/SkSkeleton.vue.d.ts +0 -0
  302. /package/dist/{src/components → components}/Skeleton/index.d.ts +0 -0
  303. /package/dist/{src/components → components}/Skeleton/types.d.ts +0 -0
  304. /package/dist/{src/components → components}/Slider/index.d.ts +0 -0
  305. /package/dist/{src/components → components}/Spinner/index.d.ts +0 -0
  306. /package/dist/{src/components → components}/Splitter/SkSplitter.vue.d.ts +0 -0
  307. /package/dist/{src/components → components}/Splitter/SkSplitterHandle.vue.d.ts +0 -0
  308. /package/dist/{src/components → components}/Splitter/SkSplitterPanel.vue.d.ts +0 -0
  309. /package/dist/{src/components → components}/Splitter/index.d.ts +0 -0
  310. /package/dist/{src/components → components}/Switch/index.d.ts +0 -0
  311. /package/dist/{src/components → components}/Table/index.d.ts +0 -0
  312. /package/dist/{src/components → components}/Tabs/SkTabList.vue.d.ts +0 -0
  313. /package/dist/{src/components → components}/Tabs/SkTabPanel.vue.d.ts +0 -0
  314. /package/dist/{src/components → components}/Tabs/SkTabPanels.vue.d.ts +0 -0
  315. /package/dist/{src/components → components}/Tabs/types.d.ts +0 -0
  316. /package/dist/{src/components → components}/Tag/types.d.ts +0 -0
  317. /package/dist/{src/components → components}/TagsInput/index.d.ts +0 -0
  318. /package/dist/{src/components → components}/Textarea/index.d.ts +0 -0
  319. /package/dist/{src/components → components}/Theme/SkTheme.vue.d.ts +0 -0
  320. /package/dist/{src/components → components}/Theme/types.d.ts +0 -0
  321. /package/dist/{src/components → components}/Theme/useTheme.d.ts +0 -0
  322. /package/dist/{src/components → components}/Toast/SkToast.vue.d.ts +0 -0
  323. /package/dist/{src/components → components}/Toast/SkToastProvider.vue.d.ts +0 -0
  324. /package/dist/{src/components → components}/Toast/index.d.ts +0 -0
  325. /package/dist/{src/components → components}/Toast/types.d.ts +0 -0
  326. /package/dist/{src/components → components}/Toast/useToast.d.ts +0 -0
  327. /package/dist/{src/components → components}/Toolbar/SkToolbar.vue.d.ts +0 -0
  328. /package/dist/{src/components → components}/Toolbar/SkToolbarButton.vue.d.ts +0 -0
  329. /package/dist/{src/components → components}/Toolbar/SkToolbarSeparator.vue.d.ts +0 -0
  330. /package/dist/{src/components → components}/Toolbar/SkToolbarToggleGroup.vue.d.ts +0 -0
  331. /package/dist/{src/components → components}/Toolbar/SkToolbarToggleItem.vue.d.ts +0 -0
  332. /package/dist/{src/components → components}/Toolbar/index.d.ts +0 -0
  333. /package/dist/{src/components → components}/Tooltip/SkTooltipProvider.vue.d.ts +0 -0
  334. /package/dist/{src/components → components}/Tooltip/index.d.ts +0 -0
  335. /package/dist/{src/components → components}/TreeView/SkTreeItem.vue.d.ts +0 -0
  336. /package/dist/{src/components → components}/TreeView/SkTreeView.vue.d.ts +0 -0
  337. /package/dist/{src/components → components}/TreeView/index.d.ts +0 -0
  338. /package/dist/{src/composables → composables}/useFocusTrap.d.ts +0 -0
  339. /package/dist/{src/composables → composables}/usePageDrawer.d.ts +0 -0
  340. /package/dist/{src/composables → composables}/usePortalContext.d.ts +0 -0
  341. /package/dist/{src/index.d.ts → index.d.ts} +0 -0
@@ -0,0 +1,2575 @@
1
+ //----------------------------------------------------------------------------------------------------------------------
2
+ // Component Selector Tests (SCSS)
3
+ //
4
+ // Compiles component SCSS files and asserts that the dual-selector mixin output contains both
5
+ // class-form and attribute-form selectors for each kind, size, modifier, and default variant.
6
+ //----------------------------------------------------------------------------------------------------------------------
7
+
8
+ import { describe, expect, it } from 'vitest';
9
+ import { compileString } from 'sass';
10
+ import { dirname, resolve } from 'node:path';
11
+ import { fileURLToPath, pathToFileURL } from 'node:url';
12
+
13
+ // Utils
14
+ import { KINDS, SEMANTIC_KINDS, SIZES, attrValueRe, dualSelectorRe } from '../../__tests__/testHelpers';
15
+
16
+ //----------------------------------------------------------------------------------------------------------------------
17
+
18
+ const here = dirname(fileURLToPath(import.meta.url));
19
+ // styles root: __tests__ → components → styles
20
+ const stylesDir = resolve(here, '../..');
21
+
22
+ function compileComponent(component : string) : string
23
+ {
24
+ const source = `@use "components/${ component }";`;
25
+ const { css } = compileString(source, {
26
+ loadPaths: [ stylesDir ],
27
+ // URL placed at styles root so relative imports inside _panel.scss work correctly.
28
+ url: pathToFileURL(resolve(stylesDir, `${ component }.test.scss`)),
29
+ });
30
+ return css;
31
+ }
32
+
33
+ //----------------------------------------------------------------------------------------------------------------------
34
+
35
+ // Sass strips quotes from attribute-selector values that don't need them for CSS validity,
36
+ // so `[kind="primary"]` may be emitted as `[kind=primary]`. Accept both forms.
37
+ // `dualSelectorRe`, `attrValueRe`, `KINDS`, and `SIZES` are imported from testHelpers above.
38
+
39
+ //----------------------------------------------------------------------------------------------------------------------
40
+
41
+ describe('component selectors (SCSS)', () =>
42
+ {
43
+ describe('panel', () =>
44
+ {
45
+ // Compile once for the whole describe block — all assertions share the same CSS output.
46
+ let css : string;
47
+
48
+ // Vitest runs `beforeAll` lazily; we use a helper that memoises on first call.
49
+ function panelCSS() : string
50
+ {
51
+ if(!css) { css = compileComponent('panel'); }
52
+ return css;
53
+ }
54
+
55
+ //--------------------------------------------------------------------------------------------------------------
56
+ // Kind variants (17 values from $kinds in theme/_variables.scss)
57
+ //--------------------------------------------------------------------------------------------------------------
58
+
59
+ const kinds = KINDS;
60
+
61
+ describe('kind variants', () =>
62
+ {
63
+ for(const kind of kinds)
64
+ {
65
+ it(`emits .sk-panel.sk-${ kind } and sk-panel[kind="${ kind }"]`, () =>
66
+ {
67
+ const out = panelCSS();
68
+ expect(out).toMatch(
69
+ new RegExp(dualSelectorRe('panel', kind, 'kind', kind))
70
+ );
71
+ });
72
+ }
73
+ });
74
+
75
+ //--------------------------------------------------------------------------------------------------------------
76
+ // Size variants (xs, sm, md, lg, xl)
77
+ //--------------------------------------------------------------------------------------------------------------
78
+
79
+ const sizes = SIZES;
80
+
81
+ describe('size variants', () =>
82
+ {
83
+ for(const size of sizes)
84
+ {
85
+ // Each size emits three selectors: .sk-<size>, .sk-size-<size>, and [size="<size>"].
86
+ // Check all three exist anywhere in the output (not necessarily adjacent).
87
+ it(`emits .sk-panel.sk-${ size } and sk-panel[size="${ size }"]`, () =>
88
+ {
89
+ const out = panelCSS();
90
+ expect(out).toMatch(new RegExp(`\\.sk-panel\\.sk-${ size }[,\\s{]`));
91
+ expect(out).toMatch(new RegExp(`sk-panel\\[size=${ attrValueRe(size) }\\]`));
92
+ });
93
+ }
94
+ });
95
+
96
+ //--------------------------------------------------------------------------------------------------------------
97
+ // Corner cut modifiers (list-modifier with `corners` attribute)
98
+ //--------------------------------------------------------------------------------------------------------------
99
+
100
+ const corners = [ 'top-left', 'top-right', 'bottom-right', 'bottom-left' ];
101
+
102
+ describe('corner cut modifiers', () =>
103
+ {
104
+ for(const corner of corners)
105
+ {
106
+ it(`emits .sk-panel.sk-cut-${ corner } and sk-panel[corners~="${ corner }"]`, () =>
107
+ {
108
+ const out = panelCSS();
109
+ expect(out).toMatch(
110
+ new RegExp(dualSelectorRe('panel', `cut-${ corner }`, 'corners', corner, { attrOp: '~=' }))
111
+ );
112
+ });
113
+ }
114
+ });
115
+
116
+ //--------------------------------------------------------------------------------------------------------------
117
+ // Decoration corner modifiers (single-choice-modifier with `decoration-corner` attribute)
118
+ //--------------------------------------------------------------------------------------------------------------
119
+
120
+ describe('decoration corner modifiers', () =>
121
+ {
122
+ for(const corner of corners)
123
+ {
124
+ // Decoration rules are applied via `&::after`, so the emitted selector includes
125
+ // the `::after` pseudo-element on both the class and attribute forms.
126
+ it(`emits .sk-panel.sk-decoration-${ corner } and sk-panel[decoration-corner="${ corner }"]`, () =>
127
+ {
128
+ const out = panelCSS();
129
+ const opts = { suffix: '::after' };
130
+ expect(out).toMatch(
131
+ new RegExp(dualSelectorRe('panel', `decoration-${ corner }`, 'decoration-corner', corner, opts))
132
+ );
133
+ });
134
+ }
135
+ });
136
+
137
+ //--------------------------------------------------------------------------------------------------------------
138
+ // Boolean modifiers
139
+ //--------------------------------------------------------------------------------------------------------------
140
+
141
+ describe('boolean modifiers', () =>
142
+ {
143
+ it('emits .sk-panel.sk-no-border and sk-panel[no-border]', () =>
144
+ {
145
+ expect(panelCSS()).toMatch(/\.sk-panel\.sk-no-border,\s*sk-panel\[no-border\]/);
146
+ });
147
+
148
+ it('emits .sk-panel.sk-no-decoration and sk-panel[no-decoration]', () =>
149
+ {
150
+ // no-decoration hides the ::after element, so the selector pair includes ::after.
151
+ expect(panelCSS()).toMatch(/\.sk-panel\.sk-no-decoration::after,\s*sk-panel\[no-decoration\]::after/);
152
+ });
153
+ });
154
+
155
+ //--------------------------------------------------------------------------------------------------------------
156
+ // Defaults-when-absent
157
+ //--------------------------------------------------------------------------------------------------------------
158
+
159
+ describe('defaults-when-absent', () =>
160
+ {
161
+ it('applies default bottom-right cut when no sk-cut-* class is present', () =>
162
+ {
163
+ const out = panelCSS();
164
+ // Selector: .sk-panel:where(:not([class*="sk-cut-"])) { --sk-panel-cut-br: ... }
165
+ expect(out).toMatch(
166
+ new RegExp(`\\.sk-panel:where\\(:not\\(\\[class\\*=${ attrValueRe('sk-cut-') }\\]\\)\\)`)
167
+ );
168
+ // The default must set the br cut variable
169
+ expect(out).toMatch(new RegExp(
170
+ String.raw`\.sk-panel:where\(:not\(\[class\*=["']?sk-cut-["']?\]\)\)[^{]*\{[^}]*--sk-panel-cut-br`
171
+ ));
172
+ });
173
+
174
+ it('applies default bottom-right decoration when no sk-decoration-* class is present', () =>
175
+ {
176
+ const out = panelCSS();
177
+ // Selector: .sk-panel:where(:not([class*="sk-decoration-"])) { ... }
178
+ expect(out).toMatch(
179
+ new RegExp(`\\.sk-panel:where\\(:not\\(\\[class\\*=${ attrValueRe('sk-decoration-') }\\]\\)\\)`)
180
+ );
181
+ });
182
+
183
+ it('applies default neutral kind via explicit :not() chain when no kind class present', () =>
184
+ {
185
+ const out = panelCSS();
186
+ // The kind default can't use [class*="sk-"] because every Sleekspace class starts
187
+ // with sk-. Instead we expect an explicit :not() chain for each of the 17 kinds.
188
+ // Check for at least the :where(:not(.sk-neutral):not(.sk-primary)...) pattern.
189
+ expect(out).toMatch(/:where\(:not\(\.sk-neutral\)/);
190
+ expect(out).toMatch(/:not\(\.sk-primary\)/);
191
+ expect(out).toMatch(/:not\(\.sk-red\)\)/);
192
+ });
193
+
194
+ it('applies default md size when no sk-size-* class is present', () =>
195
+ {
196
+ const out = panelCSS();
197
+ // Selector: .sk-panel:where(:not([class*="sk-size-"])) { --sk-panel-cut-size: ... }
198
+ expect(out).toMatch(
199
+ new RegExp(`\\.sk-panel:where\\(:not\\(\\[class\\*=${ attrValueRe('sk-size-') }\\]\\)\\)`)
200
+ );
201
+ // The default size block must set the cut-size variable
202
+ expect(out).toMatch(new RegExp(
203
+ String.raw`\.sk-panel:where\(:not\(\[class\*=["']?sk-size-["']?\]\)\)`
204
+ + String.raw`[^{]*\{[^}]*--sk-panel-cut-size`
205
+ ));
206
+ });
207
+ });
208
+
209
+ //--------------------------------------------------------------------------------------------------------------
210
+ // Base dual-selector (custom-element tag)
211
+ //--------------------------------------------------------------------------------------------------------------
212
+
213
+ it('base rule includes the sk-panel custom-element tag', () =>
214
+ {
215
+ const out = panelCSS();
216
+ expect(out).toMatch(/\.sk-panel,\s*sk-panel\s*\{/);
217
+ });
218
+
219
+ //--------------------------------------------------------------------------------------------------------------
220
+ // Custom-element attribute-absence defaults
221
+ //--------------------------------------------------------------------------------------------------------------
222
+
223
+ describe('custom-element defaults (defaults-when-no-attr)', () =>
224
+ {
225
+ it('emits default-kind rule targeting bare <sk-panel> without [kind]', () =>
226
+ {
227
+ expect(panelCSS()).toMatch(/sk-panel:where\(:not\(\[kind\]\)\)/);
228
+ });
229
+
230
+ it('emits default-size rule for bare <sk-panel>', () =>
231
+ {
232
+ expect(panelCSS()).toMatch(/sk-panel:where\(:not\(\[size\]\)\)/);
233
+ });
234
+
235
+ it('emits default-cut rule for bare <sk-panel>', () =>
236
+ {
237
+ expect(panelCSS()).toMatch(/sk-panel:where\(:not\(\[corners\]\)\)/);
238
+ });
239
+
240
+ it('emits default-decoration rule for bare <sk-panel>', () =>
241
+ {
242
+ expect(panelCSS()).toMatch(/sk-panel:where\(:not\(\[decoration-corner\]\)\)/);
243
+ });
244
+ });
245
+ });
246
+
247
+ //------------------------------------------------------------------------------------------------------------------
248
+
249
+ describe('card', () =>
250
+ {
251
+ let css : string;
252
+
253
+ function cardCSS() : string
254
+ {
255
+ if(!css) { css = compileComponent('card'); }
256
+ return css;
257
+ }
258
+
259
+ //--------------------------------------------------------------------------------------------------------------
260
+ // Card is a structural extension of Panel — it adds layout (flexbox sections) but has
261
+ // no modifier families of its own. Kind, size, and decoration come from Panel. This block
262
+ // verifies the base rule is present so compilation is exercised and regressions are caught.
263
+ //--------------------------------------------------------------------------------------------------------------
264
+
265
+ it('compiles without errors', () =>
266
+ {
267
+ expect(() => cardCSS()).not.toThrow();
268
+ });
269
+
270
+ it('emits .sk-panel.sk-card base rule', () =>
271
+ {
272
+ expect(cardCSS()).toMatch(/\.sk-panel\.sk-card/);
273
+ });
274
+
275
+ //--------------------------------------------------------------------------------------------------------------
276
+ // Kind variants — card inherits kind via Panel. When Card emits its own kind-variant
277
+ // selectors (after the refactor), each kind should appear as both .sk-card.sk-<kind>
278
+ // and sk-card[kind="<kind>"].
279
+ //--------------------------------------------------------------------------------------------------------------
280
+
281
+ describe('kind variants', () =>
282
+ {
283
+ for(const kind of KINDS)
284
+ {
285
+ it(`emits .sk-card.sk-${ kind } and sk-card[kind="${ kind }"]`, () =>
286
+ {
287
+ const out = cardCSS();
288
+ expect(out).toMatch(
289
+ new RegExp(dualSelectorRe('card', kind, 'kind', kind))
290
+ );
291
+ });
292
+ }
293
+ });
294
+
295
+ //--------------------------------------------------------------------------------------------------------------
296
+ // Defaults-when-absent: kind default (neutral)
297
+ //--------------------------------------------------------------------------------------------------------------
298
+
299
+ describe('defaults-when-absent', () =>
300
+ {
301
+ it('applies default neutral kind via :not() chain', () =>
302
+ {
303
+ const out = cardCSS();
304
+ expect(out).toMatch(/\.sk-card:where\(:not\(\.sk-neutral\)/);
305
+ expect(out).toMatch(/:not\(\.sk-primary\)/);
306
+ expect(out).toMatch(/:not\(\.sk-red\)\)/);
307
+ });
308
+ });
309
+
310
+ //--------------------------------------------------------------------------------------------------------------
311
+ // Base dual-selector (custom-element tag)
312
+ //--------------------------------------------------------------------------------------------------------------
313
+
314
+ it('base rule includes the sk-card custom-element tag', () =>
315
+ {
316
+ expect(cardCSS()).toMatch(/\.sk-card,\s*sk-card\s*\{/);
317
+ });
318
+
319
+ //--------------------------------------------------------------------------------------------------------------
320
+ // Custom-element attribute-absence defaults
321
+ //--------------------------------------------------------------------------------------------------------------
322
+
323
+ describe('custom-element defaults (defaults-when-no-attr)', () =>
324
+ {
325
+ it('emits default-kind rule targeting bare <sk-card> without [kind]', () =>
326
+ {
327
+ expect(cardCSS()).toMatch(/sk-card:where\(:not\(\[kind\]\)\)/);
328
+ });
329
+ });
330
+ });
331
+
332
+ //------------------------------------------------------------------------------------------------------------------
333
+
334
+ describe('alert', () =>
335
+ {
336
+ let css : string;
337
+
338
+ function alertCSS() : string
339
+ {
340
+ if(!css) { css = compileComponent('alert'); }
341
+ return css;
342
+ }
343
+
344
+ //--------------------------------------------------------------------------------------------------------------
345
+ // Kind variants (full $kinds list — alert accepts all 17 kinds)
346
+ //--------------------------------------------------------------------------------------------------------------
347
+
348
+ describe('kind variants', () =>
349
+ {
350
+ for(const kind of KINDS)
351
+ {
352
+ it(`emits .sk-alert.sk-${ kind } and sk-alert[kind="${ kind }"]`, () =>
353
+ {
354
+ const out = alertCSS();
355
+ expect(out).toMatch(
356
+ new RegExp(dualSelectorRe('alert', kind, 'kind', kind))
357
+ );
358
+ });
359
+ }
360
+ });
361
+
362
+ //--------------------------------------------------------------------------------------------------------------
363
+ // Boolean modifiers
364
+ //--------------------------------------------------------------------------------------------------------------
365
+
366
+ describe('boolean modifiers', () =>
367
+ {
368
+ it('emits .sk-alert.sk-subtle and sk-alert[subtle]', () =>
369
+ {
370
+ expect(alertCSS()).toMatch(/\.sk-alert\.sk-subtle,\s*sk-alert\[subtle\]/);
371
+ });
372
+ });
373
+
374
+ //--------------------------------------------------------------------------------------------------------------
375
+ // Defaults-when-absent: kind default (info)
376
+ //--------------------------------------------------------------------------------------------------------------
377
+
378
+ describe('defaults-when-absent', () =>
379
+ {
380
+ it('applies default info kind via :not() chain', () =>
381
+ {
382
+ const out = alertCSS();
383
+ // Alert's default kind is 'info'. The :where(:not()) chain covers all kinds.
384
+ expect(out).toMatch(/\.sk-alert:where\(:not\(\.sk-neutral\)/);
385
+ expect(out).toMatch(/:not\(\.sk-info\)/);
386
+ expect(out).toMatch(/:not\(\.sk-red\)\)/);
387
+ });
388
+ });
389
+
390
+ //--------------------------------------------------------------------------------------------------------------
391
+ // Base dual-selector (custom-element tag)
392
+ //--------------------------------------------------------------------------------------------------------------
393
+
394
+ it('base rule includes the sk-alert custom-element tag', () =>
395
+ {
396
+ expect(alertCSS()).toMatch(/\.sk-alert,\s*sk-alert\s*\{/);
397
+ });
398
+
399
+ //--------------------------------------------------------------------------------------------------------------
400
+ // Custom-element attribute-absence defaults
401
+ //--------------------------------------------------------------------------------------------------------------
402
+
403
+ describe('custom-element defaults (defaults-when-no-attr)', () =>
404
+ {
405
+ it('emits default-kind rule targeting bare <sk-alert> without [kind]', () =>
406
+ {
407
+ expect(alertCSS()).toMatch(/sk-alert:where\(:not\(\[kind\]\)\)/);
408
+ });
409
+ });
410
+ });
411
+
412
+ //------------------------------------------------------------------------------------------------------------------
413
+
414
+ describe('divider', () =>
415
+ {
416
+ let css : string;
417
+
418
+ function dividerCSS() : string
419
+ {
420
+ if(!css) { css = compileComponent('divider'); }
421
+ return css;
422
+ }
423
+
424
+ //--------------------------------------------------------------------------------------------------------------
425
+ // Kind variants (full $kinds list)
426
+ //--------------------------------------------------------------------------------------------------------------
427
+
428
+ describe('kind variants', () =>
429
+ {
430
+ for(const kind of KINDS)
431
+ {
432
+ it(`emits .sk-divider.sk-${ kind } and sk-divider[kind="${ kind }"]`, () =>
433
+ {
434
+ const out = dividerCSS();
435
+ expect(out).toMatch(
436
+ new RegExp(dualSelectorRe('divider', kind, 'kind', kind))
437
+ );
438
+ });
439
+ }
440
+ });
441
+
442
+ //--------------------------------------------------------------------------------------------------------------
443
+ // Orientation: single-choice-modifier (horizontal | vertical)
444
+ //--------------------------------------------------------------------------------------------------------------
445
+
446
+ describe('orientation modifiers', () =>
447
+ {
448
+ it('emits .sk-divider.sk-orientation-horizontal and sk-divider[orientation="horizontal"]', () =>
449
+ {
450
+ expect(dividerCSS()).toMatch(
451
+ new RegExp(dualSelectorRe('divider', 'orientation-horizontal', 'orientation', 'horizontal'))
452
+ );
453
+ });
454
+
455
+ it('emits .sk-divider.sk-orientation-vertical and sk-divider[orientation="vertical"]', () =>
456
+ {
457
+ expect(dividerCSS()).toMatch(
458
+ new RegExp(dualSelectorRe('divider', 'orientation-vertical', 'orientation', 'vertical'))
459
+ );
460
+ });
461
+ });
462
+
463
+ //--------------------------------------------------------------------------------------------------------------
464
+ // Boolean modifiers
465
+ //--------------------------------------------------------------------------------------------------------------
466
+
467
+ describe('boolean modifiers', () =>
468
+ {
469
+ it('emits .sk-divider.sk-subtle and sk-divider[subtle]', () =>
470
+ {
471
+ expect(dividerCSS()).toMatch(/\.sk-divider\.sk-subtle,\s*sk-divider\[subtle\]/);
472
+ });
473
+ });
474
+
475
+ //--------------------------------------------------------------------------------------------------------------
476
+ // Defaults-when-absent
477
+ //--------------------------------------------------------------------------------------------------------------
478
+
479
+ describe('defaults-when-absent', () =>
480
+ {
481
+ it('applies default neutral kind via :not() chain', () =>
482
+ {
483
+ const out = dividerCSS();
484
+ expect(out).toMatch(/\.sk-divider:where\(:not\(\.sk-neutral\)/);
485
+ expect(out).toMatch(/:not\(\.sk-red\)\)/);
486
+ });
487
+
488
+ it('applies default horizontal orientation when no sk-orientation-* class is present', () =>
489
+ {
490
+ const out = dividerCSS();
491
+ expect(out).toMatch(
492
+ new RegExp(`\\.sk-divider:where\\(:not\\(\\[class\\*=${ attrValueRe('sk-orientation-') }\\]\\)\\)`)
493
+ );
494
+ });
495
+ });
496
+
497
+ //--------------------------------------------------------------------------------------------------------------
498
+ // Base dual-selector (custom-element tag)
499
+ //--------------------------------------------------------------------------------------------------------------
500
+
501
+ it('base rule includes the sk-divider custom-element tag', () =>
502
+ {
503
+ expect(dividerCSS()).toMatch(/\.sk-divider,\s*sk-divider\s*\{/);
504
+ });
505
+
506
+ //--------------------------------------------------------------------------------------------------------------
507
+ // Custom-element attribute-absence defaults
508
+ //--------------------------------------------------------------------------------------------------------------
509
+
510
+ describe('custom-element defaults (defaults-when-no-attr)', () =>
511
+ {
512
+ it('emits default-kind rule targeting bare <sk-divider> without [kind]', () =>
513
+ {
514
+ expect(dividerCSS()).toMatch(/sk-divider:where\(:not\(\[kind\]\)\)/);
515
+ });
516
+
517
+ it('emits default-orientation rule targeting bare <sk-divider> without [orientation]', () =>
518
+ {
519
+ expect(dividerCSS()).toMatch(/sk-divider:where\(:not\(\[orientation\]\)\)/);
520
+ });
521
+ });
522
+ });
523
+
524
+ //------------------------------------------------------------------------------------------------------------------
525
+
526
+ describe('page', () =>
527
+ {
528
+ let css : string;
529
+
530
+ function pageCSS() : string
531
+ {
532
+ if(!css) { css = compileComponent('page'); }
533
+ return css;
534
+ }
535
+
536
+ //--------------------------------------------------------------------------------------------------------------
537
+ // Base dual-selector (custom-element tag)
538
+ //--------------------------------------------------------------------------------------------------------------
539
+
540
+ it('base rule includes the sk-page custom-element tag', () =>
541
+ {
542
+ expect(pageCSS()).toMatch(/\.sk-page,\s*sk-page\s*\{/);
543
+ });
544
+
545
+ //--------------------------------------------------------------------------------------------------------------
546
+ // Boolean modifiers
547
+ //--------------------------------------------------------------------------------------------------------------
548
+
549
+ describe('boolean modifiers', () =>
550
+ {
551
+ it('emits .sk-page.sk-flush and sk-page[flush]', () =>
552
+ {
553
+ expect(pageCSS()).toMatch(/\.sk-page\.sk-flush,\s*sk-page\[flush\]/);
554
+ });
555
+
556
+ it('emits .sk-page.sk-fixed-header and sk-page[fixed-header]', () =>
557
+ {
558
+ // fixed-header only contains nested child rules (.sk-page-header), so Sass emits
559
+ // .sk-page.sk-fixed-header .sk-page-header, sk-page[fixed-header] .sk-page-header.
560
+ const opts = { descendant: '\\.sk-page-header' };
561
+ expect(pageCSS()).toMatch(new RegExp(
562
+ dualSelectorRe('page', 'fixed-header', 'fixed-header', undefined, opts)
563
+ ));
564
+ });
565
+
566
+ it('emits .sk-page.sk-fixed-footer and sk-page[fixed-footer]', () =>
567
+ {
568
+ // fixed-footer only contains nested child rules (.sk-page-footer), so Sass emits
569
+ // .sk-page.sk-fixed-footer .sk-page-footer, sk-page[fixed-footer] .sk-page-footer.
570
+ const opts = { descendant: '\\.sk-page-footer' };
571
+ expect(pageCSS()).toMatch(new RegExp(
572
+ dualSelectorRe('page', 'fixed-footer', 'fixed-footer', undefined, opts)
573
+ ));
574
+ });
575
+ });
576
+
577
+ //--------------------------------------------------------------------------------------------------------------
578
+ // Custom-element attribute-absence defaults
579
+ //--------------------------------------------------------------------------------------------------------------
580
+
581
+ describe('custom-element defaults (defaults-when-no-attr)', () =>
582
+ {
583
+ it('emits default-flush rule targeting bare <sk-page> without [flush]', () =>
584
+ {
585
+ expect(pageCSS()).toMatch(/sk-page:where\(:not\(\[flush\]\)\)/);
586
+ });
587
+ });
588
+ });
589
+
590
+ //------------------------------------------------------------------------------------------------------------------
591
+
592
+ describe('group', () =>
593
+ {
594
+ let css : string;
595
+
596
+ function groupCSS() : string
597
+ {
598
+ if(!css) { css = compileComponent('group'); }
599
+ return css;
600
+ }
601
+
602
+ //--------------------------------------------------------------------------------------------------------------
603
+ // Base dual-selector (custom-element tag)
604
+ //--------------------------------------------------------------------------------------------------------------
605
+
606
+ it('base rule includes the sk-group custom-element tag', () =>
607
+ {
608
+ expect(groupCSS()).toMatch(/\.sk-group,\s*sk-group\s*\{/);
609
+ });
610
+
611
+ //--------------------------------------------------------------------------------------------------------------
612
+ // Orientation: single-choice-modifier (horizontal | vertical)
613
+ //--------------------------------------------------------------------------------------------------------------
614
+
615
+ describe('orientation modifiers', () =>
616
+ {
617
+ it('emits .sk-group.sk-orientation-horizontal and sk-group[orientation="horizontal"]', () =>
618
+ {
619
+ expect(groupCSS()).toMatch(
620
+ new RegExp(dualSelectorRe('group', 'orientation-horizontal', 'orientation', 'horizontal'))
621
+ );
622
+ });
623
+
624
+ it('emits .sk-group.sk-orientation-vertical and sk-group[orientation="vertical"]', () =>
625
+ {
626
+ expect(groupCSS()).toMatch(
627
+ new RegExp(dualSelectorRe('group', 'orientation-vertical', 'orientation', 'vertical'))
628
+ );
629
+ });
630
+ });
631
+
632
+ //--------------------------------------------------------------------------------------------------------------
633
+ // Defaults-when-absent
634
+ //--------------------------------------------------------------------------------------------------------------
635
+
636
+ describe('defaults-when-absent', () =>
637
+ {
638
+ it('applies default horizontal orientation when no sk-orientation-* class is present', () =>
639
+ {
640
+ expect(groupCSS()).toMatch(
641
+ new RegExp(`\\.sk-group:where\\(:not\\(\\[class\\*=${ attrValueRe('sk-orientation-') }\\]\\)\\)`)
642
+ );
643
+ });
644
+ });
645
+
646
+ //--------------------------------------------------------------------------------------------------------------
647
+ // Custom-element attribute-absence defaults
648
+ //--------------------------------------------------------------------------------------------------------------
649
+
650
+ describe('custom-element defaults (defaults-when-no-attr)', () =>
651
+ {
652
+ it('emits default-orientation rule targeting bare <sk-group> without [orientation]', () =>
653
+ {
654
+ expect(groupCSS()).toMatch(/sk-group:where\(:not\(\[orientation\]\)\)/);
655
+ });
656
+ });
657
+ });
658
+
659
+ //------------------------------------------------------------------------------------------------------------------
660
+
661
+ describe('skeleton', () =>
662
+ {
663
+ let css : string;
664
+
665
+ function skeletonCSS() : string
666
+ {
667
+ if(!css) { css = compileComponent('skeleton'); }
668
+ return css;
669
+ }
670
+
671
+ //--------------------------------------------------------------------------------------------------------------
672
+ // Base dual-selector (custom-element tag)
673
+ //--------------------------------------------------------------------------------------------------------------
674
+
675
+ it('base rule includes the sk-skeleton custom-element tag', () =>
676
+ {
677
+ expect(skeletonCSS()).toMatch(/\.sk-skeleton,\s*sk-skeleton\s*\{/);
678
+ });
679
+
680
+ //--------------------------------------------------------------------------------------------------------------
681
+ // Shape: single-choice-modifier (text | circular | square | rectangular)
682
+ //--------------------------------------------------------------------------------------------------------------
683
+
684
+ describe('shape modifiers', () =>
685
+ {
686
+ const shapes = [ 'text', 'circular', 'square', 'rectangular' ];
687
+
688
+ for(const shape of shapes)
689
+ {
690
+ it(`emits .sk-skeleton.sk-shape-${ shape } and sk-skeleton[shape="${ shape }"]`, () =>
691
+ {
692
+ expect(skeletonCSS()).toMatch(
693
+ new RegExp(
694
+ `\\.sk-skeleton\\.sk-shape-${ shape },\\s*sk-skeleton\\[shape=${ attrValueRe(shape) }\\]`
695
+ )
696
+ );
697
+ });
698
+ }
699
+ });
700
+
701
+ //--------------------------------------------------------------------------------------------------------------
702
+ // Animation boolean modifiers
703
+ //--------------------------------------------------------------------------------------------------------------
704
+
705
+ describe('animation modifiers', () =>
706
+ {
707
+ it('emits .sk-skeleton.sk-shimmer and sk-skeleton[shimmer]', () =>
708
+ {
709
+ // shimmer only applies to ::after, so Sass emits the ::after-suffixed selector pair.
710
+ expect(skeletonCSS()).toMatch(/\.sk-skeleton\.sk-shimmer::after,\s*sk-skeleton\[shimmer\]::after/);
711
+ });
712
+
713
+ it('emits .sk-skeleton.sk-pulse and sk-skeleton[pulse]', () =>
714
+ {
715
+ expect(skeletonCSS()).toMatch(/\.sk-skeleton\.sk-pulse,\s*sk-skeleton\[pulse\]/);
716
+ });
717
+ });
718
+
719
+ //--------------------------------------------------------------------------------------------------------------
720
+ // Defaults-when-absent
721
+ //--------------------------------------------------------------------------------------------------------------
722
+
723
+ describe('defaults-when-absent', () =>
724
+ {
725
+ it('applies default rectangular shape when no sk-shape-* class is present', () =>
726
+ {
727
+ expect(skeletonCSS()).toMatch(
728
+ new RegExp(`\\.sk-skeleton:where\\(:not\\(\\[class\\*=${ attrValueRe('sk-shape-') }\\]\\)\\)`)
729
+ );
730
+ });
731
+ });
732
+
733
+ //--------------------------------------------------------------------------------------------------------------
734
+ // Custom-element attribute-absence defaults
735
+ //--------------------------------------------------------------------------------------------------------------
736
+
737
+ describe('custom-element defaults (defaults-when-no-attr)', () =>
738
+ {
739
+ it('emits default-shape rule targeting bare <sk-skeleton> without [shape]', () =>
740
+ {
741
+ expect(skeletonCSS()).toMatch(/sk-skeleton:where\(:not\(\[shape\]\)\)/);
742
+ });
743
+ });
744
+ });
745
+
746
+ //------------------------------------------------------------------------------------------------------------------
747
+
748
+ describe('progress', () =>
749
+ {
750
+ let css : string;
751
+
752
+ function progressCSS() : string
753
+ {
754
+ if(!css) { css = compileComponent('progress'); }
755
+ return css;
756
+ }
757
+
758
+ //--------------------------------------------------------------------------------------------------------------
759
+ // Base dual-selector (custom-element tag)
760
+ //--------------------------------------------------------------------------------------------------------------
761
+
762
+ it('base rule includes the sk-progress custom-element tag', () =>
763
+ {
764
+ expect(progressCSS()).toMatch(/\.sk-progress,\s*sk-progress\s*\{/);
765
+ });
766
+
767
+ //--------------------------------------------------------------------------------------------------------------
768
+ // Kind variants (full $kinds list)
769
+ //--------------------------------------------------------------------------------------------------------------
770
+
771
+ describe('kind variants', () =>
772
+ {
773
+ for(const kind of KINDS)
774
+ {
775
+ it(`emits .sk-progress.sk-${ kind } and sk-progress[kind="${ kind }"]`, () =>
776
+ {
777
+ expect(progressCSS()).toMatch(
778
+ new RegExp(
779
+ dualSelectorRe('progress', kind, 'kind', kind)
780
+ )
781
+ );
782
+ });
783
+ }
784
+ });
785
+
786
+ //--------------------------------------------------------------------------------------------------------------
787
+ // Size variants (xs, sm, md, lg, xl)
788
+ //--------------------------------------------------------------------------------------------------------------
789
+
790
+ describe('size variants', () =>
791
+ {
792
+ for(const size of SIZES)
793
+ {
794
+ it(`emits .sk-progress.sk-${ size } and sk-progress[size="${ size }"]`, () =>
795
+ {
796
+ const out = progressCSS();
797
+ expect(out).toMatch(new RegExp(`\\.sk-progress\\.sk-${ size }[,\\s{]`));
798
+ expect(out).toMatch(new RegExp(`sk-progress\\[size=${ attrValueRe(size) }\\]`));
799
+ });
800
+ }
801
+ });
802
+
803
+ //--------------------------------------------------------------------------------------------------------------
804
+ // Indeterminate boolean modifier
805
+ //--------------------------------------------------------------------------------------------------------------
806
+
807
+ describe('boolean modifiers', () =>
808
+ {
809
+ it('emits .sk-progress.sk-indeterminate and sk-progress[indeterminate]', () =>
810
+ {
811
+ // indeterminate only targets .sk-progress-indicator, so Sass emits the child selector pair.
812
+ expect(progressCSS()).toMatch(new RegExp(
813
+ dualSelectorRe('progress', 'indeterminate', 'indeterminate', undefined, {
814
+ descendant: '\\.sk-progress-indicator',
815
+ })
816
+ ));
817
+ });
818
+ });
819
+
820
+ //--------------------------------------------------------------------------------------------------------------
821
+ // Defaults-when-absent
822
+ //--------------------------------------------------------------------------------------------------------------
823
+
824
+ describe('defaults-when-absent', () =>
825
+ {
826
+ it('applies default md size when no sk-size-* class is present', () =>
827
+ {
828
+ expect(progressCSS()).toMatch(
829
+ new RegExp(`\\.sk-progress:where\\(:not\\(\\[class\\*=${ attrValueRe('sk-size-') }\\]\\)\\)`)
830
+ );
831
+ });
832
+
833
+ it('applies default primary kind via explicit :not() chain', () =>
834
+ {
835
+ const out = progressCSS();
836
+ expect(out).toMatch(/\.sk-progress:where\(:not\(\.sk-neutral\)/);
837
+ expect(out).toMatch(/:not\(\.sk-primary\)/);
838
+ expect(out).toMatch(/:not\(\.sk-red\)\)/);
839
+ });
840
+ });
841
+
842
+ //--------------------------------------------------------------------------------------------------------------
843
+ // Custom-element attribute-absence defaults
844
+ //--------------------------------------------------------------------------------------------------------------
845
+
846
+ describe('custom-element defaults (defaults-when-no-attr)', () =>
847
+ {
848
+ it('emits default-kind rule targeting bare <sk-progress> without [kind]', () =>
849
+ {
850
+ expect(progressCSS()).toMatch(/sk-progress:where\(:not\(\[kind\]\)\)/);
851
+ });
852
+
853
+ it('emits default-size rule targeting bare <sk-progress> without [size]', () =>
854
+ {
855
+ expect(progressCSS()).toMatch(/sk-progress:where\(:not\(\[size\]\)\)/);
856
+ });
857
+ });
858
+ });
859
+
860
+ //------------------------------------------------------------------------------------------------------------------
861
+
862
+ describe('spinner', () =>
863
+ {
864
+ let css : string;
865
+
866
+ function spinnerCSS() : string
867
+ {
868
+ if(!css) { css = compileComponent('spinner'); }
869
+ return css;
870
+ }
871
+
872
+ //--------------------------------------------------------------------------------------------------------------
873
+ // Base dual-selector (custom-element tag)
874
+ //--------------------------------------------------------------------------------------------------------------
875
+
876
+ it('base rule includes the sk-spinner custom-element tag', () =>
877
+ {
878
+ expect(spinnerCSS()).toMatch(/\.sk-spinner,\s*sk-spinner\s*\{/);
879
+ });
880
+
881
+ //--------------------------------------------------------------------------------------------------------------
882
+ // Kind variants (full $kinds list)
883
+ //--------------------------------------------------------------------------------------------------------------
884
+
885
+ describe('kind variants', () =>
886
+ {
887
+ for(const kind of KINDS)
888
+ {
889
+ it(`emits .sk-spinner.sk-${ kind } and sk-spinner[kind="${ kind }"]`, () =>
890
+ {
891
+ expect(spinnerCSS()).toMatch(
892
+ new RegExp(
893
+ dualSelectorRe('spinner', kind, 'kind', kind)
894
+ )
895
+ );
896
+ });
897
+ }
898
+ });
899
+
900
+ //--------------------------------------------------------------------------------------------------------------
901
+ // Size variants (xs, sm, md, lg, xl)
902
+ //--------------------------------------------------------------------------------------------------------------
903
+
904
+ describe('size variants', () =>
905
+ {
906
+ for(const size of SIZES)
907
+ {
908
+ it(`emits .sk-spinner.sk-${ size } and sk-spinner[size="${ size }"]`, () =>
909
+ {
910
+ const out = spinnerCSS();
911
+ expect(out).toMatch(new RegExp(`\\.sk-spinner\\.sk-${ size }[,\\s{]`));
912
+ expect(out).toMatch(new RegExp(`sk-spinner\\[size=${ attrValueRe(size) }\\]`));
913
+ });
914
+ }
915
+ });
916
+
917
+ //--------------------------------------------------------------------------------------------------------------
918
+ // Defaults-when-absent
919
+ //--------------------------------------------------------------------------------------------------------------
920
+
921
+ describe('defaults-when-absent', () =>
922
+ {
923
+ it('applies default md size when no sk-size-* class is present', () =>
924
+ {
925
+ expect(spinnerCSS()).toMatch(
926
+ new RegExp(`\\.sk-spinner:where\\(:not\\(\\[class\\*=${ attrValueRe('sk-size-') }\\]\\)\\)`)
927
+ );
928
+ });
929
+
930
+ it('applies default primary kind via explicit :not() chain', () =>
931
+ {
932
+ const out = spinnerCSS();
933
+ expect(out).toMatch(/\.sk-spinner:where\(:not\(\.sk-neutral\)/);
934
+ expect(out).toMatch(/:not\(\.sk-primary\)/);
935
+ expect(out).toMatch(/:not\(\.sk-red\)\)/);
936
+ });
937
+ });
938
+
939
+ //--------------------------------------------------------------------------------------------------------------
940
+ // Custom-element attribute-absence defaults
941
+ //--------------------------------------------------------------------------------------------------------------
942
+
943
+ describe('custom-element defaults (defaults-when-no-attr)', () =>
944
+ {
945
+ it('emits default-kind rule targeting bare <sk-spinner> without [kind]', () =>
946
+ {
947
+ expect(spinnerCSS()).toMatch(/sk-spinner:where\(:not\(\[kind\]\)\)/);
948
+ });
949
+
950
+ it('emits default-size rule targeting bare <sk-spinner> without [size]', () =>
951
+ {
952
+ expect(spinnerCSS()).toMatch(/sk-spinner:where\(:not\(\[size\]\)\)/);
953
+ });
954
+ });
955
+ });
956
+
957
+ //------------------------------------------------------------------------------------------------------------------
958
+
959
+ describe('navbar', () =>
960
+ {
961
+ let css : string;
962
+
963
+ function navbarCSS() : string
964
+ {
965
+ if(!css) { css = compileComponent('navbar'); }
966
+ return css;
967
+ }
968
+
969
+ //--------------------------------------------------------------------------------------------------------------
970
+ // Base dual-selector (custom-element tag)
971
+ //--------------------------------------------------------------------------------------------------------------
972
+
973
+ it('base rule includes the sk-navbar custom-element tag', () =>
974
+ {
975
+ expect(navbarCSS()).toMatch(/\.sk-navbar,\s*sk-navbar\s*\{/);
976
+ });
977
+
978
+ //--------------------------------------------------------------------------------------------------------------
979
+ // Kind variants (full $kinds list)
980
+ //--------------------------------------------------------------------------------------------------------------
981
+
982
+ describe('kind variants', () =>
983
+ {
984
+ for(const kind of KINDS)
985
+ {
986
+ it(`emits .sk-navbar.sk-${ kind } and sk-navbar[kind="${ kind }"]`, () =>
987
+ {
988
+ expect(navbarCSS()).toMatch(
989
+ new RegExp(
990
+ dualSelectorRe('navbar', kind, 'kind', kind)
991
+ )
992
+ );
993
+ });
994
+ }
995
+ });
996
+
997
+ //--------------------------------------------------------------------------------------------------------------
998
+ // Boolean modifiers
999
+ //--------------------------------------------------------------------------------------------------------------
1000
+
1001
+ describe('boolean modifiers', () =>
1002
+ {
1003
+ it('emits .sk-navbar.sk-sticky and sk-navbar[sticky]', () =>
1004
+ {
1005
+ expect(navbarCSS()).toMatch(/\.sk-navbar\.sk-sticky,\s*sk-navbar\[sticky\]/);
1006
+ });
1007
+ });
1008
+
1009
+ //--------------------------------------------------------------------------------------------------------------
1010
+ // Defaults-when-absent
1011
+ //--------------------------------------------------------------------------------------------------------------
1012
+
1013
+ describe('defaults-when-absent', () =>
1014
+ {
1015
+ it('applies default neutral kind via explicit :not() chain when no kind class present', () =>
1016
+ {
1017
+ const out = navbarCSS();
1018
+ expect(out).toMatch(/\.sk-navbar:where\(:not\(\.sk-neutral\)/);
1019
+ expect(out).toMatch(/:not\(\.sk-primary\)/);
1020
+ expect(out).toMatch(/:not\(\.sk-red\)\)/);
1021
+ });
1022
+ });
1023
+
1024
+ //--------------------------------------------------------------------------------------------------------------
1025
+ // Custom-element attribute-absence defaults
1026
+ //--------------------------------------------------------------------------------------------------------------
1027
+
1028
+ describe('custom-element defaults (defaults-when-no-attr)', () =>
1029
+ {
1030
+ it('emits default-kind rule targeting bare <sk-navbar> without [kind]', () =>
1031
+ {
1032
+ expect(navbarCSS()).toMatch(/sk-navbar:where\(:not\(\[kind\]\)\)/);
1033
+ });
1034
+ });
1035
+ });
1036
+
1037
+ //------------------------------------------------------------------------------------------------------------------
1038
+
1039
+ describe('toolbar', () =>
1040
+ {
1041
+ let css : string;
1042
+
1043
+ function toolbarCSS() : string
1044
+ {
1045
+ if(!css) { css = compileComponent('toolbar'); }
1046
+ return css;
1047
+ }
1048
+
1049
+ //--------------------------------------------------------------------------------------------------------------
1050
+ // Base dual-selector (custom-element tag)
1051
+ //--------------------------------------------------------------------------------------------------------------
1052
+
1053
+ it('base rule includes the sk-toolbar custom-element tag', () =>
1054
+ {
1055
+ expect(toolbarCSS()).toMatch(/\.sk-toolbar,\s*sk-toolbar\s*\{/);
1056
+ });
1057
+
1058
+ //--------------------------------------------------------------------------------------------------------------
1059
+ // Kind variants (full $kinds list)
1060
+ //--------------------------------------------------------------------------------------------------------------
1061
+
1062
+ describe('kind variants', () =>
1063
+ {
1064
+ for(const kind of KINDS)
1065
+ {
1066
+ it(`emits .sk-toolbar.sk-${ kind } and sk-toolbar[kind="${ kind }"]`, () =>
1067
+ {
1068
+ expect(toolbarCSS()).toMatch(
1069
+ new RegExp(
1070
+ dualSelectorRe('toolbar', kind, 'kind', kind)
1071
+ )
1072
+ );
1073
+ });
1074
+ }
1075
+ });
1076
+
1077
+ //--------------------------------------------------------------------------------------------------------------
1078
+ // Orientation modifiers (single-choice)
1079
+ //--------------------------------------------------------------------------------------------------------------
1080
+
1081
+ describe('orientation modifiers', () =>
1082
+ {
1083
+ it('emits .sk-toolbar.sk-orientation-vertical and sk-toolbar[orientation="vertical"]', () =>
1084
+ {
1085
+ expect(toolbarCSS()).toMatch(
1086
+ new RegExp(dualSelectorRe('toolbar', 'orientation-vertical', 'orientation', 'vertical'))
1087
+ );
1088
+ });
1089
+
1090
+ it('emits .sk-toolbar.sk-orientation-horizontal and sk-toolbar[orientation="horizontal"]', () =>
1091
+ {
1092
+ expect(toolbarCSS()).toMatch(
1093
+ new RegExp(dualSelectorRe('toolbar', 'orientation-horizontal', 'orientation', 'horizontal'))
1094
+ );
1095
+ });
1096
+ });
1097
+
1098
+ //--------------------------------------------------------------------------------------------------------------
1099
+ // Defaults-when-absent
1100
+ //--------------------------------------------------------------------------------------------------------------
1101
+
1102
+ describe('defaults-when-absent', () =>
1103
+ {
1104
+ it('applies default neutral kind via explicit :not() chain when no kind class present', () =>
1105
+ {
1106
+ const out = toolbarCSS();
1107
+ expect(out).toMatch(/\.sk-toolbar:where\(:not\(\.sk-neutral\)/);
1108
+ expect(out).toMatch(/:not\(\.sk-primary\)/);
1109
+ expect(out).toMatch(/:not\(\.sk-red\)\)/);
1110
+ });
1111
+
1112
+ it('applies default horizontal orientation when no sk-orientation-* class is present', () =>
1113
+ {
1114
+ expect(toolbarCSS()).toMatch(
1115
+ new RegExp(`\\.sk-toolbar:where\\(:not\\(\\[class\\*=${ attrValueRe('sk-orientation-') }\\]\\)\\)`)
1116
+ );
1117
+ });
1118
+ });
1119
+
1120
+ //--------------------------------------------------------------------------------------------------------------
1121
+ // Custom-element attribute-absence defaults
1122
+ //--------------------------------------------------------------------------------------------------------------
1123
+
1124
+ describe('custom-element defaults (defaults-when-no-attr)', () =>
1125
+ {
1126
+ it('emits default-kind rule targeting bare <sk-toolbar> without [kind]', () =>
1127
+ {
1128
+ expect(toolbarCSS()).toMatch(/sk-toolbar:where\(:not\(\[kind\]\)\)/);
1129
+ });
1130
+
1131
+ it('emits default-orientation rule targeting bare <sk-toolbar> without [orientation]', () =>
1132
+ {
1133
+ expect(toolbarCSS()).toMatch(/sk-toolbar:where\(:not\(\[orientation\]\)\)/);
1134
+ });
1135
+ });
1136
+ });
1137
+
1138
+ //------------------------------------------------------------------------------------------------------------------
1139
+
1140
+ describe('sidebar', () =>
1141
+ {
1142
+ let css : string;
1143
+
1144
+ function sidebarCSS() : string
1145
+ {
1146
+ if(!css) { css = compileComponent('sidebar'); }
1147
+ return css;
1148
+ }
1149
+
1150
+ //--------------------------------------------------------------------------------------------------------------
1151
+ // Base dual-selector (custom-element tag)
1152
+ //--------------------------------------------------------------------------------------------------------------
1153
+
1154
+ it('base rule includes the sk-sidebar custom-element tag', () =>
1155
+ {
1156
+ expect(sidebarCSS()).toMatch(/\.sk-sidebar,\s*sk-sidebar\s*\{/);
1157
+ });
1158
+
1159
+ //--------------------------------------------------------------------------------------------------------------
1160
+ // Kind variants (semantic kinds only)
1161
+ //--------------------------------------------------------------------------------------------------------------
1162
+
1163
+ describe('kind variants', () =>
1164
+ {
1165
+ for(const kind of SEMANTIC_KINDS)
1166
+ {
1167
+ it(`emits .sk-sidebar.sk-${ kind } and sk-sidebar[kind="${ kind }"]`, () =>
1168
+ {
1169
+ expect(sidebarCSS()).toMatch(
1170
+ new RegExp(
1171
+ dualSelectorRe('sidebar', kind, 'kind', kind)
1172
+ )
1173
+ );
1174
+ });
1175
+ }
1176
+ });
1177
+
1178
+ //--------------------------------------------------------------------------------------------------------------
1179
+ // Boolean modifiers
1180
+ //--------------------------------------------------------------------------------------------------------------
1181
+
1182
+ describe('boolean modifiers', () =>
1183
+ {
1184
+ it('emits .sk-sidebar.sk-dense and sk-sidebar[dense]', () =>
1185
+ {
1186
+ expect(sidebarCSS()).toMatch(/\.sk-sidebar\.sk-dense,\s*sk-sidebar\[dense\]/);
1187
+ });
1188
+ });
1189
+
1190
+ //--------------------------------------------------------------------------------------------------------------
1191
+ // Defaults-when-absent
1192
+ //--------------------------------------------------------------------------------------------------------------
1193
+
1194
+ describe('defaults-when-absent', () =>
1195
+ {
1196
+ it('applies default neutral kind via explicit :not() chain when no kind class present', () =>
1197
+ {
1198
+ const out = sidebarCSS();
1199
+ expect(out).toMatch(/\.sk-sidebar:where\(:not\(\.sk-neutral\)/);
1200
+ expect(out).toMatch(/:not\(\.sk-primary\)/);
1201
+ expect(out).toMatch(/:not\(\.sk-danger\)/);
1202
+ expect(out).toMatch(/:not\(\.sk-neon-pink\)/);
1203
+ expect(out).toMatch(/:not\(\.sk-red\)\)/);
1204
+ });
1205
+ });
1206
+
1207
+ //--------------------------------------------------------------------------------------------------------------
1208
+ // Custom-element attribute-absence defaults
1209
+ //--------------------------------------------------------------------------------------------------------------
1210
+
1211
+ describe('custom-element defaults (defaults-when-no-attr)', () =>
1212
+ {
1213
+ it('emits default-kind rule targeting bare <sk-sidebar> without [kind]', () =>
1214
+ {
1215
+ expect(sidebarCSS()).toMatch(/sk-sidebar:where\(:not\(\[kind\]\)\)/);
1216
+ });
1217
+ });
1218
+ });
1219
+
1220
+ //------------------------------------------------------------------------------------------------------------------
1221
+
1222
+ describe('breadcrumbs', () =>
1223
+ {
1224
+ let css : string;
1225
+
1226
+ function breadcrumbsCSS() : string
1227
+ {
1228
+ if(!css) { css = compileComponent('breadcrumbs'); }
1229
+ return css;
1230
+ }
1231
+
1232
+ //--------------------------------------------------------------------------------------------------------------
1233
+ // Base dual-selector (custom-element tag)
1234
+ //--------------------------------------------------------------------------------------------------------------
1235
+
1236
+ it('base rule includes the sk-breadcrumbs custom-element tag', () =>
1237
+ {
1238
+ expect(breadcrumbsCSS()).toMatch(/\.sk-breadcrumbs,\s*sk-breadcrumbs\s*\{/);
1239
+ });
1240
+
1241
+ //--------------------------------------------------------------------------------------------------------------
1242
+ // Kind variants (semantic kinds only)
1243
+ //--------------------------------------------------------------------------------------------------------------
1244
+
1245
+ describe('kind variants', () =>
1246
+ {
1247
+ for(const kind of SEMANTIC_KINDS)
1248
+ {
1249
+ it(`emits .sk-breadcrumbs.sk-${ kind } and sk-breadcrumbs[kind="${ kind }"]`, () =>
1250
+ {
1251
+ expect(breadcrumbsCSS()).toMatch(
1252
+ new RegExp(
1253
+ dualSelectorRe('breadcrumbs', kind, 'kind', kind)
1254
+ )
1255
+ );
1256
+ });
1257
+ }
1258
+ });
1259
+
1260
+ //--------------------------------------------------------------------------------------------------------------
1261
+ // Defaults-when-absent
1262
+ //--------------------------------------------------------------------------------------------------------------
1263
+
1264
+ describe('defaults-when-absent', () =>
1265
+ {
1266
+ it('applies default neutral kind via explicit :not() chain when no kind class present', () =>
1267
+ {
1268
+ const out = breadcrumbsCSS();
1269
+ expect(out).toMatch(/\.sk-breadcrumbs:where\(:not\(\.sk-neutral\)/);
1270
+ expect(out).toMatch(/:not\(\.sk-primary\)/);
1271
+ expect(out).toMatch(/:not\(\.sk-danger\)\)/);
1272
+ });
1273
+ });
1274
+
1275
+ //--------------------------------------------------------------------------------------------------------------
1276
+ // Custom-element attribute-absence defaults
1277
+ //--------------------------------------------------------------------------------------------------------------
1278
+
1279
+ describe('custom-element defaults (defaults-when-no-attr)', () =>
1280
+ {
1281
+ it('emits default-kind rule targeting bare <sk-breadcrumbs> without [kind]', () =>
1282
+ {
1283
+ expect(breadcrumbsCSS()).toMatch(/sk-breadcrumbs:where\(:not\(\[kind\]\)\)/);
1284
+ });
1285
+ });
1286
+ });
1287
+
1288
+ //------------------------------------------------------------------------------------------------------------------
1289
+
1290
+ describe('pagination', () =>
1291
+ {
1292
+ let css : string;
1293
+
1294
+ function paginationCSS() : string
1295
+ {
1296
+ if(!css) { css = compileComponent('pagination'); }
1297
+ return css;
1298
+ }
1299
+
1300
+ //--------------------------------------------------------------------------------------------------------------
1301
+ // Base dual-selector (custom-element tag)
1302
+ //--------------------------------------------------------------------------------------------------------------
1303
+
1304
+ it('base rule includes the sk-pagination custom-element tag', () =>
1305
+ {
1306
+ expect(paginationCSS()).toMatch(/\.sk-pagination,\s*sk-pagination\s*\{/);
1307
+ });
1308
+
1309
+ //--------------------------------------------------------------------------------------------------------------
1310
+ // Kind variants on root (semantic kinds only)
1311
+ //--------------------------------------------------------------------------------------------------------------
1312
+
1313
+ describe('kind variants', () =>
1314
+ {
1315
+ for(const kind of SEMANTIC_KINDS)
1316
+ {
1317
+ it(`emits .sk-pagination.sk-${ kind } and sk-pagination[kind="${ kind }"]`, () =>
1318
+ {
1319
+ expect(paginationCSS()).toMatch(
1320
+ new RegExp(
1321
+ dualSelectorRe('pagination', kind, 'kind', kind)
1322
+ )
1323
+ );
1324
+ });
1325
+ }
1326
+ });
1327
+
1328
+ //--------------------------------------------------------------------------------------------------------------
1329
+ // Size variants on .sk-pagination-item
1330
+ //--------------------------------------------------------------------------------------------------------------
1331
+
1332
+ describe('size variants (pagination-item)', () =>
1333
+ {
1334
+ for(const size of SIZES)
1335
+ {
1336
+ it(`emits .sk-pagination-item.sk-${ size } and sk-pagination-item[size="${ size }"]`, () =>
1337
+ {
1338
+ const out = paginationCSS();
1339
+ expect(out).toMatch(new RegExp(`\\.sk-pagination-item\\.sk-${ size }[,\\s{]`));
1340
+ expect(out).toMatch(new RegExp(`sk-pagination-item\\[size=${ attrValueRe(size) }\\]`));
1341
+ });
1342
+ }
1343
+ });
1344
+
1345
+ //--------------------------------------------------------------------------------------------------------------
1346
+ // Variant variants on .sk-pagination-item (solid, outline, subtle, ghost)
1347
+ //--------------------------------------------------------------------------------------------------------------
1348
+
1349
+ describe('variant modifiers (pagination-item)', () =>
1350
+ {
1351
+ const variants = [ 'solid', 'outline', 'subtle', 'ghost' ];
1352
+
1353
+ for(const variant of variants)
1354
+ {
1355
+ it(`emits .sk-pagination-item.sk-${ variant } and sk-pagination-item[variant="${ variant }"]`, () =>
1356
+ {
1357
+ expect(paginationCSS()).toMatch(
1358
+ new RegExp(
1359
+ dualSelectorRe('pagination-item', variant, 'variant', variant)
1360
+ )
1361
+ );
1362
+ });
1363
+ }
1364
+ });
1365
+
1366
+ //--------------------------------------------------------------------------------------------------------------
1367
+ // Defaults-when-absent
1368
+ //--------------------------------------------------------------------------------------------------------------
1369
+
1370
+ describe('defaults-when-absent', () =>
1371
+ {
1372
+ it('applies default neutral kind via explicit :not() chain when no kind class present', () =>
1373
+ {
1374
+ const out = paginationCSS();
1375
+ expect(out).toMatch(/\.sk-pagination:where\(:not\(\.sk-neutral\)/);
1376
+ expect(out).toMatch(/:not\(\.sk-primary\)/);
1377
+ expect(out).toMatch(/:not\(\.sk-danger\)\)/);
1378
+ });
1379
+
1380
+ it('applies default md size on pagination-item when no sk-size-* class is present', () =>
1381
+ {
1382
+ expect(paginationCSS()).toMatch(
1383
+ new RegExp(`\\.sk-pagination-item:where\\(:not\\(\\[class\\*=${ attrValueRe('sk-size-') }\\]\\)\\)`)
1384
+ );
1385
+ });
1386
+ });
1387
+
1388
+ //--------------------------------------------------------------------------------------------------------------
1389
+ // Custom-element attribute-absence defaults
1390
+ //--------------------------------------------------------------------------------------------------------------
1391
+
1392
+ describe('custom-element defaults (defaults-when-no-attr)', () =>
1393
+ {
1394
+ it('emits default-kind rule targeting bare <sk-pagination> without [kind]', () =>
1395
+ {
1396
+ expect(paginationCSS()).toMatch(/sk-pagination:where\(:not\(\[kind\]\)\)/);
1397
+ });
1398
+
1399
+ it('emits default-size rule targeting bare <sk-pagination-item> without [size]', () =>
1400
+ {
1401
+ expect(paginationCSS()).toMatch(/sk-pagination-item:where\(:not\(\[size\]\)\)/);
1402
+ });
1403
+ });
1404
+ });
1405
+
1406
+ //------------------------------------------------------------------------------------------------------------------
1407
+
1408
+ describe('tag', () =>
1409
+ {
1410
+ let css : string;
1411
+
1412
+ function tagCSS() : string
1413
+ {
1414
+ if(!css) { css = compileComponent('tag'); }
1415
+ return css;
1416
+ }
1417
+
1418
+ //--------------------------------------------------------------------------------------------------------------
1419
+ // Base dual-selector (custom-element tag)
1420
+ //--------------------------------------------------------------------------------------------------------------
1421
+
1422
+ it('base rule includes the sk-tag custom-element tag', () =>
1423
+ {
1424
+ expect(tagCSS()).toMatch(/\.sk-tag,\s*sk-tag\s*\{/);
1425
+ });
1426
+
1427
+ //--------------------------------------------------------------------------------------------------------------
1428
+ // Kind variants (full $kinds list)
1429
+ //--------------------------------------------------------------------------------------------------------------
1430
+
1431
+ describe('kind variants', () =>
1432
+ {
1433
+ for(const kind of KINDS)
1434
+ {
1435
+ it(`emits .sk-tag.sk-${ kind } and sk-tag[kind="${ kind }"]`, () =>
1436
+ {
1437
+ expect(tagCSS()).toMatch(
1438
+ new RegExp(
1439
+ dualSelectorRe('tag', kind, 'kind', kind)
1440
+ )
1441
+ );
1442
+ });
1443
+ }
1444
+ });
1445
+
1446
+ //--------------------------------------------------------------------------------------------------------------
1447
+ // Size variants (xs, sm, md, lg, xl)
1448
+ //--------------------------------------------------------------------------------------------------------------
1449
+
1450
+ describe('size variants', () =>
1451
+ {
1452
+ for(const size of SIZES)
1453
+ {
1454
+ it(`emits .sk-tag.sk-${ size } and sk-tag[size="${ size }"]`, () =>
1455
+ {
1456
+ const out = tagCSS();
1457
+ expect(out).toMatch(new RegExp(`\\.sk-tag\\.sk-${ size }[,\\s{]`));
1458
+ expect(out).toMatch(new RegExp(`sk-tag\\[size=${ attrValueRe(size) }\\]`));
1459
+ });
1460
+ }
1461
+ });
1462
+
1463
+ //--------------------------------------------------------------------------------------------------------------
1464
+ // Variant variants (solid, outline, subtle, ghost)
1465
+ //--------------------------------------------------------------------------------------------------------------
1466
+
1467
+ describe('variant modifiers', () =>
1468
+ {
1469
+ const variants = [ 'solid', 'outline', 'subtle', 'ghost' ];
1470
+
1471
+ for(const variant of variants)
1472
+ {
1473
+ it(`emits .sk-tag.sk-${ variant } and sk-tag[variant="${ variant }"]`, () =>
1474
+ {
1475
+ expect(tagCSS()).toMatch(
1476
+ new RegExp(
1477
+ dualSelectorRe('tag', variant, 'variant', variant)
1478
+ )
1479
+ );
1480
+ });
1481
+ }
1482
+ });
1483
+
1484
+ //--------------------------------------------------------------------------------------------------------------
1485
+ // Boolean modifiers
1486
+ //--------------------------------------------------------------------------------------------------------------
1487
+
1488
+ describe('boolean modifiers', () =>
1489
+ {
1490
+ it('emits .sk-tag.sk-removable and sk-tag[removable]', () =>
1491
+ {
1492
+ expect(tagCSS()).toMatch(/\.sk-tag\.sk-removable,\s*sk-tag\[removable\]/);
1493
+ });
1494
+ });
1495
+
1496
+ //--------------------------------------------------------------------------------------------------------------
1497
+ // Defaults-when-absent
1498
+ //--------------------------------------------------------------------------------------------------------------
1499
+
1500
+ describe('defaults-when-absent', () =>
1501
+ {
1502
+ it('applies default neutral kind via :not() chain', () =>
1503
+ {
1504
+ const out = tagCSS();
1505
+ expect(out).toMatch(/\.sk-tag:where\(:not\(\.sk-neutral\)/);
1506
+ expect(out).toMatch(/:not\(\.sk-primary\)/);
1507
+ expect(out).toMatch(/:not\(\.sk-red\)\)/);
1508
+ });
1509
+
1510
+ it('applies default md size when no sk-size-* class is present', () =>
1511
+ {
1512
+ expect(tagCSS()).toMatch(
1513
+ new RegExp(`\\.sk-tag:where\\(:not\\(\\[class\\*=${ attrValueRe('sk-size-') }\\]\\)\\)`)
1514
+ );
1515
+ });
1516
+ });
1517
+
1518
+ //--------------------------------------------------------------------------------------------------------------
1519
+ // Custom-element attribute-absence defaults
1520
+ //--------------------------------------------------------------------------------------------------------------
1521
+
1522
+ describe('custom-element defaults (defaults-when-no-attr)', () =>
1523
+ {
1524
+ it('emits default-kind rule targeting bare <sk-tag> without [kind]', () =>
1525
+ {
1526
+ expect(tagCSS()).toMatch(/sk-tag:where\(:not\(\[kind\]\)\)/);
1527
+ });
1528
+
1529
+ it('emits default-size rule targeting bare <sk-tag> without [size]', () =>
1530
+ {
1531
+ expect(tagCSS()).toMatch(/sk-tag:where\(:not\(\[size\]\)\)/);
1532
+ });
1533
+ });
1534
+ });
1535
+
1536
+ //------------------------------------------------------------------------------------------------------------------
1537
+
1538
+ describe('avatar', () =>
1539
+ {
1540
+ let css : string;
1541
+
1542
+ function avatarCSS() : string
1543
+ {
1544
+ if(!css) { css = compileComponent('avatar'); }
1545
+ return css;
1546
+ }
1547
+
1548
+ //--------------------------------------------------------------------------------------------------------------
1549
+ // Base dual-selector (custom-element tag)
1550
+ //--------------------------------------------------------------------------------------------------------------
1551
+
1552
+ it('base rule includes the sk-avatar custom-element tag', () =>
1553
+ {
1554
+ expect(avatarCSS()).toMatch(/\.sk-avatar,\s*sk-avatar\s*\{/);
1555
+ });
1556
+
1557
+ //--------------------------------------------------------------------------------------------------------------
1558
+ // Kind variants (full $kinds list)
1559
+ //--------------------------------------------------------------------------------------------------------------
1560
+
1561
+ describe('kind variants', () =>
1562
+ {
1563
+ for(const kind of KINDS)
1564
+ {
1565
+ it(`emits .sk-avatar.sk-${ kind } and sk-avatar[kind="${ kind }"]`, () =>
1566
+ {
1567
+ expect(avatarCSS()).toMatch(
1568
+ new RegExp(
1569
+ dualSelectorRe('avatar', kind, 'kind', kind)
1570
+ )
1571
+ );
1572
+ });
1573
+ }
1574
+ });
1575
+
1576
+ //--------------------------------------------------------------------------------------------------------------
1577
+ // Size variants (xs, sm, md, lg, xl)
1578
+ //--------------------------------------------------------------------------------------------------------------
1579
+
1580
+ describe('size variants', () =>
1581
+ {
1582
+ for(const size of SIZES)
1583
+ {
1584
+ it(`emits .sk-avatar.sk-${ size } and sk-avatar[size="${ size }"]`, () =>
1585
+ {
1586
+ const out = avatarCSS();
1587
+ expect(out).toMatch(new RegExp(`\\.sk-avatar\\.sk-${ size }[,\\s{]`));
1588
+ expect(out).toMatch(new RegExp(`sk-avatar\\[size=${ attrValueRe(size) }\\]`));
1589
+ });
1590
+ }
1591
+ });
1592
+
1593
+ //--------------------------------------------------------------------------------------------------------------
1594
+ // Shape modifiers (single-choice)
1595
+ //--------------------------------------------------------------------------------------------------------------
1596
+
1597
+ describe('shape modifiers', () =>
1598
+ {
1599
+ const shapes = [ 'circle', 'square' ];
1600
+
1601
+ for(const shape of shapes)
1602
+ {
1603
+ it(`emits .sk-avatar.sk-shape-${ shape } and sk-avatar[shape="${ shape }"]`, () =>
1604
+ {
1605
+ expect(avatarCSS()).toMatch(
1606
+ new RegExp(
1607
+ `\\.sk-avatar\\.sk-shape-${ shape },\\s*sk-avatar\\[shape=${ attrValueRe(shape) }\\]`
1608
+ )
1609
+ );
1610
+ });
1611
+ }
1612
+ });
1613
+
1614
+ //--------------------------------------------------------------------------------------------------------------
1615
+ // Defaults-when-absent
1616
+ //--------------------------------------------------------------------------------------------------------------
1617
+
1618
+ describe('defaults-when-absent', () =>
1619
+ {
1620
+ it('applies default neutral kind via :not() chain', () =>
1621
+ {
1622
+ const out = avatarCSS();
1623
+ expect(out).toMatch(/\.sk-avatar:where\(:not\(\.sk-neutral\)/);
1624
+ expect(out).toMatch(/:not\(\.sk-primary\)/);
1625
+ expect(out).toMatch(/:not\(\.sk-red\)\)/);
1626
+ });
1627
+
1628
+ it('applies default md size when no sk-size-* class is present', () =>
1629
+ {
1630
+ expect(avatarCSS()).toMatch(
1631
+ new RegExp(`\\.sk-avatar:where\\(:not\\(\\[class\\*=${ attrValueRe('sk-size-') }\\]\\)\\)`)
1632
+ );
1633
+ });
1634
+ });
1635
+
1636
+ //--------------------------------------------------------------------------------------------------------------
1637
+ // Custom-element attribute-absence defaults
1638
+ //--------------------------------------------------------------------------------------------------------------
1639
+
1640
+ describe('custom-element defaults (defaults-when-no-attr)', () =>
1641
+ {
1642
+ it('emits default-kind rule targeting bare <sk-avatar> without [kind]', () =>
1643
+ {
1644
+ expect(avatarCSS()).toMatch(/sk-avatar:where\(:not\(\[kind\]\)\)/);
1645
+ });
1646
+
1647
+ it('emits default-size rule targeting bare <sk-avatar> without [size]', () =>
1648
+ {
1649
+ expect(avatarCSS()).toMatch(/sk-avatar:where\(:not\(\[size\]\)\)/);
1650
+ });
1651
+ });
1652
+ });
1653
+
1654
+ //------------------------------------------------------------------------------------------------------------------
1655
+
1656
+ describe('field', () =>
1657
+ {
1658
+ let css : string;
1659
+
1660
+ function fieldCSS() : string
1661
+ {
1662
+ if(!css) { css = compileComponent('field'); }
1663
+ return css;
1664
+ }
1665
+
1666
+ //--------------------------------------------------------------------------------------------------------------
1667
+ // Base dual-selector (custom-element tag)
1668
+ //--------------------------------------------------------------------------------------------------------------
1669
+
1670
+ it('base rule includes the sk-field custom-element tag', () =>
1671
+ {
1672
+ expect(fieldCSS()).toMatch(/\.sk-field,\s*sk-field\s*\{/);
1673
+ });
1674
+
1675
+ //--------------------------------------------------------------------------------------------------------------
1676
+ // Label position modifiers (single-choice)
1677
+ //--------------------------------------------------------------------------------------------------------------
1678
+
1679
+ describe('label position modifiers', () =>
1680
+ {
1681
+ it('emits .sk-field.sk-label-top and sk-field[label-position="top"]', () =>
1682
+ {
1683
+ expect(fieldCSS()).toMatch(
1684
+ new RegExp(`\\.sk-field\\.sk-label-top,\\s*sk-field\\[label-position=${ attrValueRe('top') }\\]`)
1685
+ );
1686
+ });
1687
+
1688
+ it('emits .sk-field.sk-label-left and sk-field[label-position="left"]', () =>
1689
+ {
1690
+ expect(fieldCSS()).toMatch(
1691
+ new RegExp(`\\.sk-field\\.sk-label-left,\\s*sk-field\\[label-position=${ attrValueRe('left') }\\]`)
1692
+ );
1693
+ });
1694
+ });
1695
+
1696
+ //--------------------------------------------------------------------------------------------------------------
1697
+ // Boolean modifiers
1698
+ //--------------------------------------------------------------------------------------------------------------
1699
+
1700
+ describe('boolean modifiers', () =>
1701
+ {
1702
+ it('emits .sk-field.sk-has-error and sk-field[has-error]', () =>
1703
+ {
1704
+ expect(fieldCSS()).toMatch(/\.sk-field\.sk-has-error,\s*sk-field\[has-error\]/);
1705
+ });
1706
+ });
1707
+
1708
+ //--------------------------------------------------------------------------------------------------------------
1709
+ // Defaults-when-absent
1710
+ //--------------------------------------------------------------------------------------------------------------
1711
+
1712
+ describe('defaults-when-absent', () =>
1713
+ {
1714
+ it('applies default column (top) layout when no sk-label-* class is present', () =>
1715
+ {
1716
+ expect(fieldCSS()).toMatch(
1717
+ new RegExp(`\\.sk-field:where\\(:not\\(\\[class\\*=${ attrValueRe('sk-label-') }\\]\\)\\)`)
1718
+ );
1719
+ });
1720
+ });
1721
+
1722
+ //--------------------------------------------------------------------------------------------------------------
1723
+ // Custom-element attribute-absence defaults
1724
+ //--------------------------------------------------------------------------------------------------------------
1725
+
1726
+ describe('custom-element defaults (defaults-when-no-attr)', () =>
1727
+ {
1728
+ it('emits default-label-position rule targeting bare <sk-field> without [label-position]', () =>
1729
+ {
1730
+ expect(fieldCSS()).toMatch(/sk-field:where\(:not\(\[label-position\]\)\)/);
1731
+ });
1732
+ });
1733
+ });
1734
+
1735
+ //------------------------------------------------------------------------------------------------------------------
1736
+
1737
+ describe('table', () =>
1738
+ {
1739
+ let css : string;
1740
+
1741
+ function tableCSS() : string
1742
+ {
1743
+ if(!css) { css = compileComponent('table'); }
1744
+ return css;
1745
+ }
1746
+
1747
+ //--------------------------------------------------------------------------------------------------------------
1748
+ // Base dual-selector (custom-element tag)
1749
+ //--------------------------------------------------------------------------------------------------------------
1750
+
1751
+ it('base rule includes the sk-table custom-element tag', () =>
1752
+ {
1753
+ expect(tableCSS()).toMatch(/\.sk-table,\s*sk-table\s*\{/);
1754
+ });
1755
+
1756
+ //--------------------------------------------------------------------------------------------------------------
1757
+ // Kind variants (full $kinds list)
1758
+ //--------------------------------------------------------------------------------------------------------------
1759
+
1760
+ describe('kind variants', () =>
1761
+ {
1762
+ for(const kind of KINDS)
1763
+ {
1764
+ it(`emits .sk-table.sk-${ kind } and sk-table[kind="${ kind }"]`, () =>
1765
+ {
1766
+ expect(tableCSS()).toMatch(
1767
+ new RegExp(
1768
+ dualSelectorRe('table', kind, 'kind', kind)
1769
+ )
1770
+ );
1771
+ });
1772
+ }
1773
+ });
1774
+
1775
+ //--------------------------------------------------------------------------------------------------------------
1776
+ // Variant variants (subtle)
1777
+ //--------------------------------------------------------------------------------------------------------------
1778
+
1779
+ describe('variant modifiers', () =>
1780
+ {
1781
+ it('emits .sk-table.sk-subtle and sk-table[variant="subtle"]', () =>
1782
+ {
1783
+ expect(tableCSS()).toMatch(
1784
+ new RegExp(dualSelectorRe('table', 'subtle', 'variant', 'subtle'))
1785
+ );
1786
+ });
1787
+ });
1788
+
1789
+ //--------------------------------------------------------------------------------------------------------------
1790
+ // Boolean modifiers
1791
+ //--------------------------------------------------------------------------------------------------------------
1792
+
1793
+ describe('boolean modifiers', () =>
1794
+ {
1795
+ // Bool-modifier blocks only contain nested child selectors, so Sass emits the
1796
+ // child-selector form rather than a bare top-level pair. Check the sub-selector.
1797
+ it('emits .sk-table.sk-striped (striped row rule)', () =>
1798
+ {
1799
+ expect(tableCSS()).toMatch(new RegExp(
1800
+ dualSelectorRe('table', 'striped', 'striped', undefined, {
1801
+ descendant: 'tbody\\s+tr:nth-child\\(even\\)',
1802
+ })
1803
+ ));
1804
+ });
1805
+
1806
+ it('emits .sk-table.sk-bordered (bordered cell rule)', () =>
1807
+ {
1808
+ expect(tableCSS()).toMatch(/\.sk-table\.sk-bordered\s+td,/);
1809
+ expect(tableCSS()).toMatch(/sk-table\[bordered\]\s+td,/);
1810
+ });
1811
+
1812
+ it('emits .sk-table.sk-compact (compact cell rule)', () =>
1813
+ {
1814
+ expect(tableCSS()).toMatch(/\.sk-table\.sk-compact\s+thead\s+th,/);
1815
+ expect(tableCSS()).toMatch(/sk-table\[compact\]\s+thead\s+th,/);
1816
+ });
1817
+
1818
+ it('emits .sk-table.sk-comfortable (comfortable cell rule)', () =>
1819
+ {
1820
+ expect(tableCSS()).toMatch(/\.sk-table\.sk-comfortable\s+thead\s+th,/);
1821
+ expect(tableCSS()).toMatch(/sk-table\[comfortable\]\s+thead\s+th,/);
1822
+ });
1823
+ });
1824
+
1825
+ //--------------------------------------------------------------------------------------------------------------
1826
+ // Defaults-when-absent
1827
+ //--------------------------------------------------------------------------------------------------------------
1828
+
1829
+ describe('defaults-when-absent', () =>
1830
+ {
1831
+ it('applies default neutral kind via :not() chain', () =>
1832
+ {
1833
+ const out = tableCSS();
1834
+ expect(out).toMatch(/\.sk-table:where\(:not\(\.sk-neutral\)/);
1835
+ expect(out).toMatch(/:not\(\.sk-primary\)/);
1836
+ expect(out).toMatch(/:not\(\.sk-red\)\)/);
1837
+ });
1838
+ });
1839
+
1840
+ //--------------------------------------------------------------------------------------------------------------
1841
+ // Custom-element attribute-absence defaults
1842
+ //--------------------------------------------------------------------------------------------------------------
1843
+
1844
+ describe('custom-element defaults (defaults-when-no-attr)', () =>
1845
+ {
1846
+ it('emits default-kind rule targeting bare <sk-table> without [kind]', () =>
1847
+ {
1848
+ expect(tableCSS()).toMatch(/sk-table:where\(:not\(\[kind\]\)\)/);
1849
+ });
1850
+ });
1851
+ });
1852
+
1853
+ //------------------------------------------------------------------------------------------------------------------
1854
+
1855
+ describe('tooltip', () =>
1856
+ {
1857
+ let css : string;
1858
+
1859
+ function tooltipCSS() : string
1860
+ {
1861
+ if(!css) { css = compileComponent('tooltip'); }
1862
+ return css;
1863
+ }
1864
+
1865
+ //--------------------------------------------------------------------------------------------------------------
1866
+ // Base dual-selector (custom-element tag)
1867
+ //--------------------------------------------------------------------------------------------------------------
1868
+
1869
+ it('base rule includes the sk-tooltip custom-element tag', () =>
1870
+ {
1871
+ expect(tooltipCSS()).toMatch(/\.sk-tooltip,\s*sk-tooltip\s*\{/);
1872
+ });
1873
+
1874
+ //--------------------------------------------------------------------------------------------------------------
1875
+ // Kind variants (full $kinds list)
1876
+ //--------------------------------------------------------------------------------------------------------------
1877
+
1878
+ describe('kind variants', () =>
1879
+ {
1880
+ for(const kind of KINDS)
1881
+ {
1882
+ it(`emits .sk-tooltip.sk-${ kind } and sk-tooltip[kind="${ kind }"]`, () =>
1883
+ {
1884
+ expect(tooltipCSS()).toMatch(
1885
+ new RegExp(
1886
+ dualSelectorRe('tooltip', kind, 'kind', kind)
1887
+ )
1888
+ );
1889
+ });
1890
+ }
1891
+ });
1892
+
1893
+ //--------------------------------------------------------------------------------------------------------------
1894
+ // Variant variants (solid, outline)
1895
+ //--------------------------------------------------------------------------------------------------------------
1896
+
1897
+ describe('variant modifiers', () =>
1898
+ {
1899
+ it('emits .sk-tooltip.sk-solid and sk-tooltip[variant="solid"]', () =>
1900
+ {
1901
+ expect(tooltipCSS()).toMatch(
1902
+ new RegExp(dualSelectorRe('tooltip', 'solid', 'variant', 'solid'))
1903
+ );
1904
+ });
1905
+
1906
+ it('emits .sk-tooltip.sk-outline and sk-tooltip[variant="outline"]', () =>
1907
+ {
1908
+ expect(tooltipCSS()).toMatch(
1909
+ new RegExp(dualSelectorRe('tooltip', 'outline', 'variant', 'outline'))
1910
+ );
1911
+ });
1912
+ });
1913
+
1914
+ //--------------------------------------------------------------------------------------------------------------
1915
+ // Placement modifiers (single-choice)
1916
+ //--------------------------------------------------------------------------------------------------------------
1917
+
1918
+ describe('placement modifiers', () =>
1919
+ {
1920
+ const placements = [ 'top', 'bottom', 'left', 'right' ];
1921
+
1922
+ for(const placement of placements)
1923
+ {
1924
+ it(`emits .sk-tooltip.sk-placement-${ placement } and sk-tooltip[placement="${ placement }"]`, () =>
1925
+ {
1926
+ expect(tooltipCSS()).toMatch(
1927
+ new RegExp(dualSelectorRe('tooltip', `placement-${ placement }`, 'placement', placement))
1928
+ );
1929
+ });
1930
+ }
1931
+ });
1932
+
1933
+ //--------------------------------------------------------------------------------------------------------------
1934
+ // Defaults-when-absent
1935
+ //--------------------------------------------------------------------------------------------------------------
1936
+
1937
+ describe('defaults-when-absent', () =>
1938
+ {
1939
+ it('applies default neutral kind via :not() chain', () =>
1940
+ {
1941
+ const out = tooltipCSS();
1942
+ expect(out).toMatch(/\.sk-tooltip:where\(:not\(\.sk-neutral\)/);
1943
+ expect(out).toMatch(/:not\(\.sk-primary\)/);
1944
+ expect(out).toMatch(/:not\(\.sk-red\)\)/);
1945
+ });
1946
+ });
1947
+
1948
+ //--------------------------------------------------------------------------------------------------------------
1949
+ // Custom-element attribute-absence defaults
1950
+ //--------------------------------------------------------------------------------------------------------------
1951
+
1952
+ describe('custom-element defaults (defaults-when-no-attr)', () =>
1953
+ {
1954
+ it('emits default-kind rule targeting bare <sk-tooltip> without [kind]', () =>
1955
+ {
1956
+ expect(tooltipCSS()).toMatch(/sk-tooltip:where\(:not\(\[kind\]\)\)/);
1957
+ });
1958
+ });
1959
+ });
1960
+
1961
+ //------------------------------------------------------------------------------------------------------------------
1962
+ // Tasks 27-32: Class-only form controls (Button, Input, Textarea, NumberInput, Checkbox, Radio)
1963
+ //
1964
+ // These are class-only — no custom-element tag, no attribute-selector siblings.
1965
+ //------------------------------------------------------------------------------------------------------------------
1966
+
1967
+ describe('button', () =>
1968
+ {
1969
+ let css : string;
1970
+
1971
+ function buttonCSS() : string
1972
+ {
1973
+ if(!css) { css = compileComponent('button'); }
1974
+ return css;
1975
+ }
1976
+
1977
+ //--------------------------------------------------------------------------------------------------------------
1978
+ // No attribute selectors emitted for button — class-only component
1979
+ //--------------------------------------------------------------------------------------------------------------
1980
+
1981
+ it('does not emit sk-button[kind=...] attribute selectors', () =>
1982
+ {
1983
+ expect(buttonCSS()).not.toMatch(/sk-button\[kind=/);
1984
+ });
1985
+
1986
+ //--------------------------------------------------------------------------------------------------------------
1987
+ // Defaults-when-absent: size (md)
1988
+ //--------------------------------------------------------------------------------------------------------------
1989
+
1990
+ describe('defaults-when-absent', () =>
1991
+ {
1992
+ it('applies default md size when no sk-size-* class is present', () =>
1993
+ {
1994
+ expect(buttonCSS()).toMatch(
1995
+ new RegExp(`\\.sk-button:where\\(:not\\(\\[class\\*=${ attrValueRe('sk-size-') }\\]\\)\\)`)
1996
+ );
1997
+ });
1998
+
1999
+ it('applies default solid variant when no explicit variant class is present', () =>
2000
+ {
2001
+ const variantNotChain = [ 'solid', 'outline', 'subtle', 'ghost', 'link' ]
2002
+ .map((variant) => `:not\\(\\.sk-${ variant }\\)`).join('');
2003
+ expect(buttonCSS()).toMatch(new RegExp(`\\.sk-button:where\\(${ variantNotChain }\\)`));
2004
+ });
2005
+
2006
+ it('applies default neutral kind via :not() chain', () =>
2007
+ {
2008
+ const out = buttonCSS();
2009
+ expect(out).toMatch(/\.sk-button:where\(:not\(\.sk-neutral\)/);
2010
+ expect(out).toMatch(/:not\(\.sk-primary\)/);
2011
+ expect(out).toMatch(/:not\(\.sk-red\)\)/);
2012
+ });
2013
+ });
2014
+
2015
+ //--------------------------------------------------------------------------------------------------------------
2016
+ // sk-size-* alias selectors exist alongside sk-<size>
2017
+ //--------------------------------------------------------------------------------------------------------------
2018
+
2019
+ describe('size alias selectors', () =>
2020
+ {
2021
+ for(const size of [ 'xs', 'sm', 'md', 'lg', 'xl' ])
2022
+ {
2023
+ it(`emits .sk-button.sk-size-${ size } alongside .sk-button.sk-${ size }`, () =>
2024
+ {
2025
+ expect(buttonCSS()).toMatch(new RegExp(`\\.sk-button\\.sk-size-${ size }[,\\s{]`));
2026
+ });
2027
+ }
2028
+ });
2029
+ });
2030
+
2031
+ //------------------------------------------------------------------------------------------------------------------
2032
+
2033
+ describe('input', () =>
2034
+ {
2035
+ let css : string;
2036
+
2037
+ function inputCSS() : string
2038
+ {
2039
+ if(!css) { css = compileComponent('input'); }
2040
+ return css;
2041
+ }
2042
+
2043
+ //--------------------------------------------------------------------------------------------------------------
2044
+ // No attribute selectors emitted — class-only component
2045
+ //--------------------------------------------------------------------------------------------------------------
2046
+
2047
+ it('does not emit sk-input[kind=...] attribute selectors', () =>
2048
+ {
2049
+ expect(inputCSS()).not.toMatch(/sk-input\[kind=/);
2050
+ });
2051
+
2052
+ //--------------------------------------------------------------------------------------------------------------
2053
+ // Defaults-when-absent
2054
+ //--------------------------------------------------------------------------------------------------------------
2055
+
2056
+ describe('defaults-when-absent', () =>
2057
+ {
2058
+ it('applies default md size when no sk-size-* class is present', () =>
2059
+ {
2060
+ expect(inputCSS()).toMatch(
2061
+ new RegExp(`\\.sk-input:where\\(:not\\(\\[class\\*=${ attrValueRe('sk-size-') }\\]\\)\\)`)
2062
+ );
2063
+ });
2064
+
2065
+ it('applies default neutral kind via :not() chain', () =>
2066
+ {
2067
+ const out = inputCSS();
2068
+ expect(out).toMatch(/\.sk-input:where\(:not\(\.sk-neutral\)/);
2069
+ expect(out).toMatch(/:not\(\.sk-red\)\)/);
2070
+ });
2071
+ });
2072
+
2073
+ //--------------------------------------------------------------------------------------------------------------
2074
+ // sk-size-* alias selectors
2075
+ //--------------------------------------------------------------------------------------------------------------
2076
+
2077
+ describe('size alias selectors', () =>
2078
+ {
2079
+ for(const size of [ 'sm', 'md', 'lg', 'xl' ])
2080
+ {
2081
+ it(`emits .sk-input.sk-size-${ size } alongside .sk-input.sk-${ size }`, () =>
2082
+ {
2083
+ expect(inputCSS()).toMatch(new RegExp(`\\.sk-input\\.sk-size-${ size }[,\\s{]`));
2084
+ });
2085
+ }
2086
+ });
2087
+ });
2088
+
2089
+ //------------------------------------------------------------------------------------------------------------------
2090
+
2091
+ describe('textarea', () =>
2092
+ {
2093
+ let css : string;
2094
+
2095
+ function textareaCSS() : string
2096
+ {
2097
+ if(!css) { css = compileComponent('textarea'); }
2098
+ return css;
2099
+ }
2100
+
2101
+ //--------------------------------------------------------------------------------------------------------------
2102
+ // No attribute selectors emitted — class-only component
2103
+ //--------------------------------------------------------------------------------------------------------------
2104
+
2105
+ it('does not emit sk-textarea[kind=...] attribute selectors', () =>
2106
+ {
2107
+ expect(textareaCSS()).not.toMatch(/sk-textarea\[kind=/);
2108
+ });
2109
+
2110
+ //--------------------------------------------------------------------------------------------------------------
2111
+ // Defaults-when-absent
2112
+ //--------------------------------------------------------------------------------------------------------------
2113
+
2114
+ describe('defaults-when-absent', () =>
2115
+ {
2116
+ it('applies default md size when no sk-size-* class is present', () =>
2117
+ {
2118
+ expect(textareaCSS()).toMatch(
2119
+ new RegExp(`\\.sk-textarea:where\\(:not\\(\\[class\\*=${ attrValueRe('sk-size-') }\\]\\)\\)`)
2120
+ );
2121
+ });
2122
+
2123
+ it('applies default neutral kind via :not() chain', () =>
2124
+ {
2125
+ const out = textareaCSS();
2126
+ expect(out).toMatch(/\.sk-textarea:where\(:not\(\.sk-neutral\)/);
2127
+ expect(out).toMatch(/:not\(\.sk-red\)\)/);
2128
+ });
2129
+ });
2130
+
2131
+ //--------------------------------------------------------------------------------------------------------------
2132
+ // sk-size-* alias selectors
2133
+ //--------------------------------------------------------------------------------------------------------------
2134
+
2135
+ describe('size alias selectors', () =>
2136
+ {
2137
+ for(const size of [ 'sm', 'md', 'lg', 'xl' ])
2138
+ {
2139
+ it(`emits .sk-textarea.sk-size-${ size } alongside .sk-textarea.sk-${ size }`, () =>
2140
+ {
2141
+ expect(textareaCSS()).toMatch(new RegExp(`\\.sk-textarea\\.sk-size-${ size }[,\\s{]`));
2142
+ });
2143
+ }
2144
+ });
2145
+ });
2146
+
2147
+ //------------------------------------------------------------------------------------------------------------------
2148
+
2149
+ describe('number-input', () =>
2150
+ {
2151
+ let css : string;
2152
+
2153
+ function numberInputCSS() : string
2154
+ {
2155
+ if(!css) { css = compileComponent('number-input'); }
2156
+ return css;
2157
+ }
2158
+
2159
+ //--------------------------------------------------------------------------------------------------------------
2160
+ // No attribute selectors emitted — class-only component
2161
+ //--------------------------------------------------------------------------------------------------------------
2162
+
2163
+ it('does not emit sk-number-input[kind=...] attribute selectors', () =>
2164
+ {
2165
+ expect(numberInputCSS()).not.toMatch(/sk-number-input\[kind=/);
2166
+ });
2167
+
2168
+ //--------------------------------------------------------------------------------------------------------------
2169
+ // Defaults-when-absent
2170
+ //--------------------------------------------------------------------------------------------------------------
2171
+
2172
+ describe('defaults-when-absent', () =>
2173
+ {
2174
+ it('applies default md size when no sk-size-* class is present on the wrapper', () =>
2175
+ {
2176
+ const sizeMatch = `\\[class\\*=${ attrValueRe('sk-size-') }\\]`;
2177
+ expect(numberInputCSS()).toMatch(
2178
+ new RegExp(`\\.sk-number-input-wrapper:where\\(:not\\(${ sizeMatch }\\)\\)`)
2179
+ );
2180
+ });
2181
+
2182
+ it('applies default neutral kind via :not() chain on the wrapper', () =>
2183
+ {
2184
+ const out = numberInputCSS();
2185
+ expect(out).toMatch(/\.sk-number-input-wrapper:where\(:not\(\.sk-neutral\)/);
2186
+ expect(out).toMatch(/:not\(\.sk-red\)\)/);
2187
+ });
2188
+ });
2189
+
2190
+ //--------------------------------------------------------------------------------------------------------------
2191
+ // sk-size-* alias selectors on wrapper
2192
+ //--------------------------------------------------------------------------------------------------------------
2193
+
2194
+ describe('size alias selectors', () =>
2195
+ {
2196
+ for(const size of [ 'sm', 'md', 'lg', 'xl' ])
2197
+ {
2198
+ it(`emits both .sk-size-${ size } and .sk-${ size } on .sk-number-input-wrapper`, () =>
2199
+ {
2200
+ expect(numberInputCSS()).toMatch(
2201
+ new RegExp(`\\.sk-number-input-wrapper\\.sk-size-${ size }[,\\s{\\s]`)
2202
+ );
2203
+ });
2204
+ }
2205
+ });
2206
+ });
2207
+
2208
+ //------------------------------------------------------------------------------------------------------------------
2209
+
2210
+ describe('checkbox', () =>
2211
+ {
2212
+ let css : string;
2213
+
2214
+ function checkboxCSS() : string
2215
+ {
2216
+ if(!css) { css = compileComponent('checkbox'); }
2217
+ return css;
2218
+ }
2219
+
2220
+ //--------------------------------------------------------------------------------------------------------------
2221
+ // No attribute selectors emitted — class-only component
2222
+ //--------------------------------------------------------------------------------------------------------------
2223
+
2224
+ it('does not emit sk-checkbox[kind=...] attribute selectors', () =>
2225
+ {
2226
+ expect(checkboxCSS()).not.toMatch(/sk-checkbox\[kind=/);
2227
+ });
2228
+
2229
+ //--------------------------------------------------------------------------------------------------------------
2230
+ // Defaults-when-absent
2231
+ //--------------------------------------------------------------------------------------------------------------
2232
+
2233
+ describe('defaults-when-absent', () =>
2234
+ {
2235
+ it('applies default md size when no sk-size-* class is present', () =>
2236
+ {
2237
+ expect(checkboxCSS()).toMatch(
2238
+ new RegExp(`\\.sk-checkbox:where\\(:not\\(\\[class\\*=${ attrValueRe('sk-size-') }\\]\\)\\)`)
2239
+ );
2240
+ });
2241
+
2242
+ it('applies default neutral kind via :not() chain', () =>
2243
+ {
2244
+ const out = checkboxCSS();
2245
+ expect(out).toMatch(/\.sk-checkbox:where\(:not\(\.sk-neutral\)/);
2246
+ expect(out).toMatch(/:not\(\.sk-red\)\)/);
2247
+ });
2248
+ });
2249
+
2250
+ //--------------------------------------------------------------------------------------------------------------
2251
+ // sk-size-* alias selectors
2252
+ //--------------------------------------------------------------------------------------------------------------
2253
+
2254
+ describe('size alias selectors', () =>
2255
+ {
2256
+ for(const size of [ 'sm', 'md', 'lg', 'xl' ])
2257
+ {
2258
+ it(`emits .sk-checkbox.sk-size-${ size } alongside .sk-checkbox.sk-${ size }`, () =>
2259
+ {
2260
+ expect(checkboxCSS()).toMatch(new RegExp(`\\.sk-checkbox\\.sk-size-${ size }[,\\s{]`));
2261
+ });
2262
+ }
2263
+ });
2264
+ });
2265
+
2266
+ //------------------------------------------------------------------------------------------------------------------
2267
+
2268
+ describe('radio', () =>
2269
+ {
2270
+ let css : string;
2271
+
2272
+ function radioCSS() : string
2273
+ {
2274
+ if(!css) { css = compileComponent('radio'); }
2275
+ return css;
2276
+ }
2277
+
2278
+ //--------------------------------------------------------------------------------------------------------------
2279
+ // No attribute selectors emitted — class-only component
2280
+ //--------------------------------------------------------------------------------------------------------------
2281
+
2282
+ it('does not emit sk-radio[kind=...] attribute selectors', () =>
2283
+ {
2284
+ expect(radioCSS()).not.toMatch(/sk-radio\[kind=/);
2285
+ });
2286
+
2287
+ //--------------------------------------------------------------------------------------------------------------
2288
+ // Defaults-when-absent
2289
+ //--------------------------------------------------------------------------------------------------------------
2290
+
2291
+ describe('defaults-when-absent', () =>
2292
+ {
2293
+ it('applies default md size when no sk-size-* class is present', () =>
2294
+ {
2295
+ expect(radioCSS()).toMatch(
2296
+ new RegExp(`\\.sk-radio:where\\(:not\\(\\[class\\*=${ attrValueRe('sk-size-') }\\]\\)\\)`)
2297
+ );
2298
+ });
2299
+
2300
+ it('applies default neutral kind via :not() chain', () =>
2301
+ {
2302
+ const out = radioCSS();
2303
+ expect(out).toMatch(/\.sk-radio:where\(:not\(\.sk-neutral\)/);
2304
+ expect(out).toMatch(/:not\(\.sk-red\)\)/);
2305
+ });
2306
+ });
2307
+
2308
+ //--------------------------------------------------------------------------------------------------------------
2309
+ // sk-size-* alias selectors
2310
+ //--------------------------------------------------------------------------------------------------------------
2311
+
2312
+ describe('size alias selectors', () =>
2313
+ {
2314
+ for(const size of [ 'sm', 'md', 'lg', 'xl' ])
2315
+ {
2316
+ it(`emits .sk-radio.sk-size-${ size } alongside .sk-radio.sk-${ size }`, () =>
2317
+ {
2318
+ expect(radioCSS()).toMatch(new RegExp(`\\.sk-radio\\.sk-size-${ size }[,\\s{]`));
2319
+ });
2320
+ }
2321
+ });
2322
+ });
2323
+
2324
+ //------------------------------------------------------------------------------------------------------------------
2325
+
2326
+ describe('switch', () =>
2327
+ {
2328
+ const css = compileComponent('switch');
2329
+
2330
+ it('does NOT emit sk-switch[kind=...] attribute selectors', () =>
2331
+ {
2332
+ expect(css).not.toMatch(/sk-switch\[kind=/);
2333
+ });
2334
+
2335
+ describe('defaults-when-absent', () =>
2336
+ {
2337
+ it('applies default md size when no sk-size-* class is present', () =>
2338
+ {
2339
+ expect(css).toMatch(
2340
+ new RegExp(`\\.sk-switch:where\\(:not\\(\\[class\\*=${ attrValueRe('sk-size-') }\\]\\)\\)`)
2341
+ );
2342
+ });
2343
+
2344
+ it('applies default kind via :not() chain when no kind class is present', () =>
2345
+ {
2346
+ expect(css).toMatch(/\.sk-switch:where\(:not\(\.sk-neutral\)/);
2347
+ expect(css).toMatch(/:not\(\.sk-red\)\)/);
2348
+ });
2349
+ });
2350
+
2351
+ describe('size alias selectors', () =>
2352
+ {
2353
+ for(const size of [ 'xs', 'sm', 'md', 'lg', 'xl' ])
2354
+ {
2355
+ it(`emits sk-size-${ size } alias alongside .sk-switch.sk-${ size } on wrapper`, () =>
2356
+ {
2357
+ expect(css).toMatch(new RegExp(`sk-switch\\.sk-size-${ size }`));
2358
+ });
2359
+ }
2360
+ });
2361
+ });
2362
+
2363
+ //------------------------------------------------------------------------------------------------------------------
2364
+
2365
+ describe('select', () =>
2366
+ {
2367
+ const css = compileComponent('select');
2368
+
2369
+ it('does NOT emit sk-select[kind=...] attribute selectors', () =>
2370
+ {
2371
+ expect(css).not.toMatch(/sk-select\[kind=/);
2372
+ });
2373
+
2374
+ describe('defaults-when-absent', () =>
2375
+ {
2376
+ it('applies default md size when no sk-size-* class is present', () =>
2377
+ {
2378
+ expect(css).toMatch(
2379
+ new RegExp(`\\.sk-select:where\\(:not\\(\\[class\\*=${ attrValueRe('sk-size-') }\\]\\)\\)`)
2380
+ );
2381
+ });
2382
+
2383
+ it('applies default neutral kind via :not() chain when no kind class is present', () =>
2384
+ {
2385
+ expect(css).toMatch(/\.sk-select:where\(:not\(\.sk-neutral\)/);
2386
+ expect(css).toMatch(/:not\(\.sk-red\)\)/);
2387
+ });
2388
+ });
2389
+
2390
+ describe('size alias selectors', () =>
2391
+ {
2392
+ for(const size of [ 'sm', 'md', 'lg', 'xl' ])
2393
+ {
2394
+ it(`emits .sk-select.sk-size-${ size } alongside .sk-select.sk-${ size }`, () =>
2395
+ {
2396
+ expect(css).toMatch(new RegExp(`\\.sk-select\\.sk-size-${ size }[,\\s{]`));
2397
+ });
2398
+ }
2399
+ });
2400
+ });
2401
+
2402
+ //------------------------------------------------------------------------------------------------------------------
2403
+
2404
+ describe('slider', () =>
2405
+ {
2406
+ const css = compileComponent('slider');
2407
+
2408
+ it('does NOT emit sk-slider[kind=...] attribute selectors', () =>
2409
+ {
2410
+ expect(css).not.toMatch(/sk-slider\[kind=/);
2411
+ });
2412
+
2413
+ describe('defaults-when-absent', () =>
2414
+ {
2415
+ it('applies default md size when no sk-size-* class is present', () =>
2416
+ {
2417
+ expect(css).toMatch(
2418
+ new RegExp(`\\.sk-slider:where\\(:not\\(\\[class\\*=${ attrValueRe('sk-size-') }\\]\\)\\)`)
2419
+ );
2420
+ });
2421
+
2422
+ it('applies default kind via :not() chain when no kind class is present', () =>
2423
+ {
2424
+ expect(css).toMatch(/\.sk-slider:where\(:not\(\.sk-neutral\)/);
2425
+ expect(css).toMatch(/:not\(\.sk-red\)\)/);
2426
+ });
2427
+ });
2428
+
2429
+ describe('size alias selectors', () =>
2430
+ {
2431
+ for(const size of [ 'xs', 'sm', 'md', 'lg', 'xl' ])
2432
+ {
2433
+ it(`emits .sk-slider.sk-size-${ size } alongside .sk-slider.sk-${ size }`, () =>
2434
+ {
2435
+ expect(css).toMatch(new RegExp(`\\.sk-slider\\.sk-size-${ size }[,\\s{]`));
2436
+ });
2437
+ }
2438
+ });
2439
+
2440
+ it('preserves native range pseudo-element rules (::-webkit-slider-thumb etc.)', () =>
2441
+ {
2442
+ // Slider uses Reka UI primitives that render range-like elements.
2443
+ // The thumb is a class-based element; verify thumb rules still compile.
2444
+ expect(css).toMatch(/\.sk-slider-thumb/);
2445
+ });
2446
+ });
2447
+
2448
+ //------------------------------------------------------------------------------------------------------------------
2449
+
2450
+ describe('color-picker', () =>
2451
+ {
2452
+ const css = compileComponent('color-picker');
2453
+
2454
+ it('does NOT emit sk-color-picker[kind=...] attribute selectors', () =>
2455
+ {
2456
+ expect(css).not.toMatch(/sk-color-picker\[kind=/);
2457
+ });
2458
+
2459
+ describe('defaults-when-absent', () =>
2460
+ {
2461
+ it('applies default md size when no sk-size-* class is present', () =>
2462
+ {
2463
+ expect(css).toMatch(
2464
+ new RegExp(`\\.sk-color-picker:where\\(:not\\(\\[class\\*=${ attrValueRe('sk-size-') }\\]\\)\\)`)
2465
+ );
2466
+ });
2467
+
2468
+ it('applies default neutral kind via :not() chain when no kind class is present', () =>
2469
+ {
2470
+ expect(css).toMatch(/\.sk-color-picker:where\(:not\(\.sk-neutral\)/);
2471
+ expect(css).toMatch(/:not\(\.sk-red\)\)/);
2472
+ });
2473
+ });
2474
+
2475
+ describe('size alias selectors', () =>
2476
+ {
2477
+ for(const size of [ 'sm', 'md', 'lg', 'xl' ])
2478
+ {
2479
+ it(`emits .sk-color-picker.sk-size-${ size } alongside .sk-color-picker.sk-${ size }`, () =>
2480
+ {
2481
+ expect(css).toMatch(new RegExp(`\\.sk-color-picker\\.sk-size-${ size }[,\\s{]`));
2482
+ });
2483
+ }
2484
+ });
2485
+ });
2486
+
2487
+ //------------------------------------------------------------------------------------------------------------------
2488
+
2489
+ describe('tags-input', () =>
2490
+ {
2491
+ const css = compileComponent('tags-input');
2492
+
2493
+ it('does NOT emit sk-tags-input[kind=...] attribute selectors', () =>
2494
+ {
2495
+ expect(css).not.toMatch(/sk-tags-input\[kind=/);
2496
+ });
2497
+
2498
+ describe('defaults-when-absent', () =>
2499
+ {
2500
+ it('applies default md size when no sk-size-* class is present', () =>
2501
+ {
2502
+ expect(css).toMatch(
2503
+ new RegExp(`\\.sk-tags-input:where\\(:not\\(\\[class\\*=${ attrValueRe('sk-size-') }\\]\\)\\)`)
2504
+ );
2505
+ });
2506
+
2507
+ it('applies default neutral kind via :not() chain when no kind class is present', () =>
2508
+ {
2509
+ expect(css).toMatch(/\.sk-tags-input:where\(:not\(\.sk-neutral\)/);
2510
+ expect(css).toMatch(/:not\(\.sk-red\)\)/);
2511
+ });
2512
+ });
2513
+
2514
+ describe('size alias selectors', () =>
2515
+ {
2516
+ for(const size of [ 'sm', 'md', 'lg', 'xl' ])
2517
+ {
2518
+ it(`emits .sk-tags-input.sk-size-${ size } alongside .sk-tags-input.sk-${ size }`, () =>
2519
+ {
2520
+ expect(css).toMatch(new RegExp(`\\.sk-tags-input\\.sk-size-${ size }[,\\s{]`));
2521
+ });
2522
+ }
2523
+ });
2524
+
2525
+ it('preserves inner tag chip rules (.sk-tag)', () =>
2526
+ {
2527
+ // tags-input container hosts .sk-tag chips — verify no regression in base rules.
2528
+ expect(css).toMatch(/\.sk-tags-input-container/);
2529
+ });
2530
+ });
2531
+
2532
+ //------------------------------------------------------------------------------------------------------------------
2533
+
2534
+ describe('dropdown', () =>
2535
+ {
2536
+ const css = compileComponent('dropdown');
2537
+
2538
+ it('does NOT emit sk-dropdown[kind=...] modifier attribute selectors', () =>
2539
+ {
2540
+ // [open] on <details> is fine; we're only guarding against emitting kind= or size= attribute selectors.
2541
+ expect(css).not.toMatch(/sk-dropdown\[kind=/);
2542
+ expect(css).not.toMatch(/sk-dropdown\[size=/);
2543
+ });
2544
+
2545
+ it('styles the <summary> trigger', () =>
2546
+ {
2547
+ expect(css).toMatch(/\.sk-dropdown\s*>\s*summary/);
2548
+ });
2549
+
2550
+ it('applies default size when no sk-size-* class is present', () =>
2551
+ {
2552
+ expect(css).toMatch(
2553
+ new RegExp(`\\.sk-dropdown:where\\(:not\\(\\[class\\*=${ attrValueRe('sk-size-') }\\]\\)\\)`)
2554
+ );
2555
+ });
2556
+
2557
+ it('applies default kind when no sk-<kind> class is present', () =>
2558
+ {
2559
+ expect(css).toMatch(/\.sk-dropdown:where\(:not\(\.sk-neutral\)/);
2560
+ });
2561
+
2562
+ describe('size alias selectors', () =>
2563
+ {
2564
+ for(const size of [ 'sm', 'md', 'lg' ])
2565
+ {
2566
+ it(`emits .sk-dropdown.sk-size-${ size } alongside .sk-dropdown.sk-${ size }`, () =>
2567
+ {
2568
+ expect(css).toMatch(new RegExp(`\\.sk-dropdown\\.sk-size-${ size }[,\\s{]`));
2569
+ });
2570
+ }
2571
+ });
2572
+ });
2573
+ });
2574
+
2575
+ //----------------------------------------------------------------------------------------------------------------------