@leavepulse/ui 0.15.4 → 0.15.5

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.
@@ -0,0 +1,133 @@
1
+ import { ref, computed } from "vue";
2
+ function useZoomPan(options = {}) {
3
+ const min = options.min ?? 1;
4
+ const max = options.max ?? 8;
5
+ const step = options.step ?? 1.15;
6
+ const zoomedScale = options.zoomedScale ?? 2;
7
+ const scale = ref(min);
8
+ const x = ref(0);
9
+ const y = ref(0);
10
+ const panning = ref(false);
11
+ let viewport = null;
12
+ const pointers = /* @__PURE__ */ new Map();
13
+ let start = { x: 0, y: 0, panX: 0, panY: 0 };
14
+ let pinchStartDistance = 0;
15
+ let pinchStartScale = 1;
16
+ const zoomed = computed(() => scale.value > min + 1e-3);
17
+ function setViewport(el) {
18
+ viewport = el;
19
+ }
20
+ function clamp(value, lo, hi) {
21
+ return Math.min(hi, Math.max(lo, value));
22
+ }
23
+ function clampPan() {
24
+ if (!viewport) return;
25
+ const rect = viewport.getBoundingClientRect();
26
+ const slackX = rect.width * (scale.value - 1) / 2 / scale.value;
27
+ const slackY = rect.height * (scale.value - 1) / 2 / scale.value;
28
+ x.value = clamp(x.value, -slackX, slackX);
29
+ y.value = clamp(y.value, -slackY, slackY);
30
+ }
31
+ function zoomAbout(next, px, py) {
32
+ const clamped = clamp(next, min, max);
33
+ if (clamped === scale.value) return;
34
+ const contentX = px / scale.value - x.value;
35
+ const contentY = py / scale.value - y.value;
36
+ scale.value = clamped;
37
+ x.value = px / clamped - contentX;
38
+ y.value = py / clamped - contentY;
39
+ clampPan();
40
+ }
41
+ function relative(e) {
42
+ if (!viewport) return { x: 0, y: 0 };
43
+ const rect = viewport.getBoundingClientRect();
44
+ return {
45
+ x: e.clientX - rect.left - rect.width / 2,
46
+ y: e.clientY - rect.top - rect.height / 2
47
+ };
48
+ }
49
+ function onWheel(e) {
50
+ e.preventDefault();
51
+ const point = relative(e);
52
+ zoomAbout(scale.value * (e.deltaY < 0 ? step : 1 / step), point.x, point.y);
53
+ }
54
+ function distance(a, b) {
55
+ return Math.hypot(a.x - b.x, a.y - b.y);
56
+ }
57
+ function onPointerDown(e) {
58
+ pointers.set(e.pointerId, { x: e.clientX, y: e.clientY });
59
+ if (pointers.size === 2) {
60
+ const [a, b] = [...pointers.values()];
61
+ pinchStartDistance = distance(a, b);
62
+ pinchStartScale = scale.value;
63
+ panning.value = false;
64
+ return;
65
+ }
66
+ if (pointers.size > 2) return;
67
+ if (!zoomed.value) return;
68
+ panning.value = true;
69
+ start = { x: e.clientX, y: e.clientY, panX: x.value, panY: y.value };
70
+ e.currentTarget?.setPointerCapture?.(e.pointerId);
71
+ }
72
+ function onPointerMove(e) {
73
+ if (!pointers.has(e.pointerId)) return;
74
+ pointers.set(e.pointerId, { x: e.clientX, y: e.clientY });
75
+ if (pointers.size === 2 && pinchStartDistance > 0) {
76
+ const [a, b] = [...pointers.values()];
77
+ const mid = { clientX: (a.x + b.x) / 2, clientY: (a.y + b.y) / 2 };
78
+ const point = relative(mid);
79
+ zoomAbout(pinchStartScale * (distance(a, b) / pinchStartDistance), point.x, point.y);
80
+ return;
81
+ }
82
+ if (!panning.value) return;
83
+ x.value = start.panX + (e.clientX - start.x) / scale.value;
84
+ y.value = start.panY + (e.clientY - start.y) / scale.value;
85
+ clampPan();
86
+ }
87
+ function onPointerUp(e) {
88
+ pointers.delete(e.pointerId);
89
+ if (pointers.size < 2) pinchStartDistance = 0;
90
+ if (pointers.size === 0) panning.value = false;
91
+ }
92
+ function zoomBy(factor) {
93
+ zoomAbout(scale.value * factor, 0, 0);
94
+ }
95
+ function toggleZoom(origin) {
96
+ if (zoomed.value) {
97
+ reset();
98
+ return;
99
+ }
100
+ const point = origin ? relative({ clientX: origin.x, clientY: origin.y }) : { x: 0, y: 0 };
101
+ zoomAbout(zoomedScale, point.x, point.y);
102
+ }
103
+ function reset() {
104
+ scale.value = min;
105
+ x.value = 0;
106
+ y.value = 0;
107
+ panning.value = false;
108
+ pointers.clear();
109
+ pinchStartDistance = 0;
110
+ }
111
+ const style = computed(() => ({
112
+ transform: `scale(${scale.value}) translate(${x.value}px, ${y.value}px)`,
113
+ // Settle smoothly on discrete zooms, but follow a drag or pinch exactly.
114
+ transition: panning.value || pinchStartDistance > 0 ? "none" : void 0
115
+ }));
116
+ return {
117
+ scale,
118
+ zoomed,
119
+ panning,
120
+ style,
121
+ onWheel,
122
+ onPointerDown,
123
+ onPointerMove,
124
+ onPointerUp,
125
+ zoomBy,
126
+ toggleZoom,
127
+ reset,
128
+ setViewport
129
+ };
130
+ }
131
+ export {
132
+ useZoomPan
133
+ };
package/dist/index.d.ts CHANGED
@@ -34,6 +34,9 @@ export { default as LpIcon } from './components/LpIcon.vue';
34
34
  export { default as LpInfraNode } from './components/LpInfraNode.vue';
35
35
  export type { InfraNodeData } from './components/LpInfraNode.vue';
36
36
  export { default as LpInput } from './components/LpInput.vue';
37
+ export { default as LpLightbox } from './components/LpLightbox.vue';
38
+ export type { LightboxItem } from './components/lightbox';
39
+ export { fileNameOf } from './components/lightbox';
37
40
  export { default as LpLink } from './components/LpLink.vue';
38
41
  export { default as LpLogViewer } from './components/LpLogViewer.vue';
39
42
  export type { LogLevel, LogLine } from './components/LpLogViewer.vue';
@@ -103,5 +106,7 @@ export { useInputFilter } from './composables/useInputFilter';
103
106
  export type { UseInputFilterOptions } from './composables/useInputFilter';
104
107
  export { useTilt } from './composables/useTilt';
105
108
  export type { UseTilt, UseTiltOptions } from './composables/useTilt';
109
+ export { useZoomPan } from './composables/useZoomPan';
110
+ export type { UseZoomPan, UseZoomPanOptions } from './composables/useZoomPan';
106
111
  export { useToast } from './composables/useToast';
107
112
  export type { ToastAction, ToastItem, ToastOptions, ToastVariant, } from './composables/useToast';
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ import { _ as _10 } from "./chunks/LpCard-BmrTvWOe.js";
11
11
  import { _ as _11 } from "./chunks/LpCheckbox-BO0zuuPh.js";
12
12
  import { _ as _12 } from "./chunks/LpCodeBlock-CSiOSPs8.js";
13
13
  import { default as default2 } from "./components/LpCommandPalette.vue.js";
14
- import { _ as _13 } from "./chunks/LpConfirmDialog-D6Rhge21.js";
14
+ import { _ as _13 } from "./chunks/LpConfirmDialog-C-iOXJIM.js";
15
15
  import { _ as _14 } from "./chunks/LpContextMenu-D0mzXqA7.js";
16
16
  import { _ as _15 } from "./chunks/LpDatePicker-dY2q4dr4.js";
17
17
  import { _ as _16 } from "./chunks/LpDisclosure-DP6yfgfM.js";
@@ -25,47 +25,50 @@ import { _ as _22 } from "./chunks/LpFormField-Bn-ZwC6z.js";
25
25
  import { _ as _23 } from "./chunks/LpIcon-CCnX5_2j.js";
26
26
  import { _ as _24 } from "./chunks/LpInfraNode-C_PHcthC.js";
27
27
  import { _ as _25 } from "./chunks/LpInput-BauZRyzm.js";
28
- import { _ as _26 } from "./chunks/LpLink-DHwOEp4e.js";
29
- import { _ as _27 } from "./chunks/LpLogViewer-BvzRZPjs.js";
30
- import { _ as _28 } from "./chunks/LpModal-d6SZpfBH.js";
31
- import { _ as _29 } from "./chunks/LpNotificationBell-BtfRoL2i.js";
32
- import { _ as _30 } from "./chunks/LpNumberField-C5ar-OwE.js";
33
- import { _ as _31 } from "./chunks/LpOtpInput-DqaT0Z75.js";
34
- import { _ as _32 } from "./chunks/LpPagination-CeGuNe_q.js";
35
- import { _ as _33 } from "./chunks/LpPasswordInput-DIHFw4u0.js";
36
- import { _ as _34 } from "./chunks/LpPhoneInput-LQvVFGMw.js";
28
+ import { _ as _26 } from "./chunks/LpLightbox-BYIA9qne.js";
29
+ import { fileNameOf } from "./components/lightbox.js";
30
+ import { _ as _27 } from "./chunks/LpLink-DHwOEp4e.js";
31
+ import { _ as _28 } from "./chunks/LpLogViewer-BvzRZPjs.js";
32
+ import { _ as _29 } from "./chunks/LpModal-DeZlvK1B.js";
33
+ import { _ as _30 } from "./chunks/LpNotificationBell-BtfRoL2i.js";
34
+ import { _ as _31 } from "./chunks/LpNumberField-C5ar-OwE.js";
35
+ import { _ as _32 } from "./chunks/LpOtpInput-DqaT0Z75.js";
36
+ import { _ as _33 } from "./chunks/LpPagination-CeGuNe_q.js";
37
+ import { _ as _34 } from "./chunks/LpPasswordInput-DIHFw4u0.js";
38
+ import { _ as _35 } from "./chunks/LpPhoneInput-LQvVFGMw.js";
37
39
  import { flagEmoji, loadCountries, matchCountryByValue } from "./components/countries.js";
38
- import { _ as _35 } from "./chunks/LpPopover-8268UmlC.js";
39
- import { _ as _36 } from "./chunks/LpProgress-BhI0IbjC.js";
40
- import { _ as _37 } from "./chunks/LpRadio-CL-pI0_O.js";
41
- import { _ as _38 } from "./chunks/LpRadioGroup-B4yDTmi6.js";
42
- import { _ as _39 } from "./chunks/LpScrollArea-De9DLpa1.js";
43
- import { _ as _40 } from "./chunks/LpSegmented-BmpanyAo.js";
44
- import { _ as _41 } from "./chunks/LpSelect-DznhPEWX.js";
45
- import { _ as _42 } from "./chunks/LpServiceNode-Dc-Ut5RS.js";
46
- import { _ as _43 } from "./chunks/LpSidebar-B11IDRQi.js";
47
- import { _ as _44 } from "./chunks/LpSkeleton-eSFIo4kD.js";
48
- import { _ as _45 } from "./chunks/LpSlider-B1SF3pRV.js";
49
- import { _ as _46 } from "./chunks/LpStat-CdbEFOvn.js";
50
- import { _ as _47 } from "./chunks/LpStepper-5dFTrW21.js";
51
- import { _ as _48 } from "./chunks/LpSwitch-v6fnccSM.js";
52
- import { _ as _49 } from "./chunks/LpTable-CKGaUbEM.js";
53
- import { _ as _50 } from "./chunks/LpTableOfContents-CjvxPMpU.js";
54
- import { _ as _51 } from "./chunks/LpTabs-D7aTVPOb.js";
55
- import { _ as _52 } from "./chunks/LpTextarea-CRAkhemZ.js";
40
+ import { _ as _36 } from "./chunks/LpPopover-8268UmlC.js";
41
+ import { _ as _37 } from "./chunks/LpProgress-BhI0IbjC.js";
42
+ import { _ as _38 } from "./chunks/LpRadio-CL-pI0_O.js";
43
+ import { _ as _39 } from "./chunks/LpRadioGroup-B4yDTmi6.js";
44
+ import { _ as _40 } from "./chunks/LpScrollArea-De9DLpa1.js";
45
+ import { _ as _41 } from "./chunks/LpSegmented-BmpanyAo.js";
46
+ import { _ as _42 } from "./chunks/LpSelect-DznhPEWX.js";
47
+ import { _ as _43 } from "./chunks/LpServiceNode-Dc-Ut5RS.js";
48
+ import { _ as _44 } from "./chunks/LpSidebar-B11IDRQi.js";
49
+ import { _ as _45 } from "./chunks/LpSkeleton-eSFIo4kD.js";
50
+ import { _ as _46 } from "./chunks/LpSlider-B1SF3pRV.js";
51
+ import { _ as _47 } from "./chunks/LpStat-CdbEFOvn.js";
52
+ import { _ as _48 } from "./chunks/LpStepper-5dFTrW21.js";
53
+ import { _ as _49 } from "./chunks/LpSwitch-v6fnccSM.js";
54
+ import { _ as _50 } from "./chunks/LpTable-CKGaUbEM.js";
55
+ import { _ as _51 } from "./chunks/LpTableOfContents-CjvxPMpU.js";
56
+ import { _ as _52 } from "./chunks/LpTabs-D7aTVPOb.js";
57
+ import { _ as _53 } from "./chunks/LpTextarea-CRAkhemZ.js";
56
58
  import { p as presets, a as parseTheme, s as serializeTheme } from "./chunks/LpThemeSwitcher-BgWwfkxs.js";
57
- import { D, _ as _53, b, c, d, e, f, l, g, h, n, r, t, i, u, v } from "./chunks/LpThemeSwitcher-BgWwfkxs.js";
58
- import { _ as _54 } from "./chunks/LpTilt-DuyKbpkA.js";
59
- import { _ as _55 } from "./chunks/LpToaster-gDB1EaU3.js";
60
- import { _ as _56 } from "./chunks/LpTooltip-CIwoRCag.js";
61
- import { _ as _57 } from "./chunks/LpTopologyCanvas-DVwyQfKa.js";
62
- import { _ as _58 } from "./chunks/LpUptimeBar-CB366k0O.js";
59
+ import { D, _ as _54, b, c, d, e, f, l, g, h, n, r, t, i, u, v } from "./chunks/LpThemeSwitcher-BgWwfkxs.js";
60
+ import { _ as _55 } from "./chunks/LpTilt-DuyKbpkA.js";
61
+ import { _ as _56 } from "./chunks/LpToaster-gDB1EaU3.js";
62
+ import { _ as _57 } from "./chunks/LpTooltip-CIwoRCag.js";
63
+ import { _ as _58 } from "./chunks/LpTopologyCanvas-DVwyQfKa.js";
64
+ import { _ as _59 } from "./chunks/LpUptimeBar-CB366k0O.js";
63
65
  import { deserializeLayout, serializeLayout } from "./layout/tree.js";
64
66
  import { addLeaf, addTab, countBlocks, countLeaves, makeLayout, moveLeaf, removeBlock, removeLeaf, reorderTab, resizeAt, setActiveTab } from "./layout/tree.js";
65
67
  import { blockTitle, defineBlocks } from "./layout/registry.js";
66
68
  import { useLayout } from "./layout/useLayout.js";
67
69
  import { useInputFilter } from "./composables/useInputFilter.js";
68
70
  import { useTilt } from "./composables/useTilt.js";
71
+ import { useZoomPan } from "./composables/useZoomPan.js";
69
72
  import { useToast } from "./composables/useToast.js";
70
73
  function defineTheme(base, overrides = {}) {
71
74
  const src = typeof base === "string" ? presets[base] : base;
@@ -125,39 +128,40 @@ export {
125
128
  _23 as LpIcon,
126
129
  _24 as LpInfraNode,
127
130
  _25 as LpInput,
128
- _26 as LpLink,
129
- _27 as LpLogViewer,
130
- _28 as LpModal,
131
- _29 as LpNotificationBell,
132
- _30 as LpNumberField,
133
- _31 as LpOtpInput,
134
- _32 as LpPagination,
135
- _33 as LpPasswordInput,
136
- _34 as LpPhoneInput,
137
- _35 as LpPopover,
138
- _36 as LpProgress,
139
- _37 as LpRadio,
140
- _38 as LpRadioGroup,
141
- _39 as LpScrollArea,
142
- _40 as LpSegmented,
143
- _41 as LpSelect,
144
- _42 as LpServiceNode,
145
- _43 as LpSidebar,
146
- _44 as LpSkeleton,
147
- _45 as LpSlider,
148
- _46 as LpStat,
149
- _47 as LpStepper,
150
- _48 as LpSwitch,
151
- _49 as LpTable,
152
- _50 as LpTableOfContents,
153
- _51 as LpTabs,
154
- _52 as LpTextarea,
155
- _53 as LpThemeSwitcher,
156
- _54 as LpTilt,
157
- _55 as LpToaster,
158
- _56 as LpTooltip,
159
- _57 as LpTopologyCanvas,
160
- _58 as LpUptimeBar,
131
+ _26 as LpLightbox,
132
+ _27 as LpLink,
133
+ _28 as LpLogViewer,
134
+ _29 as LpModal,
135
+ _30 as LpNotificationBell,
136
+ _31 as LpNumberField,
137
+ _32 as LpOtpInput,
138
+ _33 as LpPagination,
139
+ _34 as LpPasswordInput,
140
+ _35 as LpPhoneInput,
141
+ _36 as LpPopover,
142
+ _37 as LpProgress,
143
+ _38 as LpRadio,
144
+ _39 as LpRadioGroup,
145
+ _40 as LpScrollArea,
146
+ _41 as LpSegmented,
147
+ _42 as LpSelect,
148
+ _43 as LpServiceNode,
149
+ _44 as LpSidebar,
150
+ _45 as LpSkeleton,
151
+ _46 as LpSlider,
152
+ _47 as LpStat,
153
+ _48 as LpStepper,
154
+ _49 as LpSwitch,
155
+ _50 as LpTable,
156
+ _51 as LpTableOfContents,
157
+ _52 as LpTabs,
158
+ _53 as LpTextarea,
159
+ _54 as LpThemeSwitcher,
160
+ _55 as LpTilt,
161
+ _56 as LpToaster,
162
+ _57 as LpTooltip,
163
+ _58 as LpTopologyCanvas,
164
+ _59 as LpUptimeBar,
161
165
  UI_CONFIG_VERSION,
162
166
  addLeaf,
163
167
  addTab,
@@ -177,6 +181,7 @@ export {
177
181
  defineTheme,
178
182
  deserializeLayout,
179
183
  fileIcon,
184
+ fileNameOf,
180
185
  flagEmoji,
181
186
  formatModified,
182
187
  formatSize,
@@ -210,5 +215,6 @@ export {
210
215
  u as useTheme,
211
216
  useTilt,
212
217
  useToast,
218
+ useZoomPan,
213
219
  v as violet
214
220
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leavepulse/ui",
3
- "version": "0.15.4",
3
+ "version": "0.15.5",
4
4
  "type": "module",
5
5
  "description": "LeavePulse token-driven Vue component kit.",
6
6
  "repository": {
@@ -31,6 +31,7 @@ export const COMPONENT_NAMES = [
31
31
  "LpInfraNode",
32
32
  "LpInput",
33
33
  "LpLaneNode",
34
+ "LpLightbox",
34
35
  "LpLink",
35
36
  "LpLogViewer",
36
37
  "LpModal",