@dvcol/neo-svelte
Version:
Neomorphic ui library for svelte 5
432 lines (431 loc) • 15 kB
TypeScript
import type { Snippet } from 'svelte';
import type { NeoButtonProps } from '../buttons/neo-button.model.js';
import type { NeoDividerProps } from '../divider/neo-divider.model.js';
import type { NeoListBaseItemProps } from './neo-list-base-item.model.js';
import type { NeoListBaseLoaderProps } from './neo-list-base-loader.model.js';
import type { NeoListBaseSectionProps } from './neo-list-base-section.model.js';
import type { NeoImageProps } from '../media/neo-image.model.js';
import type { NeoMediaProps, NeoMediaType, NeoMediaTypes } from '../media/neo-media.model.js';
import type { NeoPillProps } from '../pill/neo-pill.model.js';
import type { HTMAnimationProps, HTMLTransitionProps } from '../utils/action.utils.js';
import type { BorderRadiusInput } from '../utils/border.utils.js';
import type { Color } from '../utils/colors.utils.js';
import type { HTMLNeoBaseElement, HTMLRefProps, HTMLTagProps, SvelteEvent } from '../utils/html-element.utils.js';
import type { SizeInput } from '../utils/style.utils.js';
export interface NeoListDividerOption {
top?: boolean;
bottom?: boolean;
}
export declare function showDivider(divider?: boolean | NeoListDividerOption, position?: keyof NeoListDividerOption): boolean | undefined;
export interface NeoListItemCommon<Tag extends keyof HTMLElementTagNameMap = 'li'> {
/**
* Unique identifier for the list item.
* Used for keying the list item.
* If not provided, the index will be used.
* Note: Required for entering/leaving transitions.
*/
id?: string | number | symbol;
/**
* The HTML tag to use for the item.
* @default 'li'
*/
tag?: Tag;
/**
* Optional label to display in the list item.
* If not provided, the value will be used.
*
* @note Recommended in section for accessibility.
*/
label?: string;
/**
* If true, the list item will be disabled.
*/
disabled?: boolean;
/**
* If true, the item will not trigger selection, but will not be styled as disabled.
*/
readonly?: boolean;
/**
* Text color to use for the item.
*/
color?: Color | CSSStyleDeclaration['color'];
/**
* If true, the list section will display a divider above the title.
*/
divider?: boolean | NeoListDividerOption;
/**
* If true, the item will not be displayed.
*/
hidden?: boolean;
/**
* Reverse the direction of the item.
*
* @default false
*/
reverse?: boolean;
/**
* Optional props to pass to the divider.
*/
dividerProps?: NeoDividerProps;
/**
Optional props to pass to the container.
*/
containerProps?: HTMLNeoBaseElement<HTMLElementTagNameMap[Tag]>;
}
export interface NeoListItemContext<Value = unknown, Tag extends keyof HTMLElementTagNameMap = 'li', Context = NeoListContext> {
item: NeoListItem<Value, Tag>;
index: number;
checked?: boolean;
context: Context;
}
export type NeoListItemRender<Value = unknown, Tag extends keyof HTMLElementTagNameMap = 'li', Context = NeoListContext> = Snippet<[
NeoListItemContext<Value, Tag, Context>
]>;
/**
* TODO: Add support for video and audio
*/
export type NeoBaseListItemMedia<Type extends NeoMediaTypes = typeof NeoMediaType.Image> = NeoMediaProps & (Type extends typeof NeoMediaType.Image ? {
type?: typeof NeoMediaType.Image;
image?: NeoImageProps;
} : Record<string, never>);
export type NeoBaseListItemTag = string | NeoButtonProps | NeoPillProps;
export declare function isButtonTag(tag: NeoBaseListItemTag): tag is NeoButtonProps;
export type NeoBaseListItem<Value = unknown, Tag extends keyof HTMLElementTagNameMap = 'li', Context = any> = {
/**
* An arbitrary value to associate with the list item.
*/
value: Value;
/**
* Optional description to display in the list item.
*/
description?: string;
/**
* Optional tags to display between label and description.
*/
tags?: NeoBaseListItemTag[];
/**
* Optional media to display in the list item.
*/
media?: NeoBaseListItemMedia;
/**
* Optional snippet to display in place of the list item.
*/
render?: NeoListItemRender<Value, Tag, Context>;
/**
* Snippet to display before the list item.
* e.g. an icon or avatar.
*/
before?: NeoListItemRender<Value, Tag, Context>;
/**
* Snippet to display after the list item.
* e.g. a badge or action button.
*/
after?: NeoListItemRender<Value, Tag>;
/**
* The url to navigate to when the anchor is clicked.
*/
href?: NeoButtonProps['href'];
/**
* Callback function to be called when the button is clicked.
*/
onclick?: NeoButtonProps['onclick'];
/**
* Optional props to pass to the button.
*/
buttonProps?: NeoButtonProps;
} & NeoListItemCommon<Tag>;
export type NeoListItem<Value = unknown, Tag extends keyof HTMLElementTagNameMap = 'li', Context = NeoListContext> = NeoBaseListItem<Value, Tag, Context>;
export interface NeoListRenderContext<Value = unknown, Item = NeoListItemOrSection<Value>> {
items: Item[];
/**
* The index of the section in the list.
*/
index?: number;
section?: NeoListSection<Value>;
context?: NeoListContext;
}
export type NeoListRender<Value = unknown> = Snippet<[NeoListRenderContext<Value>]>;
export type NeoListSectionRender<Value = unknown> = Snippet<[NeoListRender<Value>, NeoListRenderContext<Value>]>;
export type NeoListSection<Value = unknown, Tag extends keyof HTMLElementTagNameMap = 'ul'> = {
/**
* Array of child list items to display.
*/
items: NeoListItem<Value>[];
/**
* Whether the section is sticky (stays on top while scrolling the content).
*/
sticky?: boolean;
/**
* Optional snippet to display in place of the list section.
* @param list - The list snippet that render items.
* @param context - The list section context.
*/
render?: NeoListSectionRender<Value>;
/**
* Optional snippet to display when the section is empty.
*/
empty?: Snippet<[NeoListContext]>;
/**
* Optional props to pass to the section container.
*/
sectionProps?: HTMLNeoBaseElement<HTMLElementTagNameMap[Tag]>;
} & NeoListItemCommon<Tag>;
export declare const isSection: <Value = unknown>(item: NeoListItem<Value> | NeoListSection<Value>) => item is NeoListSection<Value>;
export interface NeoListSelectedItem<Value = unknown, Tag extends keyof HTMLElementTagNameMap = 'li'> {
index: number;
item: NeoListItem<Value, Tag>;
sectionIndex?: number;
section?: NeoListSection<Value>;
}
export interface NeoListSelectEvent<Selected = NeoListSelectedItem | NeoListSelectedItem[]> {
type: 'select' | 'clear' | 're-select';
previous?: Selected;
current?: Selected;
removed?: Selected;
added?: Selected;
}
export interface NeoListSelectMethods<Value = unknown> {
/**
* Select an item in the list.
*
* @returns The selection event if the item was selected, undefined otherwise.
*/
selectItem: (...selection: NeoListSelectedItem<Value>[]) => NeoListSelectEvent | undefined;
/**
* Clear the selected item(s).
* If no index is provided, all items will be cleared.
*
* @returns The selection event if the list or item was cleared, undefined otherwise.
*/
clearItem: (...selection: NeoListSelectedItem<Value>[]) => NeoListSelectEvent | undefined;
/**
* Clear all items in the list then re-select the previously selected item(s) only if they still exist in the list.
*
* @note Requires the `id` property to be set and unique for each item.
* @returns The selection event if the list or item was cleared, undefined otherwise.
*/
reSelect: () => NeoListSelectEvent | undefined;
}
export interface NeoListMethods {
/**
* Scroll the list to the top.
*/
scrollToTop: (options?: ScrollToOptions) => Promise<HTMLElement | false>;
/**
* Scroll the list to the bottom.
*/
scrollToBottom: (options?: ScrollToOptions) => Promise<HTMLElement | false>;
}
export type NeoListItemOrSection<Value = unknown> = NeoListItem<Value> | NeoListSection<Value>;
export interface NeoListSelectState<Selected = NeoListSelectedItem | NeoListSelectedItem[]> {
/**
* The currently selected item(s).
*/
selected?: Selected;
/**
* Whether to allow selecting items in the list.
*/
select?: boolean;
/**
* Whether to allow multiple items in the selection.
*/
multiple?: boolean;
/**
* Whether to allow deselecting items if it will result in an empty selection.
*/
nullable?: boolean;
}
export interface NeoListBaseProps {
/**
* Whether to dim the opacity of inactive tabs on hover.
*/
dim?: boolean;
/**
* Whether to display a shadow when scrolling content.
*
* @default true
*/
shadow?: boolean;
/**
* Overrides the default scrollbars.
*/
scrollbar?: boolean;
/**
* Whether to round the corners of the list items.
*/
rounded?: BorderRadiusInput;
}
export interface NeoListState<Item = NeoListItemOrSection> {
/**
* List items to display.
*/
items?: Item[];
/**
* Optional filter to highlight text.
*/
highlight?: string;
/**
* A filter function to apply to each item in the list.
* @param item
*/
filter?: (item: Item) => boolean;
/**
* A sort function to apply to the list items.
* @param a
* @param b
*/
sort?: (a: Item, b: Item) => number;
/**
* Inverts the flow of the list (flex-direction: column-reverse).
*
* @default false
*/
flip?: boolean;
/**
* If the list is currently loading additional items.
*/
loading?: boolean;
/**
* If the list is currently being scrolled.
*/
scrolling?: boolean;
/**
* Disable all items in the list.
*/
disabled?: boolean;
/**
* Disable selection for all items in the list.
*/
readonly?: boolean;
/**
* Reverse the direction of the item.
*
* @default false
*/
reverse?: boolean;
/**
* Whether to display a divider above items in the list.
* If an item divider option is set, it will take precedence over the list divider.
*
* @default false
*/
divider?: boolean;
}
export type NeoListContext<Selected = NeoListSelectedItem | NeoListSelectedItem[], Value = unknown> = NeoListState & NeoListSelectState<Selected> & NeoListMethods & NeoListSelectMethods<Value>;
export type NeoListProps<Value = unknown, Tag extends keyof HTMLElementTagNameMap = 'ul', Selected = NeoListSelectedItem | NeoListSelectedItem[], Context = NeoListContext<Selected>> = {
/**
* Optional snippet to display in place of each list item.
*/
item?: NeoListItemRender<Value, 'li', Context>;
/**
* Optional snippet to display in place of each list section.
*/
section?: NeoListSectionRender<Value>;
/**
* Optional snippet to display when the list is empty.
*/
empty?: Snippet<[Context]>;
/**
* Optional snippet to display in place of the loading indicator.
*/
loader?: Snippet<[Context]>;
/**
* Optional snippet to display after the list.
*/
after?: Snippet<[Context]>;
/**
* Optional snippet to display before the list.
*/
before?: Snippet<[Context]>;
/**
* Optional snippet to display inside the list.
*/
children?: Snippet<[Context]>;
/**
* Transition function to apply when removing items from the list.
* Note: unique `id` is required for entering/leaving transitions.
*/
animate?: HTMAnimationProps['animate'];
/**
* Transition function to apply when adding items to the list.
* Note: unique `id` is required for entering/leaving transitions.
*/
in?: HTMLTransitionProps['in'];
/**
* Transition function to apply when removing items from the list.
* Note: unique `id` is required for entering/leaving transitions.
*/
out?: HTMLTransitionProps['out'];
/**
* Whether to scroll to the bottom when loading additional items.
*
* @default false
*/
scrollToLoader?: boolean;
/**
* Scroll tolerance when determining if the list is scrolled to the top or bottom (in pixels).
*
* @default 1
*/
scrollTolerance?: number;
/**
* Optional flex strategy for the container
*/
flex?: CSSStyleDeclaration['flex'];
/**
* Optional list width constraints.
*/
width?: SizeInput<'width'>;
/**
* Optional list height constraints.
*/
height?: SizeInput<'height'>;
/**
* The HTML tag to use for the list.
* @default 'ul'
*/
tag?: Tag | keyof HTMLElementTagNameMap;
/**
* Event listener that fires when an item is selected/deselected.
* @param event
*/
onSelect?: (event: NeoListSelectEvent<Selected>) => void;
/**
* Event listener that fires when the list is scrolled to the top.
* @param event
*/
onScrollTop?: (event?: SvelteEvent) => void;
/**
* Event listener that fires when the list is scrolled to the bottom.
* @param event
*/
onScrollBottom?: (event?: SvelteEvent) => void;
/**
* The props to pass to the list container.
*/
containerProps?: HTMLNeoBaseElement & HTMLTagProps;
/**
* The props to pass to the loader.
*/
loaderProps?: Partial<NeoListBaseLoaderProps>;
/**
* Optional props to pass to the button.
*/
buttonProps?: NeoButtonProps;
/**
* Optional props to pass to the divider.
*/
dividerProps?: NeoDividerProps;
/**
* Optional props to pass to the list item.
*/
itemProps?: Partial<Omit<NeoListBaseItemProps<Value, Context>, 'buttonProps'>>;
/**
* Optional props to pass to the list section.
*/
sectionProps?: NeoListBaseSectionProps<Value, Tag>;
} & NeoListBaseProps & HTMLRefProps & HTMLNeoBaseElement<HTMLElementTagNameMap[Tag]> & NeoListState & NeoListSelectState<Selected>;
export type NeoListHTMLElement<Value = unknown, Tag extends keyof HTMLElementTagNameMap = 'ul'> = HTMLNeoBaseElement<HTMLElementTagNameMap[Tag]> & NeoListMethods & NeoListSelectMethods<Value>;
export declare function findByIdInList<Value = unknown>(selection: NeoListSelectedItem<Value>, array: NeoListItemOrSection<Value>[]): NeoListSelectedItem<Value> | undefined;
export declare function findByValueInList<Value = unknown>(value: Value, array: NeoListItemOrSection<Value>[]): NeoListSelectedItem<Value> | undefined;
export declare function findByValuesInList<Value = unknown>(values: Value | Value[], array: NeoListItemOrSection<Value>[]): undefined | NeoListSelectedItem<Value> | NeoListSelectedItem<Value>[];