@ngxsmk/cdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -0
- package/fesm2022/ngxsmk-cdk-click-outside.mjs +45 -0
- package/fesm2022/ngxsmk-cdk-click-outside.mjs.map +1 -0
- package/fesm2022/ngxsmk-cdk-focus-trap.mjs +72 -0
- package/fesm2022/ngxsmk-cdk-focus-trap.mjs.map +1 -0
- package/fesm2022/ngxsmk-cdk-focusable.mjs +21 -0
- package/fesm2022/ngxsmk-cdk-focusable.mjs.map +1 -0
- package/fesm2022/ngxsmk-cdk-live-announcer.mjs +64 -0
- package/fesm2022/ngxsmk-cdk-live-announcer.mjs.map +1 -0
- package/fesm2022/ngxsmk-cdk-media-query.mjs +30 -0
- package/fesm2022/ngxsmk-cdk-media-query.mjs.map +1 -0
- package/fesm2022/ngxsmk-cdk-scroll-lock.mjs +52 -0
- package/fesm2022/ngxsmk-cdk-scroll-lock.mjs.map +1 -0
- package/fesm2022/ngxsmk-cdk-visually-hidden.mjs +41 -0
- package/fesm2022/ngxsmk-cdk-visually-hidden.mjs.map +1 -0
- package/fesm2022/ngxsmk-cdk.mjs +275 -0
- package/fesm2022/ngxsmk-cdk.mjs.map +1 -0
- package/package.json +56 -0
- package/types/ngxsmk-cdk-click-outside.d.ts +18 -0
- package/types/ngxsmk-cdk-focus-trap.d.ts +21 -0
- package/types/ngxsmk-cdk-focusable.d.ts +6 -0
- package/types/ngxsmk-cdk-live-announcer.d.ts +21 -0
- package/types/ngxsmk-cdk-media-query.d.ts +12 -0
- package/types/ngxsmk-cdk-scroll-lock.d.ts +19 -0
- package/types/ngxsmk-cdk-visually-hidden.d.ts +15 -0
- package/types/ngxsmk-cdk.d.ts +96 -0
package/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# @ngxsmk/cdk
|
|
2
|
+
|
|
3
|
+
Low-level, framework-agnostic Angular behaviors used by NGXSMK components:
|
|
4
|
+
|
|
5
|
+
- `ClickOutside` — detect clicks outside an element
|
|
6
|
+
- `FocusTrap` — trap focus within a container
|
|
7
|
+
- `ScrollLock` — lock/unlock page scroll
|
|
8
|
+
- `LiveAnnouncer` — announce messages to assistive tech
|
|
9
|
+
- `MediaQuery` — reactive media-query signals
|
|
10
|
+
- `VisuallyHidden` — visually hide content while keeping it accessible
|
|
11
|
+
|
|
12
|
+
Part of the [NGXSMK UI kit](https://github.com/NGXSMK/ngxsmk-ui-kit).
|
|
13
|
+
|
|
14
|
+
## Compatibility
|
|
15
|
+
|
|
16
|
+
- Angular **17.3+**
|
|
17
|
+
- `zone.js` optional (zoneless-friendly)
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @ngxsmk/cdk
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`@ngxsmk/cdk` depends only on `@angular/core` and `@angular/common`.
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { ClickOutside } from '@ngxsmk/cdk/click-outside';
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Prefer deep entry-point imports (e.g. `@ngxsmk/cdk/click-outside`) so the
|
|
34
|
+
bundler tree-shakes unused behaviors.
|
|
35
|
+
|
|
36
|
+
## Building & publishing
|
|
37
|
+
|
|
38
|
+
Built with the rest of the workspace via `npm run build:libs`. The published
|
|
39
|
+
artifact lives in `dist/ngxsmk/cdk`. To publish all packages:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm run publish
|
|
43
|
+
```
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { DOCUMENT } from '@angular/common';
|
|
2
|
+
import * as i0 from '@angular/core';
|
|
3
|
+
import { inject, ElementRef, output, DestroyRef, Directive } from '@angular/core';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Emits when a pointer press lands outside the host element.
|
|
7
|
+
*
|
|
8
|
+
* ```html
|
|
9
|
+
* <div class="menu" (ngxsmkClickOutside)="close()">…</div>
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
class NgxsmkClickOutside {
|
|
13
|
+
host = inject(ElementRef);
|
|
14
|
+
ngxsmkClickOutside = output();
|
|
15
|
+
constructor() {
|
|
16
|
+
const document = inject(DOCUMENT);
|
|
17
|
+
const destroyRef = inject(DestroyRef);
|
|
18
|
+
const onPointerDown = (event) => {
|
|
19
|
+
const target = event.target;
|
|
20
|
+
if (target && !this.host.nativeElement.contains(target)) {
|
|
21
|
+
this.ngxsmkClickOutside.emit(event);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
// Capture phase so stopped propagation inside other widgets can't
|
|
25
|
+
// swallow the outside press.
|
|
26
|
+
document.addEventListener('pointerdown', onPointerDown, true);
|
|
27
|
+
destroyRef.onDestroy(() => document.removeEventListener('pointerdown', onPointerDown, true));
|
|
28
|
+
}
|
|
29
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: NgxsmkClickOutside, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
30
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.6", type: NgxsmkClickOutside, isStandalone: true, selector: "[ngxsmkClickOutside]", outputs: { ngxsmkClickOutside: "ngxsmkClickOutside" }, ngImport: i0 });
|
|
31
|
+
}
|
|
32
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: NgxsmkClickOutside, decorators: [{
|
|
33
|
+
type: Directive,
|
|
34
|
+
args: [{
|
|
35
|
+
standalone: true,
|
|
36
|
+
selector: '[ngxsmkClickOutside]',
|
|
37
|
+
}]
|
|
38
|
+
}], ctorParameters: () => [], propDecorators: { ngxsmkClickOutside: [{ type: i0.Output, args: ["ngxsmkClickOutside"] }] } });
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Generated bundle index. Do not edit.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
export { NgxsmkClickOutside };
|
|
45
|
+
//# sourceMappingURL=ngxsmk-cdk-click-outside.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ngxsmk-cdk-click-outside.mjs","sources":["../../../../packages/cdk/click-outside/click-outside.ts","../../../../packages/cdk/click-outside/ngxsmk-cdk-click-outside.ts"],"sourcesContent":["import { DOCUMENT } from '@angular/common';\nimport {\n DestroyRef,\n Directive,\n ElementRef,\n inject,\n output,\n} from '@angular/core';\n\n/**\n * Emits when a pointer press lands outside the host element.\n *\n * ```html\n * <div class=\"menu\" (ngxsmkClickOutside)=\"close()\">…</div>\n * ```\n */\n@Directive({\n standalone: true,\n selector: '[ngxsmkClickOutside]',\n})\nexport class NgxsmkClickOutside {\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n\n readonly ngxsmkClickOutside = output<Event>();\n\n constructor() {\n const document = inject(DOCUMENT);\n const destroyRef = inject(DestroyRef);\n\n const onPointerDown = (event: Event) => {\n const target = event.target as Node | null;\n if (target && !this.host.nativeElement.contains(target)) {\n this.ngxsmkClickOutside.emit(event);\n }\n };\n\n // Capture phase so stopped propagation inside other widgets can't\n // swallow the outside press.\n document.addEventListener('pointerdown', onPointerDown, true);\n destroyRef.onDestroy(() =>\n document.removeEventListener('pointerdown', onPointerDown, true),\n );\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;AASA;;;;;;AAMG;MAKU,kBAAkB,CAAA;AACZ,IAAA,IAAI,GAAG,MAAM,CAA0B,UAAU,CAAC;IAE1D,kBAAkB,GAAG,MAAM,EAAS;AAE7C,IAAA,WAAA,GAAA;AACE,QAAA,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;AACjC,QAAA,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;AAErC,QAAA,MAAM,aAAa,GAAG,CAAC,KAAY,KAAI;AACrC,YAAA,MAAM,MAAM,GAAG,KAAK,CAAC,MAAqB;AAC1C,YAAA,IAAI,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE;AACvD,gBAAA,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC;YACrC;AACF,QAAA,CAAC;;;QAID,QAAQ,CAAC,gBAAgB,CAAC,aAAa,EAAE,aAAa,EAAE,IAAI,CAAC;AAC7D,QAAA,UAAU,CAAC,SAAS,CAAC,MACnB,QAAQ,CAAC,mBAAmB,CAAC,aAAa,EAAE,aAAa,EAAE,IAAI,CAAC,CACjE;IACH;uGAtBW,kBAAkB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;2FAAlB,kBAAkB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,sBAAA,EAAA,OAAA,EAAA,EAAA,kBAAA,EAAA,oBAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,CAAA;;2FAAlB,kBAAkB,EAAA,UAAA,EAAA,CAAA;kBAJ9B,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,UAAU,EAAE,IAAI;AAChB,oBAAA,QAAQ,EAAE,sBAAsB;AACjC,iBAAA;;;ACnBD;;AAEG;;;;"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { DOCUMENT } from '@angular/common';
|
|
2
|
+
import * as i0 from '@angular/core';
|
|
3
|
+
import { inject, ElementRef, input, booleanAttribute, afterNextRender, DestroyRef, Directive } from '@angular/core';
|
|
4
|
+
import { getFocusable } from '@ngxsmk/cdk/focusable';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Traps Tab/Shift+Tab focus cycling inside the host element while active.
|
|
8
|
+
* With `ngxsmkFocusTrapAutoCapture`, moves focus into the trap on init and
|
|
9
|
+
* restores the previously focused element on destroy.
|
|
10
|
+
*/
|
|
11
|
+
class NgxsmkFocusTrap {
|
|
12
|
+
host = inject(ElementRef);
|
|
13
|
+
document = inject(DOCUMENT);
|
|
14
|
+
/** Set to `false` to disable the trap without removing the directive. */
|
|
15
|
+
ngxsmkFocusTrap = input(true, { ...(ngDevMode ? { debugName: "ngxsmkFocusTrap" } : /* istanbul ignore next */ {}), transform: (v) => v === '' || v === true || v === 'true' });
|
|
16
|
+
/** Focus the first focusable child on init; restore focus on destroy. */
|
|
17
|
+
ngxsmkFocusTrapAutoCapture = input(false, { ...(ngDevMode ? { debugName: "ngxsmkFocusTrapAutoCapture" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
18
|
+
constructor() {
|
|
19
|
+
const previouslyFocused = this.document.activeElement;
|
|
20
|
+
afterNextRender(() => {
|
|
21
|
+
if (this.ngxsmkFocusTrapAutoCapture()) {
|
|
22
|
+
const focusable = getFocusable(this.host.nativeElement);
|
|
23
|
+
(focusable[0] ?? this.host.nativeElement).focus();
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
inject(DestroyRef).onDestroy(() => {
|
|
27
|
+
if (this.ngxsmkFocusTrapAutoCapture()) {
|
|
28
|
+
previouslyFocused?.focus?.();
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
onKeydown(event) {
|
|
33
|
+
if (event.key !== 'Tab' || !this.ngxsmkFocusTrap()) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const focusable = getFocusable(this.host.nativeElement);
|
|
37
|
+
if (focusable.length === 0) {
|
|
38
|
+
event.preventDefault();
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const first = focusable[0];
|
|
42
|
+
const last = focusable[focusable.length - 1];
|
|
43
|
+
const active = this.document.activeElement;
|
|
44
|
+
if (event.shiftKey && (active === first || active === this.host.nativeElement)) {
|
|
45
|
+
event.preventDefault();
|
|
46
|
+
last.focus();
|
|
47
|
+
}
|
|
48
|
+
else if (!event.shiftKey && active === last) {
|
|
49
|
+
event.preventDefault();
|
|
50
|
+
first.focus();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: NgxsmkFocusTrap, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
54
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.6", type: NgxsmkFocusTrap, isStandalone: true, selector: "[ngxsmkFocusTrap]", inputs: { ngxsmkFocusTrap: { classPropertyName: "ngxsmkFocusTrap", publicName: "ngxsmkFocusTrap", isSignal: true, isRequired: false, transformFunction: null }, ngxsmkFocusTrapAutoCapture: { classPropertyName: "ngxsmkFocusTrapAutoCapture", publicName: "ngxsmkFocusTrapAutoCapture", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "keydown": "onKeydown($event)" } }, ngImport: i0 });
|
|
55
|
+
}
|
|
56
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: NgxsmkFocusTrap, decorators: [{
|
|
57
|
+
type: Directive,
|
|
58
|
+
args: [{
|
|
59
|
+
standalone: true,
|
|
60
|
+
selector: '[ngxsmkFocusTrap]',
|
|
61
|
+
host: {
|
|
62
|
+
'(keydown)': 'onKeydown($event)',
|
|
63
|
+
},
|
|
64
|
+
}]
|
|
65
|
+
}], ctorParameters: () => [], propDecorators: { ngxsmkFocusTrap: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngxsmkFocusTrap", required: false }] }], ngxsmkFocusTrapAutoCapture: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngxsmkFocusTrapAutoCapture", required: false }] }] } });
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Generated bundle index. Do not edit.
|
|
69
|
+
*/
|
|
70
|
+
|
|
71
|
+
export { NgxsmkFocusTrap };
|
|
72
|
+
//# sourceMappingURL=ngxsmk-cdk-focus-trap.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ngxsmk-cdk-focus-trap.mjs","sources":["../../../../packages/cdk/focus-trap/focus-trap.ts","../../../../packages/cdk/focus-trap/ngxsmk-cdk-focus-trap.ts"],"sourcesContent":["import { DOCUMENT } from '@angular/common';\nimport {\n DestroyRef,\n Directive,\n ElementRef,\n afterNextRender,\n booleanAttribute,\n inject,\n input,\n} from '@angular/core';\nimport { getFocusable } from '@ngxsmk/cdk/focusable';\n\n/**\n * Traps Tab/Shift+Tab focus cycling inside the host element while active.\n * With `ngxsmkFocusTrapAutoCapture`, moves focus into the trap on init and\n * restores the previously focused element on destroy.\n */\n@Directive({\n standalone: true,\n selector: '[ngxsmkFocusTrap]',\n host: {\n '(keydown)': 'onKeydown($event)',\n },\n})\nexport class NgxsmkFocusTrap {\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly document = inject(DOCUMENT);\n\n /** Set to `false` to disable the trap without removing the directive. */\n readonly ngxsmkFocusTrap = input(true, {\n transform: (v: unknown) => v === '' || v === true || v === 'true',\n });\n\n /** Focus the first focusable child on init; restore focus on destroy. */\n readonly ngxsmkFocusTrapAutoCapture = input(false, {\n transform: booleanAttribute,\n });\n\n constructor() {\n const previouslyFocused = this.document.activeElement as HTMLElement | null;\n\n afterNextRender(() => {\n if (this.ngxsmkFocusTrapAutoCapture()) {\n const focusable = getFocusable(this.host.nativeElement);\n (focusable[0] ?? this.host.nativeElement).focus();\n }\n });\n\n inject(DestroyRef).onDestroy(() => {\n if (this.ngxsmkFocusTrapAutoCapture()) {\n previouslyFocused?.focus?.();\n }\n });\n }\n\n protected onKeydown(event: KeyboardEvent): void {\n if (event.key !== 'Tab' || !this.ngxsmkFocusTrap()) {\n return;\n }\n const focusable = getFocusable(this.host.nativeElement);\n if (focusable.length === 0) {\n event.preventDefault();\n return;\n }\n\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n const active = this.document.activeElement;\n\n if (event.shiftKey && (active === first || active === this.host.nativeElement)) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && active === last) {\n event.preventDefault();\n first.focus();\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,21 @@
|
|
|
1
|
+
/** Selector matching elements that can receive keyboard focus. */
|
|
2
|
+
const FOCUSABLE_SELECTOR = [
|
|
3
|
+
'a[href]',
|
|
4
|
+
'button:not([disabled])',
|
|
5
|
+
'input:not([disabled]):not([type="hidden"])',
|
|
6
|
+
'select:not([disabled])',
|
|
7
|
+
'textarea:not([disabled])',
|
|
8
|
+
'[tabindex]:not([tabindex="-1"])',
|
|
9
|
+
'[contenteditable="true"]',
|
|
10
|
+
].join(', ');
|
|
11
|
+
/** All keyboard-focusable elements inside `container`, in DOM order. */
|
|
12
|
+
function getFocusable(container) {
|
|
13
|
+
return Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR)).filter((el) => el.offsetParent !== null || el === el.ownerDocument.activeElement);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Generated bundle index. Do not edit.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export { FOCUSABLE_SELECTOR, getFocusable };
|
|
21
|
+
//# sourceMappingURL=ngxsmk-cdk-focusable.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ngxsmk-cdk-focusable.mjs","sources":["../../../../packages/cdk/focusable/focusable.ts","../../../../packages/cdk/focusable/ngxsmk-cdk-focusable.ts"],"sourcesContent":["/** Selector matching elements that can receive keyboard focus. */\nexport const FOCUSABLE_SELECTOR = [\n 'a[href]',\n 'button:not([disabled])',\n 'input:not([disabled]):not([type=\"hidden\"])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"])',\n '[contenteditable=\"true\"]',\n].join(', ');\n\n/** All keyboard-focusable elements inside `container`, in DOM order. */\nexport function getFocusable(container: HTMLElement): HTMLElement[] {\n return Array.from(\n container.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR),\n ).filter(\n (el) => el.offsetParent !== null || el === el.ownerDocument.activeElement,\n );\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"AAAA;AACO,MAAM,kBAAkB,GAAG;IAChC,SAAS;IACT,wBAAwB;IACxB,4CAA4C;IAC5C,wBAAwB;IACxB,0BAA0B;IAC1B,iCAAiC;IACjC,0BAA0B;AAC3B,CAAA,CAAC,IAAI,CAAC,IAAI;AAEX;AACM,SAAU,YAAY,CAAC,SAAsB,EAAA;AACjD,IAAA,OAAO,KAAK,CAAC,IAAI,CACf,SAAS,CAAC,gBAAgB,CAAc,kBAAkB,CAAC,CAC5D,CAAC,MAAM,CACN,CAAC,EAAE,KAAK,EAAE,CAAC,YAAY,KAAK,IAAI,IAAI,EAAE,KAAK,EAAE,CAAC,aAAa,CAAC,aAAa,CAC1E;AACH;;AClBA;;AAEG;;;;"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, Injectable } from '@angular/core';
|
|
3
|
+
import { DOCUMENT } from '@angular/common';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Announces messages to screen readers through a visually hidden
|
|
7
|
+
* `aria-live` region appended to the document body.
|
|
8
|
+
*/
|
|
9
|
+
class NgxsmkLiveAnnouncer {
|
|
10
|
+
document = inject(DOCUMENT);
|
|
11
|
+
region = null;
|
|
12
|
+
clearTimer = null;
|
|
13
|
+
announce(message, politeness = 'polite') {
|
|
14
|
+
const region = this.ensureRegion();
|
|
15
|
+
region.setAttribute('aria-live', politeness);
|
|
16
|
+
// Clear first so repeating the same message is re-announced.
|
|
17
|
+
region.textContent = '';
|
|
18
|
+
setTimeout(() => (region.textContent = message));
|
|
19
|
+
if (this.clearTimer) {
|
|
20
|
+
clearTimeout(this.clearTimer);
|
|
21
|
+
}
|
|
22
|
+
this.clearTimer = setTimeout(() => (region.textContent = ''), 10_000);
|
|
23
|
+
}
|
|
24
|
+
ngOnDestroy() {
|
|
25
|
+
if (this.clearTimer) {
|
|
26
|
+
clearTimeout(this.clearTimer);
|
|
27
|
+
}
|
|
28
|
+
this.region?.remove();
|
|
29
|
+
}
|
|
30
|
+
ensureRegion() {
|
|
31
|
+
if (!this.region) {
|
|
32
|
+
const region = this.document.createElement('div');
|
|
33
|
+
region.setAttribute('aria-atomic', 'true');
|
|
34
|
+
region.className = 'ngxsmk-live-announcer';
|
|
35
|
+
Object.assign(region.style, {
|
|
36
|
+
position: 'absolute',
|
|
37
|
+
width: '1px',
|
|
38
|
+
height: '1px',
|
|
39
|
+
margin: '-1px',
|
|
40
|
+
padding: '0',
|
|
41
|
+
border: '0',
|
|
42
|
+
overflow: 'hidden',
|
|
43
|
+
clipPath: 'inset(100%)',
|
|
44
|
+
whiteSpace: 'nowrap',
|
|
45
|
+
});
|
|
46
|
+
this.document.body.appendChild(region);
|
|
47
|
+
this.region = region;
|
|
48
|
+
}
|
|
49
|
+
return this.region;
|
|
50
|
+
}
|
|
51
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: NgxsmkLiveAnnouncer, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
52
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: NgxsmkLiveAnnouncer, providedIn: 'root' });
|
|
53
|
+
}
|
|
54
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: NgxsmkLiveAnnouncer, decorators: [{
|
|
55
|
+
type: Injectable,
|
|
56
|
+
args: [{ providedIn: 'root' }]
|
|
57
|
+
}] });
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Generated bundle index. Do not edit.
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
export { NgxsmkLiveAnnouncer };
|
|
64
|
+
//# sourceMappingURL=ngxsmk-cdk-live-announcer.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ngxsmk-cdk-live-announcer.mjs","sources":["../../../../packages/cdk/live-announcer/live-announcer.ts","../../../../packages/cdk/live-announcer/ngxsmk-cdk-live-announcer.ts"],"sourcesContent":["import { Injectable, OnDestroy, inject } from '@angular/core';\nimport { DOCUMENT } from '@angular/common';\n\nexport type AnnouncerPoliteness = 'polite' | 'assertive';\n\n/**\n * Announces messages to screen readers through a visually hidden\n * `aria-live` region appended to the document body.\n */\n@Injectable({ providedIn: 'root' })\nexport class NgxsmkLiveAnnouncer implements OnDestroy {\n private readonly document = inject(DOCUMENT);\n private region: HTMLElement | null = null;\n private clearTimer: ReturnType<typeof setTimeout> | null = null;\n\n announce(message: string, politeness: AnnouncerPoliteness = 'polite'): void {\n const region = this.ensureRegion();\n region.setAttribute('aria-live', politeness);\n\n // Clear first so repeating the same message is re-announced.\n region.textContent = '';\n setTimeout(() => (region.textContent = message));\n\n if (this.clearTimer) {\n clearTimeout(this.clearTimer);\n }\n this.clearTimer = setTimeout(() => (region.textContent = ''), 10_000);\n }\n\n ngOnDestroy(): void {\n if (this.clearTimer) {\n clearTimeout(this.clearTimer);\n }\n this.region?.remove();\n }\n\n private ensureRegion(): HTMLElement {\n if (!this.region) {\n const region = this.document.createElement('div');\n region.setAttribute('aria-atomic', 'true');\n region.className = 'ngxsmk-live-announcer';\n Object.assign(region.style, {\n position: 'absolute',\n width: '1px',\n height: '1px',\n margin: '-1px',\n padding: '0',\n border: '0',\n overflow: 'hidden',\n clipPath: 'inset(100%)',\n whiteSpace: 'nowrap',\n });\n this.document.body.appendChild(region);\n this.region = region;\n }\n return this.region;\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;AAKA;;;AAGG;MAEU,mBAAmB,CAAA;AACb,IAAA,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;IACpC,MAAM,GAAuB,IAAI;IACjC,UAAU,GAAyC,IAAI;AAE/D,IAAA,QAAQ,CAAC,OAAe,EAAE,UAAA,GAAkC,QAAQ,EAAA;AAClE,QAAA,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,EAAE;AAClC,QAAA,MAAM,CAAC,YAAY,CAAC,WAAW,EAAE,UAAU,CAAC;;AAG5C,QAAA,MAAM,CAAC,WAAW,GAAG,EAAE;AACvB,QAAA,UAAU,CAAC,OAAO,MAAM,CAAC,WAAW,GAAG,OAAO,CAAC,CAAC;AAEhD,QAAA,IAAI,IAAI,CAAC,UAAU,EAAE;AACnB,YAAA,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC;QAC/B;AACA,QAAA,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,WAAW,GAAG,EAAE,CAAC,EAAE,MAAM,CAAC;IACvE;IAEA,WAAW,GAAA;AACT,QAAA,IAAI,IAAI,CAAC,UAAU,EAAE;AACnB,YAAA,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC;QAC/B;AACA,QAAA,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE;IACvB;IAEQ,YAAY,GAAA;AAClB,QAAA,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;YAChB,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC;AACjD,YAAA,MAAM,CAAC,YAAY,CAAC,aAAa,EAAE,MAAM,CAAC;AAC1C,YAAA,MAAM,CAAC,SAAS,GAAG,uBAAuB;AAC1C,YAAA,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE;AAC1B,gBAAA,QAAQ,EAAE,UAAU;AACpB,gBAAA,KAAK,EAAE,KAAK;AACZ,gBAAA,MAAM,EAAE,KAAK;AACb,gBAAA,MAAM,EAAE,MAAM;AACd,gBAAA,OAAO,EAAE,GAAG;AACZ,gBAAA,MAAM,EAAE,GAAG;AACX,gBAAA,QAAQ,EAAE,QAAQ;AAClB,gBAAA,QAAQ,EAAE,aAAa;AACvB,gBAAA,UAAU,EAAE,QAAQ;AACrB,aAAA,CAAC;YACF,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;AACtC,YAAA,IAAI,CAAC,MAAM,GAAG,MAAM;QACtB;QACA,OAAO,IAAI,CAAC,MAAM;IACpB;uGA9CW,mBAAmB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA;AAAnB,IAAA,OAAA,KAAA,GAAA,EAAA,CAAA,qBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,mBAAmB,cADN,MAAM,EAAA,CAAA;;2FACnB,mBAAmB,EAAA,UAAA,EAAA,CAAA;kBAD/B,UAAU;mBAAC,EAAE,UAAU,EAAE,MAAM,EAAE;;;ACTlC;;AAEG;;;;"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { DOCUMENT } from '@angular/common';
|
|
2
|
+
import { inject, signal, DestroyRef } from '@angular/core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Reactive media query. Must be called in an injection context.
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* private readonly isMobile = injectMediaQuery('(max-width: 767px)');
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
function injectMediaQuery(query) {
|
|
12
|
+
const window = inject(DOCUMENT).defaultView;
|
|
13
|
+
const matches = signal(false, /* @ts-ignore */
|
|
14
|
+
...(ngDevMode ? [{ debugName: "matches" }] : /* istanbul ignore next */ []));
|
|
15
|
+
const media = window?.matchMedia?.(query);
|
|
16
|
+
if (media) {
|
|
17
|
+
matches.set(media.matches);
|
|
18
|
+
const listener = (event) => matches.set(event.matches);
|
|
19
|
+
media.addEventListener('change', listener);
|
|
20
|
+
inject(DestroyRef).onDestroy(() => media.removeEventListener('change', listener));
|
|
21
|
+
}
|
|
22
|
+
return matches.asReadonly();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Generated bundle index. Do not edit.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
export { injectMediaQuery };
|
|
30
|
+
//# sourceMappingURL=ngxsmk-cdk-media-query.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ngxsmk-cdk-media-query.mjs","sources":["../../../../packages/cdk/media-query/media-query.ts","../../../../packages/cdk/media-query/ngxsmk-cdk-media-query.ts"],"sourcesContent":["import { DOCUMENT } from '@angular/common';\nimport {\n DestroyRef,\n Signal,\n inject,\n signal,\n} from '@angular/core';\n\n/**\n * Reactive media query. Must be called in an injection context.\n *\n * ```ts\n * private readonly isMobile = injectMediaQuery('(max-width: 767px)');\n * ```\n */\nexport function injectMediaQuery(query: string): Signal<boolean> {\n const window = inject(DOCUMENT).defaultView;\n const matches = signal(false);\n\n const media = window?.matchMedia?.(query);\n if (media) {\n matches.set(media.matches);\n const listener = (event: MediaQueryListEvent) => matches.set(event.matches);\n media.addEventListener('change', listener);\n inject(DestroyRef).onDestroy(() =>\n media.removeEventListener('change', listener),\n );\n }\n\n return matches.asReadonly();\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;AAQA;;;;;;AAMG;AACG,SAAU,gBAAgB,CAAC,KAAa,EAAA;IAC5C,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,WAAW;AAC3C,IAAA,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK;gFAAC;IAE7B,MAAM,KAAK,GAAG,MAAM,EAAE,UAAU,GAAG,KAAK,CAAC;IACzC,IAAI,KAAK,EAAE;AACT,QAAA,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC;AAC1B,QAAA,MAAM,QAAQ,GAAG,CAAC,KAA0B,KAAK,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC;AAC3E,QAAA,KAAK,CAAC,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,CAAC;AAC1C,QAAA,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,MAC3B,KAAK,CAAC,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAC9C;IACH;AAEA,IAAA,OAAO,OAAO,CAAC,UAAU,EAAE;AAC7B;;AC9BA;;AAEG;;;;"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, Injectable } from '@angular/core';
|
|
3
|
+
import { DOCUMENT } from '@angular/common';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Locks body scrolling (for dialogs, drawers, sheets). Reference-counted so
|
|
7
|
+
* nested overlays can lock independently; compensates for scrollbar width to
|
|
8
|
+
* prevent layout shift.
|
|
9
|
+
*/
|
|
10
|
+
class NgxsmkScrollLock {
|
|
11
|
+
document = inject(DOCUMENT);
|
|
12
|
+
locks = 0;
|
|
13
|
+
previousOverflow = '';
|
|
14
|
+
previousPaddingRight = '';
|
|
15
|
+
lock() {
|
|
16
|
+
if (this.locks++ > 0) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const body = this.document.body;
|
|
20
|
+
const window = this.document.defaultView;
|
|
21
|
+
const scrollbar = window
|
|
22
|
+
? window.innerWidth - this.document.documentElement.clientWidth
|
|
23
|
+
: 0;
|
|
24
|
+
this.previousOverflow = body.style.overflow;
|
|
25
|
+
this.previousPaddingRight = body.style.paddingRight;
|
|
26
|
+
body.style.overflow = 'hidden';
|
|
27
|
+
if (scrollbar > 0) {
|
|
28
|
+
body.style.paddingRight = `${scrollbar}px`;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
unlock() {
|
|
32
|
+
if (this.locks === 0 || --this.locks > 0) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const body = this.document.body;
|
|
36
|
+
body.style.overflow = this.previousOverflow;
|
|
37
|
+
body.style.paddingRight = this.previousPaddingRight;
|
|
38
|
+
}
|
|
39
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: NgxsmkScrollLock, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
40
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: NgxsmkScrollLock, providedIn: 'root' });
|
|
41
|
+
}
|
|
42
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: NgxsmkScrollLock, decorators: [{
|
|
43
|
+
type: Injectable,
|
|
44
|
+
args: [{ providedIn: 'root' }]
|
|
45
|
+
}] });
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Generated bundle index. Do not edit.
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
export { NgxsmkScrollLock };
|
|
52
|
+
//# sourceMappingURL=ngxsmk-cdk-scroll-lock.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ngxsmk-cdk-scroll-lock.mjs","sources":["../../../../packages/cdk/scroll-lock/scroll-lock.ts","../../../../packages/cdk/scroll-lock/ngxsmk-cdk-scroll-lock.ts"],"sourcesContent":["import { Injectable, inject } from '@angular/core';\nimport { DOCUMENT } from '@angular/common';\n\n/**\n * Locks body scrolling (for dialogs, drawers, sheets). Reference-counted so\n * nested overlays can lock independently; compensates for scrollbar width to\n * prevent layout shift.\n */\n@Injectable({ providedIn: 'root' })\nexport class NgxsmkScrollLock {\n private readonly document = inject(DOCUMENT);\n private locks = 0;\n private previousOverflow = '';\n private previousPaddingRight = '';\n\n lock(): void {\n if (this.locks++ > 0) {\n return;\n }\n const body = this.document.body;\n const window = this.document.defaultView;\n const scrollbar = window\n ? window.innerWidth - this.document.documentElement.clientWidth\n : 0;\n\n this.previousOverflow = body.style.overflow;\n this.previousPaddingRight = body.style.paddingRight;\n body.style.overflow = 'hidden';\n if (scrollbar > 0) {\n body.style.paddingRight = `${scrollbar}px`;\n }\n }\n\n unlock(): void {\n if (this.locks === 0 || --this.locks > 0) {\n return;\n }\n const body = this.document.body;\n body.style.overflow = this.previousOverflow;\n body.style.paddingRight = this.previousPaddingRight;\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;AAGA;;;;AAIG;MAEU,gBAAgB,CAAA;AACV,IAAA,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;IACpC,KAAK,GAAG,CAAC;IACT,gBAAgB,GAAG,EAAE;IACrB,oBAAoB,GAAG,EAAE;IAEjC,IAAI,GAAA;AACF,QAAA,IAAI,IAAI,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE;YACpB;QACF;AACA,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI;AAC/B,QAAA,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW;QACxC,MAAM,SAAS,GAAG;cACd,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC;cAClD,CAAC;QAEL,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ;QAC3C,IAAI,CAAC,oBAAoB,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY;AACnD,QAAA,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,QAAQ;AAC9B,QAAA,IAAI,SAAS,GAAG,CAAC,EAAE;YACjB,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,CAAA,EAAG,SAAS,IAAI;QAC5C;IACF;IAEA,MAAM,GAAA;AACJ,QAAA,IAAI,IAAI,CAAC,KAAK,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,GAAG,CAAC,EAAE;YACxC;QACF;AACA,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI;QAC/B,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC,gBAAgB;QAC3C,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC,oBAAoB;IACrD;uGA/BW,gBAAgB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA;AAAhB,IAAA,OAAA,KAAA,GAAA,EAAA,CAAA,qBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,gBAAgB,cADH,MAAM,EAAA,CAAA;;2FACnB,gBAAgB,EAAA,UAAA,EAAA,CAAA;kBAD5B,UAAU;mBAAC,EAAE,UAAU,EAAE,MAAM,EAAE;;;ACRlC;;AAEG;;;;"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { Directive } from '@angular/core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Hides the host visually while keeping it available to screen readers.
|
|
6
|
+
*
|
|
7
|
+
* ```html
|
|
8
|
+
* <span ngxsmkVisuallyHidden>Opens in a new window</span>
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
class NgxsmkVisuallyHidden {
|
|
12
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: NgxsmkVisuallyHidden, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
13
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.6", type: NgxsmkVisuallyHidden, isStandalone: true, selector: "[ngxsmkVisuallyHidden]", host: { styleAttribute: "\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n border: 0;\n overflow: hidden;\n clip-path: inset(100%);\n white-space: nowrap;\n " }, ngImport: i0 });
|
|
14
|
+
}
|
|
15
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: NgxsmkVisuallyHidden, decorators: [{
|
|
16
|
+
type: Directive,
|
|
17
|
+
args: [{
|
|
18
|
+
standalone: true,
|
|
19
|
+
selector: '[ngxsmkVisuallyHidden]',
|
|
20
|
+
host: {
|
|
21
|
+
style: `
|
|
22
|
+
position: absolute;
|
|
23
|
+
width: 1px;
|
|
24
|
+
height: 1px;
|
|
25
|
+
margin: -1px;
|
|
26
|
+
padding: 0;
|
|
27
|
+
border: 0;
|
|
28
|
+
overflow: hidden;
|
|
29
|
+
clip-path: inset(100%);
|
|
30
|
+
white-space: nowrap;
|
|
31
|
+
`,
|
|
32
|
+
},
|
|
33
|
+
}]
|
|
34
|
+
}] });
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Generated bundle index. Do not edit.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
export { NgxsmkVisuallyHidden };
|
|
41
|
+
//# sourceMappingURL=ngxsmk-cdk-visually-hidden.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ngxsmk-cdk-visually-hidden.mjs","sources":["../../../../packages/cdk/visually-hidden/visually-hidden.ts","../../../../packages/cdk/visually-hidden/ngxsmk-cdk-visually-hidden.ts"],"sourcesContent":["import { Directive } from '@angular/core';\n\n/**\n * Hides the host visually while keeping it available to screen readers.\n *\n * ```html\n * <span ngxsmkVisuallyHidden>Opens in a new window</span>\n * ```\n */\n@Directive({\n standalone: true,\n selector: '[ngxsmkVisuallyHidden]',\n host: {\n style: `\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n border: 0;\n overflow: hidden;\n clip-path: inset(100%);\n white-space: nowrap;\n `,\n },\n})\nexport class NgxsmkVisuallyHidden {}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;AAEA;;;;;;AAMG;MAkBU,oBAAoB,CAAA;uGAApB,oBAAoB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;2FAApB,oBAAoB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,wBAAA,EAAA,IAAA,EAAA,EAAA,cAAA,EAAA,wNAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,CAAA;;2FAApB,oBAAoB,EAAA,UAAA,EAAA,CAAA;kBAjBhC,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,UAAU,EAAE,IAAI;AAChB,oBAAA,QAAQ,EAAE,wBAAwB;AAClC,oBAAA,IAAI,EAAE;AACJ,wBAAA,KAAK,EAAE;;;;;;;;;;AAUN,IAAA,CAAA;AACF,qBAAA;AACF,iBAAA;;;ACzBD;;AAEG;;;;"}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { DOCUMENT } from '@angular/common';
|
|
2
|
+
import * as i0 from '@angular/core';
|
|
3
|
+
import { inject, ElementRef, output, DestroyRef, Directive, input, booleanAttribute, afterNextRender, Injectable, signal } from '@angular/core';
|
|
4
|
+
import { getFocusable as getFocusable$1 } from '@ngxsmk/cdk/focusable';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Emits when a pointer press lands outside the host element.
|
|
8
|
+
*
|
|
9
|
+
* ```html
|
|
10
|
+
* <div class="menu" (ngxsmkClickOutside)="close()">…</div>
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
class NgxsmkClickOutside {
|
|
14
|
+
host = inject(ElementRef);
|
|
15
|
+
ngxsmkClickOutside = output();
|
|
16
|
+
constructor() {
|
|
17
|
+
const document = inject(DOCUMENT);
|
|
18
|
+
const destroyRef = inject(DestroyRef);
|
|
19
|
+
const onPointerDown = (event) => {
|
|
20
|
+
const target = event.target;
|
|
21
|
+
if (target && !this.host.nativeElement.contains(target)) {
|
|
22
|
+
this.ngxsmkClickOutside.emit(event);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
// Capture phase so stopped propagation inside other widgets can't
|
|
26
|
+
// swallow the outside press.
|
|
27
|
+
document.addEventListener('pointerdown', onPointerDown, true);
|
|
28
|
+
destroyRef.onDestroy(() => document.removeEventListener('pointerdown', onPointerDown, true));
|
|
29
|
+
}
|
|
30
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: NgxsmkClickOutside, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
31
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.6", type: NgxsmkClickOutside, isStandalone: true, selector: "[ngxsmkClickOutside]", outputs: { ngxsmkClickOutside: "ngxsmkClickOutside" }, ngImport: i0 });
|
|
32
|
+
}
|
|
33
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: NgxsmkClickOutside, decorators: [{
|
|
34
|
+
type: Directive,
|
|
35
|
+
args: [{
|
|
36
|
+
standalone: true,
|
|
37
|
+
selector: '[ngxsmkClickOutside]',
|
|
38
|
+
}]
|
|
39
|
+
}], ctorParameters: () => [], propDecorators: { ngxsmkClickOutside: [{ type: i0.Output, args: ["ngxsmkClickOutside"] }] } });
|
|
40
|
+
|
|
41
|
+
/** Selector matching elements that can receive keyboard focus. */
|
|
42
|
+
const FOCUSABLE_SELECTOR = [
|
|
43
|
+
'a[href]',
|
|
44
|
+
'button:not([disabled])',
|
|
45
|
+
'input:not([disabled]):not([type="hidden"])',
|
|
46
|
+
'select:not([disabled])',
|
|
47
|
+
'textarea:not([disabled])',
|
|
48
|
+
'[tabindex]:not([tabindex="-1"])',
|
|
49
|
+
'[contenteditable="true"]',
|
|
50
|
+
].join(', ');
|
|
51
|
+
/** All keyboard-focusable elements inside `container`, in DOM order. */
|
|
52
|
+
function getFocusable(container) {
|
|
53
|
+
return Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR)).filter((el) => el.offsetParent !== null || el === el.ownerDocument.activeElement);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Traps Tab/Shift+Tab focus cycling inside the host element while active.
|
|
58
|
+
* With `ngxsmkFocusTrapAutoCapture`, moves focus into the trap on init and
|
|
59
|
+
* restores the previously focused element on destroy.
|
|
60
|
+
*/
|
|
61
|
+
class NgxsmkFocusTrap {
|
|
62
|
+
host = inject(ElementRef);
|
|
63
|
+
document = inject(DOCUMENT);
|
|
64
|
+
/** Set to `false` to disable the trap without removing the directive. */
|
|
65
|
+
ngxsmkFocusTrap = input(true, { ...(ngDevMode ? { debugName: "ngxsmkFocusTrap" } : /* istanbul ignore next */ {}), transform: (v) => v === '' || v === true || v === 'true' });
|
|
66
|
+
/** Focus the first focusable child on init; restore focus on destroy. */
|
|
67
|
+
ngxsmkFocusTrapAutoCapture = input(false, { ...(ngDevMode ? { debugName: "ngxsmkFocusTrapAutoCapture" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
68
|
+
constructor() {
|
|
69
|
+
const previouslyFocused = this.document.activeElement;
|
|
70
|
+
afterNextRender(() => {
|
|
71
|
+
if (this.ngxsmkFocusTrapAutoCapture()) {
|
|
72
|
+
const focusable = getFocusable$1(this.host.nativeElement);
|
|
73
|
+
(focusable[0] ?? this.host.nativeElement).focus();
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
inject(DestroyRef).onDestroy(() => {
|
|
77
|
+
if (this.ngxsmkFocusTrapAutoCapture()) {
|
|
78
|
+
previouslyFocused?.focus?.();
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
onKeydown(event) {
|
|
83
|
+
if (event.key !== 'Tab' || !this.ngxsmkFocusTrap()) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const focusable = getFocusable$1(this.host.nativeElement);
|
|
87
|
+
if (focusable.length === 0) {
|
|
88
|
+
event.preventDefault();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const first = focusable[0];
|
|
92
|
+
const last = focusable[focusable.length - 1];
|
|
93
|
+
const active = this.document.activeElement;
|
|
94
|
+
if (event.shiftKey && (active === first || active === this.host.nativeElement)) {
|
|
95
|
+
event.preventDefault();
|
|
96
|
+
last.focus();
|
|
97
|
+
}
|
|
98
|
+
else if (!event.shiftKey && active === last) {
|
|
99
|
+
event.preventDefault();
|
|
100
|
+
first.focus();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: NgxsmkFocusTrap, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
104
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.6", type: NgxsmkFocusTrap, isStandalone: true, selector: "[ngxsmkFocusTrap]", inputs: { ngxsmkFocusTrap: { classPropertyName: "ngxsmkFocusTrap", publicName: "ngxsmkFocusTrap", isSignal: true, isRequired: false, transformFunction: null }, ngxsmkFocusTrapAutoCapture: { classPropertyName: "ngxsmkFocusTrapAutoCapture", publicName: "ngxsmkFocusTrapAutoCapture", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "keydown": "onKeydown($event)" } }, ngImport: i0 });
|
|
105
|
+
}
|
|
106
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: NgxsmkFocusTrap, decorators: [{
|
|
107
|
+
type: Directive,
|
|
108
|
+
args: [{
|
|
109
|
+
standalone: true,
|
|
110
|
+
selector: '[ngxsmkFocusTrap]',
|
|
111
|
+
host: {
|
|
112
|
+
'(keydown)': 'onKeydown($event)',
|
|
113
|
+
},
|
|
114
|
+
}]
|
|
115
|
+
}], ctorParameters: () => [], propDecorators: { ngxsmkFocusTrap: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngxsmkFocusTrap", required: false }] }], ngxsmkFocusTrapAutoCapture: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngxsmkFocusTrapAutoCapture", required: false }] }] } });
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Announces messages to screen readers through a visually hidden
|
|
119
|
+
* `aria-live` region appended to the document body.
|
|
120
|
+
*/
|
|
121
|
+
class NgxsmkLiveAnnouncer {
|
|
122
|
+
document = inject(DOCUMENT);
|
|
123
|
+
region = null;
|
|
124
|
+
clearTimer = null;
|
|
125
|
+
announce(message, politeness = 'polite') {
|
|
126
|
+
const region = this.ensureRegion();
|
|
127
|
+
region.setAttribute('aria-live', politeness);
|
|
128
|
+
// Clear first so repeating the same message is re-announced.
|
|
129
|
+
region.textContent = '';
|
|
130
|
+
setTimeout(() => (region.textContent = message));
|
|
131
|
+
if (this.clearTimer) {
|
|
132
|
+
clearTimeout(this.clearTimer);
|
|
133
|
+
}
|
|
134
|
+
this.clearTimer = setTimeout(() => (region.textContent = ''), 10_000);
|
|
135
|
+
}
|
|
136
|
+
ngOnDestroy() {
|
|
137
|
+
if (this.clearTimer) {
|
|
138
|
+
clearTimeout(this.clearTimer);
|
|
139
|
+
}
|
|
140
|
+
this.region?.remove();
|
|
141
|
+
}
|
|
142
|
+
ensureRegion() {
|
|
143
|
+
if (!this.region) {
|
|
144
|
+
const region = this.document.createElement('div');
|
|
145
|
+
region.setAttribute('aria-atomic', 'true');
|
|
146
|
+
region.className = 'ngxsmk-live-announcer';
|
|
147
|
+
Object.assign(region.style, {
|
|
148
|
+
position: 'absolute',
|
|
149
|
+
width: '1px',
|
|
150
|
+
height: '1px',
|
|
151
|
+
margin: '-1px',
|
|
152
|
+
padding: '0',
|
|
153
|
+
border: '0',
|
|
154
|
+
overflow: 'hidden',
|
|
155
|
+
clipPath: 'inset(100%)',
|
|
156
|
+
whiteSpace: 'nowrap',
|
|
157
|
+
});
|
|
158
|
+
this.document.body.appendChild(region);
|
|
159
|
+
this.region = region;
|
|
160
|
+
}
|
|
161
|
+
return this.region;
|
|
162
|
+
}
|
|
163
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: NgxsmkLiveAnnouncer, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
164
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: NgxsmkLiveAnnouncer, providedIn: 'root' });
|
|
165
|
+
}
|
|
166
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: NgxsmkLiveAnnouncer, decorators: [{
|
|
167
|
+
type: Injectable,
|
|
168
|
+
args: [{ providedIn: 'root' }]
|
|
169
|
+
}] });
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Reactive media query. Must be called in an injection context.
|
|
173
|
+
*
|
|
174
|
+
* ```ts
|
|
175
|
+
* private readonly isMobile = injectMediaQuery('(max-width: 767px)');
|
|
176
|
+
* ```
|
|
177
|
+
*/
|
|
178
|
+
function injectMediaQuery(query) {
|
|
179
|
+
const window = inject(DOCUMENT).defaultView;
|
|
180
|
+
const matches = signal(false, /* @ts-ignore */
|
|
181
|
+
...(ngDevMode ? [{ debugName: "matches" }] : /* istanbul ignore next */ []));
|
|
182
|
+
const media = window?.matchMedia?.(query);
|
|
183
|
+
if (media) {
|
|
184
|
+
matches.set(media.matches);
|
|
185
|
+
const listener = (event) => matches.set(event.matches);
|
|
186
|
+
media.addEventListener('change', listener);
|
|
187
|
+
inject(DestroyRef).onDestroy(() => media.removeEventListener('change', listener));
|
|
188
|
+
}
|
|
189
|
+
return matches.asReadonly();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Locks body scrolling (for dialogs, drawers, sheets). Reference-counted so
|
|
194
|
+
* nested overlays can lock independently; compensates for scrollbar width to
|
|
195
|
+
* prevent layout shift.
|
|
196
|
+
*/
|
|
197
|
+
class NgxsmkScrollLock {
|
|
198
|
+
document = inject(DOCUMENT);
|
|
199
|
+
locks = 0;
|
|
200
|
+
previousOverflow = '';
|
|
201
|
+
previousPaddingRight = '';
|
|
202
|
+
lock() {
|
|
203
|
+
if (this.locks++ > 0) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const body = this.document.body;
|
|
207
|
+
const window = this.document.defaultView;
|
|
208
|
+
const scrollbar = window
|
|
209
|
+
? window.innerWidth - this.document.documentElement.clientWidth
|
|
210
|
+
: 0;
|
|
211
|
+
this.previousOverflow = body.style.overflow;
|
|
212
|
+
this.previousPaddingRight = body.style.paddingRight;
|
|
213
|
+
body.style.overflow = 'hidden';
|
|
214
|
+
if (scrollbar > 0) {
|
|
215
|
+
body.style.paddingRight = `${scrollbar}px`;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
unlock() {
|
|
219
|
+
if (this.locks === 0 || --this.locks > 0) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const body = this.document.body;
|
|
223
|
+
body.style.overflow = this.previousOverflow;
|
|
224
|
+
body.style.paddingRight = this.previousPaddingRight;
|
|
225
|
+
}
|
|
226
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: NgxsmkScrollLock, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
227
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: NgxsmkScrollLock, providedIn: 'root' });
|
|
228
|
+
}
|
|
229
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: NgxsmkScrollLock, decorators: [{
|
|
230
|
+
type: Injectable,
|
|
231
|
+
args: [{ providedIn: 'root' }]
|
|
232
|
+
}] });
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Hides the host visually while keeping it available to screen readers.
|
|
236
|
+
*
|
|
237
|
+
* ```html
|
|
238
|
+
* <span ngxsmkVisuallyHidden>Opens in a new window</span>
|
|
239
|
+
* ```
|
|
240
|
+
*/
|
|
241
|
+
class NgxsmkVisuallyHidden {
|
|
242
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: NgxsmkVisuallyHidden, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
243
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.6", type: NgxsmkVisuallyHidden, isStandalone: true, selector: "[ngxsmkVisuallyHidden]", host: { styleAttribute: "\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n border: 0;\n overflow: hidden;\n clip-path: inset(100%);\n white-space: nowrap;\n " }, ngImport: i0 });
|
|
244
|
+
}
|
|
245
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: NgxsmkVisuallyHidden, decorators: [{
|
|
246
|
+
type: Directive,
|
|
247
|
+
args: [{
|
|
248
|
+
standalone: true,
|
|
249
|
+
selector: '[ngxsmkVisuallyHidden]',
|
|
250
|
+
host: {
|
|
251
|
+
style: `
|
|
252
|
+
position: absolute;
|
|
253
|
+
width: 1px;
|
|
254
|
+
height: 1px;
|
|
255
|
+
margin: -1px;
|
|
256
|
+
padding: 0;
|
|
257
|
+
border: 0;
|
|
258
|
+
overflow: hidden;
|
|
259
|
+
clip-path: inset(100%);
|
|
260
|
+
white-space: nowrap;
|
|
261
|
+
`,
|
|
262
|
+
},
|
|
263
|
+
}]
|
|
264
|
+
}] });
|
|
265
|
+
|
|
266
|
+
/*
|
|
267
|
+
* Public API Surface of @ngxsmk/cdk
|
|
268
|
+
*/
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Generated bundle index. Do not edit.
|
|
272
|
+
*/
|
|
273
|
+
|
|
274
|
+
export { FOCUSABLE_SELECTOR, NgxsmkClickOutside, NgxsmkFocusTrap, NgxsmkLiveAnnouncer, NgxsmkScrollLock, NgxsmkVisuallyHidden, getFocusable, injectMediaQuery };
|
|
275
|
+
//# sourceMappingURL=ngxsmk-cdk.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ngxsmk-cdk.mjs","sources":["../../../../packages/cdk/click-outside/click-outside.ts","../../../../packages/cdk/focusable/focusable.ts","../../../../packages/cdk/focus-trap/focus-trap.ts","../../../../packages/cdk/live-announcer/live-announcer.ts","../../../../packages/cdk/media-query/media-query.ts","../../../../packages/cdk/scroll-lock/scroll-lock.ts","../../../../packages/cdk/visually-hidden/visually-hidden.ts","../../../../packages/cdk/public-api.ts","../../../../packages/cdk/ngxsmk-cdk.ts"],"sourcesContent":["import { DOCUMENT } from '@angular/common';\nimport {\n DestroyRef,\n Directive,\n ElementRef,\n inject,\n output,\n} from '@angular/core';\n\n/**\n * Emits when a pointer press lands outside the host element.\n *\n * ```html\n * <div class=\"menu\" (ngxsmkClickOutside)=\"close()\">…</div>\n * ```\n */\n@Directive({\n standalone: true,\n selector: '[ngxsmkClickOutside]',\n})\nexport class NgxsmkClickOutside {\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n\n readonly ngxsmkClickOutside = output<Event>();\n\n constructor() {\n const document = inject(DOCUMENT);\n const destroyRef = inject(DestroyRef);\n\n const onPointerDown = (event: Event) => {\n const target = event.target as Node | null;\n if (target && !this.host.nativeElement.contains(target)) {\n this.ngxsmkClickOutside.emit(event);\n }\n };\n\n // Capture phase so stopped propagation inside other widgets can't\n // swallow the outside press.\n document.addEventListener('pointerdown', onPointerDown, true);\n destroyRef.onDestroy(() =>\n document.removeEventListener('pointerdown', onPointerDown, true),\n );\n }\n}\n","/** Selector matching elements that can receive keyboard focus. */\nexport const FOCUSABLE_SELECTOR = [\n 'a[href]',\n 'button:not([disabled])',\n 'input:not([disabled]):not([type=\"hidden\"])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"])',\n '[contenteditable=\"true\"]',\n].join(', ');\n\n/** All keyboard-focusable elements inside `container`, in DOM order. */\nexport function getFocusable(container: HTMLElement): HTMLElement[] {\n return Array.from(\n container.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR),\n ).filter(\n (el) => el.offsetParent !== null || el === el.ownerDocument.activeElement,\n );\n}\n","import { DOCUMENT } from '@angular/common';\nimport {\n DestroyRef,\n Directive,\n ElementRef,\n afterNextRender,\n booleanAttribute,\n inject,\n input,\n} from '@angular/core';\nimport { getFocusable } from '@ngxsmk/cdk/focusable';\n\n/**\n * Traps Tab/Shift+Tab focus cycling inside the host element while active.\n * With `ngxsmkFocusTrapAutoCapture`, moves focus into the trap on init and\n * restores the previously focused element on destroy.\n */\n@Directive({\n standalone: true,\n selector: '[ngxsmkFocusTrap]',\n host: {\n '(keydown)': 'onKeydown($event)',\n },\n})\nexport class NgxsmkFocusTrap {\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly document = inject(DOCUMENT);\n\n /** Set to `false` to disable the trap without removing the directive. */\n readonly ngxsmkFocusTrap = input(true, {\n transform: (v: unknown) => v === '' || v === true || v === 'true',\n });\n\n /** Focus the first focusable child on init; restore focus on destroy. */\n readonly ngxsmkFocusTrapAutoCapture = input(false, {\n transform: booleanAttribute,\n });\n\n constructor() {\n const previouslyFocused = this.document.activeElement as HTMLElement | null;\n\n afterNextRender(() => {\n if (this.ngxsmkFocusTrapAutoCapture()) {\n const focusable = getFocusable(this.host.nativeElement);\n (focusable[0] ?? this.host.nativeElement).focus();\n }\n });\n\n inject(DestroyRef).onDestroy(() => {\n if (this.ngxsmkFocusTrapAutoCapture()) {\n previouslyFocused?.focus?.();\n }\n });\n }\n\n protected onKeydown(event: KeyboardEvent): void {\n if (event.key !== 'Tab' || !this.ngxsmkFocusTrap()) {\n return;\n }\n const focusable = getFocusable(this.host.nativeElement);\n if (focusable.length === 0) {\n event.preventDefault();\n return;\n }\n\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n const active = this.document.activeElement;\n\n if (event.shiftKey && (active === first || active === this.host.nativeElement)) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && active === last) {\n event.preventDefault();\n first.focus();\n }\n }\n}\n","import { Injectable, OnDestroy, inject } from '@angular/core';\nimport { DOCUMENT } from '@angular/common';\n\nexport type AnnouncerPoliteness = 'polite' | 'assertive';\n\n/**\n * Announces messages to screen readers through a visually hidden\n * `aria-live` region appended to the document body.\n */\n@Injectable({ providedIn: 'root' })\nexport class NgxsmkLiveAnnouncer implements OnDestroy {\n private readonly document = inject(DOCUMENT);\n private region: HTMLElement | null = null;\n private clearTimer: ReturnType<typeof setTimeout> | null = null;\n\n announce(message: string, politeness: AnnouncerPoliteness = 'polite'): void {\n const region = this.ensureRegion();\n region.setAttribute('aria-live', politeness);\n\n // Clear first so repeating the same message is re-announced.\n region.textContent = '';\n setTimeout(() => (region.textContent = message));\n\n if (this.clearTimer) {\n clearTimeout(this.clearTimer);\n }\n this.clearTimer = setTimeout(() => (region.textContent = ''), 10_000);\n }\n\n ngOnDestroy(): void {\n if (this.clearTimer) {\n clearTimeout(this.clearTimer);\n }\n this.region?.remove();\n }\n\n private ensureRegion(): HTMLElement {\n if (!this.region) {\n const region = this.document.createElement('div');\n region.setAttribute('aria-atomic', 'true');\n region.className = 'ngxsmk-live-announcer';\n Object.assign(region.style, {\n position: 'absolute',\n width: '1px',\n height: '1px',\n margin: '-1px',\n padding: '0',\n border: '0',\n overflow: 'hidden',\n clipPath: 'inset(100%)',\n whiteSpace: 'nowrap',\n });\n this.document.body.appendChild(region);\n this.region = region;\n }\n return this.region;\n }\n}\n","import { DOCUMENT } from '@angular/common';\nimport {\n DestroyRef,\n Signal,\n inject,\n signal,\n} from '@angular/core';\n\n/**\n * Reactive media query. Must be called in an injection context.\n *\n * ```ts\n * private readonly isMobile = injectMediaQuery('(max-width: 767px)');\n * ```\n */\nexport function injectMediaQuery(query: string): Signal<boolean> {\n const window = inject(DOCUMENT).defaultView;\n const matches = signal(false);\n\n const media = window?.matchMedia?.(query);\n if (media) {\n matches.set(media.matches);\n const listener = (event: MediaQueryListEvent) => matches.set(event.matches);\n media.addEventListener('change', listener);\n inject(DestroyRef).onDestroy(() =>\n media.removeEventListener('change', listener),\n );\n }\n\n return matches.asReadonly();\n}\n","import { Injectable, inject } from '@angular/core';\nimport { DOCUMENT } from '@angular/common';\n\n/**\n * Locks body scrolling (for dialogs, drawers, sheets). Reference-counted so\n * nested overlays can lock independently; compensates for scrollbar width to\n * prevent layout shift.\n */\n@Injectable({ providedIn: 'root' })\nexport class NgxsmkScrollLock {\n private readonly document = inject(DOCUMENT);\n private locks = 0;\n private previousOverflow = '';\n private previousPaddingRight = '';\n\n lock(): void {\n if (this.locks++ > 0) {\n return;\n }\n const body = this.document.body;\n const window = this.document.defaultView;\n const scrollbar = window\n ? window.innerWidth - this.document.documentElement.clientWidth\n : 0;\n\n this.previousOverflow = body.style.overflow;\n this.previousPaddingRight = body.style.paddingRight;\n body.style.overflow = 'hidden';\n if (scrollbar > 0) {\n body.style.paddingRight = `${scrollbar}px`;\n }\n }\n\n unlock(): void {\n if (this.locks === 0 || --this.locks > 0) {\n return;\n }\n const body = this.document.body;\n body.style.overflow = this.previousOverflow;\n body.style.paddingRight = this.previousPaddingRight;\n }\n}\n","import { Directive } from '@angular/core';\n\n/**\n * Hides the host visually while keeping it available to screen readers.\n *\n * ```html\n * <span ngxsmkVisuallyHidden>Opens in a new window</span>\n * ```\n */\n@Directive({\n standalone: true,\n selector: '[ngxsmkVisuallyHidden]',\n host: {\n style: `\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n border: 0;\n overflow: hidden;\n clip-path: inset(100%);\n white-space: nowrap;\n `,\n },\n})\nexport class NgxsmkVisuallyHidden {}\n","/*\n * Public API Surface of @ngxsmk/cdk\n */\n\nexport * from './click-outside';\nexport * from './focusable';\nexport * from './focus-trap';\nexport * from './live-announcer';\nexport * from './media-query';\nexport * from './scroll-lock';\nexport * from './visually-hidden';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["getFocusable"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ngxsmk/cdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"peerDependencies": {
|
|
6
|
+
"@angular/common": ">=17.3.0",
|
|
7
|
+
"@angular/core": ">=17.3.0"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"tslib": "^2.3.0"
|
|
11
|
+
},
|
|
12
|
+
"sideEffects": false,
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"module": "fesm2022/ngxsmk-cdk.mjs",
|
|
17
|
+
"typings": "types/ngxsmk-cdk.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
"./package.json": {
|
|
20
|
+
"default": "./package.json"
|
|
21
|
+
},
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./types/ngxsmk-cdk.d.ts",
|
|
24
|
+
"default": "./fesm2022/ngxsmk-cdk.mjs"
|
|
25
|
+
},
|
|
26
|
+
"./click-outside": {
|
|
27
|
+
"types": "./types/ngxsmk-cdk-click-outside.d.ts",
|
|
28
|
+
"default": "./fesm2022/ngxsmk-cdk-click-outside.mjs"
|
|
29
|
+
},
|
|
30
|
+
"./focus-trap": {
|
|
31
|
+
"types": "./types/ngxsmk-cdk-focus-trap.d.ts",
|
|
32
|
+
"default": "./fesm2022/ngxsmk-cdk-focus-trap.mjs"
|
|
33
|
+
},
|
|
34
|
+
"./focusable": {
|
|
35
|
+
"types": "./types/ngxsmk-cdk-focusable.d.ts",
|
|
36
|
+
"default": "./fesm2022/ngxsmk-cdk-focusable.mjs"
|
|
37
|
+
},
|
|
38
|
+
"./live-announcer": {
|
|
39
|
+
"types": "./types/ngxsmk-cdk-live-announcer.d.ts",
|
|
40
|
+
"default": "./fesm2022/ngxsmk-cdk-live-announcer.mjs"
|
|
41
|
+
},
|
|
42
|
+
"./media-query": {
|
|
43
|
+
"types": "./types/ngxsmk-cdk-media-query.d.ts",
|
|
44
|
+
"default": "./fesm2022/ngxsmk-cdk-media-query.mjs"
|
|
45
|
+
},
|
|
46
|
+
"./scroll-lock": {
|
|
47
|
+
"types": "./types/ngxsmk-cdk-scroll-lock.d.ts",
|
|
48
|
+
"default": "./fesm2022/ngxsmk-cdk-scroll-lock.mjs"
|
|
49
|
+
},
|
|
50
|
+
"./visually-hidden": {
|
|
51
|
+
"types": "./types/ngxsmk-cdk-visually-hidden.d.ts",
|
|
52
|
+
"default": "./fesm2022/ngxsmk-cdk-visually-hidden.mjs"
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"type": "module"
|
|
56
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Emits when a pointer press lands outside the host element.
|
|
5
|
+
*
|
|
6
|
+
* ```html
|
|
7
|
+
* <div class="menu" (ngxsmkClickOutside)="close()">…</div>
|
|
8
|
+
* ```
|
|
9
|
+
*/
|
|
10
|
+
declare class NgxsmkClickOutside {
|
|
11
|
+
private readonly host;
|
|
12
|
+
readonly ngxsmkClickOutside: i0.OutputEmitterRef<Event>;
|
|
13
|
+
constructor();
|
|
14
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<NgxsmkClickOutside, never>;
|
|
15
|
+
static ɵdir: i0.ɵɵDirectiveDeclaration<NgxsmkClickOutside, "[ngxsmkClickOutside]", never, {}, { "ngxsmkClickOutside": "ngxsmkClickOutside"; }, never, never, true, never>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { NgxsmkClickOutside };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as _angular_core from '@angular/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Traps Tab/Shift+Tab focus cycling inside the host element while active.
|
|
5
|
+
* With `ngxsmkFocusTrapAutoCapture`, moves focus into the trap on init and
|
|
6
|
+
* restores the previously focused element on destroy.
|
|
7
|
+
*/
|
|
8
|
+
declare class NgxsmkFocusTrap {
|
|
9
|
+
private readonly host;
|
|
10
|
+
private readonly document;
|
|
11
|
+
/** Set to `false` to disable the trap without removing the directive. */
|
|
12
|
+
readonly ngxsmkFocusTrap: _angular_core.InputSignalWithTransform<boolean, unknown>;
|
|
13
|
+
/** Focus the first focusable child on init; restore focus on destroy. */
|
|
14
|
+
readonly ngxsmkFocusTrapAutoCapture: _angular_core.InputSignalWithTransform<boolean, unknown>;
|
|
15
|
+
constructor();
|
|
16
|
+
protected onKeydown(event: KeyboardEvent): void;
|
|
17
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<NgxsmkFocusTrap, never>;
|
|
18
|
+
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<NgxsmkFocusTrap, "[ngxsmkFocusTrap]", never, { "ngxsmkFocusTrap": { "alias": "ngxsmkFocusTrap"; "required": false; "isSignal": true; }; "ngxsmkFocusTrapAutoCapture": { "alias": "ngxsmkFocusTrapAutoCapture"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export { NgxsmkFocusTrap };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Selector matching elements that can receive keyboard focus. */
|
|
2
|
+
declare const FOCUSABLE_SELECTOR: string;
|
|
3
|
+
/** All keyboard-focusable elements inside `container`, in DOM order. */
|
|
4
|
+
declare function getFocusable(container: HTMLElement): HTMLElement[];
|
|
5
|
+
|
|
6
|
+
export { FOCUSABLE_SELECTOR, getFocusable };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { OnDestroy } from '@angular/core';
|
|
3
|
+
|
|
4
|
+
type AnnouncerPoliteness = 'polite' | 'assertive';
|
|
5
|
+
/**
|
|
6
|
+
* Announces messages to screen readers through a visually hidden
|
|
7
|
+
* `aria-live` region appended to the document body.
|
|
8
|
+
*/
|
|
9
|
+
declare class NgxsmkLiveAnnouncer implements OnDestroy {
|
|
10
|
+
private readonly document;
|
|
11
|
+
private region;
|
|
12
|
+
private clearTimer;
|
|
13
|
+
announce(message: string, politeness?: AnnouncerPoliteness): void;
|
|
14
|
+
ngOnDestroy(): void;
|
|
15
|
+
private ensureRegion;
|
|
16
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<NgxsmkLiveAnnouncer, never>;
|
|
17
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<NgxsmkLiveAnnouncer>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { NgxsmkLiveAnnouncer };
|
|
21
|
+
export type { AnnouncerPoliteness };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Signal } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Reactive media query. Must be called in an injection context.
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* private readonly isMobile = injectMediaQuery('(max-width: 767px)');
|
|
8
|
+
* ```
|
|
9
|
+
*/
|
|
10
|
+
declare function injectMediaQuery(query: string): Signal<boolean>;
|
|
11
|
+
|
|
12
|
+
export { injectMediaQuery };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Locks body scrolling (for dialogs, drawers, sheets). Reference-counted so
|
|
5
|
+
* nested overlays can lock independently; compensates for scrollbar width to
|
|
6
|
+
* prevent layout shift.
|
|
7
|
+
*/
|
|
8
|
+
declare class NgxsmkScrollLock {
|
|
9
|
+
private readonly document;
|
|
10
|
+
private locks;
|
|
11
|
+
private previousOverflow;
|
|
12
|
+
private previousPaddingRight;
|
|
13
|
+
lock(): void;
|
|
14
|
+
unlock(): void;
|
|
15
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<NgxsmkScrollLock, never>;
|
|
16
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<NgxsmkScrollLock>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export { NgxsmkScrollLock };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hides the host visually while keeping it available to screen readers.
|
|
5
|
+
*
|
|
6
|
+
* ```html
|
|
7
|
+
* <span ngxsmkVisuallyHidden>Opens in a new window</span>
|
|
8
|
+
* ```
|
|
9
|
+
*/
|
|
10
|
+
declare class NgxsmkVisuallyHidden {
|
|
11
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<NgxsmkVisuallyHidden, never>;
|
|
12
|
+
static ɵdir: i0.ɵɵDirectiveDeclaration<NgxsmkVisuallyHidden, "[ngxsmkVisuallyHidden]", never, {}, {}, never, never, true, never>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export { NgxsmkVisuallyHidden };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { OnDestroy, Signal } from '@angular/core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Emits when a pointer press lands outside the host element.
|
|
6
|
+
*
|
|
7
|
+
* ```html
|
|
8
|
+
* <div class="menu" (ngxsmkClickOutside)="close()">…</div>
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
declare class NgxsmkClickOutside {
|
|
12
|
+
private readonly host;
|
|
13
|
+
readonly ngxsmkClickOutside: i0.OutputEmitterRef<Event>;
|
|
14
|
+
constructor();
|
|
15
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<NgxsmkClickOutside, never>;
|
|
16
|
+
static ɵdir: i0.ɵɵDirectiveDeclaration<NgxsmkClickOutside, "[ngxsmkClickOutside]", never, {}, { "ngxsmkClickOutside": "ngxsmkClickOutside"; }, never, never, true, never>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Selector matching elements that can receive keyboard focus. */
|
|
20
|
+
declare const FOCUSABLE_SELECTOR: string;
|
|
21
|
+
/** All keyboard-focusable elements inside `container`, in DOM order. */
|
|
22
|
+
declare function getFocusable(container: HTMLElement): HTMLElement[];
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Traps Tab/Shift+Tab focus cycling inside the host element while active.
|
|
26
|
+
* With `ngxsmkFocusTrapAutoCapture`, moves focus into the trap on init and
|
|
27
|
+
* restores the previously focused element on destroy.
|
|
28
|
+
*/
|
|
29
|
+
declare class NgxsmkFocusTrap {
|
|
30
|
+
private readonly host;
|
|
31
|
+
private readonly document;
|
|
32
|
+
/** Set to `false` to disable the trap without removing the directive. */
|
|
33
|
+
readonly ngxsmkFocusTrap: i0.InputSignalWithTransform<boolean, unknown>;
|
|
34
|
+
/** Focus the first focusable child on init; restore focus on destroy. */
|
|
35
|
+
readonly ngxsmkFocusTrapAutoCapture: i0.InputSignalWithTransform<boolean, unknown>;
|
|
36
|
+
constructor();
|
|
37
|
+
protected onKeydown(event: KeyboardEvent): void;
|
|
38
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<NgxsmkFocusTrap, never>;
|
|
39
|
+
static ɵdir: i0.ɵɵDirectiveDeclaration<NgxsmkFocusTrap, "[ngxsmkFocusTrap]", never, { "ngxsmkFocusTrap": { "alias": "ngxsmkFocusTrap"; "required": false; "isSignal": true; }; "ngxsmkFocusTrapAutoCapture": { "alias": "ngxsmkFocusTrapAutoCapture"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type AnnouncerPoliteness = 'polite' | 'assertive';
|
|
43
|
+
/**
|
|
44
|
+
* Announces messages to screen readers through a visually hidden
|
|
45
|
+
* `aria-live` region appended to the document body.
|
|
46
|
+
*/
|
|
47
|
+
declare class NgxsmkLiveAnnouncer implements OnDestroy {
|
|
48
|
+
private readonly document;
|
|
49
|
+
private region;
|
|
50
|
+
private clearTimer;
|
|
51
|
+
announce(message: string, politeness?: AnnouncerPoliteness): void;
|
|
52
|
+
ngOnDestroy(): void;
|
|
53
|
+
private ensureRegion;
|
|
54
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<NgxsmkLiveAnnouncer, never>;
|
|
55
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<NgxsmkLiveAnnouncer>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Reactive media query. Must be called in an injection context.
|
|
60
|
+
*
|
|
61
|
+
* ```ts
|
|
62
|
+
* private readonly isMobile = injectMediaQuery('(max-width: 767px)');
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
declare function injectMediaQuery(query: string): Signal<boolean>;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Locks body scrolling (for dialogs, drawers, sheets). Reference-counted so
|
|
69
|
+
* nested overlays can lock independently; compensates for scrollbar width to
|
|
70
|
+
* prevent layout shift.
|
|
71
|
+
*/
|
|
72
|
+
declare class NgxsmkScrollLock {
|
|
73
|
+
private readonly document;
|
|
74
|
+
private locks;
|
|
75
|
+
private previousOverflow;
|
|
76
|
+
private previousPaddingRight;
|
|
77
|
+
lock(): void;
|
|
78
|
+
unlock(): void;
|
|
79
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<NgxsmkScrollLock, never>;
|
|
80
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<NgxsmkScrollLock>;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Hides the host visually while keeping it available to screen readers.
|
|
85
|
+
*
|
|
86
|
+
* ```html
|
|
87
|
+
* <span ngxsmkVisuallyHidden>Opens in a new window</span>
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
declare class NgxsmkVisuallyHidden {
|
|
91
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<NgxsmkVisuallyHidden, never>;
|
|
92
|
+
static ɵdir: i0.ɵɵDirectiveDeclaration<NgxsmkVisuallyHidden, "[ngxsmkVisuallyHidden]", never, {}, {}, never, never, true, never>;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export { FOCUSABLE_SELECTOR, NgxsmkClickOutside, NgxsmkFocusTrap, NgxsmkLiveAnnouncer, NgxsmkScrollLock, NgxsmkVisuallyHidden, getFocusable, injectMediaQuery };
|
|
96
|
+
export type { AnnouncerPoliteness };
|