ngx-column-filter-popup 1.0.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.
@@ -0,0 +1,690 @@
1
+ # Column Filter Library - Complete Documentation
2
+
3
+ ## What is This Component?
4
+
5
+ This is a **reusable Angular column filter component** that provides advanced filtering capabilities for tables, lists, or any data grid.
6
+
7
+ ### Key Features:
8
+
9
+ 1. **Multiple Filter Rules**: Add multiple filter conditions per column
10
+ 2. **Multiple Field Types**: Specialized filters for different data types:
11
+ - **Text** - For text fields (default)
12
+ - **Currency** - For currency values (with currency symbol)
13
+ - **Age/Number** - For numeric values (simple number input)
14
+ - **Date** - For date values (date picker)
15
+ - **Status** - For predefined status options (dropdown)
16
+
17
+ 3. **Various Match Types**: Different matching options available based on field type:
18
+ - **Text Fields**: Match All, Match Any, Starts with, Ends with, Contains, Equals
19
+ - **Currency/Age Fields**: Equals, Greater than, Less than, Greater or equal, Less or equal
20
+ - **Date Fields**: Date is, Date is not, Date is before, Date is after, Date is on
21
+ - **Status Fields**: Is, Is not, Equals
22
+
23
+ 4. **Global Match Mode**:
24
+ - **Match All Rules** (AND Logic): When multiple rules exist, **all rules** must match
25
+ - **Match Any Rule** (OR Logic): When multiple rules exist, **any one rule** can match (Default)
26
+
27
+ 5. **Visual Feedback**: Filter icon changes when active
28
+ 6. **Fully Customizable**: Easily configure according to your data
29
+ 7. **Single Filter Open**: Only one filter dropdown can be open at a time
30
+ 8. **ESC Key Support**: Press ESC to close the open filter
31
+ 9. **Programmatic Control**: Clear filters programmatically using `clearFilter()` method
32
+
33
+ ---
34
+
35
+ ## How to Use?
36
+
37
+ ### Basic Usage:
38
+
39
+ ```typescript
40
+ import { Component } from '@angular/core';
41
+ import { ColumnFilterComponent } from 'ngx-column-filter-popup';
42
+ import { FilterConfig, applyColumnFilter } from 'ngx-column-filter-popup';
43
+
44
+ interface User {
45
+ id: number;
46
+ firstName: string;
47
+ lastName: string;
48
+ email: string;
49
+ role: string;
50
+ }
51
+
52
+ @Component({
53
+ selector: 'app-user-list',
54
+ imports: [ColumnFilterComponent],
55
+ template: `
56
+ <table>
57
+ <thead>
58
+ <tr>
59
+ <th>
60
+ First Name
61
+ <lib-column-filter
62
+ columnName="first name"
63
+ columnKey="firstName"
64
+ (filterApplied)="onFirstNameFilter($event)"
65
+ (filterCleared)="onFirstNameClear()">
66
+ </lib-column-filter>
67
+ </th>
68
+ <th>
69
+ Last Name
70
+ <lib-column-filter
71
+ columnName="last name"
72
+ columnKey="lastName"
73
+ (filterApplied)="onLastNameFilter($event)"
74
+ (filterCleared)="onLastNameClear()">
75
+ </lib-column-filter>
76
+ </th>
77
+ </tr>
78
+ </thead>
79
+ <tbody>
80
+ <tr *ngFor="let user of filteredUsers">
81
+ <td>{{ user.firstName }}</td>
82
+ <td>{{ user.lastName }}</td>
83
+ </tr>
84
+ </tbody>
85
+ </table>
86
+ `
87
+ })
88
+ export class UserListComponent {
89
+ users: User[] = [
90
+ { id: 1, firstName: 'John', lastName: 'Doe', email: 'john@example.com', role: 'Developer' },
91
+ { id: 2, firstName: 'Jane', lastName: 'Smith', email: 'jane@example.com', role: 'Designer' }
92
+ ];
93
+
94
+ filteredUsers: User[] = [...this.users];
95
+ firstNameFilter: FilterConfig | null = null;
96
+ lastNameFilter: FilterConfig | null = null;
97
+
98
+ onFirstNameFilter(filterConfig: FilterConfig) {
99
+ this.firstNameFilter = filterConfig;
100
+ this.applyFilters();
101
+ }
102
+
103
+ onFirstNameClear() {
104
+ this.firstNameFilter = null;
105
+ this.applyFilters();
106
+ }
107
+
108
+ onLastNameFilter(filterConfig: FilterConfig) {
109
+ this.lastNameFilter = filterConfig;
110
+ this.applyFilters();
111
+ }
112
+
113
+ onLastNameClear() {
114
+ this.lastNameFilter = null;
115
+ this.applyFilters();
116
+ }
117
+
118
+ private applyFilters() {
119
+ let result = [...this.users];
120
+
121
+ // Apply firstName filter
122
+ if (this.firstNameFilter) {
123
+ result = applyColumnFilter(result, 'firstName', this.firstNameFilter);
124
+ }
125
+
126
+ // Apply lastName filter
127
+ if (this.lastNameFilter) {
128
+ result = applyColumnFilter(result, 'lastName', this.lastNameFilter);
129
+ }
130
+
131
+ this.filteredUsers = result;
132
+ }
133
+ }
134
+ ```
135
+
136
+ ---
137
+
138
+ ## Data Adaptation
139
+
140
+ When you change your **original data**, the component will automatically adapt. You only need to update **two things in HTML**:
141
+
142
+ 1. **`columnKey`**: The property name to filter on
143
+ - Example: If your data has `name` field, use `columnKey="name"`
144
+
145
+ 2. **`columnName`**: The display name shown to users (used in placeholder)
146
+ - Example: `columnName="employee name"` or `columnName="customer name"`
147
+
148
+ ### Example - Adapting to Data Changes:
149
+
150
+ ```typescript
151
+ // Your previous data:
152
+ interface OldData {
153
+ firstName: string;
154
+ lastName: string;
155
+ }
156
+
157
+ // Your new data:
158
+ interface NewData {
159
+ employeeName: string;
160
+ department: string;
161
+ salary: number;
162
+ location: string;
163
+ }
164
+ ```
165
+
166
+ **Update HTML:**
167
+ ```html
168
+ <!-- Old -->
169
+ <lib-column-filter
170
+ columnName="first name"
171
+ columnKey="firstName"
172
+ (filterApplied)="onFirstNameFilter($event)">
173
+ </lib-column-filter>
174
+
175
+ <!-- New -->
176
+ <lib-column-filter
177
+ columnName="employee name"
178
+ columnKey="employeeName"
179
+ (filterApplied)="onEmployeeNameFilter($event)">
180
+ </lib-column-filter>
181
+
182
+ <!-- New Department filter -->
183
+ <lib-column-filter
184
+ columnName="department"
185
+ columnKey="department"
186
+ (filterApplied)="onDepartmentFilter($event)">
187
+ </lib-column-filter>
188
+ ```
189
+
190
+ **Update TypeScript:**
191
+ ```typescript
192
+ // Use the same columnKey in utility function that matches HTML
193
+ if (this.employeeNameFilter) {
194
+ result = applyColumnFilter(result, 'employeeName', this.employeeNameFilter);
195
+ // ↑ This columnKey should match the one in HTML
196
+ }
197
+ ```
198
+
199
+ ---
200
+
201
+ ## Field Types
202
+
203
+ This library supports **5 different field types**:
204
+
205
+ ### 1. Text Field (Default)
206
+
207
+ **Use Case**: Text content like names, emails, descriptions, etc.
208
+
209
+ **Match Types Available**:
210
+ - Match All (all words must be present)
211
+ - Match Any (at least one word must be present)
212
+ - Starts with
213
+ - Ends with
214
+ - Contains
215
+ - Equals
216
+
217
+ **Example**:
218
+ ```html
219
+ <lib-column-filter
220
+ columnName="first name"
221
+ columnKey="firstName"
222
+ fieldType="text"
223
+ (filterApplied)="onFilterApplied($event)">
224
+ </lib-column-filter>
225
+ ```
226
+
227
+ ---
228
+
229
+ ### 2. Currency Field
230
+
231
+ **Use Case**: Currency values like price, balance, amount, etc.
232
+
233
+ **Match Types Available**:
234
+ - Equals
235
+ - Greater than
236
+ - Less than
237
+ - Greater or equal
238
+ - Less or equal
239
+
240
+ **Features**:
241
+ - Currency symbol display ($, ₹, €, etc.)
242
+ - Automatic number formatting with commas
243
+ - Decimal support
244
+
245
+ **Example**:
246
+ ```html
247
+ <lib-column-filter
248
+ columnName="balance"
249
+ columnKey="balance"
250
+ fieldType="currency"
251
+ currencySymbol="$"
252
+ (filterApplied)="onBalanceFilterApplied($event)">
253
+ </lib-column-filter>
254
+ ```
255
+
256
+ ---
257
+
258
+ ### 3. Age/Number Field
259
+
260
+ **Use Case**: Simple numeric values like age, quantity, count, ratings, etc.
261
+
262
+ **Match Types Available**:
263
+ - Equals
264
+ - Greater than
265
+ - Less than
266
+ - Greater or equal
267
+ - Less or equal
268
+
269
+ **Features**:
270
+ - Simple number input (no currency symbol)
271
+ - Integer support
272
+ - Easy to use for age, quantity, etc.
273
+
274
+ **Example**:
275
+ ```html
276
+ <lib-column-filter
277
+ columnName="age"
278
+ columnKey="age"
279
+ fieldType="age"
280
+ (filterApplied)="onAgeFilterApplied($event)">
281
+ </lib-column-filter>
282
+ ```
283
+
284
+ ---
285
+
286
+ ### 4. Date Field
287
+
288
+ **Use Case**: Date values like created date, due date, birth date, etc.
289
+
290
+ **Match Types Available**:
291
+ - Date is
292
+ - Date is not
293
+ - Date is before
294
+ - Date is after
295
+ - Date is on
296
+
297
+ **Features**:
298
+ - Native date picker
299
+ - Proper date comparison
300
+ - Date normalization for accurate filtering
301
+
302
+ **Example**:
303
+ ```html
304
+ <lib-column-filter
305
+ columnName="date"
306
+ columnKey="date"
307
+ fieldType="date"
308
+ (filterApplied)="onDateFilterApplied($event)">
309
+ </lib-column-filter>
310
+ ```
311
+
312
+ ---
313
+
314
+ ### 5. Status Field
315
+
316
+ **Use Case**: Predefined status options like order status, user status, task status, etc.
317
+
318
+ **Match Types Available**:
319
+ - Is
320
+ - Is not
321
+ - Equals
322
+
323
+ **Features**:
324
+ - Dropdown with predefined options
325
+ - Clean UI with select dropdown
326
+ - Easy status selection
327
+
328
+ **Example**:
329
+ ```html
330
+ <lib-column-filter
331
+ columnName="status"
332
+ columnKey="status"
333
+ fieldType="status"
334
+ [statusOptions]="['qualified', 'unqualified', 'negotiation', 'new']"
335
+ (filterApplied)="onStatusFilterApplied($event)">
336
+ </lib-column-filter>
337
+ ```
338
+
339
+ ---
340
+
341
+ ### Complete Example with All Field Types
342
+
343
+ ```typescript
344
+ import { Component } from '@angular/core';
345
+ import { ColumnFilterComponent } from 'ngx-column-filter-popup';
346
+ import { FilterConfig, applyColumnFilter } from 'ngx-column-filter-popup';
347
+
348
+ interface Employee {
349
+ id: number;
350
+ name: string;
351
+ age: number;
352
+ salary: number;
353
+ joinDate: string;
354
+ status: string;
355
+ }
356
+
357
+ @Component({
358
+ selector: 'app-employee-list',
359
+ imports: [ColumnFilterComponent],
360
+ template: `
361
+ <table>
362
+ <thead>
363
+ <tr>
364
+ <th>
365
+ Name
366
+ <lib-column-filter
367
+ columnName="name"
368
+ columnKey="name"
369
+ fieldType="text"
370
+ (filterApplied)="onNameFilter($event)">
371
+ </lib-column-filter>
372
+ </th>
373
+ <th>
374
+ Age
375
+ <lib-column-filter
376
+ columnName="age"
377
+ columnKey="age"
378
+ fieldType="age"
379
+ (filterApplied)="onAgeFilter($event)">
380
+ </lib-column-filter>
381
+ </th>
382
+ <th>
383
+ Salary
384
+ <lib-column-filter
385
+ columnName="salary"
386
+ columnKey="salary"
387
+ fieldType="currency"
388
+ currencySymbol="$"
389
+ (filterApplied)="onSalaryFilter($event)">
390
+ </lib-column-filter>
391
+ </th>
392
+ <th>
393
+ Join Date
394
+ <lib-column-filter
395
+ columnName="join date"
396
+ columnKey="joinDate"
397
+ fieldType="date"
398
+ (filterApplied)="onDateFilter($event)">
399
+ </lib-column-filter>
400
+ </th>
401
+ <th>
402
+ Status
403
+ <lib-column-filter
404
+ columnName="status"
405
+ columnKey="status"
406
+ fieldType="status"
407
+ [statusOptions]="statusOptions"
408
+ (filterApplied)="onStatusFilter($event)">
409
+ </lib-column-filter>
410
+ </th>
411
+ </tr>
412
+ </thead>
413
+ <tbody>
414
+ <tr *ngFor="let emp of filteredEmployees">
415
+ <td>{{ emp.name }}</td>
416
+ <td>{{ emp.age }}</td>
417
+ <td>${{ emp.salary | number }}</td>
418
+ <td>{{ emp.joinDate }}</td>
419
+ <td>{{ emp.status }}</td>
420
+ </tr>
421
+ </tbody>
422
+ </table>
423
+ `
424
+ })
425
+ export class EmployeeListComponent {
426
+ employees: Employee[] = [
427
+ { id: 1, name: 'John Doe', age: 30, salary: 50000, joinDate: '2020-01-15', status: 'active' },
428
+ { id: 2, name: 'Jane Smith', age: 25, salary: 60000, joinDate: '2019-05-20', status: 'active' }
429
+ ];
430
+
431
+ filteredEmployees: Employee[] = [...this.employees];
432
+ statusOptions = ['active', 'inactive', 'on-leave', 'terminated'];
433
+
434
+ nameFilter: FilterConfig | null = null;
435
+ ageFilter: FilterConfig | null = null;
436
+ salaryFilter: FilterConfig | null = null;
437
+ dateFilter: FilterConfig | null = null;
438
+ statusFilter: FilterConfig | null = null;
439
+
440
+ onNameFilter(filterConfig: FilterConfig) {
441
+ this.nameFilter = filterConfig;
442
+ this.applyAllFilters();
443
+ }
444
+
445
+ onAgeFilter(filterConfig: FilterConfig) {
446
+ this.ageFilter = filterConfig;
447
+ this.applyAllFilters();
448
+ }
449
+
450
+ onSalaryFilter(filterConfig: FilterConfig) {
451
+ this.salaryFilter = filterConfig;
452
+ this.applyAllFilters();
453
+ }
454
+
455
+ onDateFilter(filterConfig: FilterConfig) {
456
+ this.dateFilter = filterConfig;
457
+ this.applyAllFilters();
458
+ }
459
+
460
+ onStatusFilter(filterConfig: FilterConfig) {
461
+ this.statusFilter = filterConfig;
462
+ this.applyAllFilters();
463
+ }
464
+
465
+ private applyAllFilters() {
466
+ let result = [...this.employees];
467
+
468
+ if (this.nameFilter) {
469
+ result = applyColumnFilter(result, 'name', this.nameFilter);
470
+ }
471
+ if (this.ageFilter) {
472
+ result = applyColumnFilter(result, 'age', this.ageFilter);
473
+ }
474
+ if (this.salaryFilter) {
475
+ result = applyColumnFilter(result, 'salary', this.salaryFilter);
476
+ }
477
+ if (this.dateFilter) {
478
+ result = applyColumnFilter(result, 'joinDate', this.dateFilter);
479
+ }
480
+ if (this.statusFilter) {
481
+ result = applyColumnFilter(result, 'status', this.statusFilter);
482
+ }
483
+
484
+ this.filteredEmployees = result;
485
+ }
486
+ }
487
+ ```
488
+
489
+ ---
490
+
491
+ ## Match All Rules Feature
492
+
493
+ ### What is it?
494
+
495
+ When you add **multiple filter rules**, you can choose how to combine them:
496
+ - **Match Any Rule** (default): If any rule matches, the item is included (OR logic)
497
+ - **Match All Rules**: All rules must match for the item to be included (AND logic)
498
+
499
+ ### Example:
500
+
501
+ Let's say you have 2 rules:
502
+ - Rule 1: Contains "John"
503
+ - Rule 2: Contains "Doe"
504
+
505
+ #### Match Any Rule (OR Logic):
506
+ - ✅ "John Smith" will match (Rule 1 matches)
507
+ - ✅ "Jane Doe" will match (Rule 2 matches)
508
+ - ✅ "John Doe" will match (both rules match)
509
+
510
+ #### Match All Rules (AND Logic):
511
+ - ❌ "John Smith" won't match (Rule 2 fails)
512
+ - ❌ "Jane Doe" won't match (Rule 1 fails)
513
+ - ✅ "John Doe" will match (both rules match)
514
+
515
+ ### UI Appearance:
516
+
517
+ When you add **2 or more rules**, a **"Match All Rules" / "Match Any Rule"** toggle will appear at the top of the dropdown:
518
+
519
+ - **Green border and light green background**: When "Match All Rules" is selected
520
+ - **Normal border**: When "Match Any Rule" is selected (default)
521
+
522
+ ---
523
+
524
+ ## Component API Reference
525
+
526
+ ### Inputs / Input Properties:
527
+
528
+ | Input | Type | Default | Description |
529
+ |-------|------|---------|-------------|
530
+ | `columnName` | `string` | `''` | Display name of the column (used in placeholder) |
531
+ | `columnKey` | `string` | `''` | Property name to filter on |
532
+ | `fieldType` | `FieldType` | `'text'` | Field type: 'text', 'currency', 'age', 'date', or 'status' |
533
+ | `currencySymbol` | `string` | `'$'` | Currency symbol for currency field type (optional) |
534
+ | `statusOptions` | `string[]` | `[]` | Array of status options for status field type (required for status) |
535
+ | `initialFilter` | `FilterConfig?` | `undefined` | Initial filter configuration (optional) |
536
+ | `placeholder` | `string?` | `undefined` | Custom placeholder text. Default: "Search by {columnName}" |
537
+ | `availableMatchTypes` | `MatchType[]?` | `undefined` | Customize available match types (optional) |
538
+
539
+ ### Outputs / Output Events:
540
+
541
+ | Output | Type | Description |
542
+ |--------|------|-------------|
543
+ | `filterApplied` | `EventEmitter<FilterConfig>` | Emitted when filter is applied |
544
+ | `filterCleared` | `EventEmitter<void>` | Emitted when filter is cleared |
545
+
546
+ ### Public Methods:
547
+
548
+ | Method | Description |
549
+ |--------|-------------|
550
+ | `clearFilter()` | Programmatically clear all filter rules and reset the filter |
551
+
552
+ ### FilterConfig Interface:
553
+
554
+ ```typescript
555
+ type FieldType = 'text' | 'currency' | 'age' | 'date' | 'status';
556
+
557
+ interface FilterConfig {
558
+ rules: FilterRule[];
559
+ globalMatchMode?: GlobalMatchMode; // 'match-all-rules' | 'match-any-rule'
560
+ fieldType?: FieldType; // Field type for this filter
561
+ statusOptions?: string[]; // Status options (for status field type)
562
+ }
563
+
564
+ interface FilterRule {
565
+ id: string;
566
+ matchType: MatchType;
567
+ value: string;
568
+ }
569
+
570
+ // Match types vary based on field type:
571
+ // Text: 'match-all' | 'match-any' | 'starts-with' | 'ends-with' | 'contains' | 'equals'
572
+ // Currency/Age: 'equals' | 'greater-than' | 'less-than' | 'greater-equal' | 'less-equal'
573
+ // Date: 'is' | 'is-not' | 'is-before' | 'is-after' | 'is-on'
574
+ // Status: 'is' | 'is-not' | 'equals'
575
+ ```
576
+
577
+ ---
578
+
579
+ ## Utility Functions
580
+
581
+ ### applyColumnFilter
582
+
583
+ Apply filter rules to a dataset:
584
+
585
+ ```typescript
586
+ import { applyColumnFilter } from 'ngx-column-filter-popup';
587
+
588
+ const filteredData = applyColumnFilter(
589
+ data, // Your data array
590
+ 'columnKey', // Property name to filter on
591
+ filterConfig // FilterConfig from component
592
+ );
593
+ ```
594
+
595
+ ### itemMatchesFilter
596
+
597
+ Check if a single item matches filter rules:
598
+
599
+ ```typescript
600
+ import { itemMatchesFilter } from 'ngx-column-filter-popup';
601
+
602
+ const matches = itemMatchesFilter(
603
+ item, // Single item to check
604
+ 'columnKey', // Property name to filter on
605
+ filterConfig // FilterConfig from component
606
+ );
607
+ ```
608
+
609
+ ---
610
+
611
+ ## Programmatic Control
612
+
613
+ ### Clearing Filters Programmatically
614
+
615
+ You can clear filters programmatically using `ViewChild`:
616
+
617
+ ```typescript
618
+ import { Component, ViewChild } from '@angular/core';
619
+ import { ColumnFilterComponent } from 'ngx-column-filter-popup';
620
+
621
+ @Component({
622
+ template: `
623
+ <lib-column-filter
624
+ #nameFilter
625
+ columnName="name"
626
+ columnKey="name">
627
+ </lib-column-filter>
628
+ <button (click)="clearFilter()">Clear Filter</button>
629
+ `
630
+ })
631
+ export class ExampleComponent {
632
+ @ViewChild('nameFilter') filter!: ColumnFilterComponent;
633
+
634
+ clearFilter() {
635
+ this.filter.clearFilter(); // Programmatically clear the filter
636
+ }
637
+ }
638
+ ```
639
+
640
+ ---
641
+
642
+ ## Important Notes:
643
+
644
+ 1. **Data Type Independence**: Component works with any data type, just ensure `columnKey` is correct.
645
+
646
+ 2. **Case Insensitive**: All filters are case-insensitive (uppercase/lowercase doesn't matter)
647
+
648
+ 3. **Empty Rules**: Empty rules are automatically ignored
649
+
650
+ 4. **Multiple Columns**: You can apply filters on as many columns as you want
651
+
652
+ 5. **Filter Combination**: Filters from multiple columns are combined with **AND logic** (all column filters must match)
653
+
654
+ 6. **Global Match Mode**: This only applies to **multiple rules within one column**, not multiple columns
655
+
656
+ 7. **Single Filter Open**: Only one filter dropdown can be open at a time - opening a new filter closes the previous one automatically
657
+
658
+ 8. **ESC Key**: Press ESC key to close the currently open filter dropdown
659
+
660
+ ---
661
+
662
+ ## Troubleshooting
663
+
664
+ ### Problem: Filter not working
665
+ - Check that `columnKey` matches your data object's property name
666
+ - Check that `applyColumnFilter` function uses the same `columnKey`
667
+
668
+ ### Problem: "Match All Rules" toggle not showing
669
+ - This only appears when you have **2 or more rules**
670
+ - Use the "Add Rule" button to add more rules
671
+
672
+ ### Problem: Data not updating after data change
673
+ - Remember to update `columnKey` and `columnName` in HTML
674
+ - Also update the `columnKey` in `applyColumnFilter` function in TypeScript
675
+
676
+ ---
677
+
678
+ ## Summary
679
+
680
+ ✅ **What it does**: Provides advanced column filtering for tables/lists
681
+ ✅ **Data adaptation**: Simply update `columnKey` and `columnName` in HTML
682
+ ✅ **Match All Rules**: Feature to combine multiple rules with AND/OR logic
683
+ ✅ **Easy to Use**: Simple API, fully customizable
684
+ ✅ **Multiple Field Types**: Support for text, currency, age, date, and status fields
685
+ ✅ **Programmatic Control**: Clear filters programmatically
686
+ ✅ **Better UX**: Only one filter open at a time, ESC key support
687
+
688
+ ---
689
+
690
+ **Made with ❤️ by Shivam Sharma**
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Shivam Sharma
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.