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 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 extends BoltDragEvent { } export interface IListDragDropSecondaryData { readonly index: number; readonly sourceId: string; } export interface IListMaterializedStats { firstMaterialized: number; firstRendered: number; lastMaterialized: number; lastRendered: number; } /** * IList 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 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; /** * 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 { /** * 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; /** * ariaLabelledBy property enables authors to reference other elements on the page to define an accessible name to assistive * technology. */ ariaLabelledBy?: 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; /** * 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) => 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 { /** * 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 for those that just have a set of items * they want to supply without writing a custom ItemProvider. */ itemProvider: IItemProvider>; /** * 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>>[]; /** * 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, listRow: IListRow) => 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, listRow: IListRow) => 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, listRow: IListRow) => 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) => 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 * 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 * '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) => 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 * 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 * '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 extends IListProps { /** * onScroll is called when the list is scrolled. This event is not throttled. * * @param event Scroll event details */ onScroll?: (event: React.SyntheticEvent) => 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, Exclude, "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 { /** * 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; /** * 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; }