Skip to content

Commit 6a78076

Browse files
Merge branch '19.2.x' into ikitanov/fix#15913-19.2.x
2 parents 39eac0a + a0193ac commit 6a78076

File tree

12 files changed

+367
-78
lines changed

12 files changed

+367
-78
lines changed

projects/igniteui-angular/src/lib/core/styles/components/input/_input-group-theme.scss

Lines changed: 20 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -396,19 +396,15 @@
396396

397397
@if $variant == 'material' {
398398
%form-group-display--border {
399+
--label-position: calc((#{var-get($theme, 'size')} / 2) - #{rem(1px)});
400+
401+
399402
&:has(input:-webkit-autofill, input:autofill) {
400403
%igx-input-group__notch--border {
401404
border-block-start-color: transparent;
402405
}
403406

404-
%form-group-label {
405-
--label-position: #{sizable(18px, 22px, 26px)};
406-
407-
transform: translateY(calc(var(--label-position) * -1));
408-
margin-top: 0;
409-
overflow: hidden;
410-
will-change: font-size, color, transform;
411-
}
407+
@extend %form-group-label--float-border;
412408
}
413409
}
414410
}
@@ -1005,11 +1001,9 @@
10051001

10061002
@if $variant == 'material' {
10071003
%form-group-label--float {
1008-
--floating-label-position: -73%;
1009-
10101004
@include type-style('caption');
10111005

1012-
transform: translateY(var(--floating-label-position));
1006+
translate: 0 -73%;
10131007
}
10141008
}
10151009

@@ -1090,12 +1084,19 @@
10901084
}
10911085

10921086
%form-group-label--float-border {
1093-
--label-position: #{sizable(18px, 22px, 26px)};
1094-
1095-
transform: translateY(calc(var(--label-position) * -1));
1087+
translate: 0 calc(var(--label-position) * -1);
10961088
margin-top: 0;
10971089
overflow: hidden;
1098-
will-change: font-size, color, transform;
1090+
will-change: font-size, color, transform, translate;
1091+
}
1092+
1093+
%textarea-group {
1094+
// 3 lines * 22px + 8px bottom padding + 8px top padding
1095+
--textarea-size: #{sizable(
1096+
rem(82px, map.get($base-scale-size, 'compact')),
1097+
rem(82px, map.get($base-scale-size, 'cosy')),
1098+
rem(82px, map.get($base-scale-size, 'comfortable'))
1099+
)};
10991100
}
11001101

11011102
@if $variant == 'material' {
@@ -1139,12 +1140,6 @@
11391140
}
11401141

11411142
%form-group-textarea-group-bundle {
1142-
// 3 lines * 22px + 8px bottom padding + 8px top padding
1143-
--textarea-size: #{sizable(
1144-
rem(82px, map.get($base-scale-size, 'compact')),
1145-
rem(82px, map.get($base-scale-size, 'cosy')),
1146-
rem(82px, map.get($base-scale-size, 'comfortable'))
1147-
)};
11481143
min-height: var(--textarea-size) !important;
11491144
height: auto !important;
11501145

@@ -1156,12 +1151,12 @@
11561151
@if $material-theme {
11571152
%form-group-textarea-label {
11581153
top: calc($input-top-padding - #{rem(1px)});
1159-
margin-block-end: auto;
11601154
}
11611155

11621156
%textarea-group--outlined {
11631157
%form-group-textarea-label {
11641158
top: calc($input-top-padding - #{rem(3px)});
1159+
margin-block-end: auto;
11651160
}
11661161
}
11671162

@@ -1171,16 +1166,10 @@
11711166
}
11721167
}
11731168

1174-
%textarea-group-label--focused {
1175-
transform: translateY(0);
1176-
top: calc(#{$input-top-padding} / 4);
1177-
}
1178-
11791169
%textarea-group-label--filled--border,
11801170
%textarea-group-label--focused--border {
11811171
top: 0;
1182-
transform: translateY(-50%);
1183-
margin-block-end: auto !important;
1172+
translate: 0 -50%;
11841173
}
11851174

11861175
%textarea-group-notch--focused {
@@ -1361,20 +1350,14 @@
13611350
}
13621351

13631352
%form-group-textarea {
1364-
--textarea-size: #{sizable(
1365-
rem(82px, map.get($base-scale-size, 'compact')),
1366-
rem(82px, map.get($base-scale-size, 'cosy')),
1367-
rem(82px, map.get($base-scale-size, 'comfortable'))
1368-
)};
1369-
13701353
min-height: var(--textarea-size);
13711354
height: auto;
13721355
resize: vertical;
13731356
overflow: hidden;
13741357

13751358
@if $material-theme {
13761359
padding: 0;
1377-
inset-block-start: rem(-3px);
1360+
margin-block-start: rem(20px) !important;
13781361
}
13791362

13801363
// resets typography styles
@@ -1390,14 +1373,10 @@
13901373
%form-group-textarea-group-bundle-main {
13911374
overflow: hidden;
13921375

1393-
@if $material-theme or $indigo-theme {
1376+
@if $indigo-theme {
13941377
height: calc(100% - #{rem(2px)});
13951378
top: rem(1px);
13961379
}
1397-
1398-
@if $material-theme {
1399-
padding-block-start: $input-top-padding;
1400-
}
14011380
}
14021381

14031382
%form-group-textarea--disabled {

projects/igniteui-angular/src/lib/directives/radio/radio-group.directive.spec.ts

Lines changed: 106 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ChangeDetectionStrategy, Component, ViewChild } from '@angular/core';
1+
import { ChangeDetectionStrategy, Component, ComponentRef, OnInit, ViewChild, ViewContainerRef } from '@angular/core';
22
import { TestBed, fakeAsync, tick, waitForAsync } from '@angular/core/testing';
33
import { IgxRadioGroupDirective } from './radio-group.directive';
44
import { FormsModule, ReactiveFormsModule, UntypedFormGroup, UntypedFormBuilder, FormGroup, FormControl } from '@angular/forms';
@@ -20,7 +20,9 @@ describe('IgxRadioGroupDirective', () => {
2020
RadioGroupWithModelComponent,
2121
RadioGroupRequiredComponent,
2222
RadioGroupReactiveFormsComponent,
23-
RadioGroupDeepProjectionComponent
23+
RadioGroupDeepProjectionComponent,
24+
RadioGroupTestComponent,
25+
DynamicRadioGroupComponent
2426
]
2527
})
2628
.compileComponents();
@@ -69,13 +71,15 @@ describe('IgxRadioGroupDirective', () => {
6971
// name
7072
radioInstance.name = 'newGroupName';
7173
fixture.detectChanges();
74+
tick();
7275

7376
const allButtonsWithNewName = radioInstance.radioButtons.filter((btn) => btn.name === 'newGroupName');
7477
expect(allButtonsWithNewName.length).toEqual(radioInstance.radioButtons.length);
7578

7679
// required
7780
radioInstance.required = true;
7881
fixture.detectChanges();
82+
tick();
7983

8084
const allRequiredButtons = radioInstance.radioButtons.filter((btn) => btn.required);
8185
expect(allRequiredButtons.length).toEqual(radioInstance.radioButtons.length);
@@ -261,6 +265,38 @@ describe('IgxRadioGroupDirective', () => {
261265
expect(radioGroup.radioButtons.first.checked).toEqual(true);
262266
expect(domRadio.classList.contains('igx-radio--invalid')).toBe(false);
263267
}));
268+
269+
it('Should select radio button when added programmatically after group value is set', (() => {
270+
const fixture = TestBed.createComponent(DynamicRadioGroupComponent);
271+
const component = fixture.componentInstance;
272+
const radioGroup = component.radioGroup;
273+
274+
// Simulate AppBuilder configurator setting value before radio buttons exist
275+
radioGroup.value = 'option2';
276+
277+
// Verify no radio buttons exist yet
278+
expect(radioGroup.radioButtons.length).toBe(0);
279+
expect(radioGroup.selected).toBeNull();
280+
281+
fixture.detectChanges();
282+
283+
component.addRadioButton('option1', 'Option 1');
284+
component.addRadioButton('option2', 'Option 2');
285+
component.addRadioButton('option3', 'Option 3');
286+
287+
fixture.detectChanges();
288+
289+
// Radio button with value 'option2' should be selected
290+
expect(radioGroup.value).toBe('option2');
291+
expect(radioGroup.selected).toBeDefined();
292+
expect(radioGroup.selected.value).toBe('option2');
293+
expect(radioGroup.selected.checked).toBe(true);
294+
295+
// Verify only one radio button is selected
296+
const checkedButtons = radioGroup.radioButtons.filter(btn => btn.checked);
297+
expect(checkedButtons.length).toBe(1);
298+
expect(checkedButtons[0].value).toBe('option2');
299+
}));
264300
});
265301

266302
@Component({
@@ -444,8 +480,75 @@ class RadioGroupDeepProjectionComponent {
444480
}
445481
}
446482

483+
@Component({
484+
template: `
485+
<igx-radio-group
486+
[alignment]="alignment"
487+
[required]="required"
488+
[value]="value"
489+
(change)="handleChange($event)"
490+
>
491+
<ng-container #radioContainer></ng-container>
492+
</igx-radio-group>
493+
`,
494+
imports: [IgxRadioComponent, IgxRadioGroupDirective]
495+
})
496+
497+
class RadioGroupTestComponent implements OnInit {
498+
@ViewChild('radioContainer', { read: ViewContainerRef, static: true })
499+
public container!: ViewContainerRef;
500+
501+
public alignment = 'horizontal';
502+
public required = false;
503+
public value: any;
504+
505+
public radios: { label: string; value: any }[] = [];
506+
507+
public handleChange(args: any) {
508+
this.value = args.value;
509+
}
510+
511+
public ngOnInit(): void {
512+
this.container.clear();
513+
this.radios.forEach((option) => {
514+
const componentRef: ComponentRef<IgxRadioComponent> =
515+
this.container.createComponent(IgxRadioComponent);
516+
517+
componentRef.instance.placeholderLabel.nativeElement.textContent =
518+
option.label;
519+
componentRef.instance.value = option.value;
520+
});
521+
}
522+
}
523+
524+
@Component({
525+
template: `
526+
<igx-radio-group #radioGroup>
527+
<ng-container #radioContainer></ng-container>
528+
</igx-radio-group>
529+
`,
530+
imports: [IgxRadioGroupDirective, IgxRadioComponent]
531+
})
532+
class DynamicRadioGroupComponent {
533+
@ViewChild('radioGroup', { read: IgxRadioGroupDirective, static: true })
534+
public radioGroup: IgxRadioGroupDirective;
535+
536+
@ViewChild('radioContainer', { read: ViewContainerRef, static: true })
537+
public radioContainer: ViewContainerRef;
538+
539+
/**
540+
* Simulates how AppBuilder adds radio buttons programmatically
541+
* via ViewContainerRef.createComponent()
542+
*/
543+
public addRadioButton(value: string, label: string): void {
544+
const componentRef = this.radioContainer.createComponent(IgxRadioComponent);
545+
componentRef.instance.value = value;
546+
componentRef.instance.placeholderLabel.nativeElement.textContent = label;
547+
componentRef.changeDetectorRef.detectChanges();
548+
}
549+
}
550+
447551
const dispatchRadioEvent = (eventName, radioNativeElement, fixture) => {
448552
radioNativeElement.dispatchEvent(new Event(eventName));
449553
fixture.detectChanges();
450554
};
451-

projects/igniteui-angular/src/lib/directives/radio/radio-group.directive.ts

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import {
1212
QueryList,
1313
Self,
1414
booleanAttribute,
15-
contentChildren,
1615
effect,
1716
signal
1817
} from '@angular/core';
@@ -63,7 +62,7 @@ let nextId = 0;
6362
standalone: true
6463
})
6564
export class IgxRadioGroupDirective implements ControlValueAccessor, OnDestroy, DoCheck {
66-
private _radioButtons = contentChildren(IgxRadioComponent, { descendants: true });
65+
private _radioButtons = signal<IgxRadioComponent[]>([]);
6766
private _radioButtonsList = new QueryList<IgxRadioComponent>();
6867

6968
/**
@@ -75,8 +74,7 @@ export class IgxRadioGroupDirective implements ControlValueAccessor, OnDestroy,
7574
* ```
7675
*/
7776
public get radioButtons(): QueryList<IgxRadioComponent> {
78-
const buttons = Array.from(this._radioButtons());
79-
this._radioButtonsList.reset(buttons);
77+
this._radioButtonsList.reset(this._radioButtons());
8078
return this._radioButtonsList;
8179
}
8280

@@ -493,10 +491,7 @@ export class IgxRadioGroupDirective implements ControlValueAccessor, OnDestroy,
493491

494492
effect(() => {
495493
this.initialize();
496-
497-
Promise.resolve().then(() => {
498-
this.setRadioButtons();
499-
});
494+
this.setRadioButtons();
500495
});
501496
}
502497

@@ -534,8 +529,10 @@ export class IgxRadioGroupDirective implements ControlValueAccessor, OnDestroy,
534529
*/
535530
private setRadioButtons() {
536531
this._radioButtons().forEach((button) => {
537-
button.name = this._name;
538-
button.required = this._required;
532+
Promise.resolve().then(() => {
533+
button.name = this._name;
534+
button.required = this._required;
535+
});
539536

540537
if (button.value === this._value) {
541538
button.checked = true;
@@ -647,6 +644,33 @@ export class IgxRadioGroupDirective implements ControlValueAccessor, OnDestroy,
647644
}
648645
}
649646

647+
/**
648+
* Registers a radio button with this radio group.
649+
* This method is called by radio button components when they are created.
650+
* @hidden @internal
651+
*/
652+
public _addRadioButton(radioButton: IgxRadioComponent): void {
653+
this._radioButtons.update(buttons => {
654+
if (!buttons.includes(radioButton)) {
655+
this._setRadioButtonEvents(radioButton);
656+
657+
return [...buttons, radioButton];
658+
}
659+
return buttons;
660+
});
661+
}
662+
663+
/**
664+
* Unregisters a radio button from this radio group.
665+
* This method is called by radio button components when they are destroyed.
666+
* @hidden @internal
667+
*/
668+
public _removeRadioButton(radioButton: IgxRadioComponent): void {
669+
this._radioButtons.update(buttons =>
670+
buttons.filter(btn => btn !== radioButton)
671+
);
672+
}
673+
650674
/**
651675
* @hidden
652676
* @internal

projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575
(selectionChanging)="onReturnFieldSelectChanging($event)"
7676
[overlaySettings]="returnFieldSelectOverlaySettings"
7777
[disabled]="!selectedEntity"
78-
[ngModel]="selectedReturnFields[0]"
78+
[ngModel]="selectedReturnFields ? selectedReturnFields[0] : null"
7979
[placeholder]="this.resourceStrings.igx_query_builder_select_return_field_single"
8080
[style.display]="isInEditMode() ? 'block' : 'none'"
8181
(opening)="exitEditAddMode()"
@@ -466,7 +466,7 @@
466466
<ng-container>
467467
<igx-query-builder-tree
468468
[style.display]="expressionItem.inEditMode || expressionItem.expanded ? 'block' : 'none'"
469-
[entities]="(this.selectedEntity ? this.selectedEntity.childEntities : entities[0].childEntities) ?? entities"
469+
[entities]="(this.selectedEntity ? this.selectedEntity.childEntities : entities?.[0]?.childEntities) ?? (entities ?? [])"
470470
[queryBuilder]="this.queryBuilder"
471471
[parentExpression]="expressionItem"
472472
[expectedReturnField]="this.selectedField?.field"

0 commit comments

Comments
 (0)