Skip to content

Commit 5f0896c

Browse files
authored
feat(): next (#1564)
2 parents 3c44d42 + 4416e42 commit 5f0896c

File tree

21 files changed

+50539
-49739
lines changed

21 files changed

+50539
-49739
lines changed

projects/demo/assets/documentation.json

+50,375-49,718
Large diffs are not rendered by default.

projects/novo-elements/src/elements/common/overlay/Overlay.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ export class NovoOverlayTemplateComponent implements OnDestroy {
159159
}
160160

161161
/**
162-
* A stream of actions that should close the autocomplete panel, including
162+
* A stream of actions that should close the panel, including
163163
* when an option is selected, on blur, and when TAB is pressed.
164164
*/
165165
public get panelClosingActions(): Observable<any> {
@@ -169,7 +169,7 @@ export class NovoOverlayTemplateComponent implements OnDestroy {
169169
);
170170
}
171171

172-
/** Stream of clicks outside of the autocomplete panel. */
172+
/** Stream of clicks outside of the panel. */
173173
protected get outsideClickStream(): Observable<any> {
174174
if (!this.document) {
175175
return observableOf();

projects/novo-elements/src/elements/common/typography/base/base-text.component.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ export class NovoBaseTextElement {
115115
@BooleanInput()
116116
extrabold: boolean;
117117

118-
constructor(protected element: ElementRef) {}
118+
constructor(public element: ElementRef) {}
119119

120120
get nativeElement() {
121121
return this.element.nativeElement;

projects/novo-elements/src/elements/data-table/interfaces.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export interface IDataTablePreferences {
44
name: string;
55
sort?: IDataTableSort;
66
filter?: IDataTableFilter | IDataTableFilter[];
7-
where?: { query: string; criteria?: AdaptiveCriteria; form: any };
7+
where?: DataTableWhere;
88
globalSearch?: any;
99
pageSize?: number;
1010
displayedColumns?: string[];
@@ -14,6 +14,13 @@ export interface IDataTablePreferences {
1414
appliedSearchType?: AppliedSearchType;
1515
}
1616

17+
export interface DataTableWhere {
18+
query: string;
19+
criteria?: AdaptiveCriteria;
20+
keywords?: string[];
21+
form: any;
22+
}
23+
1724
export enum AppliedSearchType {
1825
Saved = 'saved',
1926
Recent = 'recent',

projects/novo-elements/src/elements/header/Header.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Component, HostBinding, Input } from '@angular/core';
1+
import { Component, ElementRef, HostBinding, Input } from '@angular/core';
22
import { BooleanInput } from 'novo-elements/utils';
33

44
@Component({
@@ -116,4 +116,6 @@ export class NovoHeaderComponent {
116116
}
117117

118118
private _theme: string;
119+
120+
constructor(public element: ElementRef) {}
119121
}

projects/novo-elements/src/elements/query-builder/condition-builder/condition-builder.component.html

+3-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
overlayHeight="20rem"
1010
[displayWith]="fieldDisplayWith"
1111
[style.minWidth.px]="160"
12-
[style.maxWidth.px]="(isFirst() || isConditionHost) ? 200 : 160">
12+
[style.maxWidth.px]="(isFirst() || isConditionHost) ? 200 : 160"
13+
[displayIcon]="displayIcon">
1314
<novo-optgroup class="filter-search-results">
1415
<novo-option>
1516
<novo-select-search [formControl]="searchTerm" [clearSearchInput]="false"></novo-select-search>
@@ -51,4 +52,4 @@
5152
<!-- LOADING TEMPLATE -->
5253
<ng-template #loading>
5354
<novo-loading></novo-loading>
54-
</ng-template>
55+
</ng-template>

projects/novo-elements/src/elements/query-builder/condition-builder/condition-builder.component.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export class ConditionBuilderComponent implements OnInit, OnChanges, AfterConten
7272
isFirst = input(false);
7373
@Input() andIndex: number;
7474
@Input() groupIndex: number;
75-
75+
7676
// This component can either be directly hosted as a host to a condition, or it can be part of a condition group within a criteria builder.
7777
// In the former case, config will come from inputs, and we will instantiate our own QueryBuilderService. In the latter, it comes from
7878
// the QueryBuilderService.
@@ -97,6 +97,7 @@ export class ConditionBuilderComponent implements OnInit, OnChanges, AfterConten
9797
public results$: Promise<any[]>;
9898
public searchTerm: FormControl = new FormControl();
9999
public fieldDisplayWith;
100+
public displayIcon: string;
100101

101102
public staticFieldSelection = computed(() => this.config().staticFieldSelection);
102103
private _lastContext: any = {};
@@ -203,6 +204,7 @@ export class ConditionBuilderComponent implements OnInit, OnChanges, AfterConten
203204
return;
204205
} else {
205206
this.fieldDisplayWith = () => fieldConf.label || fieldConf.name;
207+
this.displayIcon = fieldConf.icon || null;
206208
}
207209
const { field } = this.parentForm.value;
208210

projects/novo-elements/src/elements/query-builder/condition-definitions/picker-condition.definition.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core';
1+
import { ChangeDetectionStrategy, Component, ViewChild, ViewEncapsulation } from '@angular/core';
22
import { AbstractConditionFieldDef } from './abstract-condition.definition';
33
import { Operator } from '../query-builder.types';
4+
import { NovoSelectElement } from 'novo-elements/elements/select';
45

56
/**
67
* Handle selection of field values when a list of options is provided.
@@ -19,7 +20,7 @@ import { Operator } from '../query-builder.types';
1920
</novo-field>
2021
<ng-container *novoConditionInputDef="let formGroup; fieldMeta as meta" [ngSwitch]="formGroup.value.operator" [formGroup]="formGroup">
2122
<novo-field *novoSwitchCases="['includeAny', 'includeAll', 'excludeAny']">
22-
<novo-select formControlName="value" [placeholder]="labels.select" [multiple]="true">
23+
<novo-select extupdatefix formControlName="value" [placeholder]="labels.select" [multiple]="true">
2324
<!-- WHat about optionUrl/optionType -->
2425
<novo-option *ngFor="let option of meta?.options" [value]="option.value" [attr.data-automation-value]="option.label">
2526
{{ option.label }}

projects/novo-elements/src/elements/query-builder/query-builder.types.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,15 @@ export enum Conjunction {
77
}
88

99
export type ConditionGroup = {
10-
[K in Conjunction as `$${K}`]?: Condition[]
10+
[K in Conjunction as `$${K}`]?: Condition[];
1111
};
1212

13+
export type NestedConditionGroup = {
14+
[K in Conjunction as `$${K}`]?: ConditionOrConditionGroup[];
15+
};
16+
17+
export type ConditionOrConditionGroup = Condition | NestedConditionGroup;
18+
1319
export enum Operator {
1420
after = 'after',
1521
before = 'before',
@@ -39,6 +45,10 @@ export interface Criteria {
3945
criteria: ConditionGroup[];
4046
}
4147

48+
export interface NestedCriteria {
49+
criteria: NestedConditionGroup[];
50+
}
51+
4252
export interface BaseFieldDef {
4353
name: string;
4454
label?: string;
@@ -51,6 +61,7 @@ export interface BaseFieldDef {
5161
optionsUrl?: string;
5262
optionsType?: string;
5363
dataType?: string;
64+
icon?: string;
5465
}
5566

5667
export interface FieldConfig<T extends BaseFieldDef> {

projects/novo-elements/src/elements/search/SearchBox.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
HostBinding,
1111
Input,
1212
NgZone,
13+
OnInit,
1314
Output,
1415
ViewChild,
1516
} from '@angular/core';
@@ -60,7 +61,7 @@ const SEARCH_VALUE_ACCESSOR = {
6061
`,
6162
styleUrls: ['./SearchBox.scss'],
6263
})
63-
export class NovoSearchBoxElement implements ControlValueAccessor {
64+
export class NovoSearchBoxElement implements ControlValueAccessor, OnInit {
6465
@Input()
6566
public name: string;
6667
@Input()
@@ -90,6 +91,8 @@ export class NovoSearchBoxElement implements ControlValueAccessor {
9091
public hasBackdrop: boolean = false;
9192
@Input()
9293
public allowPropagation: boolean = false;
94+
@Input()
95+
public overrideElement: ElementRef;
9396
@Output()
9497
public searchChanged: EventEmitter<string> = new EventEmitter<string>();
9598
@Output()
@@ -118,6 +121,12 @@ export class NovoSearchBoxElement implements ControlValueAccessor {
118121
private _zone: NgZone,
119122
) {}
120123

124+
ngOnInit() {
125+
if (this.overrideElement) {
126+
this.element = this.overrideElement;
127+
}
128+
}
129+
121130
/**
122131
* @name showFasterFind
123132
* @description This function shows the picker and adds the active class (for animation)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// NG
2+
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
3+
// App
4+
import { NovoSelectElement } from './Select';
5+
import { NovoSelectModule } from './Select.module';
6+
import { NovoLabelService } from 'novo-elements/services';
7+
import { NovoOptionModule } from 'novo-elements/elements/common';
8+
import { Component, ViewChild } from '@angular/core';
9+
import { FormControl, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
10+
11+
@Component({
12+
template: `
13+
<form [formGroup]="form">
14+
<novo-select #select extupdatefix formControlName="value" multiple>
15+
<novo-option value="1">One</novo-option>
16+
<novo-option value="2">Two</novo-option>
17+
<novo-option value="3">Three</novo-option>
18+
</novo-select>
19+
</form>`
20+
})
21+
class FixedSelectComponent {
22+
@ViewChild('select')
23+
select: NovoSelectElement;
24+
25+
form = new FormGroup({
26+
value: new FormControl(['2'])
27+
});
28+
}
29+
30+
describe('Directive: NovoSelectExtUpdateFix', () => {
31+
let fixture: ComponentFixture<FixedSelectComponent>;
32+
let comp: FixedSelectComponent;
33+
34+
beforeEach(waitForAsync(() => {
35+
TestBed.configureTestingModule({
36+
imports: [NovoSelectModule, NovoOptionModule, FormsModule, ReactiveFormsModule],
37+
providers: [NovoLabelService],
38+
declarations: [FixedSelectComponent],
39+
}).compileComponents();
40+
fixture = TestBed.createComponent(FixedSelectComponent);
41+
comp = fixture.debugElement.componentInstance;
42+
fixture.detectChanges();
43+
}));
44+
45+
it('should update checkboxes when the ngmodel value is updated externally', () => {
46+
expect(comp.select.contentOptions.map(opt => opt.selected)).toEqual([false, true, false]);
47+
comp.form.controls.value.setValue(['1']);
48+
expect(comp.select.contentOptions.map(opt => opt.selected)).toEqual([true, false, false]);
49+
});
50+
51+
// This case may arise if, for instance, a dynamic form is about to transform this
52+
// control from a novo-select to something else.
53+
it('should not trigger errors from negative use-case variables', () => {
54+
(comp.form.controls.value as FormControl).setValue('candy');
55+
})
56+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { Directive, OnInit, inject } from '@angular/core';
2+
import { FormControl, NgControl } from '@angular/forms';
3+
import { NovoSelectElement } from './Select';
4+
5+
/**
6+
* Fixes a <novo-select> element so that if its value is updated externally, the checkboxes in the dropdown selector
7+
* update accordingly. Because this is a functionality change to a core control, this fix is provided as a directive
8+
* to only be used if needed.
9+
*/
10+
@Directive({
11+
selector: 'novo-select[extupdatefix]'
12+
})
13+
export class NovoSelectExtUpdateFix implements OnInit {
14+
control = inject(NgControl);
15+
selectElement = inject(NovoSelectElement);
16+
17+
ngOnInit() {
18+
if (this.control?.control && 'registerOnChange' in this.control.control) {
19+
(this.control.control as FormControl).registerOnChange((rawValue, viewToModelUpdate) => {
20+
if (this.selectElement.multiple === Array.isArray(rawValue)) {
21+
this.afterExternalUpdate(rawValue);
22+
}
23+
});
24+
}
25+
}
26+
27+
afterExternalUpdate(rawValue: any) {
28+
this.selectElement['_setSelectionByValue'](rawValue);
29+
}
30+
}

projects/novo-elements/src/elements/select/Select.module.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import { NovoOptionModule, NovoOverlayModule } from 'novo-elements/elements/comm
1010
import { NovoDividerModule } from 'novo-elements/elements/divider';
1111
import { NovoTooltipModule } from 'novo-elements/elements/tooltip';
1212
import { NovoSelectElement } from './Select';
13+
import { NovoSelectExtUpdateFix } from './Select.extupdatefix.directive';
14+
import { NovoIconModule } from 'novo-elements/elements/icon';
1315

1416
@NgModule({
1517
imports: [
@@ -22,8 +24,9 @@ import { NovoSelectElement } from './Select';
2224
NovoOverlayModule,
2325
NovoPipesModule,
2426
NovoTooltipModule,
27+
NovoIconModule,
2528
],
26-
declarations: [NovoSelectElement],
27-
exports: [NovoSelectElement],
29+
declarations: [NovoSelectElement, NovoSelectExtUpdateFix],
30+
exports: [NovoSelectElement, NovoSelectExtUpdateFix],
2831
})
2932
export class NovoSelectModule {}

projects/novo-elements/src/elements/select/Select.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ let nextId = 0;
9999
template: `
100100
<div class="novo-select-trigger" #dropdownElement (click)="togglePanel(); (false)" tabIndex="{{ disabled ? -1 : 0 }}" type="button">
101101
<span class="novo-select-placeholder" *ngIf="empty">{{ placeholder }}</span>
102-
<span class="text-ellipsis" *ngIf="!empty">{{ displayValue }}</span>
102+
<span class="text-ellipsis" *ngIf="!empty"><novo-icon size="sm" style="margin: 0 0 .25rem .1rem" *ngIf="displayIcon">{{ displayIcon }}</novo-icon> {{ displayValue }}</span>
103103
<i class="bhi-collapse"></i>
104104
</div>
105105
<novo-overlay-template
@@ -222,6 +222,8 @@ export class NovoSelectElement
222222
overlayWidth: number;
223223
@Input()
224224
overlayHeight: number;
225+
@Input()
226+
displayIcon: string = null;
225227
@Output()
226228
onSelect: EventEmitter<any> = new EventEmitter();
227229
/** Event emitted when the selected value has been changed by the user. */
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './Select';
22
export * from './Select.module';
3+
export * from './Select.extupdatefix.directive';

projects/novo-elements/src/elements/tabbed-group-picker/TabbedGroupPicker.html

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
<novo-option
5050
*cdkVirtualFor="let item of displayTab.data"
5151
[attr.data-automation-id]="item[displayTab.labelField]"
52+
[attr.data-automation-value]="item[displayTab.valueField]"
5253
[allowSelection]="selectionEnabled"
5354
[selected]="item.selected"
5455
(click)="activateItem(item)"

projects/novo-elements/src/elements/tabbed-group-picker/TabbedGroupPicker.spec.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -80,17 +80,19 @@ describe('Elements: NovoTabbedGroupPickerElement', () => {
8080
.map((e, i) => String(Math.pow(1000 + i, 5))); // make a bunch of ~16 character strings
8181
const tabNames = names.slice(0, 100);
8282
const labelFieldNames = names.splice(0, 100);
83-
const tabs = tabNames.map((typeName, i) => ({
83+
const tabs: any[] = tabNames.map((typeName, i) => ({
8484
typeName,
8585
labelField: labelFieldNames[i], // search/filter only looks at labelField
86+
valueField: 'value',
8687
data: null,
8788
}));
8889
tabs.forEach((tab) => {
89-
const { labelField } = tab;
90+
const { labelField, valueField } = tab;
9091
tab.data = Array(1000)
9192
.fill(0)
9293
.map((n, i) => ({
9394
[labelField]: turnNumbersIntoLetters(`${labelField}${i}`),
95+
[valueField]: i,
9496
}));
9597
});
9698
return tabs;

projects/novo-elements/src/elements/tabbed-group-picker/TabbedGroupPicker.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,8 @@ export class NovoTabbedGroupPickerElement implements OnDestroy, OnInit {
416416
this.displayTabs.forEach(
417417
(displayTab, i) =>
418418
(displayTab.data = this.tabs[i].data.filter((item) =>
419-
item[displayTab.labelField].toLowerCase().includes(searchTerm.toLowerCase()),
419+
item[displayTab.labelField].toLowerCase().includes(searchTerm.toLowerCase()) ||
420+
item[displayTab.valueField]?.toString().toLowerCase().includes(searchTerm.toLowerCase()),
420421
)),
421422
);
422423
this.ref.markForCheck();

projects/novo-elements/src/services/novo-label-service.ts

+1
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ export class NovoLabelService {
136136
after = 'After';
137137
between = 'Between';
138138
within = 'Within';
139+
isNull = 'Is Empty';
139140
isEmpty = 'Is Empty?';
140141
refreshPagination = 'Refresh Pagination';
141142
location = 'Location';

0 commit comments

Comments
 (0)