import { XIN_PATH, XIN_VALUE, TOSI_ACCESSOR, TAKE_DESCRIPTOR } from './metadata'; import { TosiStyleRule } from './css-types'; import { ElementsProxy } from './elements-types'; export type AnyFunction = (...args: any[]) => any | Promise; export type TosiScalar = string | boolean | number | symbol | AnyFunction; export type TosiArray = any[]; export interface TosiObject { [key: string | number | symbol]: any; } export type TosiProxyTarget = TosiObject | TosiArray; export type TosiValue = TosiObject | TosiArray | TosiScalar | null | undefined; type ProxyObserveFunc = (callback: ObserverCallbackFunction) => VoidFunction; type ProxyBindFunc = (element: T, binding: TosiBinding, options?: TosiObject) => 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: TosiBinding, options?: TosiObject) => void; on: (element: HTMLElement, eventType: keyof HTMLElementEventMap) => VoidFunction; binding: (binding: TosiBinding) => { bind: { value: string; binding: TosiBinding; }; }; tosiBinding: (binding: TosiBinding) => { bind: { value: string; binding: TosiBinding; }; }; 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; } /** * TosiProps 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 TosiProps { [TOSI_ACCESSOR]: TosiAccessor; tosi: TosiAccessor; path: string; value: T; touch: () => void; observe: ProxyObserveFunc; bind: ProxyBindFunc; on: (element: HTMLElement, eventType: keyof HTMLElementEventMap) => VoidFunction; binding: (binding: TosiBinding) => { bind: { value: string; binding: TosiBinding; }; }; tosiBinding: (binding: TosiBinding) => { bind: { value: string; binding: TosiBinding; }; }; take: (...args: [...sources: any[], transform: (...values: any[]) => any]) => TakeDescriptor; valueOf: () => T; toJSON: () => T; [XIN_PATH]: string; xinPath: string; tosiPath: string; [XIN_VALUE]: T; xinValue: T; tosiValue: T; xinObserve: ProxyObserveFunc; tosiObserve: ProxyObserveFunc; xinBind: 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: TosiBinding, options?: TosiObject) => void; on: (element: HTMLElement, eventType: keyof HTMLElementEventMap) => VoidFunction; binding: (binding: TosiBinding) => { bind: { value: string; binding: TosiBinding; }; }; take: (...args: [...sources: any[], transform: (...values: any[]) => any]) => TakeDescriptor; 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: TosiBinding, options?: TosiObject) => void; tosiBind: (element: E, binding: TosiBinding, options?: TosiObject) => void; xinOn: (element: HTMLElement, eventType: keyof HTMLElementEventMap) => VoidFunction; tosiOn: (element: HTMLElement, eventType: keyof HTMLElementEventMap) => VoidFunction; tosiBinding: (binding: TosiBinding) => { bind: { value: string; binding: TosiBinding; }; }; } /** * 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> : unknown); export type BoxedProxy = T extends Array ? Array> & TosiProps & BoxedArrayProps : T extends Function ? T & TosiProps : T extends object ? { [K in keyof T]: BoxedProxy; } & TosiProps : 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 TosiProxy = T extends Array ? Array> : T extends Function ? T : T extends object ? { [K in keyof T]: T[K] extends object ? TosiProxy : T[K]; } : T; export type TosiProxyObject = TosiProps & { [key: string]: TosiProxyObject | TosiProxyArray | TosiObject | TosiArray | TosiScalar; }; export type TosiProxyArray = TosiProps<[]> & { [key: string]: TosiProxyObject; } & (TosiProxyObject[] | TosiScalar[]); export type TosiTouchableType = string | TosiProxy | BoxedProxy | String | Number | Boolean; export type EventType = keyof HTMLElementEventMap; export type TosiEventHandler = ((evt: T & { target: E; }) => void) | ((evt: T & { target: E; }) => Promise) | string; export type TosiBindingShortcut = TosiTouchableType | TosiBindingSpec | 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 TosiBindingSpec { value: TosiTouchableType | any; [key: string]: any; } export type TosiBindingSetter = (element: T, value: any, options?: TosiObject) => void; export type TosiBindingGetter = (element: T, options?: TosiObject) => any; export interface TosiBinding { toDOM?: TosiBindingSetter; fromDOM?: TosiBindingGetter; } export interface TosiInlineBinding { value: TosiTouchableType; binding: TosiBinding | TosiBindingSetter | string; /** forwarded as `bind()`'s fourth argument — idPath, virtual, hiddenProp… */ options?: TosiObject; } export type TosiClassSpec = string | false | null | Array | Record; export interface ElementProps { onClick?: TosiEventHandler; onMousedown?: TosiEventHandler; onMouseenter?: TosiEventHandler; onMouseleave?: TosiEventHandler; onMouseup?: TosiEventHandler; onTouchstart?: TosiEventHandler; onTouchmove?: TosiEventHandler; onTouchend?: TosiEventHandler; onTouchcancel?: TosiEventHandler; onDragstart?: TosiEventHandler; onDragover?: TosiEventHandler; onDragend?: TosiEventHandler; onDragenter?: TosiEventHandler; onDragleave?: TosiEventHandler; onInput?: TosiEventHandler; onChange?: TosiEventHandler; onSubmit?: TosiEventHandler; onKeydown?: TosiEventHandler; onKeyup?: TosiEventHandler; /** one inline binding, or several — `create()` accumulates rather than * overwriting, so a container can be list-bound AND carry its own binding */ bind?: TosiInlineBinding | Array>; /** TWO-WAY value binding; `value: proxy` is one-way (state -> DOM) only */ bindValue?: TosiBindingShortcut; /** Text binding. With a proxy, `{ textContent: proxy }` says the same thing * in a plain prop and is the more durable spelling; with a PATH STRING this * is the only form that binds — `textContent: 'path'` sets the literal text * "path" and silently does not bind (a mistake that typechecks and passes a * unit suite; it was caught only by the browser doc-test lane). Not * deprecated in either form. */ bindText?: TosiBindingShortcut; /** the low-level list-binding prop; `.tosi.listBinding()` is sugar over it */ bindList?: TosiBindingShortcut; /** Enabled binding (inverted `disabled`). With a proxy, * `{ disabled: proxy.tosi.take(v => !v) }` is the plain-prop equivalent; * with a PATH STRING this is the only form that works — `disabled: 'path'` * assigns a non-empty, therefore always truthy, string and permanently * DISABLES the control. Not deprecated in either form. */ bindEnabled?: TosiBindingShortcut; /** Disabled binding. With a proxy, `{ disabled: proxy }` is the plain-prop * equivalent; with a PATH STRING this is the only form that works, for the * same always-truthy reason as `bindEnabled`. Not deprecated. */ bindDisabled?: TosiBindingShortcut; style?: TosiStyleRule; class?: TosiClassSpec; apply?: (element: Element) => void | Promise; /** inline contract: JSON-Schema-shaped description of the element's bound * value — harvested into the agent surface's map, enforced on agent * writes, overridable by top-level curation (expose.contract) */ contract?: Record; [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 | Map | BoxedScalar | TosiProps; export type HTMLElementCreator = (...contents: ElementPart[]) => T; export type FragmentCreator = (...contents: ElementPart[]) => DocumentFragment; export type ElementCreator = (...contents: ElementPart[]) => T; export type ContentPart = ElementPart | null | undefined; 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?: TosiTouchableType; } /** @deprecated Use `TosiScalar` */ export type XinScalar = TosiScalar; /** @deprecated Use `TosiArray` */ export type XinArray = TosiArray; /** @deprecated Use `TosiObject` */ export type XinObject = TosiObject; /** @deprecated Use `TosiProxyTarget` */ export type XinProxyTarget = TosiProxyTarget; /** @deprecated Use `TosiValue` */ export type XinValue = TosiValue; /** @deprecated Use `TosiProps` */ export type XinProps = TosiProps; /** @deprecated Use `TosiProxy` */ export type XinProxy = TosiProxy; /** @deprecated Use `TosiProxyObject` */ export type XinProxyObject = TosiProxyObject; /** @deprecated Use `TosiProxyArray` */ export type XinProxyArray = TosiProxyArray; /** @deprecated Use `TosiTouchableType` */ export type XinTouchableType = TosiTouchableType; /** @deprecated Use `TosiEventHandler` */ export type XinEventHandler = TosiEventHandler; /** @deprecated Use `TosiBindingShortcut` */ export type XinBindingShortcut = TosiBindingShortcut; /** @deprecated Use `TosiBindingSpec` */ export type XinBindingSpec = TosiBindingSpec; /** @deprecated Use `TosiBindingSetter` */ export type XinBindingSetter = TosiBindingSetter; /** @deprecated Use `TosiBindingGetter` */ export type XinBindingGetter = TosiBindingGetter; /** @deprecated Use `TosiBinding` */ export type XinBinding = TosiBinding; /** @deprecated Use `TosiInlineBinding` */ export type XinInlineBinding = TosiInlineBinding; /** @deprecated Use `TosiClassSpec` */ export type XinClassSpec = TosiClassSpec; export {};