@signality/core 0.0.1-alpha.2
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.
- package/README.md +60 -0
- package/browser/battery/index.d.ts +34 -0
- package/browser/bluetooth/index.d.ts +56 -0
- package/browser/breakpoints/index.d.ts +32 -0
- package/browser/broadcast-channel/index.d.ts +42 -0
- package/browser/browser-language/index.d.ts +34 -0
- package/browser/clipboard/index.d.ts +48 -0
- package/browser/device-posture/index.d.ts +18 -0
- package/browser/display-media/index.d.ts +80 -0
- package/browser/eye-dropper/index.d.ts +47 -0
- package/browser/favicon/index.d.ts +39 -0
- package/browser/fps/index.d.ts +46 -0
- package/browser/gamepad/index.d.ts +28 -0
- package/browser/geolocation/index.d.ts +64 -0
- package/browser/index.d.ts +29 -0
- package/browser/input-modality/index.d.ts +26 -0
- package/browser/listener/index.d.ts +61 -0
- package/browser/media-query/index.d.ts +36 -0
- package/browser/network/index.d.ts +44 -0
- package/browser/online/index.d.ts +27 -0
- package/browser/page-visibility/index.d.ts +27 -0
- package/browser/picture-in-picture/index.d.ts +42 -0
- package/browser/pointer-lock-element/index.d.ts +22 -0
- package/browser/screen-orientation/index.d.ts +29 -0
- package/browser/speech-recognition/index.d.ts +77 -0
- package/browser/speech-synthesis/index.d.ts +76 -0
- package/browser/storage/index.d.ts +142 -0
- package/browser/text-direction/index.d.ts +43 -0
- package/browser/vibration/index.d.ts +37 -0
- package/browser/wake-lock/index.d.ts +37 -0
- package/browser/web-notification/index.d.ts +58 -0
- package/browser/web-share/index.d.ts +42 -0
- package/browser/web-worker/index.d.ts +52 -0
- package/elements/active-element/index.d.ts +27 -0
- package/elements/dropzone/index.d.ts +61 -0
- package/elements/element-focus/index.d.ts +38 -0
- package/elements/element-focus-within/index.d.ts +29 -0
- package/elements/element-hover/index.d.ts +27 -0
- package/elements/element-size/index.d.ts +40 -0
- package/elements/element-visibility/index.d.ts +53 -0
- package/elements/index.d.ts +16 -0
- package/elements/mouse-position/index.d.ts +64 -0
- package/elements/on-click-outside/index.d.ts +42 -0
- package/elements/on-disconnect/index.d.ts +45 -0
- package/elements/on-long-press/index.d.ts +44 -0
- package/elements/pointer-swipe/index.d.ts +58 -0
- package/elements/scroll-position/index.d.ts +96 -0
- package/elements/swipe/index.d.ts +49 -0
- package/elements/text-selection/index.d.ts +39 -0
- package/elements/window-size/index.d.ts +46 -0
- package/fesm2022/signality-core-browser-battery.mjs +80 -0
- package/fesm2022/signality-core-browser-battery.mjs.map +1 -0
- package/fesm2022/signality-core-browser-bluetooth.mjs +112 -0
- package/fesm2022/signality-core-browser-bluetooth.mjs.map +1 -0
- package/fesm2022/signality-core-browser-breakpoints.mjs +51 -0
- package/fesm2022/signality-core-browser-breakpoints.mjs.map +1 -0
- package/fesm2022/signality-core-browser-broadcast-channel.mjs +74 -0
- package/fesm2022/signality-core-browser-broadcast-channel.mjs.map +1 -0
- package/fesm2022/signality-core-browser-browser-language.mjs +48 -0
- package/fesm2022/signality-core-browser-browser-language.mjs.map +1 -0
- package/fesm2022/signality-core-browser-clipboard.mjs +102 -0
- package/fesm2022/signality-core-browser-clipboard.mjs.map +1 -0
- package/fesm2022/signality-core-browser-device-posture.mjs +40 -0
- package/fesm2022/signality-core-browser-device-posture.mjs.map +1 -0
- package/fesm2022/signality-core-browser-display-media.mjs +121 -0
- package/fesm2022/signality-core-browser-display-media.mjs.map +1 -0
- package/fesm2022/signality-core-browser-eye-dropper.mjs +82 -0
- package/fesm2022/signality-core-browser-eye-dropper.mjs.map +1 -0
- package/fesm2022/signality-core-browser-favicon.mjs +100 -0
- package/fesm2022/signality-core-browser-favicon.mjs.map +1 -0
- package/fesm2022/signality-core-browser-fps.mjs +103 -0
- package/fesm2022/signality-core-browser-fps.mjs.map +1 -0
- package/fesm2022/signality-core-browser-gamepad.mjs +93 -0
- package/fesm2022/signality-core-browser-gamepad.mjs.map +1 -0
- package/fesm2022/signality-core-browser-geolocation.mjs +120 -0
- package/fesm2022/signality-core-browser-geolocation.mjs.map +1 -0
- package/fesm2022/signality-core-browser-input-modality.mjs +64 -0
- package/fesm2022/signality-core-browser-input-modality.mjs.map +1 -0
- package/fesm2022/signality-core-browser-listener.mjs +132 -0
- package/fesm2022/signality-core-browser-listener.mjs.map +1 -0
- package/fesm2022/signality-core-browser-media-query.mjs +55 -0
- package/fesm2022/signality-core-browser-media-query.mjs.map +1 -0
- package/fesm2022/signality-core-browser-network.mjs +76 -0
- package/fesm2022/signality-core-browser-network.mjs.map +1 -0
- package/fesm2022/signality-core-browser-online.mjs +49 -0
- package/fesm2022/signality-core-browser-online.mjs.map +1 -0
- package/fesm2022/signality-core-browser-page-visibility.mjs +47 -0
- package/fesm2022/signality-core-browser-page-visibility.mjs.map +1 -0
- package/fesm2022/signality-core-browser-picture-in-picture.mjs +93 -0
- package/fesm2022/signality-core-browser-picture-in-picture.mjs.map +1 -0
- package/fesm2022/signality-core-browser-pointer-lock-element.mjs +43 -0
- package/fesm2022/signality-core-browser-pointer-lock-element.mjs.map +1 -0
- package/fesm2022/signality-core-browser-screen-orientation.mjs +43 -0
- package/fesm2022/signality-core-browser-screen-orientation.mjs.map +1 -0
- package/fesm2022/signality-core-browser-speech-recognition.mjs +171 -0
- package/fesm2022/signality-core-browser-speech-recognition.mjs.map +1 -0
- package/fesm2022/signality-core-browser-speech-synthesis.mjs +146 -0
- package/fesm2022/signality-core-browser-speech-synthesis.mjs.map +1 -0
- package/fesm2022/signality-core-browser-storage.mjs +261 -0
- package/fesm2022/signality-core-browser-storage.mjs.map +1 -0
- package/fesm2022/signality-core-browser-text-direction.mjs +62 -0
- package/fesm2022/signality-core-browser-text-direction.mjs.map +1 -0
- package/fesm2022/signality-core-browser-vibration.mjs +94 -0
- package/fesm2022/signality-core-browser-vibration.mjs.map +1 -0
- package/fesm2022/signality-core-browser-wake-lock.mjs +149 -0
- package/fesm2022/signality-core-browser-wake-lock.mjs.map +1 -0
- package/fesm2022/signality-core-browser-web-notification.mjs +137 -0
- package/fesm2022/signality-core-browser-web-notification.mjs.map +1 -0
- package/fesm2022/signality-core-browser-web-share.mjs +92 -0
- package/fesm2022/signality-core-browser-web-share.mjs.map +1 -0
- package/fesm2022/signality-core-browser-web-worker.mjs +105 -0
- package/fesm2022/signality-core-browser-web-worker.mjs.map +1 -0
- package/fesm2022/signality-core-browser.mjs +34 -0
- package/fesm2022/signality-core-browser.mjs.map +1 -0
- package/fesm2022/signality-core-elements-active-element.mjs +88 -0
- package/fesm2022/signality-core-elements-active-element.mjs.map +1 -0
- package/fesm2022/signality-core-elements-dropzone.mjs +158 -0
- package/fesm2022/signality-core-elements-dropzone.mjs.map +1 -0
- package/fesm2022/signality-core-elements-element-focus-within.mjs +56 -0
- package/fesm2022/signality-core-elements-element-focus-within.mjs.map +1 -0
- package/fesm2022/signality-core-elements-element-focus.mjs +54 -0
- package/fesm2022/signality-core-elements-element-focus.mjs.map +1 -0
- package/fesm2022/signality-core-elements-element-hover.mjs +48 -0
- package/fesm2022/signality-core-elements-element-hover.mjs.map +1 -0
- package/fesm2022/signality-core-elements-element-size.mjs +73 -0
- package/fesm2022/signality-core-elements-element-size.mjs.map +1 -0
- package/fesm2022/signality-core-elements-element-visibility.mjs +76 -0
- package/fesm2022/signality-core-elements-element-visibility.mjs.map +1 -0
- package/fesm2022/signality-core-elements-mouse-position.mjs +109 -0
- package/fesm2022/signality-core-elements-mouse-position.mjs.map +1 -0
- package/fesm2022/signality-core-elements-on-click-outside.mjs +97 -0
- package/fesm2022/signality-core-elements-on-click-outside.mjs.map +1 -0
- package/fesm2022/signality-core-elements-on-disconnect.mjs +99 -0
- package/fesm2022/signality-core-elements-on-disconnect.mjs.map +1 -0
- package/fesm2022/signality-core-elements-on-long-press.mjs +84 -0
- package/fesm2022/signality-core-elements-on-long-press.mjs.map +1 -0
- package/fesm2022/signality-core-elements-pointer-swipe.mjs +116 -0
- package/fesm2022/signality-core-elements-pointer-swipe.mjs.map +1 -0
- package/fesm2022/signality-core-elements-scroll-position.mjs +175 -0
- package/fesm2022/signality-core-elements-scroll-position.mjs.map +1 -0
- package/fesm2022/signality-core-elements-swipe.mjs +107 -0
- package/fesm2022/signality-core-elements-swipe.mjs.map +1 -0
- package/fesm2022/signality-core-elements-text-selection.mjs +70 -0
- package/fesm2022/signality-core-elements-text-selection.mjs.map +1 -0
- package/fesm2022/signality-core-elements-window-size.mjs +81 -0
- package/fesm2022/signality-core-elements-window-size.mjs.map +1 -0
- package/fesm2022/signality-core-elements.mjs +21 -0
- package/fesm2022/signality-core-elements.mjs.map +1 -0
- package/fesm2022/signality-core-forms-cva.mjs +140 -0
- package/fesm2022/signality-core-forms-cva.mjs.map +1 -0
- package/fesm2022/signality-core-forms.mjs +6 -0
- package/fesm2022/signality-core-forms.mjs.map +1 -0
- package/fesm2022/signality-core-internal.mjs +268 -0
- package/fesm2022/signality-core-internal.mjs.map +1 -0
- package/fesm2022/signality-core-observers-intersection-observer.mjs +70 -0
- package/fesm2022/signality-core-observers-intersection-observer.mjs.map +1 -0
- package/fesm2022/signality-core-observers-mutation-observer.mjs +77 -0
- package/fesm2022/signality-core-observers-mutation-observer.mjs.map +1 -0
- package/fesm2022/signality-core-observers-performance-observer.mjs +84 -0
- package/fesm2022/signality-core-observers-performance-observer.mjs.map +1 -0
- package/fesm2022/signality-core-observers-resize-observer.mjs +69 -0
- package/fesm2022/signality-core-observers-resize-observer.mjs.map +1 -0
- package/fesm2022/signality-core-observers.mjs +9 -0
- package/fesm2022/signality-core-observers.mjs.map +1 -0
- package/fesm2022/signality-core-reactivity-debounced.mjs +27 -0
- package/fesm2022/signality-core-reactivity-debounced.mjs.map +1 -0
- package/fesm2022/signality-core-reactivity-throttled.mjs +27 -0
- package/fesm2022/signality-core-reactivity-throttled.mjs.map +1 -0
- package/fesm2022/signality-core-reactivity-watcher.mjs +36 -0
- package/fesm2022/signality-core-reactivity-watcher.mjs.map +1 -0
- package/fesm2022/signality-core-reactivity.mjs +8 -0
- package/fesm2022/signality-core-reactivity.mjs.map +1 -0
- package/fesm2022/signality-core-router-fragment.mjs +41 -0
- package/fesm2022/signality-core-router-fragment.mjs.map +1 -0
- package/fesm2022/signality-core-router-params.mjs +45 -0
- package/fesm2022/signality-core-router-params.mjs.map +1 -0
- package/fesm2022/signality-core-router-query-params.mjs +67 -0
- package/fesm2022/signality-core-router-query-params.mjs.map +1 -0
- package/fesm2022/signality-core-router-route-data.mjs +46 -0
- package/fesm2022/signality-core-router-route-data.mjs.map +1 -0
- package/fesm2022/signality-core-router-router-listener.mjs +50 -0
- package/fesm2022/signality-core-router-router-listener.mjs.map +1 -0
- package/fesm2022/signality-core-router-title.mjs +54 -0
- package/fesm2022/signality-core-router-title.mjs.map +1 -0
- package/fesm2022/signality-core-router-url.mjs +53 -0
- package/fesm2022/signality-core-router-url.mjs.map +1 -0
- package/fesm2022/signality-core-router.mjs +12 -0
- package/fesm2022/signality-core-router.mjs.map +1 -0
- package/fesm2022/signality-core-scheduling-debounce-callback.mjs +59 -0
- package/fesm2022/signality-core-scheduling-debounce-callback.mjs.map +1 -0
- package/fesm2022/signality-core-scheduling-interval.mjs +110 -0
- package/fesm2022/signality-core-scheduling-interval.mjs.map +1 -0
- package/fesm2022/signality-core-scheduling-throttle-callback.mjs +66 -0
- package/fesm2022/signality-core-scheduling-throttle-callback.mjs.map +1 -0
- package/fesm2022/signality-core-scheduling.mjs +8 -0
- package/fesm2022/signality-core-scheduling.mjs.map +1 -0
- package/fesm2022/signality-core-types.mjs +4 -0
- package/fesm2022/signality-core-types.mjs.map +1 -0
- package/fesm2022/signality-core.mjs +13 -0
- package/fesm2022/signality-core.mjs.map +1 -0
- package/forms/cva/index.d.ts +60 -0
- package/forms/index.d.ts +1 -0
- package/index.d.ts +8 -0
- package/internal/constants/index.d.ts +2 -0
- package/internal/constants/mobile-regex.d.ts +1 -0
- package/internal/constants/stubs.d.ts +32 -0
- package/internal/index.d.ts +4 -0
- package/internal/providers/index.d.ts +3 -0
- package/internal/providers/is-browser.d.ts +2 -0
- package/internal/providers/is-mobile.d.ts +2 -0
- package/internal/providers/is-server.d.ts +2 -0
- package/internal/types/index.d.ts +2 -0
- package/internal/types/timer.d.ts +1 -0
- package/internal/types/union.d.ts +1 -0
- package/internal/utils/bom/index.d.ts +1 -0
- package/internal/utils/bom/is-window.d.ts +1 -0
- package/internal/utils/const-signal.d.ts +10 -0
- package/internal/utils/context.d.ts +18 -0
- package/internal/utils/create-token.d.ts +8 -0
- package/internal/utils/dom/get-active-element.d.ts +1 -0
- package/internal/utils/dom/get-event-target.d.ts +1 -0
- package/internal/utils/dom/get-pip-element.d.ts +1 -0
- package/internal/utils/dom/get-shadow-root.d.ts +1 -0
- package/internal/utils/dom/index.d.ts +6 -0
- package/internal/utils/dom/is-element.d.ts +1 -0
- package/internal/utils/dom/is-node-within.d.ts +1 -0
- package/internal/utils/index.d.ts +10 -0
- package/internal/utils/is-plain-object.d.ts +1 -0
- package/internal/utils/is-query-signal.d.ts +10 -0
- package/internal/utils/proxy-signal.d.ts +18 -0
- package/internal/utils/to-element.d.ts +12 -0
- package/internal/utils/to-value.d.ts +6 -0
- package/observers/index.d.ts +4 -0
- package/observers/intersection-observer/index.d.ts +42 -0
- package/observers/mutation-observer/index.d.ts +45 -0
- package/observers/performance-observer/index.d.ts +58 -0
- package/observers/resize-observer/index.d.ts +40 -0
- package/package.json +343 -0
- package/reactivity/debounced/index.d.ts +50 -0
- package/reactivity/index.d.ts +3 -0
- package/reactivity/throttled/index.d.ts +53 -0
- package/reactivity/watcher/index.d.ts +68 -0
- package/router/fragment/index.d.ts +26 -0
- package/router/index.d.ts +7 -0
- package/router/params/index.d.ts +28 -0
- package/router/query-params/index.d.ts +80 -0
- package/router/route-data/index.d.ts +28 -0
- package/router/router-listener/index.d.ts +83 -0
- package/router/title/index.d.ts +29 -0
- package/router/url/index.d.ts +32 -0
- package/scheduling/debounce-callback/index.d.ts +28 -0
- package/scheduling/index.d.ts +3 -0
- package/scheduling/interval/index.d.ts +51 -0
- package/scheduling/throttle-callback/index.d.ts +30 -0
- package/types/index.d.ts +6 -0
- package/types/maybe-element-signal.d.ts +2 -0
- package/types/maybe-signal.d.ts +2 -0
- package/types/signal-value.d.ts +2 -0
- package/types/signal-values.d.ts +5 -0
- package/types/unref-element.d.ts +2 -0
- package/types/with-injector.d.ts +8 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { signal } from '@angular/core';
|
|
2
|
+
import { setupContext, constSignal, isWindow } from '@signality/core/internal';
|
|
3
|
+
import { listener } from '@signality/core/browser/listener';
|
|
4
|
+
import { onDisconnect } from '@signality/core/elements/on-disconnect';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Reactive tracking of mouse position.
|
|
8
|
+
* Track cursor coordinates globally or relative to an element.
|
|
9
|
+
*
|
|
10
|
+
* @param options - Optional configuration
|
|
11
|
+
* @returns A signal containing the current mouse position
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* @Component({
|
|
16
|
+
* template: `
|
|
17
|
+
* <p>Mouse position: X={{ position().x }}, Y={{ position().y }}</p>
|
|
18
|
+
* `
|
|
19
|
+
* })
|
|
20
|
+
* class MouseTracker {
|
|
21
|
+
* readonly position = mousePosition();
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* // Track mouse position on a specific element
|
|
28
|
+
* @Component({
|
|
29
|
+
* template: `
|
|
30
|
+
* <div #box>
|
|
31
|
+
* <p>Position: X={{ position().x }}, Y={{ position().y }}</p>
|
|
32
|
+
* </div>
|
|
33
|
+
* `
|
|
34
|
+
* })
|
|
35
|
+
* class MouseElementTracker {
|
|
36
|
+
* readonly box = viewChild<ElementRef>('box');
|
|
37
|
+
* readonly position = mousePosition({ target: this.box });
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
function mousePosition(options) {
|
|
42
|
+
const { runInContext } = setupContext(options?.injector, mousePosition);
|
|
43
|
+
return runInContext(({ isServer }) => {
|
|
44
|
+
const initialValue = options?.initialValue ?? DEFAULT_POSITION;
|
|
45
|
+
if (isServer) {
|
|
46
|
+
return constSignal(initialValue);
|
|
47
|
+
}
|
|
48
|
+
const target = options?.target ?? window;
|
|
49
|
+
const targetIsWindow = isWindow(target);
|
|
50
|
+
const coordinateType = options?.type ?? 'page';
|
|
51
|
+
const trackTouch = options?.touch ?? true;
|
|
52
|
+
const trackScroll = coordinateType === 'page';
|
|
53
|
+
const extractor = EXTRACTORS[coordinateType];
|
|
54
|
+
const position = signal(initialValue, ...(ngDevMode ? [{ debugName: "position" }] : []));
|
|
55
|
+
let prevMouseEvent = null;
|
|
56
|
+
let prevScrollX = 0;
|
|
57
|
+
let prevScrollY = 0;
|
|
58
|
+
const handleMouse = (e) => {
|
|
59
|
+
prevMouseEvent = e;
|
|
60
|
+
position.set(extractor(e));
|
|
61
|
+
if (trackScroll) {
|
|
62
|
+
prevScrollX = window.scrollX;
|
|
63
|
+
prevScrollY = window.scrollY;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
listener(target, 'mousemove', handleMouse);
|
|
67
|
+
listener(target, 'dragover', handleMouse);
|
|
68
|
+
if (trackTouch) {
|
|
69
|
+
listener.passive(target, 'touchmove', (e) => {
|
|
70
|
+
if (e.touches.length === 0) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
position.set(extractor(e.touches[0]));
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
if (trackScroll) {
|
|
77
|
+
listener(window, 'scroll', () => {
|
|
78
|
+
if (!prevMouseEvent) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const pos = extractor(prevMouseEvent);
|
|
82
|
+
position.set({
|
|
83
|
+
x: pos.x + window.scrollX - prevScrollX,
|
|
84
|
+
y: pos.y + window.scrollY - prevScrollY,
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
if (!targetIsWindow) {
|
|
89
|
+
onDisconnect(target, () => position.set(DEFAULT_POSITION));
|
|
90
|
+
}
|
|
91
|
+
return position.asReadonly();
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
const DEFAULT_POSITION = {
|
|
95
|
+
x: 0,
|
|
96
|
+
y: 0,
|
|
97
|
+
};
|
|
98
|
+
const EXTRACTORS = {
|
|
99
|
+
page: e => ({ x: e.pageX, y: e.pageY }),
|
|
100
|
+
client: e => ({ x: e.clientX, y: e.clientY }),
|
|
101
|
+
screen: e => ({ x: e.screenX, y: e.screenY }),
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Generated bundle index. Do not edit.
|
|
106
|
+
*/
|
|
107
|
+
|
|
108
|
+
export { mousePosition };
|
|
109
|
+
//# sourceMappingURL=signality-core-elements-mouse-position.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signality-core-elements-mouse-position.mjs","sources":["../../../projects/core/elements/mouse-position/index.ts","../../../projects/core/elements/mouse-position/signality-core-elements-mouse-position.ts"],"sourcesContent":["import { type Signal, signal } from '@angular/core';\nimport { constSignal, isWindow, setupContext } from '@signality/core/internal';\nimport type { MaybeElementSignal, WithInjector } from '@signality/core/types';\nimport { listener } from '@signality/core/browser/listener';\nimport { onDisconnect } from '@signality/core/elements/on-disconnect';\n\nexport interface MousePosition {\n readonly x: number;\n readonly y: number;\n}\n\nexport type MouseCoordinateType = 'page' | 'client' | 'screen';\n\nexport interface MousePositionOptions extends WithInjector {\n /**\n * Element or window to track mouse position on.\n * @default window\n */\n readonly target?: MaybeElementSignal<Element> | Window;\n\n /**\n * Coordinate type to use.\n * @default 'page'\n */\n readonly type?: MouseCoordinateType;\n\n /**\n * Whether to track touch events as well.\n * @default true\n */\n readonly touch?: boolean;\n\n /**\n * Initial mouse position.\n */\n readonly initialValue?: MousePosition;\n}\n\n/**\n * Reactive tracking of mouse position.\n * Track cursor coordinates globally or relative to an element.\n *\n * @param options - Optional configuration\n * @returns A signal containing the current mouse position\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * <p>Mouse position: X={{ position().x }}, Y={{ position().y }}</p>\n * `\n * })\n * class MouseTracker {\n * readonly position = mousePosition();\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Track mouse position on a specific element\n * @Component({\n * template: `\n * <div #box>\n * <p>Position: X={{ position().x }}, Y={{ position().y }}</p>\n * </div>\n * `\n * })\n * class MouseElementTracker {\n * readonly box = viewChild<ElementRef>('box');\n * readonly position = mousePosition({ target: this.box });\n * }\n * ```\n */\nexport function mousePosition(options?: MousePositionOptions): Signal<MousePosition> {\n const { runInContext } = setupContext(options?.injector, mousePosition);\n\n return runInContext(({ isServer }) => {\n const initialValue = options?.initialValue ?? DEFAULT_POSITION;\n\n if (isServer) {\n return constSignal(initialValue);\n }\n\n const target = options?.target ?? window;\n const targetIsWindow = isWindow(target);\n const coordinateType = options?.type ?? 'page';\n const trackTouch = options?.touch ?? true;\n const trackScroll = coordinateType === 'page';\n const extractor = EXTRACTORS[coordinateType];\n\n const position = signal<MousePosition>(initialValue);\n\n let prevMouseEvent: MouseEvent | null = null;\n let prevScrollX = 0;\n let prevScrollY = 0;\n\n const handleMouse = (e: MouseEvent): void => {\n prevMouseEvent = e;\n\n position.set(extractor(e));\n\n if (trackScroll) {\n prevScrollX = window.scrollX;\n prevScrollY = window.scrollY;\n }\n };\n\n listener(target, 'mousemove', handleMouse);\n listener(target, 'dragover', handleMouse);\n\n if (trackTouch) {\n listener.passive(target, 'touchmove', (e: TouchEvent) => {\n if (e.touches.length === 0) {\n return;\n }\n\n position.set(extractor(e.touches[0]));\n });\n }\n\n if (trackScroll) {\n listener(window, 'scroll', () => {\n if (!prevMouseEvent) {\n return;\n }\n\n const pos = extractor(prevMouseEvent);\n\n position.set({\n x: pos.x + window.scrollX - prevScrollX,\n y: pos.y + window.scrollY - prevScrollY,\n });\n });\n }\n\n if (!targetIsWindow) {\n onDisconnect(target, () => position.set(DEFAULT_POSITION));\n }\n\n return position.asReadonly();\n });\n}\n\nconst DEFAULT_POSITION: MousePosition = {\n x: 0,\n y: 0,\n};\n\nconst EXTRACTORS: Record<MouseCoordinateType, (e: MouseEvent | Touch) => MousePosition> = {\n page: e => ({ x: e.pageX, y: e.pageY }),\n client: e => ({ x: e.clientX, y: e.clientY }),\n screen: e => ({ x: e.screenX, y: e.screenY }),\n};\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { setupContext, NOOP_EFFECT_REF, toElement } from '@signality/core/internal';
|
|
2
|
+
import { setupSync, listener } from '@signality/core/browser/listener';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Detects clicks outside a target element and invokes a callback.
|
|
6
|
+
* Useful for closing dropdowns, modals, and popovers when the user clicks elsewhere.
|
|
7
|
+
*
|
|
8
|
+
* @param target - Element to detect clicks outside of
|
|
9
|
+
* @param handler - Callback invoked when a click outside the target is detected
|
|
10
|
+
* @param options - Optional configuration including ignore list and injector
|
|
11
|
+
* @returns An OnClickOutsideRef with a destroy method to stop detection
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* @Component({
|
|
16
|
+
* template: `
|
|
17
|
+
* <div #dropdown class="dropdown">
|
|
18
|
+
* <p>Dropdown content</p>
|
|
19
|
+
* </div>
|
|
20
|
+
* `
|
|
21
|
+
* })
|
|
22
|
+
* class DropdownComponent {
|
|
23
|
+
* readonly dropdown = viewChild<ElementRef>('dropdown');
|
|
24
|
+
* readonly isOpen = signal(true);
|
|
25
|
+
*
|
|
26
|
+
* constructor() {
|
|
27
|
+
* onClickOutside(this.dropdown, () => {
|
|
28
|
+
* this.isOpen.set(false);
|
|
29
|
+
* });
|
|
30
|
+
* }
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
function onClickOutside(target, handler, options) {
|
|
35
|
+
const { runInContext } = setupContext(options?.injector, onClickOutside);
|
|
36
|
+
return runInContext(({ isServer }) => {
|
|
37
|
+
if (isServer) {
|
|
38
|
+
return NOOP_EFFECT_REF;
|
|
39
|
+
}
|
|
40
|
+
const ignoreList = options?.ignore ?? [];
|
|
41
|
+
let shouldFire = false;
|
|
42
|
+
function isOutside(event) {
|
|
43
|
+
const el = toElement(target);
|
|
44
|
+
const path = event.composedPath();
|
|
45
|
+
if (el && path.includes(el)) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
if (ignoreList.length) {
|
|
49
|
+
return !ignoreList.some(ignored => {
|
|
50
|
+
const ignoredEl = toElement(ignored);
|
|
51
|
+
return ignoredEl && path.includes(ignoredEl);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
const pointerDownListener = setupSync(() => listener.capture(window, 'pointerdown', (e) => {
|
|
57
|
+
shouldFire = isOutside(e);
|
|
58
|
+
}));
|
|
59
|
+
const clickListener = setupSync(() => listener.capture(window, 'pointerup', (e) => {
|
|
60
|
+
if (!shouldFire) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
shouldFire = false;
|
|
64
|
+
if (!isOutside(e)) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
handler(e);
|
|
68
|
+
}));
|
|
69
|
+
const blurListener = setupSync(() => listener(window, 'blur', (e) => {
|
|
70
|
+
setTimeout(() => {
|
|
71
|
+
const active = document.activeElement;
|
|
72
|
+
if (active?.tagName !== 'IFRAME') {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const el = toElement(target);
|
|
76
|
+
if (el?.contains(active)) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
handler(e);
|
|
80
|
+
}, 0);
|
|
81
|
+
}));
|
|
82
|
+
return {
|
|
83
|
+
destroy: () => {
|
|
84
|
+
pointerDownListener.destroy();
|
|
85
|
+
clickListener.destroy();
|
|
86
|
+
blurListener.destroy();
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Generated bundle index. Do not edit.
|
|
94
|
+
*/
|
|
95
|
+
|
|
96
|
+
export { onClickOutside };
|
|
97
|
+
//# sourceMappingURL=signality-core-elements-on-click-outside.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signality-core-elements-on-click-outside.mjs","sources":["../../../projects/core/elements/on-click-outside/index.ts","../../../projects/core/elements/on-click-outside/signality-core-elements-on-click-outside.ts"],"sourcesContent":["import { NOOP_EFFECT_REF, setupContext, toElement } from '@signality/core/internal';\nimport type { MaybeElementSignal, WithInjector } from '@signality/core/types';\nimport { listener, setupSync } from '@signality/core/browser/listener';\n\nexport interface OnClickOutsideOptions extends WithInjector {\n /**\n * Elements that should not trigger the outside click handler.\n */\n readonly ignore?: MaybeElementSignal<Element>[];\n}\n\nexport interface OnClickOutsideRef {\n /** Stops listening for outside clicks. */\n readonly destroy: () => void;\n}\n\n/**\n * Detects clicks outside a target element and invokes a callback.\n * Useful for closing dropdowns, modals, and popovers when the user clicks elsewhere.\n *\n * @param target - Element to detect clicks outside of\n * @param handler - Callback invoked when a click outside the target is detected\n * @param options - Optional configuration including ignore list and injector\n * @returns An OnClickOutsideRef with a destroy method to stop detection\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * <div #dropdown class=\"dropdown\">\n * <p>Dropdown content</p>\n * </div>\n * `\n * })\n * class DropdownComponent {\n * readonly dropdown = viewChild<ElementRef>('dropdown');\n * readonly isOpen = signal(true);\n *\n * constructor() {\n * onClickOutside(this.dropdown, () => {\n * this.isOpen.set(false);\n * });\n * }\n * }\n * ```\n */\nexport function onClickOutside(\n target: MaybeElementSignal<HTMLElement>,\n handler: (event: PointerEvent | FocusEvent) => void,\n options?: OnClickOutsideOptions\n): OnClickOutsideRef {\n const { runInContext } = setupContext(options?.injector, onClickOutside);\n\n return runInContext(({ isServer }) => {\n if (isServer) {\n return NOOP_EFFECT_REF;\n }\n\n const ignoreList = options?.ignore ?? [];\n\n let shouldFire = false;\n\n function isOutside(event: Event): boolean {\n const el = toElement(target);\n const path = event.composedPath();\n\n if (el && path.includes(el)) {\n return false;\n }\n\n if (ignoreList.length) {\n return !ignoreList.some(ignored => {\n const ignoredEl = toElement(ignored);\n return ignoredEl && path.includes(ignoredEl);\n });\n }\n\n return true;\n }\n\n const pointerDownListener = setupSync(() =>\n listener.capture(window, 'pointerdown', (e: PointerEvent) => {\n shouldFire = isOutside(e);\n })\n );\n\n const clickListener = setupSync(() =>\n listener.capture(window, 'pointerup', (e: PointerEvent) => {\n if (!shouldFire) {\n return;\n }\n\n shouldFire = false;\n\n if (!isOutside(e)) {\n return;\n }\n\n handler(e);\n })\n );\n\n const blurListener = setupSync(() =>\n listener(window, 'blur', (e: FocusEvent) => {\n setTimeout(() => {\n const active = document.activeElement;\n\n if (active?.tagName !== 'IFRAME') {\n return;\n }\n\n const el = toElement(target);\n\n if (el?.contains(active)) {\n return;\n }\n\n handler(e);\n }, 0);\n })\n );\n\n return {\n destroy: () => {\n pointerDownListener.destroy();\n clickListener.destroy();\n blurListener.destroy();\n },\n };\n });\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;AAgBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BG;SACa,cAAc,CAC5B,MAAuC,EACvC,OAAmD,EACnD,OAA+B,EAAA;AAE/B,IAAA,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,cAAc,CAAC;AAExE,IAAA,OAAO,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAI;QACnC,IAAI,QAAQ,EAAE;AACZ,YAAA,OAAO,eAAe;QACxB;AAEA,QAAA,MAAM,UAAU,GAAG,OAAO,EAAE,MAAM,IAAI,EAAE;QAExC,IAAI,UAAU,GAAG,KAAK;QAEtB,SAAS,SAAS,CAAC,KAAY,EAAA;AAC7B,YAAA,MAAM,EAAE,GAAG,SAAS,CAAC,MAAM,CAAC;AAC5B,YAAA,MAAM,IAAI,GAAG,KAAK,CAAC,YAAY,EAAE;YAEjC,IAAI,EAAE,IAAI,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE;AAC3B,gBAAA,OAAO,KAAK;YACd;AAEA,YAAA,IAAI,UAAU,CAAC,MAAM,EAAE;AACrB,gBAAA,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,IAAG;AAChC,oBAAA,MAAM,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC;oBACpC,OAAO,SAAS,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC;AAC9C,gBAAA,CAAC,CAAC;YACJ;AAEA,YAAA,OAAO,IAAI;QACb;AAEA,QAAA,MAAM,mBAAmB,GAAG,SAAS,CAAC,MACpC,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,aAAa,EAAE,CAAC,CAAe,KAAI;AAC1D,YAAA,UAAU,GAAG,SAAS,CAAC,CAAC,CAAC;QAC3B,CAAC,CAAC,CACH;AAED,QAAA,MAAM,aAAa,GAAG,SAAS,CAAC,MAC9B,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAAe,KAAI;YACxD,IAAI,CAAC,UAAU,EAAE;gBACf;YACF;YAEA,UAAU,GAAG,KAAK;AAElB,YAAA,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE;gBACjB;YACF;YAEA,OAAO,CAAC,CAAC,CAAC;QACZ,CAAC,CAAC,CACH;AAED,QAAA,MAAM,YAAY,GAAG,SAAS,CAAC,MAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAa,KAAI;YACzC,UAAU,CAAC,MAAK;AACd,gBAAA,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa;AAErC,gBAAA,IAAI,MAAM,EAAE,OAAO,KAAK,QAAQ,EAAE;oBAChC;gBACF;AAEA,gBAAA,MAAM,EAAE,GAAG,SAAS,CAAC,MAAM,CAAC;AAE5B,gBAAA,IAAI,EAAE,EAAE,QAAQ,CAAC,MAAM,CAAC,EAAE;oBACxB;gBACF;gBAEA,OAAO,CAAC,CAAC,CAAC;YACZ,CAAC,EAAE,CAAC,CAAC;QACP,CAAC,CAAC,CACH;QAED,OAAO;YACL,OAAO,EAAE,MAAK;gBACZ,mBAAmB,CAAC,OAAO,EAAE;gBAC7B,aAAa,CAAC,OAAO,EAAE;gBACvB,YAAY,CAAC,OAAO,EAAE;YACxB,CAAC;SACF;AACH,IAAA,CAAC,CAAC;AACJ;;AClIA;;AAEG;;;;"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { inject, ElementRef, DestroyRef, afterRenderEffect, afterEveryRender } from '@angular/core';
|
|
2
|
+
import { setupContext, NOOP_EFFECT_REF, toElement, isQuerySignal } from '@signality/core/internal';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Executes a callback when an element is disconnected from the DOM.
|
|
6
|
+
*
|
|
7
|
+
* @param target - The element to watch for disconnection
|
|
8
|
+
* @param callback - Callback to execute when the element is disconnected
|
|
9
|
+
* @param options - Optional configuration including injector
|
|
10
|
+
* @returns OnDisconnectRef with a destroy method to stop watching for disconnection
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* @Component({
|
|
15
|
+
* template: `
|
|
16
|
+
* <div #box>Content</div>
|
|
17
|
+
* <button (click)="remove()">Remove</button>
|
|
18
|
+
* `
|
|
19
|
+
* })
|
|
20
|
+
* class DisconnectComponent {
|
|
21
|
+
* readonly box = viewChild<ElementRef>('box');
|
|
22
|
+
*
|
|
23
|
+
* constructor() {
|
|
24
|
+
* onDisconnect(this.box, el => {
|
|
25
|
+
* console.log('Element disconnected: ', el.tagName);
|
|
26
|
+
* // perform cleanup without storing the reference
|
|
27
|
+
* });
|
|
28
|
+
* }
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* // Manual cleanup
|
|
35
|
+
* const disconnectRef = onDisconnect(element, () => {
|
|
36
|
+
* console.log('Disconnected');
|
|
37
|
+
* });
|
|
38
|
+
*
|
|
39
|
+
* // Stop watching before disconnection
|
|
40
|
+
* disconnectRef.destroy();
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
function onDisconnect(target, callback, options) {
|
|
44
|
+
const { runInContext } = setupContext(options?.injector, onDisconnect);
|
|
45
|
+
return runInContext(({ isServer }) => {
|
|
46
|
+
if (isServer) {
|
|
47
|
+
return NOOP_EFFECT_REF;
|
|
48
|
+
}
|
|
49
|
+
// (1) Host element check
|
|
50
|
+
// if we are inside a directive and the target element is its host,
|
|
51
|
+
// then we hook into the directive's lifecycle via its DestroyRef
|
|
52
|
+
const hostElRef = inject(ElementRef, { optional: true, self: true });
|
|
53
|
+
if (hostElRef) {
|
|
54
|
+
const targetEl = toElement(target);
|
|
55
|
+
if (targetEl && hostElRef.nativeElement === targetEl) {
|
|
56
|
+
return {
|
|
57
|
+
destroy: inject(DestroyRef).onDestroy(() => callback(targetEl)),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// (2) Query signal check (viewChild/contentChild)
|
|
62
|
+
// if target is a query signal, we rely on its automatic state transition
|
|
63
|
+
// managed by Angular's change detection, calling callback at render completion timing
|
|
64
|
+
if (isQuerySignal(target)) {
|
|
65
|
+
const effectRef = afterRenderEffect({
|
|
66
|
+
read: onCleanup => {
|
|
67
|
+
const targetEl = toElement(target);
|
|
68
|
+
if (targetEl) {
|
|
69
|
+
onCleanup(() => {
|
|
70
|
+
if (!targetEl.isConnected)
|
|
71
|
+
callback(targetEl);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
return { destroy: () => effectRef.destroy() };
|
|
77
|
+
}
|
|
78
|
+
// (3) Fallback
|
|
79
|
+
// for any DOM target (reactive or non-reactive), we assume the value was
|
|
80
|
+
// manually read from the DOM. Therefore, we perform isConnected check after
|
|
81
|
+
// every render cycle to detect disconnection
|
|
82
|
+
// @TODO: document the behavior of state transitions when the target is reactive
|
|
83
|
+
const afterRenderRef = afterEveryRender({
|
|
84
|
+
read: () => {
|
|
85
|
+
const targetEl = toElement(target);
|
|
86
|
+
if (targetEl && !targetEl.isConnected)
|
|
87
|
+
callback(targetEl);
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
return { destroy: () => afterRenderRef.destroy() };
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Generated bundle index. Do not edit.
|
|
96
|
+
*/
|
|
97
|
+
|
|
98
|
+
export { onDisconnect };
|
|
99
|
+
//# sourceMappingURL=signality-core-elements-on-disconnect.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signality-core-elements-on-disconnect.mjs","sources":["../../../projects/core/elements/on-disconnect/index.ts","../../../projects/core/elements/on-disconnect/signality-core-elements-on-disconnect.ts"],"sourcesContent":["import { afterEveryRender, afterRenderEffect, DestroyRef, ElementRef, inject } from '@angular/core';\nimport { isQuerySignal, NOOP_EFFECT_REF, setupContext, toElement } from '@signality/core/internal';\nimport type { MaybeElementSignal, WithInjector } from '@signality/core/types';\n\nexport type OnDisconnectOptions = WithInjector;\n\nexport interface OnDisconnectRef {\n readonly destroy: () => void;\n}\n\n/**\n * Executes a callback when an element is disconnected from the DOM.\n *\n * @param target - The element to watch for disconnection\n * @param callback - Callback to execute when the element is disconnected\n * @param options - Optional configuration including injector\n * @returns OnDisconnectRef with a destroy method to stop watching for disconnection\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * <div #box>Content</div>\n * <button (click)=\"remove()\">Remove</button>\n * `\n * })\n * class DisconnectComponent {\n * readonly box = viewChild<ElementRef>('box');\n *\n * constructor() {\n * onDisconnect(this.box, el => {\n * console.log('Element disconnected: ', el.tagName);\n * // perform cleanup without storing the reference\n * });\n * }\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Manual cleanup\n * const disconnectRef = onDisconnect(element, () => {\n * console.log('Disconnected');\n * });\n *\n * // Stop watching before disconnection\n * disconnectRef.destroy();\n * ```\n */\nexport function onDisconnect<T extends Element>(\n target: MaybeElementSignal<T>,\n callback: (element: T) => void,\n options?: OnDisconnectOptions\n): OnDisconnectRef {\n const { runInContext } = setupContext(options?.injector, onDisconnect);\n\n return runInContext(({ isServer }) => {\n if (isServer) {\n return NOOP_EFFECT_REF;\n }\n\n // (1) Host element check\n // if we are inside a directive and the target element is its host,\n // then we hook into the directive's lifecycle via its DestroyRef\n const hostElRef = inject(ElementRef, { optional: true, self: true });\n if (hostElRef) {\n const targetEl = toElement(target);\n if (targetEl && hostElRef.nativeElement === targetEl) {\n return {\n destroy: inject(DestroyRef).onDestroy(() => callback(targetEl)),\n };\n }\n }\n\n // (2) Query signal check (viewChild/contentChild)\n // if target is a query signal, we rely on its automatic state transition\n // managed by Angular's change detection, calling callback at render completion timing\n if (isQuerySignal(target)) {\n const effectRef = afterRenderEffect({\n read: onCleanup => {\n const targetEl = toElement(target);\n\n if (targetEl) {\n onCleanup(() => {\n if (!targetEl.isConnected) callback(targetEl);\n });\n }\n },\n });\n\n return { destroy: () => effectRef.destroy() };\n }\n\n // (3) Fallback\n // for any DOM target (reactive or non-reactive), we assume the value was\n // manually read from the DOM. Therefore, we perform isConnected check after\n // every render cycle to detect disconnection\n // @TODO: document the behavior of state transitions when the target is reactive\n const afterRenderRef = afterEveryRender({\n read: () => {\n const targetEl = toElement(target);\n if (targetEl && !targetEl.isConnected) callback(targetEl);\n },\n });\n\n return { destroy: () => afterRenderRef.destroy() };\n });\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;AAUA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCG;SACa,YAAY,CAC1B,MAA6B,EAC7B,QAA8B,EAC9B,OAA6B,EAAA;AAE7B,IAAA,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,YAAY,CAAC;AAEtE,IAAA,OAAO,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAI;QACnC,IAAI,QAAQ,EAAE;AACZ,YAAA,OAAO,eAAe;QACxB;;;;AAKA,QAAA,MAAM,SAAS,GAAG,MAAM,CAAC,UAAU,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QACpE,IAAI,SAAS,EAAE;AACb,YAAA,MAAM,QAAQ,GAAG,SAAS,CAAC,MAAM,CAAC;YAClC,IAAI,QAAQ,IAAI,SAAS,CAAC,aAAa,KAAK,QAAQ,EAAE;gBACpD,OAAO;AACL,oBAAA,OAAO,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,MAAM,QAAQ,CAAC,QAAQ,CAAC,CAAC;iBAChE;YACH;QACF;;;;AAKA,QAAA,IAAI,aAAa,CAAC,MAAM,CAAC,EAAE;YACzB,MAAM,SAAS,GAAG,iBAAiB,CAAC;gBAClC,IAAI,EAAE,SAAS,IAAG;AAChB,oBAAA,MAAM,QAAQ,GAAG,SAAS,CAAC,MAAM,CAAC;oBAElC,IAAI,QAAQ,EAAE;wBACZ,SAAS,CAAC,MAAK;4BACb,IAAI,CAAC,QAAQ,CAAC,WAAW;gCAAE,QAAQ,CAAC,QAAQ,CAAC;AAC/C,wBAAA,CAAC,CAAC;oBACJ;gBACF,CAAC;AACF,aAAA,CAAC;YAEF,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC,OAAO,EAAE,EAAE;QAC/C;;;;;;QAOA,MAAM,cAAc,GAAG,gBAAgB,CAAC;YACtC,IAAI,EAAE,MAAK;AACT,gBAAA,MAAM,QAAQ,GAAG,SAAS,CAAC,MAAM,CAAC;AAClC,gBAAA,IAAI,QAAQ,IAAI,CAAC,QAAQ,CAAC,WAAW;oBAAE,QAAQ,CAAC,QAAQ,CAAC;YAC3D,CAAC;AACF,SAAA,CAAC;QAEF,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC,OAAO,EAAE,EAAE;AACpD,IAAA,CAAC,CAAC;AACJ;;AC3GA;;AAEG;;;;"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { setupContext, NOOP_EFFECT_REF, toValue } from '@signality/core/internal';
|
|
2
|
+
import { listener } from '@signality/core/browser/listener';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Detect long press gestures on an element.
|
|
6
|
+
* Calls a callback after a configurable delay if the pointer stays down
|
|
7
|
+
* without moving beyond the distance threshold.
|
|
8
|
+
*
|
|
9
|
+
* @param target - The element to detect long presses on
|
|
10
|
+
* @param handler - Callback invoked when a long press is detected
|
|
11
|
+
* @param options - Optional configuration including delay, distance threshold, and injector
|
|
12
|
+
* @returns A OnLongPressRef with a destroy method to stop detection
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* @Component({
|
|
17
|
+
* template: `<button #btn>Hold me</button>`
|
|
18
|
+
* })
|
|
19
|
+
* class LongPressComponent {
|
|
20
|
+
* readonly btn = viewChild<ElementRef>('btn');
|
|
21
|
+
*
|
|
22
|
+
* constructor() {
|
|
23
|
+
* onLongPress(this.btn, () => {
|
|
24
|
+
* console.log('Long press detected!');
|
|
25
|
+
* });
|
|
26
|
+
* }
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
function onLongPress(target, handler, options) {
|
|
31
|
+
const { runInContext } = setupContext(options?.injector, onLongPress);
|
|
32
|
+
return runInContext(({ isServer, onCleanup }) => {
|
|
33
|
+
if (isServer) {
|
|
34
|
+
return NOOP_EFFECT_REF;
|
|
35
|
+
}
|
|
36
|
+
const distanceThreshold = options?.distanceThreshold;
|
|
37
|
+
let startX = 0;
|
|
38
|
+
let startY = 0;
|
|
39
|
+
let longPressTimeout;
|
|
40
|
+
function abortPendingPress() {
|
|
41
|
+
clearTimeout(longPressTimeout);
|
|
42
|
+
longPressTimeout = undefined;
|
|
43
|
+
}
|
|
44
|
+
const downListener = listener(target, 'pointerdown', (e) => {
|
|
45
|
+
startX = e.clientX;
|
|
46
|
+
startY = e.clientY;
|
|
47
|
+
const delay = toValue(options?.delay) ?? 500;
|
|
48
|
+
longPressTimeout = setTimeout(() => {
|
|
49
|
+
handler(e);
|
|
50
|
+
longPressTimeout = undefined;
|
|
51
|
+
}, delay);
|
|
52
|
+
});
|
|
53
|
+
const moveListener = listener(target, 'pointermove', (e) => {
|
|
54
|
+
if (!longPressTimeout || distanceThreshold === false) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const threshold = distanceThreshold ?? 10;
|
|
58
|
+
const dx = e.clientX - startX;
|
|
59
|
+
const dy = e.clientY - startY;
|
|
60
|
+
if (Math.sqrt(dx * dx + dy * dy) > threshold) {
|
|
61
|
+
abortPendingPress();
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
const upListener = listener(target, 'pointerup', abortPendingPress);
|
|
65
|
+
const leaveListener = listener(target, 'pointerleave', abortPendingPress);
|
|
66
|
+
onCleanup(abortPendingPress);
|
|
67
|
+
return {
|
|
68
|
+
destroy: () => {
|
|
69
|
+
abortPendingPress();
|
|
70
|
+
upListener.destroy();
|
|
71
|
+
downListener.destroy();
|
|
72
|
+
moveListener.destroy();
|
|
73
|
+
leaveListener.destroy();
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Generated bundle index. Do not edit.
|
|
81
|
+
*/
|
|
82
|
+
|
|
83
|
+
export { onLongPress };
|
|
84
|
+
//# sourceMappingURL=signality-core-elements-on-long-press.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signality-core-elements-on-long-press.mjs","sources":["../../../projects/core/elements/on-long-press/index.ts","../../../projects/core/elements/on-long-press/signality-core-elements-on-long-press.ts"],"sourcesContent":["import { NOOP_EFFECT_REF, setupContext, type Timer, toValue } from '@signality/core/internal';\nimport type { MaybeElementSignal, MaybeSignal, WithInjector } from '@signality/core/types';\nimport { listener } from '@signality/core/browser/listener';\n\nexport interface OnLongPressOptions extends WithInjector {\n /**\n * Time in ms before the callback is triggered.\n * @default 500\n */\n readonly delay?: MaybeSignal<number>;\n\n /**\n * Maximum distance (in pixels) the pointer can move before cancelling.\n * Set to `false` to disable distance checking.\n * @default 10\n */\n readonly distanceThreshold?: number | false;\n}\n\nexport interface OnLongPressRef {\n readonly destroy: () => void;\n}\n\n/**\n * Detect long press gestures on an element.\n * Calls a callback after a configurable delay if the pointer stays down\n * without moving beyond the distance threshold.\n *\n * @param target - The element to detect long presses on\n * @param handler - Callback invoked when a long press is detected\n * @param options - Optional configuration including delay, distance threshold, and injector\n * @returns A OnLongPressRef with a destroy method to stop detection\n *\n * @example\n * ```typescript\n * @Component({\n * template: `<button #btn>Hold me</button>`\n * })\n * class LongPressComponent {\n * readonly btn = viewChild<ElementRef>('btn');\n *\n * constructor() {\n * onLongPress(this.btn, () => {\n * console.log('Long press detected!');\n * });\n * }\n * }\n * ```\n */\nexport function onLongPress(\n target: MaybeElementSignal<HTMLElement>,\n handler: (event: PointerEvent) => void,\n options?: OnLongPressOptions\n): OnLongPressRef {\n const { runInContext } = setupContext(options?.injector, onLongPress);\n\n return runInContext(({ isServer, onCleanup }) => {\n if (isServer) {\n return NOOP_EFFECT_REF;\n }\n\n const distanceThreshold = options?.distanceThreshold;\n\n let startX = 0;\n let startY = 0;\n let longPressTimeout: Timer;\n\n function abortPendingPress(): void {\n clearTimeout(longPressTimeout);\n longPressTimeout = undefined;\n }\n\n const downListener = listener(target, 'pointerdown', (e: PointerEvent) => {\n startX = e.clientX;\n startY = e.clientY;\n\n const delay = toValue(options?.delay) ?? 500;\n\n longPressTimeout = setTimeout(() => {\n handler(e);\n longPressTimeout = undefined;\n }, delay);\n });\n\n const moveListener = listener(target, 'pointermove', (e: PointerEvent) => {\n if (!longPressTimeout || distanceThreshold === false) {\n return;\n }\n\n const threshold = distanceThreshold ?? 10;\n const dx = e.clientX - startX;\n const dy = e.clientY - startY;\n\n if (Math.sqrt(dx * dx + dy * dy) > threshold) {\n abortPendingPress();\n }\n });\n\n const upListener = listener(target, 'pointerup', abortPendingPress);\n const leaveListener = listener(target, 'pointerleave', abortPendingPress);\n\n onCleanup(abortPendingPress);\n\n return {\n destroy: () => {\n abortPendingPress();\n upListener.destroy();\n downListener.destroy();\n moveListener.destroy();\n leaveListener.destroy();\n },\n };\n });\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;AAuBA;;;;;;;;;;;;;;;;;;;;;;;;;AAyBG;SACa,WAAW,CACzB,MAAuC,EACvC,OAAsC,EACtC,OAA4B,EAAA;AAE5B,IAAA,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,WAAW,CAAC;IAErE,OAAO,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAI;QAC9C,IAAI,QAAQ,EAAE;AACZ,YAAA,OAAO,eAAe;QACxB;AAEA,QAAA,MAAM,iBAAiB,GAAG,OAAO,EAAE,iBAAiB;QAEpD,IAAI,MAAM,GAAG,CAAC;QACd,IAAI,MAAM,GAAG,CAAC;AACd,QAAA,IAAI,gBAAuB;AAE3B,QAAA,SAAS,iBAAiB,GAAA;YACxB,YAAY,CAAC,gBAAgB,CAAC;YAC9B,gBAAgB,GAAG,SAAS;QAC9B;QAEA,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,EAAE,aAAa,EAAE,CAAC,CAAe,KAAI;AACvE,YAAA,MAAM,GAAG,CAAC,CAAC,OAAO;AAClB,YAAA,MAAM,GAAG,CAAC,CAAC,OAAO;YAElB,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,GAAG;AAE5C,YAAA,gBAAgB,GAAG,UAAU,CAAC,MAAK;gBACjC,OAAO,CAAC,CAAC,CAAC;gBACV,gBAAgB,GAAG,SAAS;YAC9B,CAAC,EAAE,KAAK,CAAC;AACX,QAAA,CAAC,CAAC;QAEF,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,EAAE,aAAa,EAAE,CAAC,CAAe,KAAI;AACvE,YAAA,IAAI,CAAC,gBAAgB,IAAI,iBAAiB,KAAK,KAAK,EAAE;gBACpD;YACF;AAEA,YAAA,MAAM,SAAS,GAAG,iBAAiB,IAAI,EAAE;AACzC,YAAA,MAAM,EAAE,GAAG,CAAC,CAAC,OAAO,GAAG,MAAM;AAC7B,YAAA,MAAM,EAAE,GAAG,CAAC,CAAC,OAAO,GAAG,MAAM;AAE7B,YAAA,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,GAAG,SAAS,EAAE;AAC5C,gBAAA,iBAAiB,EAAE;YACrB;AACF,QAAA,CAAC,CAAC;QAEF,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,EAAE,WAAW,EAAE,iBAAiB,CAAC;QACnE,MAAM,aAAa,GAAG,QAAQ,CAAC,MAAM,EAAE,cAAc,EAAE,iBAAiB,CAAC;QAEzE,SAAS,CAAC,iBAAiB,CAAC;QAE5B,OAAO;YACL,OAAO,EAAE,MAAK;AACZ,gBAAA,iBAAiB,EAAE;gBACnB,UAAU,CAAC,OAAO,EAAE;gBACpB,YAAY,CAAC,OAAO,EAAE;gBACtB,YAAY,CAAC,OAAO,EAAE;gBACtB,aAAa,CAAC,OAAO,EAAE;YACzB,CAAC;SACF;AACH,IAAA,CAAC,CAAC;AACJ;;ACjHA;;AAEG;;;;"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { signal } from '@angular/core';
|
|
2
|
+
import { setupContext, constSignal } from '@signality/core/internal';
|
|
3
|
+
import { listener } from '@signality/core/browser/listener';
|
|
4
|
+
import { onDisconnect } from '@signality/core/elements/on-disconnect';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Reactive pointer-swipe detection on an element.
|
|
8
|
+
* Tracks swipe gestures using Pointer Events and provides direction and distance signals.
|
|
9
|
+
* Works with mouse, touch, and pen input.
|
|
10
|
+
*
|
|
11
|
+
* @param target - Element to detect swipe gestures on
|
|
12
|
+
* @param options - Optional configuration including threshold, pointer types, and injector
|
|
13
|
+
* @returns A PointerSwipeRef with reactive signals for swipe state
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* @Component({
|
|
18
|
+
* template: `
|
|
19
|
+
* <div #area>
|
|
20
|
+
* <p>Swiping: {{ sw.isSwiping() }}</p>
|
|
21
|
+
* <p>Direction: {{ sw.direction() }}</p>
|
|
22
|
+
* </div>
|
|
23
|
+
* `
|
|
24
|
+
* })
|
|
25
|
+
* class PointerSwipeComponent {
|
|
26
|
+
* readonly area = viewChild<ElementRef>('area');
|
|
27
|
+
* readonly sw = pointerSwipe(this.area);
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
function pointerSwipe(target, options) {
|
|
32
|
+
const { runInContext } = setupContext(options?.injector, pointerSwipe);
|
|
33
|
+
return runInContext(({ isServer }) => {
|
|
34
|
+
if (isServer) {
|
|
35
|
+
return {
|
|
36
|
+
isSwiping: constSignal(false),
|
|
37
|
+
direction: constSignal('none'),
|
|
38
|
+
distanceX: constSignal(0),
|
|
39
|
+
distanceY: constSignal(0),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const threshold = options?.threshold ?? 50;
|
|
43
|
+
const pointerTypes = options?.pointerTypes;
|
|
44
|
+
const isSwiping = signal(false, ...(ngDevMode ? [{ debugName: "isSwiping" }] : []));
|
|
45
|
+
const direction = signal('none', ...(ngDevMode ? [{ debugName: "direction" }] : []));
|
|
46
|
+
const distanceX = signal(0, ...(ngDevMode ? [{ debugName: "distanceX" }] : []));
|
|
47
|
+
const distanceY = signal(0, ...(ngDevMode ? [{ debugName: "distanceY" }] : []));
|
|
48
|
+
let startX = 0;
|
|
49
|
+
let startY = 0;
|
|
50
|
+
let isPointerDown = false;
|
|
51
|
+
function isAllowedPointerType(e) {
|
|
52
|
+
return !pointerTypes || pointerTypes.includes(e.pointerType);
|
|
53
|
+
}
|
|
54
|
+
listener.passive(target, 'pointerdown', (e) => {
|
|
55
|
+
if (!isAllowedPointerType(e)) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
isPointerDown = true;
|
|
59
|
+
e.target?.setPointerCapture(e.pointerId);
|
|
60
|
+
startX = e.clientX;
|
|
61
|
+
startY = e.clientY;
|
|
62
|
+
isSwiping.set(false);
|
|
63
|
+
direction.set('none');
|
|
64
|
+
distanceX.set(0);
|
|
65
|
+
distanceY.set(0);
|
|
66
|
+
});
|
|
67
|
+
listener.passive(target, 'pointermove', (e) => {
|
|
68
|
+
if (!isPointerDown || !isAllowedPointerType(e)) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const dx = startX - e.clientX;
|
|
72
|
+
const dy = startY - e.clientY;
|
|
73
|
+
distanceX.set(dx);
|
|
74
|
+
distanceY.set(dy);
|
|
75
|
+
const absDx = Math.abs(dx);
|
|
76
|
+
const absDy = Math.abs(dy);
|
|
77
|
+
if (Math.max(absDx, absDy) >= threshold) {
|
|
78
|
+
if (!isSwiping()) {
|
|
79
|
+
isSwiping.set(true);
|
|
80
|
+
}
|
|
81
|
+
if (absDx > absDy) {
|
|
82
|
+
direction.set(dx > 0 ? 'left' : 'right');
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
direction.set(dy > 0 ? 'up' : 'down');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
const onPointerEnd = () => {
|
|
90
|
+
isPointerDown = false;
|
|
91
|
+
isSwiping.set(false);
|
|
92
|
+
};
|
|
93
|
+
listener.passive(target, 'pointerup', onPointerEnd);
|
|
94
|
+
listener.passive(target, 'pointercancel', onPointerEnd);
|
|
95
|
+
onDisconnect(target, () => {
|
|
96
|
+
isPointerDown = false;
|
|
97
|
+
isSwiping.set(false);
|
|
98
|
+
direction.set('none');
|
|
99
|
+
distanceX.set(0);
|
|
100
|
+
distanceY.set(0);
|
|
101
|
+
});
|
|
102
|
+
return {
|
|
103
|
+
isSwiping: isSwiping.asReadonly(),
|
|
104
|
+
direction: direction.asReadonly(),
|
|
105
|
+
distanceX: distanceX.asReadonly(),
|
|
106
|
+
distanceY: distanceY.asReadonly(),
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Generated bundle index. Do not edit.
|
|
113
|
+
*/
|
|
114
|
+
|
|
115
|
+
export { pointerSwipe };
|
|
116
|
+
//# sourceMappingURL=signality-core-elements-pointer-swipe.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signality-core-elements-pointer-swipe.mjs","sources":["../../../projects/core/elements/pointer-swipe/index.ts","../../../projects/core/elements/pointer-swipe/signality-core-elements-pointer-swipe.ts"],"sourcesContent":["import { type Signal, signal } from '@angular/core';\nimport { constSignal, setupContext } from '@signality/core/internal';\nimport type { MaybeElementSignal, WithInjector } from '@signality/core/types';\nimport { listener } from '@signality/core/browser/listener';\nimport { onDisconnect } from '@signality/core/elements/on-disconnect';\n\n/**\n * Pointer type that triggered the event.\n */\nexport type PointerType = 'mouse' | 'touch' | 'pen';\n\n/**\n * Possible swipe direction values.\n */\nexport type PointerSwipeDirection = 'up' | 'down' | 'left' | 'right' | 'none';\n\nexport interface PointerSwipeOptions extends WithInjector {\n /**\n * Minimum distance in pixels before a swipe is recognized.\n * @default 50\n */\n readonly threshold?: number;\n\n /**\n * Pointer types to listen for.\n * @default ['mouse', 'touch', 'pen']\n */\n readonly pointerTypes?: PointerType[];\n}\n\nexport interface PointerSwipeRef {\n /** Whether a swipe gesture is currently in progress. */\n readonly isSwiping: Signal<boolean>;\n\n /** Current swipe direction, or `'none'` if a threshold has not been exceeded. */\n readonly direction: Signal<PointerSwipeDirection>;\n\n /** Horizontal distance from start (positive = swiped left). */\n readonly distanceX: Signal<number>;\n\n /** Vertical distance from start (positive = swiped up). */\n readonly distanceY: Signal<number>;\n}\n\n/**\n * Reactive pointer-swipe detection on an element.\n * Tracks swipe gestures using Pointer Events and provides direction and distance signals.\n * Works with mouse, touch, and pen input.\n *\n * @param target - Element to detect swipe gestures on\n * @param options - Optional configuration including threshold, pointer types, and injector\n * @returns A PointerSwipeRef with reactive signals for swipe state\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * <div #area>\n * <p>Swiping: {{ sw.isSwiping() }}</p>\n * <p>Direction: {{ sw.direction() }}</p>\n * </div>\n * `\n * })\n * class PointerSwipeComponent {\n * readonly area = viewChild<ElementRef>('area');\n * readonly sw = pointerSwipe(this.area);\n * }\n * ```\n */\nexport function pointerSwipe(\n target: MaybeElementSignal<HTMLElement>,\n options?: PointerSwipeOptions\n): PointerSwipeRef {\n const { runInContext } = setupContext(options?.injector, pointerSwipe);\n\n return runInContext(({ isServer }) => {\n if (isServer) {\n return {\n isSwiping: constSignal(false),\n direction: constSignal('none'),\n distanceX: constSignal(0),\n distanceY: constSignal(0),\n };\n }\n\n const threshold = options?.threshold ?? 50;\n const pointerTypes = options?.pointerTypes;\n\n const isSwiping = signal(false);\n const direction = signal<PointerSwipeDirection>('none');\n const distanceX = signal(0);\n const distanceY = signal(0);\n\n let startX = 0;\n let startY = 0;\n let isPointerDown = false;\n\n function isAllowedPointerType(e: PointerEvent): boolean {\n return !pointerTypes || pointerTypes.includes(e.pointerType as PointerType);\n }\n\n listener.passive(target, 'pointerdown', (e: PointerEvent) => {\n if (!isAllowedPointerType(e)) {\n return;\n }\n\n isPointerDown = true;\n\n (e.target as HTMLElement | undefined)?.setPointerCapture(e.pointerId);\n\n startX = e.clientX;\n startY = e.clientY;\n\n isSwiping.set(false);\n direction.set('none');\n distanceX.set(0);\n distanceY.set(0);\n });\n\n listener.passive(target, 'pointermove', (e: PointerEvent) => {\n if (!isPointerDown || !isAllowedPointerType(e)) {\n return;\n }\n\n const dx = startX - e.clientX;\n const dy = startY - e.clientY;\n\n distanceX.set(dx);\n distanceY.set(dy);\n\n const absDx = Math.abs(dx);\n const absDy = Math.abs(dy);\n\n if (Math.max(absDx, absDy) >= threshold) {\n if (!isSwiping()) {\n isSwiping.set(true);\n }\n\n if (absDx > absDy) {\n direction.set(dx > 0 ? 'left' : 'right');\n } else {\n direction.set(dy > 0 ? 'up' : 'down');\n }\n }\n });\n\n const onPointerEnd = () => {\n isPointerDown = false;\n isSwiping.set(false);\n };\n\n listener.passive(target, 'pointerup', onPointerEnd);\n listener.passive(target, 'pointercancel', onPointerEnd);\n\n onDisconnect(target, () => {\n isPointerDown = false;\n isSwiping.set(false);\n direction.set('none');\n distanceX.set(0);\n distanceY.set(0);\n });\n\n return {\n isSwiping: isSwiping.asReadonly(),\n direction: direction.asReadonly(),\n distanceX: distanceX.asReadonly(),\n distanceY: distanceY.asReadonly(),\n };\n });\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|