sn-datatable 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,134 @@
1
+ # sn-table
2
+ A powerful and flexible data table component for Angular with sorting, pagination, and multiple styling options.
3
+ ## Overview
4
+ The `sn-table` component provides:
5
+ - ✅ Sortable columns (click header to sort)
6
+ - ✅ Pagination with next/previous navigation
7
+ - ✅ Multiple row styling (striped, hoverable, bordered)
8
+ - ✅ Responsive table layout
9
+ - ✅ Empty state handling
10
+ - ✅ Support for multiple data types
11
+ - ✅ Customizable column alignment
12
+ - ✅ Full accessibility support
13
+ ## Installation
14
+ ```bash
15
+ npm install sn-table
16
+ ```
17
+ ## Usage
18
+ ### Basic Table
19
+ ```typescript
20
+ import { Component } from '@angular/core';
21
+ import { SnTableComponent, TableColumn, TableRow } from 'sn-table';
22
+ @Component({
23
+ selector: 'app-demo',
24
+ template: `
25
+ <sn-table
26
+ [columns]="columns"
27
+ [data]="employees"
28
+ ></sn-table>
29
+ `,
30
+ imports: [SnTableComponent]
31
+ })
32
+ export class DemoComponent {
33
+ columns: TableColumn[] = [
34
+ { header: 'ID', key: 'id', sortable: true },
35
+ { header: 'Name', key: 'name', sortable: true },
36
+ { header: 'Email', key: 'email', sortable: true },
37
+ { header: 'Status', key: 'status', sortable: false },
38
+ ];
39
+ employees: TableRow[] = [
40
+ { id: 1, name: 'Alice', email: 'alice@example.com', status: 'Active' },
41
+ { id: 2, name: 'Bob', email: 'bob@example.com', status: 'Inactive' },
42
+ { id: 3, name: 'Charlie', email: 'charlie@example.com', status: 'Active' },
43
+ ];
44
+ }
45
+ ```
46
+ ### With Styling Options
47
+ ```typescript
48
+ <sn-table
49
+ [columns]="columns"
50
+ [data]="data"
51
+ [striped]="true"
52
+ [hoverable]="true"
53
+ [bordered]="false"
54
+ [pageSize]="25"
55
+ (sorted)="onSort($event)"
56
+ ></sn-table>
57
+ ```
58
+ ### With Custom Column Width and Alignment
59
+ ```typescript
60
+ columns: TableColumn[] = [
61
+ { header: 'ID', key: 'id', width: '100px', align: 'center' },
62
+ { header: 'Name', key: 'name', width: '200px', align: 'left' },
63
+ { header: 'Email', key: 'email', width: '250px', align: 'left' },
64
+ { header: 'Amount', key: 'amount', width: '150px', align: 'right' },
65
+ ];
66
+ ```
67
+ ## API
68
+ ### Inputs
69
+ | Input | Type | Default | Description |
70
+ |-------|------|---------|-------------|
71
+ | `columns` | `TableColumn[]` | `[]` | Column definitions |
72
+ | `data` | `TableRow[]` | `[]` | Table data rows |
73
+ | `striped` | `boolean` | `true` | Alternate row colors |
74
+ | `hoverable` | `boolean` | `true` | Highlight row on hover |
75
+ | `bordered` | `boolean` | `false` | Show table border |
76
+ | `pageSize` | `number` | `10` | Rows per page |
77
+ ### Outputs
78
+ | Output | Type | Description |
79
+ |--------|------|-------------|
80
+ | `sorted` | `EventEmitter<SortEvent>` | Emitted when column is sorted |
81
+ ### Interfaces
82
+ ```typescript
83
+ interface TableColumn {
84
+ header: string; // Column header text
85
+ key: string; // Data key (object property)
86
+ sortable?: boolean; // Allow sorting (default: true)
87
+ width?: string; // CSS width (e.g., '200px', '20%')
88
+ align?: 'left' | 'center' | 'right'; // Text alignment
89
+ }
90
+ interface TableRow {
91
+ [key: string]: any; // Any key-value pairs
92
+ }
93
+ interface SortEvent {
94
+ column: string; // Sorted column key
95
+ direction: 'asc' | 'desc'; // Sort direction
96
+ }
97
+ ```
98
+ ## Features
99
+ ### Sorting
100
+ Click any sortable column header to sort. Click again to reverse direction. Supports:
101
+ - Strings (alphabetical)
102
+ - Numbers (numeric)
103
+ - Dates (chronological)
104
+ ### Pagination
105
+ Automatically paginated based on `pageSize`. Navigation buttons appear when data exceeds one page.
106
+ ### Styling Options
107
+ - **Striped:** Alternate row background colors
108
+ - **Hoverable:** Highlight rows on hover
109
+ - **Bordered:** Show table border
110
+ ## Styling
111
+ The table uses Tailwind CSS utilities and custom SCSS. Customization via:
112
+ - CSS variables (future)
113
+ - SCSS variables override
114
+ - Custom CSS classes
115
+ ## Testing
116
+ ```bash
117
+ ng test sn-table
118
+ ```
119
+ ## Building
120
+ ```bash
121
+ ng build sn-table
122
+ ```
123
+ ## Accessibility
124
+ - Semantic HTML table structure
125
+ - Proper heading hierarchy
126
+ - Sortable column indicators
127
+ - Keyboard navigation support
128
+ - Screen reader friendly
129
+ ## Best Practices
130
+ 1. **Make relevant columns sortable** - Set `sortable: false` for status/action columns
131
+ 2. **Set appropriate column widths** - Fixed widths for better layout control
132
+ 3. **Use correct alignment** - Right-align numbers, left-align text
133
+ 4. **Handle empty states** - Component shows "No data available" message
134
+ 5. **Optimize page size** - Balance between scrolling and loading time
@@ -0,0 +1,7 @@
1
+ {
2
+ "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3
+ "dest": "../../dist/sn-table",
4
+ "lib": {
5
+ "entryFile": "src/public-api.ts"
6
+ }
7
+ }
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "sn-datatable",
3
+ "version": "0.0.1",
4
+ "description": "Angular data table component with sorting and pagination - SnUI Library",
5
+ "author": {
6
+ "name": "Swapnil Nakate",
7
+ "email": "nakate.swapnil7@gmail.com",
8
+ "url": "https://swapnilnakate.in"
9
+ },
10
+ "keywords": [
11
+ "table",
12
+ "data-table",
13
+ "angular",
14
+ "sorting",
15
+ "pagination",
16
+ "tailwindcss",
17
+ "accessible",
18
+ "snui"
19
+ ],
20
+ "peerDependencies": {
21
+ "@angular/common": "^21.2.0",
22
+ "@angular/core": "^21.2.0"
23
+ },
24
+ "dependencies": {
25
+ "tslib": "^2.3.0"
26
+ },
27
+ "sideEffects": false,
28
+ "bugs": {
29
+ "url": "https://github.com/swapnilnakate7/sn-ui/issues",
30
+ "email": "nakate.swapnil7@gmail.com"
31
+ },
32
+ "repository": {
33
+ "url": "https://github.com/swapnilnakate7/sn-ui",
34
+ "type": "git",
35
+ "directory": "projects/sn-table"
36
+ },
37
+ "license": "MIT"
38
+ }
@@ -0,0 +1,71 @@
1
+ <div class="sn-table-wrapper">
2
+ <table class="sn-table" [class.striped]="striped" [class.hoverable]="hoverable" [class.bordered]="bordered">
3
+ <thead>
4
+ <tr>
5
+ @for (column of columns; track column.key) {
6
+ <th
7
+ class="sn-table-header"
8
+ [style.width]="column.width"
9
+ [style.text-align]="column.align || 'left'"
10
+ [class.sortable]="column.sortable !== false"
11
+ (click)="column.sortable !== false && sort(column.key)"
12
+ >
13
+ <span class="sn-table-header-content">
14
+ {{ column.header }}
15
+ @if (column.sortable !== false) {
16
+ <span class="sn-table-sort-icon">{{ getSortIcon(column.key) }}</span>
17
+ }
18
+ </span>
19
+ </th>
20
+ }
21
+ </tr>
22
+ </thead>
23
+ <tbody>
24
+ @if (paginatedData.length > 0) {
25
+ @for (row of paginatedData; track $index) {
26
+ <tr class="sn-table-row">
27
+ @for (column of columns; track column.key) {
28
+ <td
29
+ class="sn-table-cell"
30
+ [style.text-align]="column.align || 'left'"
31
+ >
32
+ {{ row[column.key] }}
33
+ </td>
34
+ }
35
+ </tr>
36
+ }
37
+ } @else {
38
+ <tr>
39
+ <td [attr.colspan]="columns.length" class="sn-table-empty">
40
+ No data available
41
+ </td>
42
+ </tr>
43
+ }
44
+ </tbody>
45
+ </table>
46
+
47
+ @if (totalPages > 1) {
48
+ <div class="sn-table-pagination">
49
+ <button
50
+ class="sn-table-btn-prev"
51
+ (click)="prevPage()"
52
+ [disabled]="currentPage === 1"
53
+ >
54
+ ← Previous
55
+ </button>
56
+
57
+ <span class="sn-table-page-info">
58
+ Page {{ currentPage }} of {{ totalPages }}
59
+ </span>
60
+
61
+ <button
62
+ class="sn-table-btn-next"
63
+ (click)="nextPage()"
64
+ [disabled]="currentPage === totalPages"
65
+ >
66
+ Next →
67
+ </button>
68
+ </div>
69
+ }
70
+ </div>
71
+
@@ -0,0 +1,140 @@
1
+ .sn-table-wrapper {
2
+ display: flex;
3
+ flex-direction: column;
4
+ width: 100%;
5
+ overflow: auto;
6
+ }
7
+
8
+ .sn-table {
9
+ width: 100%;
10
+ border-collapse: collapse;
11
+ font-size: 0.875rem;
12
+ background-color: #ffffff;
13
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
14
+ border-radius: 0.375rem;
15
+ overflow: hidden;
16
+
17
+ &.bordered {
18
+ border: 1px solid #e5e7eb;
19
+ }
20
+ }
21
+
22
+ .sn-table-header {
23
+ padding: 0.75rem;
24
+ background-color: #f9fafb;
25
+ font-weight: 600;
26
+ color: #374151;
27
+ border-bottom: 2px solid #e5e7eb;
28
+ user-select: none;
29
+ transition: all 0.2s ease-in-out;
30
+
31
+ &.sortable {
32
+ cursor: pointer;
33
+
34
+ &:hover {
35
+ background-color: #f3f4f6;
36
+ }
37
+ }
38
+ }
39
+
40
+ .sn-table-header-content {
41
+ display: flex;
42
+ align-items: center;
43
+ gap: 0.5rem;
44
+ }
45
+
46
+ .sn-table-sort-icon {
47
+ font-size: 0.75rem;
48
+ color: #9ca3af;
49
+ transition: color 0.2s ease-in-out;
50
+ }
51
+
52
+ .sn-table-header:hover .sn-table-sort-icon {
53
+ color: #3b82f6;
54
+ }
55
+
56
+ .sn-table-row {
57
+ border-bottom: 1px solid #f3f4f6;
58
+ transition: background-color 0.15s ease-in-out;
59
+
60
+ &:last-child {
61
+ border-bottom: none;
62
+ }
63
+
64
+ @media (hover: hover) {
65
+ .sn-table.hoverable &:hover {
66
+ background-color: #f9fafb;
67
+ }
68
+ }
69
+ }
70
+
71
+ .sn-table-cell {
72
+ padding: 0.75rem;
73
+ color: #1f2937;
74
+
75
+ &:first-child {
76
+ font-weight: 500;
77
+ }
78
+ }
79
+
80
+ .sn-table.striped tbody tr:nth-child(odd) {
81
+ background-color: #f9fafb;
82
+
83
+ &:hover {
84
+ background-color: #f3f4f6;
85
+ }
86
+ }
87
+
88
+ .sn-table-empty {
89
+ text-align: center;
90
+ color: #9ca3af;
91
+ padding: 2rem 0.75rem !important;
92
+ font-style: italic;
93
+ }
94
+
95
+ .sn-table-pagination {
96
+ display: flex;
97
+ align-items: center;
98
+ justify-content: center;
99
+ gap: 1rem;
100
+ padding: 1rem;
101
+ border-top: 1px solid #e5e7eb;
102
+ background-color: #f9fafb;
103
+ flex-wrap: wrap;
104
+ }
105
+
106
+ .sn-table-page-info {
107
+ font-size: 0.875rem;
108
+ color: #6b7280;
109
+ font-weight: 500;
110
+ min-width: 8rem;
111
+ text-align: center;
112
+ }
113
+
114
+ .sn-table-btn-prev,
115
+ .sn-table-btn-next {
116
+ padding: 0.5rem 1rem;
117
+ border: 1px solid #d1d5db;
118
+ border-radius: 0.375rem;
119
+ background-color: #ffffff;
120
+ color: #374151;
121
+ font-size: 0.875rem;
122
+ cursor: pointer;
123
+ transition: all 0.2s ease-in-out;
124
+
125
+ &:hover:not(:disabled) {
126
+ border-color: #3b82f6;
127
+ color: #3b82f6;
128
+ background-color: #f0f9ff;
129
+ }
130
+
131
+ &:disabled {
132
+ opacity: 0.5;
133
+ cursor: not-allowed;
134
+ }
135
+
136
+ &:active:not(:disabled) {
137
+ background-color: #dbeafe;
138
+ }
139
+ }
140
+
@@ -0,0 +1,139 @@
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+ import { SnDatatableComponent, TableColumn, TableRow } from './sn-datatable';
3
+
4
+ describe('SnDatatableComponent', () => {
5
+ let component: SnDatatableComponent;
6
+ let fixture: ComponentFixture<SnDatatableComponent>;
7
+
8
+ const mockColumns: TableColumn[] = [
9
+ { header: 'ID', key: 'id', sortable: true },
10
+ { header: 'Name', key: 'name', sortable: true },
11
+ { header: 'Email', key: 'email', sortable: true },
12
+ { header: 'Status', key: 'status', sortable: false },
13
+ ];
14
+
15
+ const mockData: TableRow[] = [
16
+ { id: 1, name: 'Alice', email: 'alice@example.com', status: 'Active' },
17
+ { id: 2, name: 'Bob', email: 'bob@example.com', status: 'Inactive' },
18
+ { id: 3, name: 'Charlie', email: 'charlie@example.com', status: 'Active' },
19
+ { id: 4, name: 'David', email: 'david@example.com', status: 'Active' },
20
+ { id: 5, name: 'Eve', email: 'eve@example.com', status: 'Inactive' },
21
+ ];
22
+
23
+ beforeEach(async () => {
24
+ await TestBed.configureTestingModule({
25
+ imports: [SnDatatableComponent],
26
+ }).compileComponents();
27
+
28
+ fixture = TestBed.createComponent(SnDatatableComponent);
29
+ component = fixture.componentInstance;
30
+ component.columns = mockColumns;
31
+ component.data = mockData;
32
+ fixture.detectChanges();
33
+ });
34
+
35
+ it('should create', () => {
36
+ expect(component).toBeTruthy();
37
+ });
38
+
39
+ it('should display all data on first page', () => {
40
+ expect(component.paginatedData.length).toBe(5);
41
+ });
42
+
43
+ it('should sort data in ascending order', () => {
44
+ component.sort('name');
45
+ fixture.detectChanges();
46
+ expect(component.sortedData[0]['name']).toBe('Alice');
47
+ expect(component.sortDirection).toBe('asc');
48
+ });
49
+
50
+ it('should sort data in descending order on second click', () => {
51
+ component.sort('name');
52
+ component.sort('name');
53
+ fixture.detectChanges();
54
+ expect(component.sortedData[0]['name']).toBe('Eve');
55
+ expect(component.sortDirection).toBe('desc');
56
+ });
57
+
58
+ it('should emit sorted event', () => {
59
+ const spy = spyOn(component.sorted, 'emit');
60
+ component.sort('email');
61
+ expect(spy).toHaveBeenCalledWith({ column: 'email', direction: 'asc' });
62
+ });
63
+
64
+ it('should not sort non-sortable columns', () => {
65
+ component.sort('status');
66
+ expect(component.sortColumn).toBeNull();
67
+ });
68
+
69
+ it('should handle pagination correctly', () => {
70
+ component.pageSize = 2;
71
+ component.ngOnInit();
72
+ fixture.detectChanges();
73
+
74
+ expect(component.totalPages).toBe(3);
75
+ expect(component.paginatedData.length).toBe(2);
76
+ });
77
+
78
+ it('should navigate to next page', () => {
79
+ component.pageSize = 2;
80
+ component.ngOnInit();
81
+ component.nextPage();
82
+ expect(component.currentPage).toBe(2);
83
+ });
84
+
85
+ it('should navigate to previous page', () => {
86
+ component.pageSize = 2;
87
+ component.ngOnInit();
88
+ component.currentPage = 2;
89
+ component.prevPage();
90
+ expect(component.currentPage).toBe(1);
91
+ });
92
+
93
+ it('should not go beyond last page', () => {
94
+ component.pageSize = 2;
95
+ component.ngOnInit();
96
+ component.goToPage(10);
97
+ expect(component.currentPage).toBe(1);
98
+ });
99
+
100
+ it('should display correct sort icon', () => {
101
+ component.sort('name');
102
+ expect(component.getSortIcon('name')).toBe('↑');
103
+ component.sort('name');
104
+ expect(component.getSortIcon('name')).toBe('↓');
105
+ expect(component.getSortIcon('email')).toBe('↕');
106
+ });
107
+
108
+ it('should handle empty data', () => {
109
+ component.data = [];
110
+ component.ngOnInit();
111
+ fixture.detectChanges();
112
+ expect(component.paginatedData.length).toBe(0);
113
+ });
114
+
115
+ it('should sort numbers correctly', () => {
116
+ component.sort('id');
117
+ fixture.detectChanges();
118
+ expect(component.sortedData[0]['id']).toBe(1);
119
+ });
120
+
121
+ it('should apply striped style', () => {
122
+ component.striped = true;
123
+ fixture.detectChanges();
124
+ expect(component.striped).toBe(true);
125
+ });
126
+
127
+ it('should apply hoverable style', () => {
128
+ component.hoverable = true;
129
+ fixture.detectChanges();
130
+ expect(component.hoverable).toBe(true);
131
+ });
132
+
133
+ it('should apply bordered style', () => {
134
+ component.bordered = true;
135
+ fixture.detectChanges();
136
+ expect(component.bordered).toBe(true);
137
+ });
138
+ });
139
+
@@ -0,0 +1,117 @@
1
+ import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+
4
+ export interface TableColumn {
5
+ header: string;
6
+ key: string;
7
+ sortable?: boolean;
8
+ width?: string;
9
+ align?: 'left' | 'center' | 'right';
10
+ }
11
+
12
+ export interface TableRow {
13
+ [key: string]: any;
14
+ }
15
+
16
+ export interface SortEvent {
17
+ column: string;
18
+ direction: 'asc' | 'desc';
19
+ }
20
+
21
+ @Component({
22
+ selector: 'sn-datatable',
23
+ standalone: true,
24
+ imports: [CommonModule],
25
+ templateUrl: './sn-datatable.html',
26
+ styleUrl: './sn-datatable.scss',
27
+ })
28
+ export class SnDatatableComponent implements OnInit {
29
+ @Input() columns: TableColumn[] = [];
30
+ @Input() data: TableRow[] = [];
31
+ @Input() striped: boolean = true;
32
+ @Input() hoverable: boolean = true;
33
+ @Input() bordered: boolean = false;
34
+ @Input() pageSize: number = 10;
35
+ @Output() sorted = new EventEmitter<SortEvent>();
36
+
37
+ sortedData: TableRow[] = [];
38
+ paginatedData: TableRow[] = [];
39
+ currentPage: number = 1;
40
+ totalPages: number = 1;
41
+ sortColumn: string | null = null;
42
+ sortDirection: 'asc' | 'desc' = 'asc';
43
+
44
+ ngOnInit(): void {
45
+ this.updateTable();
46
+ }
47
+
48
+ ngOnChanges(): void {
49
+ this.updateTable();
50
+ }
51
+
52
+ sort(column: string): void {
53
+ const col = this.columns.find(c => c.key === column);
54
+ if (!col || col.sortable === false) return;
55
+
56
+ if (this.sortColumn === column) {
57
+ this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';
58
+ } else {
59
+ this.sortColumn = column;
60
+ this.sortDirection = 'asc';
61
+ }
62
+
63
+ this.sorted.emit({ column, direction: this.sortDirection });
64
+ this.updateTable();
65
+ }
66
+
67
+ private updateTable(): void {
68
+ // Sort data
69
+ this.sortedData = [...this.data];
70
+ if (this.sortColumn) {
71
+ this.sortedData.sort((a, b) => {
72
+ const aVal = a[this.sortColumn!];
73
+ const bVal = b[this.sortColumn!];
74
+
75
+ if (aVal === null || aVal === undefined) return 1;
76
+ if (bVal === null || bVal === undefined) return -1;
77
+
78
+ let comparison = 0;
79
+ if (typeof aVal === 'string') {
80
+ comparison = aVal.localeCompare(bVal);
81
+ } else if (typeof aVal === 'number') {
82
+ comparison = aVal - bVal;
83
+ } else if (aVal instanceof Date && bVal instanceof Date) {
84
+ comparison = aVal.getTime() - bVal.getTime();
85
+ }
86
+
87
+ return this.sortDirection === 'asc' ? comparison : -comparison;
88
+ });
89
+ }
90
+
91
+ // Paginate
92
+ this.totalPages = Math.ceil(this.sortedData.length / this.pageSize);
93
+ const startIdx = (this.currentPage - 1) * this.pageSize;
94
+ this.paginatedData = this.sortedData.slice(startIdx, startIdx + this.pageSize);
95
+ }
96
+
97
+ goToPage(page: number): void {
98
+ if (page >= 1 && page <= this.totalPages) {
99
+ this.currentPage = page;
100
+ this.updateTable();
101
+ }
102
+ }
103
+
104
+ nextPage(): void {
105
+ this.goToPage(this.currentPage + 1);
106
+ }
107
+
108
+ prevPage(): void {
109
+ this.goToPage(this.currentPage - 1);
110
+ }
111
+
112
+ getSortIcon(column: string): string {
113
+ if (this.sortColumn !== column) return '↕';
114
+ return this.sortDirection === 'asc' ? '↑' : '↓';
115
+ }
116
+ }
117
+
@@ -0,0 +1,5 @@
1
+ /*
2
+ * Public API Surface of sn-datatable
3
+ */
4
+
5
+ export * from './lib/sn-datatable';
@@ -0,0 +1,17 @@
1
+ /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
2
+ /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
3
+ {
4
+ "extends": "../../tsconfig.json",
5
+ "compilerOptions": {
6
+ "outDir": "../../out-tsc/lib",
7
+ "declaration": true,
8
+ "declarationMap": true,
9
+ "types": []
10
+ },
11
+ "include": [
12
+ "src/**/*.ts"
13
+ ],
14
+ "exclude": [
15
+ "**/*.spec.ts"
16
+ ]
17
+ }
@@ -0,0 +1,11 @@
1
+ /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
2
+ /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
3
+ {
4
+ "extends": "./tsconfig.lib.json",
5
+ "compilerOptions": {
6
+ "declarationMap": false
7
+ },
8
+ "angularCompilerOptions": {
9
+ "compilationMode": "partial"
10
+ }
11
+ }
@@ -0,0 +1,15 @@
1
+ /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
2
+ /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
3
+ {
4
+ "extends": "../../tsconfig.json",
5
+ "compilerOptions": {
6
+ "outDir": "../../out-tsc/spec",
7
+ "types": [
8
+ "jasmine"
9
+ ]
10
+ },
11
+ "include": [
12
+ "src/**/*.d.ts",
13
+ "src/**/*.spec.ts"
14
+ ]
15
+ }