@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,88 @@
|
|
|
1
|
+
import { signal } from '@angular/core';
|
|
2
|
+
import { setupContext, constSignal, getActiveElement, getShadowRoot, getEventTarget, createToken } from '@signality/core/internal';
|
|
3
|
+
import { listener, setupSync } from '@signality/core/browser/listener';
|
|
4
|
+
import { onDisconnect } from '@signality/core/elements/on-disconnect';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Signal-based wrapper around the [document.activeElement](https://developer.mozilla.org/en-US/docs/Web/API/Document/activeElement) property.
|
|
8
|
+
*
|
|
9
|
+
* @param options - Optional configuration including signal options and injector
|
|
10
|
+
* @returns A signal containing the currently active element, or null
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* @Component({
|
|
15
|
+
* template: `
|
|
16
|
+
* @if (activeEl(); as el) {
|
|
17
|
+
* <p>Active element: {{ el.tagName }}</p>
|
|
18
|
+
* } @else {
|
|
19
|
+
* <p>No element is active</p>
|
|
20
|
+
* }
|
|
21
|
+
* `
|
|
22
|
+
* })
|
|
23
|
+
* class ActiveElementComponent {
|
|
24
|
+
* readonly activeEl = activeElement();
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
function activeElement(options) {
|
|
29
|
+
const { runInContext } = setupContext(options?.injector, activeElement);
|
|
30
|
+
return runInContext(({ isServer, injector }) => {
|
|
31
|
+
if (isServer) {
|
|
32
|
+
return constSignal(null);
|
|
33
|
+
}
|
|
34
|
+
const activeEl = signal(getActiveElement(document), options);
|
|
35
|
+
let shadowFocusinListener;
|
|
36
|
+
let shadowFocusoutListener;
|
|
37
|
+
const updateActiveElement = () => {
|
|
38
|
+
activeEl.set(getActiveElement(document));
|
|
39
|
+
};
|
|
40
|
+
const cleanupShadowListeners = () => {
|
|
41
|
+
shadowFocusinListener?.destroy();
|
|
42
|
+
shadowFocusinListener = null;
|
|
43
|
+
shadowFocusoutListener?.destroy();
|
|
44
|
+
shadowFocusoutListener = null;
|
|
45
|
+
};
|
|
46
|
+
const setupShadowListeners = (target) => {
|
|
47
|
+
cleanupShadowListeners();
|
|
48
|
+
if (!target) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const shadowRoot = getShadowRoot(target);
|
|
52
|
+
if (!shadowRoot) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
shadowFocusinListener = listener(shadowRoot, 'focusin', updateActiveElement, { injector });
|
|
56
|
+
shadowFocusoutListener = listener(shadowRoot, 'focusout', () => {
|
|
57
|
+
setTimeout(() => {
|
|
58
|
+
updateActiveElement();
|
|
59
|
+
setupShadowListeners(activeEl());
|
|
60
|
+
});
|
|
61
|
+
}, { injector });
|
|
62
|
+
};
|
|
63
|
+
setupSync(() => {
|
|
64
|
+
listener.capture(window, 'focusin', (e) => {
|
|
65
|
+
updateActiveElement();
|
|
66
|
+
setupShadowListeners(getEventTarget(e));
|
|
67
|
+
});
|
|
68
|
+
listener.capture(window, 'focusout', (e) => {
|
|
69
|
+
if (e.relatedTarget === null) {
|
|
70
|
+
updateActiveElement();
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
onDisconnect(activeEl, () => {
|
|
75
|
+
cleanupShadowListeners();
|
|
76
|
+
updateActiveElement();
|
|
77
|
+
});
|
|
78
|
+
return activeEl.asReadonly();
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
const ACTIVE_ELEMENT = /* @__PURE__ */ createToken(activeElement);
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Generated bundle index. Do not edit.
|
|
85
|
+
*/
|
|
86
|
+
|
|
87
|
+
export { ACTIVE_ELEMENT, activeElement };
|
|
88
|
+
//# sourceMappingURL=signality-core-elements-active-element.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signality-core-elements-active-element.mjs","sources":["../../../projects/core/elements/active-element/index.ts","../../../projects/core/elements/active-element/signality-core-elements-active-element.ts"],"sourcesContent":["import { type CreateSignalOptions, type Signal, signal } from '@angular/core';\nimport {\n constSignal,\n createToken,\n getActiveElement,\n getEventTarget,\n getShadowRoot,\n setupContext,\n} from '@signality/core/internal';\nimport type { WithInjector } from '@signality/core/types';\nimport { listener, type ListenerRef, setupSync } from '@signality/core/browser/listener';\nimport { onDisconnect } from '@signality/core/elements/on-disconnect';\n\nexport type ActiveElementOptions = CreateSignalOptions<Element | null> & WithInjector;\n\n/**\n * Signal-based wrapper around the [document.activeElement](https://developer.mozilla.org/en-US/docs/Web/API/Document/activeElement) property.\n *\n * @param options - Optional configuration including signal options and injector\n * @returns A signal containing the currently active element, or null\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * @if (activeEl(); as el) {\n * <p>Active element: {{ el.tagName }}</p>\n * } @else {\n * <p>No element is active</p>\n * }\n * `\n * })\n * class ActiveElementComponent {\n * readonly activeEl = activeElement();\n * }\n * ```\n */\nexport function activeElement(options?: ActiveElementOptions): Signal<Element | null> {\n const { runInContext } = setupContext(options?.injector, activeElement);\n\n return runInContext(({ isServer, injector }) => {\n if (isServer) {\n return constSignal(null);\n }\n\n const activeEl = signal<Element | null>(getActiveElement(document), options);\n\n let shadowFocusinListener: ListenerRef | null;\n let shadowFocusoutListener: ListenerRef | null;\n\n const updateActiveElement = () => {\n activeEl.set(getActiveElement(document));\n };\n\n const cleanupShadowListeners = () => {\n shadowFocusinListener?.destroy();\n shadowFocusinListener = null;\n shadowFocusoutListener?.destroy();\n shadowFocusoutListener = null;\n };\n\n const setupShadowListeners = (target: Element | null) => {\n cleanupShadowListeners();\n\n if (!target) {\n return;\n }\n\n const shadowRoot = getShadowRoot(target);\n\n if (!shadowRoot) {\n return;\n }\n\n shadowFocusinListener = listener(shadowRoot, 'focusin', updateActiveElement, { injector });\n\n shadowFocusoutListener = listener(\n shadowRoot,\n 'focusout',\n () => {\n setTimeout(() => {\n updateActiveElement();\n setupShadowListeners(activeEl());\n });\n },\n { injector }\n );\n };\n\n setupSync(() => {\n listener.capture(window, 'focusin', (e: FocusEvent) => {\n updateActiveElement();\n setupShadowListeners(getEventTarget(e));\n });\n\n listener.capture(window, 'focusout', (e: FocusEvent) => {\n if (e.relatedTarget === null) {\n updateActiveElement();\n }\n });\n });\n\n onDisconnect(activeEl, () => {\n cleanupShadowListeners();\n updateActiveElement();\n });\n\n return activeEl.asReadonly();\n });\n}\n\nexport const ACTIVE_ELEMENT = /* @__PURE__ */ createToken(activeElement);\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;AAeA;;;;;;;;;;;;;;;;;;;;;AAqBG;AACG,SAAU,aAAa,CAAC,OAA8B,EAAA;AAC1D,IAAA,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,aAAa,CAAC;IAEvE,OAAO,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAI;QAC7C,IAAI,QAAQ,EAAE;AACZ,YAAA,OAAO,WAAW,CAAC,IAAI,CAAC;QAC1B;QAEA,MAAM,QAAQ,GAAG,MAAM,CAAiB,gBAAgB,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;AAE5E,QAAA,IAAI,qBAAyC;AAC7C,QAAA,IAAI,sBAA0C;QAE9C,MAAM,mBAAmB,GAAG,MAAK;YAC/B,QAAQ,CAAC,GAAG,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC;AAC1C,QAAA,CAAC;QAED,MAAM,sBAAsB,GAAG,MAAK;YAClC,qBAAqB,EAAE,OAAO,EAAE;YAChC,qBAAqB,GAAG,IAAI;YAC5B,sBAAsB,EAAE,OAAO,EAAE;YACjC,sBAAsB,GAAG,IAAI;AAC/B,QAAA,CAAC;AAED,QAAA,MAAM,oBAAoB,GAAG,CAAC,MAAsB,KAAI;AACtD,YAAA,sBAAsB,EAAE;YAExB,IAAI,CAAC,MAAM,EAAE;gBACX;YACF;AAEA,YAAA,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC;YAExC,IAAI,CAAC,UAAU,EAAE;gBACf;YACF;AAEA,YAAA,qBAAqB,GAAG,QAAQ,CAAC,UAAU,EAAE,SAAS,EAAE,mBAAmB,EAAE,EAAE,QAAQ,EAAE,CAAC;YAE1F,sBAAsB,GAAG,QAAQ,CAC/B,UAAU,EACV,UAAU,EACV,MAAK;gBACH,UAAU,CAAC,MAAK;AACd,oBAAA,mBAAmB,EAAE;AACrB,oBAAA,oBAAoB,CAAC,QAAQ,EAAE,CAAC;AAClC,gBAAA,CAAC,CAAC;AACJ,YAAA,CAAC,EACD,EAAE,QAAQ,EAAE,CACb;AACH,QAAA,CAAC;QAED,SAAS,CAAC,MAAK;YACb,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,CAAa,KAAI;AACpD,gBAAA,mBAAmB,EAAE;AACrB,gBAAA,oBAAoB,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC;AACzC,YAAA,CAAC,CAAC;YAEF,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC,CAAa,KAAI;AACrD,gBAAA,IAAI,CAAC,CAAC,aAAa,KAAK,IAAI,EAAE;AAC5B,oBAAA,mBAAmB,EAAE;gBACvB;AACF,YAAA,CAAC,CAAC;AACJ,QAAA,CAAC,CAAC;AAEF,QAAA,YAAY,CAAC,QAAQ,EAAE,MAAK;AAC1B,YAAA,sBAAsB,EAAE;AACxB,YAAA,mBAAmB,EAAE;AACvB,QAAA,CAAC,CAAC;AAEF,QAAA,OAAO,QAAQ,CAAC,UAAU,EAAE;AAC9B,IAAA,CAAC,CAAC;AACJ;AAEO,MAAM,cAAc,mBAAmB,WAAW,CAAC,aAAa;;AC/GvE;;AAEG;;;;"}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { signal, isSignal } from '@angular/core';
|
|
2
|
+
import { setupContext, constSignal, toValue, toElement, isNodeWithin } from '@signality/core/internal';
|
|
3
|
+
import { listener } from '@signality/core/browser/listener';
|
|
4
|
+
import { onDisconnect } from '@signality/core/elements/on-disconnect';
|
|
5
|
+
import { watcher } from '@signality/core/reactivity/watcher';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Signal-based wrapper around the [HTML Drag and Drop API](https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API).
|
|
9
|
+
*
|
|
10
|
+
* @param target - Drop zone element
|
|
11
|
+
* @param options - Optional configuration
|
|
12
|
+
* @returns An object with isOver, files, data, and isDragging signals
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* @Component({
|
|
17
|
+
* template: `
|
|
18
|
+
* <div
|
|
19
|
+
* #zone
|
|
20
|
+
* [class.over]="drop.isOver()"
|
|
21
|
+
* [class.dragging]="drop.isDragging()"
|
|
22
|
+
* >
|
|
23
|
+
* Drop files here
|
|
24
|
+
* @if (drop.files().length > 0) {
|
|
25
|
+
* <ul>
|
|
26
|
+
* @for (file of drop.files(); track file.name) {
|
|
27
|
+
* <li>{{ file.name }} ({{ file.size }} bytes)</li>
|
|
28
|
+
* }
|
|
29
|
+
* </ul>
|
|
30
|
+
* }
|
|
31
|
+
* </div>
|
|
32
|
+
* `
|
|
33
|
+
* })
|
|
34
|
+
* class DropzoneComponent {
|
|
35
|
+
* readonly zone = viewChild<ElementRef>('zone');
|
|
36
|
+
* readonly drop = dropzone(this.zone, { accept: ['image/*'], multiple: true });
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
function dropzone(target, options) {
|
|
41
|
+
const { runInContext } = setupContext(options?.injector, dropzone);
|
|
42
|
+
return runInContext(({ isServer }) => {
|
|
43
|
+
if (isServer) {
|
|
44
|
+
return {
|
|
45
|
+
files: signal([]),
|
|
46
|
+
isOver: constSignal(false),
|
|
47
|
+
isDragging: constSignal(false),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const accept = options?.accept ?? ['*'];
|
|
51
|
+
const multiple = options?.multiple ?? true;
|
|
52
|
+
const preventDocumentDrop = options?.preventDocumentDrop ?? true;
|
|
53
|
+
const files = signal([], ...(ngDevMode ? [{ debugName: "files" }] : []));
|
|
54
|
+
const isOver = signal(false, ...(ngDevMode ? [{ debugName: "isOver" }] : []));
|
|
55
|
+
const isDragging = signal(false, ...(ngDevMode ? [{ debugName: "isDragging" }] : []));
|
|
56
|
+
let dragCounter = 0;
|
|
57
|
+
const isAcceptedType = (file) => {
|
|
58
|
+
const acceptTypes = toValue(accept);
|
|
59
|
+
if (acceptTypes.includes('*')) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
return acceptTypes.some(type => {
|
|
63
|
+
if (type.endsWith('/*')) {
|
|
64
|
+
const prefix = type.slice(0, -1);
|
|
65
|
+
return file.type.startsWith(prefix);
|
|
66
|
+
}
|
|
67
|
+
return file.type === type;
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
const filterFiles = (files) => {
|
|
71
|
+
const isMultiple = toValue(multiple);
|
|
72
|
+
const result = [];
|
|
73
|
+
for (let i = 0; i < files.length; i++) {
|
|
74
|
+
const file = files[i];
|
|
75
|
+
if (isAcceptedType(file)) {
|
|
76
|
+
result.push(file);
|
|
77
|
+
if (!isMultiple) {
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return result;
|
|
83
|
+
};
|
|
84
|
+
listener.prevent.capture(target, 'dragenter', () => {
|
|
85
|
+
dragCounter++;
|
|
86
|
+
isOver.set(true);
|
|
87
|
+
});
|
|
88
|
+
listener.prevent.capture(target, 'dragleave', () => {
|
|
89
|
+
dragCounter--;
|
|
90
|
+
if (dragCounter === 0) {
|
|
91
|
+
isOver.set(false);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
listener.prevent.capture(target, 'dragover', (e) => {
|
|
95
|
+
if (e.dataTransfer) {
|
|
96
|
+
e.dataTransfer.dropEffect = 'copy';
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
listener.prevent.stop.capture(target, 'drop', (e) => {
|
|
100
|
+
dragCounter = 0;
|
|
101
|
+
isOver.set(false);
|
|
102
|
+
isDragging.set(false);
|
|
103
|
+
if (e.dataTransfer && e.dataTransfer.files.length > 0) {
|
|
104
|
+
const filtered = filterFiles(Array.from(e.dataTransfer.files));
|
|
105
|
+
files.set(filtered);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
listener.capture(document, 'dragenter', () => {
|
|
109
|
+
isDragging.set(true);
|
|
110
|
+
});
|
|
111
|
+
listener.capture(document, 'dragleave', (e) => {
|
|
112
|
+
if (e.relatedTarget === null) {
|
|
113
|
+
isDragging.set(false);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
listener.capture(document, 'drop', () => {
|
|
117
|
+
isDragging.set(false);
|
|
118
|
+
});
|
|
119
|
+
if (preventDocumentDrop) {
|
|
120
|
+
listener.capture(document, 'dragover', (e) => {
|
|
121
|
+
const el = toElement(target);
|
|
122
|
+
if (el && !isNodeWithin(e.target, el)) {
|
|
123
|
+
e.preventDefault();
|
|
124
|
+
if (e.dataTransfer) {
|
|
125
|
+
e.dataTransfer.dropEffect = 'none';
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
listener.capture(document, 'drop', (e) => {
|
|
130
|
+
const el = toElement(target);
|
|
131
|
+
if (el && !isNodeWithin(e.target, el)) {
|
|
132
|
+
e.preventDefault();
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
const filters = [accept, multiple].filter(isSignal);
|
|
137
|
+
if (filters.length) {
|
|
138
|
+
watcher(filters, () => files.update(filterFiles));
|
|
139
|
+
}
|
|
140
|
+
onDisconnect(target, () => {
|
|
141
|
+
files.set([]);
|
|
142
|
+
isOver.set(false);
|
|
143
|
+
isDragging.set(false);
|
|
144
|
+
});
|
|
145
|
+
return {
|
|
146
|
+
files,
|
|
147
|
+
isOver: isOver.asReadonly(),
|
|
148
|
+
isDragging: isDragging.asReadonly(),
|
|
149
|
+
};
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Generated bundle index. Do not edit.
|
|
155
|
+
*/
|
|
156
|
+
|
|
157
|
+
export { dropzone };
|
|
158
|
+
//# sourceMappingURL=signality-core-elements-dropzone.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signality-core-elements-dropzone.mjs","sources":["../../../projects/core/elements/dropzone/index.ts","../../../projects/core/elements/dropzone/signality-core-elements-dropzone.ts"],"sourcesContent":["import { isSignal, type Signal, signal, type WritableSignal } from '@angular/core';\nimport {\n constSignal,\n isNodeWithin,\n setupContext,\n toElement,\n toValue,\n} from '@signality/core/internal';\nimport type { MaybeElementSignal, MaybeSignal, WithInjector } from '@signality/core/types';\nimport { listener } from '@signality/core/browser/listener';\nimport { onDisconnect } from '@signality/core/elements/on-disconnect';\nimport { watcher } from '@signality/core/reactivity/watcher';\n\nexport interface DropzoneOptions extends WithInjector {\n /**\n * Accepted MIME types.\n * @default ['*']\n */\n readonly accept?: MaybeSignal<string[]>;\n\n /**\n * Allow multiple files.\n * @default true\n */\n readonly multiple?: MaybeSignal<boolean>;\n\n /**\n * Prevent drops outside the zone.\n * @default true\n */\n readonly preventDocumentDrop?: boolean;\n}\n\nexport interface DropzoneRef {\n /** Dropped files */\n readonly files: WritableSignal<File[]>;\n\n /** Whether dragging over the zone */\n readonly isOver: Signal<boolean>;\n\n /** Whether any drag is in progress (document-wide) */\n readonly isDragging: Signal<boolean>;\n}\n\n/**\n * Signal-based wrapper around the [HTML Drag and Drop API](https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API).\n *\n * @param target - Drop zone element\n * @param options - Optional configuration\n * @returns An object with isOver, files, data, and isDragging signals\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * <div\n * #zone\n * [class.over]=\"drop.isOver()\"\n * [class.dragging]=\"drop.isDragging()\"\n * >\n * Drop files here\n * @if (drop.files().length > 0) {\n * <ul>\n * @for (file of drop.files(); track file.name) {\n * <li>{{ file.name }} ({{ file.size }} bytes)</li>\n * }\n * </ul>\n * }\n * </div>\n * `\n * })\n * class DropzoneComponent {\n * readonly zone = viewChild<ElementRef>('zone');\n * readonly drop = dropzone(this.zone, { accept: ['image/*'], multiple: true });\n * }\n * ```\n */\nexport function dropzone(\n target: MaybeElementSignal<HTMLElement>,\n options?: DropzoneOptions\n): DropzoneRef {\n const { runInContext } = setupContext(options?.injector, dropzone);\n\n return runInContext(({ isServer }) => {\n if (isServer) {\n return {\n files: signal([]),\n isOver: constSignal(false),\n isDragging: constSignal(false),\n };\n }\n\n const accept = options?.accept ?? ['*'];\n const multiple = options?.multiple ?? true;\n const preventDocumentDrop = options?.preventDocumentDrop ?? true;\n\n const files = signal<File[]>([]);\n const isOver = signal(false);\n const isDragging = signal(false);\n\n let dragCounter = 0;\n\n const isAcceptedType = (file: File): boolean => {\n const acceptTypes = toValue(accept);\n\n if (acceptTypes.includes('*')) {\n return true;\n }\n\n return acceptTypes.some(type => {\n if (type.endsWith('/*')) {\n const prefix = type.slice(0, -1);\n return file.type.startsWith(prefix);\n }\n return file.type === type;\n });\n };\n\n const filterFiles = (files: File[]): File[] => {\n const isMultiple = toValue(multiple);\n const result: File[] = [];\n\n for (let i = 0; i < files.length; i++) {\n const file = files[i];\n if (isAcceptedType(file)) {\n result.push(file);\n if (!isMultiple) {\n break;\n }\n }\n }\n\n return result;\n };\n\n listener.prevent.capture(target, 'dragenter', () => {\n dragCounter++;\n isOver.set(true);\n });\n\n listener.prevent.capture(target, 'dragleave', () => {\n dragCounter--;\n if (dragCounter === 0) {\n isOver.set(false);\n }\n });\n\n listener.prevent.capture(target, 'dragover', (e: DragEvent) => {\n if (e.dataTransfer) {\n e.dataTransfer.dropEffect = 'copy';\n }\n });\n\n listener.prevent.stop.capture(target, 'drop', (e: DragEvent) => {\n dragCounter = 0;\n isOver.set(false);\n isDragging.set(false);\n\n if (e.dataTransfer && e.dataTransfer.files.length > 0) {\n const filtered = filterFiles(Array.from(e.dataTransfer.files));\n files.set(filtered);\n }\n });\n\n listener.capture(document, 'dragenter', () => {\n isDragging.set(true);\n });\n\n listener.capture(document, 'dragleave', (e: DragEvent) => {\n if (e.relatedTarget === null) {\n isDragging.set(false);\n }\n });\n\n listener.capture(document, 'drop', () => {\n isDragging.set(false);\n });\n\n if (preventDocumentDrop) {\n listener.capture(document, 'dragover', (e: DragEvent) => {\n const el = toElement(target);\n if (el && !isNodeWithin(e.target as Node, el)) {\n e.preventDefault();\n if (e.dataTransfer) {\n e.dataTransfer.dropEffect = 'none';\n }\n }\n });\n\n listener.capture(document, 'drop', (e: DragEvent) => {\n const el = toElement(target);\n if (el && !isNodeWithin(e.target as Node, el)) {\n e.preventDefault();\n }\n });\n }\n\n const filters = [accept, multiple].filter(isSignal) as Signal<any>[];\n\n if (filters.length) {\n watcher(filters, () => files.update(filterFiles));\n }\n\n onDisconnect(target, () => {\n files.set([]);\n isOver.set(false);\n isDragging.set(false);\n });\n\n return {\n files,\n isOver: isOver.asReadonly(),\n isDragging: isDragging.asReadonly(),\n };\n });\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { signal } from '@angular/core';
|
|
2
|
+
import { setupContext, constSignal, toElement } 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 focus-within state on an element.
|
|
8
|
+
* Detects when focus is inside an element or any of its descendants,
|
|
9
|
+
* analogous to the CSS `:focus-within` pseudo-class.
|
|
10
|
+
*
|
|
11
|
+
* @param target - The element to track focus-within state on
|
|
12
|
+
* @param options - Optional configuration including signal options and injector
|
|
13
|
+
* @returns A signal that is `true` when focus is within the element
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* @Component({
|
|
18
|
+
* template: `
|
|
19
|
+
* <div #container [class.focused]="isFocusedWithin()">
|
|
20
|
+
* <input placeholder="First" />
|
|
21
|
+
* <input placeholder="Second" />
|
|
22
|
+
* </div>
|
|
23
|
+
* `
|
|
24
|
+
* })
|
|
25
|
+
* class FocusWithinComponent {
|
|
26
|
+
* readonly container = viewChild<ElementRef>('container');
|
|
27
|
+
* readonly isFocusedWithin = elementFocusWithin(this.container);
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
function elementFocusWithin(target, options) {
|
|
32
|
+
const { runInContext } = setupContext(options?.injector, elementFocusWithin);
|
|
33
|
+
return runInContext(({ isServer }) => {
|
|
34
|
+
if (isServer) {
|
|
35
|
+
return constSignal(false);
|
|
36
|
+
}
|
|
37
|
+
const focused = signal(false, options);
|
|
38
|
+
listener(target, 'focusin', () => focused.set(true));
|
|
39
|
+
listener(target, 'focusout', e => {
|
|
40
|
+
const el = toElement(target);
|
|
41
|
+
if (el && e.relatedTarget instanceof Node && el.contains(e.relatedTarget)) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
focused.set(false);
|
|
45
|
+
});
|
|
46
|
+
onDisconnect(target, () => focused.set(false));
|
|
47
|
+
return focused.asReadonly();
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Generated bundle index. Do not edit.
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
export { elementFocusWithin };
|
|
56
|
+
//# sourceMappingURL=signality-core-elements-element-focus-within.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signality-core-elements-element-focus-within.mjs","sources":["../../../projects/core/elements/element-focus-within/index.ts","../../../projects/core/elements/element-focus-within/signality-core-elements-element-focus-within.ts"],"sourcesContent":["import { type CreateSignalOptions, signal, type Signal } from '@angular/core';\nimport { constSignal, setupContext, toElement } 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 type ElementFocusWithinOptions = CreateSignalOptions<boolean> & WithInjector;\n\n/**\n * Reactive tracking of focus-within state on an element.\n * Detects when focus is inside an element or any of its descendants,\n * analogous to the CSS `:focus-within` pseudo-class.\n *\n * @param target - The element to track focus-within state on\n * @param options - Optional configuration including signal options and injector\n * @returns A signal that is `true` when focus is within the element\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * <div #container [class.focused]=\"isFocusedWithin()\">\n * <input placeholder=\"First\" />\n * <input placeholder=\"Second\" />\n * </div>\n * `\n * })\n * class FocusWithinComponent {\n * readonly container = viewChild<ElementRef>('container');\n * readonly isFocusedWithin = elementFocusWithin(this.container);\n * }\n * ```\n */\nexport function elementFocusWithin(\n target: MaybeElementSignal<HTMLElement>,\n options?: ElementFocusWithinOptions\n): Signal<boolean> {\n const { runInContext } = setupContext(options?.injector, elementFocusWithin);\n\n return runInContext(({ isServer }) => {\n if (isServer) {\n return constSignal(false);\n }\n\n const focused = signal<boolean>(false, options);\n\n listener(target, 'focusin', () => focused.set(true));\n\n listener(target, 'focusout', e => {\n const el = toElement(target);\n\n if (el && e.relatedTarget instanceof Node && el.contains(e.relatedTarget)) {\n return;\n }\n\n focused.set(false);\n });\n\n onDisconnect(target, () => focused.set(false));\n\n return focused.asReadonly();\n });\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;AAQA;;;;;;;;;;;;;;;;;;;;;;;;AAwBG;AACG,SAAU,kBAAkB,CAChC,MAAuC,EACvC,OAAmC,EAAA;AAEnC,IAAA,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,kBAAkB,CAAC;AAE5E,IAAA,OAAO,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAI;QACnC,IAAI,QAAQ,EAAE;AACZ,YAAA,OAAO,WAAW,CAAC,KAAK,CAAC;QAC3B;QAEA,MAAM,OAAO,GAAG,MAAM,CAAU,KAAK,EAAE,OAAO,CAAC;AAE/C,QAAA,QAAQ,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AAEpD,QAAA,QAAQ,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC,IAAG;AAC/B,YAAA,MAAM,EAAE,GAAG,SAAS,CAAC,MAAM,CAAC;AAE5B,YAAA,IAAI,EAAE,IAAI,CAAC,CAAC,aAAa,YAAY,IAAI,IAAI,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,EAAE;gBACzE;YACF;AAEA,YAAA,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;AACpB,QAAA,CAAC,CAAC;AAEF,QAAA,YAAY,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AAE9C,QAAA,OAAO,OAAO,CAAC,UAAU,EAAE;AAC7B,IAAA,CAAC,CAAC;AACJ;;AC9DA;;AAEG;;;;"}
|
|
@@ -0,0 +1,54 @@
|
|
|
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 tracking of focus state on an element.
|
|
8
|
+
* Detects when an element gains or loses focus.
|
|
9
|
+
*
|
|
10
|
+
* @param target - The element to track focus state on
|
|
11
|
+
* @param options - Optional configuration including focusVisible mode and injector
|
|
12
|
+
* @returns A signal that is `true` when the element has focus
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* @Component({
|
|
17
|
+
* template: `
|
|
18
|
+
* <input #input [class.focused]="isFocused()" />
|
|
19
|
+
* @if (isFocused()) {
|
|
20
|
+
* <p>Input is focused</p>
|
|
21
|
+
* }
|
|
22
|
+
* `
|
|
23
|
+
* })
|
|
24
|
+
* class FocusComponent {
|
|
25
|
+
* readonly input = viewChild<ElementRef>('input');
|
|
26
|
+
* readonly isFocused = elementFocus(this.input);
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
function elementFocus(target, options) {
|
|
31
|
+
const { runInContext } = setupContext(options?.injector, elementFocus);
|
|
32
|
+
return runInContext(({ isServer }) => {
|
|
33
|
+
if (isServer) {
|
|
34
|
+
return constSignal(false);
|
|
35
|
+
}
|
|
36
|
+
const focusVisible = options?.focusVisible ?? false;
|
|
37
|
+
const focused = signal(false, options);
|
|
38
|
+
listener(target, 'focus', e => {
|
|
39
|
+
focused.set(focusVisible ? e.target.matches(':focus-visible') : true);
|
|
40
|
+
});
|
|
41
|
+
listener(target, 'blur', () => {
|
|
42
|
+
focused.set(false);
|
|
43
|
+
});
|
|
44
|
+
onDisconnect(target, () => focused.set(false));
|
|
45
|
+
return focused.asReadonly();
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Generated bundle index. Do not edit.
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
export { elementFocus };
|
|
54
|
+
//# sourceMappingURL=signality-core-elements-element-focus.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signality-core-elements-element-focus.mjs","sources":["../../../projects/core/elements/element-focus/index.ts","../../../projects/core/elements/element-focus/signality-core-elements-element-focus.ts"],"sourcesContent":["import { type CreateSignalOptions, signal, type 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\nexport interface ElementFocusOptions extends CreateSignalOptions<boolean>, WithInjector {\n /**\n * Track focus using the `:focus-visible` pseudo-class.\n * The browser uses heuristics to determine when focus should be visually indicated\n * (e.g., keyboard navigation, programmatic focus, or when the element requires user attention).\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/:focus-visible MDN: :focus-visible}\n * @default false\n */\n readonly focusVisible?: boolean;\n}\n\n/**\n * Reactive tracking of focus state on an element.\n * Detects when an element gains or loses focus.\n *\n * @param target - The element to track focus state on\n * @param options - Optional configuration including focusVisible mode and injector\n * @returns A signal that is `true` when the element has focus\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * <input #input [class.focused]=\"isFocused()\" />\n * @if (isFocused()) {\n * <p>Input is focused</p>\n * }\n * `\n * })\n * class FocusComponent {\n * readonly input = viewChild<ElementRef>('input');\n * readonly isFocused = elementFocus(this.input);\n * }\n * ```\n */\nexport function elementFocus(\n target: MaybeElementSignal<HTMLElement>,\n options?: ElementFocusOptions\n): Signal<boolean> {\n const { runInContext } = setupContext(options?.injector, elementFocus);\n\n return runInContext(({ isServer }) => {\n if (isServer) {\n return constSignal(false);\n }\n\n const focusVisible = options?.focusVisible ?? false;\n const focused = signal<boolean>(false, options);\n\n listener(target, 'focus', e => {\n focused.set(focusVisible ? (e.target as HTMLElement).matches(':focus-visible') : true);\n });\n\n listener(target, 'blur', () => {\n focused.set(false);\n });\n\n onDisconnect(target, () => focused.set(false));\n\n return focused.asReadonly();\n });\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;AAkBA;;;;;;;;;;;;;;;;;;;;;;;AAuBG;AACG,SAAU,YAAY,CAC1B,MAAuC,EACvC,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,WAAW,CAAC,KAAK,CAAC;QAC3B;AAEA,QAAA,MAAM,YAAY,GAAG,OAAO,EAAE,YAAY,IAAI,KAAK;QACnD,MAAM,OAAO,GAAG,MAAM,CAAU,KAAK,EAAE,OAAO,CAAC;AAE/C,QAAA,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,IAAG;YAC5B,OAAO,CAAC,GAAG,CAAC,YAAY,GAAI,CAAC,CAAC,MAAsB,CAAC,OAAO,CAAC,gBAAgB,CAAC,GAAG,IAAI,CAAC;AACxF,QAAA,CAAC,CAAC;AAEF,QAAA,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,MAAK;AAC5B,YAAA,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;AACpB,QAAA,CAAC,CAAC;AAEF,QAAA,YAAY,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AAE9C,QAAA,OAAO,OAAO,CAAC,UAAU,EAAE;AAC7B,IAAA,CAAC,CAAC;AACJ;;ACpEA;;AAEG;;;;"}
|
|
@@ -0,0 +1,48 @@
|
|
|
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 tracking of the hover state on an element.
|
|
8
|
+
* Detects when the mouse enters or leaves an element.
|
|
9
|
+
*
|
|
10
|
+
* @param target - The element to track hover state on
|
|
11
|
+
* @param options - Optional configuration including signal options and injector
|
|
12
|
+
* @returns A signal that is `true` when the element is being hovered
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* @Component({
|
|
17
|
+
* template: `
|
|
18
|
+
* <div #box [class.hovered]="isHovered()">
|
|
19
|
+
* Hover over me!
|
|
20
|
+
* </div>
|
|
21
|
+
* `
|
|
22
|
+
* })
|
|
23
|
+
* class HoverComponent {
|
|
24
|
+
* readonly box = viewChild<ElementRef>('box');
|
|
25
|
+
* readonly isHovered = elementHover(this.box);
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
function elementHover(target, options) {
|
|
30
|
+
const { runInContext } = setupContext(options?.injector, elementHover);
|
|
31
|
+
return runInContext(({ isServer, isMobile }) => {
|
|
32
|
+
if (isServer || isMobile) {
|
|
33
|
+
return constSignal(false);
|
|
34
|
+
}
|
|
35
|
+
const hovered = signal(false, options);
|
|
36
|
+
listener(target, 'mouseenter', () => hovered.set(true));
|
|
37
|
+
listener(target, 'mouseleave', () => hovered.set(false));
|
|
38
|
+
onDisconnect(target, () => hovered.set(false));
|
|
39
|
+
return hovered.asReadonly();
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Generated bundle index. Do not edit.
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
export { elementHover };
|
|
48
|
+
//# sourceMappingURL=signality-core-elements-element-hover.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signality-core-elements-element-hover.mjs","sources":["../../../projects/core/elements/element-hover/index.ts","../../../projects/core/elements/element-hover/signality-core-elements-element-hover.ts"],"sourcesContent":["import { type CreateSignalOptions, 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\nexport type ElementHoverOptions = CreateSignalOptions<boolean> & WithInjector;\n\n/**\n * Reactive tracking of the hover state on an element.\n * Detects when the mouse enters or leaves an element.\n *\n * @param target - The element to track hover state on\n * @param options - Optional configuration including signal options and injector\n * @returns A signal that is `true` when the element is being hovered\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * <div #box [class.hovered]=\"isHovered()\">\n * Hover over me!\n * </div>\n * `\n * })\n * class HoverComponent {\n * readonly box = viewChild<ElementRef>('box');\n * readonly isHovered = elementHover(this.box);\n * }\n * ```\n */\nexport function elementHover(\n target: MaybeElementSignal<HTMLElement>,\n options?: ElementHoverOptions\n): Signal<boolean> {\n const { runInContext } = setupContext(options?.injector, elementHover);\n\n return runInContext(({ isServer, isMobile }) => {\n if (isServer || isMobile) {\n return constSignal(false);\n }\n\n const hovered = signal<boolean>(false, options);\n\n listener(target, 'mouseenter', () => hovered.set(true));\n listener(target, 'mouseleave', () => hovered.set(false));\n\n onDisconnect(target, () => hovered.set(false));\n\n return hovered.asReadonly();\n });\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;AAQA;;;;;;;;;;;;;;;;;;;;;;AAsBG;AACG,SAAU,YAAY,CAC1B,MAAuC,EACvC,OAA6B,EAAA;AAE7B,IAAA,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,YAAY,CAAC;IAEtE,OAAO,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAI;AAC7C,QAAA,IAAI,QAAQ,IAAI,QAAQ,EAAE;AACxB,YAAA,OAAO,WAAW,CAAC,KAAK,CAAC;QAC3B;QAEA,MAAM,OAAO,GAAG,MAAM,CAAU,KAAK,EAAE,OAAO,CAAC;AAE/C,QAAA,QAAQ,CAAC,MAAM,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACvD,QAAA,QAAQ,CAAC,MAAM,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AAExD,QAAA,YAAY,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AAE9C,QAAA,OAAO,OAAO,CAAC,UAAU,EAAE;AAC7B,IAAA,CAAC,CAAC;AACJ;;ACnDA;;AAEG;;;;"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { signal } from '@angular/core';
|
|
2
|
+
import { setupContext, constSignal, toValue } from '@signality/core/internal';
|
|
3
|
+
import { resizeObserver } from '@signality/core/observers/resize-observer';
|
|
4
|
+
import { onDisconnect } from '@signality/core/elements/on-disconnect';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Signal-based wrapper around the [ResizeObserver API](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver).
|
|
8
|
+
*
|
|
9
|
+
* @param target - The element to observe
|
|
10
|
+
* @param options - Optional configuration including signal options (equal, debugName), box model, and injector
|
|
11
|
+
* @returns A signal containing the current element dimensions
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* @Component({
|
|
16
|
+
* template: `
|
|
17
|
+
* <div #box>
|
|
18
|
+
* Size: {{ size().width }} × {{ size().height }}px
|
|
19
|
+
* </div>
|
|
20
|
+
* `
|
|
21
|
+
* })
|
|
22
|
+
* class SizeComponent {
|
|
23
|
+
* readonly box = viewChild<ElementRef>('box');
|
|
24
|
+
* readonly size = elementSize(this.box);
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
function elementSize(target, options) {
|
|
29
|
+
const { runInContext } = setupContext(options?.injector, elementSize);
|
|
30
|
+
return runInContext(({ isServer }) => {
|
|
31
|
+
if (isServer) {
|
|
32
|
+
return constSignal(DEFAULT_SIZE);
|
|
33
|
+
}
|
|
34
|
+
const size = signal(DEFAULT_SIZE, options);
|
|
35
|
+
const updateSize = ([entry]) => {
|
|
36
|
+
const contentBoxSize = entry.contentBoxSize?.[0];
|
|
37
|
+
const borderBoxSize = entry.borderBoxSize?.[0];
|
|
38
|
+
const contentWidth = contentBoxSize?.inlineSize ?? entry.contentRect.width;
|
|
39
|
+
const contentHeight = contentBoxSize?.blockSize ?? entry.contentRect.height;
|
|
40
|
+
const borderBoxWidth = borderBoxSize?.inlineSize ?? entry.contentRect.width;
|
|
41
|
+
const borderBoxHeight = borderBoxSize?.blockSize ?? entry.contentRect.height;
|
|
42
|
+
const box = toValue(options?.box) ?? 'border-box';
|
|
43
|
+
const width = box === 'content-box' ? contentWidth : borderBoxWidth;
|
|
44
|
+
const height = box === 'content-box' ? contentHeight : borderBoxHeight;
|
|
45
|
+
size.set({
|
|
46
|
+
width,
|
|
47
|
+
height,
|
|
48
|
+
contentWidth,
|
|
49
|
+
contentHeight,
|
|
50
|
+
borderBoxWidth,
|
|
51
|
+
borderBoxHeight,
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
resizeObserver(target, updateSize, options);
|
|
55
|
+
onDisconnect(target, () => size.set(DEFAULT_SIZE));
|
|
56
|
+
return size;
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
const DEFAULT_SIZE = {
|
|
60
|
+
width: 0,
|
|
61
|
+
height: 0,
|
|
62
|
+
contentWidth: 0,
|
|
63
|
+
contentHeight: 0,
|
|
64
|
+
borderBoxWidth: 0,
|
|
65
|
+
borderBoxHeight: 0,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Generated bundle index. Do not edit.
|
|
70
|
+
*/
|
|
71
|
+
|
|
72
|
+
export { elementSize };
|
|
73
|
+
//# sourceMappingURL=signality-core-elements-element-size.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signality-core-elements-element-size.mjs","sources":["../../../projects/core/elements/element-size/index.ts","../../../projects/core/elements/element-size/signality-core-elements-element-size.ts"],"sourcesContent":["import { type CreateSignalOptions, signal, type Signal } from '@angular/core';\nimport { constSignal, setupContext, toValue } from '@signality/core/internal';\nimport type { MaybeElementSignal, MaybeSignal, WithInjector } from '@signality/core/types';\nimport { resizeObserver } from '@signality/core/observers/resize-observer';\nimport { onDisconnect } from '@signality/core/elements/on-disconnect';\n\nexport interface ElementSizeValue {\n readonly width: number;\n readonly height: number;\n readonly contentWidth: number;\n readonly contentHeight: number;\n readonly borderBoxWidth: number;\n readonly borderBoxHeight: number;\n}\n\nexport interface ElementSizeOptions extends CreateSignalOptions<ElementSizeValue>, WithInjector {\n /**\n * Which box model to observe. Can be a reactive signal.\n * @default 'border-box'\n */\n readonly box?: MaybeSignal<ResizeObserverBoxOptions>;\n}\n\n/**\n * Signal-based wrapper around the [ResizeObserver API](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver).\n *\n * @param target - The element to observe\n * @param options - Optional configuration including signal options (equal, debugName), box model, and injector\n * @returns A signal containing the current element dimensions\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * <div #box>\n * Size: {{ size().width }} × {{ size().height }}px\n * </div>\n * `\n * })\n * class SizeComponent {\n * readonly box = viewChild<ElementRef>('box');\n * readonly size = elementSize(this.box);\n * }\n * ```\n */\nexport function elementSize(\n target: MaybeElementSignal<HTMLElement>,\n options?: ElementSizeOptions\n): Signal<ElementSizeValue> {\n const { runInContext } = setupContext(options?.injector, elementSize);\n\n return runInContext(({ isServer }) => {\n if (isServer) {\n return constSignal(DEFAULT_SIZE);\n }\n\n const size = signal<ElementSizeValue>(DEFAULT_SIZE, options);\n\n const updateSize = ([entry]: readonly ResizeObserverEntry[]) => {\n const contentBoxSize = entry.contentBoxSize?.[0];\n const borderBoxSize = entry.borderBoxSize?.[0];\n\n const contentWidth = contentBoxSize?.inlineSize ?? entry.contentRect.width;\n const contentHeight = contentBoxSize?.blockSize ?? entry.contentRect.height;\n const borderBoxWidth = borderBoxSize?.inlineSize ?? entry.contentRect.width;\n const borderBoxHeight = borderBoxSize?.blockSize ?? entry.contentRect.height;\n\n const box = toValue(options?.box) ?? 'border-box';\n const width = box === 'content-box' ? contentWidth : borderBoxWidth;\n const height = box === 'content-box' ? contentHeight : borderBoxHeight;\n\n size.set({\n width,\n height,\n contentWidth,\n contentHeight,\n borderBoxWidth,\n borderBoxHeight,\n });\n };\n\n resizeObserver(target, updateSize, options);\n\n onDisconnect(target, () => size.set(DEFAULT_SIZE));\n\n return size;\n });\n}\n\nconst DEFAULT_SIZE: ElementSizeValue = {\n width: 0,\n height: 0,\n contentWidth: 0,\n contentHeight: 0,\n borderBoxWidth: 0,\n borderBoxHeight: 0,\n};\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;AAuBA;;;;;;;;;;;;;;;;;;;;;AAqBG;AACG,SAAU,WAAW,CACzB,MAAuC,EACvC,OAA4B,EAAA;AAE5B,IAAA,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,WAAW,CAAC;AAErE,IAAA,OAAO,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAI;QACnC,IAAI,QAAQ,EAAE;AACZ,YAAA,OAAO,WAAW,CAAC,YAAY,CAAC;QAClC;QAEA,MAAM,IAAI,GAAG,MAAM,CAAmB,YAAY,EAAE,OAAO,CAAC;AAE5D,QAAA,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAiC,KAAI;YAC7D,MAAM,cAAc,GAAG,KAAK,CAAC,cAAc,GAAG,CAAC,CAAC;YAChD,MAAM,aAAa,GAAG,KAAK,CAAC,aAAa,GAAG,CAAC,CAAC;YAE9C,MAAM,YAAY,GAAG,cAAc,EAAE,UAAU,IAAI,KAAK,CAAC,WAAW,CAAC,KAAK;YAC1E,MAAM,aAAa,GAAG,cAAc,EAAE,SAAS,IAAI,KAAK,CAAC,WAAW,CAAC,MAAM;YAC3E,MAAM,cAAc,GAAG,aAAa,EAAE,UAAU,IAAI,KAAK,CAAC,WAAW,CAAC,KAAK;YAC3E,MAAM,eAAe,GAAG,aAAa,EAAE,SAAS,IAAI,KAAK,CAAC,WAAW,CAAC,MAAM;YAE5E,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,IAAI,YAAY;AACjD,YAAA,MAAM,KAAK,GAAG,GAAG,KAAK,aAAa,GAAG,YAAY,GAAG,cAAc;AACnE,YAAA,MAAM,MAAM,GAAG,GAAG,KAAK,aAAa,GAAG,aAAa,GAAG,eAAe;YAEtE,IAAI,CAAC,GAAG,CAAC;gBACP,KAAK;gBACL,MAAM;gBACN,YAAY;gBACZ,aAAa;gBACb,cAAc;gBACd,eAAe;AAChB,aAAA,CAAC;AACJ,QAAA,CAAC;AAED,QAAA,cAAc,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,CAAC;AAE3C,QAAA,YAAY,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;AAElD,QAAA,OAAO,IAAI;AACb,IAAA,CAAC,CAAC;AACJ;AAEA,MAAM,YAAY,GAAqB;AACrC,IAAA,KAAK,EAAE,CAAC;AACR,IAAA,MAAM,EAAE,CAAC;AACT,IAAA,YAAY,EAAE,CAAC;AACf,IAAA,aAAa,EAAE,CAAC;AAChB,IAAA,cAAc,EAAE,CAAC;AACjB,IAAA,eAAe,EAAE,CAAC;CACnB;;AChGD;;AAEG;;;;"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { signal } from '@angular/core';
|
|
2
|
+
import { setupContext, constSignal } from '@signality/core/internal';
|
|
3
|
+
import { intersectionObserver } from '@signality/core/observers/intersection-observer';
|
|
4
|
+
import { onDisconnect } from '@signality/core/elements/on-disconnect';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Signal-based wrapper around the [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API).
|
|
8
|
+
*
|
|
9
|
+
* @param target - The element to observe
|
|
10
|
+
* @param options - Optional configuration including signal options (equal, debugName), observer options (threshold, root, rootMargin, initialValue), and injector
|
|
11
|
+
* @returns A signal containing the current visibility state
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* @Component({
|
|
16
|
+
* template: `
|
|
17
|
+
* <div #section [class.visible]="visibility().isVisible">
|
|
18
|
+
* Visibility: {{ visibility().ratio * 100 }}%
|
|
19
|
+
* </div>
|
|
20
|
+
* `
|
|
21
|
+
* })
|
|
22
|
+
* class VisibilityComponent {
|
|
23
|
+
* readonly section = viewChild<ElementRef>('section');
|
|
24
|
+
* readonly visibility = elementVisibility(this.section);
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
function elementVisibility(target, options) {
|
|
29
|
+
const { runInContext } = setupContext(options?.injector, elementVisibility);
|
|
30
|
+
const initialValue = options?.initialValue ?? DEFAULT_VISIBILITY;
|
|
31
|
+
return runInContext(({ isServer }) => {
|
|
32
|
+
if (isServer) {
|
|
33
|
+
return constSignal(initialValue);
|
|
34
|
+
}
|
|
35
|
+
const visibility = signal(initialValue, options);
|
|
36
|
+
const threshold = options?.threshold ?? 0;
|
|
37
|
+
const root = options?.root ?? undefined;
|
|
38
|
+
const rootMargin = options?.rootMargin ?? '0px';
|
|
39
|
+
const update = (entries) => {
|
|
40
|
+
if (entries.length === 0) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
// Find the entry with the latest time to ensure we use the most up-to-date state
|
|
44
|
+
// IntersectionObserver may batch multiple changes and call the callback once
|
|
45
|
+
// with multiple entries, and the order in the array doesn't guarantee
|
|
46
|
+
// that the last entry is the most recent one
|
|
47
|
+
let latestEntry = entries[0];
|
|
48
|
+
let latestTime = entries[0].time;
|
|
49
|
+
for (let i = 1; i < entries.length; i++) {
|
|
50
|
+
const entry = entries[i];
|
|
51
|
+
if (entry.time >= latestTime) {
|
|
52
|
+
latestTime = entry.time;
|
|
53
|
+
latestEntry = entry;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
visibility.set({
|
|
57
|
+
isVisible: latestEntry.isIntersecting,
|
|
58
|
+
ratio: latestEntry.intersectionRatio,
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
intersectionObserver(target, update, { threshold, root, rootMargin });
|
|
62
|
+
onDisconnect(target, () => visibility.set(initialValue));
|
|
63
|
+
return visibility;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
const DEFAULT_VISIBILITY = {
|
|
67
|
+
isVisible: true,
|
|
68
|
+
ratio: 1,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Generated bundle index. Do not edit.
|
|
73
|
+
*/
|
|
74
|
+
|
|
75
|
+
export { elementVisibility };
|
|
76
|
+
//# sourceMappingURL=signality-core-elements-element-visibility.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signality-core-elements-element-visibility.mjs","sources":["../../../projects/core/elements/element-visibility/index.ts","../../../projects/core/elements/element-visibility/signality-core-elements-element-visibility.ts"],"sourcesContent":["import { type CreateSignalOptions, type Signal, signal } from '@angular/core';\nimport { constSignal, setupContext } from '@signality/core/internal';\nimport type { MaybeElementSignal, MaybeSignal, WithInjector } from '@signality/core/types';\nimport { intersectionObserver } from '@signality/core/observers/intersection-observer';\nimport { onDisconnect } from '@signality/core/elements/on-disconnect';\n\nexport interface ElementVisibilityOptions\n extends CreateSignalOptions<ElementVisibilityValue>,\n WithInjector {\n /**\n * Visibility threshold(s). A number between 0 and 1, or an array of thresholds.\n * @default 0\n */\n readonly threshold?: MaybeSignal<number | number[]>;\n\n /**\n * Scrollable ancestor element (null = viewport).\n * @default undefined\n */\n readonly root?: MaybeElementSignal<Element> | Document;\n\n /**\n * Margin around the root element.\n * @default '0px'\n */\n readonly rootMargin?: MaybeSignal<string>;\n\n /**\n * Initial value for SSR.\n * @default { isVisible: true, ratio: 1 }\n */\n readonly initialValue?: ElementVisibilityValue;\n}\n\nexport interface ElementVisibilityValue {\n /** Whether the element is visible in the viewport */\n readonly isVisible: boolean;\n\n /** Intersection ratio (0.0 to 1.0) */\n readonly ratio: number;\n}\n\n/**\n * Signal-based wrapper around the [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API).\n *\n * @param target - The element to observe\n * @param options - Optional configuration including signal options (equal, debugName), observer options (threshold, root, rootMargin, initialValue), and injector\n * @returns A signal containing the current visibility state\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * <div #section [class.visible]=\"visibility().isVisible\">\n * Visibility: {{ visibility().ratio * 100 }}%\n * </div>\n * `\n * })\n * class VisibilityComponent {\n * readonly section = viewChild<ElementRef>('section');\n * readonly visibility = elementVisibility(this.section);\n * }\n * ```\n */\nexport function elementVisibility(\n target: MaybeElementSignal<HTMLElement>,\n options?: ElementVisibilityOptions\n): Signal<ElementVisibilityValue> {\n const { runInContext } = setupContext(options?.injector, elementVisibility);\n const initialValue = options?.initialValue ?? DEFAULT_VISIBILITY;\n\n return runInContext(({ isServer }) => {\n if (isServer) {\n return constSignal(initialValue);\n }\n\n const visibility = signal(initialValue, options);\n\n const threshold = options?.threshold ?? 0;\n const root = options?.root ?? undefined;\n const rootMargin = options?.rootMargin ?? '0px';\n\n const update = (entries: readonly IntersectionObserverEntry[]) => {\n if (entries.length === 0) {\n return;\n }\n\n // Find the entry with the latest time to ensure we use the most up-to-date state\n // IntersectionObserver may batch multiple changes and call the callback once\n // with multiple entries, and the order in the array doesn't guarantee\n // that the last entry is the most recent one\n let latestEntry = entries[0];\n let latestTime = entries[0].time;\n\n for (let i = 1; i < entries.length; i++) {\n const entry = entries[i];\n if (entry.time >= latestTime) {\n latestTime = entry.time;\n latestEntry = entry;\n }\n }\n\n visibility.set({\n isVisible: latestEntry.isIntersecting,\n ratio: latestEntry.intersectionRatio,\n });\n };\n\n intersectionObserver(target, update, { threshold, root, rootMargin });\n\n onDisconnect(target, () => visibility.set(initialValue));\n\n return visibility;\n });\n}\n\nconst DEFAULT_VISIBILITY: ElementVisibilityValue = {\n isVisible: true,\n ratio: 1,\n};\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;AA0CA;;;;;;;;;;;;;;;;;;;;;AAqBG;AACG,SAAU,iBAAiB,CAC/B,MAAuC,EACvC,OAAkC,EAAA;AAElC,IAAA,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,iBAAiB,CAAC;AAC3E,IAAA,MAAM,YAAY,GAAG,OAAO,EAAE,YAAY,IAAI,kBAAkB;AAEhE,IAAA,OAAO,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAI;QACnC,IAAI,QAAQ,EAAE;AACZ,YAAA,OAAO,WAAW,CAAC,YAAY,CAAC;QAClC;QAEA,MAAM,UAAU,GAAG,MAAM,CAAC,YAAY,EAAE,OAAO,CAAC;AAEhD,QAAA,MAAM,SAAS,GAAG,OAAO,EAAE,SAAS,IAAI,CAAC;AACzC,QAAA,MAAM,IAAI,GAAG,OAAO,EAAE,IAAI,IAAI,SAAS;AACvC,QAAA,MAAM,UAAU,GAAG,OAAO,EAAE,UAAU,IAAI,KAAK;AAE/C,QAAA,MAAM,MAAM,GAAG,CAAC,OAA6C,KAAI;AAC/D,YAAA,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE;gBACxB;YACF;;;;;AAMA,YAAA,IAAI,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC;YAC5B,IAAI,UAAU,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI;AAEhC,YAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;AACvC,gBAAA,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC;AACxB,gBAAA,IAAI,KAAK,CAAC,IAAI,IAAI,UAAU,EAAE;AAC5B,oBAAA,UAAU,GAAG,KAAK,CAAC,IAAI;oBACvB,WAAW,GAAG,KAAK;gBACrB;YACF;YAEA,UAAU,CAAC,GAAG,CAAC;gBACb,SAAS,EAAE,WAAW,CAAC,cAAc;gBACrC,KAAK,EAAE,WAAW,CAAC,iBAAiB;AACrC,aAAA,CAAC;AACJ,QAAA,CAAC;AAED,QAAA,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;AAErE,QAAA,YAAY,CAAC,MAAM,EAAE,MAAM,UAAU,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;AAExD,QAAA,OAAO,UAAU;AACnB,IAAA,CAAC,CAAC;AACJ;AAEA,MAAM,kBAAkB,GAA2B;AACjD,IAAA,SAAS,EAAE,IAAI;AACf,IAAA,KAAK,EAAE,CAAC;CACT;;ACvHD;;AAEG;;;;"}
|