azure-devops-ui
Version:
React components for building web UI in Azure DevOps
627 lines (626 loc) • 26.7 kB
TypeScript
import * as React from "react";
import { IReadonlyObservableValue } from '../../Core/Observable';
import { IFocusZoneProps } from '../../FocusZone';
import { IIconProps } from '../../Icon';
import { ILinkProps } from '../../Link';
import { IBehavior } from '../../Utilities/Behavior';
import { IEventDispatch } from '../../Utilities/Dispatch';
import { BoltDragEvent } from '../../Utilities/DragDrop';
import { IItemProvider } from '../../Utilities/Provider';
import { IIndexed, ISelectableUI, ISelection, ISelectionRange, ISelectOptions } from '../../Utilities/Selection';
/**
* The IListCellDetails is used to describe the elements and indexes for a given cell.
* The header row uses rowIndex = -1.
*/
export interface IListCellDetails {
cellElement: HTMLElement | null;
cellIndex: number;
rowElement: HTMLElement | null;
rowIndex: number;
}
/**
* An IListRow is used to communicate details about a given row in a list.
*/
export interface IListRow<T> extends IIndexed {
/**
* Data that represents the instance of T for this row. In some cases the
* row data may not be available.
*/
data: T;
}
export interface IFilteredListSelection extends IListSelection {
/**
* Let the filtered selection know that its parent selection has changed.
* @param value the selection ranges returned from the change on the parent selection.
* @param action the action that occured on the parent selection.
*/
selectionChanged: (value: ISelectionRange[], action: string) => void;
/**
* Using the parent selection and a map of filtered indices to parent selection indices,
* update the state of the filteredSelection.
* @param filteredIndexMap a mapping of filtered indices to parent selection indices
*/
updateFilteredSelection: (filteredIndexMap: number[]) => void;
}
/**
* The List uses an IListSelection to manage the selection state for the
* rows within the list. This is required for a multi-select list and optional
* for single select lists. For single select the caller can just use the
* onSelect property to detect selection changes.
*/
export interface IListSelection extends ISelection, IBehavior<{}, ISelectableUI> {
/**
* Whether or not this selection will select items when they receive focus.
*/
selectOnFocus: boolean;
}
/**
* Options to describe the list selection behavior.
*/
export interface IListSelectionOptions extends ISelectOptions {
/**
* Set to true to select items when they receive focus.
* @default true
*/
selectOnFocus?: boolean;
}
export interface IOverlayRenderProps {
rowElement: HTMLElement;
}
export interface IRowOverlayableUI {
/**
* addOverlay is used to create, or update an existing, overlay over an existing list row.
* This allows the caller to apply effects to the row by having elements
* that appear over top of the row.
*
* @param id This is a unique id for this overlay.
* @param rowIndex The index of the row the overlay should hover over.
* @param render A function which returns the React nodes that should be placed within the overlay.
* The parent element will be positioned, and have the same height and width
* as the underlying row element.
* @param zIndex If there are multiple overlays the caller can use the zIndex
* parameter to ensure they are layered correctly.
* @param columnIndex The index of the column the overlay should hover over (used for column header drag/drop)
*/
addOverlay(id: string, rowIndex: number, render: (props: IOverlayRenderProps) => React.ReactNode, zIndex?: number, columnIndex?: number): void;
/**
* removeOverlay is used to remove an overlay that has already been registered.
* The unique id is all that is needed.
* @param id This is a unique id for this overlay.
*/
removeOverlay(id: string): void;
}
export interface IDragDroppableUI extends ISelectableUI, IRowOverlayableUI {
}
export interface BoltListDragEvent<T, D> extends BoltDragEvent<T, D, IListDragDropSecondaryData> {
}
export interface IListDragDropSecondaryData {
readonly index: number;
readonly sourceId: string;
}
export interface IListMaterializedStats {
firstMaterialized: number;
firstRendered: number;
lastMaterialized: number;
lastRendered: number;
}
/**
* IList<T> is the interace the List component exposes for use by callers.
* If they ref the component they should access the List with this interface.
*/
export interface IList<T> extends ISelectableUI, IRowOverlayableUI {
/**
* Sets the focus to the given row after scrolling to it. If it's not focusable, it will use `direction` to find the closest one.
* @param rowIndex The index of the row to focus.
* @param direction Use 1 or -1 to try previous or next rows when given row is not focusable.
*/
focusRow(rowIndex: number, direction?: number): Promise<void>;
/**
* scrollIntoView works like the element method in the browser, but instead of being
* based on the element scrollIntoView will scroll the row specified by the rowIndex
* into view.
*
* @param rowIndex The 0 based rowIndex that should be scrolled into view.
* @param options These options are passed on to the underlying element.
* @param onScrollComplete This delegate is called when the scrolling is complete.
* This may not be immediate if the list has to materialize rows to scroll.
* NOTE: The rowIndex passed to the delegate will be the requested rowIndex or
* -1 if another scrollIntoView request is made before this scroll request completes.
*/
scrollIntoView: (rowIndex: number, options?: ScrollIntoViewOptions, onScrollComplete?: (rowIndex: number) => void) => void;
/**
* Returns an object with the indexes of the first and last materialized and rendered rows.
*
*/
getStats(): IListMaterializedStats;
}
/**
* An interface to group properties that get passed to the row renderer of each list row.
*/
export interface IListItemDetails<T> {
/**
* Sets aria-busy on the list item element.
*/
ariaBusy?: boolean;
/**
* Sets aria-current value if the row matches the requirements of aria-current
* More information: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-current
*/
ariaCurrent?: "page" | "step" | "location" | "date" | "time" | "true" | "false";
/**
* Sets aria-describedby on the list item element.
*/
ariaDescribedBy?: string;
/**
* ariaLabel allows the caller to describe the elements contents to assistive
* technology.
*/
ariaLabel?: string;
/**
* Sets aria-posinset on the tr element. Defaults to the item index.
*/
ariaPosInSet?: number | null;
/**
* Amount to offset the aria-rowindex attribute of a row. This should be added
* to the index to produce the aria-rowindex of the row. The common use case for this property
* is accounting for the header row of a Table. Per the ARIA spec, a header row should be included in
* the aria-rowcount, and provided an aria-rowindex of 1, meaning the first actual row of the table
* needs an aria-rowindex of 2.
*
* @default 1 The default value is 1 because the aria-rowindex is 1-based instead of 0-based
*/
ariaRowOffset: number;
/**
* Sets aria-setsize on the tr element. Defaults to the itemProvider length.
*/
ariaSetSize?: number | null;
/**
* The data represents the object being rendered in this row. If the caller
* has asynchronous loading of rows, the data MAY be undefined while we wait
* for the data to be resolved.
*/
data?: T;
/**
* An event dispatch the row MAY use to dispatch custom events to list behaviors.
*/
eventDispatch: IEventDispatch;
/**
* Set to true to exclude the list item from the focus zone.
*/
excludeFocusZone?: boolean;
/**
* If false, the row will get tabIndex=0, otherwise tabIndex=-1.
* @default true
*/
excludeTabStop?: boolean;
/**
* Properties used to render the list as a whole.
*/
listProps: IListProps<T>;
/**
* The row MUST call onFocusItem when a row or one of the child elements of the row receives focus.
*/
onFocusItem: (rowIndex: number, event: React.FocusEvent<HTMLElement>) => void;
/**
* Set to true if text in the list row should be selectable.
*/
selectableText?: boolean;
/**
* If true, will make cursor display as a pointer when hovering over the item otherwise default.
*/
singleClickActivation?: boolean;
}
/**
* IListProps are used to describe the set of features used by the List component
* and any attached behaviors.
*
* The list provides NO scrolling or virutalization behaviors by default. It requires
* the user to define the behavior through an Intersection component. This is used to
* monitor the visiblity with the intersections viewport.
*
* If the user just wants a List where its contents scroll, make sure you use a
* ScrollableList or SimpleList with scrollable set to true, instead of a List. If
* the user wants to embed the list in scrollable element with other elements the
* scrolling element should have an Intersection defined around it.
*/
export interface IListProps<T> {
/**
* The caller MUST supply the set of items to be shown through the ItemProvider.
* The IItemProvider allows the caller to store their items in the form that
* bests suits their needs but gives the list a well defined interface for
* requesting the items. This can include async fetching of items through
* observables.
*
* There is simple ArrayItemProvider<T> for those that just have a set of items
* they want to supply without writing a custom ItemProvider.
*/
itemProvider: IItemProvider<T | IReadonlyObservableValue<T | undefined>>;
/**
* Amount to offset the aria-rowcount attribute of a list. This should be added
* to the number of items in the list to produce the aria-rowcount. The common use case for this property
* is accounting for the header row of a Table. Per the ARIA spec, a header row should be included in
* the aria-rowcount.
*
* @default 0
*/
ariaRowOffset?: number;
/**
* The number of columns visible to the screen reader.
*
* By default this is the value of columnCount
*/
ariaColumnCount?: number;
/**
* Aria label property for the list.
*/
ariaLabel?: string;
/**
* Behaviors can be added to the list and monitor events and interact with the
* component through the IList methods.
*/
behaviors?: IBehavior<Partial<IListProps<T>>, Partial<IList<T>>>[];
/**
* CSS className to add to the list root element.
*/
className?: string;
/**
* Number of columns the list is going to represent. This is important to get
* correct if the contents are going to show as multi-column since the
* virtualization is emitting elements (non-visible) into the list for paging.
*
* By default the list is a single column value.
*/
columnCount?: number;
/**
* The index of the row that should be tabbable before the list has received focus.
* @default 0
*/
defaultTabbableRow?: number;
/**
* A custom way to force single-select options in a multi-select selection.
*/
enforceSingleSelect?: boolean;
/**
* The caller can supply an EventDispatch to the list if it wishes to
* participate it extending the behaviors. If one isn't supplied the list will
* create its own dispatcher when behaviors are supplied.
*/
eventDispatch?: IEventDispatch;
/**
* The element should not be tabable but still should be able to receieve focus.
*/
excludeTabStop?: boolean;
/**
* focuszoneProps allows the caller to manage the how the list rows are focused.
* The default focuszone if one isn't supplied is a Vertical non-cyclic focus zone.
*/
focuszoneProps?: IFocusZoneProps | null;
/**
* Unique Id for this list.
*/
id?: string;
/**
* initialPageCount is used when the list is drawing pages and no pages are
* visible. This primarily happens when the list is initially rendered.
*
* The impact of setting this value effects performance of the initial render.
* Since the component performs 0 measurements it renders the initialPages and
* uses the IntersectionObserver to determine whether or not it has filled the
* components viewport. This means that in the first pass it will render the
* requested rows and then update the rows after the observer reports on the
* visibility of the rendered pages. If 3 was not enough to fill the UI it will
* render more, but this will happen async.
*
* The default is 3 pages with a default of 10 rows per page - 30 rows..
*/
initialPageCount?: number;
/**
* The maximum height of the table when virtualized. Browsers have issues
* rendering elements that are too large and when the List contains thousands
* of elements, the list renders very large spacer elements to correctly
* position the scroll bar. The large spacer elements cause rendering issues
* across browsers. To bypass this, we need to limit how large the list can
* grow to. By default this size is 100,000px. However, if you have multiple
* items within a scrollable region, this number might need to be reduced.
* For instance, if you have 5 lists that can contain a lot of rows in the
* same scrollable region, you would likely want to set the max height for
* each list to 20,000. Keep in mind that the smaller this number, the harder
* it will be for a user to scroll with precision.
*
* @default 100000
*/
maxHeight?: number;
/**
* maxWidth is used to control the maximum width of the list.
*/
maxWidth?: string;
/**
* minWidth is used to control the minumum width of the list.
*/
minWidth?: string;
/**
* onActivate is called when the row is activated. Activation occurs on
* the Enter keystroke or double click.
*
* @param event This is the event that is causing the activation.
* @param listRow Details about the list row being activated.
*/
onActivate?: (event: React.SyntheticEvent<HTMLElement>, listRow: IListRow<T>) => void;
/**
* onFocus is called when a item in the list is focused. Preventing default
* on the focus event will prevent row selection from occuring even if
* selectOnFocus is set to true.
*
* @param event This is the event that is causing the activation.
* @param listRow Details about the list row being activated.
*/
onFocus?: (event: React.SyntheticEvent<HTMLElement>, listRow: IListRow<T>) => void;
/**
* onSelect is called when the row is selected. Selection occurs on the
* Space keystroke or click.
*
* @param event - This is the event that is causing the selection.
* @param listRow - Details about the list row being selected.
*/
onSelect?: (event: React.SyntheticEvent<HTMLElement>, listRow: IListRow<T>) => void;
/**
* pageSize controls the granularity of row rendering. The list always renders
* a full page worth of rows even when they are not needed to fill the viewport.
*
* Smaller values will help reduce the number of wasted rows that are rendered
* outside the viewport, but will force the list to re-render more often as
* scrolling occurs.
*/
pageSize?: number;
/**
* When a row's value is given as an ObservableValue with an undefined value,
* the list will render a Loading row for the content. The default will be
* a shimmer row that is semi random and matches the content.
*
* @param index This is the 0 based row index that should be rendered.
* @param details Additional details about this row.
*/
renderLoadingRow?: (rowIndex: number, details: IListItemDetails<T>) => JSX.Element;
/**
* All callers must supply a function for rendering the list row.
*
* The requirements of this function are quite complicated and before writing
* a new row renderer you should ensure one doesnt already exist that solves
* your needs.
*
* Requirements:
*
* 1) The row function MUST return a singlely rooted component that resolves
* to single rooted element, or return a single element that is either a <tr>
* or another acceptable tag that is marked as a display: table-row.
*
* 2) The row function MUST only include direct child elements that are either
* <td>'s or acceptable elements that are maked as display: table-cell. The
* renderer MUST return one element for each defined column even if there is
* no data to be rendered in the column.
*
* 3) The row function MUST call onFocusItem when either the row element or
* any of the rows child elements receive the focus. This is needed to ensure
* navigation within the list as well as in and out of the list function
* correctly.
*
* Exception: If your list doesn't support row level focus/selection.
*
* 4) The row function MUST add data-row-index on the row element. This is
* needed to ensure mouse events can be translated to rows.
*
* 5) The row function MAY dispatch events for behaviors but is not required.
* If any events are dispatched they should be documented on the row renderer.
*
* 6) The row function is responsible for all accessibility and focus
* management within the row.
*
* Exception: If your list doesn't support row level focus/selection.
*
* @param index This is the 0 based row index that should be rendered.
* @param item This is the object that represents the current rows data.
* @param details Additional details to handle aria attributes and focus.
*/
renderRow: (rowIndex: number, item: T, details: IListItemDetails<T>) => JSX.Element | null;
/**
* Lists MAY render a header above the list contents. This is done through the
* onRenderHeader. Like the renderRow method, there are set of restrictions the
* implmentation MUST follow.
*
* Requirements:
*
* 1) The header function MUST return a singlely rooted component that resolves
* to single rooted element, or return a single element that is either a <tr>
* or another acceptable tag that is marked as a display: table-row.
*
* 2) The header function MUST only include direct child elements that are either
* <td>'s or acceptable elements that are maked as display: table-cell. The
* renderer MUST return one element for each defined column even if there is
* no data to be rendered in the column.
*/
renderHeader?: () => JSX.Element;
/**
* role defines the aria role of the list and defaults to "listbox"
*/
role?: string;
/**
* If the caller has variable height rows they can specify the rowHeight they
* want used to estimate the size of virtualized rows. This means that when rows
* are not rendered, the component will create virtual space for those rows to
* ensure the scrolling behavior acts appropriately.
*
* If the list has fixed size rows there is no need to specify a rowHeight. The
* list will determine the height of the rows after the initial render when
* the observer reports on page visibility.
*
* Question: How do I determine the rowHeight if their are variable height rows.
* This one is a tough question, and the general answer is come up with a fair
* average for the rows on a given page. If the select too large or too small
* scrolling behaviors can become a bit odd, generally select on the smaller
* side if you are unsure.
*/
rowHeight?: number;
/**
* An array of heights to be used when calculating the spacer heights. If not supplied, the heights used
* will be estimations.
*/
rowHeights?: number[];
/**
* Set to true to allow text selection on all list rows.
*/
selectableText?: boolean;
/**
* A selection object can be supplied for managing the list selection. This
* is not required since the list offers onSelect as a delegate. If the caller
* wants multi-selction they must use an IListSelection that supports multi
* select. Selection should be specified on mount if used and not updated to
* a new object during the List's lifecycle.
*
* There is a basic ListSelection implementation available from the List
* component.
*/
selection?: IListSelection;
/**
* Should the list select a row when it is clicked?
* Provides a way to turn off row-click selection when necessary, for things like
* Table with singleClickActivation
*
* @default true
*/
selectRowOnClick?: boolean;
/**
* Using singleClickActivation will activate the item when the row is clicked.
* Where setting singleClickActivation to false will require a doubleclick to
* activate a given row.
*
* @default false
*/
singleClickActivation?: boolean;
/**
* Whether we should allow scrolling on this list
* If false, we will add scroll-hidden css class to the list
*
* @default false
*/
showScroll?: boolean;
/**
* If virtualize false is supplied the list will render all the items supplied
* to it. This shouldn't be used unless you know you have a limited number of
* rows. Virtualization is used to avoid performance problems.
*/
virtualize?: boolean;
/**
* width can be any supported css width value, either a %, px, and vw value.
*/
width?: string;
/**
* This determines which element will be used for the overlay instead of the default element.
* The format expected here is the CSS selector.
*/
overlay?: string;
}
/**
* ISimpleListCell instances are used with the SimpleList to render basic cells
* in a list or table. It supports an optional icon, text and the text can be made
* into a hyper link.
*/
export interface ISimpleListCell {
/**
* iconProps for the icon within the cell.
*/
iconProps?: IIconProps;
/**
* text that should within the cell.
*/
text?: string;
/**
* Optional node that will render instead of the text string.
* Separate to preserve needed access to the text like with sorting.
*/
textNode?: React.ReactNode;
/**
* Optionally className that should be applied to the text.
*/
textClassName?: string;
/**
* A link that should be applied to the text of the cell.
*/
href?: string;
/**
* Optional. Where to display the linked URL, as the name for a browsing context. Used only if href is defined.
* See https://developer.mozilla.org/docs/Web/HTML/Element/a for more information.
*/
hrefTarget?: string;
/**
* Optional. The relationship of the linked URL as space-separated link types. Used only if href is defined.
* See https://developer.mozilla.org/docs/Web/HTML/Element/a for more information.
*/
hrefRel?: string;
}
/**
* Scrollable lists use a container to house the list. This container can be
* hooked up to a scroll event handler.
*/
export interface IScrollableListProps<T> extends IListProps<T> {
/**
* onScroll is called when the list is scrolled. This event is not throttled.
*
* @param event Scroll event details
*/
onScroll?: (event: React.SyntheticEvent<HTMLElement>) => void;
/**
* Scrollable List has an outer div. Use this to add classes to it. Use className to
* add classes to the inner list element.
*/
outerClassName?: string;
}
/**
* ISimpleListProps are used to create a SimpleList. The SimpleList renders its
* rows using either an ISimpleCell, string, or number.
*/
export interface ISimpleListProps extends Pick<IListProps<ISimpleListCell | string | number>, Exclude<keyof IListProps<ISimpleListCell | string | number>, "renderRow">> {
/**
* scrollable should be set to true if the list is not contained in a
* scrolling element. This will ensure the list scrolls vertically within
* the list element itself.
*/
scrollable?: true;
}
/**
* If the caller wants to control the row they can render the entire row on their
* own, or they can use a ListItem to get the majority of the row handled.
*
* Using the ListItem instead of renderListItem gives the caller access to more
* detailed control over the list item. An example is the ability to render the
* list item as a link (a tag) instead of a table row.
*/
export interface IListItemProps<T> {
/**
* css class that will be added to the list item.
*/
className?: string;
/**
* Details about the list item being rendered. This is given to the caller
* when a row needs to be rendered.
*/
details: IListItemDetails<T>;
/**
* Index of the row that is being rendered.
*/
index: number;
/**
* If the list item should be rendered as a link, the caller can supply the
* links properties. These are merged with the lists properties to build
* a list row that is a anchor instead of a table row.
*/
/** @deprecated Please add links within the cell. Rows rendered as links is an inaccessible pattern and support will be removed in future versions. */
linkProps?: ILinkProps;
/**
* Id of represented work item
*/
itemId?: number;
/**
* Desired tab index
*/
tabIndex?: number;
}