@maas/vue-equipment 0.14.3 → 0.15.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.
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@maas/vue-equipment/nuxt",
3
3
  "configKey": "vueEquipment",
4
- "version": "0.14.2"
4
+ "version": "0.14.4"
5
5
  }
@@ -19,19 +19,20 @@
19
19
  >
20
20
  <transition
21
21
  v-if="mappedOptions.backdrop || !!$slots.backdrop"
22
- :name="mappedOptions.transitions?.backdrop"
22
+ :name="backdropTransition"
23
23
  >
24
24
  <div
25
25
  v-show="innerActive"
26
26
  class="magic-drawer__backdrop"
27
- @click.self="close"
27
+ @click.self="closeIfAllowed"
28
28
  >
29
29
  <slot name="backdrop" />
30
30
  </div>
31
31
  </transition>
32
- <div class="magic-drawer__wrapper">
32
+
33
+ <div class="magic-drawer__wrapper" ref="wrapperRef">
33
34
  <transition
34
- :name="mappedOptions.transitions?.content"
35
+ :name="contentTransition"
35
36
  @before-leave="onBeforeLeave"
36
37
  @leave="onLeave"
37
38
  @after-leave="onAfterLeave"
@@ -39,20 +40,21 @@
39
40
  @enter="onEnter"
40
41
  @after-enter="onAfterEnter"
41
42
  >
42
- <div
43
- ref="elRef"
44
- v-show="innerActive"
45
- class="magic-drawer__content"
46
- @pointerdown="onPointerdown"
47
- :style="style"
48
- >
49
- <component
50
- v-if="component"
51
- v-bind="props"
52
- :is="component"
53
- @close="close"
54
- />
55
- <slot v-else />
43
+ <div v-show="innerActive" class="magic-drawer__content">
44
+ <div
45
+ ref="elRef"
46
+ class="magic-drawer__drag"
47
+ :style="style"
48
+ @pointerdown="onPointerdown"
49
+ >
50
+ <component
51
+ v-if="component"
52
+ v-bind="props"
53
+ :is="component"
54
+ @close="closeIfAllowed"
55
+ />
56
+ <slot v-else />
57
+ </div>
56
58
  </div>
57
59
  </transition>
58
60
  </div>
@@ -65,8 +67,11 @@
65
67
  import {
66
68
  ref,
67
69
  watch,
70
+ computed,
68
71
  nextTick,
69
72
  toValue,
73
+ onBeforeMount,
74
+ onBeforeUnmount,
70
75
  type Component,
71
76
  type MaybeRef,
72
77
  } from 'vue'
@@ -90,9 +95,9 @@ import '@maas/vue-equipment/utils/css/animations/slide-rtl-out.css'
90
95
  import '@maas/vue-equipment/utils/css/animations/slide-ttb-out.css'
91
96
  import '@maas/vue-equipment/utils/css/animations/slide-btt-out.css'
92
97
 
93
- // Prevent keys array from being merged with default
98
+ // Prevent deep merge of certain options
94
99
  const customDefu = createDefu((obj, key, value) => {
95
- if (key === 'keys') {
100
+ if (key === 'keys' || key === 'snapPoints') {
96
101
  obj[key] = value
97
102
  return true
98
103
  }
@@ -112,17 +117,20 @@ const props = withDefaults(defineProps<MagicDrawerProps>(), {
112
117
 
113
118
  const elRef = ref<HTMLDivElement | undefined>(undefined)
114
119
  const drawerRef = ref<HTMLElement | undefined>(undefined)
120
+ const wrapperRef = ref<HTMLDivElement | undefined>(undefined)
115
121
  const drawerApi = useDrawerApi(props.id, { focusTarget: drawerRef })
122
+
116
123
  const mappedOptions: typeof defaultOptions = customDefu(
117
124
  props.options,
118
125
  defaultOptions
119
126
  )
120
127
 
121
128
  const overshoot = ref(0)
122
- const { position, threshold } = mappedOptions
129
+ const { position, threshold, snapPoints, snapPoint, canClose } = mappedOptions
123
130
 
124
131
  const {
125
132
  isActive,
133
+ open,
126
134
  close,
127
135
  trapFocus,
128
136
  releaseFocus,
@@ -133,16 +141,21 @@ const {
133
141
  } = drawerApi
134
142
 
135
143
  const { onPointerdown, dragging, style } = useDrawerDrag({
144
+ elRef,
145
+ wrapperRef,
136
146
  position,
137
147
  threshold,
138
148
  overshoot,
139
- elRef,
149
+ snapPoints,
150
+ snapPoint,
151
+ canClose,
140
152
  close,
141
153
  })
142
154
 
143
155
  // Split isActive into two values to animate drawer smoothly
144
156
  const innerActive = ref(false)
145
157
  const wrapperActive = ref(false)
158
+ const wasActive = ref(false)
146
159
 
147
160
  const {
148
161
  onBeforeEnter,
@@ -161,19 +174,32 @@ const {
161
174
  trapFocus,
162
175
  releaseFocus,
163
176
  wrapperActive,
177
+ wasActive,
164
178
  })
165
179
 
166
- // Handle state
167
- async function onOpen() {
168
- wrapperActive.value = true
169
- await nextTick()
170
- innerActive.value = true
171
- }
180
+ // Surpress animation on initial mount if the options call for it
181
+ // To achive this, the transition names are set to undefined
182
+ const surpressTransition = computed(() => {
183
+ return (
184
+ mappedOptions.beforeMount.open &&
185
+ !mappedOptions.beforeMount.animate &&
186
+ !wasActive.value
187
+ )
188
+ })
172
189
 
173
- function onClose() {
174
- innerActive.value = false
175
- }
190
+ const backdropTransition = computed(() => {
191
+ return surpressTransition.value
192
+ ? undefined
193
+ : mappedOptions.transitions?.backdrop
194
+ })
195
+
196
+ const contentTransition = computed(() => {
197
+ return surpressTransition.value
198
+ ? undefined
199
+ : mappedOptions.transitions?.content
200
+ })
176
201
 
202
+ // Private functions
177
203
  function convertToPixels(value: string) {
178
204
  const regex = /^(\d*\.?\d+)\s*(rem|px)$/
179
205
 
@@ -199,6 +225,23 @@ function convertToPixels(value: string) {
199
225
  }
200
226
  }
201
227
 
228
+ async function onOpen() {
229
+ wrapperActive.value = true
230
+ await nextTick()
231
+ innerActive.value = true
232
+ }
233
+
234
+ function onClose() {
235
+ innerActive.value = false
236
+ }
237
+
238
+ // Public functions
239
+ function closeIfAllowed() {
240
+ if (canClose) {
241
+ close()
242
+ }
243
+ }
244
+
202
245
  function saveOvershoot() {
203
246
  const element = unrefElement(drawerRef)
204
247
  const overshootVar = getComputedStyle(element!).getPropertyValue(
@@ -208,7 +251,8 @@ function saveOvershoot() {
208
251
  overshoot.value = convertToPixels(overshootVar) || 0
209
252
  }
210
253
 
211
- if (mappedOptions.keys) {
254
+ // Lifecycle hooks and listeners
255
+ if (mappedOptions.keys && canClose) {
212
256
  for (const key of mappedOptions.keys) {
213
257
  onKeyStroke(key, (e) => {
214
258
  e.preventDefault()
@@ -229,8 +273,20 @@ watch(isActive, async (value) => {
229
273
  watch(innerActive, () => {
230
274
  saveOvershoot()
231
275
  })
276
+
277
+ onBeforeMount(async () => {
278
+ // Force open
279
+ if (mappedOptions.beforeMount.open) {
280
+ open()
281
+ }
282
+ })
283
+
284
+ // Reset state on unmount
285
+ onBeforeUnmount(() => {
286
+ close()
287
+ })
232
288
  </script>
233
289
 
234
290
  <style>
235
- :root{--magic-drawer-z-index:999;--magic-drawer-justify-content:center;--magic-drawer-align-items:flex-end;--magic-drawer-backdrop-color:rgba(0,0,0,.5);--magic-drawer-backdrop-filter:unset;--magic-drawer-content-overflow-y:auto;--magic-drawer-handle-wrapper-height:2rem;--magic-drawer-handle-width:3rem;--magic-drawer-handle-height:0.375rem;--magic-drawer-handle-color:#d4d4d8;--magic-drawer-handle-border-radius:0.25rem;--magic-drawer-enter-animation:slide-btt-in 300ms ease;--magic-drawer-leave-animation:slide-btt-out 300ms ease;--magic-drawer-drag-overshoot:4rem;--magic-drawer-drag-overshoot-x:0;--magic-drawer-drag-overshoot-y:0}.magic-drawer{align-items:var(--magic-drawer-align-items);background:transparent;border:none;color:inherit;display:flex;height:100%;inset:0;justify-content:var(--magic-drawer-justify-content);padding:0;position:fixed;width:100%;z-index:var(--magic-drawer-z-index)}.magic-drawer.-bottom{--magic-drawer-drag-overshoot-y:var(--magic-drawer-drag-overshoot)}.magic-drawer.-top{--magic-drawer-enter-animation:slide-ttb-in 300ms ease;--magic-drawer-leave-animation:slide-ttb-out 300ms ease;--magic-drawer-align-items:flex-start;--magic-drawer-drag-overshoot-y:calc(var(--magic-drawer-drag-overshoot)*-1)}.magic-drawer.-right{--magic-drawer-enter-animation:slide-rtl-in 300ms ease;--magic-drawer-leave-animation:slide-rtl-out 300ms ease;--magic-drawer-align-items:center;--magic-drawer-justify-content:flex-end;--magic-drawer-drag-overshoot-x:var(--magic-drawer-drag-overshoot)}.magic-drawer.-left{--magic-drawer-enter-animation:slide-ltr-in 300ms ease;--magic-drawer-leave-animation:slide-ltr-out 300ms ease;--magic-drawer-align-items:center;--magic-drawer-justify-content:flex-start;--magic-drawer-drag-overshoot-x:calc(var(--magic-drawer-drag-overshoot)*-1)}.magic-drawer__wrapper{transform:translate(var(--magic-drawer-drag-overshoot-x),var(--magic-drawer-drag-overshoot-y));width:100%}.magic-drawer__content{-webkit-overflow-scrolling:touch;align-items:var(--magic-drawer-align-items);cursor:grab;display:flex;justify-content:var(--magic-drawer-justify-content);max-height:100%;overflow-y:var(--magic-drawer-content-overflow-y);position:relative;scroll-behavior:smooth;width:100%}.magic-drawer.-dragging .magic-drawer__content{cursor:grabbing}.magic-drawer__backdrop{-webkit-backdrop-filter:var(--magic-drawer-backdrop-filter);backdrop-filter:var(--magic-drawer-backdrop-filter);background-color:var(--magic-drawer-backdrop-color);bottom:0;height:100%;left:0;position:fixed;right:0;top:0;width:100%;z-index:-1}.magic-drawer--content-enter-active{animation:var(--magic-drawer-enter-animation)}.magic-drawer--content-leave-active{animation:var(--magic-drawer-leave-animation)}.magic-drawer--backdrop-enter-active{animation:fade-in .3s ease}.magic-drawer--backdrop-leave-active{animation:fade-out .3s ease}
291
+ :root{--magic-drawer-height:75svh;--magic-drawer-z-index:999;--magic-drawer-justify-content:center;--magic-drawer-align-items:flex-end;--magic-drawer-backdrop-color:rgba(0,0,0,.5);--magic-drawer-backdrop-filter:unset;--magic-drawer-content-overflow-y:auto;--magic-drawer-handle-wrapper-height:2rem;--magic-drawer-handle-width:3rem;--magic-drawer-handle-height:0.375rem;--magic-drawer-handle-color:#d4d4d8;--magic-drawer-handle-border-radius:0.25rem;--magic-drawer-enter-animation:slide-btt-in 300ms ease;--magic-drawer-leave-animation:slide-btt-out 300ms ease;--magic-drawer-drag-overshoot:4rem;--magic-drawer-drag-overshoot-x:0;--magic-drawer-drag-overshoot-y:0}.magic-drawer{align-items:var(--magic-drawer-align-items);background:transparent;border:none;color:inherit;display:flex;height:100%;inset:0;justify-content:var(--magic-drawer-justify-content);padding:0;position:fixed;width:100%;z-index:var(--magic-drawer-z-index)}.magic-drawer.-bottom{--magic-drawer-drag-overshoot-y:var(--magic-drawer-drag-overshoot)}.magic-drawer.-top{--magic-drawer-enter-animation:slide-ttb-in 300ms ease;--magic-drawer-leave-animation:slide-ttb-out 300ms ease;--magic-drawer-align-items:flex-start;--magic-drawer-drag-overshoot-y:calc(var(--magic-drawer-drag-overshoot)*-1)}.magic-drawer.-right{--magic-drawer-enter-animation:slide-rtl-in 300ms ease;--magic-drawer-leave-animation:slide-rtl-out 300ms ease;--magic-drawer-align-items:center;--magic-drawer-justify-content:flex-end;--magic-drawer-drag-overshoot-x:var(--magic-drawer-drag-overshoot)}.magic-drawer.-left{--magic-drawer-enter-animation:slide-ltr-in 300ms ease;--magic-drawer-leave-animation:slide-ltr-out 300ms ease;--magic-drawer-align-items:center;--magic-drawer-justify-content:flex-start;--magic-drawer-drag-overshoot-x:calc(var(--magic-drawer-drag-overshoot)*-1)}.magic-drawer__wrapper{height:calc(var(--magic-drawer-height) + var(--magic-drawer-drag-overshoot));pointer-events:none;transform:translate(var(--magic-drawer-drag-overshoot-x),var(--magic-drawer-drag-overshoot-y));width:100%}.magic-drawer__content{height:100%;max-height:100%;position:relative;width:100%}.magic-drawer__drag{-webkit-overflow-scrolling:touch;align-items:var(--magic-drawer-align-items);cursor:grab;display:flex;height:100%;justify-content:var(--magic-drawer-justify-content);overflow-y:var(--magic-drawer-content-overflow-y);pointer-events:auto;position:relative;scroll-behavior:smooth;width:100%}.magic-drawer.-dragging .magic-drawer__content{cursor:grabbing}.magic-drawer__backdrop{-webkit-backdrop-filter:var(--magic-drawer-backdrop-filter);backdrop-filter:var(--magic-drawer-backdrop-filter);background-color:var(--magic-drawer-backdrop-color);bottom:0;height:100%;left:0;position:fixed;right:0;top:0;width:100%;z-index:-1}.magic-drawer--content-enter-active{animation:var(--magic-drawer-enter-animation)}.magic-drawer--content-leave-active{animation:var(--magic-drawer-leave-animation)}.magic-drawer--backdrop-enter-active{animation:fade-in .3s ease}.magic-drawer--backdrop-leave-active{animation:fade-out .3s ease}
236
292
  </style>
@@ -10,6 +10,7 @@ type UseDrawerCallbackArgs = {
10
10
  trapFocus: () => void;
11
11
  releaseFocus: () => void;
12
12
  wrapperActive: Ref<boolean>;
13
+ wasActive: Ref<boolean>;
13
14
  };
14
15
  export declare function useDrawerCallback(args: UseDrawerCallbackArgs): {
15
16
  onBeforeEnter: (_el: Element) => void;
@@ -10,7 +10,8 @@ export function useDrawerCallback(args) {
10
10
  unlockScroll,
11
11
  trapFocus,
12
12
  releaseFocus,
13
- wrapperActive
13
+ wrapperActive,
14
+ wasActive
14
15
  } = args;
15
16
  function onBeforeEnter(_el) {
16
17
  useDrawerEmitter().emit("beforeEnter", toValue(id));
@@ -30,6 +31,7 @@ export function useDrawerCallback(args) {
30
31
  await nextTick();
31
32
  trapFocus();
32
33
  }
34
+ wasActive.value = true;
33
35
  }
34
36
  function onBeforeLeave(_el) {
35
37
  useDrawerEmitter().emit("beforeLeave", toValue(id));
@@ -2,13 +2,19 @@ import { type MaybeRef } from 'vue';
2
2
  import { type DefaultOptions } from '../../utils/defaultOptions.js';
3
3
  type UseDrawerDragArgs = {
4
4
  elRef: MaybeRef<HTMLDivElement | undefined>;
5
+ wrapperRef: MaybeRef<HTMLDivElement | undefined>;
5
6
  position: MaybeRef<DefaultOptions['position']>;
6
7
  threshold: MaybeRef<DefaultOptions['threshold']>;
8
+ snapPoints: MaybeRef<DefaultOptions['snapPoints']>;
9
+ snapPoint: MaybeRef<DefaultOptions['snapPoint']>;
10
+ canClose: MaybeRef<DefaultOptions['canClose']>;
7
11
  overshoot: MaybeRef<number>;
8
12
  close: () => void;
9
13
  };
10
14
  export declare function useDrawerDrag(args: UseDrawerDragArgs): {
11
15
  style: import("vue").ComputedRef<string>;
16
+ draggedX: import("vue").Ref<number>;
17
+ draggedY: import("vue").Ref<number>;
12
18
  dragging: import("vue").Ref<boolean>;
13
19
  onPointerdown: (e: PointerEvent) => void;
14
20
  };
@@ -4,20 +4,46 @@ import {
4
4
  onMounted,
5
5
  watch,
6
6
  onBeforeUnmount,
7
- toValue
7
+ toValue,
8
+ nextTick
8
9
  } from "vue";
9
10
  import { useEventListener, unrefElement } from "@vueuse/core";
10
11
  import { interpolate } from "@maas/vue-equipment/utils";
11
12
  import { useDrawerEmitter } from "../useDrawerEmitter.mjs";
13
+ import { useDrawerSnap } from "./useDrawerSnap.mjs";
12
14
  export function useDrawerDrag(args) {
13
- const { elRef, position, overshoot, threshold, close } = args;
15
+ const {
16
+ elRef,
17
+ wrapperRef,
18
+ snapPoints,
19
+ position,
20
+ overshoot,
21
+ threshold,
22
+ snapPoint,
23
+ canClose,
24
+ close
25
+ } = args;
26
+ const { findClosestSnapPoint, mapSnapPoint, drawerHeight, drawerWidth } = useDrawerSnap({
27
+ wrapperRef,
28
+ snapPoints,
29
+ canClose,
30
+ position,
31
+ overshoot
32
+ });
14
33
  const dragStart = ref(void 0);
15
- const originX = ref(0);
16
- const originY = ref(0);
17
34
  const dragging = ref(false);
18
35
  const shouldClose = ref(false);
36
+ const interpolateTo = ref(void 0);
19
37
  const elRect = ref(void 0);
38
+ let cancelPointerup = void 0;
20
39
  let cancelPointermove = void 0;
40
+ const originX = ref(0);
41
+ const originY = ref(0);
42
+ const snappedY = ref(0);
43
+ const snappedX = ref(0);
44
+ const directionY = ref("absolute");
45
+ const directionX = ref("absolute");
46
+ const hasSnapPoints = computed(() => toValue(snapPoints).length > 1);
21
47
  const draggedX = ref(0);
22
48
  const draggedY = ref(0);
23
49
  const style = computed(
@@ -26,53 +52,125 @@ export function useDrawerDrag(args) {
26
52
  function getSizes() {
27
53
  elRect.value = unrefElement(elRef)?.getBoundingClientRect();
28
54
  }
29
- function checkPosition() {
55
+ async function checkPosition() {
30
56
  switch (position) {
31
57
  case "bottom":
32
- if (draggedY.value > toValue(threshold).distance) {
33
- shouldClose.value = true;
58
+ const snapPointB = await findClosestSnapPoint({
59
+ draggedX,
60
+ draggedY,
61
+ direction: directionY.value
62
+ });
63
+ if (draggedY.value > toValue(threshold).distance || hasSnapPoints.value) {
64
+ if (snapPointB === drawerHeight.value) {
65
+ shouldClose.value = true;
66
+ } else {
67
+ interpolateTo.value = snapPointB;
68
+ }
34
69
  }
35
70
  break;
36
71
  case "top":
37
- if (draggedY.value < toValue(threshold).distance * -1) {
38
- shouldClose.value = true;
72
+ const snapPointT = await findClosestSnapPoint({
73
+ draggedX,
74
+ draggedY,
75
+ direction: directionY.value
76
+ });
77
+ if (draggedY.value < toValue(threshold).distance * -1 || hasSnapPoints.value) {
78
+ if (snapPointT === drawerHeight.value * -1) {
79
+ shouldClose.value = true;
80
+ } else {
81
+ interpolateTo.value = snapPointT;
82
+ }
39
83
  }
40
84
  break;
41
85
  case "right":
42
- if (draggedX.value > toValue(threshold).distance) {
43
- shouldClose.value = true;
86
+ const snapPointR = await findClosestSnapPoint({
87
+ draggedX,
88
+ draggedY,
89
+ direction: directionX.value
90
+ });
91
+ if (draggedX.value > toValue(threshold).distance || hasSnapPoints.value) {
92
+ if (snapPointR === drawerWidth.value) {
93
+ shouldClose.value = true;
94
+ } else {
95
+ interpolateTo.value = snapPointR;
96
+ }
44
97
  }
45
98
  break;
46
99
  case "left":
47
- if (draggedX.value < toValue(threshold).distance * -1) {
48
- shouldClose.value = true;
100
+ const snapPointL = await findClosestSnapPoint({
101
+ draggedX,
102
+ draggedY,
103
+ direction: directionX.value
104
+ });
105
+ if (draggedX.value < toValue(threshold).distance * -1 || hasSnapPoints.value) {
106
+ if (snapPointL === drawerWidth.value * -1) {
107
+ shouldClose.value = true;
108
+ } else {
109
+ interpolateTo.value = snapPointL;
110
+ }
49
111
  }
50
112
  break;
51
113
  }
52
114
  }
53
- function checkMomentum({ x, y }) {
115
+ async function checkMomentum({ x, y }) {
54
116
  const elapsed = Date.now() - dragStart.value.getTime();
55
117
  const velocityX = (x - originX.value) / elapsed;
56
118
  const velocityY = (y - originY.value) / elapsed;
57
119
  switch (position) {
58
120
  case "bottom":
121
+ const snapPointB = await findClosestSnapPoint({
122
+ draggedX,
123
+ draggedY,
124
+ direction: directionY.value
125
+ });
59
126
  if (velocityY > toValue(threshold).momentum) {
60
- shouldClose.value = true;
127
+ if (snapPointB === drawerHeight.value) {
128
+ shouldClose.value = true;
129
+ } else {
130
+ interpolateTo.value = snapPointB;
131
+ }
61
132
  }
62
133
  break;
63
134
  case "top":
135
+ const snapPointT = await findClosestSnapPoint({
136
+ draggedX,
137
+ draggedY,
138
+ direction: directionY.value
139
+ });
64
140
  if (velocityY < toValue(threshold).momentum * -1) {
65
- shouldClose.value = true;
141
+ if (snapPointT === drawerHeight.value) {
142
+ shouldClose.value = true;
143
+ } else {
144
+ interpolateTo.value = snapPointT;
145
+ }
66
146
  }
67
147
  break;
68
148
  case "right":
149
+ const snapPointR = await findClosestSnapPoint({
150
+ draggedX,
151
+ draggedY,
152
+ direction: directionX.value
153
+ });
69
154
  if (velocityX > toValue(threshold).momentum) {
70
- shouldClose.value = true;
155
+ if (snapPointR === drawerWidth.value) {
156
+ shouldClose.value = true;
157
+ } else {
158
+ interpolateTo.value = snapPointR;
159
+ }
71
160
  }
72
161
  break;
73
162
  case "left":
74
- if (velocityX < toValue(threshold).momentum * -1) {
75
- shouldClose.value = true;
163
+ const snapPointL = await findClosestSnapPoint({
164
+ draggedX,
165
+ draggedY,
166
+ direction: directionX.value
167
+ });
168
+ if (velocityX > toValue(threshold).momentum) {
169
+ if (snapPointL === drawerWidth.value) {
170
+ shouldClose.value = true;
171
+ } else {
172
+ interpolateTo.value = snapPointL;
173
+ }
76
174
  }
77
175
  break;
78
176
  }
@@ -99,59 +197,109 @@ export function useDrawerDrag(args) {
99
197
  function setDragged({ x, y }) {
100
198
  switch (position) {
101
199
  case "bottom":
102
- draggedY.value = clamp(y - originY.value, 0, toValue(overshoot) * -1);
200
+ const newDraggedB = clamp(y - originY.value, 0, toValue(overshoot) * -1);
201
+ directionY.value = newDraggedB < draggedY.value ? "below" : "above";
202
+ draggedY.value = newDraggedB;
103
203
  break;
104
204
  case "top":
105
- draggedY.value = clamp(y - originY.value, 0, toValue(overshoot));
205
+ const newDraggedT = clamp(y - originY.value, 0, toValue(overshoot));
206
+ directionY.value = newDraggedT < draggedY.value ? "below" : "above";
207
+ draggedY.value = newDraggedT;
106
208
  break;
107
209
  case "right":
108
- draggedX.value = clamp(x - originX.value, 0, toValue(overshoot) * -1);
210
+ const newDraggedR = clamp(x - originX.value, 0, toValue(overshoot) * -1);
211
+ directionX.value = newDraggedR < draggedX.value ? "below" : "above";
212
+ draggedX.value = newDraggedR;
109
213
  break;
110
214
  case "left":
111
- draggedX.value = clamp(x - originX.value, 0, toValue(overshoot));
215
+ const newDraggedL = clamp(x - originX.value, 0, toValue(overshoot));
216
+ directionX.value = newDraggedL < draggedX.value ? "below" : "above";
217
+ draggedX.value = newDraggedL;
218
+ break;
219
+ }
220
+ }
221
+ async function setInitial() {
222
+ await nextTick();
223
+ switch (position) {
224
+ case "top":
225
+ case "bottom":
226
+ draggedY.value = await findClosestSnapPoint({
227
+ draggedX,
228
+ draggedY: mapSnapPoint(toValue(snapPoint)) || 0
229
+ }) || 0;
230
+ break;
231
+ case "left":
232
+ case "right":
233
+ draggedX.value = await findClosestSnapPoint({
234
+ draggedX: mapSnapPoint(toValue(snapPoint)) || 0,
235
+ draggedY
236
+ }) || 0;
112
237
  break;
113
238
  }
114
239
  }
115
240
  function resetStateAndListeners() {
116
241
  dragging.value = false;
117
242
  shouldClose.value = false;
243
+ interpolateTo.value = void 0;
244
+ cancelPointerup?.();
118
245
  cancelPointermove?.();
119
246
  }
120
247
  function resetDragged() {
121
248
  draggedX.value = 0;
122
249
  draggedY.value = 0;
123
250
  }
251
+ function resetSnapped() {
252
+ snappedX.value = 0;
253
+ snappedY.value = 0;
254
+ }
124
255
  function emitterCallback(event, _payload) {
125
256
  if (event === "afterLeave") {
126
257
  resetDragged();
258
+ resetSnapped();
259
+ }
260
+ }
261
+ async function interpolateDragged(target) {
262
+ switch (position) {
263
+ case "bottom":
264
+ case "top":
265
+ snappedY.value = target;
266
+ interpolate({
267
+ from: draggedY.value,
268
+ to: target,
269
+ duration: 300,
270
+ callback: (value) => {
271
+ draggedY.value = value;
272
+ }
273
+ });
274
+ break;
275
+ case "right":
276
+ case "left":
277
+ snappedX.value = target;
278
+ interpolate({
279
+ from: draggedX.value,
280
+ to: target,
281
+ duration: 300,
282
+ callback: (value) => {
283
+ draggedX.value = value;
284
+ }
285
+ });
286
+ break;
127
287
  }
128
288
  }
129
289
  function onPointerup(e) {
130
290
  if (shouldClose.value) {
131
291
  close();
292
+ } else if (interpolateTo.value || interpolateTo.value === 0) {
293
+ interpolateDragged(interpolateTo.value);
132
294
  } else {
133
295
  switch (position) {
134
296
  case "bottom":
135
297
  case "top":
136
- interpolate({
137
- from: draggedY.value,
138
- to: 0,
139
- duration: 50,
140
- callback: (value) => {
141
- draggedY.value = value;
142
- }
143
- });
298
+ interpolateDragged(snappedY.value);
144
299
  break;
145
300
  case "right":
146
301
  case "left":
147
- interpolate({
148
- from: draggedX.value,
149
- to: 0,
150
- duration: 50,
151
- callback: (value) => {
152
- draggedX.value = value;
153
- }
154
- });
302
+ interpolateDragged(snappedX.value);
155
303
  break;
156
304
  }
157
305
  }
@@ -173,10 +321,10 @@ export function useDrawerDrag(args) {
173
321
  }
174
322
  ;
175
323
  e.target.setPointerCapture(e.pointerId);
176
- useEventListener(document, "pointerup", onPointerup);
324
+ cancelPointerup = useEventListener(document, "pointerup", onPointerup);
177
325
  cancelPointermove = useEventListener(document, "pointermove", onPointermove);
178
- originX.value = e.screenX;
179
- originY.value = e.screenY;
326
+ originX.value = e.screenX - draggedX.value;
327
+ originY.value = e.screenY - draggedY.value;
180
328
  dragStart.value = /* @__PURE__ */ new Date();
181
329
  onPointermove(e);
182
330
  e.preventDefault();
@@ -186,9 +334,10 @@ export function useDrawerDrag(args) {
186
334
  useDrawerEmitter().on("*", emitterCallback);
187
335
  });
188
336
  watch(
189
- () => unrefElement(elRef),
337
+ () => [unrefElement(elRef), unrefElement(wrapperRef)],
190
338
  () => {
191
339
  getSizes();
340
+ setInitial();
192
341
  }
193
342
  );
194
343
  onBeforeUnmount(() => {
@@ -196,6 +345,8 @@ export function useDrawerDrag(args) {
196
345
  });
197
346
  return {
198
347
  style,
348
+ draggedX,
349
+ draggedY,
199
350
  dragging,
200
351
  onPointerdown
201
352
  };
@@ -0,0 +1,22 @@
1
+ import { type MaybeRef } from 'vue';
2
+ import { type DefaultOptions } from '../../utils/defaultOptions.js';
3
+ import { type SnapPoint } from '../../types.js';
4
+ type UseDrawerSnapArgs = {
5
+ wrapperRef: MaybeRef<HTMLDivElement | undefined>;
6
+ position: MaybeRef<DefaultOptions['position']>;
7
+ snapPoints: MaybeRef<DefaultOptions['snapPoints']>;
8
+ canClose: MaybeRef<DefaultOptions['canClose']>;
9
+ overshoot: MaybeRef<number>;
10
+ };
11
+ type FindClosestSnapPointArgs = {
12
+ draggedY: MaybeRef<number>;
13
+ draggedX: MaybeRef<number>;
14
+ direction?: 'below' | 'above' | 'absolute';
15
+ };
16
+ export declare function useDrawerSnap(args: UseDrawerSnapArgs): {
17
+ findClosestSnapPoint: (args: FindClosestSnapPointArgs) => Promise<number | undefined>;
18
+ mapSnapPoint: (snapPoint: SnapPoint) => number | undefined;
19
+ drawerHeight: import("vue").ComputedRef<number>;
20
+ drawerWidth: import("vue").ComputedRef<number>;
21
+ };
22
+ export {};
@@ -0,0 +1,135 @@
1
+ import { ref, computed, toValue, nextTick } from "vue";
2
+ import { unrefElement } from "@vueuse/core";
3
+ import { mapValue } from "@maas/vue-equipment/utils";
4
+ export function useDrawerSnap(args) {
5
+ const { snapPoints, position, wrapperRef, overshoot, canClose } = args;
6
+ const wrapperRect = ref(void 0);
7
+ const mappedSnapPoints = computed(() => {
8
+ const extended = toValue(canClose) ? [...toValue(snapPoints), 0] : toValue(snapPoints);
9
+ const mapped = extended.map((snapPoint) => {
10
+ return mapSnapPoint(snapPoint);
11
+ });
12
+ const filtered = mapped.filter((snapPoint) => !!snapPoint).sort((a, b) => a - b);
13
+ return filtered;
14
+ });
15
+ const drawerHeight = computed(
16
+ () => wrapperRect.value?.height - toValue(overshoot)
17
+ );
18
+ const drawerWidth = computed(
19
+ () => wrapperRect.value?.width - toValue(overshoot)
20
+ );
21
+ async function getSizes() {
22
+ wrapperRect.value = unrefElement(wrapperRef)?.getBoundingClientRect();
23
+ await nextTick();
24
+ }
25
+ function findClosestNumber(args2) {
26
+ const { number, numbers, direction } = args2;
27
+ let filtered = numbers;
28
+ switch (direction) {
29
+ case "above":
30
+ filtered = numbers.filter((num) => num > number);
31
+ break;
32
+ case "below":
33
+ filtered = numbers.filter((num) => num < number);
34
+ break;
35
+ }
36
+ if (filtered.length === 0) {
37
+ switch (direction) {
38
+ case "above":
39
+ return Math.max(...numbers);
40
+ case "below":
41
+ return Math.min(...numbers);
42
+ default:
43
+ return void 0;
44
+ }
45
+ }
46
+ const closestNumber = filtered.reduce(
47
+ (closest, current) => Math.abs(current - number) < Math.abs(closest - number) ? current : closest
48
+ );
49
+ return closestNumber;
50
+ }
51
+ function mapSnapPoint(snapPoint) {
52
+ if (typeof snapPoint === "number") {
53
+ const reversedSnapPoint = mapValue(snapPoint, 0, 1, 1, 0);
54
+ const vh = window.innerHeight;
55
+ const vw = window.innerWidth;
56
+ if (wrapperRect.value === void 0) {
57
+ return void 0;
58
+ }
59
+ switch (position) {
60
+ case "bottom":
61
+ if (reversedSnapPoint === 1)
62
+ return drawerHeight.value;
63
+ else if (reversedSnapPoint === 0)
64
+ return 0;
65
+ else
66
+ return vh * reversedSnapPoint - wrapperRect.value?.top;
67
+ case "top":
68
+ if (reversedSnapPoint === 1)
69
+ return drawerHeight.value * -1;
70
+ else if (reversedSnapPoint === 0)
71
+ return 0;
72
+ else
73
+ return vh * reversedSnapPoint - wrapperRect.value?.bottom;
74
+ case "right":
75
+ if (reversedSnapPoint === 1)
76
+ return drawerWidth.value;
77
+ else if (reversedSnapPoint === 0)
78
+ return 0;
79
+ else
80
+ return vw * reversedSnapPoint - wrapperRect.value?.left;
81
+ case "left":
82
+ if (reversedSnapPoint === 1)
83
+ return drawerWidth.value * -1;
84
+ else if (reversedSnapPoint === 0)
85
+ return 0;
86
+ else
87
+ return vw * reversedSnapPoint - wrapperRect.value?.right;
88
+ default:
89
+ return 0;
90
+ }
91
+ } else {
92
+ const parsedSnapPoint = parseFloat(snapPoint);
93
+ if (drawerHeight.value === void 0 || drawerWidth.value === void 0) {
94
+ return void 0;
95
+ }
96
+ switch (position) {
97
+ case "bottom":
98
+ return drawerHeight.value - parsedSnapPoint;
99
+ case "top":
100
+ return (drawerHeight.value - parsedSnapPoint) * -1;
101
+ case "right":
102
+ return drawerWidth.value - parsedSnapPoint;
103
+ case "left":
104
+ return (drawerWidth.value - parsedSnapPoint) * -1;
105
+ default:
106
+ return parseFloat(snapPoint);
107
+ }
108
+ }
109
+ }
110
+ async function findClosestSnapPoint(args2) {
111
+ const { draggedY, draggedX, direction = "absolute" } = args2;
112
+ await getSizes();
113
+ let closest = 0;
114
+ switch (position) {
115
+ case "bottom":
116
+ case "top":
117
+ closest = findClosestNumber({
118
+ number: toValue(draggedY),
119
+ numbers: mappedSnapPoints.value,
120
+ direction
121
+ });
122
+ break;
123
+ case "right":
124
+ case "left":
125
+ closest = findClosestNumber({
126
+ number: toValue(draggedX),
127
+ numbers: mappedSnapPoints.value,
128
+ direction
129
+ });
130
+ break;
131
+ }
132
+ return closest;
133
+ }
134
+ return { findClosestSnapPoint, mapSnapPoint, drawerHeight, drawerWidth };
135
+ }
@@ -1,3 +1,4 @@
1
+ export type SnapPoint = number | `${string}px`;
1
2
  export type DrawerOptions = {
2
3
  position?: 'top' | 'right' | 'bottom' | 'left';
3
4
  backdrop?: boolean;
@@ -18,6 +19,13 @@ export type DrawerOptions = {
18
19
  };
19
20
  tag?: 'dialog' | 'div';
20
21
  keys?: string[] | false;
22
+ beforeMount?: {
23
+ open: boolean;
24
+ animate: boolean;
25
+ };
26
+ snapPoints?: SnapPoint[];
27
+ snapPoint?: SnapPoint;
28
+ canClose?: boolean;
21
29
  };
22
30
  export type DrawerEvents = {
23
31
  beforeEnter: string;
@@ -1,7 +1,7 @@
1
1
  const defaultOptions = {
2
2
  position: "bottom",
3
3
  backdrop: true,
4
- focusTrap: true,
4
+ focusTrap: false,
5
5
  scrollLock: true,
6
6
  scrollLockPadding: true,
7
7
  teleport: {
@@ -17,6 +17,13 @@ const defaultOptions = {
17
17
  momentum: 1
18
18
  },
19
19
  tag: "dialog",
20
- keys: ["Escape"]
20
+ keys: ["Escape"],
21
+ beforeMount: {
22
+ open: false,
23
+ animate: false
24
+ },
25
+ snapPoints: [1],
26
+ snapPoint: 1,
27
+ canClose: true
21
28
  };
22
29
  export { defaultOptions };
@@ -60,6 +60,7 @@ import {
60
60
  watch,
61
61
  nextTick,
62
62
  toValue,
63
+ onBeforeUnmount,
63
64
  type Component,
64
65
  type MaybeRef,
65
66
  } from 'vue'
@@ -159,6 +160,11 @@ watch(isActive, async (value) => {
159
160
  onClose()
160
161
  }
161
162
  })
163
+
164
+ // Reset state on unmount
165
+ onBeforeUnmount(() => {
166
+ close()
167
+ })
162
168
  </script>
163
169
 
164
170
  <style>
@@ -5,7 +5,7 @@
5
5
  </template>
6
6
 
7
7
  <script setup lang="ts">
8
- import { ref, inject, computed, onMounted, toRaw, watch } from 'vue'
8
+ import { ref, inject, computed, onMounted, watch } from 'vue'
9
9
  import { unrefElement } from '@vueuse/core'
10
10
  import { animate, type Easing } from 'motion'
11
11
  import { ScrollProgressKey } from '../symbols'
@@ -37,26 +37,21 @@ function clampValue(value, min, max) {
37
37
 
38
38
  // src/functions/interpolate.ts
39
39
  function interpolate(args) {
40
- const {
41
- from,
42
- to,
43
- duration,
44
- callback,
45
- easing = (t) => t * (2 - t),
46
- interval = 1
47
- } = args;
48
- const steps = Math.ceil(duration / interval);
49
- const stepSize = 1 / steps;
50
- let currentStep = 0;
51
- const intervalId = setInterval(() => {
52
- const progress = currentStep * stepSize;
53
- const value = from + (to - from) * easing(progress);
40
+ const { from, to, duration, callback, easing = (t) => t * (2 - t) } = args;
41
+ let startTime;
42
+ const speed = 1;
43
+ function animate(timestamp) {
44
+ if (!startTime)
45
+ startTime = timestamp;
46
+ const progress = Math.min(1, (timestamp - startTime) / (duration * speed));
47
+ const easedProgress = easing(progress);
48
+ const value = from + (to - from) * easedProgress;
54
49
  callback(value);
55
- currentStep++;
56
- if (currentStep >= steps) {
57
- clearInterval(intervalId);
50
+ if (progress < 1) {
51
+ requestAnimationFrame(animate);
58
52
  }
59
- }, interval);
53
+ }
54
+ requestAnimationFrame(animate);
60
55
  }
61
56
 
62
57
  // src/functions/isIOS.ts
@@ -1 +1 @@
1
- {"version":3,"sources":["../../packages/utils/index.ts","../../packages/utils/src/functions/clampValue.ts","../../packages/utils/src/functions/interpolate.ts","../../packages/utils/src/functions/isIOS.ts","../../packages/utils/src/functions/mapValue.ts","../../packages/utils/src/functions/uuid.ts","../../packages/utils/src/functions/uniq.ts","../../packages/utils/src/functions/slugify.ts"],"sourcesContent":["export * from './src/functions/clampValue'\nexport * from './src/functions/interpolate'\nexport * from './src/functions/isIOS'\nexport * from './src/functions/mapValue'\nexport * from './src/functions/uuid'\nexport * from './src/functions/uniq'\nexport * from './src/functions/slugify'\n\nexport type * from './src/types'\n","export function clampValue(value: number, min: number, max: number) {\n return value <= min ? min : value >= max ? max : value\n}\n","export type InterpolateArgs = {\n from: number\n to: number\n duration: number\n interval?: number\n easing?: (t: number) => number\n callback: (result: number) => void\n}\n\nexport function interpolate(args: InterpolateArgs) {\n const {\n from,\n to,\n duration,\n callback,\n easing = (t: number) => t * (2 - t),\n interval = 1,\n } = args\n\n const steps = Math.ceil(duration / interval)\n const stepSize = 1 / steps\n\n let currentStep = 0\n\n const intervalId = setInterval(() => {\n const progress = currentStep * stepSize\n const value = from + (to - from) * easing(progress)\n\n callback(value)\n currentStep++\n\n if (currentStep >= steps) {\n clearInterval(intervalId)\n }\n }, interval)\n}\n","export function isIOS() {\n if (typeof window === 'undefined') return false\n return /iPad|iPhone|iPod/.test(navigator?.userAgent)\n}\n","export function mapValue(\n value: number,\n inMin: number,\n inMax: number,\n outMin: number,\n outMax: number,\n) {\n return ((value - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin\n}\n","// This implementation is meant for internal use only.\n// It is only used to generate a unique IDs for the `key` props.\n// It should not replace crypto.randomUUID() or window.crypto.randomUUID().\n\nexport function uuid() {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'\n .split('')\n .reduce(\n (c, i) =>\n c +\n (i === 'x'\n ? Math.floor(Math.random() * 0xf).toString(16)\n : i === 'y'\n ? Math.floor(Math.random() * 4 + 8).toString(16)\n : i),\n '',\n )\n}\n","export function uniq<T extends any[]>(a: T) {\n return Array.from(new Set(a))\n}\n","export interface SlugifyOptions {\n separator?: string\n trim?: boolean\n remove?: RegExp\n strict?: boolean\n lowercase?: boolean\n}\n\nconst defaultOptions: SlugifyOptions = {\n separator: '-',\n trim: true,\n remove: undefined,\n strict: true,\n lowercase: true,\n}\n\nexport function slugify(string: string, options?: SlugifyOptions): string {\n if (typeof string !== 'string') {\n throw new Error('slugify: string argument expected')\n }\n\n // Merge provided options with default options\n const _options = { ...defaultOptions, ...options }\n\n const charMap: { [key: string]: string } = {}\n\n let slug = string\n .normalize()\n .split('')\n .reduce(function (result, ch) {\n let appendChar = charMap[ch]\n if (appendChar === undefined) appendChar = ch\n if (appendChar === _options?.separator) appendChar = ' '\n return (\n result +\n appendChar.replace(_options?.remove || /[^\\w\\s$*_+~.()'\"!\\-:@]+/g, '')\n )\n }, '')\n\n if (_options.strict) {\n slug = slug.replace(/[^A-Za-z0-9\\s]/g, '')\n }\n\n if (_options.trim) {\n slug = slug.trim()\n }\n\n if (_options.separator) {\n slug = slug.replace(/ +/g, _options.separator)\n }\n\n if (_options.lowercase) {\n slug = slug.toLocaleLowerCase()\n }\n\n return slug\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAO,SAAS,WAAW,OAAe,KAAa,KAAa;AAClE,SAAO,SAAS,MAAM,MAAM,SAAS,MAAM,MAAM;AACnD;;;ACOO,SAAS,YAAY,MAAuB;AACjD,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,CAAC,MAAc,KAAK,IAAI;AAAA,IACjC,WAAW;AAAA,EACb,IAAI;AAEJ,QAAM,QAAQ,KAAK,KAAK,WAAW,QAAQ;AAC3C,QAAM,WAAW,IAAI;AAErB,MAAI,cAAc;AAElB,QAAM,aAAa,YAAY,MAAM;AACnC,UAAM,WAAW,cAAc;AAC/B,UAAM,QAAQ,QAAQ,KAAK,QAAQ,OAAO,QAAQ;AAElD,aAAS,KAAK;AACd;AAEA,QAAI,eAAe,OAAO;AACxB,oBAAc,UAAU;AAAA,IAC1B;AAAA,EACF,GAAG,QAAQ;AACb;;;ACnCO,SAAS,QAAQ;AACtB,MAAI,OAAO,WAAW;AAAa,WAAO;AAC1C,SAAO,mBAAmB,KAAK,uCAAW,SAAS;AACrD;;;ACHO,SAAS,SACd,OACA,OACA,OACA,QACA,QACA;AACA,UAAS,QAAQ,UAAU,SAAS,WAAY,QAAQ,SAAS;AACnE;;;ACJO,SAAS,OAAO;AACrB,SAAO,uCACJ,MAAM,EAAE,EACR;AAAA,IACC,CAAC,GAAG,MACF,KACC,MAAM,MACH,KAAK,MAAM,KAAK,OAAO,IAAI,EAAG,EAAE,SAAS,EAAE,IAC3C,MAAM,MACN,KAAK,MAAM,KAAK,OAAO,IAAI,IAAI,CAAC,EAAE,SAAS,EAAE,IAC7C;AAAA,IACN;AAAA,EACF;AACJ;;;ACjBO,SAAS,KAAsB,GAAM;AAC1C,SAAO,MAAM,KAAK,IAAI,IAAI,CAAC,CAAC;AAC9B;;;ACMA,IAAM,iBAAiC;AAAA,EACrC,WAAW;AAAA,EACX,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,WAAW;AACb;AAEO,SAAS,QAAQ,QAAgB,SAAkC;AACxE,MAAI,OAAO,WAAW,UAAU;AAC9B,UAAM,IAAI,MAAM,mCAAmC;AAAA,EACrD;AAGA,QAAM,WAAW,EAAE,GAAG,gBAAgB,GAAG,QAAQ;AAEjD,QAAM,UAAqC,CAAC;AAE5C,MAAI,OAAO,OACR,UAAU,EACV,MAAM,EAAE,EACR,OAAO,SAAU,QAAQ,IAAI;AAC5B,QAAI,aAAa,QAAQ,EAAE;AAC3B,QAAI,eAAe;AAAW,mBAAa;AAC3C,QAAI,gBAAe,qCAAU;AAAW,mBAAa;AACrD,WACE,SACA,WAAW,SAAQ,qCAAU,WAAU,4BAA4B,EAAE;AAAA,EAEzE,GAAG,EAAE;AAEP,MAAI,SAAS,QAAQ;AACnB,WAAO,KAAK,QAAQ,mBAAmB,EAAE;AAAA,EAC3C;AAEA,MAAI,SAAS,MAAM;AACjB,WAAO,KAAK,KAAK;AAAA,EACnB;AAEA,MAAI,SAAS,WAAW;AACtB,WAAO,KAAK,QAAQ,OAAO,SAAS,SAAS;AAAA,EAC/C;AAEA,MAAI,SAAS,WAAW;AACtB,WAAO,KAAK,kBAAkB;AAAA,EAChC;AAEA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../../packages/utils/index.ts","../../packages/utils/src/functions/clampValue.ts","../../packages/utils/src/functions/interpolate.ts","../../packages/utils/src/functions/isIOS.ts","../../packages/utils/src/functions/mapValue.ts","../../packages/utils/src/functions/uuid.ts","../../packages/utils/src/functions/uniq.ts","../../packages/utils/src/functions/slugify.ts"],"sourcesContent":["export * from './src/functions/clampValue'\nexport * from './src/functions/interpolate'\nexport * from './src/functions/isIOS'\nexport * from './src/functions/mapValue'\nexport * from './src/functions/uuid'\nexport * from './src/functions/uniq'\nexport * from './src/functions/slugify'\n\nexport type * from './src/types'\n","export function clampValue(value: number, min: number, max: number) {\n return value <= min ? min : value >= max ? max : value\n}\n","export type InterpolateArgs = {\n from: number\n to: number\n duration: number\n interval?: number\n easing?: (t: number) => number\n callback: (result: number) => void\n}\n\nexport function interpolate(args: InterpolateArgs) {\n const { from, to, duration, callback, easing = (t) => t * (2 - t) } = args\n\n let startTime: number\n const speed = 1\n\n function animate(timestamp: number) {\n if (!startTime) startTime = timestamp\n\n const progress = Math.min(1, (timestamp - startTime) / (duration * speed))\n const easedProgress = easing(progress)\n const value = from + (to - from) * easedProgress\n\n callback(value)\n\n if (progress < 1) {\n requestAnimationFrame(animate)\n }\n }\n\n requestAnimationFrame(animate)\n}\n","export function isIOS() {\n if (typeof window === 'undefined') return false\n return /iPad|iPhone|iPod/.test(navigator?.userAgent)\n}\n","export function mapValue(\n value: number,\n inMin: number,\n inMax: number,\n outMin: number,\n outMax: number,\n) {\n return ((value - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin\n}\n","// This implementation is meant for internal use only.\n// It is only used to generate a unique IDs for the `key` props.\n// It should not replace crypto.randomUUID() or window.crypto.randomUUID().\n\nexport function uuid() {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'\n .split('')\n .reduce(\n (c, i) =>\n c +\n (i === 'x'\n ? Math.floor(Math.random() * 0xf).toString(16)\n : i === 'y'\n ? Math.floor(Math.random() * 4 + 8).toString(16)\n : i),\n '',\n )\n}\n","export function uniq<T extends any[]>(a: T) {\n return Array.from(new Set(a))\n}\n","export interface SlugifyOptions {\n separator?: string\n trim?: boolean\n remove?: RegExp\n strict?: boolean\n lowercase?: boolean\n}\n\nconst defaultOptions: SlugifyOptions = {\n separator: '-',\n trim: true,\n remove: undefined,\n strict: true,\n lowercase: true,\n}\n\nexport function slugify(string: string, options?: SlugifyOptions): string {\n if (typeof string !== 'string') {\n throw new Error('slugify: string argument expected')\n }\n\n // Merge provided options with default options\n const _options = { ...defaultOptions, ...options }\n\n const charMap: { [key: string]: string } = {}\n\n let slug = string\n .normalize()\n .split('')\n .reduce(function (result, ch) {\n let appendChar = charMap[ch]\n if (appendChar === undefined) appendChar = ch\n if (appendChar === _options?.separator) appendChar = ' '\n return (\n result +\n appendChar.replace(_options?.remove || /[^\\w\\s$*_+~.()'\"!\\-:@]+/g, '')\n )\n }, '')\n\n if (_options.strict) {\n slug = slug.replace(/[^A-Za-z0-9\\s]/g, '')\n }\n\n if (_options.trim) {\n slug = slug.trim()\n }\n\n if (_options.separator) {\n slug = slug.replace(/ +/g, _options.separator)\n }\n\n if (_options.lowercase) {\n slug = slug.toLocaleLowerCase()\n }\n\n return slug\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAO,SAAS,WAAW,OAAe,KAAa,KAAa;AAClE,SAAO,SAAS,MAAM,MAAM,SAAS,MAAM,MAAM;AACnD;;;ACOO,SAAS,YAAY,MAAuB;AACjD,QAAM,EAAE,MAAM,IAAI,UAAU,UAAU,SAAS,CAAC,MAAM,KAAK,IAAI,GAAG,IAAI;AAEtE,MAAI;AACJ,QAAM,QAAQ;AAEd,WAAS,QAAQ,WAAmB;AAClC,QAAI,CAAC;AAAW,kBAAY;AAE5B,UAAM,WAAW,KAAK,IAAI,IAAI,YAAY,cAAc,WAAW,MAAM;AACzE,UAAM,gBAAgB,OAAO,QAAQ;AACrC,UAAM,QAAQ,QAAQ,KAAK,QAAQ;AAEnC,aAAS,KAAK;AAEd,QAAI,WAAW,GAAG;AAChB,4BAAsB,OAAO;AAAA,IAC/B;AAAA,EACF;AAEA,wBAAsB,OAAO;AAC/B;;;AC9BO,SAAS,QAAQ;AACtB,MAAI,OAAO,WAAW;AAAa,WAAO;AAC1C,SAAO,mBAAmB,KAAK,uCAAW,SAAS;AACrD;;;ACHO,SAAS,SACd,OACA,OACA,OACA,QACA,QACA;AACA,UAAS,QAAQ,UAAU,SAAS,WAAY,QAAQ,SAAS;AACnE;;;ACJO,SAAS,OAAO;AACrB,SAAO,uCACJ,MAAM,EAAE,EACR;AAAA,IACC,CAAC,GAAG,MACF,KACC,MAAM,MACH,KAAK,MAAM,KAAK,OAAO,IAAI,EAAG,EAAE,SAAS,EAAE,IAC3C,MAAM,MACN,KAAK,MAAM,KAAK,OAAO,IAAI,IAAI,CAAC,EAAE,SAAS,EAAE,IAC7C;AAAA,IACN;AAAA,EACF;AACJ;;;ACjBO,SAAS,KAAsB,GAAM;AAC1C,SAAO,MAAM,KAAK,IAAI,IAAI,CAAC,CAAC;AAC9B;;;ACMA,IAAM,iBAAiC;AAAA,EACrC,WAAW;AAAA,EACX,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,WAAW;AACb;AAEO,SAAS,QAAQ,QAAgB,SAAkC;AACxE,MAAI,OAAO,WAAW,UAAU;AAC9B,UAAM,IAAI,MAAM,mCAAmC;AAAA,EACrD;AAGA,QAAM,WAAW,EAAE,GAAG,gBAAgB,GAAG,QAAQ;AAEjD,QAAM,UAAqC,CAAC;AAE5C,MAAI,OAAO,OACR,UAAU,EACV,MAAM,EAAE,EACR,OAAO,SAAU,QAAQ,IAAI;AAC5B,QAAI,aAAa,QAAQ,EAAE;AAC3B,QAAI,eAAe;AAAW,mBAAa;AAC3C,QAAI,gBAAe,qCAAU;AAAW,mBAAa;AACrD,WACE,SACA,WAAW,SAAQ,qCAAU,WAAU,4BAA4B,EAAE;AAAA,EAEzE,GAAG,EAAE;AAEP,MAAI,SAAS,QAAQ;AACnB,WAAO,KAAK,QAAQ,mBAAmB,EAAE;AAAA,EAC3C;AAEA,MAAI,SAAS,MAAM;AACjB,WAAO,KAAK,KAAK;AAAA,EACnB;AAEA,MAAI,SAAS,WAAW;AACtB,WAAO,KAAK,QAAQ,OAAO,SAAS,SAAS;AAAA,EAC/C;AAEA,MAAI,SAAS,WAAW;AACtB,WAAO,KAAK,kBAAkB;AAAA,EAChC;AAEA,SAAO;AACT;","names":[]}
@@ -5,26 +5,21 @@ function clampValue(value, min, max) {
5
5
 
6
6
  // src/functions/interpolate.ts
7
7
  function interpolate(args) {
8
- const {
9
- from,
10
- to,
11
- duration,
12
- callback,
13
- easing = (t) => t * (2 - t),
14
- interval = 1
15
- } = args;
16
- const steps = Math.ceil(duration / interval);
17
- const stepSize = 1 / steps;
18
- let currentStep = 0;
19
- const intervalId = setInterval(() => {
20
- const progress = currentStep * stepSize;
21
- const value = from + (to - from) * easing(progress);
8
+ const { from, to, duration, callback, easing = (t) => t * (2 - t) } = args;
9
+ let startTime;
10
+ const speed = 1;
11
+ function animate(timestamp) {
12
+ if (!startTime)
13
+ startTime = timestamp;
14
+ const progress = Math.min(1, (timestamp - startTime) / (duration * speed));
15
+ const easedProgress = easing(progress);
16
+ const value = from + (to - from) * easedProgress;
22
17
  callback(value);
23
- currentStep++;
24
- if (currentStep >= steps) {
25
- clearInterval(intervalId);
18
+ if (progress < 1) {
19
+ requestAnimationFrame(animate);
26
20
  }
27
- }, interval);
21
+ }
22
+ requestAnimationFrame(animate);
28
23
  }
29
24
 
30
25
  // src/functions/isIOS.ts
@@ -1 +1 @@
1
- {"version":3,"sources":["../../packages/utils/src/functions/clampValue.ts","../../packages/utils/src/functions/interpolate.ts","../../packages/utils/src/functions/isIOS.ts","../../packages/utils/src/functions/mapValue.ts","../../packages/utils/src/functions/uuid.ts","../../packages/utils/src/functions/uniq.ts","../../packages/utils/src/functions/slugify.ts"],"sourcesContent":["export function clampValue(value: number, min: number, max: number) {\n return value <= min ? min : value >= max ? max : value\n}\n","export type InterpolateArgs = {\n from: number\n to: number\n duration: number\n interval?: number\n easing?: (t: number) => number\n callback: (result: number) => void\n}\n\nexport function interpolate(args: InterpolateArgs) {\n const {\n from,\n to,\n duration,\n callback,\n easing = (t: number) => t * (2 - t),\n interval = 1,\n } = args\n\n const steps = Math.ceil(duration / interval)\n const stepSize = 1 / steps\n\n let currentStep = 0\n\n const intervalId = setInterval(() => {\n const progress = currentStep * stepSize\n const value = from + (to - from) * easing(progress)\n\n callback(value)\n currentStep++\n\n if (currentStep >= steps) {\n clearInterval(intervalId)\n }\n }, interval)\n}\n","export function isIOS() {\n if (typeof window === 'undefined') return false\n return /iPad|iPhone|iPod/.test(navigator?.userAgent)\n}\n","export function mapValue(\n value: number,\n inMin: number,\n inMax: number,\n outMin: number,\n outMax: number,\n) {\n return ((value - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin\n}\n","// This implementation is meant for internal use only.\n// It is only used to generate a unique IDs for the `key` props.\n// It should not replace crypto.randomUUID() or window.crypto.randomUUID().\n\nexport function uuid() {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'\n .split('')\n .reduce(\n (c, i) =>\n c +\n (i === 'x'\n ? Math.floor(Math.random() * 0xf).toString(16)\n : i === 'y'\n ? Math.floor(Math.random() * 4 + 8).toString(16)\n : i),\n '',\n )\n}\n","export function uniq<T extends any[]>(a: T) {\n return Array.from(new Set(a))\n}\n","export interface SlugifyOptions {\n separator?: string\n trim?: boolean\n remove?: RegExp\n strict?: boolean\n lowercase?: boolean\n}\n\nconst defaultOptions: SlugifyOptions = {\n separator: '-',\n trim: true,\n remove: undefined,\n strict: true,\n lowercase: true,\n}\n\nexport function slugify(string: string, options?: SlugifyOptions): string {\n if (typeof string !== 'string') {\n throw new Error('slugify: string argument expected')\n }\n\n // Merge provided options with default options\n const _options = { ...defaultOptions, ...options }\n\n const charMap: { [key: string]: string } = {}\n\n let slug = string\n .normalize()\n .split('')\n .reduce(function (result, ch) {\n let appendChar = charMap[ch]\n if (appendChar === undefined) appendChar = ch\n if (appendChar === _options?.separator) appendChar = ' '\n return (\n result +\n appendChar.replace(_options?.remove || /[^\\w\\s$*_+~.()'\"!\\-:@]+/g, '')\n )\n }, '')\n\n if (_options.strict) {\n slug = slug.replace(/[^A-Za-z0-9\\s]/g, '')\n }\n\n if (_options.trim) {\n slug = slug.trim()\n }\n\n if (_options.separator) {\n slug = slug.replace(/ +/g, _options.separator)\n }\n\n if (_options.lowercase) {\n slug = slug.toLocaleLowerCase()\n }\n\n return slug\n}\n"],"mappings":";AAAO,SAAS,WAAW,OAAe,KAAa,KAAa;AAClE,SAAO,SAAS,MAAM,MAAM,SAAS,MAAM,MAAM;AACnD;;;ACOO,SAAS,YAAY,MAAuB;AACjD,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,CAAC,MAAc,KAAK,IAAI;AAAA,IACjC,WAAW;AAAA,EACb,IAAI;AAEJ,QAAM,QAAQ,KAAK,KAAK,WAAW,QAAQ;AAC3C,QAAM,WAAW,IAAI;AAErB,MAAI,cAAc;AAElB,QAAM,aAAa,YAAY,MAAM;AACnC,UAAM,WAAW,cAAc;AAC/B,UAAM,QAAQ,QAAQ,KAAK,QAAQ,OAAO,QAAQ;AAElD,aAAS,KAAK;AACd;AAEA,QAAI,eAAe,OAAO;AACxB,oBAAc,UAAU;AAAA,IAC1B;AAAA,EACF,GAAG,QAAQ;AACb;;;ACnCO,SAAS,QAAQ;AACtB,MAAI,OAAO,WAAW;AAAa,WAAO;AAC1C,SAAO,mBAAmB,KAAK,uCAAW,SAAS;AACrD;;;ACHO,SAAS,SACd,OACA,OACA,OACA,QACA,QACA;AACA,UAAS,QAAQ,UAAU,SAAS,WAAY,QAAQ,SAAS;AACnE;;;ACJO,SAAS,OAAO;AACrB,SAAO,uCACJ,MAAM,EAAE,EACR;AAAA,IACC,CAAC,GAAG,MACF,KACC,MAAM,MACH,KAAK,MAAM,KAAK,OAAO,IAAI,EAAG,EAAE,SAAS,EAAE,IAC3C,MAAM,MACN,KAAK,MAAM,KAAK,OAAO,IAAI,IAAI,CAAC,EAAE,SAAS,EAAE,IAC7C;AAAA,IACN;AAAA,EACF;AACJ;;;ACjBO,SAAS,KAAsB,GAAM;AAC1C,SAAO,MAAM,KAAK,IAAI,IAAI,CAAC,CAAC;AAC9B;;;ACMA,IAAM,iBAAiC;AAAA,EACrC,WAAW;AAAA,EACX,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,WAAW;AACb;AAEO,SAAS,QAAQ,QAAgB,SAAkC;AACxE,MAAI,OAAO,WAAW,UAAU;AAC9B,UAAM,IAAI,MAAM,mCAAmC;AAAA,EACrD;AAGA,QAAM,WAAW,EAAE,GAAG,gBAAgB,GAAG,QAAQ;AAEjD,QAAM,UAAqC,CAAC;AAE5C,MAAI,OAAO,OACR,UAAU,EACV,MAAM,EAAE,EACR,OAAO,SAAU,QAAQ,IAAI;AAC5B,QAAI,aAAa,QAAQ,EAAE;AAC3B,QAAI,eAAe;AAAW,mBAAa;AAC3C,QAAI,gBAAe,qCAAU;AAAW,mBAAa;AACrD,WACE,SACA,WAAW,SAAQ,qCAAU,WAAU,4BAA4B,EAAE;AAAA,EAEzE,GAAG,EAAE;AAEP,MAAI,SAAS,QAAQ;AACnB,WAAO,KAAK,QAAQ,mBAAmB,EAAE;AAAA,EAC3C;AAEA,MAAI,SAAS,MAAM;AACjB,WAAO,KAAK,KAAK;AAAA,EACnB;AAEA,MAAI,SAAS,WAAW;AACtB,WAAO,KAAK,QAAQ,OAAO,SAAS,SAAS;AAAA,EAC/C;AAEA,MAAI,SAAS,WAAW;AACtB,WAAO,KAAK,kBAAkB;AAAA,EAChC;AAEA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../../packages/utils/src/functions/clampValue.ts","../../packages/utils/src/functions/interpolate.ts","../../packages/utils/src/functions/isIOS.ts","../../packages/utils/src/functions/mapValue.ts","../../packages/utils/src/functions/uuid.ts","../../packages/utils/src/functions/uniq.ts","../../packages/utils/src/functions/slugify.ts"],"sourcesContent":["export function clampValue(value: number, min: number, max: number) {\n return value <= min ? min : value >= max ? max : value\n}\n","export type InterpolateArgs = {\n from: number\n to: number\n duration: number\n interval?: number\n easing?: (t: number) => number\n callback: (result: number) => void\n}\n\nexport function interpolate(args: InterpolateArgs) {\n const { from, to, duration, callback, easing = (t) => t * (2 - t) } = args\n\n let startTime: number\n const speed = 1\n\n function animate(timestamp: number) {\n if (!startTime) startTime = timestamp\n\n const progress = Math.min(1, (timestamp - startTime) / (duration * speed))\n const easedProgress = easing(progress)\n const value = from + (to - from) * easedProgress\n\n callback(value)\n\n if (progress < 1) {\n requestAnimationFrame(animate)\n }\n }\n\n requestAnimationFrame(animate)\n}\n","export function isIOS() {\n if (typeof window === 'undefined') return false\n return /iPad|iPhone|iPod/.test(navigator?.userAgent)\n}\n","export function mapValue(\n value: number,\n inMin: number,\n inMax: number,\n outMin: number,\n outMax: number,\n) {\n return ((value - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin\n}\n","// This implementation is meant for internal use only.\n// It is only used to generate a unique IDs for the `key` props.\n// It should not replace crypto.randomUUID() or window.crypto.randomUUID().\n\nexport function uuid() {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'\n .split('')\n .reduce(\n (c, i) =>\n c +\n (i === 'x'\n ? Math.floor(Math.random() * 0xf).toString(16)\n : i === 'y'\n ? Math.floor(Math.random() * 4 + 8).toString(16)\n : i),\n '',\n )\n}\n","export function uniq<T extends any[]>(a: T) {\n return Array.from(new Set(a))\n}\n","export interface SlugifyOptions {\n separator?: string\n trim?: boolean\n remove?: RegExp\n strict?: boolean\n lowercase?: boolean\n}\n\nconst defaultOptions: SlugifyOptions = {\n separator: '-',\n trim: true,\n remove: undefined,\n strict: true,\n lowercase: true,\n}\n\nexport function slugify(string: string, options?: SlugifyOptions): string {\n if (typeof string !== 'string') {\n throw new Error('slugify: string argument expected')\n }\n\n // Merge provided options with default options\n const _options = { ...defaultOptions, ...options }\n\n const charMap: { [key: string]: string } = {}\n\n let slug = string\n .normalize()\n .split('')\n .reduce(function (result, ch) {\n let appendChar = charMap[ch]\n if (appendChar === undefined) appendChar = ch\n if (appendChar === _options?.separator) appendChar = ' '\n return (\n result +\n appendChar.replace(_options?.remove || /[^\\w\\s$*_+~.()'\"!\\-:@]+/g, '')\n )\n }, '')\n\n if (_options.strict) {\n slug = slug.replace(/[^A-Za-z0-9\\s]/g, '')\n }\n\n if (_options.trim) {\n slug = slug.trim()\n }\n\n if (_options.separator) {\n slug = slug.replace(/ +/g, _options.separator)\n }\n\n if (_options.lowercase) {\n slug = slug.toLocaleLowerCase()\n }\n\n return slug\n}\n"],"mappings":";AAAO,SAAS,WAAW,OAAe,KAAa,KAAa;AAClE,SAAO,SAAS,MAAM,MAAM,SAAS,MAAM,MAAM;AACnD;;;ACOO,SAAS,YAAY,MAAuB;AACjD,QAAM,EAAE,MAAM,IAAI,UAAU,UAAU,SAAS,CAAC,MAAM,KAAK,IAAI,GAAG,IAAI;AAEtE,MAAI;AACJ,QAAM,QAAQ;AAEd,WAAS,QAAQ,WAAmB;AAClC,QAAI,CAAC;AAAW,kBAAY;AAE5B,UAAM,WAAW,KAAK,IAAI,IAAI,YAAY,cAAc,WAAW,MAAM;AACzE,UAAM,gBAAgB,OAAO,QAAQ;AACrC,UAAM,QAAQ,QAAQ,KAAK,QAAQ;AAEnC,aAAS,KAAK;AAEd,QAAI,WAAW,GAAG;AAChB,4BAAsB,OAAO;AAAA,IAC/B;AAAA,EACF;AAEA,wBAAsB,OAAO;AAC/B;;;AC9BO,SAAS,QAAQ;AACtB,MAAI,OAAO,WAAW;AAAa,WAAO;AAC1C,SAAO,mBAAmB,KAAK,uCAAW,SAAS;AACrD;;;ACHO,SAAS,SACd,OACA,OACA,OACA,QACA,QACA;AACA,UAAS,QAAQ,UAAU,SAAS,WAAY,QAAQ,SAAS;AACnE;;;ACJO,SAAS,OAAO;AACrB,SAAO,uCACJ,MAAM,EAAE,EACR;AAAA,IACC,CAAC,GAAG,MACF,KACC,MAAM,MACH,KAAK,MAAM,KAAK,OAAO,IAAI,EAAG,EAAE,SAAS,EAAE,IAC3C,MAAM,MACN,KAAK,MAAM,KAAK,OAAO,IAAI,IAAI,CAAC,EAAE,SAAS,EAAE,IAC7C;AAAA,IACN;AAAA,EACF;AACJ;;;ACjBO,SAAS,KAAsB,GAAM;AAC1C,SAAO,MAAM,KAAK,IAAI,IAAI,CAAC,CAAC;AAC9B;;;ACMA,IAAM,iBAAiC;AAAA,EACrC,WAAW;AAAA,EACX,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,WAAW;AACb;AAEO,SAAS,QAAQ,QAAgB,SAAkC;AACxE,MAAI,OAAO,WAAW,UAAU;AAC9B,UAAM,IAAI,MAAM,mCAAmC;AAAA,EACrD;AAGA,QAAM,WAAW,EAAE,GAAG,gBAAgB,GAAG,QAAQ;AAEjD,QAAM,UAAqC,CAAC;AAE5C,MAAI,OAAO,OACR,UAAU,EACV,MAAM,EAAE,EACR,OAAO,SAAU,QAAQ,IAAI;AAC5B,QAAI,aAAa,QAAQ,EAAE;AAC3B,QAAI,eAAe;AAAW,mBAAa;AAC3C,QAAI,gBAAe,qCAAU;AAAW,mBAAa;AACrD,WACE,SACA,WAAW,SAAQ,qCAAU,WAAU,4BAA4B,EAAE;AAAA,EAEzE,GAAG,EAAE;AAEP,MAAI,SAAS,QAAQ;AACnB,WAAO,KAAK,QAAQ,mBAAmB,EAAE;AAAA,EAC3C;AAEA,MAAI,SAAS,MAAM;AACjB,WAAO,KAAK,KAAK;AAAA,EACnB;AAEA,MAAI,SAAS,WAAW;AACtB,WAAO,KAAK,QAAQ,OAAO,SAAS,SAAS;AAAA,EAC/C;AAEA,MAAI,SAAS,WAAW;AACtB,WAAO,KAAK,kBAAkB;AAAA,EAChC;AAEA,SAAO;AACT;","names":[]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@maas/vue-equipment",
3
3
  "description": "A magic collection of Vue composables, plugins, components and directives",
4
- "version": "0.14.3",
4
+ "version": "0.15.0",
5
5
  "author": "Robin Scholz <https://github.com/robinscholz>, Christoph Jeworutzki <https://github.com/ChristophJeworutzki>",
6
6
  "devDependencies": {
7
7
  "@antfu/ni": "^0.21.12",