Skip to content

Commit

Permalink
chore(tree-item, tree-view): decouples foundation (VIV-2014) (#2049)
Browse files Browse the repository at this point in the history
* import foundation code

* import foundation code

* import foundation code

* formatting

* chore: fixes for tree-item and view

* chore: refactor

* chore: uopdates tests

* chore: updates tests

* chore: formatting

* chore: refactor

* chore: updates tests

---------

Co-authored-by: TaylorJ76 <[email protected]>
Co-authored-by: James Taylor <[email protected]>
  • Loading branch information
3 people authored Dec 11, 2024
1 parent b133c15 commit 62cf94d
Show file tree
Hide file tree
Showing 4 changed files with 798 additions and 9 deletions.
19 changes: 17 additions & 2 deletions libs/components/src/lib/tree-item/tree-item.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ describe('vwc-tree-item', () => {
expect(treeItem1).toBeInstanceOf(TreeItem);
expect(treeItem1.text).toBeUndefined();
expect(treeItem1.icon).toBeUndefined();
expect(treeItem1.selected).toBeUndefined();
expect(treeItem1.selected).toBeFalsy();
expect(treeItem1.expanded).toEqual(false);
expect(treeItem1.disabled).toBeUndefined();
expect(treeItem1.disabled).toBeFalsy();
});
});

Expand Down Expand Up @@ -79,6 +79,11 @@ describe('vwc-tree-item', () => {
});
});

it('should include a role of `treeitem', async () => {
await elementUpdated(treeItem1);
expect(treeItem1.getAttribute('role')).toEqual('treeitem');
});

it('should set the `aria-selected` attribute with the `selected` value when provided', async () => {
treeItem1.selected = true;
await elementUpdated(treeItem1);
Expand Down Expand Up @@ -144,6 +149,16 @@ describe('vwc-tree-item', () => {
});
});

describe('focus-item', () => {
it('should focus on the element', async () => {
expect(treeItem1.contains(document.activeElement)).toBeFalsy();
TreeItem.focusItem(treeItem1);
await elementUpdated(treeItem1);

expect(treeItem1.contains(document.activeElement)).toBeTruthy();
});
});

describe('a11y', () => {
it('should pass html a11y test', async () => {
treeItem1.text = 'Tree item 1';
Expand Down
181 changes: 178 additions & 3 deletions libs/components/src/lib/tree-item/tree-item.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
import { TreeItem as FastTreeItem } from '@microsoft/fast-foundation';
import { attr } from '@microsoft/fast-element';
import { FoundationElement } from '@microsoft/fast-foundation';
import { isHTMLElement } from '@microsoft/fast-web-utilities';
import { attr, observable } from '@microsoft/fast-element';
import { applyMixins } from '../../shared/foundation/utilities/apply-mixins';
import { AffixIcon } from '../../shared/patterns/affix';

/**
* check if the item is a tree item
* @public
* @remarks
* determines if element is an HTMLElement and if it has the role treeitem
*/
export function isTreeItemElement(el: Element): el is HTMLElement {
return (
isHTMLElement(el) && (el.getAttribute('role') as string) === 'treeitem'
);
}

/**
* @public
* @component tree-item
Expand All @@ -11,7 +24,7 @@ import { AffixIcon } from '../../shared/patterns/affix';
* @event {CustomEvent<HTMLElement>} expanded-change - Fires a custom 'expanded-change' event when the expanded state changes
* @event {CustomEvent<HTMLElement>} selected-change - Fires a custom 'selected-change' event when the selected state changes
*/
export class TreeItem extends FastTreeItem {
export class TreeItem extends FoundationElement {
/**
* Indicates the text's text.
*
Expand All @@ -20,6 +33,168 @@ export class TreeItem extends FastTreeItem {
* HTML Attribute: text
*/
@attr text?: string;

/**
* When true, the control will be appear expanded by user interaction.
* @public
* @remarks
* HTML Attribute: expanded
*/
@attr({ mode: 'boolean' }) expanded = false;
expandedChanged(): void {
if (this.$fastController.isConnected) {
this.$emit('expanded-change', this);
}
}

/**
* When true, the control will appear selected by user interaction.
* @public
* @remarks
* HTML Attribute: selected
*/
@attr({
mode: 'boolean',
})
selected = false;
selectedChanged(): void {
if (this.$fastController.isConnected) {
this.$emit('selected-change', this);
}
}

/**
* When true, the control will be immutable by user interaction. See {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/disabled | disabled HTML attribute} for more information.
* @public
* @remarks
* HTML Attribute: disabled
*/
@attr({ mode: 'boolean' })
disabled = false;

/**
* Reference to the expand/collapse button
*
* @internal
*/
// @ts-expect-error Type is incorrectly non-optional
expandCollapseButton: HTMLDivElement;

/**
* Whether the item is focusable
*
* @internal
*/
@observable
focusable = false;

/**
*
*
* @internal
*/
@observable
// @ts-expect-error Type is incorrectly non-optional
childItems: HTMLElement[];

/**
* The slotted child tree items
*
* @internal
*/
@observable
items!: HTMLElement[];
itemsChanged(): void {
if (this.$fastController.isConnected) {
this.items.forEach((node: HTMLElement) => {
if (isTreeItemElement(node)) {
// TODO: maybe not require it to be a TreeItem?
(node as TreeItem).nested = true;
}
});
}
}

/**
* Indicates if the tree item is nested
*
* @internal
*/
@observable
// @ts-expect-error Type is incorrectly non-optional
nested: boolean;

/**
*
*
* @internal
*/
@observable
// @ts-expect-error Type is incorrectly non-optional
renderCollapsedChildren: boolean;

/**
* Places document focus on a tree item
*
* @public
* @param el - the element to focus
*/
static focusItem(el: HTMLElement) {
(el as TreeItem).focusable = true;
el.focus();
}

/**
* Whether the tree is nested
*
* @public
*/
readonly isNestedItem = (): boolean => {
return isTreeItemElement(this.parentElement as Element);
};

/**
* Handle expand button click
*
* @internal
*/
handleExpandCollapseButtonClick = (e: MouseEvent): void => {
if (!this.disabled && !e.defaultPrevented) {
this.expanded = !this.expanded;
}
};

/**
* Handle focus events
*
* @internal
*/
handleFocus = (_e: FocusEvent): void => {
this.setAttribute('tabindex', '0');
};

/**
* Handle blur events
*
* @internal
*/
handleBlur = (_e: FocusEvent): void => {
this.setAttribute('tabindex', '-1');
};

/**
* Gets number of children
*
* @internal
*/
childItemLength(): number {
const treeChildren: HTMLElement[] = this.childItems.filter(
(item: HTMLElement) => {
return isTreeItemElement(item);
}
);
return treeChildren.length;
}
}

export interface TreeItem extends AffixIcon {}
Expand Down
Loading

0 comments on commit 62cf94d

Please sign in to comment.