Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions angular/projects/catalyst/src/lib/directives/proxies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1791,14 +1791,34 @@ export declare interface CatToggle extends Components.CatToggle {
}

@ProxyCmp({
inputs: ['content', 'disabled', 'hideDelay', 'longTouchDuration', 'placement', 'round', 'showDelay', 'size']
inputs: [
'content',
'disabled',
'hideDelay',
'longTouchDuration',
'placement',
'round',
'showDelay',
'size',
'trigger'
]
})
@Component({
selector: 'cat-tooltip',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: ['content', 'disabled', 'hideDelay', 'longTouchDuration', 'placement', 'round', 'showDelay', 'size'],
inputs: [
'content',
'disabled',
'hideDelay',
'longTouchDuration',
'placement',
'round',
'showDelay',
'size',
'trigger'
],
standalone: false
})
export class CatTooltip {
Expand Down
11 changes: 11 additions & 0 deletions core/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2179,6 +2179,11 @@ export namespace Components {
* @default 'm'
*/
"size": 's' | 'm' | 'l';
/**
* The trigger event(s) that show and hide the tooltip. - 'hover-focus': show on both mouse hover and keyboard focus (default) - 'hover': show on mouse hover only - 'focus': show on keyboard focus only
* @default 'hover-focus'
*/
"trigger": 'hover' | 'focus' | 'hover-focus';
}
}
export interface CatButtonCustomEvent<T> extends CustomEvent<T> {
Expand Down Expand Up @@ -5080,6 +5085,11 @@ declare namespace LocalJSX {
* @default 'm'
*/
"size"?: 's' | 'm' | 'l';
/**
* The trigger event(s) that show and hide the tooltip. - 'hover-focus': show on both mouse hover and keyboard focus (default) - 'hover': show on mouse hover only - 'focus': show on keyboard focus only
* @default 'hover-focus'
*/
"trigger"?: 'hover' | 'focus' | 'hover-focus';
}

interface CatAlertAttributes {
Expand Down Expand Up @@ -5513,6 +5523,7 @@ declare namespace LocalJSX {
"showDelay": number;
"hideDelay": number;
"longTouchDuration": number;
"trigger": 'hover' | 'focus' | 'hover-focus';
}

interface IntrinsicElements {
Expand Down
1 change: 1 addition & 0 deletions core/src/components/cat-select-demo/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ graph TD;
cat-select-demo --> cat-select
cat-select-demo --> cat-dropdown
cat-select-demo --> cat-button
cat-select --> cat-tooltip
cat-select --> cat-avatar
cat-select --> cat-button
cat-select --> cat-spinner
Expand Down
163 changes: 163 additions & 0 deletions core/src/components/cat-select/cat-select.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -240,4 +240,167 @@ describe('cat-select', () => {
expect(updatedOptions[0].render.description).toBe('Updated description');
});
});

describe('truncation tooltips', () => {
describe('option label truncation', () => {
it('should mark an option as truncated when scrollHeight exceeds clientHeight', async () => {
const { root, waitForChanges, instance } = await render(<cat-select label="Label" />);
await instance.connect(stringArrayConnector(['option1', 'option2']));

const trigger = root?.shadowRoot?.querySelector('.select-wrapper');
trigger?.dispatchEvent(new MouseEvent('click', { bubbles: true }));
await waitForChanges();

const labelEl = root?.shadowRoot?.querySelector<HTMLElement>('.select-option-label[data-option-key="option1"]');
if (labelEl) {
Object.defineProperty(labelEl, 'scrollHeight', { value: 50, configurable: true });
Object.defineProperty(labelEl, 'clientHeight', { value: 24, configurable: true });
}

instance['checkOptionsTruncation']();
await waitForChanges();

expect(instance['truncatedOptionKeys'].has('option1')).toBe(true);
expect(instance['truncatedOptionKeys'].has('option2')).toBe(false);
});

it('should not mark an option as truncated when scrollHeight equals clientHeight', async () => {
const { root, waitForChanges, instance } = await render(<cat-select label="Label" />);
await instance.connect(stringArrayConnector(['option1']));

const trigger = root?.shadowRoot?.querySelector('.select-wrapper');
trigger?.dispatchEvent(new MouseEvent('click', { bubbles: true }));
await waitForChanges();

const labelEl = root?.shadowRoot?.querySelector<HTMLElement>('.select-option-label[data-option-key="option1"]');
if (labelEl) {
Object.defineProperty(labelEl, 'scrollHeight', { value: 24, configurable: true });
Object.defineProperty(labelEl, 'clientHeight', { value: 24, configurable: true });
}

instance['checkOptionsTruncation']();
await waitForChanges();

expect(instance['truncatedOptionKeys'].has('option1')).toBe(false);
});

it('should clear truncatedOptionKeys when dropdown closes', async () => {
const { root, waitForChanges, instance } = await render(<cat-select label="Label" />);
await instance.connect(stringArrayConnector(['option1']));

instance['truncatedOptionKeys'] = new Set(['option1']);

const trigger = root?.shadowRoot?.querySelector('.select-wrapper');
trigger?.dispatchEvent(new MouseEvent('click', { bubbles: true }));
await waitForChanges();
trigger?.dispatchEvent(new MouseEvent('click', { bubbles: true }));
await waitForChanges();

expect(instance['truncatedOptionKeys'].size).toBe(0);
});
});

describe('pill label truncation', () => {
it('should mark a pill as truncated when scrollWidth exceeds clientWidth', async () => {
const { root, waitForChanges, instance } = await render(<cat-select label="Label" multiple />);
await instance.connect(stringArrayConnector(['option1', 'option2']));

instance['patchState']({
selection: [
{ item: { id: 'option1' }, render: { label: 'option1' } },
{ item: { id: 'option2' }, render: { label: 'option2' } }
],
tempSelection: []
});
await waitForChanges();

const pillSpan = root?.shadowRoot?.querySelector<HTMLElement>('span[data-pill-index="0"]');
if (pillSpan) {
Object.defineProperty(pillSpan, 'scrollWidth', { value: 100, configurable: true });
Object.defineProperty(pillSpan, 'clientWidth', { value: 60, configurable: true });
}

instance['checkInputTruncation']();
await waitForChanges();

expect(instance['truncatedPillIndices'].has(0)).toBe(true);
expect(instance['truncatedPillIndices'].has(1)).toBe(false);
});

it('should not mark a pill as truncated when scrollWidth equals clientWidth', async () => {
const { root, waitForChanges, instance } = await render(<cat-select label="Label" multiple />);
await instance.connect(stringArrayConnector(['option1']));

instance['patchState']({
selection: [{ item: { id: 'option1' }, render: { label: 'option1' } }],
tempSelection: []
});
await waitForChanges();

const pillSpan = root?.shadowRoot?.querySelector<HTMLElement>('span[data-pill-index="0"]');
if (pillSpan) {
Object.defineProperty(pillSpan, 'scrollWidth', { value: 60, configurable: true });
Object.defineProperty(pillSpan, 'clientWidth', { value: 60, configurable: true });
}

instance['checkInputTruncation']();
await waitForChanges();

expect(instance['truncatedPillIndices'].has(0)).toBe(false);
});
});

describe('single-select input truncation', () => {
it('should mark input as truncated when scrollWidth exceeds clientWidth', async () => {
const { root, waitForChanges, instance } = await render(<cat-select label="Label" />);
await instance.connect(stringArrayConnector(['option1']));
await waitForChanges();

const input = root?.shadowRoot?.querySelector<HTMLInputElement>('input.select-input');
if (input) {
Object.defineProperty(input, 'scrollWidth', { value: 200, configurable: true });
Object.defineProperty(input, 'clientWidth', { value: 80, configurable: true });
}

instance['checkInputTruncation']();
await waitForChanges();

expect(instance['singleValueTruncated']).toBe(true);
});

it('should not mark input as truncated when scrollWidth equals clientWidth', async () => {
const { root, waitForChanges, instance } = await render(<cat-select label="Label" />);
await instance.connect(stringArrayConnector(['option1']));
await waitForChanges();

const input = root?.shadowRoot?.querySelector<HTMLInputElement>('input.select-input');
if (input) {
Object.defineProperty(input, 'scrollWidth', { value: 80, configurable: true });
Object.defineProperty(input, 'clientWidth', { value: 80, configurable: true });
}

instance['checkInputTruncation']();
await waitForChanges();

expect(instance['singleValueTruncated']).toBe(false);
});

it('should not set singleValueTruncated in multiple mode', async () => {
const { root, waitForChanges, instance } = await render(<cat-select label="Label" multiple />);
await instance.connect(stringArrayConnector(['option1']));
await waitForChanges();

const input = root?.shadowRoot?.querySelector<HTMLInputElement>('input.select-input');
if (input) {
Object.defineProperty(input, 'scrollWidth', { value: 200, configurable: true });
Object.defineProperty(input, 'clientWidth', { value: 80, configurable: true });
}

instance['checkInputTruncation']();
await waitForChanges();

expect(instance['singleValueTruncated']).toBe(false);
});
});
});
});
Loading
Loading