@livestorm/ui-vue 3.0.0 → 4.0.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 (291) hide show
  1. package/README.md +3 -3
  2. package/components-vite.js +2 -2
  3. package/dist/UiButton-uyW8EHRJ.mjs +602 -0
  4. package/dist/UiTimelineItem-EiOEi1yF.mjs +7293 -0
  5. package/dist/UiTooltip-DZcPD6_N.mjs +158 -0
  6. package/dist/a11y-CaZxRWzo.mjs +6 -0
  7. package/dist/breakpoints.mjs +4 -9
  8. package/dist/bus.mjs +5 -5
  9. package/dist/components.mjs +236 -231
  10. package/dist/i18n-B8QOMuFD.mjs +46 -0
  11. package/dist/index.mjs +4 -91
  12. package/dist/integrations.mjs +3 -14
  13. package/dist/layouts.mjs +228 -236
  14. package/dist/responsive.mjs +31 -36
  15. package/dist/slot.mjs +11 -17
  16. package/dist/toastDSV2-D3xDZPe5.mjs +105 -0
  17. package/dist/types/index.d.ts +19 -1
  18. package/dist/types/src/components/Toast/ToastMessage.vue.d.ts +17 -7
  19. package/dist/types/src/components/Toast/toastDSV2.d.ts +89 -64
  20. package/dist/types/src/components/UiActionBar/types.d.ts +2 -0
  21. package/dist/types/src/components/UiAvatar/UiAvatar.vue.d.ts +4 -2
  22. package/dist/types/src/components/UiAvatarList/UiAvatarList.vue.d.ts +3 -2
  23. package/dist/types/src/components/UiBanner/UiBanner.vue.d.ts +13 -1
  24. package/dist/types/src/components/UiButton/UiButton.vue.d.ts +95 -35
  25. package/dist/types/src/components/UiButtonGroup/UiButtonGroup.vue.d.ts +8 -2
  26. package/dist/types/src/components/UiButtonGroup/types.d.ts +4 -0
  27. package/dist/types/src/components/UiChoice/UiChoice.vue.d.ts +71 -0
  28. package/dist/types/src/components/UiChoice/index.d.ts +2 -0
  29. package/dist/types/src/components/UiChoice/types.d.ts +7 -0
  30. package/dist/types/src/components/UiColorField/UiColorField.vue.d.ts +1 -1
  31. package/dist/types/src/components/UiColorPicker/UiColorPicker.vue.d.ts +2 -2
  32. package/dist/types/src/components/UiCombobox/UiCombobox.types.d.ts +47 -0
  33. package/dist/types/src/components/UiCombobox/UiCombobox.vue.d.ts +40 -0
  34. package/dist/types/src/components/UiCombobox/index.d.ts +2 -0
  35. package/dist/types/src/components/UiDataTable/UiDataTable.types.d.ts +46 -0
  36. package/dist/types/src/components/UiDataTable/UiDataTable.vue.d.ts +136 -0
  37. package/dist/types/src/components/UiDataTable/index.d.ts +5 -0
  38. package/dist/types/src/components/UiDrawer/UiDrawer.vue.d.ts +28 -0
  39. package/dist/types/src/components/UiDrawer/UiDrawerContent.vue.d.ts +18 -0
  40. package/dist/types/src/components/UiDrawer/UiDrawerTrigger.vue.d.ts +14 -0
  41. package/dist/types/src/components/UiDropdown/DropdownItemCheckbox.vue.d.ts +3 -0
  42. package/dist/types/src/components/UiDropdown/UiDropdownContent.vue.d.ts +10 -0
  43. package/dist/types/src/components/UiDropdown/UiDropdownGroup.vue.d.ts +17 -0
  44. package/dist/types/src/components/UiDropdown/UiDropdownItem.vue.d.ts +8 -5
  45. package/dist/types/src/components/UiDropdown/UiDropdownTrigger.vue.d.ts +21 -5
  46. package/dist/types/src/components/UiDropdown/index.d.ts +1 -0
  47. package/dist/types/src/components/UiEmojiPickerList/UiEmojiPickerList.vue.d.ts +22 -0
  48. package/dist/types/src/components/UiEmptyState/UiEmptyState.vue.d.ts +8 -3
  49. package/dist/types/src/components/UiFieldWrapper/UiFieldWrapper.vue.d.ts +2 -2
  50. package/dist/types/src/components/UiInputCheckbox/CheckboxGlyph.vue.d.ts +17 -0
  51. package/dist/types/src/components/UiInputCheckbox/UiInputCheckbox.vue.d.ts +35 -4
  52. package/dist/types/src/components/UiInputRadio/RadioGlyph.vue.d.ts +19 -0
  53. package/dist/types/src/components/UiInputRadio/UiInputRadio.vue.d.ts +21 -8
  54. package/dist/types/src/components/UiInputRadio/UiInputRadioGroup.vue.d.ts +6 -1
  55. package/dist/types/src/components/UiInputRadio/types.d.ts +1 -1
  56. package/dist/types/src/components/UiInputSelect/UiInputSelect.vue.d.ts +5 -5
  57. package/dist/types/src/components/UiInputTextarea/UiInputTextarea.vue.d.ts +5 -5
  58. package/dist/types/src/components/UiListItem/UiListItem.vue.d.ts +18 -4
  59. package/dist/types/src/components/UiLoader/UiSpinnerIcon.vue.d.ts +3 -0
  60. package/dist/types/src/components/UiMenu/UiMenuContent.vue.d.ts +15 -2
  61. package/dist/types/src/components/UiMenu/UiMenuItem.vue.d.ts +9 -3
  62. package/dist/types/src/components/UiNestedOptions/UiNestedOptions.vue.d.ts +13 -0
  63. package/dist/types/src/components/UiPage/UiPage.vue.d.ts +79 -15
  64. package/dist/types/src/components/UiPagination/UiPagination.vue.d.ts +36 -0
  65. package/dist/types/src/components/UiPagination/index.d.ts +2 -0
  66. package/dist/types/src/components/UiPagination/types.d.ts +4 -0
  67. package/dist/types/src/components/UiPane/UiPane.vue.d.ts +22 -0
  68. package/dist/types/src/components/UiPane/UiPaneContent.vue.d.ts +75 -0
  69. package/dist/types/src/components/UiPane/UiPaneTrigger.vue.d.ts +14 -0
  70. package/dist/types/src/components/UiPane/index.d.ts +5 -0
  71. package/dist/types/src/components/UiRating/UiRating.vue.d.ts +62 -0
  72. package/dist/types/src/components/UiRating/types.d.ts +1 -0
  73. package/dist/types/src/components/UiRoomButton/UiRoomButton.vue.d.ts +5 -3
  74. package/dist/types/src/components/UiRoomCountdown/UiRoomCountdown.vue.d.ts +32 -0
  75. package/dist/types/src/components/UiShell/UiShell.vue.d.ts +3 -0
  76. package/dist/types/src/components/UiSideMenu/UiSideMenu.vue.d.ts +22 -1
  77. package/dist/types/src/components/UiSideMenu/UiSideMenuItemContent.vue.d.ts +2 -2
  78. package/dist/types/src/components/UiSwitch/UiSwitch.vue.d.ts +10 -5
  79. package/dist/types/src/components/UiTable/UiTable.vue.d.ts +13 -4
  80. package/dist/types/src/components/UiTable/UiTableActionsCell.vue.d.ts +49 -0
  81. package/dist/types/src/components/UiTable/UiTableActionsHeaderCell.vue.d.ts +3 -0
  82. package/dist/types/src/components/UiTable/UiTableBody.vue.d.ts +11 -1
  83. package/dist/types/src/components/UiTable/UiTableCell.vue.d.ts +16 -1
  84. package/dist/types/src/components/UiTable/UiTableHeaderCell.vue.d.ts +31 -2
  85. package/dist/types/src/components/UiTable/UiTableRow.vue.d.ts +32 -2
  86. package/dist/types/src/components/UiTable/context.d.ts +3 -0
  87. package/dist/types/src/components/UiTable/index.d.ts +4 -0
  88. package/dist/types/src/components/UiTabs/UiTabs.vue.d.ts +3 -0
  89. package/dist/types/src/components/UiTag/UiTag.types.d.ts +25 -0
  90. package/dist/types/src/components/UiTag/UiTag.vue.d.ts +5 -26
  91. package/dist/types/src/components/UiTag/index.d.ts +2 -1
  92. package/dist/types/src/components/UiTextField/UiTextField.vue.d.ts +10 -5
  93. package/dist/types/src/components/UiTooltip/UiTooltipProvider.vue.d.ts +1 -0
  94. package/dist/types/src/components/UiTooltip/UiTooltipTrigger.vue.d.ts +2 -2
  95. package/dist/types/src/composables/storedThemingApplies.d.ts +17 -0
  96. package/dist/types/src/composables/useButtonStyles.d.ts +1 -0
  97. package/dist/types/src/composables/useChoiceboxSurface.d.ts +11 -0
  98. package/dist/types/src/composables/useContentMode.d.ts +7 -9
  99. package/dist/types/src/composables/useControlModes.d.ts +7 -0
  100. package/dist/types/src/composables/useControlShape.d.ts +24 -0
  101. package/dist/types/src/composables/useFieldFocusEvents.d.ts +12 -0
  102. package/dist/types/src/composables/useIconSize.d.ts +2 -0
  103. package/dist/types/src/composables/usePullGesture.d.ts +42 -0
  104. package/dist/types/src/composables/useThemeAndModes.d.ts +4 -0
  105. package/dist/types/src/theming/colorScale.d.ts +165 -0
  106. package/dist/types/src/theming/legacyColorScale.d.ts +27 -0
  107. package/dist/types/src/theming/presetColors.d.ts +11 -0
  108. package/dist/types/src/theming/primitiveTokens.d.ts +2 -0
  109. package/dist/types/src/utils/effectiveOpacity.d.ts +2 -0
  110. package/dist/types/src/utils/numbers.d.ts +1 -2
  111. package/dist/types/src/utils/tokenColor.d.ts +2 -0
  112. package/dist/ui-vue.css +2 -1
  113. package/dist/utils.mjs +165 -194
  114. package/index.d.ts +18 -0
  115. package/index.js +36 -0
  116. package/package.json +26 -21
  117. package/src/components/Toast/LayoutToasts.vue +19 -11
  118. package/src/components/Toast/ToastMessage.vue +125 -68
  119. package/src/components/Toast/toastDSV2.ts +238 -67
  120. package/src/components/UiActionBar/UiActionBar.vue +4 -3
  121. package/src/components/UiActionBar/UiActionBarItem.vue +9 -6
  122. package/src/components/UiActionBar/types.ts +13 -0
  123. package/src/components/UiAvatar/UiAvatar.vue +8 -2
  124. package/src/components/UiAvatarList/UiAvatarList.vue +51 -20
  125. package/src/components/UiBadge/UiBadge.vue +1 -1
  126. package/src/components/UiBanner/UiBanner.vue +71 -21
  127. package/src/components/UiBreadcrumb/UiBreadcrumb.vue +1 -1
  128. package/src/components/UiButton/UiButton.vue +92 -25
  129. package/src/components/UiButtonGroup/UiButtonGroup.vue +21 -17
  130. package/src/components/UiButtonGroup/UiButtonGroupItem.vue +33 -15
  131. package/src/components/UiButtonGroup/types.ts +19 -0
  132. package/src/components/UiCard/UiCard.vue +20 -23
  133. package/src/components/UiChoice/UiChoice.vue +187 -0
  134. package/src/components/UiChoice/index.ts +2 -0
  135. package/src/components/UiChoice/types.ts +11 -0
  136. package/src/components/UiColorField/UiColorField.vue +2 -2
  137. package/src/components/UiColorPicker/UiColorPicker.vue +11 -8
  138. package/src/components/UiCombobox/UiCombobox.types.ts +52 -0
  139. package/src/components/UiCombobox/UiCombobox.vue +296 -0
  140. package/src/components/UiCombobox/index.ts +2 -0
  141. package/src/components/UiDataTable/UiDataTable.types.ts +47 -0
  142. package/src/components/UiDataTable/UiDataTable.vue +422 -0
  143. package/src/components/UiDataTable/index.ts +12 -0
  144. package/src/components/UiDialog/UiDialogContent.vue +22 -7
  145. package/src/components/UiDrawer/UiDrawer.vue +16 -0
  146. package/src/components/UiDrawer/UiDrawerContent.vue +42 -0
  147. package/src/components/UiDrawer/UiDrawerTrigger.vue +17 -0
  148. package/src/components/UiDropdown/DropdownItemCheckbox.vue +10 -0
  149. package/src/components/UiDropdown/UiDropdownContent.vue +32 -21
  150. package/src/components/UiDropdown/UiDropdownGroup.vue +23 -0
  151. package/src/components/UiDropdown/UiDropdownItem.vue +38 -18
  152. package/src/components/UiDropdown/UiDropdownSeparator.vue +1 -1
  153. package/src/components/UiDropdown/UiDropdownTrigger.vue +138 -43
  154. package/src/components/UiDropdown/index.ts +1 -0
  155. package/src/components/UiEmojiPickerList/UiEmojiPickerList.vue +122 -0
  156. package/src/components/UiEmptyState/UiEmptyState.vue +16 -4
  157. package/src/components/UiFieldWrapper/UiFieldWrapper.vue +9 -3
  158. package/src/components/UiHorizontalSeparator/UiHorizontalSeparator.vue +2 -2
  159. package/src/components/UiInputCheckbox/CheckboxGlyph.vue +78 -0
  160. package/src/components/UiInputCheckbox/UiInputCheckbox.vue +213 -177
  161. package/src/components/UiInputEmoji/UiInputEmoji.vue +18 -95
  162. package/src/components/UiInputRadio/RadioGlyph.vue +45 -0
  163. package/src/components/UiInputRadio/UiInputRadio.vue +177 -169
  164. package/src/components/UiInputRadio/UiInputRadioGroup.vue +15 -5
  165. package/src/components/UiInputRadio/types.ts +1 -2
  166. package/src/components/UiInputSelect/UiInputSelect.vue +9 -7
  167. package/src/components/UiInputTextarea/UiInputTextarea.vue +20 -35
  168. package/src/components/UiLabel/UiLabel.vue +1 -1
  169. package/src/components/UiListItem/UiListItem.vue +55 -9
  170. package/src/components/UiLoader/UiLoader.vue +4 -20
  171. package/src/components/UiLoader/UiSpinnerIcon.vue +21 -0
  172. package/src/components/UiMenu/UiMenuContent.vue +24 -3
  173. package/src/components/UiMenu/UiMenuGroup.vue +1 -1
  174. package/src/components/UiMenu/UiMenuItem.vue +14 -3
  175. package/src/components/UiMenu/UiMenuSubContent.vue +1 -1
  176. package/src/components/UiMenu/UiMenuSubTrigger.vue +1 -1
  177. package/src/components/UiNestedOptions/UiNestedOptions.vue +6 -0
  178. package/src/components/UiPage/UiPage.vue +190 -32
  179. package/src/components/UiPagination/UiPagination.vue +99 -0
  180. package/src/components/UiPagination/index.ts +2 -0
  181. package/src/components/UiPagination/types.ts +28 -0
  182. package/src/components/UiPane/UiPane.vue +20 -0
  183. package/src/components/UiPane/UiPaneContent.vue +232 -0
  184. package/src/components/UiPane/UiPaneTrigger.vue +14 -0
  185. package/src/components/UiPane/index.ts +12 -0
  186. package/src/components/UiPopover/UiPopoverContent.vue +21 -1
  187. package/src/components/UiProgress/UiProgress.vue +2 -2
  188. package/src/components/UiRating/UiRating.vue +258 -0
  189. package/src/components/UiRating/types.ts +1 -0
  190. package/src/components/UiRoomButton/UiRoomButton.vue +10 -41
  191. package/src/components/UiRoomCountdown/UiRoomCountdown.vue +376 -0
  192. package/src/components/UiShell/UiShell.vue +9 -3
  193. package/src/components/UiSideMenu/UiSideMenu.vue +155 -18
  194. package/src/components/UiSideMenu/UiSideMenuItemContent.vue +20 -16
  195. package/src/components/UiStepper/UiStepper.vue +3 -16
  196. package/src/components/UiStepper/UiStepperDescription.vue +1 -13
  197. package/src/components/UiStepper/UiStepperIndicator.vue +11 -48
  198. package/src/components/UiStepper/UiStepperItem.vue +1 -8
  199. package/src/components/UiStepper/UiStepperSeparator.vue +5 -16
  200. package/src/components/UiStepper/UiStepperTitle.vue +1 -10
  201. package/src/components/UiSwitch/UiSwitch.vue +44 -28
  202. package/src/components/UiTable/UiTable.vue +98 -8
  203. package/src/components/UiTable/UiTableActionsCell.vue +135 -0
  204. package/src/components/UiTable/UiTableActionsHeaderCell.vue +19 -0
  205. package/src/components/UiTable/UiTableBody.vue +16 -1
  206. package/src/components/UiTable/UiTableCell.vue +33 -16
  207. package/src/components/UiTable/UiTableHeader.vue +6 -5
  208. package/src/components/UiTable/UiTableHeaderCell.vue +61 -9
  209. package/src/components/UiTable/UiTableRow.vue +46 -10
  210. package/src/components/UiTable/context.ts +5 -0
  211. package/src/components/UiTable/index.ts +4 -0
  212. package/src/components/UiTabs/UiTabs.vue +56 -61
  213. package/src/components/UiTag/UiTag.types.ts +28 -0
  214. package/src/components/UiTag/UiTag.vue +24 -42
  215. package/src/components/UiTag/index.ts +2 -0
  216. package/src/components/UiTextField/UiTextField.vue +21 -36
  217. package/src/components/UiTooltip/UiTooltipContent.vue +1 -1
  218. package/src/components/UiTooltip/UiTooltipProvider.vue +3 -0
  219. package/src/components/UiTooltip/UiTooltipTrigger.vue +16 -1
  220. package/src/composables/storedThemingApplies.ts +19 -0
  221. package/src/composables/useButtonStyles.ts +9 -13
  222. package/src/composables/useChoiceboxSurface.ts +36 -0
  223. package/src/composables/useContentMode.ts +29 -16
  224. package/src/composables/useControlModes.ts +11 -0
  225. package/src/composables/useControlShape.ts +34 -0
  226. package/src/composables/useFieldFocusEvents.ts +30 -0
  227. package/src/composables/useIconSize.ts +15 -0
  228. package/src/composables/usePullGesture.ts +142 -0
  229. package/src/composables/useThemeAndModes.ts +28 -1
  230. package/src/css/body.css +16 -1
  231. package/src/css/fonts.css +39 -5
  232. package/src/global.d.ts +36 -0
  233. package/src/theming/colorScale.ts +173 -0
  234. package/src/theming/legacyColorScale.ts +58 -0
  235. package/src/theming/presetColors.ts +33 -0
  236. package/src/theming/primitiveTokens.ts +71 -0
  237. package/src/utils/effectiveOpacity.ts +8 -0
  238. package/src/utils/i18n.ts +6 -0
  239. package/src/utils/numbers.ts +1 -1
  240. package/src/utils/responsive.d.ts +14 -0
  241. package/src/utils/tokenColor.ts +9 -0
  242. package/dist/UiTimelineItem.vue_vue_type_style_index_0_lang-LpdvGf5X.mjs +0 -6093
  243. package/dist/a11y-BhF_nl7L.mjs +0 -159
  244. package/dist/i18n-Cktw9hIu.mjs +0 -49
  245. package/dist/toastDSV2-wMh-UMzW.mjs +0 -74
  246. package/dist/types/src/components/UiAvatar/index.d.ts +0 -1
  247. package/dist/types/src/components/UiAvatarList/index.d.ts +0 -1
  248. package/dist/types/src/components/UiBadge/index.d.ts +0 -1
  249. package/dist/types/src/components/UiBanner/index.d.ts +0 -2
  250. package/dist/types/src/components/UiButtonGroup/index.d.ts +0 -3
  251. package/dist/types/src/components/UiCarousel/index.d.ts +0 -2
  252. package/dist/types/src/components/UiColorField/index.d.ts +0 -2
  253. package/dist/types/src/components/UiDialog/MarketplaceHeaderExample.vue.d.ts +0 -28
  254. package/dist/types/src/components/UiDialog/index.d.ts +0 -7
  255. package/dist/types/src/components/UiFieldWrapper/index.d.ts +0 -1
  256. package/dist/types/src/components/UiInputCheckbox/index.d.ts +0 -2
  257. package/dist/types/src/components/UiInputEmoji/index.d.ts +0 -1
  258. package/dist/types/src/components/UiInputRadio/index.d.ts +0 -3
  259. package/dist/types/src/components/UiInputSelect/index.d.ts +0 -2
  260. package/dist/types/src/components/UiInputTextarea/index.d.ts +0 -2
  261. package/dist/types/src/components/UiMenu/index.d.ts +0 -10
  262. package/dist/types/src/components/UiProgress/index.d.ts +0 -2
  263. package/dist/types/src/components/UiSwitch/index.d.ts +0 -2
  264. package/dist/types/src/components/UiTabs/index.d.ts +0 -2
  265. package/dist/types/src/components/UiTimeline/index.d.ts +0 -2
  266. package/dist/types/src/components/UiTooltip/index.d.ts +0 -4
  267. package/dist/types/src/mixins/DynamicAction.d.ts +0 -30
  268. package/dist/useThemeAndModes-BzYwSOEv.mjs +0 -108
  269. package/src/components/UiAvatar/index.js +0 -1
  270. package/src/components/UiAvatarList/index.js +0 -1
  271. package/src/components/UiBadge/index.ts +0 -2
  272. package/src/components/UiBanner/index.ts +0 -2
  273. package/src/components/UiButtonGroup/index.ts +0 -3
  274. package/src/components/UiCarousel/index.ts +0 -2
  275. package/src/components/UiColorField/index.ts +0 -2
  276. package/src/components/UiDialog/MarketplaceHeaderExample.vue +0 -217
  277. package/src/components/UiDialog/index.ts +0 -18
  278. package/src/components/UiFieldWrapper/index.ts +0 -1
  279. package/src/components/UiInputCheckbox/index.ts +0 -2
  280. package/src/components/UiInputEmoji/index.ts +0 -1
  281. package/src/components/UiInputRadio/index.ts +0 -3
  282. package/src/components/UiInputSelect/index.ts +0 -2
  283. package/src/components/UiInputTextarea/index.ts +0 -2
  284. package/src/components/UiMenu/index.ts +0 -10
  285. package/src/components/UiProgress/index.ts +0 -2
  286. package/src/components/UiSwitch/index.ts +0 -2
  287. package/src/components/UiTabs/index.ts +0 -5
  288. package/src/components/UiTag/index.js +0 -1
  289. package/src/components/UiTimeline/index.ts +0 -2
  290. package/src/components/UiTooltip/index.js +0 -4
  291. package/src/mixins/DynamicAction.js +0 -61
@@ -0,0 +1,142 @@
1
+ import { computed, ref, toValue, type MaybeRefOrGetter } from 'vue'
2
+
3
+ interface UsePullGestureOptions {
4
+ /**
5
+ * Pointer travel (px, ascending) at which each stage is reached. An empty
6
+ * list disables the gesture.
7
+ */
8
+ stages: MaybeRefOrGetter<number[]>
9
+ /**
10
+ * Travel (px) up to which the element tracks the pointer 1:1; resistance
11
+ * builds beyond it. Defaults to the last stage.
12
+ */
13
+ resistFrom?: MaybeRefOrGetter<number | undefined>
14
+ /**
15
+ * Visible travel the pulled element tends toward once resisting. Defaults to
16
+ * five times the last stage.
17
+ */
18
+ maxOffset?: MaybeRefOrGetter<number | undefined>
19
+ /** Called on release when at least one stage was reached. */
20
+ onRelease?: (stage: number) => void
21
+ }
22
+
23
+ interface Point {
24
+ x: number
25
+ y: number
26
+ }
27
+
28
+ const REST: Point = { x: 0, y: 0 }
29
+
30
+ // Travel (px) under which a press still counts as a click, not a drag.
31
+ const DRAG_SLOP = 4
32
+
33
+ // Detent at each stage: the element holds still for this much travel past
34
+ // the threshold, then catches up at double speed over the same distance.
35
+ const DETENT = 8
36
+
37
+ function detentLag (distance: number, stages: number[]) {
38
+ return stages.reduce(
39
+ (lag, threshold) => lag + Math.max(0, DETENT - Math.abs(distance - threshold - DETENT)),
40
+ 0,
41
+ )
42
+ }
43
+
44
+ // Free travel up to `free`, then an iOS-style rubber band lets the visible
45
+ // travel approach `max` asymptotically.
46
+ function resist (distance: number, free: number, max: number) {
47
+ if (distance <= free) return distance
48
+ const tail = max - free
49
+ if (tail <= 0) return free
50
+ const beyond = distance - free
51
+ return free + (tail * beyond) / (beyond + tail)
52
+ }
53
+
54
+ /**
55
+ * Pull gesture in any direction with staged thresholds: the element follows
56
+ * the pointer through the stages and resists beyond the last one. Bind the
57
+ * returned pointer handlers to the pulled element and apply `offset` as its
58
+ * translation; on release the state snaps back to rest.
59
+ */
60
+ export function usePullGesture (options: UsePullGestureOptions) {
61
+ const isPulling = ref(false)
62
+ const delta = ref<Point>(REST)
63
+ let pointerId: number | null = null
64
+ let start: Point = REST
65
+ let didDrag = false
66
+
67
+ const stages = computed(() => toValue(options.stages))
68
+ const enabled = computed(() => stages.value.length > 0)
69
+ const lastStage = computed(() => stages.value[stages.value.length - 1] ?? 0)
70
+ const resistFrom = computed(() => toValue(options.resistFrom) ?? lastStage.value)
71
+ const maxOffset = computed(() => toValue(options.maxOffset) ?? lastStage.value * 5)
72
+ const distance = computed(() => Math.hypot(delta.value.x, delta.value.y))
73
+ const stage = computed(() => stages.value.filter(threshold => distance.value >= threshold).length)
74
+ const offset = computed<Point>(() => {
75
+ if (distance.value === 0) return REST
76
+ const eased = distance.value - detentLag(distance.value, stages.value)
77
+ const ratio = resist(eased, resistFrom.value, maxOffset.value) / distance.value
78
+ return { x: delta.value.x * ratio, y: delta.value.y * ratio }
79
+ })
80
+
81
+ function reset () {
82
+ isPulling.value = false
83
+ delta.value = REST
84
+ pointerId = null
85
+ }
86
+
87
+ function onPointerDown (event: PointerEvent) {
88
+ if (!enabled.value || !event.isPrimary || event.button !== 0) return
89
+ pointerId = event.pointerId
90
+ start = { x: event.clientX, y: event.clientY }
91
+ didDrag = false
92
+ isPulling.value = true
93
+ // Keep receiving move/up events once the pointer leaves the element.
94
+ ;(event.currentTarget as Element | null)?.setPointerCapture?.(event.pointerId)
95
+ }
96
+
97
+ function track (event: PointerEvent) {
98
+ delta.value = { x: event.clientX - start.x, y: event.clientY - start.y }
99
+ if (distance.value > DRAG_SLOP) didDrag = true
100
+ }
101
+
102
+ function onPointerMove (event: PointerEvent) {
103
+ if (!isPulling.value || event.pointerId !== pointerId) return
104
+ track(event)
105
+ }
106
+
107
+ function onPointerUp (event: PointerEvent) {
108
+ if (!isPulling.value || event.pointerId !== pointerId) return
109
+ // A fast drag can end past the last delivered move: judge the release position.
110
+ track(event)
111
+ const reached = stage.value
112
+ reset()
113
+ if (reached > 0) options.onRelease?.(reached)
114
+ }
115
+
116
+ function onPointerCancel (event: PointerEvent) {
117
+ if (event.pointerId !== pointerId) return
118
+ reset()
119
+ }
120
+
121
+ /**
122
+ * Whether the press that just ended was a drag. Lets a click handler ignore
123
+ * the click the browser fires after a drag; the next press starts clean.
124
+ */
125
+ function consumeDrag () {
126
+ const wasDrag = didDrag
127
+ didDrag = false
128
+ return wasDrag
129
+ }
130
+
131
+ return {
132
+ enabled,
133
+ isPulling,
134
+ stage,
135
+ offset,
136
+ onPointerDown,
137
+ onPointerMove,
138
+ onPointerUp,
139
+ onPointerCancel,
140
+ consumeDrag,
141
+ }
142
+ }
@@ -1,4 +1,5 @@
1
1
  import { useStorage } from '@vueuse/core'
2
+ import { storedThemingApplies } from './storedThemingApplies'
2
3
  import { computed, getCurrentInstance, ref, watch } from 'vue'
3
4
  import {
4
5
  THEME_LIST,
@@ -124,10 +125,35 @@ export default function useThemeAndModes (
124
125
  list.value.set(selector, { element, modeOverride })
125
126
  }
126
127
 
128
+ /**
129
+ * Drops an element from the watch list. The registries outlive every
130
+ * component that registers with them, so an element that unmounts without
131
+ * this stays referenced — and keeps being written to — for the life of the
132
+ * page.
133
+ *
134
+ * The element is part of the descriptor because a selector is a surface, not
135
+ * an instance: where two instances of one surface overlap (a route leaving
136
+ * while its replacement mounts), the leaving one must not drop the entry the
137
+ * incoming one just registered.
138
+ * @param listType Which registry the element was added to.
139
+ * @param descriptor The selector it was registered under, and the element itself.
140
+ * @returns void
141
+ */
142
+ function removeElementToWatch (
143
+ listType: 'mode' | 'content',
144
+ { selector, element }: { selector: string, element: HTMLElement }
145
+ ) {
146
+ const list = listType === 'mode' ? modeTemplateRefsToWatch : contentTemplateRefsToWatch
147
+ if (list.value.get(selector)?.element === element) {
148
+ list.value.delete(selector)
149
+ }
150
+ }
151
+
127
152
  return {
128
153
  initOnAppMounted,
129
154
  registerWatchers,
130
155
  addElementToWatch,
156
+ removeElementToWatch,
131
157
  updateContentMode,
132
158
 
133
159
  updateTheme,
@@ -214,7 +240,8 @@ const DYNAMIC_TOKENS_STORAGE_KEY = 'customThemeTokens'
214
240
  */
215
241
  function applyStoredDynamicTokens () {
216
242
  try {
217
- if (localStorage.getItem('ls-internal-theming') !== 'true') return
243
+ // Seed tokens are picker output — same consumption rule as stored modes.
244
+ if (!storedThemingApplies()) return
218
245
 
219
246
  const stored = localStorage.getItem(DYNAMIC_TOKENS_STORAGE_KEY)
220
247
  if (!stored) return
package/src/css/body.css CHANGED
@@ -14,6 +14,21 @@
14
14
  line-height: 1.5;
15
15
  font-weight: 400;
16
16
  color: var(--color-grey-700);
17
+ font-feature-settings: 'cv01', 'cv05', 'cv09', 'cv11', 'ss03';
18
+ font-optical-sizing: auto;
19
+ font-kerning: normal;
20
+ font-synthesis: none;
21
+ text-rendering: optimizeLegibility;
22
+ -webkit-font-smoothing: antialiased;
23
+ -moz-osx-font-smoothing: grayscale;
24
+ }
25
+
26
+ h1, h2, h3, h4, h5, h6 {
27
+ text-wrap: balance;
28
+ }
29
+
30
+ p {
31
+ text-wrap: pretty;
17
32
  }
18
33
  }
19
34
 
@@ -34,5 +49,5 @@
34
49
  https://css-tricks.com/almanac/selectors/f/focus-visible/#aa-how-do-browsers-determine-when-something-is-focus-visible
35
50
  */
36
51
  *:focus-visible:not(input, .base-input, textarea, .v-popper__popper, .focus-indicator-none, [contenteditable]) {
37
- @apply ring-2 ring-inset ring-focus-ring;
52
+ @apply focus-ring;
38
53
  }
package/src/css/fonts.css CHANGED
@@ -1,10 +1,44 @@
1
1
  /*
2
- Inter via Google Fonts — kept OUT of the base stylesheet (./style) on
3
- purpose: a third-party font request must be the consumer's choice (GDPR,
4
- self-hosting). Apps that want the quick path import this file once:
2
+ Inter Variable, served from the inter-ui package (OFL-1.1) - kept OUT of
3
+ the base stylesheet (./style) on purpose: loading a font must stay the
4
+ consumer's choice. Apps that want the quick path import this file once:
5
5
 
6
6
  import '@livestorm/ui-vue/fonts'
7
7
 
8
- The monorepo keeps loading it through ui-vue-legacy's style chain.
8
+ The Latin subsets come last so the browser tries them first; it downloads a
9
+ full file only for text they do not cover. Their unicode-range is the
10
+ subset's own character map.
9
11
  */
10
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap');
12
+ @font-face {
13
+ font-family: 'Inter';
14
+ font-style: normal;
15
+ font-weight: 100 900;
16
+ font-display: swap;
17
+ src: url('inter-ui/variable/InterVariable.woff2') format('woff2');
18
+ }
19
+
20
+ @font-face {
21
+ font-family: 'Inter';
22
+ font-style: italic;
23
+ font-weight: 100 900;
24
+ font-display: swap;
25
+ src: url('inter-ui/variable/InterVariable-Italic.woff2') format('woff2');
26
+ }
27
+
28
+ @font-face {
29
+ font-family: 'Inter';
30
+ font-style: normal;
31
+ font-weight: 100 900;
32
+ font-display: swap;
33
+ src: url('inter-ui/variable-latin/InterVariable-subset.woff2') format('woff2');
34
+ unicode-range: U+0000, U+0020-007E, U+00A0-00AC, U+00AE-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-200B, U+2010-2027, U+202F-2055, U+2057, U+205F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+FEFF;
35
+ }
36
+
37
+ @font-face {
38
+ font-family: 'Inter';
39
+ font-style: italic;
40
+ font-weight: 100 900;
41
+ font-display: swap;
42
+ src: url('inter-ui/variable-latin/InterVariable-Italic-subset.woff2') format('woff2');
43
+ unicode-range: U+0000, U+0020-007E, U+00A0-00AC, U+00AE-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-200B, U+2010-2027, U+202F-2055, U+2057, U+205F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+FEFF;
44
+ }
package/src/global.d.ts CHANGED
@@ -13,18 +13,26 @@ import type UiButtonGroup from './components/UiButtonGroup/UiButtonGroup.vue'
13
13
  import type UiButtonGroupItem from './components/UiButtonGroup/UiButtonGroupItem.vue'
14
14
  import type UiCard from './components/UiCard/UiCard.vue'
15
15
  import type UiCarousel from './components/UiCarousel/UiCarousel.vue'
16
+ import type UiChoice from './components/UiChoice/UiChoice.vue'
16
17
  import type UiColorField from './components/UiColorField/UiColorField.vue'
17
18
  import type UiColorPicker from './components/UiColorPicker/UiColorPicker.vue'
19
+ import type UiCombobox from './components/UiCombobox/UiCombobox.vue'
20
+ import type UiDataTable from './components/UiDataTable/UiDataTable.vue'
18
21
  import type UiDialog from './components/UiDialog/UiDialog.vue'
19
22
  import type UiDialogClose from './components/UiDialog/UiDialogClose.vue'
20
23
  import type UiDialogContent from './components/UiDialog/UiDialogContent.vue'
21
24
  import type UiDialogFooter from './components/UiDialog/UiDialogFooter.vue'
22
25
  import type UiDialogTrigger from './components/UiDialog/UiDialogTrigger.vue'
26
+ import type UiDrawer from './components/UiDrawer/UiDrawer.vue'
27
+ import type UiDrawerContent from './components/UiDrawer/UiDrawerContent.vue'
28
+ import type UiDrawerTrigger from './components/UiDrawer/UiDrawerTrigger.vue'
23
29
  import type UiDropdown from './components/UiDropdown/UiDropdown.vue'
24
30
  import type UiDropdownContent from './components/UiDropdown/UiDropdownContent.vue'
31
+ import type UiDropdownGroup from './components/UiDropdown/UiDropdownGroup.vue'
25
32
  import type UiDropdownItem from './components/UiDropdown/UiDropdownItem.vue'
26
33
  import type UiDropdownSeparator from './components/UiDropdown/UiDropdownSeparator.vue'
27
34
  import type UiDropdownTrigger from './components/UiDropdown/UiDropdownTrigger.vue'
35
+ import type UiEmojiPickerList from './components/UiEmojiPickerList/UiEmojiPickerList.vue'
28
36
  import type UiEmptyState from './components/UiEmptyState/UiEmptyState.vue'
29
37
  import type UiFieldWrapper from './components/UiFieldWrapper/UiFieldWrapper.vue'
30
38
  import type UiFormField from './components/UiForm/UiFormField.vue'
@@ -39,6 +47,7 @@ import type UiLabel from './components/UiLabel/UiLabel.vue'
39
47
  import type UiList from './components/UiList/UiList.vue'
40
48
  import type UiListItem from './components/UiListItem/UiListItem.vue'
41
49
  import type UiLoader from './components/UiLoader/UiLoader.vue'
50
+ import type UiSpinnerIcon from './components/UiLoader/UiSpinnerIcon.vue'
42
51
  import type UiLogo from './components/UiLogo/UiLogo.vue'
43
52
  import type UiMenu from './components/UiMenu/UiMenu.vue'
44
53
  import type UiMenuContent from './components/UiMenu/UiMenuContent.vue'
@@ -50,14 +59,21 @@ import type UiMenuSub from './components/UiMenu/UiMenuSub.vue'
50
59
  import type UiMenuSubContent from './components/UiMenu/UiMenuSubContent.vue'
51
60
  import type UiMenuSubTrigger from './components/UiMenu/UiMenuSubTrigger.vue'
52
61
  import type UiMenuTrigger from './components/UiMenu/UiMenuTrigger.vue'
62
+ import type UiNestedOptions from './components/UiNestedOptions/UiNestedOptions.vue'
53
63
  import type UiPage from './components/UiPage/UiPage.vue'
54
64
  import type UiPageHeader from './components/UiPageHeader/UiPageHeader.vue'
65
+ import type UiPagination from './components/UiPagination/UiPagination.vue'
66
+ import type UiPane from './components/UiPane/UiPane.vue'
67
+ import type UiPaneContent from './components/UiPane/UiPaneContent.vue'
68
+ import type UiPaneTrigger from './components/UiPane/UiPaneTrigger.vue'
55
69
  import type UiPopover from './components/UiPopover/UiPopover.vue'
56
70
  import type UiPopoverAnchor from './components/UiPopover/UiPopoverAnchor.vue'
57
71
  import type UiPopoverContent from './components/UiPopover/UiPopoverContent.vue'
58
72
  import type UiPopoverTrigger from './components/UiPopover/UiPopoverTrigger.vue'
59
73
  import type UiProgress from './components/UiProgress/UiProgress.vue'
74
+ import type UiRating from './components/UiRating/UiRating.vue'
60
75
  import type UiRoomButton from './components/UiRoomButton/UiRoomButton.vue'
76
+ import type UiRoomCountdown from './components/UiRoomCountdown/UiRoomCountdown.vue'
61
77
  import type UiShell from './components/UiShell/UiShell.vue'
62
78
  import type UiSideMenu from './components/UiSideMenu/UiSideMenu.vue'
63
79
  import type UiSideMenuBottom from './components/UiSideMenu/UiSideMenuBottom.vue'
@@ -72,6 +88,8 @@ import type UiStepperSeparator from './components/UiStepper/UiStepperSeparator.v
72
88
  import type UiStepperTitle from './components/UiStepper/UiStepperTitle.vue'
73
89
  import type UiSwitch from './components/UiSwitch/UiSwitch.vue'
74
90
  import type UiTable from './components/UiTable/UiTable.vue'
91
+ import type UiTableActionsCell from './components/UiTable/UiTableActionsCell.vue'
92
+ import type UiTableActionsHeaderCell from './components/UiTable/UiTableActionsHeaderCell.vue'
75
93
  import type UiTableBody from './components/UiTable/UiTableBody.vue'
76
94
  import type UiTableCell from './components/UiTable/UiTableCell.vue'
77
95
  import type UiTableHeader from './components/UiTable/UiTableHeader.vue'
@@ -110,18 +128,26 @@ declare module 'vue' {
110
128
  UiButtonGroupItem: typeof UiButtonGroupItem
111
129
  UiCard: typeof UiCard
112
130
  UiCarousel: typeof UiCarousel
131
+ UiChoice: typeof UiChoice
113
132
  UiColorField: typeof UiColorField
114
133
  UiColorPicker: typeof UiColorPicker
134
+ UiCombobox: typeof UiCombobox
135
+ UiDataTable: typeof UiDataTable
115
136
  UiDialog: typeof UiDialog
116
137
  UiDialogClose: typeof UiDialogClose
117
138
  UiDialogContent: typeof UiDialogContent
118
139
  UiDialogFooter: typeof UiDialogFooter
119
140
  UiDialogTrigger: typeof UiDialogTrigger
141
+ UiDrawer: typeof UiDrawer
142
+ UiDrawerContent: typeof UiDrawerContent
143
+ UiDrawerTrigger: typeof UiDrawerTrigger
120
144
  UiDropdown: typeof UiDropdown
121
145
  UiDropdownContent: typeof UiDropdownContent
146
+ UiDropdownGroup: typeof UiDropdownGroup
122
147
  UiDropdownItem: typeof UiDropdownItem
123
148
  UiDropdownSeparator: typeof UiDropdownSeparator
124
149
  UiDropdownTrigger: typeof UiDropdownTrigger
150
+ UiEmojiPickerList: typeof UiEmojiPickerList
125
151
  UiEmptyState: typeof UiEmptyState
126
152
  UiFieldWrapper: typeof UiFieldWrapper
127
153
  UiFormField: typeof UiFormField
@@ -136,6 +162,7 @@ declare module 'vue' {
136
162
  UiList: typeof UiList
137
163
  UiListItem: typeof UiListItem
138
164
  UiLoader: typeof UiLoader
165
+ UiSpinnerIcon: typeof UiSpinnerIcon
139
166
  UiLogo: typeof UiLogo
140
167
  UiMenu: typeof UiMenu
141
168
  UiMenuContent: typeof UiMenuContent
@@ -147,14 +174,21 @@ declare module 'vue' {
147
174
  UiMenuSubContent: typeof UiMenuSubContent
148
175
  UiMenuSubTrigger: typeof UiMenuSubTrigger
149
176
  UiMenuTrigger: typeof UiMenuTrigger
177
+ UiNestedOptions: typeof UiNestedOptions
150
178
  UiPage: typeof UiPage
151
179
  UiPageHeader: typeof UiPageHeader
180
+ UiPagination: typeof UiPagination
181
+ UiPane: typeof UiPane
182
+ UiPaneContent: typeof UiPaneContent
183
+ UiPaneTrigger: typeof UiPaneTrigger
152
184
  UiPopover: typeof UiPopover
153
185
  UiPopoverAnchor: typeof UiPopoverAnchor
154
186
  UiPopoverContent: typeof UiPopoverContent
155
187
  UiPopoverTrigger: typeof UiPopoverTrigger
156
188
  UiProgress: typeof UiProgress
189
+ UiRating: typeof UiRating
157
190
  UiRoomButton: typeof UiRoomButton
191
+ UiRoomCountdown: typeof UiRoomCountdown
158
192
  UiShell: typeof UiShell
159
193
  UiSideMenu: typeof UiSideMenu
160
194
  UiSideMenuBottom: typeof UiSideMenuBottom
@@ -169,6 +203,8 @@ declare module 'vue' {
169
203
  UiStepperTitle: typeof UiStepperTitle
170
204
  UiSwitch: typeof UiSwitch
171
205
  UiTable: typeof UiTable
206
+ UiTableActionsCell: typeof UiTableActionsCell
207
+ UiTableActionsHeaderCell: typeof UiTableActionsHeaderCell
172
208
  UiTableBody: typeof UiTableBody
173
209
  UiTableCell: typeof UiTableCell
174
210
  UiTableHeader: typeof UiTableHeader
@@ -0,0 +1,173 @@
1
+ import { parse, converter, clampChroma, formatHex } from 'culori'
2
+
3
+ // --- OKLCH lightness & chroma targets ---
4
+ // Calibrated to reproduce the static ui-tokens palette exactly at the
5
+ // Livestorm seed (#3073ff = brand 500), pinned by colorScale.spec.ts.
6
+ // l = target lightness, cf = chroma factor (x seed chroma), hOff = hue
7
+ // offset, so other seeds keep the palette's hue drift.
8
+ //
9
+ // 500 is a fixed rung like every other step (its l happens to be the
10
+ // Livestorm seed's own). The seed only contributes hue and chroma, otherwise
11
+ // a light or dark seed would break the light-to-dark ordering of the ramp.
12
+
13
+ export const BRAND_SCALE = {
14
+ 100: { l: 0.9823, cf: 0.0376, hOff: 8.79 },
15
+ 200: { l: 0.9577, cf: 0.0906, hOff: 4.75 },
16
+ 300: { l: 0.9290, cf: 0.1542, hOff: 1.04 },
17
+ 400: { l: 0.7250, cf: 0.6476, hOff: 0.44 },
18
+ 500: { l: 0.5961, cf: 1, hOff: 0 },
19
+ 600: { l: 0.4428, cf: 0.9451, hOff: 0.66 },
20
+ 700: { l: 0.3137, cf: 0.7551, hOff: 0.89 },
21
+ 800: { l: 0.2729, cf: 0.6492, hOff: 0.79 },
22
+ 900: { l: 0.2412, cf: 0.5471, hOff: 0.34 },
23
+ } as const
24
+
25
+ export const NEUTRAL_SCALE = {
26
+ lightBg: { l: 0.9904, cf: 0.0206, hOff: -4.21 },
27
+ 100: { l: 0.9760, cf: 0.0318, hOff: 6.01 },
28
+ 200: { l: 0.9549, cf: 0.0452, hOff: 4.82 },
29
+ 300: { l: 0.9278, cf: 0.0646, hOff: 5.95 },
30
+ 400: { l: 0.7065, cf: 0.2241, hOff: 5.20 },
31
+ 500: { l: 0.5686, cf: 0.3199, hOff: 4.13 },
32
+ 600: { l: 0.4480, cf: 0.3622, hOff: 3.67 },
33
+ // 650/675: dark elevated-surface primitives (surface-neutral-elevated-200/100)
34
+ // from the 2026-09 Supernova sync; calibrated like the rest of the ramp.
35
+ 650: { l: 0.4091, cf: 0.3771, hOff: 3.65 },
36
+ 675: { l: 0.3584, cf: 0.3331, hOff: 2.93 },
37
+ 700: { l: 0.3109, cf: 0.3019, hOff: 2.71 },
38
+ 800: { l: 0.2609, cf: 0.2425, hOff: 3.52 },
39
+ 900: { l: 0.2219, cf: 0.1955, hOff: 4.56 },
40
+ darkBg: { l: 0.1847, cf: 0.1470, hOff: 4.16 },
41
+ } as const
42
+
43
+ export const ALPHA_STEPS = [0.04, 0.08, 0.12, 0.2, 0.32, 0.52, 0.72, 0.84, 0.92] as const
44
+
45
+ // --- Types ---
46
+
47
+ export interface ScaleStep {
48
+ l: number
49
+ cf: number
50
+ hOff: number
51
+ }
52
+
53
+ export interface OklchColor {
54
+ mode: 'oklch'
55
+ l: number
56
+ c: number
57
+ h: number
58
+ }
59
+
60
+ export interface BrandScale {
61
+ 100: string; 200: string; 300: string
62
+ 400: string; 500: string; 600: string
63
+ 700: string; 800: string; 900: string
64
+ }
65
+
66
+ interface NeutralScale {
67
+ lightBg: string; darkBg: string
68
+ 100: string; 200: string; 300: string
69
+ 400: string; 500: string; 600: string
70
+ 650: string; 675: string
71
+ 700: string; 800: string; 900: string
72
+ }
73
+
74
+ // --- Color math ---
75
+
76
+ const toOklch = converter('oklch')
77
+ const toRgb = converter('rgb')
78
+
79
+ export function seedToOklch(hex: string): OklchColor {
80
+ const parsed = parse(hex)
81
+ if (!parsed) throw new Error(`Invalid color: ${hex}`)
82
+ const oklch = toOklch(parsed)
83
+ return {
84
+ mode: 'oklch',
85
+ l: oklch.l ?? 0,
86
+ c: oklch.c ?? 0,
87
+ h: oklch.h ?? 0,
88
+ }
89
+ }
90
+
91
+ // Recover the seed (hue + chroma) whose brand rung `step` renders as `hex` —
92
+ // the inverse of stepOklch: undo the rung's chroma factor and hue offset.
93
+ // Generation reads only hue + chroma, so lightness is carried through but
94
+ // irrelevant. NOTE: this round-trips losslessly only for rungs whose forward
95
+ // transform stays in sRGB gamut — the cf≈1 rungs (400/500/600). Light/dark
96
+ // rungs clamp on the way out and cannot be inverted exactly, hence the narrow
97
+ // step type.
98
+ export function seedFromBrandStep(hex: string, step: 400 | 500 | 600): OklchColor {
99
+ const seed = seedToOklch(hex)
100
+ const { cf, hOff } = BRAND_SCALE[step]
101
+ return { mode: 'oklch', l: seed.l, c: seed.c / cf, h: seed.h - hOff }
102
+ }
103
+
104
+ function clampedOklch(seed: OklchColor, l: number, cf: number, hue: number = seed.h): OklchColor {
105
+ const clamped = clampChroma({ mode: 'oklch', l, c: seed.c * cf, h: hue }, 'oklch')
106
+ return { mode: 'oklch', l: clamped.l, c: clamped.c, h: clamped.h ?? hue }
107
+ }
108
+
109
+ export function oklchToHex(color: OklchColor): string {
110
+ return formatHex(color)
111
+ }
112
+
113
+ // sRGB string forms matching the ui-tokens convention (rgb / rgba). Applied
114
+ // app tokens use these so they line up exactly with the static palette and the
115
+ // hex swatch previews, instead of round-tripping through the browser's own
116
+ // oklch pipeline (which can quantize a rung differently).
117
+ function rgbChannels(color: OklchColor): [number, number, number] {
118
+ const { r, g, b } = toRgb(color)
119
+ const to255 = (v: number) => Math.round(Math.min(1, Math.max(0, v)) * 255)
120
+ return [to255(r), to255(g), to255(b)]
121
+ }
122
+
123
+ export function oklchToRgb(color: OklchColor): string {
124
+ const [r, g, b] = rgbChannels(color)
125
+ return `rgb(${r}, ${g}, ${b})`
126
+ }
127
+
128
+ export function oklchToRgba(color: OklchColor, alpha: number): string {
129
+ const [r, g, b] = rgbChannels(color)
130
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`
131
+ }
132
+
133
+ // --- Scale generators ---
134
+ // One pair of helpers drives every ramp (brand, neutral, legacy, email): a
135
+ // scale lives in its {l, cf, hOff} table and is applied to the seed here, so
136
+ // there is a single place that turns a table into colors.
137
+
138
+ export function stepOklch(seed: OklchColor, target: ScaleStep): OklchColor {
139
+ return clampedOklch(seed, target.l, target.cf, seed.h + target.hOff)
140
+ }
141
+
142
+ // Object.keys() widens to string[]; the cast restores the table's key type.
143
+ export function scaleToOklch<K extends PropertyKey>(
144
+ seed: OklchColor,
145
+ table: Record<K, ScaleStep>,
146
+ ): Record<K, OklchColor> {
147
+ const out = {} as Record<K, OklchColor>
148
+ for (const key of Object.keys(table) as unknown as K[]) {
149
+ out[key] = stepOklch(seed, table[key])
150
+ }
151
+ return out
152
+ }
153
+
154
+ export function scaleToHex<K extends PropertyKey>(
155
+ seed: OklchColor,
156
+ table: Record<K, ScaleStep>,
157
+ ): Record<K, string> {
158
+ const out = {} as Record<K, string>
159
+ for (const key of Object.keys(table) as unknown as K[]) {
160
+ out[key] = oklchToHex(stepOklch(seed, table[key]))
161
+ }
162
+ return out
163
+ }
164
+
165
+ export const generateBrandOklch = (seed: OklchColor): Record<keyof BrandScale, OklchColor> =>
166
+ scaleToOklch(seed, BRAND_SCALE)
167
+
168
+ export const generateNeutralOklch = (seed: OklchColor): Record<keyof NeutralScale, OklchColor> =>
169
+ scaleToOklch(seed, NEUTRAL_SCALE)
170
+
171
+ export const generateBrandScale = (seed: OklchColor): BrandScale => scaleToHex(seed, BRAND_SCALE)
172
+
173
+ export const generateNeutralScale = (seed: OklchColor): NeutralScale => scaleToHex(seed, NEUTRAL_SCALE)
@@ -0,0 +1,58 @@
1
+ import { scaleToOklch, scaleToHex, type OklchColor } from './colorScale'
2
+
3
+ // --- Legacy OKLCH targets ---
4
+ // Calibrated to reproduce the static legacy palette (--color-livestorm-*,
5
+ // --color-grey-*) exactly at the Livestorm seed (#3073ff). Same l/cf/hOff
6
+ // scheme as colorScale.ts.
7
+
8
+ const LEGACY_BRAND_SCALE = {
9
+ 100: { l: 0.9673, cf: 0.0697, hOff: 7.45 },
10
+ 200: { l: 0.9247, cf: 0.1567, hOff: 5.10 },
11
+ 300: { l: 0.8398, cf: 0.3526, hOff: 3.60 },
12
+ 400: { l: 0.7232, cf: 0.6433, hOff: 3.08 },
13
+ // no 500 in the legacy palette
14
+ 600: { l: 0.5316, cf: 1.1786, hOff: 0.66 },
15
+ 700: { l: 0.4428, cf: 0.9453, hOff: 0.66 },
16
+ 800: { l: 0.3595, cf: 0.8656, hOff: 1.67 },
17
+ } as const
18
+
19
+ const LEGACY_NEUTRAL_SCALE = {
20
+ 100: { l: 0.9759, cf: 0.0132, hOff: 2.00 },
21
+ 200: { l: 0.9469, cf: 0.0269, hOff: -22.72 },
22
+ 300: { l: 0.8605, cf: 0.0702, hOff: -27.13 },
23
+ 600: { l: 0.5258, cf: 0.1230, hOff: -22.05 },
24
+ 700: { l: 0.4634, cf: 0.1139, hOff: -20.73 },
25
+ 900: { l: 0.2831, cf: 0.0610, hOff: -33.07 },
26
+ } as const
27
+
28
+ // --- Types ---
29
+
30
+ type LegacyBrandStep = 100 | 200 | 300 | 400 | 600 | 700 | 800
31
+ type LegacyNeutralStep = 100 | 200 | 300 | 600 | 700 | 900
32
+
33
+ interface LegacyBrandScale {
34
+ 100: string; 200: string; 300: string; 400: string
35
+ 600: string; 700: string; 800: string
36
+ }
37
+
38
+ interface LegacyNeutralScale {
39
+ 100: string; 200: string; 300: string
40
+ 600: string; 700: string; 900: string
41
+ }
42
+
43
+ export const LEGACY_BRAND_STEPS: readonly LegacyBrandStep[] = [100, 200, 300, 400, 600, 700, 800] as const
44
+ export const LEGACY_NEUTRAL_STEPS: readonly LegacyNeutralStep[] = [100, 200, 300, 600, 700, 900] as const
45
+
46
+ // --- Generators ---
47
+
48
+ export const generateLegacyBrandOklch = (seed: OklchColor): Record<LegacyBrandStep, OklchColor> =>
49
+ scaleToOklch(seed, LEGACY_BRAND_SCALE)
50
+
51
+ export const generateLegacyNeutralOklch = (seed: OklchColor): Record<LegacyNeutralStep, OklchColor> =>
52
+ scaleToOklch(seed, LEGACY_NEUTRAL_SCALE)
53
+
54
+ export const generateLegacyBrandScale = (seed: OklchColor): LegacyBrandScale =>
55
+ scaleToHex(seed, LEGACY_BRAND_SCALE)
56
+
57
+ export const generateLegacyNeutralScale = (seed: OklchColor): LegacyNeutralScale =>
58
+ scaleToHex(seed, LEGACY_NEUTRAL_SCALE)
@@ -0,0 +1,33 @@
1
+ import { seedToOklch, generateBrandScale } from './colorScale'
2
+
3
+ const PRESET_COLORS = [
4
+ '#9b6dd7', '#4829c7', '#0b42c3', '#1e7fb8',
5
+ '#1ba8a0', '#0e7a5e', '#2d8c4e', '#5a7a11',
6
+ '#8a7b12', '#b0780e', '#d05a3a', '#a12244',
7
+ '#c41560', '#d436a8', '#9b21c4', '#6321a8',
8
+ ] as const
9
+
10
+ interface PresetSwatch {
11
+ seed: string
12
+ light: string
13
+ dark: string
14
+ }
15
+
16
+ export const presetSwatches: PresetSwatch[] = PRESET_COLORS.map((hex) => {
17
+ const brand = generateBrandScale(seedToOklch(hex))
18
+ return { seed: hex, light: brand[400], dark: brand[600] }
19
+ })
20
+
21
+ export interface BrandedPalette extends PresetSwatch {
22
+ name: string
23
+ }
24
+
25
+ // Branded workspace palettes from the Email Templates V2 figma, mirrored by
26
+ // @livestorm/ui-tokens (supernova: base = Livestorm, customer1/2/3).
27
+ // seed = brand-500, light = brand-400, dark = brand-600.
28
+ export const brandedPalettes: BrandedPalette[] = [
29
+ { name: 'Livestorm', seed: '#3073ff', light: '#76a4ff', dark: '#0b42c3' },
30
+ { name: 'Customer 1', seed: '#d04134', light: '#e5837d', dark: '#9e2114' },
31
+ { name: 'Customer 2', seed: '#1a8290', light: '#29b0c2', dark: '#115d67' },
32
+ { name: 'Customer 3', seed: '#9a51e2', light: '#bd8aef', dark: '#7420c9' },
33
+ ]