UNPKG

@dvcol/neo-svelte

Version:

Neomorphic ui library for svelte 5

432 lines (431 loc) 15 kB
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>[];