atomico
Version:
Atomico is a small library for the creation of interfaces based on web-components, only using functions and hooks.
356 lines (312 loc) • 9.54 kB
TypeScript
import { Sheet, Sheets } from "./css.js";
import { DOMFormElement, DOMFormElements } from "./dom-html.js";
import { SVGProperties } from "./dom-svg.js";
import { FillConstructor, FillObject, SchemaInfer } from "./schema.js";
import { VNodeKeyTypes } from "./vnode.js";
export type Nullable<T> = NonNullable<T> | undefined | null;
export type PropsNullable<Data> = {
[I in keyof Data]?: Nullable<Data[I]>;
};
type DOMCustomTag<Base, Props> = Omit<Base, keyof Props> & Props;
type DOMRefValue<Target> = FillObject | ((target: Target) => any);
type DOMRef<Target> = {
ref?: DOMRefValue<Target>;
};
interface DOMGenericProperties {
style?: string | Partial<CSSStyleDeclaration> | object;
class?: string;
id?: string;
slot?: string;
part?: string;
is?: string;
tabindex?: string | number;
role?: string;
shadowDom?: boolean | Partial<ShadowRootInit>;
staticNode?: boolean;
cloneNode?: boolean;
width?: string | number;
height?: string | number;
key?: any;
children?: any;
}
type DOMCleanKeys =
| keyof DOMGenericProperties
| `add${string}`
| `get${string}`
| `set${string}`
| `has${string}`
| `matches${string}`
| `remove${string}`
| `replace${string}`
| `querySelector${string}`
| `offset${string}`
| `append${string}`
| `request${string}`
| `scroll${string}`
| `is${string}`
| `toggle${string}`
| `webkit${string}`
| `insert${string}`
| `client${string}`
| `child${string}`
| `${string}_${string}`
| `${string}HTML`
| `${string}Child`
| `${string}Validity`
| `${string}Capture`
| `${string}ElementSibling`
| "classList"
| "attributes"
| "normalize"
| "closest"
| "localName"
| "contains"
| "animate"
| "attachShadow"
| "outerText"
| "attachInternals"
| "click"
| "tagName"
| "focus"
| "submit"
| "accessKeyLabel"
| "elements"
| "isContentEditable"
| "innerText"
| "prepend"
| "namespaceURI"
| "blur"
| "dataset"
| "shadowRoot"
| keyof Omit<ChildNode, "textContent">;
type HTMLTags = HTMLElementTagNameMap;
type SVGTags = Omit<SVGElementTagNameMap, "a">;
type CheckEvent<CurrentEvent, True> = CurrentEvent extends Event ? True : never;
export interface DOMListener<E = Event> extends AddEventListenerOptions {
(event: E extends Element ? DOMEvent<E> : E): any;
}
/**
* @todo Rename Handler to Listener
*/
export type DOMEventHandlerKeys<P> = {
[I in keyof P]-?: NonNullable<P[I]> extends DOMEventHandlerValue<infer E>
? CheckEvent<E, I>
: P[I] extends { value: DOMEventHandlerValue<infer E> }
? CheckEvent<E, I>
: never;
}[keyof P];
export interface DOMEventHandlerType extends FunctionConstructor {}
export interface DOMEventHandlerValue<CurrentEvent> {
(event: CurrentEvent): any;
}
type DOMEventType<Type extends string, CurrentEvent> = {
[I in keyof "0" as `on${Type}`]: {
type: DOMEventHandlerType;
value: DOMEventHandlerValue<CurrentEvent>;
};
};
interface DOM$Attrs {
[prop: `\$${string}`]: Nullable<string>;
}
interface DOMUnknown {
[prop: string]: any;
}
type DOMEventTarget<CurrentEvent, CurrentTarget, Target> = {
[I in keyof CurrentEvent]: I extends "currentTarget"
? CurrentTarget
: I extends "target"
? Target
: CurrentEvent[I];
};
type DOMTarget<
Target,
CurrentEvent,
Targets = Element | Node
> = CurrentEvent extends {
customTarget: infer EventTarget;
}
? DOMTarget<Target, Omit<CurrentEvent, "customTarget">, EventTarget>
: DOMEventTarget<CurrentEvent, Target, Targets>;
type DOMGetEventBefore<Value, Target> = Value extends DOMEventHandlerValue<
infer Event
>
? DOMEvent<HTMLElement, Event & DOMCustomTarget<Target>>
: null;
type DOMGetEvent<
Type extends string,
Element extends AtomicoStatic<any>
> = Element extends {
"##props": infer Props;
}
? `on${Type}` extends keyof Props
? DOMGetEventBefore<NonNullable<Props[`on${Type}`]>, DOMThis<Element>>
: Event
: Event;
type DOMEvent<
Target = HTMLElement,
CurrentEvent = Event
> = Target extends string
? CurrentEvent extends AtomicoStatic<any>
? DOMGetEvent<Target, CurrentEvent>
: DOMEventType<Target, CurrentEvent>
: DOMTarget<DOMThis<Target>, CurrentEvent>;
type DOMEventHandler<Target, Handler> = Handler extends (
ev: infer CurrentEvent
) => any
? CurrentEvent extends Event
? (ev: DOMEvent<Target, CurrentEvent>) => any
: Handler
: Handler;
type DOMEvents<Target> = {
[Prop in keyof Target]?: Prop extends `on${string}`
? DOMEventHandler<Target, Target[Prop]>
: Target[Prop];
};
type DOMCustomTarget<Target> = { customTarget: Target };
export type DOMTag<Element, Props = null> = Props extends null
? PropsNullable<
Omit<DOMEvents<Element>, DOMCleanKeys> &
DOMGenericProperties &
DOMRef<Element>
> &
DOM$Attrs &
DOMUnknown
: PropsNullable<
Props &
Omit<DOMEvents<Element & Props>, keyof Props | DOMCleanKeys> &
DOMGenericProperties &
DOMRef<Element & Props>
> &
DOM$Attrs &
DOMUnknown;
type S = keyof null;
export type DOMTags<HTMLTags, CustomProps = null, HTMLMerge = null> = {
[Tag in keyof HTMLTags]: Tag extends keyof HTMLMerge
? DOMTag<HTMLMerge[Tag], CustomProps>
: DOMTag<HTMLTags[Tag], CustomProps>;
};
export type DOMThis<Element> = Element extends new (
...args: any[]
) => infer This
? This
: Element;
export interface AtomicoElements {
host: HTMLElement;
}
export interface DOMCustomTags {
slot: HTMLSlotElement & {
onslotchange: (event: Event & DOMCustomTarget<HTMLSlotElement>) => void;
assignNode: ChildNode;
};
form: DOMFormElement & {
onsubmit: (
event: SubmitEvent & DOMCustomTarget<DOMFormElements>
) => any;
onchange: (event: Event & DOMCustomTarget<DOMFormElements>) => any;
oninput: (event: Event & DOMCustomTarget<DOMFormElements>) => any;
};
input: DOMCustomTag<
HTMLInputElement,
{
min: string | number;
max: string | number;
step: string | number;
value: string | number;
}
>;
}
export type JSXElements = DOMTags<AtomicoElements> &
DOMTags<HTMLTags, null, DOMCustomTags> &
DOMTags<SVGTags, SVGProperties>;
export type JSXProxy<Props, This> = {
[I in keyof Props]?: I extends `on${string}`
? NonNullable<Props[I]> extends DOMEventHandlerValue<infer CurrentEvent>
? Nullable<
(
ev: DOMEventTarget<CurrentEvent, This, Element | Node>
) => any
>
: Props[I]
: I extends "ref"
? DOMRefValue<This>
: Props[I];
};
export type JSXProps<T extends VNodeKeyTypes> = T extends Atomico<any, any, any>
? T extends { new (props: infer Props): any }
? Props
: DOMTag<T>
: T extends keyof JSXElements
? JSXElements[T]
: T extends string
? DOMTag<HTMLElement>
: DOMTag<DOMThis<T>>;
export type DOMProps<props> = Partial<Omit<props, DOMEventHandlerKeys<props>>>;
export type AtomicoThisInternal = AtomicoThis & {
_props: { [prop: string]: any };
_ignoreAttr?: string | null;
mount?: () => void;
umount?: () => void;
shadowRoot?: {
adoptedStyleSheets: CSSStyleSheet[];
};
constructor: {
styles: Sheet[];
};
};
export type AtomicoThis<Props = {}, Base = HTMLElement> = Props &
DOMThis<Base> & {
update(): Promise<void>;
updated: Promise<void>;
mounted: Promise<void>;
unmounted: Promise<void>;
readonly symbolId: unique symbol;
};
export interface AtomicoStatic<Props> extends HTMLElement {
styles: Sheets[];
props: SchemaInfer<Props>;
/**
* Meta property, allows associating the component's
* props in typescript to external environments.
*/
readonly "##props": Props;
/**
* Allows to identify a constructor created with Atomico
*/
readonly "##atomico": true;
}
export interface Atomico<Props, PropsForInstance, Base>
extends AtomicoStatic<Props> {
new (
props?: JSXProxy<
DOMTag<DOMThis<Base>, Props>,
AtomicoThis<PropsForInstance, Base>
>
): AtomicoThis<PropsForInstance, Base>;
}
/**
* This type allows retrieving the parameters of a customElement
* to be reflected in the JSX by third party APIs, eg: `@atomico/react`.
*/
export type JSXElement<Base extends FillConstructor> =
Base extends AtomicoStatic<any>
? Base extends { new (props?: infer Props): any }
? Props
: DOMThis<Base>
: DOMThis<Base>;
/**
* Type to create a wrapper to instantiate an element with type validation in JSX
* @example
* ```tsx
* const [ Template ] = useSlot< JSX<{value: number} >>(ref);
*
* <Template value={10}/>
* ```
*/
export interface JSX<Props = {}, Base = HTMLElement> extends Element {
new (
props?: JSXProxy<DOMTag<DOMThis<Base>, Props>, Base>
): PropsNullable<Props> & DOMThis<Base>;
}
export interface AtomicoElement extends CustomElementConstructor {
readonly "##atomico": true;
}