Skip to content

Commit 09bc827

Browse files
authored
Merge branch 'master' into skrastev/localization-intl
2 parents a12394d + 7962650 commit 09bc827

File tree

10 files changed

+134
-49
lines changed

10 files changed

+134
-49
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](http://keepachangelog.com/)
55
and this project adheres to [Semantic Versioning](http://semver.org/).
66

7+
## [Unreleased]
8+
### Fixed
9+
- #### Tooltip
10+
- Do not show tooltip when target is clicked [#1828](https://github.com/IgniteUI/igniteui-webcomponents/issues/1828)
11+
712
## [6.3.4] - 2025-10-22
813
### Fixed
914
- #### Date picker

src/components/combo/combo.spec.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,36 @@ describe('Combo', () => {
445445
});
446446
});
447447

448+
it('should hide the clear button when disableClear is true', async () => {
449+
combo.select();
450+
await elementUpdated(combo);
451+
452+
let button = combo.shadowRoot!.querySelector('[part="clear-icon"]');
453+
expect(button).to.exist;
454+
expect(button?.hasAttribute('hidden')).to.be.false;
455+
456+
combo.disableClear = true;
457+
await elementUpdated(combo);
458+
459+
button = combo.shadowRoot!.querySelector('[part="clear-icon"]');
460+
expect(button?.hasAttribute('hidden')).to.be.true;
461+
});
462+
463+
it('should show the clear button when disableClear is false', async () => {
464+
combo.disableClear = true;
465+
combo.select();
466+
await elementUpdated(combo);
467+
468+
let button = combo.shadowRoot!.querySelector('[part="clear-icon"]');
469+
expect(button?.hasAttribute('hidden')).to.be.true;
470+
471+
combo.disableClear = false;
472+
await elementUpdated(combo);
473+
474+
button = combo.shadowRoot!.querySelector('[part="clear-icon"]');
475+
expect(button?.hasAttribute('hidden')).to.be.false;
476+
});
477+
448478
it('should toggle case sensitivity by pressing on the case sensitive icon', async () => {
449479
const button = combo.shadowRoot!.querySelector(
450480
'[part~="case-icon"]'

src/components/combo/combo.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,14 @@ export default class IgcComboComponent<
445445
return this._disableFiltering;
446446
}
447447

448+
/**
449+
* Hides the clear button.
450+
* @attr disable-clear
451+
* @default false
452+
*/
453+
@property({ type: Boolean, attribute: 'disable-clear' })
454+
public disableClear = false;
455+
448456
/* blazorSuppress */
449457
/**
450458
* The template used for the content of each combo item.
@@ -937,7 +945,7 @@ export default class IgcComboComponent<
937945
slot="suffix"
938946
part="clear-icon"
939947
@click=${this.handleClearIconClick}
940-
?hidden=${this._selection.isEmpty}
948+
?hidden=${this.disableClear || this._selection.isEmpty}
941949
>
942950
<slot name="clear-icon">
943951
<igc-icon
Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,46 @@
1-
import { groupBy } from '../../common/util.js';
21
import type { DataController } from '../controllers/data.js';
32
import type { ComboRecord, Keys } from '../types.js';
43

5-
export default class GroupDataOperation<T extends object> {
6-
protected orderBy = new Map(
7-
Object.entries({
8-
asc: 1,
9-
desc: -1,
10-
})
11-
);
4+
const OrderBy = Object.freeze({ asc: 1, desc: -1 });
125

13-
public apply(data: ComboRecord<T>[], controller: DataController<T>) {
6+
export default class GroupDataOperation<T extends object> {
7+
public apply(
8+
data: ComboRecord<T>[],
9+
controller: DataController<T>
10+
): ComboRecord<T>[] {
1411
const {
1512
groupingOptions: { groupKey, valueKey, displayKey, direction },
1613
} = controller;
1714

18-
if (!groupKey) return data;
15+
if (!groupKey) {
16+
return data;
17+
}
1918

20-
const groups = Object.entries(
21-
groupBy(data, (item) => item.value[groupKey] ?? 'Other')
19+
const grouped = Map.groupBy(
20+
data,
21+
(item) => (item.value[groupKey] as string) ?? 'Other'
2222
);
2323

24+
const keys = Array.from(grouped.keys());
25+
2426
if (direction !== 'none') {
25-
const orderBy = this.orderBy.get(direction);
26-
groups.sort((a, b) => {
27-
return orderBy! * controller.compareCollator.compare(a[0], b[0]);
28-
});
27+
const orderBy = OrderBy[direction];
28+
keys.sort((a, b) => orderBy * controller.compareCollator.compare(a, b));
2929
}
3030

31-
return groups.flatMap(([group, items]) => {
32-
items.unshift({
33-
dataIndex: -1,
34-
header: true,
35-
value: {
36-
[valueKey as Keys<T>]: group,
37-
[displayKey as Keys<T>]: group,
38-
[groupKey as Keys<T>]: group,
39-
} as T,
40-
});
41-
42-
return items;
31+
return keys.flatMap((key) => {
32+
return [
33+
{
34+
value: {
35+
[valueKey as Keys<T>]: key,
36+
[displayKey as Keys<T>]: key,
37+
[groupKey as Keys<T>]: key,
38+
} as T,
39+
header: true,
40+
dataIndex: -1,
41+
},
42+
...(grouped.get(key) ?? []),
43+
];
4344
});
4445
}
4546
}

src/components/common/util.ts

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -168,24 +168,6 @@ export function findElementFromEventPath<T extends Element>(
168168
return getElementsFromEventPath(event).find(func) as T | undefined;
169169
}
170170

171-
export function groupBy<T>(array: T[], key: keyof T | ((item: T) => any)) {
172-
const result: Record<string, T[]> = {};
173-
const _get = isFunction(key) ? key : (item: T) => item[key];
174-
175-
for (const item of array) {
176-
const category = _get(item);
177-
const group = result[category];
178-
179-
if (Array.isArray(group)) {
180-
group.push(item);
181-
} else {
182-
result[category] = [item];
183-
}
184-
}
185-
186-
return result;
187-
}
188-
189171
export function first<T>(arr: T[]) {
190172
return arr.at(0) as T;
191173
}

src/components/tooltip/controller.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,10 @@ class TooltipController implements ReactiveController {
133133
for (const each of this._hideTriggers) {
134134
addWeakEventListener(anchor, each, this, { passive: true, signal });
135135
}
136+
137+
if (!this._showTriggers.has('click') && !this._hideTriggers.has('click')) {
138+
addWeakEventListener(anchor, 'click', this, { passive: true, signal });
139+
}
136140
}
137141

138142
private _addTooltipListeners(): void {
@@ -161,6 +165,15 @@ class TooltipController implements ReactiveController {
161165
}
162166

163167
private async _handleAnchorEvent(event: Event): Promise<void> {
168+
if (
169+
!this.open &&
170+
!this._showTriggers.has(event.type) &&
171+
event.type === 'click'
172+
) {
173+
this._options.onClick.call(this._host);
174+
return;
175+
}
176+
164177
if (!this._open && this._showTriggers.has(event.type)) {
165178
await this._options.onShow.call(this._host);
166179
}
@@ -283,4 +296,5 @@ type TooltipCallbacks = {
283296
onShow: (event?: Event) => unknown;
284297
onHide: (event?: Event) => unknown;
285298
onEscape: (event?: Event) => unknown;
299+
onClick: (event?: Event) => unknown;
286300
};

src/components/tooltip/tooltip.spec.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -534,7 +534,7 @@ describe('Tooltip', () => {
534534

535535
describe('Behaviors', () => {
536536
beforeEach(async () => {
537-
clock = useFakeTimers({ toFake: ['setTimeout'] });
537+
clock = useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] });
538538
const container = await fixture(createTooltipWithTarget());
539539
anchor = container.querySelector('button')!;
540540
tooltip = container.querySelector(IgcTooltipComponent.tagName)!;
@@ -660,6 +660,40 @@ describe('Tooltip', () => {
660660
await hideComplete(tooltip);
661661
expect(tooltip.open).to.be.false;
662662
});
663+
664+
it('prevents tooltip from showing when clicking the target - #1828', async () => {
665+
const eventSpy = spy(tooltip, 'emitEvent');
666+
667+
tooltip.showTriggers = 'pointerenter';
668+
tooltip.hideTriggers = 'pointerleave';
669+
670+
simulatePointerEnter(anchor);
671+
await clock.tickAsync(199);
672+
expect(tooltip.open).to.be.false;
673+
674+
// Click on the target before the tooltip is shown
675+
simulateClick(anchor);
676+
await clock.tickAsync(1);
677+
await showComplete(tooltip);
678+
expect(tooltip.open).to.be.false;
679+
expect(eventSpy.callCount).to.equal(1);
680+
681+
eventSpy.resetHistory();
682+
683+
// Does not prevent showing when showTriggers includes 'click'
684+
tooltip.showTriggers = 'click';
685+
686+
simulateClick(anchor);
687+
await clock.tickAsync(DEFAULT_SHOW_DELAY);
688+
await showComplete(tooltip);
689+
expect(tooltip.open).to.be.true;
690+
691+
simulatePointerLeave(anchor);
692+
await clock.tickAsync(endTick(DEFAULT_HIDE_DELAY));
693+
await hideComplete(tooltip);
694+
expect(tooltip.open).to.be.false;
695+
expect(eventSpy.callCount).to.equal(4);
696+
});
663697
});
664698

665699
describe('Events', () => {

src/components/tooltip/tooltip.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ export default class IgcTooltipComponent extends EventEmitterMixin<
7777
onShow: this._showOnInteraction,
7878
onHide: this._hideOnInteraction,
7979
onEscape: this._hideOnEscape,
80+
onClick: this._stopTimeoutAndAnimation,
8081
});
8182

8283
private readonly _containerRef = createRef<HTMLElement>();

stories/combo.stories.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,12 @@ const metadata: Meta<IgcComboComponent> = {
134134
control: 'boolean',
135135
table: { defaultValue: { summary: 'false' } },
136136
},
137+
disableClear: {
138+
type: 'boolean',
139+
description: 'Hides the clear button.',
140+
control: 'boolean',
141+
table: { defaultValue: { summary: 'false' } },
142+
},
137143
required: {
138144
type: 'boolean',
139145
description:
@@ -168,6 +174,7 @@ const metadata: Meta<IgcComboComponent> = {
168174
groupSorting: 'asc',
169175
caseSensitiveIcon: false,
170176
disableFiltering: false,
177+
disableClear: false,
171178
required: false,
172179
disabled: false,
173180
invalid: false,
@@ -207,6 +214,8 @@ interface IgcComboArgs {
207214
caseSensitiveIcon: boolean;
208215
/** Disables the filtering of the list of options. */
209216
disableFiltering: boolean;
217+
/** Hides the clear button. */
218+
disableClear: boolean;
210219
/** When set, makes the component a required field for validation. */
211220
required: boolean;
212221
/** The name attribute of the control. */
@@ -323,6 +332,7 @@ export const Default: Story = {
323332
.groupSorting=${args.groupSorting}
324333
?case-sensitive-icon=${args.caseSensitiveIcon}
325334
?disable-filtering=${args.disableFiltering}
335+
?disable-clear=${args.disableClear}
326336
?open=${args.open}
327337
?autofocus=${args.autofocus}
328338
?autofocus-list=${args.autofocusList}

tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"compilerOptions": {
33
"target": "es2022",
44
"module": "NodeNext",
5-
"lib": ["es2023", "DOM", "DOM.Iterable"],
5+
"lib": ["ESNext", "DOM", "DOM.Iterable"],
66
"outDir": "dist",
77
"rootDir": "./",
88
"declaration": true,

0 commit comments

Comments
 (0)