diff --git a/packages/elements/src/item/__snapshots__/Item.md b/packages/elements/src/item/__snapshots__/Item.md index 5af8718921..5e8ef81115 100644 --- a/packages/elements/src/item/__snapshots__/Item.md +++ b/packages/elements/src/item/__snapshots__/Item.md @@ -9,10 +9,7 @@ -
+
@@ -35,10 +32,7 @@
-
+
@@ -61,10 +55,7 @@
-
+
@@ -82,10 +73,7 @@
-
+
tiger @@ -107,10 +95,7 @@
-
+
tiger @@ -132,10 +117,7 @@
-
+
@@ -153,10 +135,7 @@
-
+
@@ -177,10 +156,7 @@
-
+
tiger @@ -225,10 +201,7 @@
-
+
@@ -255,10 +228,7 @@
-
+
@@ -282,10 +252,7 @@
-
+
@@ -301,10 +268,7 @@
-
+
diff --git a/packages/elements/src/item/__test__/item.test.js b/packages/elements/src/item/__test__/item.test.js index fedc23e459..20914b85e5 100644 --- a/packages/elements/src/item/__test__/item.test.js +++ b/packages/elements/src/item/__test__/item.test.js @@ -1,4 +1,4 @@ -import { elementUpdated, expect, fixture, nextFrame } from '@refinitiv-ui/test-helpers'; +import { elementUpdated, expect, fixture } from '@refinitiv-ui/test-helpers'; // import element and theme import '@refinitiv-ui/elements/item'; import '@refinitiv-ui/elemental-theme/light/ef-item'; @@ -17,6 +17,10 @@ const createFixture = (type = '') => { return fixture('Test Not Highlightable'); case 'is_truncated': return fixture('
Super vary long string that need to be truncated by parent
'); + case 'is_truncated_label': + return fixture('
'); + case 'is_truncated_subLabel': + return fixture('
'); case 'with_icon': return fixture('With settings icon'); case 'with_empty_icon': @@ -130,23 +134,27 @@ describe('item/Item', () => { expect(el.highlightable).to.equal(false); }); - it('Should truncate text', async () => { + it('Should truncate item text', async () => { const div = await createFixture('is_truncated'); const el = div.querySelector('ef-item'); + expect(el.isItemOverflown(), 'Should truncate text').to.equal(true); + }); - await elementUpdated(el); - await nextFrame(); + it('Should truncate label', async () => { + const div = await createFixture('is_truncated_label'); + const el = div.querySelector('ef-item'); + expect(el.isItemOverflown(), 'Should truncate text').to.equal(true); + }); - expect(el.isTruncated, 'Should truncate text').to.equal(true); + it('Should truncate subLabel', async () => { + const div = await createFixture('is_truncated_subLabel'); + const el = div.querySelector('ef-item'); + expect(el.isItemOverflown(), 'Should truncate text').to.equal(true); }); - it('Should not truncate text', async () => { + it('Should not truncate item text', async () => { const el = await createFixture(); - - await elementUpdated(el); - await nextFrame(); - - expect(el.isTruncated, 'Should not truncate text').to.equal(false); + expect(el.isItemOverflown(), 'Should not truncate text').to.equal(false); }); }); diff --git a/packages/elements/src/item/index.ts b/packages/elements/src/item/index.ts index faa7de59ec..de340ed1a5 100644 --- a/packages/elements/src/item/index.ts +++ b/packages/elements/src/item/index.ts @@ -8,11 +8,12 @@ import { } from '@refinitiv-ui/core'; import { customElement } from '@refinitiv-ui/core/decorators/custom-element.js'; import { property } from '@refinitiv-ui/core/decorators/property.js'; -import { query } from '@refinitiv-ui/core/decorators/query.js'; import { VERSION } from '../version.js'; import '../icon/index.js'; import '../checkbox/index.js'; - +import { registerOverflowTooltip } from '../tooltip/index.js'; +import { isElementOverflown } from '@refinitiv-ui/utils/element.js'; +import { createRef, ref, Ref } from '@refinitiv-ui/core/directives/ref.js'; import type { ItemType, ItemText, ItemHeader, ItemDivider, ItemData } from './helpers/types'; export type { ItemType, ItemText, ItemHeader, ItemDivider, ItemData }; @@ -126,10 +127,19 @@ export class Item extends ControlElement { public for: string | null = null; /** - * Cache label element + * Reference to the label element + */ + private labelRef: Ref = createRef(); + + /** + * Reference to the subLabel element + */ + private subLabelRef: Ref = createRef(); + + /** + * Reference to the slot element */ - @query('#label') - private labelEl?: HTMLElement; + private slotRef: Ref = createRef(); /** * True, if there is no slotted content @@ -187,6 +197,16 @@ export class Item extends ControlElement { } } + /** + * Called after the component is first rendered + * @param changedProperties Properties which have changed + * @returns {void} + */ + protected firstUpdated (changedProperties: PropertyValues): void { + super.firstUpdated(changedProperties); + registerOverflowTooltip(this, () => this.getItemContent(), () => this.isItemOverflown()); + } + /** * Invoked before update() to compute values needed during the update. * @param changedProperties changed properties @@ -204,6 +224,45 @@ export class Item extends ControlElement { } } + + /** + * Get Item content + * @returns return item content from slot or label and sub-label + */ + private getItemContent (): string { + + if (this.isSlotEmpty) { + let text = ''; + if (this.label) { + text += this.label; + } + if (this.subLabel) { + text += text ? ` (${this.subLabel})` : this.subLabel; + } + return text; + } + else { + return this.slotContent; + } + } + + /** + * Get element overflown + * @param element Target element + * @returns return true if element is overflown. + */ + private isItemElementOverflown (element?: HTMLElement): boolean { + return element ? isElementOverflown(element) : false; + } + + /** + * Get item overflown + * @returns return true if an item is overflown. + */ + private isItemOverflown (): boolean { + return this.isItemElementOverflown(this.labelRef.value) || this.isItemElementOverflown(this.subLabelRef.value); + } + /** * Get icon template if icon attribute is defined */ @@ -215,14 +274,22 @@ export class Item extends ControlElement { * Get subLabel template if it is defined and no slot content present */ private get subLabelTemplate (): TemplateResult | undefined { - return this.subLabel && this.isSlotEmpty ? html`
${this.subLabel}
` : undefined; + return html`
${this.subLabel}
`; } /** * Get label template if it is defined and no slot content present */ private get labelTemplate (): TemplateResult | undefined { - return this.label && this.isSlotEmpty ? html`${this.label}` : undefined; + return html`${this.label}`; + } + + /** + * Get slot content + */ + private get slotContent (): string { + const nodes = this.slotRef.value?.assignedNodes() || []; + return nodes.map(node => node.textContent).join(' ').trim(); } /** @@ -251,15 +318,6 @@ export class Item extends ControlElement { return !this.disabled && this.type !== 'header' && this.type !== 'divider'; } - /** - * Getter returning if the label is truncated - * @prop {boolean} isTruncated - * @returns whether element is truncated or not - */ - public get isTruncated (): boolean { - return !!(this.labelEl && (this.labelEl.offsetWidth < this.labelEl.scrollWidth)); - } - /** * A `TemplateResult` that will be used * to render the updated internal template. @@ -272,10 +330,10 @@ export class Item extends ControlElement { ${this.multipleTemplate}
-
- ${this.labelTemplate} - - ${this.subLabelTemplate} +
+ ${this.label && this.isSlotEmpty ? this.labelTemplate : undefined} + + ${this.subLabel && this.isSlotEmpty ? this.subLabelTemplate : undefined}