import { XIN_PATH, XIN_VALUE, XIN_OBSERVE, XIN_BIND, TOSI_ACCESSOR, TAKE_DESCRIPTOR } from './metadata'; import { XinStyleRule } from './css-types'; import { ElementsProxy } from './elements-types'; export type AnyFunction = (...args: any[]) => any | Promise; export type XinScalar = string | boolean | number | symbol | AnyFunction; export type XinArray = any[]; export interface XinObject { [key: string | number | symbol]: any; } export type XinProxyTarget = XinObject | XinArray; export type XinValue = XinObject | XinArray | XinScalar | null | undefined; type ProxyObserveFunc = ((path: string) => void); type ProxyBindFunc = (element: T, binding: XinBinding, options?: XinObject) => VoidFunction; /** * TakeDescriptor is returned by `.take()` — a reactive binding descriptor * that carries paths to observe and a transform function. * The binding system uses this to wire up multi-path reactive transforms. */ export interface TakeDescriptor { [TAKE_DESCRIPTOR]: true; paths: string[]; transform: (...values: any[]) => any; } /** * TosiAccessor is the collision-free observer API accessed via `.tosi`. * Unlike the direct properties (path, value, observe, etc.) which can be * shadowed by actual object properties, `.tosi` is always available. */ export interface TosiAccessor { value: T; readonly path: string; touch: () => void; observe: (callback: ObserverCallbackFunction) => VoidFunction; bind: (element: E, binding: XinBinding, options?: XinObject) => void; on: (element: HTMLElement, eventType: keyof HTMLElementEventMap) => VoidFunction; binding: (binding: XinBinding) => { bind: { value: string; binding: XinBinding; }; }; listBinding: (templateBuilder: ListTemplateBuilder, options?: ListBindingOptions) => ListBinding; listFind: { (selector: (item: any) => any, value: any): BoxedProxy | undefined; (element: Element): BoxedProxy | undefined; }; listUpdate: (selector: (item: any) => any, newValue: any) => BoxedProxy; listRemove: (selector: (item: any) => any, value: any) => boolean; take: (...args: [...sources: any[], transform: (...values: any[]) => any]) => TakeDescriptor; } /** * XinProps provides the observer API for boxed objects and arrays. * The `.tosi` accessor is the preferred, collision-free way to access * the observer API. The direct properties (path, value, observe, etc.) * still work but can be shadowed by actual object properties with the * same names. */ export interface XinProps { [TOSI_ACCESSOR]: TosiAccessor; tosi: TosiAccessor; path: string; value: T; touch: () => void; observe: ProxyObserveFunc; bind: ProxyBindFunc; on: (element: HTMLElement, eventType: keyof HTMLElementEventMap) => VoidFunction; binding: (binding: XinBinding) => { bind: { value: string; binding: XinBinding; }; }; valueOf: () => T; toJSON: () => T; [XIN_PATH]: string; tosiPath: string; [XIN_VALUE]: T; tosiValue: T; [XIN_OBSERVE]: ProxyObserveFunc; tosiObserve: ProxyObserveFunc; [XIN_BIND]: ProxyBindFunc; tosiBind: ProxyBindFunc; } type ListTemplateBuilder = (elements: ElementsProxy, item: U, columnIndex?: number) => HTMLElement; type ListBinding = [ElementProps, HTMLTemplateElement]; type ListFieldSelector = (item: BoxedProxy) => BoxedScalar; export interface BoxedArrayProps { listBinding: (templateBuilder: ListTemplateBuilder, options?: ListBindingOptions) => ListBinding; tosiListBinding: (templateBuilder: ListTemplateBuilder, options?: ListBindingOptions) => ListBinding; listFind: { (selector: ListFieldSelector, value: any): BoxedProxy | undefined; (element: Element): BoxedProxy | undefined; }; listUpdate: (selector: ListFieldSelector, newValue: U) => BoxedProxy; listRemove: (selector: ListFieldSelector, value: any) => boolean; } /** * BoxedScalarAPI is the observer API surface for boxed primitives. */ interface BoxedScalarAPI { [TOSI_ACCESSOR]: TosiAccessor; tosi: TosiAccessor; value: T; path: string; touch: () => void; observe: (callback: ObserverCallbackFunction) => VoidFunction; bind: (element: E, binding: XinBinding, options?: XinObject) => void; on: (element: HTMLElement, eventType: keyof HTMLElementEventMap) => VoidFunction; binding: (binding: XinBinding) => { bind: { value: string; binding: XinBinding; }; }; listBinding: (templateBuilder: ListTemplateBuilder, options?: ListBindingOptions) => ListBinding; valueOf: () => T; toString: () => string; toJSON: () => T; xinValue: T; xinPath: string; tosiValue: T; tosiPath: string; xinObserve: (callback: ObserverCallbackFunction) => VoidFunction; tosiObserve: (callback: ObserverCallbackFunction) => VoidFunction; xinBind: (element: E, binding: XinBinding, options?: XinObject) => void; tosiBind: (element: E, binding: XinBinding, options?: XinObject) => void; xinOn: (element: HTMLElement, eventType: keyof HTMLElementEventMap) => VoidFunction; tosiOn: (element: HTMLElement, eventType: keyof HTMLElementEventMap) => VoidFunction; tosiBinding: (binding: XinBinding) => { bind: { value: string; binding: XinBinding; }; }; } /** * BoxedScalar represents a boxed primitive value (string, number, boolean, null, undefined). * It provides the reactive API (value, path, observe, etc.) plus all methods from the * underlying primitive's prototype (e.g. toLocaleLowerCase for strings, toFixed for numbers). * * Note: Direct assignment like `proxy.x = 3` is a TypeScript type error due to * fundamental limitations in TypeScript's mapped types (no asymmetric get/set). * Use `proxy.x.value = 3` instead. */ export type BoxedScalar = BoxedScalarAPI & (T extends string ? Omit> : T extends number ? Omit> : T extends boolean ? Omit> : {}); export type BoxedProxy = T extends Array ? Array> & XinProps & BoxedArrayProps : T extends Function ? T & XinProps : T extends object ? { [K in keyof T]: BoxedProxy; } & XinProps : T extends string ? BoxedScalar : T extends number ? BoxedScalar : T extends boolean ? BoxedScalar : T extends undefined | null ? BoxedScalar : T; export type Unboxed = T extends BoxedScalar ? U : T extends String ? string : T extends Number ? number : T extends Boolean ? boolean : T; export type XinProxy = T extends Array ? Array> : T extends Function ? T : T extends object ? { [K in keyof T]: T[K] extends object ? XinProxy : T[K]; } : T; export type XinProxyObject = XinProps & { [key: string]: XinProxyObject | XinProxyArray | XinObject | XinArray | XinScalar; }; export type XinProxyArray = XinProps<[]> & { [key: string]: XinProxyObject; } & (XinProxyObject[] | XinScalar[]); export type XinTouchableType = string | XinProxy | BoxedProxy | String | Number | Boolean; export type EventType = keyof HTMLElementEventMap; export type XinEventHandler = ((evt: T & { target: E; }) => void) | ((evt: T & { target: E; }) => Promise) | string; export type XinBindingShortcut = XinTouchableType | XinBindingSpec | TakeDescriptor; type _BooleanFunction = () => boolean; type _PathTestFunction = (path: string) => boolean | symbol; export type PathTestFunction = _BooleanFunction | _PathTestFunction; type OptionalSymbol = symbol | undefined; type _CallbackFunction = (() => void) | (() => OptionalSymbol); type _PathCallbackFunction = ((path: string) => void) | ((path: string) => OptionalSymbol); export type ObserverCallbackFunction = _PathCallbackFunction | _CallbackFunction; export interface XinBindingSpec { value: XinTouchableType | any; [key: string]: any; } export type XinBindingSetter = (element: T, value: any, options?: XinObject) => void; export type XinBindingGetter = (element: T, options?: XinObject) => any; export interface XinBinding { toDOM?: XinBindingSetter; fromDOM?: XinBindingGetter; } export interface XinInlineBinding { value: XinTouchableType; binding: XinBinding | XinBindingSetter | string; } export interface ElementProps { onClick?: XinEventHandler; onMousedown?: XinEventHandler; onMouseenter?: XinEventHandler; onMouseleave?: XinEventHandler; onMouseup?: XinEventHandler; onTouchstart?: XinEventHandler; onTouchmove?: XinEventHandler; onTouchend?: XinEventHandler; onTouchcancel?: XinEventHandler; onDragstart?: XinEventHandler; onDragover?: XinEventHandler; onDragend?: XinEventHandler; onDragenter?: XinEventHandler; onDragleave?: XinEventHandler; onInput?: XinEventHandler; onChange?: XinEventHandler; onSubmit?: XinEventHandler; onKeydown?: XinEventHandler; onKeyup?: XinEventHandler; bind?: XinInlineBinding; bindValue?: XinBindingShortcut; /** @deprecated Use { textContent: proxy } instead */ bindText?: XinBindingShortcut; /** @deprecated Use .tosi.listBinding() instead */ bindList?: XinBindingShortcut; /** @deprecated Use { disabled: proxy.tosi.take(v => !v) } instead */ bindEnabled?: XinBindingShortcut; /** @deprecated Use { disabled: proxy } instead */ bindDisabled?: XinBindingShortcut; style?: XinStyleRule; class?: string; apply?: (element: Element) => void | Promise; [key: string]: any; } export interface StringMap { [key: string]: any; } export interface PartsMap { [key: string]: Element; } export type ValueElement = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement; export type ElementPart = Element | DocumentFragment | ElementProps | string | number; export type HTMLElementCreator = (...contents: ElementPart[]) => T; export type FragmentCreator = (...contents: ElementPart[]) => DocumentFragment; export type ElementCreator = (...contents: ElementPart[]) => T; export type ContentPart = Element | DocumentFragment | string; export type ContentType = ContentPart | ContentPart[]; export type ListFilter = (array: any[], needle: any) => any[]; export interface ListBindingOptions { idPath?: string; virtual?: { height: number; /** When set, enables variable-height mode using scroll-fraction interpolation. * Items render at natural height; minHeight is used for scroll area estimation. */ minHeight?: number; width?: number; visibleColumns?: number; rowChunkSize?: number; /** Use 'window' to virtualize based on window scroll position instead of element scroll */ scrollContainer?: 'window' | 'element'; /** Number of elements to stamp per array item (for grid layouts). Default 1. */ itemsPerRow?: number; }; hiddenProp?: symbol | string; visibleProp?: symbol | string; filter?: ListFilter; needle?: XinTouchableType; } export {};