Skip to content

Commit

Permalink
feat(ui5-grid): implementing new grid component (#8362)
Browse files Browse the repository at this point in the history
- introduces a new grid/table component
- enhanced accessibility: keyboard navigation, roles in the light DOM
- new API mirroring the structure of the HTML table
- sticky handling: sticky header row, more control (offset), focus rows below header
- grid navigation: navigation similar to the UI5 tables
- no data: use a text or any other HTML element to represent no data state
- improved pop-in behavior: if the table overflows, columns will move into the pop-in area based on importance and space
- features enhancing the grid: Selection and Growing
  • Loading branch information
DonkeyCo committed Jun 11, 2024
1 parent f710eff commit 04d291d
Show file tree
Hide file tree
Showing 98 changed files with 4,865 additions and 20 deletions.
5 changes: 3 additions & 2 deletions packages/base/src/UI5Element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -864,8 +864,9 @@ abstract class UI5Element extends HTMLElement {
await this._waitForDomRef();

const focusDomRef = this.getFocusDomRef();

if (focusDomRef && typeof focusDomRef.focus === "function") {
if (focusDomRef === this) {
HTMLElement.prototype.focus.call(this, focusOptions);
} else if (focusDomRef && typeof focusDomRef.focus === "function") {
focusDomRef.focus(focusOptions);
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/base/src/features/F6Navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ class F6Navigation {
}

deepActive(root: DocumentOrShadowRoot): Element | null {
if (root.activeElement && root.activeElement.shadowRoot) {
if (root?.activeElement?.shadowRoot?.activeElement) {
return this.deepActive(root.activeElement.shadowRoot);
}

Expand Down
15 changes: 3 additions & 12 deletions packages/base/src/util/TabbableElements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,29 +35,20 @@ const getTabbables = (nodes: Array<Node>, tabbables?: Array<HTMLElement>): Array
return;
}

let currentElement = currentNode as HTMLElement;
const currentElement = currentNode as HTMLElement;
if (currentElement.hasAttribute("data-sap-no-tab-ref")) {
return;
}

if (currentElement.shadowRoot) {
// get the root node of the ShadowDom (1st none style tag)
const children = currentElement.shadowRoot.children;
currentElement = Array.from(children).find(node => node.tagName !== "STYLE") as HTMLElement;
}

if (!currentElement) {
return;
}

if (isElementTabbable(currentElement)) {
tabbableElements.push(currentElement);
}

if (currentElement.tagName === "SLOT") {
getTabbables((currentElement as HTMLSlotElement).assignedNodes() as Array<HTMLElement>, tabbableElements);
} else {
getTabbables([...currentElement.children], tabbableElements);
const children = currentElement.shadowRoot ? currentElement.shadowRoot.children : currentElement.children;
getTabbables([...children], tabbableElements);
}
});

Expand Down
5 changes: 2 additions & 3 deletions packages/base/src/util/isElementTabbable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ const isElementTabbable = (el: HTMLElement): boolean => {
return false;
}

const nodeName = el.nodeName.toLowerCase();

if (el.hasAttribute("data-sap-no-tab-ref")) {
return false;
}
Expand All @@ -27,7 +25,8 @@ const isElementTabbable = (el: HTMLElement): boolean => {
return parseInt(tabIndex) >= 0;
}

if (nodeName === "a" || /input|select|textarea|button|object/.test(nodeName)) {
const nodeName = el.nodeName.toLowerCase();
if (nodeName === "a" || /^(input|select|textarea|button|object)$/.test(nodeName)) {
return !(el as HTMLLinkElement).disabled;
}

Expand Down
2 changes: 1 addition & 1 deletion packages/compat/test/pages/Table.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
<ui5-label>Product</ui5-label>
</ui5-table-column>

<ui5-table-column id="column-2" slot="columns" min-width="800" popin-text="Supplier">
<ui5-table-column id="column-2" min-width="800" slot="columns" popin-text="Supplier">
<ui5-label>Supplier</ui5-label>
</ui5-table-column>

Expand Down
59 changes: 59 additions & 0 deletions packages/main/src/Grid.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<div id="before" role="none" tabindex="0" ui5-grid-dummy-focus-area></div>

{{#if loading}}
<!-- TODO: Change to loading indicator -->
<ui5-busy-indicator
id="busy-indicator"
delay="50"
class="ui5-table-busy-ind"
active
data-sap-focus-ref
></ui5-busy-indicator>
{{/if}}

<div id="grid" role="grid"
style="{{styles.grid}}"
aria-label="{{_ariaLabel}}"
aria-multiselectable="{{_ariaMultiSelectable}}"
>
<slot name="headerRow"></slot>
<slot></slot>
{{#unless rows.length}}
<ui5-grid-row id="nodata-row">
<ui5-grid-cell id="nodata-cell" excluded-from-navigation>
{{#if nodata.length}}
<slot name="nodata"></slot>
{{else}}
{{_effectiveNoDataText}}
{{/if}}
</ui5-grid-cell>
</ui5-grid-row>
{{/unless}}

{{#if _shouldRenderGrowing}}
<div id="footer" role="rowgroup">
{{> growingRow}}
</div>
{{/if}}

{{> tableEndRow}}
</div>
<div id="after" role="none" tabindex="0" ui5-grid-dummy-focus-area></div>

{{#*inline "growingRow"}}
<ui5-grid-row id="growing-row">
<ui5-grid-cell id="growing-cell">
<!-- The growing button is a div filling the cell -->
<!-- It has a growing text at the top and a growingSubText at the bottom -->
<slot name="{{_growing._individualSlot}}"></slot>
</ui5-grid-cell>
</ui5-grid-row>
{{/inline}}

{{#*inline "tableEndRow"}}
<div aria-hidden="true" id="table-end-row">
<div id="table-end-cell">
<div id="table-end" aria-hidden="true" tabindex="-1"></div>
</div>
</div>
{{/inline}}
Loading

0 comments on commit 04d291d

Please sign in to comment.