@radix-ng/primitives 0.40.0 → 0.42.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/dismissible-layer/README.md +1 -0
- package/dismissible-layer/index.d.ts +1 -0
- package/dismissible-layer/src/utils.d.ts +38 -0
- package/editable/README.md +1 -0
- package/editable/index.d.ts +1 -0
- package/editable/src/editable-root.d.ts +67 -0
- package/fesm2022/radix-ng-primitives-dismissible-layer.mjs +211 -0
- package/fesm2022/radix-ng-primitives-dismissible-layer.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-editable.mjs +118 -0
- package/fesm2022/radix-ng-primitives-editable.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-focus-scope.mjs +336 -0
- package/fesm2022/radix-ng-primitives-focus-scope.mjs.map +1 -0
- package/focus-scope/README.md +1 -0
- package/focus-scope/index.d.ts +1 -0
- package/focus-scope/src/focus-scope.d.ts +53 -0
- package/focus-scope/src/stack.d.ts +13 -0
- package/focus-scope/src/utils.d.ts +46 -0
- package/hover-card/src/hover-card-root.directive.d.ts +4 -4
- package/package.json +13 -1
- package/popover/src/popover-root.directive.d.ts +4 -4
- package/tooltip/src/tooltip-root.directive.d.ts +4 -4
@@ -0,0 +1 @@
|
|
1
|
+
# @radix-ng/primitives/dismissible-layer
|
@@ -0,0 +1 @@
|
|
1
|
+
export * from './src/utils';
|
@@ -0,0 +1,38 @@
|
|
1
|
+
import { BooleanInput } from '@angular/cdk/coercion';
|
2
|
+
import * as i0 from "@angular/core";
|
3
|
+
/**
|
4
|
+
* Listens for when focus happens outside a DOM subtree.
|
5
|
+
* Returns props to pass to the root (node) of the subtree we want to check.
|
6
|
+
*/
|
7
|
+
export declare class RdxFocusOutside {
|
8
|
+
#private;
|
9
|
+
readonly enabledInput: import("@angular/core").InputSignalWithTransform<boolean, BooleanInput>;
|
10
|
+
set enabled(value: boolean);
|
11
|
+
get enabled(): boolean;
|
12
|
+
readonly focusOutside: import("@angular/core").OutputEmitterRef<FocusEvent>;
|
13
|
+
private readonly isFocusInsideDOMTree;
|
14
|
+
private readonly focusCaptureHandler;
|
15
|
+
private readonly blurCaptureHandler;
|
16
|
+
constructor();
|
17
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<RdxFocusOutside, never>;
|
18
|
+
static ɵdir: i0.ɵɵDirectiveDeclaration<RdxFocusOutside, "[rdxFocusOutside]", ["rdxFocusOutside"], { "enabledInput": { "alias": "enabled"; "required": false; "isSignal": true; }; }, { "focusOutside": "focusOutside"; }, never, never, true, never>;
|
19
|
+
}
|
20
|
+
/**
|
21
|
+
* Listens for `pointerdown` outside a DOM subtree. We use `pointerdown` rather than `pointerup`
|
22
|
+
* to mimic layer dismissing behavior present in OS.
|
23
|
+
* Returns props to pass to the node we want to check for outside events.
|
24
|
+
*/
|
25
|
+
export declare class RdxPointerDownOutside {
|
26
|
+
#private;
|
27
|
+
private readonly elementRef;
|
28
|
+
readonly enabledInput: import("@angular/core").InputSignalWithTransform<boolean, BooleanInput>;
|
29
|
+
set enabled(value: boolean);
|
30
|
+
get enabled(): boolean;
|
31
|
+
readonly pointerDownOutside: import("@angular/core").OutputEmitterRef<PointerEvent>;
|
32
|
+
private readonly isPointerInsideDOMTree;
|
33
|
+
private readonly handleAndDispatchPointerDownOutsideEvent;
|
34
|
+
private handleClick;
|
35
|
+
constructor();
|
36
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<RdxPointerDownOutside, never>;
|
37
|
+
static ɵdir: i0.ɵɵDirectiveDeclaration<RdxPointerDownOutside, "[rdxPointerDownOutside]", ["rdxPointerDownOutside"], { "enabledInput": { "alias": "enabled"; "required": false; "isSignal": true; }; }, { "pointerDownOutside": "pointerDownOutside"; }, never, never, true, never>;
|
38
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
# @radix-ng/primitives/editable
|
@@ -0,0 +1 @@
|
|
1
|
+
export * from './src/editable-root';
|
@@ -0,0 +1,67 @@
|
|
1
|
+
import { BooleanInput, NumberInput } from '@angular/cdk/coercion';
|
2
|
+
import { Signal, WritableSignal } from '@angular/core';
|
3
|
+
import { RdxPointerDownOutside } from '@radix-ng/primitives/dismissible-layer';
|
4
|
+
import * as i0 from "@angular/core";
|
5
|
+
import * as i1 from "@radix-ng/primitives/dismissible-layer";
|
6
|
+
type EditableRootContext = {
|
7
|
+
disabled: Signal<boolean>;
|
8
|
+
value: Signal<string | null | undefined>;
|
9
|
+
inputValue: WritableSignal<string | undefined>;
|
10
|
+
placeholder: Signal<{
|
11
|
+
edit: string;
|
12
|
+
preview: string;
|
13
|
+
}>;
|
14
|
+
isEditing: Signal<boolean>;
|
15
|
+
submitMode: Signal<SubmitMode>;
|
16
|
+
activationMode: Signal<ActivationMode>;
|
17
|
+
edit: () => void;
|
18
|
+
cancel: () => void;
|
19
|
+
submit: () => void;
|
20
|
+
maxLength: Signal<number | undefined>;
|
21
|
+
startWithEditMode: Signal<boolean>;
|
22
|
+
isEmpty: Signal<boolean>;
|
23
|
+
readonly: Signal<boolean>;
|
24
|
+
selectOnFocus: Signal<boolean>;
|
25
|
+
autoResize: Signal<boolean>;
|
26
|
+
inputRef: WritableSignal<HTMLInputElement | undefined>;
|
27
|
+
};
|
28
|
+
export declare const injectEditableRootContext: (optional?: boolean) => EditableRootContext | null, provideEditableRootContext: (useFactory: () => EditableRootContext) => import("@angular/core").Provider;
|
29
|
+
type ActivationMode = 'focus' | 'dblclick' | 'none';
|
30
|
+
type SubmitMode = 'blur' | 'enter' | 'none' | 'both';
|
31
|
+
/**
|
32
|
+
* @group Components
|
33
|
+
*/
|
34
|
+
export declare class RdxEditableRoot {
|
35
|
+
private readonly focusOutside;
|
36
|
+
readonly pointerDownOutside: RdxPointerDownOutside;
|
37
|
+
readonly value: import("@angular/core").ModelSignal<string | undefined>;
|
38
|
+
readonly placeholder: import("@angular/core").InputSignal<string>;
|
39
|
+
readonly disabled: import("@angular/core").InputSignalWithTransform<boolean, BooleanInput>;
|
40
|
+
readonly readonly: import("@angular/core").InputSignalWithTransform<boolean, BooleanInput>;
|
41
|
+
readonly selectOnFocus: import("@angular/core").InputSignalWithTransform<boolean, BooleanInput>;
|
42
|
+
readonly submitMode: import("@angular/core").InputSignal<SubmitMode>;
|
43
|
+
readonly maxLength: import("@angular/core").InputSignalWithTransform<number | undefined, NumberInput>;
|
44
|
+
/**
|
45
|
+
* Whether to start with the edit mode active
|
46
|
+
*/
|
47
|
+
readonly startWithEditMode: import("@angular/core").InputSignalWithTransform<boolean, BooleanInput>;
|
48
|
+
readonly activationMode: import("@angular/core").InputSignal<ActivationMode>;
|
49
|
+
readonly autoResize: import("@angular/core").InputSignalWithTransform<boolean, BooleanInput>;
|
50
|
+
readonly required: import("@angular/core").InputSignalWithTransform<boolean, BooleanInput>;
|
51
|
+
readonly isEmpty: Signal<boolean>;
|
52
|
+
readonly $placeholder: Signal<string | {
|
53
|
+
edit: string;
|
54
|
+
preview: string;
|
55
|
+
}>;
|
56
|
+
readonly isEditing: WritableSignal<boolean>;
|
57
|
+
readonly inputValue: WritableSignal<string | undefined>;
|
58
|
+
readonly inputRef: WritableSignal<HTMLInputElement | undefined>;
|
59
|
+
constructor();
|
60
|
+
handleDismiss(): void;
|
61
|
+
submit(): void;
|
62
|
+
cancel(): void;
|
63
|
+
edit(): void;
|
64
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<RdxEditableRoot, never>;
|
65
|
+
static ɵdir: i0.ɵɵDirectiveDeclaration<RdxEditableRoot, "[rdxEditableRoot]", ["rdxEditableRoot"], { "value": { "alias": "value"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "selectOnFocus": { "alias": "selectOnFocus"; "required": false; "isSignal": true; }; "submitMode": { "alias": "submitMode"; "required": false; "isSignal": true; }; "maxLength": { "alias": "maxLength"; "required": false; "isSignal": true; }; "startWithEditMode": { "alias": "startWithEditMode"; "required": false; "isSignal": true; }; "activationMode": { "alias": "activationMode"; "required": false; "isSignal": true; }; "autoResize": { "alias": "autoResize"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; }, never, never, true, [{ directive: typeof i1.RdxFocusOutside; inputs: {}; outputs: {}; }, { directive: typeof i1.RdxPointerDownOutside; inputs: {}; outputs: {}; }]>;
|
66
|
+
}
|
67
|
+
export {};
|
@@ -0,0 +1,211 @@
|
|
1
|
+
import * as i0 from '@angular/core';
|
2
|
+
import { input, booleanAttribute, linkedSignal, output, signal, inject, ElementRef, effect, Directive } from '@angular/core';
|
3
|
+
|
4
|
+
function isLayerExist(layerElement, targetElement) {
|
5
|
+
const targetLayer = targetElement.closest('[data-dismissable-layer]');
|
6
|
+
const mainLayer = layerElement.dataset['dismissableLayer'] === ''
|
7
|
+
? layerElement
|
8
|
+
: layerElement.querySelector('[data-dismissable-layer]');
|
9
|
+
const nodeList = Array.from(layerElement.ownerDocument.querySelectorAll('[data-dismissable-layer]'));
|
10
|
+
if (targetLayer && (mainLayer === targetLayer || nodeList.indexOf(mainLayer) < nodeList.indexOf(targetLayer))) {
|
11
|
+
return true;
|
12
|
+
}
|
13
|
+
else {
|
14
|
+
return false;
|
15
|
+
}
|
16
|
+
}
|
17
|
+
/**
|
18
|
+
* Listens for when focus happens outside a DOM subtree.
|
19
|
+
* Returns props to pass to the root (node) of the subtree we want to check.
|
20
|
+
*/
|
21
|
+
class RdxFocusOutside {
|
22
|
+
#enabled;
|
23
|
+
set enabled(value) {
|
24
|
+
if (this.#enabled() !== value) {
|
25
|
+
this.#enabled.set(value);
|
26
|
+
}
|
27
|
+
}
|
28
|
+
get enabled() {
|
29
|
+
return this.#enabled();
|
30
|
+
}
|
31
|
+
constructor() {
|
32
|
+
this.enabledInput = input(true, { transform: booleanAttribute, alias: 'enabled' });
|
33
|
+
this.#enabled = linkedSignal(() => this.enabledInput());
|
34
|
+
this.focusOutside = output();
|
35
|
+
/*
|
36
|
+
* Flag to indicate if the focus is currently within the DOM subtree
|
37
|
+
*/
|
38
|
+
this.isFocusInsideDOMTree = signal(false);
|
39
|
+
/*
|
40
|
+
* Handles capturing the focus event to mark focus as inside the DOM subtree
|
41
|
+
*/
|
42
|
+
this.focusCaptureHandler = () => {
|
43
|
+
if (!this.enabled)
|
44
|
+
return;
|
45
|
+
this.isFocusInsideDOMTree.set(true);
|
46
|
+
};
|
47
|
+
/*
|
48
|
+
* Handles capturing the blur event to mark focus as outside the DOM subtree
|
49
|
+
*/
|
50
|
+
this.blurCaptureHandler = () => {
|
51
|
+
if (!this.enabled)
|
52
|
+
return;
|
53
|
+
this.isFocusInsideDOMTree.set(false);
|
54
|
+
};
|
55
|
+
const elementRef = inject(ElementRef);
|
56
|
+
effect((onCleanup) => {
|
57
|
+
if (!this.#enabled()) {
|
58
|
+
return;
|
59
|
+
}
|
60
|
+
const ownerDocument = elementRef.nativeElement.ownerDocument ?? globalThis.document;
|
61
|
+
const focusHandler = async (event) => {
|
62
|
+
if (!elementRef?.nativeElement) {
|
63
|
+
return;
|
64
|
+
}
|
65
|
+
await Promise.resolve();
|
66
|
+
await Promise.resolve();
|
67
|
+
const target = event.target;
|
68
|
+
if (!elementRef.nativeElement || !target || isLayerExist(elementRef.nativeElement, target)) {
|
69
|
+
return;
|
70
|
+
}
|
71
|
+
if (target && !this.isFocusInsideDOMTree()) {
|
72
|
+
this.focusOutside.emit(event);
|
73
|
+
}
|
74
|
+
};
|
75
|
+
elementRef.nativeElement.addEventListener('focus', this.focusCaptureHandler, {
|
76
|
+
capture: true
|
77
|
+
});
|
78
|
+
elementRef.nativeElement.addEventListener('blur', this.blurCaptureHandler, {
|
79
|
+
capture: true
|
80
|
+
});
|
81
|
+
ownerDocument.addEventListener('focusin', focusHandler);
|
82
|
+
onCleanup(() => {
|
83
|
+
elementRef.nativeElement.removeEventListener('focus', this.focusCaptureHandler, {
|
84
|
+
capture: true
|
85
|
+
});
|
86
|
+
elementRef.nativeElement.removeEventListener('blur', this.blurCaptureHandler, {
|
87
|
+
capture: true
|
88
|
+
});
|
89
|
+
ownerDocument.removeEventListener('focusin', focusHandler);
|
90
|
+
});
|
91
|
+
});
|
92
|
+
}
|
93
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: RdxFocusOutside, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
94
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.4", type: RdxFocusOutside, isStandalone: true, selector: "[rdxFocusOutside]", inputs: { enabledInput: { classPropertyName: "enabledInput", publicName: "enabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { focusOutside: "focusOutside" }, exportAs: ["rdxFocusOutside"], ngImport: i0 }); }
|
95
|
+
}
|
96
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: RdxFocusOutside, decorators: [{
|
97
|
+
type: Directive,
|
98
|
+
args: [{
|
99
|
+
selector: '[rdxFocusOutside]',
|
100
|
+
exportAs: 'rdxFocusOutside'
|
101
|
+
}]
|
102
|
+
}], ctorParameters: () => [] });
|
103
|
+
/**
|
104
|
+
* Listens for `pointerdown` outside a DOM subtree. We use `pointerdown` rather than `pointerup`
|
105
|
+
* to mimic layer dismissing behavior present in OS.
|
106
|
+
* Returns props to pass to the node we want to check for outside events.
|
107
|
+
*/
|
108
|
+
class RdxPointerDownOutside {
|
109
|
+
#enabled;
|
110
|
+
set enabled(value) {
|
111
|
+
if (this.#enabled() !== value) {
|
112
|
+
this.#enabled.set(value);
|
113
|
+
}
|
114
|
+
}
|
115
|
+
get enabled() {
|
116
|
+
return this.#enabled();
|
117
|
+
}
|
118
|
+
constructor() {
|
119
|
+
this.elementRef = inject(ElementRef);
|
120
|
+
this.enabledInput = input(true, { transform: booleanAttribute, alias: 'enabled' });
|
121
|
+
this.#enabled = linkedSignal(() => this.enabledInput());
|
122
|
+
this.pointerDownOutside = output();
|
123
|
+
this.isPointerInsideDOMTree = signal(false);
|
124
|
+
this.handleAndDispatchPointerDownOutsideEvent = (e) => () => this.pointerDownOutside.emit(e);
|
125
|
+
effect((onCleanup) => {
|
126
|
+
if (!this.#enabled()) {
|
127
|
+
return;
|
128
|
+
}
|
129
|
+
const ownerDocument = this.elementRef.nativeElement.ownerDocument ?? globalThis.document;
|
130
|
+
const handlePointerDown = async (event) => {
|
131
|
+
if (event.target && !this.isPointerInsideDOMTree()) {
|
132
|
+
/**
|
133
|
+
* On touch devices, we need to wait for a click event because browsers implement
|
134
|
+
* a ~350ms delay between the time the user stops touching the display and when the
|
135
|
+
* browser executres events. We need to ensure we don't reactivate pointer-events within
|
136
|
+
* this timeframe otherwise the browser may execute events that should have been prevented.
|
137
|
+
*
|
138
|
+
* Additionally, this also lets us deal automatically with cancellations when a click event
|
139
|
+
* isn't raised because the page was considered scrolled/drag-scrolled, long-pressed, etc.
|
140
|
+
*
|
141
|
+
* This is why we also continuously remove the previous listener, because we cannot be
|
142
|
+
* certain that it was raised, and therefore cleaned-up.
|
143
|
+
*/
|
144
|
+
if (event.pointerType === 'touch') {
|
145
|
+
ownerDocument.removeEventListener('click', this.handleClick);
|
146
|
+
this.handleClick = this.handleAndDispatchPointerDownOutsideEvent(event);
|
147
|
+
ownerDocument.addEventListener('click', this.handleClick, {
|
148
|
+
once: true
|
149
|
+
});
|
150
|
+
}
|
151
|
+
else {
|
152
|
+
this.pointerDownOutside.emit(event);
|
153
|
+
}
|
154
|
+
}
|
155
|
+
else {
|
156
|
+
// We need to remove the event listener in case the outside click has been canceled.
|
157
|
+
// See: https://github.com/radix-ui/primitives/issues/2171
|
158
|
+
ownerDocument.removeEventListener('click', this.handleClick);
|
159
|
+
}
|
160
|
+
this.isPointerInsideDOMTree.set(false);
|
161
|
+
};
|
162
|
+
/**
|
163
|
+
* if this directive executes in a component that mounts via a `pointerdown` event, the event
|
164
|
+
* would bubble up to the document and trigger a `pointerDownOutside` event. We avoid
|
165
|
+
* this by delaying the event listener registration on the document.
|
166
|
+
* This is not Angular specific, but rather how the DOM works, ie:
|
167
|
+
* ```
|
168
|
+
* button.addEventListener('pointerdown', () => {
|
169
|
+
* console.log('I will log');
|
170
|
+
* document.addEventListener('pointerdown', () => {
|
171
|
+
* console.log('I will also log');
|
172
|
+
* })
|
173
|
+
* });
|
174
|
+
*/
|
175
|
+
const timerId = window.setTimeout(() => {
|
176
|
+
ownerDocument.addEventListener('pointerdown', handlePointerDown);
|
177
|
+
}, 0);
|
178
|
+
const onPointerDownCapture = () => {
|
179
|
+
if (!this.enabled) {
|
180
|
+
return;
|
181
|
+
}
|
182
|
+
this.isPointerInsideDOMTree.set(true);
|
183
|
+
};
|
184
|
+
this.elementRef.nativeElement.addEventListener('pointerdown', onPointerDownCapture, { capture: true });
|
185
|
+
onCleanup(() => {
|
186
|
+
window.clearTimeout(timerId);
|
187
|
+
ownerDocument.removeEventListener('pointerdown', handlePointerDown);
|
188
|
+
ownerDocument.removeEventListener('click', this.handleClick);
|
189
|
+
this.elementRef.nativeElement.removeEventListener('pointerdown', onPointerDownCapture, {
|
190
|
+
capture: true
|
191
|
+
});
|
192
|
+
});
|
193
|
+
});
|
194
|
+
}
|
195
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: RdxPointerDownOutside, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
196
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.4", type: RdxPointerDownOutside, isStandalone: true, selector: "[rdxPointerDownOutside]", inputs: { enabledInput: { classPropertyName: "enabledInput", publicName: "enabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pointerDownOutside: "pointerDownOutside" }, exportAs: ["rdxPointerDownOutside"], ngImport: i0 }); }
|
197
|
+
}
|
198
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: RdxPointerDownOutside, decorators: [{
|
199
|
+
type: Directive,
|
200
|
+
args: [{
|
201
|
+
selector: '[rdxPointerDownOutside]',
|
202
|
+
exportAs: 'rdxPointerDownOutside'
|
203
|
+
}]
|
204
|
+
}], ctorParameters: () => [] });
|
205
|
+
|
206
|
+
/**
|
207
|
+
* Generated bundle index. Do not edit.
|
208
|
+
*/
|
209
|
+
|
210
|
+
export { RdxFocusOutside, RdxPointerDownOutside };
|
211
|
+
//# sourceMappingURL=radix-ng-primitives-dismissible-layer.mjs.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"radix-ng-primitives-dismissible-layer.mjs","sources":["../../../packages/primitives/dismissible-layer/src/utils.ts","../../../packages/primitives/dismissible-layer/radix-ng-primitives-dismissible-layer.ts"],"sourcesContent":["import { BooleanInput } from '@angular/cdk/coercion';\nimport {\n booleanAttribute,\n Directive,\n effect,\n ElementRef,\n inject,\n input,\n linkedSignal,\n output,\n signal\n} from '@angular/core';\n\nfunction isLayerExist(layerElement: HTMLElement, targetElement: HTMLElement) {\n const targetLayer = targetElement.closest('[data-dismissable-layer]');\n\n const mainLayer =\n layerElement.dataset['dismissableLayer'] === ''\n ? layerElement\n : (layerElement.querySelector('[data-dismissable-layer]') as HTMLElement);\n\n const nodeList = Array.from(layerElement.ownerDocument.querySelectorAll('[data-dismissable-layer]'));\n\n if (targetLayer && (mainLayer === targetLayer || nodeList.indexOf(mainLayer) < nodeList.indexOf(targetLayer))) {\n return true;\n } else {\n return false;\n }\n}\n\n/**\n * Listens for when focus happens outside a DOM subtree.\n * Returns props to pass to the root (node) of the subtree we want to check.\n */\n@Directive({\n selector: '[rdxFocusOutside]',\n exportAs: 'rdxFocusOutside'\n})\nexport class RdxFocusOutside {\n readonly enabledInput = input<boolean, BooleanInput>(true, { transform: booleanAttribute, alias: 'enabled' });\n\n readonly #enabled = linkedSignal(() => this.enabledInput());\n\n set enabled(value: boolean) {\n if (this.#enabled() !== value) {\n this.#enabled.set(value);\n }\n }\n\n get enabled() {\n return this.#enabled();\n }\n\n readonly focusOutside = output<FocusEvent>();\n\n /*\n * Flag to indicate if the focus is currently within the DOM subtree\n */\n private readonly isFocusInsideDOMTree = signal(false);\n\n /*\n * Handles capturing the focus event to mark focus as inside the DOM subtree\n */\n private readonly focusCaptureHandler = () => {\n if (!this.enabled) return;\n this.isFocusInsideDOMTree.set(true);\n };\n\n /*\n * Handles capturing the blur event to mark focus as outside the DOM subtree\n */\n private readonly blurCaptureHandler = () => {\n if (!this.enabled) return;\n this.isFocusInsideDOMTree.set(false);\n };\n\n constructor() {\n const elementRef = inject<ElementRef<HTMLElement>>(ElementRef);\n\n effect((onCleanup) => {\n if (!this.#enabled()) {\n return;\n }\n\n const ownerDocument = elementRef.nativeElement.ownerDocument ?? globalThis.document;\n\n const focusHandler = async (event: FocusEvent) => {\n if (!elementRef?.nativeElement) {\n return;\n }\n\n await Promise.resolve();\n await Promise.resolve();\n\n const target = event.target as HTMLElement | undefined;\n if (!elementRef.nativeElement || !target || isLayerExist(elementRef.nativeElement, target)) {\n return;\n }\n\n if (target && !this.isFocusInsideDOMTree()) {\n this.focusOutside.emit(event);\n }\n };\n\n elementRef.nativeElement.addEventListener('focus', this.focusCaptureHandler, {\n capture: true\n });\n elementRef.nativeElement.addEventListener('blur', this.blurCaptureHandler, {\n capture: true\n });\n\n ownerDocument.addEventListener('focusin', focusHandler);\n\n onCleanup(() => {\n elementRef.nativeElement.removeEventListener('focus', this.focusCaptureHandler, {\n capture: true\n });\n\n elementRef.nativeElement.removeEventListener('blur', this.blurCaptureHandler, {\n capture: true\n });\n\n ownerDocument.removeEventListener('focusin', focusHandler);\n });\n });\n }\n}\n\n/**\n * Listens for `pointerdown` outside a DOM subtree. We use `pointerdown` rather than `pointerup`\n * to mimic layer dismissing behavior present in OS.\n * Returns props to pass to the node we want to check for outside events.\n */\n@Directive({\n selector: '[rdxPointerDownOutside]',\n exportAs: 'rdxPointerDownOutside'\n})\nexport class RdxPointerDownOutside {\n private readonly elementRef = inject<ElementRef<HTMLElement>>(ElementRef);\n\n readonly enabledInput = input<boolean, BooleanInput>(true, { transform: booleanAttribute, alias: 'enabled' });\n\n readonly #enabled = linkedSignal(() => this.enabledInput());\n\n set enabled(value: boolean) {\n if (this.#enabled() !== value) {\n this.#enabled.set(value);\n }\n }\n\n get enabled() {\n return this.#enabled();\n }\n\n readonly pointerDownOutside = output<PointerEvent>();\n\n private readonly isPointerInsideDOMTree = signal(false);\n\n private readonly handleAndDispatchPointerDownOutsideEvent = (e: PointerEvent) => () =>\n this.pointerDownOutside.emit(e);\n\n private handleClick: () => void | undefined;\n\n constructor() {\n effect((onCleanup) => {\n if (!this.#enabled()) {\n return;\n }\n\n const ownerDocument = this.elementRef.nativeElement.ownerDocument ?? globalThis.document;\n\n const handlePointerDown = async (event: PointerEvent) => {\n if (event.target && !this.isPointerInsideDOMTree()) {\n /**\n * On touch devices, we need to wait for a click event because browsers implement\n * a ~350ms delay between the time the user stops touching the display and when the\n * browser executres events. We need to ensure we don't reactivate pointer-events within\n * this timeframe otherwise the browser may execute events that should have been prevented.\n *\n * Additionally, this also lets us deal automatically with cancellations when a click event\n * isn't raised because the page was considered scrolled/drag-scrolled, long-pressed, etc.\n *\n * This is why we also continuously remove the previous listener, because we cannot be\n * certain that it was raised, and therefore cleaned-up.\n */\n if (event.pointerType === 'touch') {\n ownerDocument.removeEventListener('click', this.handleClick);\n this.handleClick = this.handleAndDispatchPointerDownOutsideEvent(event);\n ownerDocument.addEventListener('click', this.handleClick, {\n once: true\n });\n } else {\n this.pointerDownOutside.emit(event);\n }\n } else {\n // We need to remove the event listener in case the outside click has been canceled.\n // See: https://github.com/radix-ui/primitives/issues/2171\n ownerDocument.removeEventListener('click', this.handleClick);\n }\n this.isPointerInsideDOMTree.set(false);\n };\n /**\n * if this directive executes in a component that mounts via a `pointerdown` event, the event\n * would bubble up to the document and trigger a `pointerDownOutside` event. We avoid\n * this by delaying the event listener registration on the document.\n * This is not Angular specific, but rather how the DOM works, ie:\n * ```\n * button.addEventListener('pointerdown', () => {\n * console.log('I will log');\n * document.addEventListener('pointerdown', () => {\n * console.log('I will also log');\n * })\n * });\n */\n const timerId = window.setTimeout(() => {\n ownerDocument.addEventListener('pointerdown', handlePointerDown);\n }, 0);\n\n const onPointerDownCapture = () => {\n if (!this.enabled) {\n return;\n }\n this.isPointerInsideDOMTree.set(true);\n };\n\n this.elementRef.nativeElement.addEventListener('pointerdown', onPointerDownCapture, { capture: true });\n\n onCleanup(() => {\n window.clearTimeout(timerId);\n ownerDocument.removeEventListener('pointerdown', handlePointerDown);\n ownerDocument.removeEventListener('click', this.handleClick);\n this.elementRef.nativeElement.removeEventListener('pointerdown', onPointerDownCapture, {\n capture: true\n });\n });\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,118 @@
|
|
1
|
+
import * as i0 from '@angular/core';
|
2
|
+
import { inject, model, input, booleanAttribute, numberAttribute, computed, signal, afterNextRender, Directive } from '@angular/core';
|
3
|
+
import { createContext, watch } from '@radix-ng/primitives/core';
|
4
|
+
import * as i1 from '@radix-ng/primitives/dismissible-layer';
|
5
|
+
import { RdxFocusOutside, RdxPointerDownOutside } from '@radix-ng/primitives/dismissible-layer';
|
6
|
+
|
7
|
+
const [injectEditableRootContext, provideEditableRootContext] = createContext('EditableRoot');
|
8
|
+
const rootContext = () => {
|
9
|
+
const context = inject(RdxEditableRoot);
|
10
|
+
return {
|
11
|
+
disabled: context.disabled,
|
12
|
+
value: context.value,
|
13
|
+
inputValue: context.inputValue,
|
14
|
+
placeholder: context.$placeholder,
|
15
|
+
isEditing: context.isEditing,
|
16
|
+
submitMode: context.submitMode,
|
17
|
+
activationMode: context.activationMode,
|
18
|
+
edit: context.edit,
|
19
|
+
cancel: context.cancel,
|
20
|
+
submit: context.submit,
|
21
|
+
maxLength: context.maxLength,
|
22
|
+
startWithEditMode: context.startWithEditMode,
|
23
|
+
isEmpty: context.isEmpty,
|
24
|
+
readonly: context.readonly,
|
25
|
+
autoResize: context.autoResize,
|
26
|
+
selectOnFocus: context.selectOnFocus,
|
27
|
+
inputRef: context.inputRef
|
28
|
+
};
|
29
|
+
};
|
30
|
+
/**
|
31
|
+
* @group Components
|
32
|
+
*/
|
33
|
+
class RdxEditableRoot {
|
34
|
+
constructor() {
|
35
|
+
this.focusOutside = inject(RdxFocusOutside);
|
36
|
+
this.pointerDownOutside = inject(RdxPointerDownOutside);
|
37
|
+
this.value = model();
|
38
|
+
this.placeholder = input('Enter text...');
|
39
|
+
this.disabled = input(false, { transform: booleanAttribute });
|
40
|
+
this.readonly = input(false, { transform: booleanAttribute });
|
41
|
+
this.selectOnFocus = input(false, { transform: booleanAttribute });
|
42
|
+
this.submitMode = input('blur');
|
43
|
+
this.maxLength = input(undefined, { transform: numberAttribute });
|
44
|
+
/**
|
45
|
+
* Whether to start with the edit mode active
|
46
|
+
*/
|
47
|
+
this.startWithEditMode = input(false, { transform: booleanAttribute });
|
48
|
+
this.activationMode = input('focus');
|
49
|
+
this.autoResize = input(false, { transform: booleanAttribute });
|
50
|
+
this.required = input(false, { transform: booleanAttribute });
|
51
|
+
this.isEmpty = computed(() => this.value() === '');
|
52
|
+
this.$placeholder = computed(() => {
|
53
|
+
return typeof this.placeholder() === 'string'
|
54
|
+
? { edit: this.placeholder(), preview: this.placeholder() }
|
55
|
+
: this.placeholder();
|
56
|
+
});
|
57
|
+
this.isEditing = signal(false);
|
58
|
+
this.inputValue = signal(this.value());
|
59
|
+
this.inputRef = signal(undefined);
|
60
|
+
watch([this.value], ([value]) => {
|
61
|
+
if (value) {
|
62
|
+
this.inputValue.set(this.value());
|
63
|
+
}
|
64
|
+
});
|
65
|
+
watch([this.isEditing], ([value]) => {
|
66
|
+
this.pointerDownOutside.enabled = value;
|
67
|
+
this.focusOutside.enabled = value;
|
68
|
+
});
|
69
|
+
this.pointerDownOutside.pointerDownOutside.subscribe(() => this.handleDismiss());
|
70
|
+
this.focusOutside.focusOutside.subscribe(() => this.handleDismiss());
|
71
|
+
afterNextRender(() => {
|
72
|
+
this.isEditing.set(this.startWithEditMode() ?? false);
|
73
|
+
this.inputValue.set(this.value());
|
74
|
+
});
|
75
|
+
}
|
76
|
+
handleDismiss() {
|
77
|
+
if (this.isEditing()) {
|
78
|
+
if (this.submitMode() === 'blur' || this.submitMode() === 'both') {
|
79
|
+
this.submit();
|
80
|
+
}
|
81
|
+
else {
|
82
|
+
this.cancel();
|
83
|
+
}
|
84
|
+
}
|
85
|
+
}
|
86
|
+
submit() {
|
87
|
+
this.value.set(this.inputValue());
|
88
|
+
this.isEditing.set(false);
|
89
|
+
}
|
90
|
+
cancel() {
|
91
|
+
this.isEditing.set(false);
|
92
|
+
}
|
93
|
+
edit() {
|
94
|
+
this.isEditing.set(true);
|
95
|
+
this.inputValue.set(this.value());
|
96
|
+
}
|
97
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: RdxEditableRoot, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
98
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.4", type: RdxEditableRoot, isStandalone: true, selector: "[rdxEditableRoot]", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, selectOnFocus: { classPropertyName: "selectOnFocus", publicName: "selectOnFocus", isSignal: true, isRequired: false, transformFunction: null }, submitMode: { classPropertyName: "submitMode", publicName: "submitMode", isSignal: true, isRequired: false, transformFunction: null }, maxLength: { classPropertyName: "maxLength", publicName: "maxLength", isSignal: true, isRequired: false, transformFunction: null }, startWithEditMode: { classPropertyName: "startWithEditMode", publicName: "startWithEditMode", isSignal: true, isRequired: false, transformFunction: null }, activationMode: { classPropertyName: "activationMode", publicName: "activationMode", isSignal: true, isRequired: false, transformFunction: null }, autoResize: { classPropertyName: "autoResize", publicName: "autoResize", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { properties: { "attr.data-dismissable-layer": "\"\"" } }, providers: [provideEditableRootContext(rootContext)], exportAs: ["rdxEditableRoot"], hostDirectives: [{ directive: i1.RdxFocusOutside }, { directive: i1.RdxPointerDownOutside }], ngImport: i0 }); }
|
99
|
+
}
|
100
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: RdxEditableRoot, decorators: [{
|
101
|
+
type: Directive,
|
102
|
+
args: [{
|
103
|
+
selector: '[rdxEditableRoot]',
|
104
|
+
exportAs: 'rdxEditableRoot',
|
105
|
+
providers: [provideEditableRootContext(rootContext)],
|
106
|
+
hostDirectives: [RdxFocusOutside, RdxPointerDownOutside],
|
107
|
+
host: {
|
108
|
+
'[attr.data-dismissable-layer]': '""'
|
109
|
+
}
|
110
|
+
}]
|
111
|
+
}], ctorParameters: () => [] });
|
112
|
+
|
113
|
+
/**
|
114
|
+
* Generated bundle index. Do not edit.
|
115
|
+
*/
|
116
|
+
|
117
|
+
export { RdxEditableRoot, injectEditableRootContext, provideEditableRootContext };
|
118
|
+
//# sourceMappingURL=radix-ng-primitives-editable.mjs.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"radix-ng-primitives-editable.mjs","sources":["../../../packages/primitives/editable/src/editable-root.ts","../../../packages/primitives/editable/radix-ng-primitives-editable.ts"],"sourcesContent":["import { BooleanInput, NumberInput } from '@angular/cdk/coercion';\nimport {\n afterNextRender,\n booleanAttribute,\n computed,\n Directive,\n inject,\n input,\n model,\n numberAttribute,\n Signal,\n signal,\n WritableSignal\n} from '@angular/core';\nimport { createContext, watch } from '@radix-ng/primitives/core';\nimport { RdxFocusOutside, RdxPointerDownOutside } from '@radix-ng/primitives/dismissible-layer';\n\ntype EditableRootContext = {\n disabled: Signal<boolean>;\n value: Signal<string | null | undefined>;\n inputValue: WritableSignal<string | undefined>;\n placeholder: Signal<{ edit: string; preview: string }>;\n isEditing: Signal<boolean>;\n submitMode: Signal<SubmitMode>;\n activationMode: Signal<ActivationMode>;\n edit: () => void;\n cancel: () => void;\n submit: () => void;\n maxLength: Signal<number | undefined>;\n startWithEditMode: Signal<boolean>;\n isEmpty: Signal<boolean>;\n readonly: Signal<boolean>;\n selectOnFocus: Signal<boolean>;\n autoResize: Signal<boolean>;\n inputRef: WritableSignal<HTMLInputElement | undefined>;\n};\n\nexport const [injectEditableRootContext, provideEditableRootContext] =\n createContext<EditableRootContext>('EditableRoot');\n\nconst rootContext = (): EditableRootContext => {\n const context = inject(RdxEditableRoot);\n return {\n disabled: context.disabled,\n value: context.value,\n inputValue: context.inputValue,\n placeholder: context.$placeholder as Signal<{ edit: string; preview: string }>,\n isEditing: context.isEditing,\n submitMode: context.submitMode,\n activationMode: context.activationMode,\n edit: context.edit,\n cancel: context.cancel,\n submit: context.submit,\n maxLength: context.maxLength,\n startWithEditMode: context.startWithEditMode,\n isEmpty: context.isEmpty,\n readonly: context.readonly,\n autoResize: context.autoResize,\n selectOnFocus: context.selectOnFocus,\n inputRef: context.inputRef\n };\n};\n\ntype ActivationMode = 'focus' | 'dblclick' | 'none';\ntype SubmitMode = 'blur' | 'enter' | 'none' | 'both';\n\n/**\n * @group Components\n */\n@Directive({\n selector: '[rdxEditableRoot]',\n exportAs: 'rdxEditableRoot',\n providers: [provideEditableRootContext(rootContext)],\n hostDirectives: [RdxFocusOutside, RdxPointerDownOutside],\n host: {\n '[attr.data-dismissable-layer]': '\"\"'\n }\n})\nexport class RdxEditableRoot {\n private readonly focusOutside = inject(RdxFocusOutside);\n readonly pointerDownOutside = inject(RdxPointerDownOutside);\n\n readonly value = model<string>();\n\n readonly placeholder = input<string>('Enter text...');\n\n readonly disabled = input<boolean, BooleanInput>(false, { transform: booleanAttribute });\n\n readonly readonly = input<boolean, BooleanInput>(false, { transform: booleanAttribute });\n\n readonly selectOnFocus = input<boolean, BooleanInput>(false, { transform: booleanAttribute });\n\n readonly submitMode = input<SubmitMode>('blur');\n\n readonly maxLength = input<number, NumberInput>(undefined, { transform: numberAttribute });\n\n /**\n * Whether to start with the edit mode active\n */\n readonly startWithEditMode = input<boolean, BooleanInput>(false, { transform: booleanAttribute });\n\n readonly activationMode = input<ActivationMode>('focus');\n\n readonly autoResize = input<boolean, BooleanInput>(false, { transform: booleanAttribute });\n\n readonly required = input<boolean, BooleanInput>(false, { transform: booleanAttribute });\n\n readonly isEmpty = computed(() => this.value() === '');\n\n readonly $placeholder = computed(() => {\n return typeof this.placeholder() === 'string'\n ? { edit: this.placeholder(), preview: this.placeholder() }\n : this.placeholder();\n });\n\n readonly isEditing = signal(false);\n\n readonly inputValue = signal(this.value());\n\n readonly inputRef = signal<HTMLInputElement | undefined>(undefined);\n\n constructor() {\n watch([this.value], ([value]) => {\n if (value) {\n this.inputValue.set(this.value());\n }\n });\n\n watch([this.isEditing], ([value]) => {\n this.pointerDownOutside.enabled = value;\n this.focusOutside.enabled = value;\n });\n\n this.pointerDownOutside.pointerDownOutside.subscribe(() => this.handleDismiss());\n this.focusOutside.focusOutside.subscribe(() => this.handleDismiss());\n\n afterNextRender(() => {\n this.isEditing.set(this.startWithEditMode() ?? false);\n this.inputValue.set(this.value());\n });\n }\n\n handleDismiss() {\n if (this.isEditing()) {\n if (this.submitMode() === 'blur' || this.submitMode() === 'both') {\n this.submit();\n } else {\n this.cancel();\n }\n }\n }\n\n submit() {\n this.value.set(this.inputValue());\n this.isEditing.set(false);\n }\n\n cancel() {\n this.isEditing.set(false);\n }\n\n edit() {\n this.isEditing.set(true);\n this.inputValue.set(this.value());\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,336 @@
|
|
1
|
+
import * as i0 from '@angular/core';
|
2
|
+
import { signal, inject, Injector, ElementRef, input, booleanAttribute, output, afterNextRender, effect, Directive } from '@angular/core';
|
3
|
+
import { getActiveElement, createContext } from '@radix-ng/primitives/core';
|
4
|
+
|
5
|
+
function createGlobalState(factory) {
|
6
|
+
const state = factory();
|
7
|
+
return () => state;
|
8
|
+
}
|
9
|
+
const useFocusStackState = createGlobalState(() => signal([]));
|
10
|
+
function createFocusScopesStack() {
|
11
|
+
/** A stack of focus scopes, with the active one at the top */
|
12
|
+
const stack = useFocusStackState();
|
13
|
+
return {
|
14
|
+
add(focusScope) {
|
15
|
+
const current = stack();
|
16
|
+
const active = current[0];
|
17
|
+
if (focusScope !== active) {
|
18
|
+
active?.pause();
|
19
|
+
}
|
20
|
+
const updated = arrayRemove(current, focusScope);
|
21
|
+
updated.unshift(focusScope);
|
22
|
+
stack.set(updated);
|
23
|
+
},
|
24
|
+
remove(focusScope) {
|
25
|
+
const current = stack();
|
26
|
+
const updated = arrayRemove(current, focusScope);
|
27
|
+
stack.set(updated);
|
28
|
+
// после удаления «возобновляем» новый верхний
|
29
|
+
stack()[0]?.resume();
|
30
|
+
}
|
31
|
+
};
|
32
|
+
}
|
33
|
+
function arrayRemove(array, item) {
|
34
|
+
const copy = [...array];
|
35
|
+
const idx = copy.indexOf(item);
|
36
|
+
if (idx !== -1) {
|
37
|
+
copy.splice(idx, 1);
|
38
|
+
}
|
39
|
+
return copy;
|
40
|
+
}
|
41
|
+
function removeLinks(items) {
|
42
|
+
return items.filter((el) => el.tagName !== 'A');
|
43
|
+
}
|
44
|
+
|
45
|
+
const AUTOFOCUS_ON_MOUNT = 'focusScope.autoFocusOnMount';
|
46
|
+
const AUTOFOCUS_ON_UNMOUNT = 'focusScope.autoFocusOnUnmount';
|
47
|
+
const EVENT_OPTIONS = { bubbles: false, cancelable: true };
|
48
|
+
/**
|
49
|
+
* Attempts focusing the first element in a list of candidates.
|
50
|
+
* Stops when focus has actually moved.
|
51
|
+
*/
|
52
|
+
function focusFirst(candidates, { select = false } = {}) {
|
53
|
+
const previouslyFocusedElement = getActiveElement();
|
54
|
+
for (const candidate of candidates) {
|
55
|
+
focus(candidate, { select });
|
56
|
+
if (getActiveElement() !== previouslyFocusedElement)
|
57
|
+
return true;
|
58
|
+
}
|
59
|
+
return;
|
60
|
+
}
|
61
|
+
/**
|
62
|
+
* Returns a list of potential tabbable candidates.
|
63
|
+
*
|
64
|
+
* NOTE: This is only a close approximation. For example it doesn't take into account cases like when
|
65
|
+
* elements are not visible. This cannot be worked out easily by just reading a property, but rather
|
66
|
+
* necessitate runtime knowledge (computed styles, etc). We deal with these cases separately.
|
67
|
+
*
|
68
|
+
* See: https://developer.mozilla.org/en-US/docs/Web/API/TreeWalker
|
69
|
+
* Credit: https://github.com/discord/focus-layers/blob/master/src/util/wrapFocus.tsx#L1
|
70
|
+
*/
|
71
|
+
function getTabbableCandidates(container) {
|
72
|
+
const nodes = [];
|
73
|
+
const walker = document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT, {
|
74
|
+
acceptNode: (node) => {
|
75
|
+
const isHiddenInput = node.tagName === 'INPUT' && node.type === 'hidden';
|
76
|
+
if (node.disabled || node.hidden || isHiddenInput)
|
77
|
+
return NodeFilter.FILTER_SKIP;
|
78
|
+
// `.tabIndex` is not the same as the `tabindex` attribute. It works on the
|
79
|
+
// runtime's understanding of tabbability, so this automatically accounts
|
80
|
+
// for any kind of element that could be tabbed to.
|
81
|
+
return node.tabIndex >= 0 ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
|
82
|
+
}
|
83
|
+
});
|
84
|
+
while (walker.nextNode())
|
85
|
+
nodes.push(walker.currentNode);
|
86
|
+
// we do not take into account the order of nodes with positive `tabIndex` as it
|
87
|
+
// hinders accessibility to have tab order different from visual order.
|
88
|
+
return nodes;
|
89
|
+
}
|
90
|
+
function isHidden(node, { upTo }) {
|
91
|
+
if (getComputedStyle(node).visibility === 'hidden')
|
92
|
+
return true;
|
93
|
+
while (node) {
|
94
|
+
// we stop at `upTo` (excluding it)
|
95
|
+
if (upTo !== undefined && node === upTo)
|
96
|
+
return false;
|
97
|
+
if (getComputedStyle(node).display === 'none')
|
98
|
+
return true;
|
99
|
+
node = node.parentElement;
|
100
|
+
}
|
101
|
+
return false;
|
102
|
+
}
|
103
|
+
/**
|
104
|
+
* Returns the first visible element in a list.
|
105
|
+
* NOTE: Only checks visibility up to the `container`.
|
106
|
+
*/
|
107
|
+
function findVisible(elements, container) {
|
108
|
+
for (const element of elements) {
|
109
|
+
// we stop checking if it's hidden at the `container` level (excluding)
|
110
|
+
if (!isHidden(element, { upTo: container }))
|
111
|
+
return element;
|
112
|
+
}
|
113
|
+
return undefined;
|
114
|
+
}
|
115
|
+
/**
|
116
|
+
* Returns the first and last tabbable elements inside a container.
|
117
|
+
*/
|
118
|
+
function getTabbableEdges(container) {
|
119
|
+
const candidates = getTabbableCandidates(container);
|
120
|
+
const first = findVisible(candidates, container);
|
121
|
+
const last = findVisible(candidates.reverse(), container);
|
122
|
+
return [first, last];
|
123
|
+
}
|
124
|
+
function isSelectableInput(element) {
|
125
|
+
return element instanceof HTMLInputElement && 'select' in element;
|
126
|
+
}
|
127
|
+
function focus(element, { select = false } = {}) {
|
128
|
+
// only focus if that element is focusable
|
129
|
+
if (element && element.focus) {
|
130
|
+
const previouslyFocusedElement = getActiveElement();
|
131
|
+
// NOTE: we prevent scrolling on focus, to minimize jarring transitions for users
|
132
|
+
element.focus({ preventScroll: true });
|
133
|
+
// only select if its not the same element, it supports selection and we need to select
|
134
|
+
if (element !== previouslyFocusedElement && isSelectableInput(element) && select) {
|
135
|
+
element.select();
|
136
|
+
}
|
137
|
+
}
|
138
|
+
}
|
139
|
+
|
140
|
+
const [injectFocusScopeContext, provideFocusScopeContext] = createContext('FocusScope Context');
|
141
|
+
const rootContext = () => {
|
142
|
+
const context = inject(RdxFocusScope);
|
143
|
+
return {
|
144
|
+
loop: context.loop,
|
145
|
+
trapped: context.trapped
|
146
|
+
};
|
147
|
+
};
|
148
|
+
/**
|
149
|
+
* @group Components
|
150
|
+
*/
|
151
|
+
class RdxFocusScope {
|
152
|
+
constructor() {
|
153
|
+
this.injector = inject(Injector);
|
154
|
+
this.elementRef = inject(ElementRef);
|
155
|
+
/**
|
156
|
+
* When `true`, tabbing from last item will focus first tabbable
|
157
|
+
* and shift+tab from first item will focus last tababble.
|
158
|
+
*
|
159
|
+
* @group Props
|
160
|
+
* @defaultValue false
|
161
|
+
*/
|
162
|
+
this.loop = input(false, { transform: booleanAttribute });
|
163
|
+
/**
|
164
|
+
* When `true`, focus cannot escape the focus scope via keyboard,
|
165
|
+
* pointer, or a programmatic focus.
|
166
|
+
*
|
167
|
+
* @group Props
|
168
|
+
* @defaultValue false
|
169
|
+
*/
|
170
|
+
this.trapped = input(false, { transform: booleanAttribute });
|
171
|
+
/**
|
172
|
+
* Event handler called when auto-focusing on mount.
|
173
|
+
* Can be prevented.
|
174
|
+
*
|
175
|
+
* @group Emits
|
176
|
+
*/
|
177
|
+
this.mountAutoFocus = output();
|
178
|
+
/**
|
179
|
+
* Event handler called when auto-focusing on unmount.
|
180
|
+
* Can be prevented.
|
181
|
+
*
|
182
|
+
* @group Emits
|
183
|
+
*/
|
184
|
+
this.unmountAutoFocus = output();
|
185
|
+
this.lastFocusedElement = signal(null);
|
186
|
+
this.focusScopesStack = createFocusScopesStack();
|
187
|
+
this.focusScope = {
|
188
|
+
paused: signal(false),
|
189
|
+
pause: () => this.focusScope.paused.set(true),
|
190
|
+
resume: () => this.focusScope.paused.set(false)
|
191
|
+
};
|
192
|
+
afterNextRender(() => {
|
193
|
+
effect((onCleanup) => {
|
194
|
+
const container = this.elementRef.nativeElement;
|
195
|
+
if (this.trapped()) {
|
196
|
+
const handleFocusIn = (event) => {
|
197
|
+
if (this.focusScope.paused() || !container) {
|
198
|
+
return;
|
199
|
+
}
|
200
|
+
const target = event.target;
|
201
|
+
if (this.elementRef.nativeElement.contains(target)) {
|
202
|
+
this.lastFocusedElement.set(target);
|
203
|
+
}
|
204
|
+
else {
|
205
|
+
focus(this.lastFocusedElement(), { select: true });
|
206
|
+
}
|
207
|
+
};
|
208
|
+
const handleFocusOut = (event) => {
|
209
|
+
if (this.focusScope.paused() || !container) {
|
210
|
+
return;
|
211
|
+
}
|
212
|
+
const relatedTarget = event.relatedTarget;
|
213
|
+
// A `focusout` event with a `null` `relatedTarget` will happen in at least two cases:
|
214
|
+
//
|
215
|
+
// 1. When the user switches app/tabs/windows/the browser itself loses focus.
|
216
|
+
// 2. In Google Chrome, when the focused element is removed from the DOM.
|
217
|
+
//
|
218
|
+
// We let the browser do its thing here because:
|
219
|
+
//
|
220
|
+
// 1. The browser already keeps a memory of what's focused for when the page gets refocused.
|
221
|
+
// 2. In Google Chrome, if we try to focus the deleted focused element (as per below), it
|
222
|
+
// throws the CPU to 100%, so we avoid doing anything for this reason here too.
|
223
|
+
if (relatedTarget === null)
|
224
|
+
return;
|
225
|
+
// If the focus has moved to an actual legitimate element (`relatedTarget !== null`)
|
226
|
+
// that is outside the container, we move focus to the last valid focused element inside.
|
227
|
+
if (!container.contains(relatedTarget)) {
|
228
|
+
focus(this.lastFocusedElement(), { select: true });
|
229
|
+
}
|
230
|
+
};
|
231
|
+
const handleMutations = () => {
|
232
|
+
const isLastFocusedElementExist = container.contains(this.lastFocusedElement());
|
233
|
+
if (!isLastFocusedElementExist) {
|
234
|
+
focus(container);
|
235
|
+
}
|
236
|
+
};
|
237
|
+
const mutationObserver = new MutationObserver(handleMutations);
|
238
|
+
if (container) {
|
239
|
+
mutationObserver.observe(container, { childList: true, subtree: true });
|
240
|
+
}
|
241
|
+
document.addEventListener('focusin', handleFocusIn);
|
242
|
+
document.addEventListener('focusout', handleFocusOut);
|
243
|
+
onCleanup(() => {
|
244
|
+
document.removeEventListener('focusin', handleFocusIn);
|
245
|
+
document.removeEventListener('focusout', handleFocusOut);
|
246
|
+
mutationObserver.disconnect();
|
247
|
+
});
|
248
|
+
}
|
249
|
+
}, { injector: this.injector });
|
250
|
+
effect(async (onCleanup) => {
|
251
|
+
const container = this.elementRef.nativeElement;
|
252
|
+
await Promise.resolve();
|
253
|
+
if (!container) {
|
254
|
+
return;
|
255
|
+
}
|
256
|
+
this.focusScopesStack.add(this.focusScope);
|
257
|
+
const previouslyFocusedElement = getActiveElement();
|
258
|
+
const hasFocusedCandidate = container.contains(previouslyFocusedElement);
|
259
|
+
if (!hasFocusedCandidate) {
|
260
|
+
const mountEvent = new CustomEvent(AUTOFOCUS_ON_MOUNT, EVENT_OPTIONS);
|
261
|
+
container.addEventListener(AUTOFOCUS_ON_MOUNT, (ev) => this.mountAutoFocus.emit(ev));
|
262
|
+
container.dispatchEvent(mountEvent);
|
263
|
+
if (!mountEvent.defaultPrevented) {
|
264
|
+
focusFirst(removeLinks(getTabbableCandidates(container)), {
|
265
|
+
select: true
|
266
|
+
});
|
267
|
+
if (getActiveElement() === previouslyFocusedElement)
|
268
|
+
focus(container);
|
269
|
+
}
|
270
|
+
}
|
271
|
+
onCleanup(() => {
|
272
|
+
container.removeEventListener(AUTOFOCUS_ON_MOUNT, (ev) => this.mountAutoFocus.emit(ev));
|
273
|
+
const unmountEvent = new CustomEvent(AUTOFOCUS_ON_UNMOUNT, EVENT_OPTIONS);
|
274
|
+
const unmountEventHandler = (ev) => this.unmountAutoFocus.emit(ev);
|
275
|
+
container.addEventListener(AUTOFOCUS_ON_UNMOUNT, unmountEventHandler);
|
276
|
+
container.dispatchEvent(unmountEvent);
|
277
|
+
setTimeout(() => {
|
278
|
+
if (!unmountEvent.defaultPrevented)
|
279
|
+
focus(previouslyFocusedElement ?? document.body, { select: true });
|
280
|
+
// we need to remove the listener after we `dispatchEvent`
|
281
|
+
container.removeEventListener(AUTOFOCUS_ON_UNMOUNT, unmountEventHandler);
|
282
|
+
this.focusScopesStack.remove(this.focusScope);
|
283
|
+
}, 0);
|
284
|
+
});
|
285
|
+
}, { injector: this.injector });
|
286
|
+
});
|
287
|
+
}
|
288
|
+
handleKeyDown(event) {
|
289
|
+
const isTabKey = event.key === 'Tab' && !event.altKey && !event.ctrlKey && !event.metaKey;
|
290
|
+
const focusedElement = getActiveElement();
|
291
|
+
if (isTabKey && focusedElement) {
|
292
|
+
const container = event.currentTarget;
|
293
|
+
const [first, last] = getTabbableEdges(container);
|
294
|
+
const hasTabbableElementsInside = first && last;
|
295
|
+
// we can only wrap focus if we have tabbable edges
|
296
|
+
if (!hasTabbableElementsInside) {
|
297
|
+
if (focusedElement === container)
|
298
|
+
event.preventDefault();
|
299
|
+
}
|
300
|
+
else {
|
301
|
+
if (!event.shiftKey && focusedElement === last) {
|
302
|
+
event.preventDefault();
|
303
|
+
if (this.loop()) {
|
304
|
+
focus(first, { select: true });
|
305
|
+
}
|
306
|
+
}
|
307
|
+
else if (event.shiftKey && focusedElement === first) {
|
308
|
+
event.preventDefault();
|
309
|
+
if (this.loop()) {
|
310
|
+
focus(last, { select: true });
|
311
|
+
}
|
312
|
+
}
|
313
|
+
}
|
314
|
+
}
|
315
|
+
}
|
316
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: RdxFocusScope, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
317
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.4", type: RdxFocusScope, isStandalone: true, selector: "[rdxFocusScope]", inputs: { loop: { classPropertyName: "loop", publicName: "loop", isSignal: true, isRequired: false, transformFunction: null }, trapped: { classPropertyName: "trapped", publicName: "trapped", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { mountAutoFocus: "mountAutoFocus", unmountAutoFocus: "unmountAutoFocus" }, host: { attributes: { "tabindex": "-1" }, listeners: { "keydown": "handleKeyDown($event)" } }, providers: [provideFocusScopeContext(rootContext)], ngImport: i0 }); }
|
318
|
+
}
|
319
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: RdxFocusScope, decorators: [{
|
320
|
+
type: Directive,
|
321
|
+
args: [{
|
322
|
+
selector: '[rdxFocusScope]',
|
323
|
+
providers: [provideFocusScopeContext(rootContext)],
|
324
|
+
host: {
|
325
|
+
tabindex: '-1',
|
326
|
+
'(keydown)': 'handleKeyDown($event)'
|
327
|
+
}
|
328
|
+
}]
|
329
|
+
}], ctorParameters: () => [] });
|
330
|
+
|
331
|
+
/**
|
332
|
+
* Generated bundle index. Do not edit.
|
333
|
+
*/
|
334
|
+
|
335
|
+
export { RdxFocusScope, injectFocusScopeContext, provideFocusScopeContext };
|
336
|
+
//# sourceMappingURL=radix-ng-primitives-focus-scope.mjs.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"radix-ng-primitives-focus-scope.mjs","sources":["../../../packages/primitives/focus-scope/src/stack.ts","../../../packages/primitives/focus-scope/src/utils.ts","../../../packages/primitives/focus-scope/src/focus-scope.ts","../../../packages/primitives/focus-scope/radix-ng-primitives-focus-scope.ts"],"sourcesContent":["import { signal, WritableSignal } from '@angular/core';\n\nexport function createGlobalState<T>(factory: () => T): () => T {\n const state = factory();\n return () => state;\n}\n\nexport interface FocusScopeAPI {\n paused: WritableSignal<boolean>;\n pause(): void;\n resume(): void;\n}\n\nconst useFocusStackState = createGlobalState(() => signal<FocusScopeAPI[]>([]));\n\nexport function createFocusScopesStack() {\n /** A stack of focus scopes, with the active one at the top */\n const stack = useFocusStackState();\n\n return {\n add(focusScope: FocusScopeAPI) {\n const current = stack();\n const active = current[0];\n if (focusScope !== active) {\n active?.pause();\n }\n const updated = arrayRemove(current, focusScope);\n updated.unshift(focusScope);\n stack.set(updated);\n },\n\n remove(focusScope: FocusScopeAPI) {\n const current = stack();\n const updated = arrayRemove(current, focusScope);\n stack.set(updated);\n // после удаления «возобновляем» новый верхний\n stack()[0]?.resume();\n }\n };\n}\n\nexport function arrayRemove<T>(array: T[], item: T): T[] {\n const copy = [...array];\n const idx = copy.indexOf(item);\n if (idx !== -1) {\n copy.splice(idx, 1);\n }\n return copy;\n}\n\nexport function removeLinks(items: HTMLElement[]): HTMLElement[] {\n return items.filter((el) => el.tagName !== 'A');\n}\n","import { getActiveElement } from '@radix-ng/primitives/core';\n\nexport const AUTOFOCUS_ON_MOUNT = 'focusScope.autoFocusOnMount';\nexport const AUTOFOCUS_ON_UNMOUNT = 'focusScope.autoFocusOnUnmount';\nexport const EVENT_OPTIONS = { bubbles: false, cancelable: true };\n\ntype FocusableTarget = HTMLElement | { focus: () => void };\n\n/**\n * Attempts focusing the first element in a list of candidates.\n * Stops when focus has actually moved.\n */\nexport function focusFirst(candidates: HTMLElement[], { select = false } = {}) {\n const previouslyFocusedElement = getActiveElement();\n for (const candidate of candidates) {\n focus(candidate, { select });\n if (getActiveElement() !== previouslyFocusedElement) return true;\n }\n\n return;\n}\n\n/**\n * Returns a list of potential tabbable candidates.\n *\n * NOTE: This is only a close approximation. For example it doesn't take into account cases like when\n * elements are not visible. This cannot be worked out easily by just reading a property, but rather\n * necessitate runtime knowledge (computed styles, etc). We deal with these cases separately.\n *\n * See: https://developer.mozilla.org/en-US/docs/Web/API/TreeWalker\n * Credit: https://github.com/discord/focus-layers/blob/master/src/util/wrapFocus.tsx#L1\n */\nexport function getTabbableCandidates(container: HTMLElement) {\n const nodes: HTMLElement[] = [];\n const walker = document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT, {\n acceptNode: (node: any) => {\n const isHiddenInput = node.tagName === 'INPUT' && node.type === 'hidden';\n if (node.disabled || node.hidden || isHiddenInput) return NodeFilter.FILTER_SKIP;\n // `.tabIndex` is not the same as the `tabindex` attribute. It works on the\n // runtime's understanding of tabbability, so this automatically accounts\n // for any kind of element that could be tabbed to.\n return node.tabIndex >= 0 ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;\n }\n });\n while (walker.nextNode()) nodes.push(walker.currentNode as HTMLElement);\n // we do not take into account the order of nodes with positive `tabIndex` as it\n // hinders accessibility to have tab order different from visual order.\n return nodes;\n}\n\nexport function isHidden(node: HTMLElement, { upTo }: { upTo?: HTMLElement }) {\n if (getComputedStyle(node).visibility === 'hidden') return true;\n while (node) {\n // we stop at `upTo` (excluding it)\n if (upTo !== undefined && node === upTo) return false;\n if (getComputedStyle(node).display === 'none') return true;\n node = node.parentElement as HTMLElement;\n }\n return false;\n}\n\n/**\n * Returns the first visible element in a list.\n * NOTE: Only checks visibility up to the `container`.\n */\nexport function findVisible(elements: HTMLElement[], container: HTMLElement): HTMLElement | undefined {\n for (const element of elements) {\n // we stop checking if it's hidden at the `container` level (excluding)\n if (!isHidden(element, { upTo: container })) return element;\n }\n return undefined;\n}\n\n/**\n * Returns the first and last tabbable elements inside a container.\n */\nexport function getTabbableEdges(container: HTMLElement) {\n const candidates = getTabbableCandidates(container);\n const first = findVisible(candidates, container);\n const last = findVisible(candidates.reverse(), container);\n return [first, last] as const;\n}\n\nexport function isSelectableInput(element: any): element is FocusableTarget & { select: () => void } {\n return element instanceof HTMLInputElement && 'select' in element;\n}\n\nexport function focus(element?: FocusableTarget | null, { select = false } = {}) {\n // only focus if that element is focusable\n if (element && element.focus) {\n const previouslyFocusedElement = getActiveElement();\n // NOTE: we prevent scrolling on focus, to minimize jarring transitions for users\n element.focus({ preventScroll: true });\n // only select if its not the same element, it supports selection and we need to select\n if (element !== previouslyFocusedElement && isSelectableInput(element) && select) {\n element.select();\n }\n }\n}\n","import { BooleanInput } from '@angular/cdk/coercion';\nimport {\n afterNextRender,\n booleanAttribute,\n Directive,\n effect,\n ElementRef,\n inject,\n Injector,\n input,\n output,\n Signal,\n signal\n} from '@angular/core';\nimport { createContext, getActiveElement } from '@radix-ng/primitives/core';\nimport { createFocusScopesStack, FocusScopeAPI, removeLinks } from './stack';\nimport {\n AUTOFOCUS_ON_MOUNT,\n AUTOFOCUS_ON_UNMOUNT,\n EVENT_OPTIONS,\n focus,\n focusFirst,\n getTabbableCandidates,\n getTabbableEdges\n} from './utils';\n\nexport interface FocusScopeContext {\n loop?: Signal<boolean>;\n\n trapped?: Signal<boolean>;\n}\n\nexport const [injectFocusScopeContext, provideFocusScopeContext] =\n createContext<FocusScopeContext>('FocusScope Context');\n\nconst rootContext = (): FocusScopeContext => {\n const context = inject(RdxFocusScope);\n\n return {\n loop: context.loop,\n trapped: context.trapped\n };\n};\n\n/**\n * @group Components\n */\n@Directive({\n selector: '[rdxFocusScope]',\n providers: [provideFocusScopeContext(rootContext)],\n host: {\n tabindex: '-1',\n '(keydown)': 'handleKeyDown($event)'\n }\n})\nexport class RdxFocusScope {\n private readonly injector = inject(Injector);\n\n private readonly elementRef = inject<ElementRef<HTMLElement>>(ElementRef);\n\n /**\n * When `true`, tabbing from last item will focus first tabbable\n * and shift+tab from first item will focus last tababble.\n *\n * @group Props\n * @defaultValue false\n */\n readonly loop = input<boolean, BooleanInput>(false, { transform: booleanAttribute });\n\n /**\n * When `true`, focus cannot escape the focus scope via keyboard,\n * pointer, or a programmatic focus.\n *\n * @group Props\n * @defaultValue false\n */\n readonly trapped = input<boolean, BooleanInput>(false, { transform: booleanAttribute });\n\n /**\n * Event handler called when auto-focusing on mount.\n * Can be prevented.\n *\n * @group Emits\n */\n readonly mountAutoFocus = output<Event>();\n\n /**\n * Event handler called when auto-focusing on unmount.\n * Can be prevented.\n *\n * @group Emits\n */\n readonly unmountAutoFocus = output<Event>();\n\n readonly lastFocusedElement = signal<HTMLElement | null>(null);\n\n private readonly focusScopesStack = createFocusScopesStack();\n\n readonly focusScope: FocusScopeAPI = {\n paused: signal(false),\n pause: () => this.focusScope.paused.set(true),\n resume: () => this.focusScope.paused.set(false)\n };\n\n constructor() {\n afterNextRender(() => {\n effect(\n (onCleanup) => {\n const container = this.elementRef.nativeElement;\n\n if (this.trapped()) {\n const handleFocusIn = (event: FocusEvent) => {\n if (this.focusScope.paused() || !container) {\n return;\n }\n\n const target = event.target as HTMLElement | null;\n if (this.elementRef.nativeElement.contains(target)) {\n this.lastFocusedElement.set(target);\n } else {\n focus(this.lastFocusedElement(), { select: true });\n }\n };\n\n const handleFocusOut = (event: FocusEvent) => {\n if (this.focusScope.paused() || !container) {\n return;\n }\n const relatedTarget = event.relatedTarget as HTMLElement | null;\n\n // A `focusout` event with a `null` `relatedTarget` will happen in at least two cases:\n //\n // 1. When the user switches app/tabs/windows/the browser itself loses focus.\n // 2. In Google Chrome, when the focused element is removed from the DOM.\n //\n // We let the browser do its thing here because:\n //\n // 1. The browser already keeps a memory of what's focused for when the page gets refocused.\n // 2. In Google Chrome, if we try to focus the deleted focused element (as per below), it\n // throws the CPU to 100%, so we avoid doing anything for this reason here too.\n if (relatedTarget === null) return;\n\n // If the focus has moved to an actual legitimate element (`relatedTarget !== null`)\n // that is outside the container, we move focus to the last valid focused element inside.\n if (!container.contains(relatedTarget)) {\n focus(this.lastFocusedElement(), { select: true });\n }\n };\n\n const handleMutations = () => {\n const isLastFocusedElementExist = container.contains(this.lastFocusedElement());\n\n if (!isLastFocusedElementExist) {\n focus(container);\n }\n };\n\n const mutationObserver = new MutationObserver(handleMutations);\n if (container) {\n mutationObserver.observe(container, { childList: true, subtree: true });\n }\n\n document.addEventListener('focusin', handleFocusIn);\n document.addEventListener('focusout', handleFocusOut);\n\n onCleanup(() => {\n document.removeEventListener('focusin', handleFocusIn);\n document.removeEventListener('focusout', handleFocusOut);\n mutationObserver.disconnect();\n });\n }\n },\n { injector: this.injector }\n );\n\n effect(\n async (onCleanup) => {\n const container = this.elementRef.nativeElement;\n\n await Promise.resolve();\n if (!container) {\n return;\n }\n\n this.focusScopesStack.add(this.focusScope);\n\n const previouslyFocusedElement = getActiveElement() as HTMLElement | null;\n const hasFocusedCandidate = container.contains(previouslyFocusedElement);\n\n if (!hasFocusedCandidate) {\n const mountEvent = new CustomEvent(AUTOFOCUS_ON_MOUNT, EVENT_OPTIONS);\n container.addEventListener(AUTOFOCUS_ON_MOUNT, (ev: Event) => this.mountAutoFocus.emit(ev));\n container.dispatchEvent(mountEvent);\n\n if (!mountEvent.defaultPrevented) {\n focusFirst(removeLinks(getTabbableCandidates(container)), {\n select: true\n });\n if (getActiveElement() === previouslyFocusedElement) focus(container);\n }\n }\n\n onCleanup(() => {\n container.removeEventListener(AUTOFOCUS_ON_MOUNT, (ev: Event) => this.mountAutoFocus.emit(ev));\n\n const unmountEvent = new CustomEvent(AUTOFOCUS_ON_UNMOUNT, EVENT_OPTIONS);\n const unmountEventHandler = (ev: Event) => this.unmountAutoFocus.emit(ev);\n container.addEventListener(AUTOFOCUS_ON_UNMOUNT, unmountEventHandler);\n container.dispatchEvent(unmountEvent);\n\n setTimeout(() => {\n if (!unmountEvent.defaultPrevented)\n focus(previouslyFocusedElement ?? document.body, { select: true });\n\n // we need to remove the listener after we `dispatchEvent`\n container.removeEventListener(AUTOFOCUS_ON_UNMOUNT, unmountEventHandler);\n\n this.focusScopesStack.remove(this.focusScope);\n }, 0);\n });\n },\n { injector: this.injector }\n );\n });\n }\n\n handleKeyDown(event: KeyboardEvent) {\n const isTabKey = event.key === 'Tab' && !event.altKey && !event.ctrlKey && !event.metaKey;\n\n const focusedElement = getActiveElement() as HTMLElement | null;\n\n if (isTabKey && focusedElement) {\n const container = event.currentTarget as HTMLElement;\n\n const [first, last] = getTabbableEdges(container);\n const hasTabbableElementsInside = first && last;\n\n // we can only wrap focus if we have tabbable edges\n if (!hasTabbableElementsInside) {\n if (focusedElement === container) event.preventDefault();\n } else {\n if (!event.shiftKey && focusedElement === last) {\n event.preventDefault();\n if (this.loop()) {\n focus(first, { select: true });\n }\n } else if (event.shiftKey && focusedElement === first) {\n event.preventDefault();\n if (this.loop()) {\n focus(last, { select: true });\n }\n }\n }\n }\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;AAEM,SAAU,iBAAiB,CAAI,OAAgB,EAAA;AACjD,IAAA,MAAM,KAAK,GAAG,OAAO,EAAE;AACvB,IAAA,OAAO,MAAM,KAAK;AACtB;AAQA,MAAM,kBAAkB,GAAG,iBAAiB,CAAC,MAAM,MAAM,CAAkB,EAAE,CAAC,CAAC;SAE/D,sBAAsB,GAAA;;AAElC,IAAA,MAAM,KAAK,GAAG,kBAAkB,EAAE;IAElC,OAAO;AACH,QAAA,GAAG,CAAC,UAAyB,EAAA;AACzB,YAAA,MAAM,OAAO,GAAG,KAAK,EAAE;AACvB,YAAA,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;AACzB,YAAA,IAAI,UAAU,KAAK,MAAM,EAAE;gBACvB,MAAM,EAAE,KAAK,EAAE;;YAEnB,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,EAAE,UAAU,CAAC;AAChD,YAAA,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC;AAC3B,YAAA,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC;SACrB;AAED,QAAA,MAAM,CAAC,UAAyB,EAAA;AAC5B,YAAA,MAAM,OAAO,GAAG,KAAK,EAAE;YACvB,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,EAAE,UAAU,CAAC;AAChD,YAAA,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC;;AAElB,YAAA,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE;;KAE3B;AACL;AAEgB,SAAA,WAAW,CAAI,KAAU,EAAE,IAAO,EAAA;AAC9C,IAAA,MAAM,IAAI,GAAG,CAAC,GAAG,KAAK,CAAC;IACvB,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;AAC9B,IAAA,IAAI,GAAG,KAAK,CAAC,CAAC,EAAE;AACZ,QAAA,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC;;AAEvB,IAAA,OAAO,IAAI;AACf;AAEM,SAAU,WAAW,CAAC,KAAoB,EAAA;AAC5C,IAAA,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,OAAO,KAAK,GAAG,CAAC;AACnD;;AClDO,MAAM,kBAAkB,GAAG,6BAA6B;AACxD,MAAM,oBAAoB,GAAG,+BAA+B;AAC5D,MAAM,aAAa,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE;AAIjE;;;AAGG;AACG,SAAU,UAAU,CAAC,UAAyB,EAAE,EAAE,MAAM,GAAG,KAAK,EAAE,GAAG,EAAE,EAAA;AACzE,IAAA,MAAM,wBAAwB,GAAG,gBAAgB,EAAE;AACnD,IAAA,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE;AAChC,QAAA,KAAK,CAAC,SAAS,EAAE,EAAE,MAAM,EAAE,CAAC;QAC5B,IAAI,gBAAgB,EAAE,KAAK,wBAAwB;AAAE,YAAA,OAAO,IAAI;;IAGpE;AACJ;AAEA;;;;;;;;;AASG;AACG,SAAU,qBAAqB,CAAC,SAAsB,EAAA;IACxD,MAAM,KAAK,GAAkB,EAAE;IAC/B,MAAM,MAAM,GAAG,QAAQ,CAAC,gBAAgB,CAAC,SAAS,EAAE,UAAU,CAAC,YAAY,EAAE;AACzE,QAAA,UAAU,EAAE,CAAC,IAAS,KAAI;AACtB,YAAA,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,KAAK,OAAO,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ;YACxE,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,MAAM,IAAI,aAAa;gBAAE,OAAO,UAAU,CAAC,WAAW;;;;AAIhF,YAAA,OAAO,IAAI,CAAC,QAAQ,IAAI,CAAC,GAAG,UAAU,CAAC,aAAa,GAAG,UAAU,CAAC,WAAW;;AAEpF,KAAA,CAAC;IACF,OAAO,MAAM,CAAC,QAAQ,EAAE;AAAE,QAAA,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,WAA0B,CAAC;;;AAGvE,IAAA,OAAO,KAAK;AAChB;SAEgB,QAAQ,CAAC,IAAiB,EAAE,EAAE,IAAI,EAA0B,EAAA;AACxE,IAAA,IAAI,gBAAgB,CAAC,IAAI,CAAC,CAAC,UAAU,KAAK,QAAQ;AAAE,QAAA,OAAO,IAAI;IAC/D,OAAO,IAAI,EAAE;;AAET,QAAA,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,IAAI;AAAE,YAAA,OAAO,KAAK;AACrD,QAAA,IAAI,gBAAgB,CAAC,IAAI,CAAC,CAAC,OAAO,KAAK,MAAM;AAAE,YAAA,OAAO,IAAI;AAC1D,QAAA,IAAI,GAAG,IAAI,CAAC,aAA4B;;AAE5C,IAAA,OAAO,KAAK;AAChB;AAEA;;;AAGG;AACa,SAAA,WAAW,CAAC,QAAuB,EAAE,SAAsB,EAAA;AACvE,IAAA,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE;;QAE5B,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;AAAE,YAAA,OAAO,OAAO;;AAE/D,IAAA,OAAO,SAAS;AACpB;AAEA;;AAEG;AACG,SAAU,gBAAgB,CAAC,SAAsB,EAAA;AACnD,IAAA,MAAM,UAAU,GAAG,qBAAqB,CAAC,SAAS,CAAC;IACnD,MAAM,KAAK,GAAG,WAAW,CAAC,UAAU,EAAE,SAAS,CAAC;IAChD,MAAM,IAAI,GAAG,WAAW,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC;AACzD,IAAA,OAAO,CAAC,KAAK,EAAE,IAAI,CAAU;AACjC;AAEM,SAAU,iBAAiB,CAAC,OAAY,EAAA;AAC1C,IAAA,OAAO,OAAO,YAAY,gBAAgB,IAAI,QAAQ,IAAI,OAAO;AACrE;AAEM,SAAU,KAAK,CAAC,OAAgC,EAAE,EAAE,MAAM,GAAG,KAAK,EAAE,GAAG,EAAE,EAAA;;AAE3E,IAAA,IAAI,OAAO,IAAI,OAAO,CAAC,KAAK,EAAE;AAC1B,QAAA,MAAM,wBAAwB,GAAG,gBAAgB,EAAE;;QAEnD,OAAO,CAAC,KAAK,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;;QAEtC,IAAI,OAAO,KAAK,wBAAwB,IAAI,iBAAiB,CAAC,OAAO,CAAC,IAAI,MAAM,EAAE;YAC9E,OAAO,CAAC,MAAM,EAAE;;;AAG5B;;AClEO,MAAM,CAAC,uBAAuB,EAAE,wBAAwB,CAAC,GAC5D,aAAa,CAAoB,oBAAoB;AAEzD,MAAM,WAAW,GAAG,MAAwB;AACxC,IAAA,MAAM,OAAO,GAAG,MAAM,CAAC,aAAa,CAAC;IAErC,OAAO;QACH,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,OAAO,EAAE,OAAO,CAAC;KACpB;AACL,CAAC;AAED;;AAEG;MASU,aAAa,CAAA;AAiDtB,IAAA,WAAA,GAAA;AAhDiB,QAAA,IAAA,CAAA,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;AAE3B,QAAA,IAAA,CAAA,UAAU,GAAG,MAAM,CAA0B,UAAU,CAAC;AAEzE;;;;;;AAMG;QACM,IAAI,CAAA,IAAA,GAAG,KAAK,CAAwB,KAAK,EAAE,EAAE,SAAS,EAAE,gBAAgB,EAAE,CAAC;AAEpF;;;;;;AAMG;QACM,IAAO,CAAA,OAAA,GAAG,KAAK,CAAwB,KAAK,EAAE,EAAE,SAAS,EAAE,gBAAgB,EAAE,CAAC;AAEvF;;;;;AAKG;QACM,IAAc,CAAA,cAAA,GAAG,MAAM,EAAS;AAEzC;;;;;AAKG;QACM,IAAgB,CAAA,gBAAA,GAAG,MAAM,EAAS;AAElC,QAAA,IAAA,CAAA,kBAAkB,GAAG,MAAM,CAAqB,IAAI,CAAC;QAE7C,IAAgB,CAAA,gBAAA,GAAG,sBAAsB,EAAE;AAEnD,QAAA,IAAA,CAAA,UAAU,GAAkB;AACjC,YAAA,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC;AACrB,YAAA,KAAK,EAAE,MAAM,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;AAC7C,YAAA,MAAM,EAAE,MAAM,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK;SACjD;QAGG,eAAe,CAAC,MAAK;AACjB,YAAA,MAAM,CACF,CAAC,SAAS,KAAI;AACV,gBAAA,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,aAAa;AAE/C,gBAAA,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE;AAChB,oBAAA,MAAM,aAAa,GAAG,CAAC,KAAiB,KAAI;wBACxC,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC,SAAS,EAAE;4BACxC;;AAGJ,wBAAA,MAAM,MAAM,GAAG,KAAK,CAAC,MAA4B;wBACjD,IAAI,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE;AAChD,4BAAA,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,MAAM,CAAC;;6BAChC;AACH,4BAAA,KAAK,CAAC,IAAI,CAAC,kBAAkB,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;;AAE1D,qBAAC;AAED,oBAAA,MAAM,cAAc,GAAG,CAAC,KAAiB,KAAI;wBACzC,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC,SAAS,EAAE;4BACxC;;AAEJ,wBAAA,MAAM,aAAa,GAAG,KAAK,CAAC,aAAmC;;;;;;;;;;;wBAY/D,IAAI,aAAa,KAAK,IAAI;4BAAE;;;wBAI5B,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE;AACpC,4BAAA,KAAK,CAAC,IAAI,CAAC,kBAAkB,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;;AAE1D,qBAAC;oBAED,MAAM,eAAe,GAAG,MAAK;wBACzB,MAAM,yBAAyB,GAAG,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC;wBAE/E,IAAI,CAAC,yBAAyB,EAAE;4BAC5B,KAAK,CAAC,SAAS,CAAC;;AAExB,qBAAC;AAED,oBAAA,MAAM,gBAAgB,GAAG,IAAI,gBAAgB,CAAC,eAAe,CAAC;oBAC9D,IAAI,SAAS,EAAE;AACX,wBAAA,gBAAgB,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;;AAG3E,oBAAA,QAAQ,CAAC,gBAAgB,CAAC,SAAS,EAAE,aAAa,CAAC;AACnD,oBAAA,QAAQ,CAAC,gBAAgB,CAAC,UAAU,EAAE,cAAc,CAAC;oBAErD,SAAS,CAAC,MAAK;AACX,wBAAA,QAAQ,CAAC,mBAAmB,CAAC,SAAS,EAAE,aAAa,CAAC;AACtD,wBAAA,QAAQ,CAAC,mBAAmB,CAAC,UAAU,EAAE,cAAc,CAAC;wBACxD,gBAAgB,CAAC,UAAU,EAAE;AACjC,qBAAC,CAAC;;aAET,EACD,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAC9B;AAED,YAAA,MAAM,CACF,OAAO,SAAS,KAAI;AAChB,gBAAA,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,aAAa;AAE/C,gBAAA,MAAM,OAAO,CAAC,OAAO,EAAE;gBACvB,IAAI,CAAC,SAAS,EAAE;oBACZ;;gBAGJ,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC;AAE1C,gBAAA,MAAM,wBAAwB,GAAG,gBAAgB,EAAwB;gBACzE,MAAM,mBAAmB,GAAG,SAAS,CAAC,QAAQ,CAAC,wBAAwB,CAAC;gBAExE,IAAI,CAAC,mBAAmB,EAAE;oBACtB,MAAM,UAAU,GAAG,IAAI,WAAW,CAAC,kBAAkB,EAAE,aAAa,CAAC;AACrE,oBAAA,SAAS,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,CAAC,EAAS,KAAK,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAC3F,oBAAA,SAAS,CAAC,aAAa,CAAC,UAAU,CAAC;AAEnC,oBAAA,IAAI,CAAC,UAAU,CAAC,gBAAgB,EAAE;wBAC9B,UAAU,CAAC,WAAW,CAAC,qBAAqB,CAAC,SAAS,CAAC,CAAC,EAAE;AACtD,4BAAA,MAAM,EAAE;AACX,yBAAA,CAAC;wBACF,IAAI,gBAAgB,EAAE,KAAK,wBAAwB;4BAAE,KAAK,CAAC,SAAS,CAAC;;;gBAI7E,SAAS,CAAC,MAAK;AACX,oBAAA,SAAS,CAAC,mBAAmB,CAAC,kBAAkB,EAAE,CAAC,EAAS,KAAK,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;oBAE9F,MAAM,YAAY,GAAG,IAAI,WAAW,CAAC,oBAAoB,EAAE,aAAa,CAAC;AACzE,oBAAA,MAAM,mBAAmB,GAAG,CAAC,EAAS,KAAK,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC;AACzE,oBAAA,SAAS,CAAC,gBAAgB,CAAC,oBAAoB,EAAE,mBAAmB,CAAC;AACrE,oBAAA,SAAS,CAAC,aAAa,CAAC,YAAY,CAAC;oBAErC,UAAU,CAAC,MAAK;wBACZ,IAAI,CAAC,YAAY,CAAC,gBAAgB;AAC9B,4BAAA,KAAK,CAAC,wBAAwB,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;;AAGtE,wBAAA,SAAS,CAAC,mBAAmB,CAAC,oBAAoB,EAAE,mBAAmB,CAAC;wBAExE,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC;qBAChD,EAAE,CAAC,CAAC;AACT,iBAAC,CAAC;aACL,EACD,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAC9B;AACL,SAAC,CAAC;;AAGN,IAAA,aAAa,CAAC,KAAoB,EAAA;QAC9B,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,KAAK,KAAK,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO;AAEzF,QAAA,MAAM,cAAc,GAAG,gBAAgB,EAAwB;AAE/D,QAAA,IAAI,QAAQ,IAAI,cAAc,EAAE;AAC5B,YAAA,MAAM,SAAS,GAAG,KAAK,CAAC,aAA4B;YAEpD,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,gBAAgB,CAAC,SAAS,CAAC;AACjD,YAAA,MAAM,yBAAyB,GAAG,KAAK,IAAI,IAAI;;YAG/C,IAAI,CAAC,yBAAyB,EAAE;gBAC5B,IAAI,cAAc,KAAK,SAAS;oBAAE,KAAK,CAAC,cAAc,EAAE;;iBACrD;gBACH,IAAI,CAAC,KAAK,CAAC,QAAQ,IAAI,cAAc,KAAK,IAAI,EAAE;oBAC5C,KAAK,CAAC,cAAc,EAAE;AACtB,oBAAA,IAAI,IAAI,CAAC,IAAI,EAAE,EAAE;wBACb,KAAK,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;;;qBAE/B,IAAI,KAAK,CAAC,QAAQ,IAAI,cAAc,KAAK,KAAK,EAAE;oBACnD,KAAK,CAAC,cAAc,EAAE;AACtB,oBAAA,IAAI,IAAI,CAAC,IAAI,EAAE,EAAE;wBACb,KAAK,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;;;;;;8GAlMxC,aAAa,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA,CAAA;AAAb,IAAA,SAAA,IAAA,CAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,aAAa,ifANX,CAAC,wBAAwB,CAAC,WAAW,CAAC,CAAC,EAAA,QAAA,EAAA,EAAA,EAAA,CAAA,CAAA;;2FAMzC,aAAa,EAAA,UAAA,EAAA,CAAA;kBARzB,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACP,oBAAA,QAAQ,EAAE,iBAAiB;AAC3B,oBAAA,SAAS,EAAE,CAAC,wBAAwB,CAAC,WAAW,CAAC,CAAC;AAClD,oBAAA,IAAI,EAAE;AACF,wBAAA,QAAQ,EAAE,IAAI;AACd,wBAAA,WAAW,EAAE;AAChB;AACJ,iBAAA;;;ACtDD;;AAEG;;;;"}
|
@@ -0,0 +1 @@
|
|
1
|
+
# @radix-ng/primitives/focus-scope
|
@@ -0,0 +1 @@
|
|
1
|
+
export * from './src/focus-scope';
|
@@ -0,0 +1,53 @@
|
|
1
|
+
import { BooleanInput } from '@angular/cdk/coercion';
|
2
|
+
import { Signal } from '@angular/core';
|
3
|
+
import { FocusScopeAPI } from './stack';
|
4
|
+
import * as i0 from "@angular/core";
|
5
|
+
export interface FocusScopeContext {
|
6
|
+
loop?: Signal<boolean>;
|
7
|
+
trapped?: Signal<boolean>;
|
8
|
+
}
|
9
|
+
export declare const injectFocusScopeContext: (optional?: boolean) => FocusScopeContext | null, provideFocusScopeContext: (useFactory: () => FocusScopeContext) => import("@angular/core").Provider;
|
10
|
+
/**
|
11
|
+
* @group Components
|
12
|
+
*/
|
13
|
+
export declare class RdxFocusScope {
|
14
|
+
private readonly injector;
|
15
|
+
private readonly elementRef;
|
16
|
+
/**
|
17
|
+
* When `true`, tabbing from last item will focus first tabbable
|
18
|
+
* and shift+tab from first item will focus last tababble.
|
19
|
+
*
|
20
|
+
* @group Props
|
21
|
+
* @defaultValue false
|
22
|
+
*/
|
23
|
+
readonly loop: import("@angular/core").InputSignalWithTransform<boolean, BooleanInput>;
|
24
|
+
/**
|
25
|
+
* When `true`, focus cannot escape the focus scope via keyboard,
|
26
|
+
* pointer, or a programmatic focus.
|
27
|
+
*
|
28
|
+
* @group Props
|
29
|
+
* @defaultValue false
|
30
|
+
*/
|
31
|
+
readonly trapped: import("@angular/core").InputSignalWithTransform<boolean, BooleanInput>;
|
32
|
+
/**
|
33
|
+
* Event handler called when auto-focusing on mount.
|
34
|
+
* Can be prevented.
|
35
|
+
*
|
36
|
+
* @group Emits
|
37
|
+
*/
|
38
|
+
readonly mountAutoFocus: import("@angular/core").OutputEmitterRef<Event>;
|
39
|
+
/**
|
40
|
+
* Event handler called when auto-focusing on unmount.
|
41
|
+
* Can be prevented.
|
42
|
+
*
|
43
|
+
* @group Emits
|
44
|
+
*/
|
45
|
+
readonly unmountAutoFocus: import("@angular/core").OutputEmitterRef<Event>;
|
46
|
+
readonly lastFocusedElement: import("@angular/core").WritableSignal<HTMLElement | null>;
|
47
|
+
private readonly focusScopesStack;
|
48
|
+
readonly focusScope: FocusScopeAPI;
|
49
|
+
constructor();
|
50
|
+
handleKeyDown(event: KeyboardEvent): void;
|
51
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<RdxFocusScope, never>;
|
52
|
+
static ɵdir: i0.ɵɵDirectiveDeclaration<RdxFocusScope, "[rdxFocusScope]", never, { "loop": { "alias": "loop"; "required": false; "isSignal": true; }; "trapped": { "alias": "trapped"; "required": false; "isSignal": true; }; }, { "mountAutoFocus": "mountAutoFocus"; "unmountAutoFocus": "unmountAutoFocus"; }, never, never, true, never>;
|
53
|
+
}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import { WritableSignal } from '@angular/core';
|
2
|
+
export declare function createGlobalState<T>(factory: () => T): () => T;
|
3
|
+
export interface FocusScopeAPI {
|
4
|
+
paused: WritableSignal<boolean>;
|
5
|
+
pause(): void;
|
6
|
+
resume(): void;
|
7
|
+
}
|
8
|
+
export declare function createFocusScopesStack(): {
|
9
|
+
add(focusScope: FocusScopeAPI): void;
|
10
|
+
remove(focusScope: FocusScopeAPI): void;
|
11
|
+
};
|
12
|
+
export declare function arrayRemove<T>(array: T[], item: T): T[];
|
13
|
+
export declare function removeLinks(items: HTMLElement[]): HTMLElement[];
|
@@ -0,0 +1,46 @@
|
|
1
|
+
export declare const AUTOFOCUS_ON_MOUNT = "focusScope.autoFocusOnMount";
|
2
|
+
export declare const AUTOFOCUS_ON_UNMOUNT = "focusScope.autoFocusOnUnmount";
|
3
|
+
export declare const EVENT_OPTIONS: {
|
4
|
+
bubbles: boolean;
|
5
|
+
cancelable: boolean;
|
6
|
+
};
|
7
|
+
type FocusableTarget = HTMLElement | {
|
8
|
+
focus: () => void;
|
9
|
+
};
|
10
|
+
/**
|
11
|
+
* Attempts focusing the first element in a list of candidates.
|
12
|
+
* Stops when focus has actually moved.
|
13
|
+
*/
|
14
|
+
export declare function focusFirst(candidates: HTMLElement[], { select }?: {
|
15
|
+
select?: boolean | undefined;
|
16
|
+
}): true | undefined;
|
17
|
+
/**
|
18
|
+
* Returns a list of potential tabbable candidates.
|
19
|
+
*
|
20
|
+
* NOTE: This is only a close approximation. For example it doesn't take into account cases like when
|
21
|
+
* elements are not visible. This cannot be worked out easily by just reading a property, but rather
|
22
|
+
* necessitate runtime knowledge (computed styles, etc). We deal with these cases separately.
|
23
|
+
*
|
24
|
+
* See: https://developer.mozilla.org/en-US/docs/Web/API/TreeWalker
|
25
|
+
* Credit: https://github.com/discord/focus-layers/blob/master/src/util/wrapFocus.tsx#L1
|
26
|
+
*/
|
27
|
+
export declare function getTabbableCandidates(container: HTMLElement): HTMLElement[];
|
28
|
+
export declare function isHidden(node: HTMLElement, { upTo }: {
|
29
|
+
upTo?: HTMLElement;
|
30
|
+
}): boolean;
|
31
|
+
/**
|
32
|
+
* Returns the first visible element in a list.
|
33
|
+
* NOTE: Only checks visibility up to the `container`.
|
34
|
+
*/
|
35
|
+
export declare function findVisible(elements: HTMLElement[], container: HTMLElement): HTMLElement | undefined;
|
36
|
+
/**
|
37
|
+
* Returns the first and last tabbable elements inside a container.
|
38
|
+
*/
|
39
|
+
export declare function getTabbableEdges(container: HTMLElement): readonly [HTMLElement | undefined, HTMLElement | undefined];
|
40
|
+
export declare function isSelectableInput(element: any): element is FocusableTarget & {
|
41
|
+
select: () => void;
|
42
|
+
};
|
43
|
+
export declare function focus(element?: FocusableTarget | null, { select }?: {
|
44
|
+
select?: boolean | undefined;
|
45
|
+
}): void;
|
46
|
+
export {};
|
@@ -93,7 +93,7 @@ export declare class RdxHoverCardRootDirective {
|
|
93
93
|
window: Window & typeof globalThis;
|
94
94
|
primitiveConfigs?: import("./utils/types").PrimitiveConfigs;
|
95
95
|
onDestroyCallbacks: Set<() => void>;
|
96
|
-
"__#
|
96
|
+
"__#14839@#clickDomRootEventCallbacks": Set<(event: MouseEvent) => void>;
|
97
97
|
registerPrimitive<T extends object>(primitiveInstance: T): void;
|
98
98
|
deregisterPrimitive<T extends object>(primitiveInstance: T): void;
|
99
99
|
preventPrimitiveFromCdkEvent<T extends object>(primitiveInstance: T, eventType: import("./utils/types").EventType): void;
|
@@ -104,9 +104,9 @@ export declare class RdxHoverCardRootDirective {
|
|
104
104
|
primitivePreventedFromCdkEvent<T extends object>(primitiveInstance: T, eventType: import("./utils/types").EventType): boolean | undefined;
|
105
105
|
addClickDomRootEventCallback(callback: (event: MouseEvent) => void): void;
|
106
106
|
removeClickDomRootEventCallback(callback: (event: MouseEvent) => void): boolean;
|
107
|
-
"__#
|
108
|
-
"__#
|
109
|
-
"__#
|
107
|
+
"__#14839@#setPreventPrimitiveFromCdkEvent"<T extends object, R extends import("./utils/types").EventType, K extends import("./utils/types").PrimitiveConfig[`prevent${Capitalize<R>}`]>(primitiveInstance: T, eventType: R, value: K): void;
|
108
|
+
"__#14839@#registerOnDestroyCallbacks"(): void;
|
109
|
+
"__#14839@#listenToClickDomRootEvent"(): void;
|
110
110
|
} | null;
|
111
111
|
/** @ignore */
|
112
112
|
readonly destroyRef: DestroyRef;
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@radix-ng/primitives",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.42.0",
|
4
4
|
"license": "MIT",
|
5
5
|
"publishConfig": {
|
6
6
|
"access": "public"
|
@@ -88,10 +88,22 @@
|
|
88
88
|
"types": "./dialog/index.d.ts",
|
89
89
|
"default": "./fesm2022/radix-ng-primitives-dialog.mjs"
|
90
90
|
},
|
91
|
+
"./dismissible-layer": {
|
92
|
+
"types": "./dismissible-layer/index.d.ts",
|
93
|
+
"default": "./fesm2022/radix-ng-primitives-dismissible-layer.mjs"
|
94
|
+
},
|
91
95
|
"./dropdown-menu": {
|
92
96
|
"types": "./dropdown-menu/index.d.ts",
|
93
97
|
"default": "./fesm2022/radix-ng-primitives-dropdown-menu.mjs"
|
94
98
|
},
|
99
|
+
"./editable": {
|
100
|
+
"types": "./editable/index.d.ts",
|
101
|
+
"default": "./fesm2022/radix-ng-primitives-editable.mjs"
|
102
|
+
},
|
103
|
+
"./focus-scope": {
|
104
|
+
"types": "./focus-scope/index.d.ts",
|
105
|
+
"default": "./fesm2022/radix-ng-primitives-focus-scope.mjs"
|
106
|
+
},
|
95
107
|
"./hover-card": {
|
96
108
|
"types": "./hover-card/index.d.ts",
|
97
109
|
"default": "./fesm2022/radix-ng-primitives-hover-card.mjs"
|
@@ -70,7 +70,7 @@ export declare class RdxPopoverRootDirective {
|
|
70
70
|
window: Window & typeof globalThis;
|
71
71
|
primitiveConfigs?: import("./utils/types").PrimitiveConfigs;
|
72
72
|
onDestroyCallbacks: Set<() => void>;
|
73
|
-
"__#
|
73
|
+
"__#19150@#clickDomRootEventCallbacks": Set<(event: MouseEvent) => void>;
|
74
74
|
registerPrimitive<T extends object>(primitiveInstance: T): void;
|
75
75
|
deregisterPrimitive<T extends object>(primitiveInstance: T): void;
|
76
76
|
preventPrimitiveFromCdkEvent<T extends object>(primitiveInstance: T, eventType: import("./utils/types").EventType): void;
|
@@ -81,9 +81,9 @@ export declare class RdxPopoverRootDirective {
|
|
81
81
|
primitivePreventedFromCdkEvent<T extends object>(primitiveInstance: T, eventType: import("./utils/types").EventType): boolean | undefined;
|
82
82
|
addClickDomRootEventCallback(callback: (event: MouseEvent) => void): void;
|
83
83
|
removeClickDomRootEventCallback(callback: (event: MouseEvent) => void): boolean;
|
84
|
-
"__#
|
85
|
-
"__#
|
86
|
-
"__#
|
84
|
+
"__#19150@#setPreventPrimitiveFromCdkEvent"<T extends object, R extends import("./utils/types").EventType, K extends import("./utils/types").PrimitiveConfig[`prevent${Capitalize<R>}`]>(primitiveInstance: T, eventType: R, value: K): void;
|
85
|
+
"__#19150@#registerOnDestroyCallbacks"(): void;
|
86
|
+
"__#19150@#listenToClickDomRootEvent"(): void;
|
87
87
|
} | null;
|
88
88
|
/** @ignore */
|
89
89
|
readonly destroyRef: DestroyRef;
|
@@ -79,7 +79,7 @@ export declare class RdxTooltipRootDirective {
|
|
79
79
|
window: Window & typeof globalThis;
|
80
80
|
primitiveConfigs?: import("./utils/types").PrimitiveConfigs;
|
81
81
|
onDestroyCallbacks: Set<() => void>;
|
82
|
-
"__#
|
82
|
+
"__#22995@#clickDomRootEventCallbacks": Set<(event: MouseEvent) => void>;
|
83
83
|
registerPrimitive<T extends object>(primitiveInstance: T): void;
|
84
84
|
deregisterPrimitive<T extends object>(primitiveInstance: T): void;
|
85
85
|
preventPrimitiveFromCdkEvent<T extends object>(primitiveInstance: T, eventType: import("./utils/types").EventType): void;
|
@@ -90,9 +90,9 @@ export declare class RdxTooltipRootDirective {
|
|
90
90
|
primitivePreventedFromCdkEvent<T extends object>(primitiveInstance: T, eventType: import("./utils/types").EventType): boolean | undefined;
|
91
91
|
addClickDomRootEventCallback(callback: (event: MouseEvent) => void): void;
|
92
92
|
removeClickDomRootEventCallback(callback: (event: MouseEvent) => void): boolean;
|
93
|
-
"__#
|
94
|
-
"__#
|
95
|
-
"__#
|
93
|
+
"__#22995@#setPreventPrimitiveFromCdkEvent"<T extends object, R extends import("./utils/types").EventType, K extends import("./utils/types").PrimitiveConfig[`prevent${Capitalize<R>}`]>(primitiveInstance: T, eventType: R, value: K): void;
|
94
|
+
"__#22995@#registerOnDestroyCallbacks"(): void;
|
95
|
+
"__#22995@#listenToClickDomRootEvent"(): void;
|
96
96
|
} | null;
|
97
97
|
/** @ignore */
|
98
98
|
readonly destroyRef: DestroyRef;
|