-
Notifications
You must be signed in to change notification settings - Fork 78
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(activeElement): introduce injectActiveElement (#110)
* feat(activeElement): add injectActiveElement * docs: active-element * feat(activeElement): add activeElement example to the test app * feat(activeElement): add activeElement e2e tests * chore: rollback unnecessary changes * chore: use local-plugin entry point generator * feat(activeElement): use assertInjector * Update docs/src/content/docs/utilities/Injectors/active-element.md Co-authored-by: Enea Jahollari <jahollarienea14@gmail.com> * Update libs/ngxtension/active-element/src/active-element.ts --------- Co-authored-by: Chau Tran <nartc7789@gmail.com> Co-authored-by: Enea Jahollari <jahollarienea14@gmail.com>
- Loading branch information
1 parent
1755b74
commit 48fdf25
Showing
12 changed files
with
267 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
describe('activeElement', () => { | ||
beforeEach(() => cy.visit('/active-element')); | ||
|
||
it('should emit the focussed button', () => { | ||
cy.get('button').eq(1).as('buttonToFocus'); | ||
cy.get('span').as('focussedElementHTML'); | ||
|
||
cy.get('@buttonToFocus').focus(); | ||
cy.get('@focussedElementHTML').should('contain.text', 'btn2'); | ||
}); | ||
}); |
22 changes: 22 additions & 0 deletions
22
apps/test-app/src/app/active-element/active-element.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { AsyncPipe } from '@angular/common'; | ||
import { Component } from '@angular/core'; | ||
import { injectActiveElement } from 'ngxtension/active-element'; | ||
|
||
@Component({ | ||
standalone: true, | ||
host: { | ||
style: 'display: block; margin: 12px', | ||
}, | ||
imports: [AsyncPipe], | ||
template: ` | ||
<button>btn1</button> | ||
<button>btn2</button> | ||
<button>btn3</button> | ||
<span> | ||
{{ (activeElement$ | async)?.innerHTML }} | ||
</span> | ||
`, | ||
}) | ||
export default class ActiveElement { | ||
readonly activeElement$ = injectActiveElement(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
60 changes: 60 additions & 0 deletions
60
docs/src/content/docs/utilities/Injectors/active-element.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
--- | ||
title: injectActiveElement | ||
description: An Angular utility to create an Observable that emits active element from the document. | ||
--- | ||
|
||
## Import | ||
|
||
```ts | ||
import { injectActiveElement } from 'ngxtension/active-element'; | ||
``` | ||
|
||
## Usage | ||
|
||
### Basic | ||
|
||
Create an Observable that emits when the active -focussed- element changes. | ||
|
||
```ts | ||
import { Component } from '@angular/core'; | ||
import { injectActiveElement } from 'ngxtension/active-element'; | ||
|
||
@Component({ | ||
standalone: true, | ||
selector: 'app-example', | ||
template: ` | ||
<button>btn1</button> | ||
<button>btn2</button> | ||
<button>btn3</button> | ||
<span>{{ (activeElement$ | async)?.innerHTML }}</span> | ||
`, | ||
}) | ||
export class ExampleComponent { | ||
activeElement$ = injectActiveElement(); | ||
} | ||
``` | ||
|
||
## Use Outside of an Injection Context | ||
|
||
The `injectActiveElement` function accepts an optional `Injector` parameter, enabling usage outside of an injection context. | ||
|
||
```ts | ||
@Component() | ||
export class ExampleComponent implements OnInit { | ||
private readonly injector = inject(Injector); | ||
|
||
ngOnInit() { | ||
const activeElement$ = injectActiveElement(this.injector); | ||
} | ||
} | ||
``` | ||
|
||
## API | ||
|
||
### Inputs | ||
|
||
- `injector?: Injector` - Optional. Allows using the function outside of an Angular injection context. | ||
|
||
### Outputs | ||
|
||
- Emits an Observable of active HTMLElement from the document. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# ngxtension/active-element | ||
|
||
Secondary entry point of `ngxtension`. It can be used by importing from `ngxtension/active-element`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"lib": { | ||
"entryFile": "src/index.ts" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
{ | ||
"name": "ngxtension/active-element", | ||
"$schema": "../../../node_modules/nx/schemas/project-schema.json", | ||
"projectType": "library", | ||
"sourceRoot": "libs/ngxtension/active-element/src", | ||
"targets": { | ||
"test": { | ||
"executor": "@nx/jest:jest", | ||
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"], | ||
"options": { | ||
"jestConfig": "libs/ngxtension/jest.config.ts", | ||
"testPathPattern": ["active-element"], | ||
"passWithNoTests": true | ||
}, | ||
"configurations": { | ||
"ci": { | ||
"ci": true, | ||
"codeCoverage": true | ||
} | ||
} | ||
}, | ||
"lint": { | ||
"executor": "@nx/linter:eslint", | ||
"outputs": ["{options.outputFile}"], | ||
"options": { | ||
"lintFilePatterns": [ | ||
"libs/ngxtension/active-element/**/*.ts", | ||
"libs/ngxtension/active-element/**/*.html" | ||
] | ||
} | ||
} | ||
} | ||
} |
100 changes: 100 additions & 0 deletions
100
libs/ngxtension/active-element/src/active-element.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
import { AsyncPipe } from '@angular/common'; | ||
import { | ||
Component, | ||
DebugElement, | ||
INJECTOR, | ||
OnInit, | ||
inject, | ||
} from '@angular/core'; | ||
import { TestBed } from '@angular/core/testing'; | ||
import { By } from '@angular/platform-browser'; | ||
import { Observable } from 'rxjs'; | ||
import { injectActiveElement } from './active-element'; | ||
|
||
describe('injectActiveElement', () => { | ||
@Component({ | ||
standalone: true, | ||
imports: [AsyncPipe], | ||
template: ` | ||
<button>btn1</button> | ||
<button>btn2</button> | ||
<button>btn3</button> | ||
<span>{{ (activeElement | async)?.innerHTML }}</span> | ||
`, | ||
}) | ||
class TestComponent { | ||
activeElement = injectActiveElement(); | ||
} | ||
|
||
it('update focussed element when it changes', () => { | ||
const fixture = TestBed.createComponent(TestComponent); | ||
fixture.detectChanges(); | ||
const buttons = fixture.debugElement.queryAll(By.css('button')); | ||
const span: DebugElement = fixture.debugElement.query(By.css('span')); | ||
|
||
const buttonToFocus = buttons.at(1); | ||
|
||
buttonToFocus?.nativeElement.focus(); | ||
fixture.detectChanges(); | ||
|
||
const actual = span.nativeElement.innerHTML; | ||
const expected = buttonToFocus?.nativeElement.innerHTML; | ||
|
||
expect(actual).toBe(expected); | ||
}); | ||
|
||
it('be null when no active element', () => { | ||
const fixture = TestBed.createComponent(TestComponent); | ||
fixture.detectChanges(); | ||
const buttons = fixture.debugElement.queryAll(By.css('button')); | ||
const span: DebugElement = fixture.debugElement.query(By.css('span')); | ||
|
||
const buttonToFocus = buttons.at(1); | ||
|
||
buttonToFocus?.nativeElement.focus(); | ||
fixture.detectChanges(); | ||
buttonToFocus?.nativeElement.blur(); | ||
fixture.detectChanges(); | ||
|
||
const actual = span.nativeElement.innerHTML; | ||
const expected = ''; | ||
|
||
expect(actual).toBe(expected); | ||
}); | ||
|
||
it('work with given injector', () => { | ||
@Component({ | ||
standalone: true, | ||
imports: [AsyncPipe], | ||
template: ` | ||
<button>btn1</button> | ||
<button>btn2</button> | ||
<button>btn3</button> | ||
<span>{{ (activeElement | async)?.innerHTML }}</span> | ||
`, | ||
}) | ||
class DummyComponent implements OnInit { | ||
readonly injector = inject(INJECTOR); | ||
activeElement?: Observable<Element | null>; | ||
|
||
ngOnInit(): void { | ||
this.activeElement = injectActiveElement(this.injector); | ||
} | ||
} | ||
|
||
const fixture = TestBed.createComponent(DummyComponent); | ||
fixture.detectChanges(); | ||
const buttons = fixture.debugElement.queryAll(By.css('button')); | ||
const span: DebugElement = fixture.debugElement.query(By.css('span')); | ||
|
||
const buttonToFocus = buttons.at(2); | ||
|
||
buttonToFocus?.nativeElement.focus(); | ||
fixture.detectChanges(); | ||
|
||
const actual = span.nativeElement.innerHTML; | ||
const expected = buttonToFocus?.nativeElement.innerHTML; | ||
|
||
expect(actual).toBe(expected); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { DOCUMENT } from '@angular/common'; | ||
import { inject, Injector } from '@angular/core'; | ||
import { assertInjector } from 'ngxtension/assert-injector'; | ||
import { fromEvent, map, merge, shareReplay } from 'rxjs'; | ||
|
||
export function injectActiveElement(injector?: Injector) { | ||
return assertInjector(injectActiveElement, injector, () => { | ||
const doc = inject(DOCUMENT); | ||
return merge( | ||
fromEvent(doc, 'focus', { capture: true, passive: true }).pipe( | ||
map(() => true) | ||
), | ||
fromEvent(doc, 'blur', { capture: true, passive: true }).pipe( | ||
map(() => false) | ||
) | ||
).pipe( | ||
map((hasFocus) => (hasFocus ? doc.activeElement : null)), | ||
shareReplay({ refCount: true, bufferSize: 1 }) | ||
); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './active-element'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters