@hug/ngx-search-container 1.1.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/CHANGELOG.md +11 -0
- package/README.md +6 -0
- package/karma.conf.js +45 -0
- package/ng-package.json +15 -0
- package/package.json +46 -0
- package/src/_search-container-theme.scss +76 -0
- package/src/index.ts +2 -0
- package/src/search-container.component.html +22 -0
- package/src/search-container.component.scss +65 -0
- package/src/search-container.component.ts +99 -0
- package/src/search-container.module.ts +27 -0
- package/src/test.ts +28 -0
- package/tsconfig.lib.json +15 -0
- package/tsconfig.lib.prod.json +10 -0
- package/tsconfig.spec.json +17 -0
package/CHANGELOG.md
ADDED
package/README.md
ADDED
package/karma.conf.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Karma configuration file, see link for more information
|
|
2
|
+
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
|
3
|
+
|
|
4
|
+
module.exports = config => {
|
|
5
|
+
config.set({
|
|
6
|
+
basePath: '',
|
|
7
|
+
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
|
8
|
+
plugins: [
|
|
9
|
+
require('karma-jasmine'),
|
|
10
|
+
require('karma-chrome-launcher'),
|
|
11
|
+
require('karma-jasmine-html-reporter'),
|
|
12
|
+
require('karma-coverage'),
|
|
13
|
+
require('@angular-devkit/build-angular/plugins/karma')
|
|
14
|
+
],
|
|
15
|
+
client: {
|
|
16
|
+
jasmine: {
|
|
17
|
+
// you can add configuration options for Jasmine here
|
|
18
|
+
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
|
|
19
|
+
// for example, you can disable the random execution with `random: false`
|
|
20
|
+
// or set a specific seed with `seed: 4321`
|
|
21
|
+
},
|
|
22
|
+
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
|
23
|
+
},
|
|
24
|
+
jasmineHtmlReporter: {
|
|
25
|
+
suppressAll: true // removes the duplicated traces
|
|
26
|
+
},
|
|
27
|
+
coverageReporter: {
|
|
28
|
+
dir: require('path').join(__dirname, '../../coverage/search-container'),
|
|
29
|
+
subdir: '.',
|
|
30
|
+
reporters: [
|
|
31
|
+
{ type: 'html' },
|
|
32
|
+
{ type: 'text-summary' }
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
reporters: ['progress', 'kjhtml'],
|
|
36
|
+
port: 9876,
|
|
37
|
+
colors: true,
|
|
38
|
+
logLevel: config.LOG_INFO,
|
|
39
|
+
autoWatch: true,
|
|
40
|
+
browsers: ['Chrome'],
|
|
41
|
+
singleRun: false,
|
|
42
|
+
failOnEmptyTestSuite: false,
|
|
43
|
+
restartOnFileChange: true
|
|
44
|
+
});
|
|
45
|
+
};
|
package/ng-package.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
|
|
3
|
+
"dest": "../../dist/search-container",
|
|
4
|
+
"assets": [
|
|
5
|
+
"CHANGELOG.md",
|
|
6
|
+
{
|
|
7
|
+
"input": "src/",
|
|
8
|
+
"glob": "_search-container-theme.scss",
|
|
9
|
+
"output": "."
|
|
10
|
+
}
|
|
11
|
+
],
|
|
12
|
+
"lib": {
|
|
13
|
+
"entryFile": "src/index.ts"
|
|
14
|
+
}
|
|
15
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hug/ngx-search-container",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "HUG Angular - search-container component",
|
|
5
|
+
"homepage": "https://github.com/dsi-hug/ngx-components",
|
|
6
|
+
"license": "GPL-3.0-only",
|
|
7
|
+
"author": "HUG - Hôpitaux Universitaires Genève",
|
|
8
|
+
"contributors": [
|
|
9
|
+
"badisi (https://github.com/badisi)",
|
|
10
|
+
"vapkse (https://github.com/vapkse)"
|
|
11
|
+
],
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/dsi-hug/ngx-components.git"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"angular",
|
|
18
|
+
"material",
|
|
19
|
+
"material design",
|
|
20
|
+
"components"
|
|
21
|
+
],
|
|
22
|
+
"sideEffects": false,
|
|
23
|
+
"scripts": {
|
|
24
|
+
"lint": "eslint . --fix",
|
|
25
|
+
"test": "ng test search-container",
|
|
26
|
+
"test:ci": "ng test search-container --watch=false --browsers=ChromeHeadless",
|
|
27
|
+
"build:ng": "ng build search-container -c=production",
|
|
28
|
+
"build": "nx build:ng @hug/ngx-search-container --verbose",
|
|
29
|
+
"release": "nx release -p=@hug/ngx-search-container --yes --verbose",
|
|
30
|
+
"release:dry-run": "nx release -p=@hug/ngx-search-container --verbose --dry-run"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"@angular/common": ">= 14",
|
|
34
|
+
"@angular/core": ">= 14",
|
|
35
|
+
"@angular/forms": ">= 14",
|
|
36
|
+
"@angular/material": ">= 14",
|
|
37
|
+
"rxjs": ">= 7.0.0",
|
|
38
|
+
"@hug/ngx-core": "1.1.5"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"tslib": "^2.6.3"
|
|
42
|
+
},
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
@use '@angular/material' as mat;
|
|
2
|
+
|
|
3
|
+
@mixin search-container-theme($theme) {
|
|
4
|
+
$primary: map-get($theme, primary);
|
|
5
|
+
$accent: map-get($theme, accent);
|
|
6
|
+
$background: map-get($theme, background);
|
|
7
|
+
$foreground: map-get($theme, foreground);
|
|
8
|
+
|
|
9
|
+
search-container {
|
|
10
|
+
.search-container {
|
|
11
|
+
background-color: mat.get-color-from-palette($background, background, 0.2);
|
|
12
|
+
|
|
13
|
+
&:hover {
|
|
14
|
+
background-color: mat.get-color-from-palette($background, background, 0.3);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
&:focus,
|
|
18
|
+
&:focus-within {
|
|
19
|
+
color: mat.get-color-from-palette($foreground, text, 0.8);
|
|
20
|
+
background-color: mat.get-color-from-palette($background, background, 0.8);
|
|
21
|
+
|
|
22
|
+
input {
|
|
23
|
+
color: mat.get-color-from-palette($foreground, text, 0.6);
|
|
24
|
+
|
|
25
|
+
&::placeholder {
|
|
26
|
+
color: mat.get-color-from-palette($foreground, text, 0.6);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.icon-clear {
|
|
31
|
+
transition: 0.3s ease-in-out;
|
|
32
|
+
color: mat.get-color-from-palette($foreground, disabled);
|
|
33
|
+
|
|
34
|
+
&:hover {
|
|
35
|
+
color: mat.get-color-from-palette($foreground, text);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.icon-clear {
|
|
41
|
+
:hover {
|
|
42
|
+
color: mat.get-color-from-palette($background, background)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
input {
|
|
47
|
+
color: mat.get-color-from-palette($background, background, 0.8);
|
|
48
|
+
|
|
49
|
+
&::placeholder {
|
|
50
|
+
color: mat.get-color-from-palette($background, background, 0.8);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.search-container-mobile {
|
|
56
|
+
&.active {
|
|
57
|
+
background-color: mat.get-color-from-palette($background, background);
|
|
58
|
+
color: mat.get-color-from-palette($foreground, text);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.search-container {
|
|
62
|
+
&::placeholder {
|
|
63
|
+
color: mat.get-color-from-palette($foreground, secondary-text);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
input {
|
|
67
|
+
color: mat.get-color-from-palette($foreground, text, 0.6);
|
|
68
|
+
|
|
69
|
+
&::placeholder {
|
|
70
|
+
color: mat.get-color-from-palette($foreground, text, 0.6);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<div *ngIf="mediaService.isHandset$ | async; else noHandsetTpl" class="search-container-mobile mobile" [class.active]="activeSearch$ | async" #mobileSearch>
|
|
2
|
+
<ng-container *ngIf="activeSearch$ | async; else noActiveSearchTpl">
|
|
3
|
+
<mat-icon (click)="activeSearch$.next(false)">arrow_back</mat-icon>
|
|
4
|
+
<ng-container *ngTemplateOutlet="searchContainerTpl"></ng-container>
|
|
5
|
+
</ng-container>
|
|
6
|
+
<ng-template #noActiveSearchTpl>
|
|
7
|
+
<mat-icon (click)="activeSearch$.next(true)">search</mat-icon>
|
|
8
|
+
</ng-template>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<ng-template #noHandsetTpl>
|
|
12
|
+
<ng-container *ngTemplateOutlet="searchContainerTpl"></ng-container>
|
|
13
|
+
</ng-template>
|
|
14
|
+
|
|
15
|
+
<ng-template #searchContainerTpl>
|
|
16
|
+
<div class="search-container">
|
|
17
|
+
<mat-icon>search</mat-icon>
|
|
18
|
+
<ng-content></ng-content>
|
|
19
|
+
<mat-icon *ngIf="searchInputValue$ | async" class="icon-clear" [matTooltip]="clearTooltip" (click)="reset()">close</mat-icon>
|
|
20
|
+
<ng-container *ngTemplateOutlet="right"></ng-container>
|
|
21
|
+
</div>
|
|
22
|
+
</ng-template>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
search-container {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex: 1 1 auto;
|
|
4
|
+
padding-left: 0.5rem;
|
|
5
|
+
|
|
6
|
+
.search-container {
|
|
7
|
+
width: 100%;
|
|
8
|
+
|
|
9
|
+
&.mobile {
|
|
10
|
+
justify-self: flex-end;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
&:not(.mobile) {
|
|
14
|
+
min-width: 35vw;
|
|
15
|
+
max-width: 800px;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
transition: 0.3s ease-in-out;
|
|
19
|
+
border-radius: 4px;
|
|
20
|
+
height: 40px;
|
|
21
|
+
display: flex;
|
|
22
|
+
|
|
23
|
+
.mat-icon {
|
|
24
|
+
padding: 0.5rem;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.icon-clear {
|
|
28
|
+
cursor: pointer;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
input {
|
|
32
|
+
width: 100%;
|
|
33
|
+
background: none;
|
|
34
|
+
border: none;
|
|
35
|
+
outline: none;
|
|
36
|
+
font-size: 1.2rem;
|
|
37
|
+
|
|
38
|
+
&:focus {
|
|
39
|
+
outline: none;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.search-container-mobile {
|
|
45
|
+
width: 100%;
|
|
46
|
+
display: flex;
|
|
47
|
+
justify-content: flex-end;
|
|
48
|
+
height: 40px;
|
|
49
|
+
|
|
50
|
+
&.active {
|
|
51
|
+
display: flex;
|
|
52
|
+
align-items: center;
|
|
53
|
+
position: fixed;
|
|
54
|
+
top: 0;
|
|
55
|
+
left: 0;
|
|
56
|
+
width: 100vw;
|
|
57
|
+
height: 56px;
|
|
58
|
+
z-index: 10;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.mat-icon {
|
|
62
|
+
padding: 0.5rem;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { AfterContentInit, ChangeDetectionStrategy, Component, ContentChild, Directive, ElementRef, EventEmitter, Input, NgZone, Optional, Output, Self, TemplateRef, ViewEncapsulation } from '@angular/core';
|
|
2
|
+
import { NgControl } from '@angular/forms';
|
|
3
|
+
import { Destroy, MediaService } from '@hug/ngx-core';
|
|
4
|
+
import { BehaviorSubject, distinctUntilChanged, first, Observable, shareReplay, switchMap, takeUntil, tap } from 'rxjs';
|
|
5
|
+
|
|
6
|
+
@Directive({
|
|
7
|
+
selector: '[searchInput]'
|
|
8
|
+
})
|
|
9
|
+
export class SearchInputDirective {
|
|
10
|
+
public constructor(
|
|
11
|
+
@Optional() @Self() public ngControl: NgControl,
|
|
12
|
+
private elementRef: ElementRef<HTMLElement>
|
|
13
|
+
) {
|
|
14
|
+
this.focus();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public focus(): void {
|
|
18
|
+
this.elementRef.nativeElement.focus();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@Component({
|
|
23
|
+
selector: 'search-container',
|
|
24
|
+
templateUrl: './search-container.component.html',
|
|
25
|
+
styleUrls: ['./search-container.component.scss'],
|
|
26
|
+
encapsulation: ViewEncapsulation.None,
|
|
27
|
+
changeDetection: ChangeDetectionStrategy.OnPush
|
|
28
|
+
})
|
|
29
|
+
export class SearchContainerComponent extends Destroy implements AfterContentInit {
|
|
30
|
+
|
|
31
|
+
@Output()
|
|
32
|
+
public readonly cleared = new EventEmitter<void>();
|
|
33
|
+
|
|
34
|
+
@Input()
|
|
35
|
+
public clearTooltip = 'Effacer la recherche';
|
|
36
|
+
|
|
37
|
+
@ContentChild('mobileSearch')
|
|
38
|
+
public mobileSearch: TemplateRef<unknown> | undefined;
|
|
39
|
+
|
|
40
|
+
protected readonly activeSearch$ = new BehaviorSubject(false);
|
|
41
|
+
|
|
42
|
+
protected searchInputValue$: Observable<string> | null | undefined;
|
|
43
|
+
|
|
44
|
+
private _searchInput: SearchInputDirective | undefined;
|
|
45
|
+
|
|
46
|
+
@ContentChild(SearchInputDirective)
|
|
47
|
+
public set searchInput(searchInput: SearchInputDirective) {
|
|
48
|
+
if (!searchInput) {
|
|
49
|
+
throw new Error('You need to add the attribute searchInput to the SearchContainerComponent');
|
|
50
|
+
}
|
|
51
|
+
if (!searchInput.ngControl) {
|
|
52
|
+
throw new Error('You need to add the attribute ngModel to the SearchContainerComponent');
|
|
53
|
+
}
|
|
54
|
+
this._searchInput = searchInput;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private _right: TemplateRef<unknown> | null = null;
|
|
58
|
+
|
|
59
|
+
@Input()
|
|
60
|
+
public set right(value: TemplateRef<unknown> | null) {
|
|
61
|
+
this._right = value;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public get right(): TemplateRef<unknown> | null {
|
|
65
|
+
return this._right;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public constructor(
|
|
69
|
+
protected mediaService: MediaService,
|
|
70
|
+
private zone: NgZone
|
|
71
|
+
) {
|
|
72
|
+
super();
|
|
73
|
+
|
|
74
|
+
this.activeSearch$.pipe(
|
|
75
|
+
switchMap(activeSearch => this.zone.onStable.pipe(
|
|
76
|
+
first(),
|
|
77
|
+
tap(() => {
|
|
78
|
+
if (!activeSearch) {
|
|
79
|
+
this.reset();
|
|
80
|
+
}
|
|
81
|
+
this._searchInput?.focus();
|
|
82
|
+
})
|
|
83
|
+
)),
|
|
84
|
+
takeUntil(this.destroyed$)
|
|
85
|
+
).subscribe();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
public ngAfterContentInit(): void {
|
|
89
|
+
this.searchInputValue$ = this._searchInput?.ngControl?.valueChanges?.pipe(
|
|
90
|
+
distinctUntilChanged(),
|
|
91
|
+
shareReplay({ bufferSize: 1, refCount: false })
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
public reset(): void {
|
|
96
|
+
this._searchInput?.ngControl?.reset();
|
|
97
|
+
this.cleared.emit();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { CommonModule } from '@angular/common';
|
|
2
|
+
import { NgModule } from '@angular/core';
|
|
3
|
+
import { FormsModule } from '@angular/forms';
|
|
4
|
+
import { MatButtonModule } from '@angular/material/button';
|
|
5
|
+
import { MatIconModule } from '@angular/material/icon';
|
|
6
|
+
import { MatTooltipModule } from '@angular/material/tooltip';
|
|
7
|
+
|
|
8
|
+
import { SearchContainerComponent, SearchInputDirective } from './search-container.component';
|
|
9
|
+
|
|
10
|
+
@NgModule({
|
|
11
|
+
imports: [
|
|
12
|
+
CommonModule,
|
|
13
|
+
FormsModule,
|
|
14
|
+
MatButtonModule,
|
|
15
|
+
MatIconModule,
|
|
16
|
+
MatTooltipModule
|
|
17
|
+
],
|
|
18
|
+
declarations: [
|
|
19
|
+
SearchContainerComponent,
|
|
20
|
+
SearchInputDirective
|
|
21
|
+
],
|
|
22
|
+
exports: [
|
|
23
|
+
SearchContainerComponent,
|
|
24
|
+
SearchInputDirective
|
|
25
|
+
]
|
|
26
|
+
})
|
|
27
|
+
export class SearchContainerModule { }
|
package/src/test.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
|
|
2
|
+
|
|
3
|
+
import 'zone.js';
|
|
4
|
+
import 'zone.js/testing';
|
|
5
|
+
|
|
6
|
+
import { getTestBed } from '@angular/core/testing';
|
|
7
|
+
import {
|
|
8
|
+
BrowserDynamicTestingModule,
|
|
9
|
+
platformBrowserDynamicTesting
|
|
10
|
+
} from '@angular/platform-browser-dynamic/testing';
|
|
11
|
+
|
|
12
|
+
declare const require: {
|
|
13
|
+
context: (path: string, deep?: boolean, filter?: RegExp) => {
|
|
14
|
+
<T>(id: string): T;
|
|
15
|
+
keys: () => string[];
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// First, initialize the Angular testing environment.
|
|
20
|
+
getTestBed().initTestEnvironment(
|
|
21
|
+
BrowserDynamicTestingModule,
|
|
22
|
+
platformBrowserDynamicTesting()
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
// Then we find all the tests.
|
|
26
|
+
const context = require.context('./', true, /\.spec\.ts$/);
|
|
27
|
+
// And load the modules.
|
|
28
|
+
context.keys().forEach(context);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
|
2
|
+
{
|
|
3
|
+
"extends": "../../tsconfig.base.json",
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
"outDir": "../../out-tsc/lib",
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"declarationMap": true,
|
|
8
|
+
"inlineSources": true,
|
|
9
|
+
"types": []
|
|
10
|
+
},
|
|
11
|
+
"exclude": [
|
|
12
|
+
"src/test.ts",
|
|
13
|
+
"**/*.spec.ts"
|
|
14
|
+
]
|
|
15
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
|
2
|
+
{
|
|
3
|
+
"extends": "../../tsconfig.json",
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
"outDir": "../../out-tsc/spec",
|
|
6
|
+
"types": [
|
|
7
|
+
"jasmine"
|
|
8
|
+
]
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"src/test.ts"
|
|
12
|
+
],
|
|
13
|
+
"include": [
|
|
14
|
+
"**/*.spec.ts",
|
|
15
|
+
"**/*.d.ts"
|
|
16
|
+
]
|
|
17
|
+
}
|