Skip to content

Commit ea6756c

Browse files
refactor(rating): Skip default symbols when there are projected ones (#1381)
This commit prevents the default rating symbols from being rendered in the shadow DOM when there are user projected ones. Updated some internal event handlers and types to use pointer events instead of mouse ones as well as unit tests adjustments to these changes. Co-authored-by: Simeon Simeonoff <sim.simeonoff@gmail.com>
1 parent fa44652 commit ea6756c

File tree

2 files changed

+53
-86
lines changed

2 files changed

+53
-86
lines changed

src/components/rating/rating.spec.ts

Lines changed: 26 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,6 @@ import {
88
import { nothing } from 'lit';
99
import { spy } from 'sinon';
1010

11-
import {
12-
IgcRatingComponent,
13-
type IgcRatingSymbolComponent,
14-
defineComponents,
15-
} from '../../index.js';
1611
import {
1712
arrowDown,
1813
arrowLeft,
@@ -21,34 +16,38 @@ import {
2116
endKey,
2217
homeKey,
2318
} from '../common/controllers/key-bindings.js';
19+
import { defineComponents } from '../common/definitions/defineComponents.js';
2420
import {
2521
FormAssociatedTestBed,
22+
simulateClick,
2623
simulateKeyboard,
24+
simulatePointerMove,
2725
} from '../common/utils.spec.js';
26+
import IgcRatingSymbolComponent from './rating-symbol.js';
27+
import IgcRatingComponent from './rating.js';
2828

2929
describe('Rating component', () => {
3030
before(() => {
3131
defineComponents(IgcRatingComponent);
3232
});
3333

34+
let el: IgcRatingComponent;
35+
3436
const getRatingSymbols = (el: IgcRatingComponent) =>
35-
el.shadowRoot!.querySelectorAll(
36-
'igc-rating-symbol'
37-
) as NodeListOf<IgcRatingSymbolComponent>;
38-
const getProjectedSymbols = (el: IgcRatingComponent) => {
39-
const slot = el.shadowRoot!.querySelector(
40-
'slot[name="symbol"]'
41-
) as HTMLSlotElement;
42-
return slot
43-
.assignedElements()
44-
.filter((el) => el.matches('igc-rating-symbol'));
45-
};
37+
el.renderRoot.querySelectorAll(IgcRatingSymbolComponent.tagName);
38+
39+
const getProjectedSymbols = (el: IgcRatingComponent) =>
40+
el.renderRoot
41+
.querySelector<HTMLSlotElement>('slot[name="symbol"]')
42+
?.assignedElements()
43+
.filter((each) =>
44+
each.matches(IgcRatingSymbolComponent.tagName)
45+
) as IgcRatingSymbolComponent[];
46+
4647
const getRatingWrapper = (el: IgcRatingComponent) =>
47-
el.shadowRoot!.querySelector(`[part='base']`) as HTMLElement;
48-
const fireMouseEvent = (type: string, opts: MouseEventInit) =>
49-
new MouseEvent(type, opts);
48+
el.renderRoot.querySelector<HTMLElement>('[part="base"]')!;
49+
5050
const getBoundingRect = (el: Element) => el.getBoundingClientRect();
51-
let el: IgcRatingComponent;
5251

5352
describe('', () => {
5453
beforeEach(async () => {
@@ -280,13 +279,8 @@ describe('Rating component', () => {
280279
const eventSpy = spy(el, 'emitEvent');
281280
const symbol = getRatingSymbols(el).item(2);
282281
const { x, width } = getBoundingRect(symbol);
283-
symbol.dispatchEvent(
284-
fireMouseEvent('click', {
285-
bubbles: true,
286-
composed: true,
287-
clientX: x + width / 2,
288-
})
289-
);
282+
simulateClick(symbol, { clientX: x + width / 2 });
283+
290284
expect(eventSpy).calledOnceWithExactly('igcChange', { detail: 3 });
291285
expect(el.value).to.equal(3);
292286
});
@@ -298,13 +292,8 @@ describe('Rating component', () => {
298292

299293
const symbol = getRatingSymbols(el).item(2);
300294
const { x, width } = getBoundingRect(symbol);
301-
symbol.dispatchEvent(
302-
fireMouseEvent('click', {
303-
bubbles: true,
304-
composed: true,
305-
clientX: x + width / 4,
306-
})
307-
);
295+
simulateClick(symbol, { clientX: x + width / 4 });
296+
308297
expect(eventSpy).calledOnceWithExactly('igcChange', { detail: 2.5 });
309298
expect(el.value).to.equal(2.5);
310299
});
@@ -316,14 +305,8 @@ describe('Rating component', () => {
316305
await elementUpdated(el);
317306
const symbol = getRatingSymbols(el).item(2);
318307
const { x, width } = getBoundingRect(symbol);
308+
simulatePointerMove(symbol, { clientX: x + width / 2 });
319309

320-
symbol.dispatchEvent(
321-
fireMouseEvent('mousemove', {
322-
bubbles: true,
323-
composed: true,
324-
clientX: x + width / 2,
325-
})
326-
);
327310
expect(eventSpy).calledOnceWithExactly('igcHover', { detail: 3 });
328311
expect(el.value).to.equal(2);
329312
});
@@ -335,14 +318,7 @@ describe('Rating component', () => {
335318

336319
el.value = 5;
337320
await elementUpdated(el);
338-
339-
symbol.dispatchEvent(
340-
fireMouseEvent('click', {
341-
bubbles: true,
342-
composed: true,
343-
clientX: x + width / 2,
344-
})
345-
);
321+
simulateClick(symbol, { clientX: x + width / 2 });
346322

347323
expect(el.value).to.equal(5);
348324
expect(eventSpy).not.to.be.called;
@@ -357,13 +333,7 @@ describe('Rating component', () => {
357333
el.allowReset = true;
358334
await elementUpdated(el);
359335

360-
symbol.dispatchEvent(
361-
fireMouseEvent('click', {
362-
bubbles: true,
363-
composed: true,
364-
clientX: x + width / 2,
365-
})
366-
);
336+
simulateClick(symbol, { clientX: x + width / 2 });
367337

368338
expect(el.value).to.equal(0);
369339
expect(eventSpy).to.have.been.calledOnceWith('igcChange', { detail: 0 });

src/components/rating/rating.ts

Lines changed: 27 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ export default class IgcRatingComponent extends FormAssociatedMixin(
237237
.set(endKey, () => this.emitValueUpdate(this.max));
238238
}
239239

240-
protected handleClick({ clientX }: MouseEvent) {
240+
protected handleClick({ clientX }: PointerEvent) {
241241
const value = this.calcNewValue(clientX);
242242
const sameValue = this.value === value;
243243

@@ -248,11 +248,11 @@ export default class IgcRatingComponent extends FormAssociatedMixin(
248248
}
249249
}
250250

251-
protected handleMouseMove({ clientX }: MouseEvent) {
251+
protected handlePointerMove({ clientX }: PointerEvent) {
252252
const value = this.calcNewValue(clientX);
253253

254254
if (this.hoverValue !== value) {
255-
// Since mousemove spams a lot, only emit on a value change
255+
// Since pointermove spams a lot, only emit on a value change
256256
this.hoverValue = value;
257257
this.emitEvent('igcHover', { detail: this.hoverValue });
258258
}
@@ -358,26 +358,22 @@ export default class IgcRatingComponent extends FormAssociatedMixin(
358358
}
359359

360360
protected clipProjected() {
361-
if (this.hasProjectedSymbols) {
362-
const ltr = isLTR(this);
363-
this.ratingSymbols.forEach((symbol: IgcRatingSymbolComponent, i) => {
364-
const full = symbol.shadowRoot?.querySelector(
365-
'[part="symbol full"]'
366-
) as HTMLElement;
367-
368-
const empty = symbol.shadowRoot?.querySelector(
369-
'[part="symbol empty"]'
370-
) as HTMLElement;
371-
const { forward, backward } = this.clipSymbol(i, ltr);
372-
373-
if (full) {
374-
full.style.clipPath = forward;
375-
}
376-
377-
if (empty) {
378-
empty.style.clipPath = backward;
379-
}
380-
});
361+
const ltr = isLTR(this);
362+
const partFull = '[part="symbol full"]';
363+
const partEmpty = '[part="symbol empty"]';
364+
365+
for (const [i, symbol] of this.ratingSymbols.entries()) {
366+
const full = symbol.renderRoot.querySelector<HTMLElement>(partFull);
367+
const empty = symbol.renderRoot.querySelector<HTMLElement>(partEmpty);
368+
const { forward, backward } = this.clipSymbol(i, ltr);
369+
370+
if (full) {
371+
full.style.clipPath = forward;
372+
}
373+
374+
if (empty) {
375+
empty.style.clipPath = backward;
376+
}
381377
}
382378
}
383379

@@ -412,15 +408,16 @@ export default class IgcRatingComponent extends FormAssociatedMixin(
412408
aria-hidden="true"
413409
part="symbols"
414410
@click=${this.isInteractive ? this.handleClick : nothing}
415-
@mouseenter=${hoverActive ? this.handleHoverEnabled : nothing}
416-
@mouseleave=${hoverActive ? this.handleHoverDisabled : nothing}
417-
@mousemove=${hoverActive ? this.handleMouseMove : nothing}
411+
@pointerenter=${hoverActive ? this.handleHoverEnabled : nothing}
412+
@pointerleave=${hoverActive ? this.handleHoverDisabled : nothing}
413+
@pointermove=${hoverActive ? this.handlePointerMove : nothing}
418414
>
419415
<slot name="symbol" @slotchange=${this.handleSlotChange}>
420-
${guard(props, () => {
421-
this.clipProjected();
422-
return this.renderSymbols();
423-
})}
416+
${guard(props, () =>
417+
this.hasProjectedSymbols
418+
? this.clipProjected()
419+
: this.renderSymbols()
420+
)}
424421
</slot>
425422
</div>
426423
<label part="value-label" ?hidden=${this.valueLabel.length === 0}>

0 commit comments

Comments
 (0)