@open-dcp/grid 0.0.1 → 0.2.17

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.
Files changed (46) hide show
  1. package/.github/workflows/main.yml +42 -0
  2. package/CLA.md +22 -0
  3. package/LICENSE +190 -0
  4. package/NOTICE.md +31 -0
  5. package/README.md +39 -39
  6. package/SECURITY.md +27 -0
  7. package/dist/GridService.d.ts +83 -0
  8. package/dist/GridService.js +410 -0
  9. package/dist/__tests__/GridServices.test.d.ts +1 -0
  10. package/dist/__tests__/GridServices.test.js +288 -0
  11. package/dist/index.d.ts +4 -0
  12. package/dist/index.js +1 -0
  13. package/dist/services/FilteringService.d.ts +34 -0
  14. package/dist/services/FilteringService.js +278 -0
  15. package/dist/services/FormattingService.d.ts +17 -0
  16. package/dist/services/FormattingService.js +179 -0
  17. package/dist/services/RowHeightService.d.ts +15 -0
  18. package/dist/services/RowHeightService.js +63 -0
  19. package/dist/services/SelectionService.d.ts +14 -0
  20. package/dist/services/SelectionService.js +55 -0
  21. package/dist/services/SortingService.d.ts +15 -0
  22. package/dist/services/SortingService.js +82 -0
  23. package/dist/services/__tests__/FilteringService.test.d.ts +1 -0
  24. package/dist/services/__tests__/FilteringService.test.js +140 -0
  25. package/dist/services/__tests__/FormattingService.test.d.ts +1 -0
  26. package/dist/services/__tests__/FormattingService.test.js +122 -0
  27. package/dist/types/grid.d.ts +71 -0
  28. package/dist/types/grid.js +1 -0
  29. package/dist/types/sorting.d.ts +8 -0
  30. package/dist/types/sorting.js +1 -0
  31. package/jest.config.js +16 -0
  32. package/package.json +40 -9
  33. package/reports/junit.xml +3 -0
  34. package/src/GridService.ts +561 -0
  35. package/src/__tests__/GridServices.test.ts +348 -0
  36. package/src/index.ts +17 -0
  37. package/src/services/FilteringService.ts +347 -0
  38. package/src/services/FormattingService.ts +205 -0
  39. package/src/services/RowHeightService.ts +81 -0
  40. package/src/services/SelectionService.ts +71 -0
  41. package/src/services/SortingService.ts +105 -0
  42. package/src/services/__tests__/FilteringService.test.ts +173 -0
  43. package/src/services/__tests__/FormattingService.test.ts +157 -0
  44. package/src/types/grid.ts +84 -0
  45. package/src/types/sorting.ts +9 -0
  46. package/tsconfig.json +19 -0
@@ -0,0 +1,105 @@
1
+ import type { GridService } from "../GridService";
2
+ import type { Row } from "../types/grid";
3
+ import { SortingCondition } from '../types/sorting';
4
+
5
+ export class SortingService {
6
+ private readonly gridService: GridService;
7
+ private readonly sortConditions: SortingCondition[];
8
+ private sortedIndexes: number[] | null = null;
9
+
10
+ constructor(gridService: GridService, sortConditions: SortingCondition[] = []) {
11
+ this.gridService = gridService;
12
+ this.sortConditions = sortConditions;
13
+ }
14
+
15
+ getSorts(): SortingCondition[]
16
+ {
17
+ return this.sortConditions;
18
+ }
19
+
20
+ getSortCondition(columnKey: string | number): SortingCondition[] {
21
+ return this.sortConditions.filter(condition => columnKey === condition.columnKey);
22
+ }
23
+
24
+ toggleSort(columnKey: string | number): void {
25
+ const existingIndex = this.sortConditions.findIndex(condition => condition.columnKey === columnKey);
26
+
27
+ if (existingIndex === -1) {
28
+ this.sortConditions.push({ columnKey, direction: 'asc' });
29
+ } else {
30
+ switch (this.sortConditions[existingIndex].direction) {
31
+ case 'asc':
32
+ this.sortConditions[existingIndex].direction = 'desc'
33
+ break;
34
+ case 'desc':
35
+ this.sortConditions.splice(existingIndex, 1)
36
+ break;
37
+ }
38
+ }
39
+
40
+ this.sortedIndexes = null;
41
+ }
42
+
43
+ applySorting(rows: Row[]): Row[] {
44
+ if (this.sortConditions.length === 0) {
45
+ return rows;
46
+ }
47
+
48
+ if (
49
+ this.sortedIndexes &&
50
+ this.sortedIndexes.length === rows.length &&
51
+ this.sortedIndexes.every(index => index >= 0 && index < rows.length)
52
+ ) {
53
+ return this.sortedIndexes.map(index => rows[index]); // Apply cached order
54
+ }
55
+
56
+ const indexedRows = rows.map((row, index) => ({ row, index }));
57
+
58
+ indexedRows.sort((a, b) => {
59
+ for (const { columnKey, direction } of this.sortConditions) {
60
+ const valueA = a.row[columnKey].value;
61
+ const valueB = b.row[columnKey].value;
62
+
63
+ let comparison = 0;
64
+
65
+ if (typeof valueA === 'string' && typeof valueB === 'string') {
66
+ comparison = valueA.localeCompare(valueB);
67
+ } else if (typeof valueA === 'number' && typeof valueB === 'number') {
68
+ comparison = valueA - valueB;
69
+ } else if (valueA instanceof Date && valueB instanceof Date) {
70
+ comparison = valueA.getTime() - valueB.getTime();
71
+ } else {
72
+ comparison = String(valueA).localeCompare(String(valueB));
73
+ }
74
+
75
+ if (direction === 'desc') {
76
+ comparison = -comparison;
77
+ }
78
+
79
+ if (comparison !== 0) {
80
+ return comparison;
81
+ }
82
+ }
83
+
84
+ return 0; // Keep stable sorting
85
+ });
86
+
87
+ const filteredIndexes = this.gridService.getFilterIndexes() ?? this.gridService.getRows().map((_, i) => i);;
88
+
89
+ this.gridService.clearIndexMap(); // Clear the existing mapping
90
+ indexedRows.forEach((indexedRow, i) => {
91
+ this.gridService.setIndexMappingValue(i, filteredIndexes[indexedRow.index]); // Map the sorted index to the original index
92
+ });
93
+
94
+ this.sortedIndexes = indexedRows.map(item => item.index);
95
+ return indexedRows.map(item => item.row); // Return sorted rows
96
+ }
97
+
98
+ getSortedIndexes(): number[] | null {
99
+ return this.sortedIndexes;
100
+ }
101
+
102
+ resetSorting() {
103
+ this.sortedIndexes = null;
104
+ }
105
+ }
@@ -0,0 +1,173 @@
1
+ import { FilteringService } from '../FilteringService';
2
+ import { GridService } from '../../GridService';
3
+ import { Column, Row } from '../../types/grid';
4
+ import { FilterNode } from '@open-dcp/filter';
5
+
6
+ describe('FilteringService', () => {
7
+ let gridService: GridService;
8
+ let filteringService: FilteringService;
9
+ let mockRows: Row[];
10
+ let mockColumns: Column[];
11
+
12
+ beforeEach(() => {
13
+ mockColumns = [
14
+ { key: 'id', label: 'ID', type: 'integer' },
15
+ { key: 'name', label: 'Name', type: 'string' },
16
+ { key: 'age', label: 'Age', type: 'number' },
17
+ { key: 'status', label: 'Status', type: 'string' },
18
+ ];
19
+
20
+ mockRows = [
21
+ { id: { raw: 1, value: 1 }, name: { raw: 'Alice', value: 'Alice' }, age: { raw: 30, value: 30 }, status: { raw: 'active', value: 'active' } },
22
+ { id: { raw: 2, value: 2 }, name: { raw: 'Bob', value: 'Bob' }, age: { raw: 25, value: 25 }, status: { raw: 'inactive', value: 'inactive' } },
23
+ { id: { raw: 3, value: 3 }, name: { raw: 'Charlie', value: 'Charlie' }, age: { raw: 35, value: 35 }, status: { raw: 'active', value: 'active' } },
24
+ ];
25
+
26
+ gridService = new GridService(mockRows, mockColumns);
27
+ filteringService = gridService['filteringService'];
28
+ });
29
+
30
+ describe('Basic Operations', () => {
31
+ it('should initialize with no filter', () => {
32
+ expect(filteringService.getFilterTree()).toBeNull();
33
+ expect(filteringService.getFilterIndexes()).toBeNull();
34
+ });
35
+
36
+ it('should filter by equals condition', () => {
37
+ const filter: FilterNode = {
38
+ type: 'condition',
39
+ columnKey: 'name',
40
+ operator: '=',
41
+ value: 'Alice'
42
+ };
43
+
44
+ filteringService.setFilterTree(filter);
45
+ expect(filteringService.getFilterIndexes()).toEqual([0]);
46
+ });
47
+
48
+ it('should filter by greater than condition', () => {
49
+ const filter: FilterNode = {
50
+ type: 'condition',
51
+ columnKey: 'age',
52
+ operator: '>',
53
+ value: 30
54
+ };
55
+
56
+ filteringService.setFilterTree(filter);
57
+ expect(filteringService.getFilterIndexes()).toEqual([2]);
58
+ });
59
+ });
60
+
61
+ describe('Group Filters', () => {
62
+ it('should apply AND group filter', () => {
63
+ const filter: FilterNode = {
64
+ type: 'group',
65
+ operator: 'and',
66
+ filters: [
67
+ { type: 'condition', columnKey: 'status', operator: '=', value: 'active' },
68
+ { type: 'condition', columnKey: 'age', operator: '>=', value: 30 }
69
+ ]
70
+ };
71
+
72
+ filteringService.setFilterTree(filter);
73
+ expect(filteringService.getFilterIndexes()).toEqual([0, 2]);
74
+ });
75
+
76
+ it('should apply OR group filter', () => {
77
+ const filter: FilterNode = {
78
+ type: 'group',
79
+ operator: 'or',
80
+ filters: [
81
+ { type: 'condition', columnKey: 'name', operator: '=', value: 'Alice' },
82
+ { type: 'condition', columnKey: 'name', operator: '=', value: 'Bob' }
83
+ ]
84
+ };
85
+
86
+ filteringService.setFilterTree(filter);
87
+ expect(filteringService.getFilterIndexes()).toEqual([0, 1]);
88
+ });
89
+ });
90
+
91
+ describe('Add and Reset Filters', () => {
92
+ it('should add filter and replace existing for same column', () => {
93
+ const filter1: FilterNode = { type: 'condition', columnKey: 'age', operator: '>', value: 25 };
94
+ const filter2: FilterNode = { type: 'condition', columnKey: 'age', operator: '<', value: 35 };
95
+
96
+ filteringService.addFilter(filter1);
97
+ filteringService.addFilter(filter2);
98
+
99
+ const tree = filteringService.getFilterTree();
100
+ expect(tree?.type).toBe('group');
101
+ if (tree?.type === 'group') {
102
+ expect(tree.filters.length).toBe(1);
103
+ expect(tree.filters[0]).toEqual(filter2);
104
+ }
105
+ });
106
+
107
+ it('should reset all filters', () => {
108
+ filteringService.setFilterTree({ type: 'condition', columnKey: 'status', operator: '=', value: 'active' });
109
+ filteringService.resetFilters();
110
+
111
+ expect(filteringService.getFilterTree()).toBeNull();
112
+ expect(filteringService.getFilterIndexes()).toBeNull();
113
+ });
114
+
115
+ it('should reset specific column filter', () => {
116
+ const filter: FilterNode = {
117
+ type: 'group',
118
+ operator: 'and',
119
+ filters: [
120
+ { type: 'condition', columnKey: 'status', operator: '=', value: 'active' },
121
+ { type: 'condition', columnKey: 'age', operator: '>', value: 25 }
122
+ ]
123
+ };
124
+
125
+ filteringService.setFilterTree(filter);
126
+ filteringService.resetFilter('status');
127
+
128
+ const tree = filteringService.getFilterTree();
129
+ if (tree?.type === 'group') {
130
+ expect(tree.filters.length).toBe(1);
131
+ expect((tree.filters[0] as any).columnKey).toBe('age');
132
+ }
133
+ });
134
+ });
135
+
136
+ describe('Helper Methods', () => {
137
+ it('should check if column has filter', () => {
138
+ filteringService.setFilterTree({ type: 'condition', columnKey: 'status', operator: '=', value: 'active' });
139
+
140
+ expect(filteringService.hasFilter('status')).toBe(true);
141
+ expect(filteringService.hasFilter('age')).toBe(false);
142
+ });
143
+
144
+ it('should apply filter to rows', () => {
145
+ filteringService.setFilterTree({ type: 'condition', columnKey: 'status', operator: '=', value: 'active' });
146
+ const filtered = filteringService.applyFilter(mockRows);
147
+
148
+ expect(filtered.length).toBe(2);
149
+ expect(filtered[0].name.value).toBe('Alice');
150
+ expect(filtered[1].name.value).toBe('Charlie');
151
+ });
152
+
153
+ it('should combine global search with filter tree using AND semantics', () => {
154
+ filteringService.setFilterTree({ type: 'condition', columnKey: 'status', operator: '=', value: 'active' });
155
+ filteringService.setGlobalSearch('char', { caseInsensitive: true });
156
+
157
+ const filtered = filteringService.applyFilter(mockRows);
158
+ expect(filtered.length).toBe(1);
159
+ expect(filtered[0].name.value).toBe('Charlie');
160
+ });
161
+
162
+ it('should use grid case-insensitive option when global search option is not set', () => {
163
+ const caseInsensitiveGridService = new GridService(mockRows, mockColumns, { caseInsensitiveFiltering: true });
164
+ const caseInsensitiveFilteringService = caseInsensitiveGridService['filteringService'];
165
+
166
+ caseInsensitiveFilteringService.setGlobalSearch('ALICE');
167
+
168
+ const filtered = caseInsensitiveFilteringService.applyFilter(mockRows);
169
+ expect(filtered.length).toBe(1);
170
+ expect(filtered[0].name.value).toBe('Alice');
171
+ });
172
+ });
173
+ });
@@ -0,0 +1,157 @@
1
+ import { FormattingService } from '../FormattingService';
2
+ import { GridService } from '../../GridService';
3
+ import { Column, Row } from '../../types/grid';
4
+
5
+ describe('FormattingService', () => {
6
+ let gridService: GridService;
7
+ let formattingService: FormattingService;
8
+ let mockColumns: Column[];
9
+
10
+ beforeEach(() => {
11
+ mockColumns = [
12
+ { key: 'id', label: 'ID', type: 'integer' },
13
+ { key: 'name', label: 'Name', type: 'string' },
14
+ { key: 'price', label: 'Price', type: 'number' },
15
+ { key: 'created', label: 'Created', type: 'date' },
16
+ { key: 'updated', label: 'Updated', type: 'datetime' },
17
+ { key: 'duration', label: 'Duration', type: 'duration' },
18
+ ];
19
+
20
+ gridService = new GridService([], mockColumns);
21
+ formattingService = gridService['formattingService'];
22
+ });
23
+
24
+ describe('applyFormattingToRow', () => {
25
+ it('should format a complete row with all column types', () => {
26
+ const row: Row = {
27
+ id: { raw: '123', value: '123' },
28
+ name: { raw: 'Product', value: 'Product' },
29
+ price: { raw: '99.99', value: '99.99' },
30
+ created: { raw: '2024-01-15', value: '2024-01-15' },
31
+ updated: { raw: '2024-01-15T14:30:00Z', value: '2024-01-15T14:30:00Z' },
32
+ duration: { raw: '3661', value: '3661' },
33
+ };
34
+
35
+ const formatted = formattingService.applyFormattingToRow(row);
36
+
37
+ expect(formatted.id.value).toBe(123);
38
+ expect(formatted.name.value).toBe('Product');
39
+ expect(formatted.price.value).toBe(99.99);
40
+ expect(formatted.duration.value).toBe(3661);
41
+ expect(formatted.created.raw).toBe('2024-01-15');
42
+ expect(formatted.updated.raw).toBe('2024-01-15T14:30:00Z');
43
+ });
44
+
45
+ it('should handle null values', () => {
46
+ const row: Row = {
47
+ id: { raw: null, value: null },
48
+ name: { raw: null, value: null },
49
+ };
50
+
51
+ const formatted = formattingService.applyFormattingToRow(row);
52
+
53
+ expect(formatted.id.value).toBeNull();
54
+ expect(formatted.name.value).toBeNull();
55
+ });
56
+ });
57
+
58
+ describe('formatCellValue', () => {
59
+ it('should transform integer values', () => {
60
+ const row: Row = { id: { raw: '42', value: '42' } };
61
+ const column: Column = { key: 'id', label: 'ID', type: 'integer' };
62
+
63
+ const result = formattingService.formatCellValue(row, column);
64
+ expect(result).toBe(42);
65
+ });
66
+
67
+ it('should transform number values', () => {
68
+ const row: Row = { price: { raw: '19.99', value: '19.99' } };
69
+ const column: Column = { key: 'price', label: 'Price', type: 'number' };
70
+
71
+ const result = formattingService.formatCellValue(row, column);
72
+ expect(result).toBe(19.99);
73
+ });
74
+
75
+ it('should transform date values to timestamps', () => {
76
+ const row: Row = { created: { raw: '2024-01-15', value: '2024-01-15' } };
77
+ const column: Column = { key: 'created', label: 'Created', type: 'date' };
78
+
79
+ const result = formattingService.formatCellValue(row, column);
80
+ expect(typeof result).toBe('number');
81
+ expect(result).toBeGreaterThan(0);
82
+ });
83
+
84
+ it('should transform string values', () => {
85
+ const row: Row = { name: { raw: 'Test', value: 'Test' } };
86
+ const column: Column = { key: 'name', label: 'Name', type: 'string' };
87
+
88
+ const result = formattingService.formatCellValue(row, column);
89
+ expect(result).toBe('Test');
90
+ });
91
+ });
92
+
93
+ describe('formatCellDisplay', () => {
94
+ it('should format integer for display', () => {
95
+ const row: Row = { id: { raw: 42, value: 42 } };
96
+ const column: Column = { key: 'id', label: 'ID', type: 'integer' };
97
+
98
+ const result = formattingService.formatCellDisplay(row, column);
99
+ expect(result.display).toBe('42');
100
+ });
101
+
102
+ it('should format date for display', () => {
103
+ const row: Row = { created: { raw: '2024-01-15', value: new Date('2024-01-15').getTime() } };
104
+ const column: Column = { key: 'created', label: 'Created', type: 'date' };
105
+
106
+ const result = formattingService.formatCellDisplay(row, column);
107
+ expect(result.display).toContain('2024');
108
+ });
109
+
110
+ it('should format datetime for display', () => {
111
+ const row: Row = { updated: { raw: '2024-01-15T14:30:00Z', value: new Date('2024-01-15T14:30:00Z').getTime() } };
112
+ const column: Column = { key: 'updated', label: 'Updated', type: 'datetime' };
113
+
114
+ const result = formattingService.formatCellDisplay(row, column);
115
+ expect(result.display).toBeTruthy();
116
+ expect(result.display).toContain('2024');
117
+ });
118
+
119
+ it('should format duration for display', () => {
120
+ const row: Row = { duration: { raw: 3661, value: 3661 } };
121
+ const column: Column = { key: 'duration', label: 'Duration', type: 'duration' };
122
+
123
+ const result = formattingService.formatCellDisplay(row, column);
124
+ expect(result.display).toBe('01:01:01');
125
+ });
126
+
127
+ it('should handle null values in display', () => {
128
+ const row: Row = { name: { raw: null, value: null } };
129
+ const column: Column = { key: 'name', label: 'Name', type: 'string' };
130
+
131
+ const result = formattingService.formatCellDisplay(row, column);
132
+ expect(result.display).toBeNull();
133
+ });
134
+ });
135
+
136
+ describe('Display format changes', () => {
137
+ it('should respect timezone changes for date formatting', () => {
138
+ const row: Row = { created: { raw: '2024-01-15T12:00:00Z', value: new Date('2024-01-15T12:00:00Z').getTime() } };
139
+ const column: Column = { key: 'created', label: 'Created', type: 'datetime' };
140
+
141
+ gridService.updateDisplayFormat({ timeZone: 'America/New_York' });
142
+ const result = formattingService.formatCellDisplay(row, column);
143
+
144
+ expect(result.display).toBeTruthy();
145
+ });
146
+
147
+ it('should respect date format changes', () => {
148
+ const row: Row = { created: { raw: '2024-01-15', value: new Date('2024-01-15').getTime() } };
149
+ const column: Column = { key: 'created', label: 'Created', type: 'date' };
150
+
151
+ gridService.updateDisplayFormat({ dateFormat: 'dd/MM/yyyy' });
152
+ const result = formattingService.formatCellDisplay(row, column);
153
+
154
+ expect(result.display).toContain('/');
155
+ });
156
+ });
157
+ });
@@ -0,0 +1,84 @@
1
+ import type { FilterNode } from '@open-dcp/filter';
2
+ import type { SortingCondition } from './sorting';
3
+
4
+ export type ColumnType = 'string' | 'integer' | 'number' | 'date' | 'datetime' | 'duration' | 'custom' | 'component' | 'enum';
5
+ export type GlobalSearchMode = 'value' | 'raw' | 'display';
6
+
7
+ export interface FilterOption {
8
+ label: string;
9
+ value: string | number | boolean;
10
+ }
11
+
12
+ export interface Column {
13
+ label: string;
14
+ style?: string;
15
+ key: string | number;
16
+ type: ColumnType;
17
+ renderCell?: (cellValue: CellValue, row: Row) => string | HTMLElement; // Function-based renderer
18
+ displayAsHtml?: boolean;
19
+ component?: any; // Placeholder for a component (framework-specific)
20
+ componentPropsMapper?: (cellValue: CellValue, row: Row) => Record<string, any>; // Function to map cell values to component props
21
+ globalSearchText?: (cellValue: CellValue | null, row: Row, mode: GlobalSearchMode) => string | number | boolean | null | undefined;
22
+ transformer?: (rawValue: any) => string | number | boolean;
23
+ filterOptions?: FilterOption[]; // Options for enum filter type
24
+ }
25
+
26
+ export interface Row {
27
+ [key: string]: CellValue;
28
+ }
29
+
30
+ export interface RenderRow {
31
+ globaIndex: number;
32
+ data: Row;
33
+ }
34
+
35
+ export interface CellValue {
36
+ raw: any; // The original unmodified value.
37
+ value: any; // The transformed value (for sorting, filtering).
38
+ display?: string | null; // The display value (formatted for rendering).
39
+ renderedElement?: HTMLElement;
40
+ }
41
+
42
+ export interface GridState {
43
+ startIndex: number;
44
+ step: number;
45
+ firstRowHeight: number;
46
+ lastRowHeight: number;
47
+ renderRows: RenderRow[];
48
+ }
49
+
50
+ export interface DisplayFormat {
51
+ timeZone: string;
52
+ dateFormat: string;
53
+ timeFormat: string;
54
+ }
55
+
56
+ export interface DisplayValue {
57
+ display: string | undefined | null;
58
+ renderedElement: HTMLElement | undefined;
59
+ }
60
+
61
+ export interface GlobalSearchOptions {
62
+ columns?: Array<string | number>;
63
+ caseInsensitive?: boolean;
64
+ searchMode?: GlobalSearchMode;
65
+ }
66
+
67
+ export interface GlobalSearchState {
68
+ query: string;
69
+ options?: GlobalSearchOptions;
70
+ }
71
+
72
+ export interface FilterSortUpdatePayload {
73
+ filterTree: FilterNode | null;
74
+ sorts: SortingCondition[];
75
+ globalSearch?: GlobalSearchState | null;
76
+ }
77
+
78
+ export interface GridOptions {
79
+ elementHeight: number;
80
+ multiSelect: boolean;
81
+ allSelector: boolean;
82
+ displayFormat: Partial<DisplayFormat>;
83
+ caseInsensitiveFiltering: boolean;
84
+ }
@@ -0,0 +1,9 @@
1
+ export interface SortingCondition {
2
+ columnKey: string | number;
3
+ direction: 'asc' | 'desc';
4
+ }
5
+
6
+ export interface ApiModeConfig {
7
+ clientSideSort?: boolean; // If true, sorting happens client-side
8
+ clientSideFilter?: boolean; // If true, filtering happens client-side
9
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es2017",
4
+ "module": "esnext",
5
+ "declaration": true,
6
+ "outDir": "dist",
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "resolveJsonModule": true,
11
+ "moduleResolution": "node",
12
+ "lib": ["es2017", "dom"],
13
+ "baseUrl": ".",
14
+ "paths": {
15
+ "@/*": ["src/*"]
16
+ }
17
+ },
18
+ "include": ["src"]
19
+ }