@leavepulse/ui 0.29.4 → 0.30.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 (43) hide show
  1. package/dist/assets/LpChart-CzgCGaAA.css +8 -0
  2. package/dist/chunks/LpActionBar-CxmBZpGu.js +173 -0
  3. package/dist/chunks/{LpAppShell-BJk3b-u4.js → LpAppShell-DnocEJAu.js} +1 -1
  4. package/dist/chunks/{LpCodeBlock-rS_6mO3D.js → LpCodeBlock-BWX1KZwk.js} +7 -13
  5. package/dist/chunks/LpColorPicker-CFC6iaNK.js +491 -0
  6. package/dist/chunks/{LpDisclosure-BnwP4Q-b.js → LpDisclosure-C1_YkMrd.js} +8 -2
  7. package/dist/chunks/{LpLogViewer-CVaGfTF_.js → LpLogViewer-TSRyt3t9.js} +3 -6
  8. package/dist/chunks/{LpSidebar-C_Mr-JPI.js → LpSidebar-CWSbczWi.js} +11 -2
  9. package/dist/component-names.d.ts +1 -1
  10. package/dist/components/LpActionBar.vue.d.ts +84 -0
  11. package/dist/components/LpActionBar.vue.js +4 -0
  12. package/dist/components/LpAppShell.vue.js +1 -1
  13. package/dist/components/LpChart.vue.js +814 -2
  14. package/dist/components/LpCodeBlock.vue.js +1 -1
  15. package/dist/components/LpColorPicker.vue.d.ts +124 -0
  16. package/dist/components/LpColorPicker.vue.js +4 -0
  17. package/dist/components/LpDisclosure.vue.js +1 -1
  18. package/dist/components/LpLogViewer.vue.js +1 -1
  19. package/dist/components/LpNotificationBell.vue.d.ts +2 -2
  20. package/dist/components/LpSidebar.vue.d.ts +21 -0
  21. package/dist/components/LpSidebar.vue.js +1 -1
  22. package/dist/components/actionBar.d.ts +19 -0
  23. package/dist/components/actionBar.js +31 -0
  24. package/dist/components/color.d.ts +65 -0
  25. package/dist/components/color.js +158 -0
  26. package/dist/composables/useClipboard.d.ts +46 -0
  27. package/dist/composables/useClipboard.js +28 -0
  28. package/dist/index.d.ts +8 -0
  29. package/dist/index.js +154 -135
  30. package/package.json +1 -1
  31. package/src/component-names.ts +2 -0
  32. package/src/components/LpActionBar.vue +268 -0
  33. package/src/components/LpChart.vue +337 -63
  34. package/src/components/LpCodeBlock.vue +5 -10
  35. package/src/components/LpColorPicker.vue +784 -0
  36. package/src/components/LpDisclosure.vue +17 -2
  37. package/src/components/LpLogViewer.vue +5 -8
  38. package/src/components/LpSidebar.vue +42 -6
  39. package/src/components/actionBar.ts +73 -0
  40. package/src/components/color.ts +257 -0
  41. package/src/composables/useClipboard.ts +107 -0
  42. package/src/index.ts +21 -0
  43. package/dist/chunks/LpChart-Bmw7y34Z.js +0 -672
package/dist/index.js CHANGED
@@ -2,84 +2,88 @@ import { _ } from "./chunks/LayoutCanvas-Dd20boUG.js";
2
2
  import { _ as _2 } from "./chunks/LpAddressInput-Bk6D8Bcy.js";
3
3
  import { _ as _3 } from "./chunks/LpMapPicker-CXvoTrAe.js";
4
4
  import { OSM_TILES, createNominatimProvider, emptyAddress, formatAddress, project, tileUrl, unproject } from "./components/address.js";
5
- import { _ as _4 } from "./chunks/LpAlert-BAYHDSS1.js";
6
- import { _ as _5 } from "./chunks/LpAppShell-BJk3b-u4.js";
7
- import { _ as _6 } from "./chunks/LpAutocomplete-DExlIZe_.js";
8
- import { _ as _7 } from "./chunks/LpAvatar-D3l4nabg.js";
9
- import { _ as _8 } from "./chunks/LpBadge-BPodVcJa.js";
10
- import { _ as _9 } from "./chunks/LpBreadcrumbs-5uvViCXi.js";
11
- import { _ as _10 } from "./chunks/LpButton-5VXXCjwF.js";
12
- import { _ as _11 } from "./chunks/LpCalendar-B7ZL0ty3.js";
13
- import { _ as _12 } from "./chunks/LpCard-D0M2ZCnB.js";
14
- import { _ as _13 } from "./chunks/LpChart-Bmw7y34Z.js";
5
+ import { _ as _4 } from "./chunks/LpActionBar-CxmBZpGu.js";
6
+ import { _ as _5 } from "./chunks/LpAlert-BAYHDSS1.js";
7
+ import { _ as _6 } from "./chunks/LpAppShell-DnocEJAu.js";
8
+ import { _ as _7 } from "./chunks/LpAutocomplete-DExlIZe_.js";
9
+ import { _ as _8 } from "./chunks/LpAvatar-D3l4nabg.js";
10
+ import { _ as _9 } from "./chunks/LpBadge-BPodVcJa.js";
11
+ import { _ as _10 } from "./chunks/LpBreadcrumbs-5uvViCXi.js";
12
+ import { _ as _11 } from "./chunks/LpButton-5VXXCjwF.js";
13
+ import { _ as _12 } from "./chunks/LpCalendar-B7ZL0ty3.js";
14
+ import { _ as _13 } from "./chunks/LpCard-D0M2ZCnB.js";
15
+ import { default as default2 } from "./components/LpChart.vue.js";
15
16
  import { CHART_PALETTE, axisTicks, formatTick, niceScale, resolveSlices } from "./components/chart.js";
16
17
  import { _ as _14 } from "./chunks/LpCheckbox-BO0zuuPh.js";
17
- import { _ as _15 } from "./chunks/LpCodeBlock-rS_6mO3D.js";
18
- import { default as default2 } from "./components/LpCommandPalette.vue.js";
19
- import { _ as _16 } from "./chunks/LpConfirmDialog-CjOGcj1e.js";
20
- import { _ as _17 } from "./chunks/LpContextMenu-B6WBLleS.js";
21
- import { _ as _18 } from "./chunks/LpDatePicker-dY2q4dr4.js";
22
- import { _ as _19 } from "./chunks/LpDisclosure-BnwP4Q-b.js";
23
- import { _ as _20 } from "./chunks/LpDivider-CUX46KR0.js";
24
- import { _ as _21 } from "./chunks/LpDrawer-DAJN5rGK.js";
25
- import { _ as _22 } from "./chunks/LpDropdownMenu-DJpoqRCb.js";
26
- import { default as default3 } from "./components/LpDropzone.vue.js";
27
- import { _ as _23 } from "./chunks/LpEmptyState-BV3q7Qyi.js";
28
- import { _ as _24 } from "./chunks/LpFileTree-DfFqY__1.js";
18
+ import { _ as _15 } from "./chunks/LpCodeBlock-BWX1KZwk.js";
19
+ import { _ as _16 } from "./chunks/LpColorPicker-CFC6iaNK.js";
20
+ import { DEFAULT_SWATCHES, clampHsv, formatColor, hsvToHsl, hsvToRgb, isLight, normalizeHue, opaqueCss, parseColor, parseHex, rgbToHex, rgbToHsv } from "./components/color.js";
21
+ import { default as default3 } from "./components/LpCommandPalette.vue.js";
22
+ import { _ as _17 } from "./chunks/LpConfirmDialog-CjOGcj1e.js";
23
+ import { _ as _18 } from "./chunks/LpContextMenu-B6WBLleS.js";
24
+ import { _ as _19 } from "./chunks/LpDatePicker-dY2q4dr4.js";
25
+ import { _ as _20 } from "./chunks/LpDisclosure-C1_YkMrd.js";
26
+ import { _ as _21 } from "./chunks/LpDivider-CUX46KR0.js";
27
+ import { _ as _22 } from "./chunks/LpDrawer-DAJN5rGK.js";
28
+ import { _ as _23 } from "./chunks/LpDropdownMenu-DJpoqRCb.js";
29
+ import { default as default4 } from "./components/LpDropzone.vue.js";
30
+ import { _ as _24 } from "./chunks/LpEmptyState-BV3q7Qyi.js";
31
+ import { _ as _25 } from "./chunks/LpFileTree-DfFqY__1.js";
29
32
  import { ancestorIds, checkStateOf, checkedCount, computeStats, fileIcon, formatModified, formatSize, sortNodes, subtreeIds, subtreeSize } from "./components/fileTree.js";
30
- import { _ as _25 } from "./chunks/LpFormField-fV_2ZdCk.js";
31
- import { _ as _26 } from "./chunks/LpIcon-CCnX5_2j.js";
32
- import { default as default4 } from "./components/LpImageEditor.vue.js";
33
- import { _ as _27 } from "./chunks/LpInfraNode-Ds_8SZgN.js";
34
- import { _ as _28 } from "./chunks/LpInput-CYcTL9gj.js";
35
- import { _ as _29 } from "./chunks/LpLightbox-Ci35Y_xP.js";
33
+ import { _ as _26 } from "./chunks/LpFormField-fV_2ZdCk.js";
34
+ import { _ as _27 } from "./chunks/LpIcon-CCnX5_2j.js";
35
+ import { default as default5 } from "./components/LpImageEditor.vue.js";
36
+ import { _ as _28 } from "./chunks/LpInfraNode-Ds_8SZgN.js";
37
+ import { _ as _29 } from "./chunks/LpInput-CYcTL9gj.js";
38
+ import { _ as _30 } from "./chunks/LpLightbox-Ci35Y_xP.js";
36
39
  import { fileNameOf } from "./components/lightbox.js";
37
- import { _ as _30 } from "./chunks/LpLink-BHZ1CLlF.js";
38
- import { _ as _31 } from "./chunks/LpLogViewer-CVaGfTF_.js";
39
- import { _ as _32 } from "./chunks/LpModal-D_o-56To.js";
40
- import { _ as _33 } from "./chunks/LpNotificationBell-CTsIcPqX.js";
41
- import { _ as _34 } from "./chunks/LpNumberField-NPE8Bumx.js";
42
- import { _ as _35 } from "./chunks/LpNumberFlow-amWYaeuA.js";
43
- import { _ as _36 } from "./chunks/LpOtpInput-DqaT0Z75.js";
44
- import { _ as _37 } from "./chunks/LpPagination-CeGuNe_q.js";
45
- import { _ as _38 } from "./chunks/LpPasswordInput-DrYE4J5d.js";
46
- import { _ as _39 } from "./chunks/LpPie-BL1vTDSM.js";
47
- import { _ as _40 } from "./chunks/LpPhoneInput-NXXjciJJ.js";
40
+ import { _ as _31 } from "./chunks/LpLink-BHZ1CLlF.js";
41
+ import { _ as _32 } from "./chunks/LpLogViewer-TSRyt3t9.js";
42
+ import { _ as _33 } from "./chunks/LpModal-D_o-56To.js";
43
+ import { _ as _34 } from "./chunks/LpNotificationBell-CTsIcPqX.js";
44
+ import { _ as _35 } from "./chunks/LpNumberField-NPE8Bumx.js";
45
+ import { _ as _36 } from "./chunks/LpNumberFlow-amWYaeuA.js";
46
+ import { _ as _37 } from "./chunks/LpOtpInput-DqaT0Z75.js";
47
+ import { _ as _38 } from "./chunks/LpPagination-CeGuNe_q.js";
48
+ import { _ as _39 } from "./chunks/LpPasswordInput-DrYE4J5d.js";
49
+ import { _ as _40 } from "./chunks/LpPie-BL1vTDSM.js";
50
+ import { _ as _41 } from "./chunks/LpPhoneInput-NXXjciJJ.js";
48
51
  import { flagEmoji, loadCountries, matchCountryByValue } from "./components/countries.js";
49
- import { _ as _41 } from "./chunks/LpPopover-8268UmlC.js";
50
- import { _ as _42 } from "./chunks/LpProgress-Btq5W4Ut.js";
51
- import { _ as _43 } from "./chunks/LpRadio-CL-pI0_O.js";
52
- import { _ as _44 } from "./chunks/LpRadioGroup-B4yDTmi6.js";
53
- import { _ as _45 } from "./chunks/LpRollingText-CM5gkSeF.js";
54
- import { _ as _46 } from "./chunks/LpScrollArea-D8xyKXcF.js";
55
- import { _ as _47 } from "./chunks/LpSegmented-BmpanyAo.js";
56
- import { _ as _48 } from "./chunks/LpSection-aM6J_u51.js";
57
- import { _ as _49 } from "./chunks/LpSelect-ANIVtrpM.js";
58
- import { _ as _50 } from "./chunks/LpServiceNode-D8uJ8sGk.js";
59
- import { _ as _51 } from "./chunks/LpShift-DXNsauQZ.js";
60
- import { default as default5 } from "./components/LpShortcutsDialog.vue.js";
61
- import { _ as _52 } from "./chunks/LpSidebar-C_Mr-JPI.js";
62
- import { _ as _53 } from "./chunks/LpSkeleton-CTI-vBgW.js";
63
- import { _ as _54 } from "./chunks/LpSlider-B1SF3pRV.js";
64
- import { _ as _55 } from "./chunks/LpSparkline-C6xQ2dwv.js";
65
- import { _ as _56 } from "./chunks/LpStat-CDanpzU0.js";
66
- import { _ as _57 } from "./chunks/LpStepper-Wtl-6nUh.js";
67
- import { _ as _58 } from "./chunks/LpSwitch-v6fnccSM.js";
68
- import { default as default6 } from "./components/LpTable.vue.js";
69
- import { _ as _59 } from "./chunks/LpTableOfContents-CjvxPMpU.js";
70
- import { _ as _60 } from "./chunks/LpTabs-Cefe_lys.js";
71
- import { _ as _61 } from "./chunks/LpTextarea-BMIOfw5l.js";
52
+ import { _ as _42 } from "./chunks/LpPopover-8268UmlC.js";
53
+ import { _ as _43 } from "./chunks/LpProgress-Btq5W4Ut.js";
54
+ import { _ as _44 } from "./chunks/LpRadio-CL-pI0_O.js";
55
+ import { _ as _45 } from "./chunks/LpRadioGroup-B4yDTmi6.js";
56
+ import { _ as _46 } from "./chunks/LpRollingText-CM5gkSeF.js";
57
+ import { _ as _47 } from "./chunks/LpScrollArea-D8xyKXcF.js";
58
+ import { _ as _48 } from "./chunks/LpSegmented-BmpanyAo.js";
59
+ import { _ as _49 } from "./chunks/LpSection-aM6J_u51.js";
60
+ import { _ as _50 } from "./chunks/LpSelect-ANIVtrpM.js";
61
+ import { _ as _51 } from "./chunks/LpServiceNode-D8uJ8sGk.js";
62
+ import { _ as _52 } from "./chunks/LpShift-DXNsauQZ.js";
63
+ import { default as default6 } from "./components/LpShortcutsDialog.vue.js";
64
+ import { _ as _53 } from "./chunks/LpSidebar-CWSbczWi.js";
65
+ import { _ as _54 } from "./chunks/LpSkeleton-CTI-vBgW.js";
66
+ import { _ as _55 } from "./chunks/LpSlider-B1SF3pRV.js";
67
+ import { _ as _56 } from "./chunks/LpSparkline-C6xQ2dwv.js";
68
+ import { _ as _57 } from "./chunks/LpStat-CDanpzU0.js";
69
+ import { _ as _58 } from "./chunks/LpStepper-Wtl-6nUh.js";
70
+ import { _ as _59 } from "./chunks/LpSwitch-v6fnccSM.js";
71
+ import { default as default7 } from "./components/LpTable.vue.js";
72
+ import { _ as _60 } from "./chunks/LpTableOfContents-CjvxPMpU.js";
73
+ import { _ as _61 } from "./chunks/LpTabs-Cefe_lys.js";
74
+ import { _ as _62 } from "./chunks/LpTextarea-BMIOfw5l.js";
72
75
  import { p as presets, a as parseTheme, s as serializeTheme } from "./chunks/LpThemeSwitcher-r1te9nB2.js";
73
- import { D, _ as _62, N, T, b, c, d, e, f, g, i, l, h, j, n, r, t, k, m, u, v } from "./chunks/LpThemeSwitcher-r1te9nB2.js";
74
- import { _ as _63 } from "./chunks/LpTilt-DuyKbpkA.js";
75
- import { _ as _64 } from "./chunks/LpToaster-gDB1EaU3.js";
76
- import { _ as _65 } from "./chunks/LpTooltip-hCLPAqMG.js";
77
- import { _ as _66 } from "./chunks/LpTopologyCanvas-Cm1L39uL.js";
78
- import { _ as _67 } from "./chunks/LpUptimeBar-DcQ8HikG.js";
76
+ import { D, _ as _63, N, T, b, c, d, e, f, g, i, l, h, j, n, r, t, k, m, u, v } from "./chunks/LpThemeSwitcher-r1te9nB2.js";
77
+ import { _ as _64 } from "./chunks/LpTilt-DuyKbpkA.js";
78
+ import { _ as _65 } from "./chunks/LpToaster-gDB1EaU3.js";
79
+ import { _ as _66 } from "./chunks/LpTooltip-hCLPAqMG.js";
80
+ import { _ as _67 } from "./chunks/LpTopologyCanvas-Cm1L39uL.js";
81
+ import { _ as _68 } from "./chunks/LpUptimeBar-DcQ8HikG.js";
79
82
  import { deserializeLayout, serializeLayout } from "./layout/tree.js";
80
83
  import { addLeaf, addTab, countBlocks, countLeaves, makeLayout, moveLeaf, removeBlock, removeLeaf, reorderTab, resizeAt, setActiveTab } from "./layout/tree.js";
81
84
  import { blockTitle, defineBlocks } from "./layout/registry.js";
82
85
  import { useLayout } from "./layout/useLayout.js";
86
+ import { useClipboard } from "./composables/useClipboard.js";
83
87
  import { appleModifierLabel, isAppleKeyboard, useHotkeys } from "./composables/useHotkeys.js";
84
88
  import { useInputFilter } from "./composables/useInputFilter.js";
85
89
  import { easeOut, easeTravel, easingToken, prefersReducedMotion } from "./composables/easing.js";
@@ -124,78 +128,81 @@ function parseConfig(json) {
124
128
  export {
125
129
  CHART_PALETTE,
126
130
  D as DEFAULT_SURFACE,
131
+ DEFAULT_SWATCHES,
127
132
  _ as LayoutCanvas,
133
+ _4 as LpActionBar,
128
134
  _2 as LpAddressInput,
129
- _4 as LpAlert,
130
- _5 as LpAppShell,
131
- _6 as LpAutocomplete,
132
- _7 as LpAvatar,
133
- _8 as LpBadge,
134
- _9 as LpBreadcrumbs,
135
- _10 as LpButton,
136
- _11 as LpCalendar,
137
- _12 as LpCard,
138
- _13 as LpChart,
135
+ _5 as LpAlert,
136
+ _6 as LpAppShell,
137
+ _7 as LpAutocomplete,
138
+ _8 as LpAvatar,
139
+ _9 as LpBadge,
140
+ _10 as LpBreadcrumbs,
141
+ _11 as LpButton,
142
+ _12 as LpCalendar,
143
+ _13 as LpCard,
144
+ default2 as LpChart,
139
145
  _14 as LpCheckbox,
140
146
  _15 as LpCodeBlock,
141
- default2 as LpCommandPalette,
142
- _16 as LpConfirmDialog,
143
- _17 as LpContextMenu,
144
- _18 as LpDatePicker,
145
- _19 as LpDisclosure,
146
- _20 as LpDivider,
147
- _21 as LpDrawer,
148
- _22 as LpDropdownMenu,
149
- default3 as LpDropzone,
150
- _23 as LpEmptyState,
151
- _24 as LpFileTree,
152
- _25 as LpFormField,
153
- _26 as LpIcon,
154
- default4 as LpImageEditor,
155
- _27 as LpInfraNode,
156
- _28 as LpInput,
157
- _29 as LpLightbox,
158
- _30 as LpLink,
159
- _31 as LpLogViewer,
147
+ _16 as LpColorPicker,
148
+ default3 as LpCommandPalette,
149
+ _17 as LpConfirmDialog,
150
+ _18 as LpContextMenu,
151
+ _19 as LpDatePicker,
152
+ _20 as LpDisclosure,
153
+ _21 as LpDivider,
154
+ _22 as LpDrawer,
155
+ _23 as LpDropdownMenu,
156
+ default4 as LpDropzone,
157
+ _24 as LpEmptyState,
158
+ _25 as LpFileTree,
159
+ _26 as LpFormField,
160
+ _27 as LpIcon,
161
+ default5 as LpImageEditor,
162
+ _28 as LpInfraNode,
163
+ _29 as LpInput,
164
+ _30 as LpLightbox,
165
+ _31 as LpLink,
166
+ _32 as LpLogViewer,
160
167
  _3 as LpMapPicker,
161
- _32 as LpModal,
162
- _33 as LpNotificationBell,
163
- _34 as LpNumberField,
164
- _35 as LpNumberFlow,
165
- _36 as LpOtpInput,
166
- _37 as LpPagination,
167
- _38 as LpPasswordInput,
168
- _40 as LpPhoneInput,
169
- _39 as LpPie,
170
- _41 as LpPopover,
171
- _42 as LpProgress,
172
- _43 as LpRadio,
173
- _44 as LpRadioGroup,
174
- _45 as LpRollingText,
175
- _46 as LpScrollArea,
176
- _48 as LpSection,
177
- _47 as LpSegmented,
178
- _49 as LpSelect,
179
- _50 as LpServiceNode,
180
- _51 as LpShift,
181
- default5 as LpShortcutsDialog,
182
- _52 as LpSidebar,
183
- _53 as LpSkeleton,
184
- _54 as LpSlider,
185
- _55 as LpSparkline,
186
- _56 as LpStat,
187
- _57 as LpStepper,
188
- _58 as LpSwitch,
189
- default6 as LpTable,
190
- _59 as LpTableOfContents,
191
- _60 as LpTabs,
192
- _61 as LpTextarea,
193
- _62 as LpThemeSwitcher,
194
- _63 as LpTilt,
195
- _64 as LpToaster,
196
- _65 as LpTooltip,
197
- _66 as LpTopologyCanvas,
198
- _67 as LpUptimeBar,
168
+ _33 as LpModal,
169
+ _34 as LpNotificationBell,
170
+ _35 as LpNumberField,
171
+ _36 as LpNumberFlow,
172
+ _37 as LpOtpInput,
173
+ _38 as LpPagination,
174
+ _39 as LpPasswordInput,
175
+ _41 as LpPhoneInput,
176
+ _40 as LpPie,
177
+ _42 as LpPopover,
178
+ _43 as LpProgress,
179
+ _44 as LpRadio,
180
+ _45 as LpRadioGroup,
181
+ _46 as LpRollingText,
182
+ _47 as LpScrollArea,
183
+ _49 as LpSection,
184
+ _48 as LpSegmented,
185
+ _50 as LpSelect,
186
+ _51 as LpServiceNode,
187
+ _52 as LpShift,
188
+ default6 as LpShortcutsDialog,
189
+ _53 as LpSidebar,
190
+ _54 as LpSkeleton,
191
+ _55 as LpSlider,
192
+ _56 as LpSparkline,
193
+ _57 as LpStat,
194
+ _58 as LpStepper,
195
+ _59 as LpSwitch,
196
+ default7 as LpTable,
197
+ _60 as LpTableOfContents,
198
+ _61 as LpTabs,
199
+ _62 as LpTextarea,
200
+ _63 as LpThemeSwitcher,
201
+ _64 as LpTilt,
202
+ _65 as LpToaster,
203
+ _66 as LpTooltip,
204
+ _67 as LpTopologyCanvas,
205
+ _68 as LpUptimeBar,
199
206
  N as NO_BACKDROP_ATTR,
200
207
  OSM_TILES,
201
208
  T as THEME_COOKIE,
@@ -212,6 +219,7 @@ export {
212
219
  e as bootstrapTheme,
213
220
  checkStateOf,
214
221
  checkedCount,
222
+ clampHsv,
215
223
  computeStats,
216
224
  countBlocks,
217
225
  countLeaves,
@@ -229,11 +237,15 @@ export {
229
237
  fileNameOf,
230
238
  flagEmoji,
231
239
  formatAddress,
240
+ formatColor,
232
241
  formatModified,
233
242
  formatSize,
234
243
  formatTick,
244
+ hsvToHsl,
245
+ hsvToRgb,
235
246
  isAppleKeyboard,
236
247
  i as isBrokenBackdropEngine,
248
+ isLight,
237
249
  l as leavepulse,
238
250
  h as light,
239
251
  j as lime,
@@ -243,7 +255,11 @@ export {
243
255
  moveLeaf,
244
256
  niceScale,
245
257
  n as nord,
258
+ normalizeHue,
259
+ opaqueCss,
260
+ parseColor,
246
261
  parseConfig,
262
+ parseHex,
247
263
  parseTheme,
248
264
  prefersReducedMotion,
249
265
  presets,
@@ -253,6 +269,8 @@ export {
253
269
  reorderTab,
254
270
  resizeAt,
255
271
  resolveSlices,
272
+ rgbToHex,
273
+ rgbToHsv,
256
274
  r as rose,
257
275
  serializeConfig,
258
276
  serializeLayout,
@@ -266,6 +284,7 @@ export {
266
284
  m as themeToCssVars,
267
285
  tileUrl,
268
286
  unproject,
287
+ useClipboard,
269
288
  useFormDraft,
270
289
  useHotkeys,
271
290
  useInputFilter,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leavepulse/ui",
3
- "version": "0.29.4",
3
+ "version": "0.30.0",
4
4
  "type": "module",
5
5
  "description": "LeavePulse token-driven Vue component kit.",
6
6
  "repository": {
@@ -4,6 +4,7 @@
4
4
  export const COMPONENT_NAMES = [
5
5
  "LayoutCanvas",
6
6
  "LayoutNode",
7
+ "LpActionBar",
7
8
  "LpAddressInput",
8
9
  "LpAlert",
9
10
  "LpAppShell",
@@ -17,6 +18,7 @@ export const COMPONENT_NAMES = [
17
18
  "LpChart",
18
19
  "LpCheckbox",
19
20
  "LpCodeBlock",
21
+ "LpColorPicker",
20
22
  "LpCommandPalette",
21
23
  "LpConfirmDialog",
22
24
  "LpContextMenu",
@@ -0,0 +1,268 @@
1
+ <script setup lang="ts">
2
+ /*
3
+ * A bar that rises from the bottom of the window to ask for one decision, then
4
+ * leaves. Unsaved changes with Save/Discard, a cookie notice, "3 rows selected"
5
+ * with a bulk action, an update waiting to be applied.
6
+ *
7
+ * It is deliberately NOT a dialog and NOT a toast, because it is neither:
8
+ *
9
+ * A dialog takes the page hostage. The whole point of an unsaved-changes bar
10
+ * is that you can keep editing while it sits there — trapping focus and
11
+ * dimming the form behind it would be answering a question the user has not
12
+ * been asked yet.
13
+ *
14
+ * A toast leaves on a timer. A decision that vanishes after four seconds is a
15
+ * decision the user can miss, and "your changes were lost because you looked
16
+ * away" is the outcome. This stays until something resolves it.
17
+ *
18
+ * So: fixed to the bottom, above the page but below modals, never focus-
19
+ * trapped, and it does not close itself. `role="region"` with an accessible
20
+ * name rather than `alertdialog` — it is a landmark you can Tab into, not a
21
+ * thing demanding an answer before anything else can happen.
22
+ */
23
+ import { computed, nextTick, ref, useSlots, watch } from "vue"
24
+ import { useBarStack } from "./actionBar"
25
+ import { CLOSE_ICON } from "./dropdown"
26
+ import LpButton from "./LpButton.vue"
27
+ import LpIcon from "./LpIcon.vue"
28
+
29
+ export interface ActionBarAction {
30
+ label: string
31
+ /** Leading icon (any name LpIcon takes). */
32
+ icon?: string
33
+ /** Mirrors LpButton's variants; the default reads as the primary action. */
34
+ variant?: "solid" | "action" | "outline" | "soft" | "ghost" | "muted" | "light" | "danger"
35
+ disabled?: boolean
36
+ /** Shows a spinner in place of the icon and blocks repeat clicks. */
37
+ loading?: boolean
38
+ onClick?: () => void | Promise<void>
39
+ }
40
+
41
+ const props = withDefaults(
42
+ defineProps<{
43
+ /** Shown or not (v-model:open, or a plain :open). */
44
+ open?: boolean
45
+ /** Lead line — "You have unsaved changes". */
46
+ title?: string
47
+ /** Second line, quieter. Long explanations belong here, not in the title. */
48
+ description?: string
49
+ /** Leading icon. Omit for a bar that is purely text + buttons. */
50
+ icon?: string
51
+ /**
52
+ * Buttons on the right, in reading order. The LAST one is the primary and
53
+ * gets the solid treatment unless it says otherwise — the confirming action
54
+ * sits closest to the edge the thumb and the eye finish on.
55
+ */
56
+ actions?: ActionBarAction[]
57
+ /** Adds a "✕" that emits `dismiss`. For notices, not for decisions. */
58
+ dismissible?: boolean
59
+ /**
60
+ * Tone. `danger` for a destructive pending action; the default stays neutral
61
+ * because most of these bars are routine and a page edged in red for every
62
+ * unsaved field trains people to ignore red.
63
+ */
64
+ variant?: "default" | "danger"
65
+ /**
66
+ * How wide the bar runs.
67
+ *
68
+ * "contained" (the default) keeps it to the width of its own content, up to
69
+ * a cap, and centres it: a bar carrying six words and two buttons has no
70
+ * business spanning a 1920px display, and stretching it puts the buttons a
71
+ * screen away from the field you were editing.
72
+ *
73
+ * "full" spans the viewport edge to edge — for a cookie notice, which reads
74
+ * as part of the page frame rather than as a message about one thing on it.
75
+ * Its CONTENTS are still capped and centred, so the buttons stay beside the
76
+ * text instead of being flung to the far corner.
77
+ */
78
+ width?: "contained" | "full"
79
+ /**
80
+ * Cap for the bar (or, when `width="full"`, for the row inside it). Any CSS
81
+ * length. Defaults to a comfortable measure for a line of text and two or
82
+ * three buttons — wider for `full`, whose job is to span the page and whose
83
+ * contents therefore sit on the page's own measure, not a card's.
84
+ */
85
+ maxWidth?: string
86
+ /** Accessible name for the landmark. Falls back to `title`. */
87
+ ariaLabel?: string
88
+ }>(),
89
+ {
90
+ open: true,
91
+ actions: () => [],
92
+ variant: "default",
93
+ width: "contained",
94
+ },
95
+ )
96
+
97
+ const emit = defineEmits<{
98
+ (e: "update:open", value: boolean): void
99
+ (e: "dismiss"): void
100
+ }>()
101
+
102
+ const slots = useSlots()
103
+
104
+ // The last action is the primary one unless the caller has said otherwise; the
105
+ // rest default to `ghost` so a bar with three buttons still has one obvious
106
+ // answer instead of three competing ones.
107
+ const resolvedActions = computed(() =>
108
+ props.actions.map((a, i) => ({
109
+ ...a,
110
+ variant:
111
+ a.variant ??
112
+ (i === props.actions.length - 1
113
+ ? props.variant === "danger"
114
+ ? ("danger" as const)
115
+ : ("solid" as const)
116
+ : ("ghost" as const)),
117
+ })),
118
+ )
119
+
120
+ const hasBody = computed(() => !!props.title || !!props.description || !!slots.default)
121
+
122
+ /**
123
+ * 36rem for a contained bar: a line of text and two or three buttons, at about
124
+ * the measure prose stays comfortable at — wider starts reading as a page
125
+ * element, which a contained bar is not. A full-width bar IS page furniture, so
126
+ * its row sits on the shell's own measure (LpAppShell centres content at
127
+ * max-w-6xl) rather than being squeezed into a card's width, which cropped the
128
+ * description of a cookie notice halfway through a word.
129
+ */
130
+ const maxWidth = computed(() => props.maxWidth ?? (props.width === "full" ? "72rem" : "36rem"))
131
+
132
+ function run(action: ActionBarAction) {
133
+ if (action.disabled || action.loading) return
134
+ void action.onClick?.()
135
+ }
136
+
137
+ function dismiss() {
138
+ emit("dismiss")
139
+ emit("update:open", false)
140
+ }
141
+
142
+ /* ── stacking ─────────────────────────────────────
143
+ Two bars at once is ordinary — a cookie notice over a form that has just been
144
+ edited — and both are translucent, so overlapping them makes each unreadable
145
+ through the other. Each open bar takes a slot and rides above the ones that
146
+ opened before it. */
147
+ const root = ref<HTMLElement | null>(null)
148
+ const { register, unregister, setHeight, offset, bars } = useBarStack()
149
+
150
+ const stackOffset = computed(() => {
151
+ // `bars` is read so the offset recomputes when a bar below is added, resized
152
+ // or removed; the value itself comes from the registry.
153
+ void bars.value
154
+ return offset()
155
+ })
156
+
157
+ watch(
158
+ () => props.open,
159
+ async (isOpen) => {
160
+ if (!isOpen) {
161
+ unregister()
162
+ return
163
+ }
164
+ register()
165
+ await nextTick()
166
+ if (root.value) setHeight(root.value.getBoundingClientRect().height)
167
+ },
168
+ { immediate: true },
169
+ )
170
+ </script>
171
+
172
+ <template>
173
+ <!-- Teleported so the bar is pinned to the VIEWPORT rather than to whatever
174
+ container it was declared in: a `position: fixed` element inside an
175
+ ancestor that has a transform, filter or backdrop-filter anchors to that
176
+ ancestor instead, and the kit is full of such ancestors (any panel with a
177
+ blur). Declaring it next to the form it belongs to should not decide
178
+ where on screen it lands. -->
179
+ <Teleport to="body">
180
+ <Transition
181
+ enter-active-class="animate-[rise-in_var(--duration-medium)_var(--ease-emphasized)]"
182
+ leave-active-class="animate-[rise-out_var(--duration-fast)_ease]"
183
+ >
184
+ <div
185
+ v-if="open"
186
+ ref="root"
187
+ role="region"
188
+ :aria-label="ariaLabel ?? title"
189
+ class="pointer-events-none fixed inset-x-0 bottom-0 z-(--z-modal) flex justify-center transition-[translate] duration-[var(--duration-medium)] ease-[var(--ease-emphasized)] motion-reduce:transition-none"
190
+ :class="width === 'full' ? '' : 'p-3 sm:p-4'"
191
+ :style="{ translate: `0 -${stackOffset}px` }"
192
+ >
193
+ <div
194
+ class="lp-skin-panel pointer-events-auto flex items-center gap-3 border-solid bg-surface-overlay"
195
+ :class="[
196
+ // A full-width bar is the page's own furniture: square, edge to
197
+ // edge, and only its top border drawn, so it reads as a frame
198
+ // rather than as a card that happens to be very wide.
199
+ width === 'full'
200
+ ? 'w-full justify-center border-x-0 border-b-0 px-4 py-3 sm:px-6'
201
+ : 'w-full rounded-card px-4 py-3',
202
+ variant === 'danger' ? 'border-danger/40' : 'border-line',
203
+ ]"
204
+ :style="width === 'full' ? undefined : { maxWidth }"
205
+ >
206
+ <!-- `full` keeps its contents on the same measure as a contained bar,
207
+ so the buttons stay next to the words instead of being pushed to
208
+ the far edge of a wide display. -->
209
+ <div
210
+ class="flex w-full items-center gap-3"
211
+ :style="{ maxWidth }"
212
+ >
213
+ <LpIcon
214
+ v-if="icon"
215
+ :name="icon"
216
+ :size="18"
217
+ :class="['shrink-0', variant === 'danger' ? 'text-danger' : 'text-brand']"
218
+ />
219
+
220
+ <!-- min-w-0 so a long description truncates instead of pushing the
221
+ buttons off the end of the bar. -->
222
+ <div v-if="hasBody" class="min-w-0 flex-1">
223
+ <slot>
224
+ <p v-if="title" class="truncate text-sm font-medium text-ink">{{ title }}</p>
225
+ <p v-if="description" class="truncate text-xs text-muted">{{ description }}</p>
226
+ </slot>
227
+ </div>
228
+ <div v-else class="flex-1" />
229
+
230
+ <!-- Buttons never shrink: the bar gives up text before it gives up
231
+ the thing you came here to click. -->
232
+ <div class="flex shrink-0 items-center gap-2">
233
+ <slot name="actions">
234
+ <LpButton
235
+ v-for="(action, i) in resolvedActions"
236
+ :key="i"
237
+ :variant="action.variant"
238
+ size="sm"
239
+ :disabled="action.disabled || action.loading"
240
+ @click="run(action)"
241
+ >
242
+ <LpIcon
243
+ v-if="action.loading"
244
+ name="lucide:loader-circle"
245
+ :size="15"
246
+ class="animate-spin"
247
+ />
248
+ <LpIcon v-else-if="action.icon" :name="action.icon" :size="15" />
249
+ {{ action.label }}
250
+ </LpButton>
251
+ </slot>
252
+
253
+ <button
254
+ v-if="dismissible"
255
+ type="button"
256
+ aria-label="Dismiss"
257
+ class="group -mr-1 flex size-8 shrink-0 items-center justify-center rounded-control text-muted outline-none transition-colors duration-[var(--duration-fast)] hover:text-ink focus-visible:ring-2 focus-visible:ring-ring"
258
+ @click="dismiss"
259
+ >
260
+ <LpIcon name="lucide:x" :size="16" :class="CLOSE_ICON" />
261
+ </button>
262
+ </div>
263
+ </div>
264
+ </div>
265
+ </div>
266
+ </Transition>
267
+ </Teleport>
268
+ </template>