@signality/cdk-interop 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
- import { inject, signal, afterRenderEffect } from '@angular/core';
1
+ import { inject, signal, computed, afterRenderEffect } from '@angular/core';
2
2
  import { FocusMonitor } from '@angular/cdk/a11y';
3
- import { onDisconnect } from '@signality/core';
4
- import { setupContext, NOOP_FN, constSignal, toElement } from '@signality/core/internal';
3
+ import { toElement } from '@signality/core';
4
+ import { setupContext, NOOP_FN, constSignal, assertElement } from '@signality/core/internal';
5
5
 
6
6
  /**
7
7
  * Signal-based wrapper around the [Angular CDK FocusMonitor](https://material.angular.io/cdk/a11y/overview#focusmonitor).
@@ -31,7 +31,7 @@ import { setupContext, NOOP_FN, constSignal, toElement } from '@signality/core/i
31
31
  */
32
32
  function focusMonitor(target, options) {
33
33
  const { runInContext } = setupContext(options?.injector, focusMonitor);
34
- return runInContext(({ isServer, onCleanup }) => {
34
+ return runInContext(({ isServer }) => {
35
35
  if (isServer) {
36
36
  return {
37
37
  origin: constSignal(null),
@@ -41,44 +41,35 @@ function focusMonitor(target, options) {
41
41
  }
42
42
  const cdkMonitor = inject(FocusMonitor);
43
43
  const origin = signal(null, ...(ngDevMode ? [{ debugName: "origin" }] : []));
44
- const isFocused = signal(false, ...(ngDevMode ? [{ debugName: "isFocused" }] : []));
45
- let subscription;
44
+ const isFocused = computed(() => origin() !== null, ...(ngDevMode ? [{ debugName: "isFocused" }] : []));
45
+ let subscription = null;
46
46
  const startMonitoring = (el) => {
47
47
  subscription?.unsubscribe();
48
- subscription = cdkMonitor.monitor(el, options?.checkChildren).subscribe(focusOrigin => {
49
- origin.set(focusOrigin);
50
- isFocused.set(focusOrigin !== null);
51
- });
48
+ subscription = cdkMonitor.monitor(el, options?.checkChildren).subscribe(origin.set);
52
49
  };
53
50
  const stopMonitoring = (el) => {
54
51
  origin.set(null);
55
- isFocused.set(false);
56
52
  subscription?.unsubscribe();
53
+ subscription = null;
57
54
  cdkMonitor.stopMonitoring(el);
58
55
  };
59
56
  const focusVia = (origin, options) => {
60
57
  const el = toElement.untracked(target);
61
- if (!el) {
62
- if (ngDevMode) {
63
- console.warn('[focusMonitor] Cannot focus: element is not available');
64
- }
65
- return;
66
- }
58
+ ngDevMode && assertElement(el, 'focusVia');
67
59
  cdkMonitor.focusVia(el, origin, options);
68
60
  };
69
- const setupMonitoring = (onCleanup) => {
70
- const el = toElement(target);
71
- if (el) {
72
- startMonitoring(el);
73
- onCleanup(() => stopMonitoring(el));
74
- }
75
- };
76
- onCleanup(() => subscription?.unsubscribe());
77
- afterRenderEffect({ read: setupMonitoring });
78
- onDisconnect(target, stopMonitoring);
61
+ afterRenderEffect({
62
+ read: onCleanup => {
63
+ const el = toElement(target);
64
+ if (el) {
65
+ startMonitoring(el);
66
+ onCleanup(() => stopMonitoring(el));
67
+ }
68
+ },
69
+ });
79
70
  return {
80
71
  origin: origin.asReadonly(),
81
- isFocused: isFocused.asReadonly(),
72
+ isFocused,
82
73
  focusVia,
83
74
  };
84
75
  });
@@ -1 +1 @@
1
- {"version":3,"file":"signality-cdk-interop-focus-monitor.mjs","sources":["../../../projects/cdk-interop/focus-monitor/index.ts","../../../projects/cdk-interop/focus-monitor/signality-cdk-interop-focus-monitor.ts"],"sourcesContent":["import {\n afterRenderEffect,\n type EffectCleanupRegisterFn,\n inject,\n type Signal,\n signal,\n} from '@angular/core';\nimport { FocusMonitor, type FocusOrigin } from '@angular/cdk/a11y';\nimport type { Subscription } from 'rxjs';\nimport { MaybeElementSignal, onDisconnect, WithInjector } from '@signality/core';\nimport { constSignal, NOOP_FN, setupContext, toElement } from '@signality/core/internal';\n\nexport interface FocusMonitorOptions extends WithInjector {\n /**\n * Whether to also monitor focus changes within child elements of the target.\n *\n * @default false\n * @see [FocusMonitor on Angular CDK](https://material.angular.io/cdk/a11y/overview#focusmonitor)\n */\n readonly checkChildren?: boolean;\n}\n\nexport interface FocusMonitorRef {\n /**\n * Whether the target element (or any of its children when `checkChildren` is `true`) is focused.\n */\n readonly isFocused: Signal<boolean>;\n\n /**\n * How the element received focus. One of:\n * - `'mouse'` — focused via mouse click\n * - `'keyboard'` — focused via keyboard navigation\n * - `'touch'` — focused via touch interaction\n * - `'program'` — focused programmatically (e.g. via `focusVia`)\n * - `null` — element is not focused\n *\n * @see [FocusOrigin on Angular CDK](https://material.angular.io/cdk/a11y/api#FocusOrigin)\n */\n readonly origin: Signal<FocusOrigin>;\n\n /**\n * Programmatically focus the target element with a specific origin.\n * The `origin` will be reflected in the `origin` signal after focusing.\n *\n * @see [FocusMonitor.focusVia() on Angular CDK](https://material.angular.io/cdk/a11y/api#FocusMonitor)\n */\n readonly focusVia: (origin: FocusOrigin, options?: FocusOptions) => void;\n}\n\n/**\n * Signal-based wrapper around the [Angular CDK FocusMonitor](https://material.angular.io/cdk/a11y/overview#focusmonitor).\n *\n * @param target - Target element to monitor\n * @param options - Optional configuration\n * @returns A FocusMonitorRef with focus state signals and control methods\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * <button #btn [class.focused]=\"focus.isFocused()\">\n * Click me\n * @if (focus.origin(); as origin) {\n * <span>({{ origin }})</span>\n * }\n * </button>\n * <button (click)=\"focus.focusVia('program')\">Focus programmatically</button>\n * `\n * })\n * class FocusComponent {\n * readonly btn = viewChild<ElementRef>('btn');\n * readonly focus = focusMonitor(this.btn);\n * }\n * ```\n */\nexport function focusMonitor(\n target: MaybeElementSignal<HTMLElement>,\n options?: FocusMonitorOptions\n): FocusMonitorRef {\n const { runInContext } = setupContext(options?.injector, focusMonitor);\n\n return runInContext(({ isServer, onCleanup }) => {\n if (isServer) {\n return {\n origin: constSignal(null),\n isFocused: constSignal(false),\n focusVia: NOOP_FN,\n };\n }\n\n const cdkMonitor = inject(FocusMonitor);\n\n const origin = signal<FocusOrigin>(null);\n const isFocused = signal(false);\n\n let subscription: Subscription | undefined;\n\n const startMonitoring = (el: HTMLElement) => {\n subscription?.unsubscribe();\n\n subscription = cdkMonitor.monitor(el, options?.checkChildren).subscribe(focusOrigin => {\n origin.set(focusOrigin);\n isFocused.set(focusOrigin !== null);\n });\n };\n\n const stopMonitoring = (el: HTMLElement) => {\n origin.set(null);\n isFocused.set(false);\n subscription?.unsubscribe();\n cdkMonitor.stopMonitoring(el);\n };\n\n const focusVia = (origin: FocusOrigin, options?: FocusOptions) => {\n const el = toElement.untracked(target);\n\n if (!el) {\n if (ngDevMode) {\n console.warn('[focusMonitor] Cannot focus: element is not available');\n }\n return;\n }\n\n cdkMonitor.focusVia(el, origin, options);\n };\n\n const setupMonitoring = (onCleanup: EffectCleanupRegisterFn) => {\n const el = toElement(target);\n\n if (el) {\n startMonitoring(el);\n onCleanup(() => stopMonitoring(el));\n }\n };\n\n onCleanup(() => subscription?.unsubscribe());\n\n afterRenderEffect({ read: setupMonitoring });\n\n onDisconnect(target, stopMonitoring);\n\n return {\n origin: origin.asReadonly(),\n isFocused: isFocused.asReadonly(),\n focusVia,\n };\n });\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;AAiDA;;;;;;;;;;;;;;;;;;;;;;;;;AAyBG;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,SAAS,EAAE,KAAI;QAC9C,IAAI,QAAQ,EAAE;YACZ,OAAO;AACL,gBAAA,MAAM,EAAE,WAAW,CAAC,IAAI,CAAC;AACzB,gBAAA,SAAS,EAAE,WAAW,CAAC,KAAK,CAAC;AAC7B,gBAAA,QAAQ,EAAE,OAAO;aAClB;QACH;AAEA,QAAA,MAAM,UAAU,GAAG,MAAM,CAAC,YAAY,CAAC;AAEvC,QAAA,MAAM,MAAM,GAAG,MAAM,CAAc,IAAI,kDAAC;AACxC,QAAA,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,qDAAC;AAE/B,QAAA,IAAI,YAAsC;AAE1C,QAAA,MAAM,eAAe,GAAG,CAAC,EAAe,KAAI;YAC1C,YAAY,EAAE,WAAW,EAAE;AAE3B,YAAA,YAAY,GAAG,UAAU,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC,SAAS,CAAC,WAAW,IAAG;AACpF,gBAAA,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC;AACvB,gBAAA,SAAS,CAAC,GAAG,CAAC,WAAW,KAAK,IAAI,CAAC;AACrC,YAAA,CAAC,CAAC;AACJ,QAAA,CAAC;AAED,QAAA,MAAM,cAAc,GAAG,CAAC,EAAe,KAAI;AACzC,YAAA,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;AAChB,YAAA,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC;YACpB,YAAY,EAAE,WAAW,EAAE;AAC3B,YAAA,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;AAC/B,QAAA,CAAC;AAED,QAAA,MAAM,QAAQ,GAAG,CAAC,MAAmB,EAAE,OAAsB,KAAI;YAC/D,MAAM,EAAE,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC;YAEtC,IAAI,CAAC,EAAE,EAAE;gBACP,IAAI,SAAS,EAAE;AACb,oBAAA,OAAO,CAAC,IAAI,CAAC,uDAAuD,CAAC;gBACvE;gBACA;YACF;YAEA,UAAU,CAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC;AAC1C,QAAA,CAAC;AAED,QAAA,MAAM,eAAe,GAAG,CAAC,SAAkC,KAAI;AAC7D,YAAA,MAAM,EAAE,GAAG,SAAS,CAAC,MAAM,CAAC;YAE5B,IAAI,EAAE,EAAE;gBACN,eAAe,CAAC,EAAE,CAAC;gBACnB,SAAS,CAAC,MAAM,cAAc,CAAC,EAAE,CAAC,CAAC;YACrC;AACF,QAAA,CAAC;QAED,SAAS,CAAC,MAAM,YAAY,EAAE,WAAW,EAAE,CAAC;AAE5C,QAAA,iBAAiB,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC;AAE5C,QAAA,YAAY,CAAC,MAAM,EAAE,cAAc,CAAC;QAEpC,OAAO;AACL,YAAA,MAAM,EAAE,MAAM,CAAC,UAAU,EAAE;AAC3B,YAAA,SAAS,EAAE,SAAS,CAAC,UAAU,EAAE;YACjC,QAAQ;SACT;AACH,IAAA,CAAC,CAAC;AACJ;;ACnJA;;AAEG;;;;"}
1
+ {"version":3,"file":"signality-cdk-interop-focus-monitor.mjs","sources":["../../../projects/cdk-interop/focus-monitor/index.ts","../../../projects/cdk-interop/focus-monitor/signality-cdk-interop-focus-monitor.ts"],"sourcesContent":["import { afterRenderEffect, computed, inject, type Signal, signal } from '@angular/core';\nimport { FocusMonitor, type FocusOrigin } from '@angular/cdk/a11y';\nimport type { Subscription } from 'rxjs';\nimport { MaybeElementSignal, toElement, WithInjector } from '@signality/core';\nimport { assertElement, constSignal, NOOP_FN, setupContext } from '@signality/core/internal';\n\nexport interface FocusMonitorOptions extends WithInjector {\n /**\n * Whether to also monitor focus changes within child elements of the target.\n *\n * @default false\n * @see [FocusMonitor on Angular CDK](https://material.angular.io/cdk/a11y/overview#focusmonitor)\n */\n readonly checkChildren?: boolean;\n}\n\nexport interface FocusMonitorRef {\n /**\n * Whether the target element (or any of its children when `checkChildren` is `true`) is focused.\n */\n readonly isFocused: Signal<boolean>;\n\n /**\n * How the element received focus. One of:\n * - `'mouse'` — focused via mouse click\n * - `'keyboard'` — focused via keyboard navigation\n * - `'touch'` — focused via touch interaction\n * - `'program'` — focused programmatically (e.g. via `focusVia`)\n * - `null` — element is not focused\n *\n * @see [FocusOrigin on Angular CDK](https://material.angular.io/cdk/a11y/api#FocusOrigin)\n */\n readonly origin: Signal<FocusOrigin>;\n\n /**\n * Programmatically focus the target element with a specific origin.\n * The `origin` will be reflected in the `origin` signal after focusing.\n *\n * @see [FocusMonitor.focusVia() on Angular CDK](https://material.angular.io/cdk/a11y/api#FocusMonitor)\n */\n readonly focusVia: (origin: FocusOrigin, options?: FocusOptions) => void;\n}\n\n/**\n * Signal-based wrapper around the [Angular CDK FocusMonitor](https://material.angular.io/cdk/a11y/overview#focusmonitor).\n *\n * @param target - Target element to monitor\n * @param options - Optional configuration\n * @returns A FocusMonitorRef with focus state signals and control methods\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * <button #btn [class.focused]=\"focus.isFocused()\">\n * Click me\n * @if (focus.origin(); as origin) {\n * <span>({{ origin }})</span>\n * }\n * </button>\n * <button (click)=\"focus.focusVia('program')\">Focus programmatically</button>\n * `\n * })\n * class FocusComponent {\n * readonly btn = viewChild<ElementRef>('btn');\n * readonly focus = focusMonitor(this.btn);\n * }\n * ```\n */\nexport function focusMonitor(\n target: MaybeElementSignal<HTMLElement>,\n options?: FocusMonitorOptions\n): FocusMonitorRef {\n const { runInContext } = setupContext(options?.injector, focusMonitor);\n\n return runInContext(({ isServer }) => {\n if (isServer) {\n return {\n origin: constSignal(null),\n isFocused: constSignal(false),\n focusVia: NOOP_FN,\n };\n }\n\n const cdkMonitor = inject(FocusMonitor);\n\n const origin = signal<FocusOrigin>(null);\n const isFocused = computed(() => origin() !== null);\n\n let subscription: Subscription | null = null;\n\n const startMonitoring = (el: HTMLElement) => {\n subscription?.unsubscribe();\n subscription = cdkMonitor.monitor(el, options?.checkChildren).subscribe(origin.set);\n };\n\n const stopMonitoring = (el: HTMLElement) => {\n origin.set(null);\n subscription?.unsubscribe();\n subscription = null;\n cdkMonitor.stopMonitoring(el);\n };\n\n const focusVia = (origin: FocusOrigin, options?: FocusOptions) => {\n const el = toElement.untracked(target)!;\n ngDevMode && assertElement(el, 'focusVia');\n cdkMonitor.focusVia(el, origin, options);\n };\n\n afterRenderEffect({\n read: onCleanup => {\n const el = toElement(target);\n\n if (el) {\n startMonitoring(el);\n onCleanup(() => stopMonitoring(el));\n }\n },\n });\n\n return {\n origin: origin.asReadonly(),\n isFocused,\n focusVia,\n };\n });\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;AA2CA;;;;;;;;;;;;;;;;;;;;;;;;;AAyBG;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;YACZ,OAAO;AACL,gBAAA,MAAM,EAAE,WAAW,CAAC,IAAI,CAAC;AACzB,gBAAA,SAAS,EAAE,WAAW,CAAC,KAAK,CAAC;AAC7B,gBAAA,QAAQ,EAAE,OAAO;aAClB;QACH;AAEA,QAAA,MAAM,UAAU,GAAG,MAAM,CAAC,YAAY,CAAC;AAEvC,QAAA,MAAM,MAAM,GAAG,MAAM,CAAc,IAAI,kDAAC;AACxC,QAAA,MAAM,SAAS,GAAG,QAAQ,CAAC,MAAM,MAAM,EAAE,KAAK,IAAI,EAAA,IAAA,SAAA,GAAA,CAAA,EAAA,SAAA,EAAA,WAAA,EAAA,CAAA,GAAA,EAAA,CAAA,CAAC;QAEnD,IAAI,YAAY,GAAwB,IAAI;AAE5C,QAAA,MAAM,eAAe,GAAG,CAAC,EAAe,KAAI;YAC1C,YAAY,EAAE,WAAW,EAAE;AAC3B,YAAA,YAAY,GAAG,UAAU,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC;AACrF,QAAA,CAAC;AAED,QAAA,MAAM,cAAc,GAAG,CAAC,EAAe,KAAI;AACzC,YAAA,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;YAChB,YAAY,EAAE,WAAW,EAAE;YAC3B,YAAY,GAAG,IAAI;AACnB,YAAA,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;AAC/B,QAAA,CAAC;AAED,QAAA,MAAM,QAAQ,GAAG,CAAC,MAAmB,EAAE,OAAsB,KAAI;YAC/D,MAAM,EAAE,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,CAAE;AACvC,YAAA,SAAS,IAAI,aAAa,CAAC,EAAE,EAAE,UAAU,CAAC;YAC1C,UAAU,CAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC;AAC1C,QAAA,CAAC;AAED,QAAA,iBAAiB,CAAC;YAChB,IAAI,EAAE,SAAS,IAAG;AAChB,gBAAA,MAAM,EAAE,GAAG,SAAS,CAAC,MAAM,CAAC;gBAE5B,IAAI,EAAE,EAAE;oBACN,eAAe,CAAC,EAAE,CAAC;oBACnB,SAAS,CAAC,MAAM,cAAc,CAAC,EAAE,CAAC,CAAC;gBACrC;YACF,CAAC;AACF,SAAA,CAAC;QAEF,OAAO;AACL,YAAA,MAAM,EAAE,MAAM,CAAC,UAAU,EAAE;YAC3B,SAAS;YACT,QAAQ;SACT;AACH,IAAA,CAAC,CAAC;AACJ;;AC9HA;;AAEG;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signality/cdk-interop",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "license": "MIT",
5
5
  "author": "Vyacheslav Borodin <https://github.com/vs-borodin>",
6
6
  "description": "Signal-based utilities for Angular CDK",