From d52d9d9ad11adbb9d0746e44f6951c56c551e9e4 Mon Sep 17 00:00:00 2001 From: Peter Zingg Date: Sat, 18 Jan 2025 09:08:22 -0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=A4=96=20Merge=20PR=20#71578=20phoenix=5F?= =?UTF-8?q?live=5Fview=201.0,=20metadata=20LiveSocket=20option=20by=20@pzi?= =?UTF-8?q?ngg?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- types/phoenix_live_view/README.md | 24 +- types/phoenix_live_view/hooks.d.ts | 36 ++ types/phoenix_live_view/index.d.ts | 408 +++--------------- types/phoenix_live_view/live_socket.d.ts | 127 ++++++ types/phoenix_live_view/package.json | 36 +- .../phoenix_live_view-tests.ts | 145 ++++--- types/phoenix_live_view/rendered.d.ts | 41 ++ types/phoenix_live_view/socket_options.d.ts | 205 +++++++++ types/phoenix_live_view/tsconfig.json | 37 +- types/phoenix_live_view/upload_entry.d.ts | 60 +++ types/phoenix_live_view/view.d.ts | 138 ++++++ types/phoenix_live_view/view_hook.d.ts | 32 ++ 12 files changed, 832 insertions(+), 457 deletions(-) create mode 100644 types/phoenix_live_view/hooks.d.ts create mode 100644 types/phoenix_live_view/live_socket.d.ts create mode 100644 types/phoenix_live_view/rendered.d.ts create mode 100644 types/phoenix_live_view/socket_options.d.ts create mode 100644 types/phoenix_live_view/upload_entry.d.ts create mode 100644 types/phoenix_live_view/view.d.ts create mode 100644 types/phoenix_live_view/view_hook.d.ts diff --git a/types/phoenix_live_view/README.md b/types/phoenix_live_view/README.md index bd7a418b8e0ef3..3b523349d3088d 100644 --- a/types/phoenix_live_view/README.md +++ b/types/phoenix_live_view/README.md @@ -8,26 +8,33 @@ This package contains the type definitions for the [Phoenix LiveView](https://gi npm install --save-dev @types/phoenix_live_view ``` -## BREAKING CHANGE in 0.20.0 +## BREAKING CHANGE in 0.20 and 1.0 -The `ViewHook` interface has been modified to better reflect the type expected by developers writing their own hooks. It used to correspond to the type of the hook object as it is constructed by the Phoenix LiveView library internally, but it included properties which are not expected for developers to provide. This required casting or commenting to make the typescript compiler happy when declaring hooks. +To preserve the actual namespaces of the phoenix_live_view Javascript modules, we distinguish +three different exports: -The new `ViewHook` interface is now correct and only expects the hook functions that developers are expected to provide given the public API. But it still provides the previous interface as `ViewHookInternal` for those who need to use it. + * `ViewHookInterface` is the interface (contract) for a LiveView hook. + * `ViewHook` is the implemented class in the phoenix_live_view module that implements + `ViewHookInterface` + * `Hook` is the generic interface that uses intersection types to let developers + add additional functionality to an instance of a hook. -Besides, and it correctly assigns the `ViewHookInternal` type to `this` when writing a hook, so properties like `el`, `viewName`, and functions like `push_event` are all there. The `ViewHook` interface can also now be used as a generic for developers who want to assign their own functions and properties to the hook object. +The `Hook` interface only expects the hook functions that developers are expected to provide given the public API. + +`Hook` correctly assigns the `ViewHookInterface` interface to `this` when writing a hook, so properties like `el` and functions like `push_event` are all there. The `Hook` interface can also now be used as a generic for developers who want to assign their own functions and properties to the hook object. ```typescript -const testHook: ViewHook = { +const testHook: Hook = { mounted() { const hook = this; - console.log("TestHook mounted", { element: this.el, viewName: this.viewName }); - hook.pushEvent("hook-mounted", { name: "testHook" }, (reply, ref) => { + console.log("TestHook mounted", { element: this.el }); + hook.pushEvent("hook-mounted", { name: "testHook" }, (reply: object, ref: number) => { console.log(`Got hook-mounted reply ${JSON.stringify(reply)} ref ${ref}`); }); }, }; -const testHookWithExtendedPrototype: ViewHook<{ handleClick: (event: MouseEvent) => void }> = { +const testHookWithExtendedPrototype: Hook<{ handleClick: (event: MouseEvent) => void }> = { mounted() { this.handleClick = (event: MouseEvent) => { console.log("click", event); @@ -46,6 +53,5 @@ const MyHooks: HooksOptions = { }; const liveSocket = new LiveSocket("/live", Socket, opts); - ``` diff --git a/types/phoenix_live_view/hooks.d.ts b/types/phoenix_live_view/hooks.d.ts new file mode 100644 index 00000000000000..e9054597839fc8 --- /dev/null +++ b/types/phoenix_live_view/hooks.d.ts @@ -0,0 +1,36 @@ +import LiveSocket from "./live_socket"; + +export type OnReply = (reply: any, ref: number) => any; +export type CallbackRef = (customEvent: any, bypass: boolean) => string; + +export interface ViewHookInterface { + el: HTMLElement; + liveSocket: LiveSocket; + + mounted?: () => void; + updated?: () => void; + beforeUpdate?: () => void; + destroyed?: () => void; + reconnected?: () => void; + disconnected?: () => void; + + js(): object; + pushEvent(event: string, payload: any, onReply?: OnReply): any; + pushEventTo(phxTarget: any, event: string, payload: object, onReply?: OnReply): any; + handleEvent(event: string, callback: any): CallbackRef; + removeHandleEvent(callbackRef: CallbackRef): void; + upload(name: any, files: any): any; + uploadTo(phxTarget: any, name: any, files: any): any; +} + +export interface Hook { + mounted?: (this: T & ViewHookInterface) => void; + beforeUpdate?: (this: T & ViewHookInterface) => void; + updated?: (this: T & ViewHookInterface) => void; + beforeDestroy?: (this: T & ViewHookInterface) => void; + destroyed?: (this: T & ViewHookInterface) => void; + disconnected?: (this: T & ViewHookInterface) => void; + reconnected?: (this: T & ViewHookInterface) => void; +} + +export type HooksOptions = Record>; diff --git a/types/phoenix_live_view/index.d.ts b/types/phoenix_live_view/index.d.ts index 4d43eb405a7544..e4d8ad94d7026b 100644 --- a/types/phoenix_live_view/index.d.ts +++ b/types/phoenix_live_view/index.d.ts @@ -3,8 +3,12 @@ // Igor Barchenkov // Rodolfo Carvalho // François Roland -// // Changelog: +// Version 1.0 renamed ViewHookInterface and Hook (interfaces) and ViewHook (class). +// Added the EventMetadata interface for the metadata LiveSocket option, and included +// other phoenix Socket and LiveSocket options. Imported other d.ts modules directly +// from tsc-emitted declarations from Javascript in phoenix_live_view version 1.0. +// // Version 0.20 refactored ViewHook interface with generic type and // ViewHookInternal interface // @@ -13,361 +17,59 @@ // // Version 0.15 added options and interfaces for LiveView uploads // See: https://hexdocs.pm/phoenix_live_view/uploads.html +// // Version 0.15.4 added options and interfaces for LiveView uploads // See: https://hexdocs.pm/phoenix_live_view/uploads.html - +// // Version 0.17.0 added LiveSocket.execJS() method for executing JavaScript utility operations on the client // See: https://github.com/phoenixframework/phoenix_live_view/blob/master/CHANGELOG.md#enhancements-17 -import { Socket } from "phoenix"; - -export interface Defaults { - debounce?: number | undefined; - throttle?: number | undefined; -} - -// From morphdom -export interface DomOptions { - getNodeKey?: ((node: Node) => any) | undefined; - onBeforeNodeAdded?: ((node: Node) => Node) | undefined; - onNodeAdded?: ((node: Node) => Node) | undefined; - onBeforeElUpdated?: ((fromEl: HTMLElement, toEl: HTMLElement) => boolean) | undefined; - onElUpdated?: ((el: HTMLElement) => void) | undefined; - onBeforeNodeDiscarded?: ((node: Node) => boolean) | undefined; - onNodeDiscarded?: ((node: Node) => void) | undefined; - onBeforeElChildrenUpdated?: ((fromEl: HTMLElement, toEl: HTMLElement) => boolean) | undefined; - childrenOnly?: boolean | undefined; -} - -export type ViewLogger = (view: View, kind: string, msg: any, obj: any) => void; - -export interface SocketOptions { - longPollFallbackMs?: number | undefined; - bindingPrefix?: string | undefined; - defaults?: Defaults | undefined; - dom?: DomOptions | undefined; - hooks?: HooksOptions | undefined; - loaderTimeout?: number | undefined; - params?: object | undefined; - uploaders?: object | undefined; - viewLogger?: ViewLogger | undefined; -} - -export type BindCallback = ( - e: Event, - event: string, - view: View, - el: HTMLElement, - targetCtx: object, - phxEvent: string, - windowOwner?: string, -) => void; - -export class LiveSocket { - // phxSocket should be the Socket class (LiveSocket will use the constructor) - constructor(url: string, phxSocket: any, opts: SocketOptions); - - // public - connect(): void; - disableDebug(): void; - disableLatencySim(): void; - disableProfiling(): void; - disconnect(callback: any): void; - enableDebug(): void; - enableLatencySim(upperBoundMs: number): void; - enableProfiling(): void; - getLatencySim(): number | null; - getSocket(): Socket; - isDebugEnabled(): boolean; - isProfileEnabled(): boolean; - execJS(el: HTMLElement, encodedJS: string, eventType?: string): void; - - // private - // bind(events: string[], callback: BindCallback): void; - // bindClick(eventName: string, bindingName: string, capture: boolean): void; - // bindClicks(): void; - // bindForms(): void; - // binding(kind: string): string; - // bindNav(): void; - // bindTopLevelEvents(): void; - // blurActiveElement(): void; - // channel(topic: string, params: any): Channel; - // commitPendingLink(linkRef: number): boolean; - // debounce(el: HTMLElement, event: Event, callback: any): void; - // destroyAllViews(): void; - // destroyViewByEl(el: HTMLElement): void; - // dropActiveElement(view: View): void; - // eventMeta(eventName: string, e: Event, targetEl: HTMLElement): object; - // getActiveElement(): Element; - // getBindingPrefix(): string; - // getHookCallbacks(hookName: string): any; - // getHref(): string; - // getRootById(id: string): any; - // getViewByEl(el: HTMLElement): any; - // hasPendingLink(): boolean; - // historyPatch(href: string, linkState: string): void; - // historyRedirect(href: string, linkState: string, flash: string): void; - // isConnected(): boolean; - // isPhxView(el: HTMLElement): boolean; - // isUnloaded(): boolean; - // joinRootView(el: HTMLElement, href: string, flash: string, callback: (view: View, joinCount: number) => void): View; - // joinRootViews(): boolean; - // log(view: View, kind: string, msgCallback: () => [any, any]): void; - // on(event: string, callback: (e: Event) => void): void; - // onChannel(channel: Channel, event: string, cb: (data: any) => void): void; - // owner(childEl: HTMLElement, callback: (view: View) => void): void; - // pushHistoryPatch(href: string, linkState: any, targetEl: HTMLElement): void; - // redirect(to: string, flash: string): void; - // registerNewLocation(newLocation: Location): boolean; - // reloadWithJitter(view: View): void; - // replaceMain(href: string, flash: string, callback?: any, linkRef?: number): void; - // replaceRootHistory(): void; - // restorePreviouslyActiveFocus(): void; - // setActiveElement(target: Element): void; - // setPendingLink(href: string): number; - // silenceEvents(callback: () => void): void; - // time(name: string, func: () => any): any; - // triggerDOM(kind: string, args: any): void; - // withinOwners(childEl: HTMLElement, callback: (view: View, el: HTMLElement) => void): void; - // withPageLoading(info: Event, callback: any): any; - // wrapPush(view: View, opts: any, push: () => any): any; -} - -export class Rendered { - constructor(viewId: string, rendered: any); - - // public - componentCIDs(diff: any): number[]; - componentToString(cid: number): string; - doRecursiveMerge(target: any, source: any): void; - getComponent(diff: any, cid: number): any; - isComponentOnlyDiff(diff: any): boolean; - mergeDiff(diff: any): void; - parentViewId(): string; - pruneCIDs(cids: number[]): any; - recursiveMerge(target: object, source: object): void; - recursiveToString(rendered: any, components: any, onlyCids?: number[]): string; - toString(onlyCids?: number[]): string; - - // private - // comprehensionToBuffer(rendered: any, output: any): void; - // createSpan(text: string, cid: number): HTMLSpanElement; - // dynamicToBuffer(rendered: any, output: any): void; - // get(): any; - // isNewFingerprint(diff: object): boolean; - // recursiveCIDToString(components: any, cid: string, onlyCids?: number[]): any; - // toOutputBuffer(rendered: any, output: object): any; -} - -export interface ViewHookInternal { - el: HTMLElement; - viewName: string; - pushEvent(event: string, payload: object, onReply?: (reply: any, ref: number) => any): void; - pushEventTo( - selectorOrTarget: any, - event: string, - payload: object, - onReply?: (reply: any, ref: number) => any, - ): void; - handleEvent(event: string, callback: (payload: object) => void): void; - - // callbacks - mounted?: () => void; - beforeUpdate?: () => void; - updated?: () => void; - beforeDestroy?: () => void; - destroyed?: () => void; - disconnected?: () => void; - reconnected?: () => void; -} - -export interface ViewHook { - mounted?: (this: T & ViewHookInternal) => void; - beforeUpdate?: (this: T & ViewHookInternal) => void; - updated?: (this: T & ViewHookInternal) => void; - beforeDestroy?: (this: T & ViewHookInternal) => void; - destroyed?: (this: T & ViewHookInternal) => void; - disconnected?: (this: T & ViewHookInternal) => void; - reconnected?: (this: T & ViewHookInternal) => void; -} - -export type HooksOptions = Record>; - -export class View { - constructor(el: HTMLElement, liveSocket: LiveSocket, parentView: View, href: string, flash: string); - - // public? - ackJoin(child: View): void; - addHook(el: HTMLElement): void; - applyDiff(type: string, rawDiff: any, callback: any): any; - applyJoinPatch(live_patch: any, html: string, events: Array<[string, object]>): void; - applyPendingUpdates(): void; - attachTrueDocEl(): void; - bindChannel(): void; - binding(kind: string): any; - cancelSubmit(formEl: HTMLElement): void; - closestComponentID(targetCtx: object | null): number | null; - componentID(el: HTMLElement): number | null; - componentPatch(diff: any, cid: number): boolean; - connectParams(): object; - destroy(callback?: () => void): void; - destroyAllChildren(): void; - destroyDescendent(id: string): any; - destroyHook(hook: ViewHook): void; - dispatchEvents(events: Array<[string, object]>): void; - displayError(): void; - dropPendingRefs(): void; - expandURL(to: string): string; - extractMeta(el: HTMLElement, meta: object): object; - formsForRecovery(html: string): HTMLElement[]; - getChildById(id: string): any; - getDescendentByEl(el: HTMLElement): any; - getHook(el: HTMLElement): ViewHook; - getScheduledSubmit(formEl: HTMLElement): any; - getSession(): any; - getStatic(): string | null; - hideLoader(): void; - isConnected(): boolean; - isDestroyed(): boolean; - isJoinPending(): boolean; - isLoading(): boolean; - isMain(): boolean; - join(callback: any): void; - joinChild(el: HTMLElement): any; - joinNewChildren(): void; - log(kind: string, msgCallback: any): void; - maybePushComponentsDestroyed(destroyedCIDs: number[]): any; - name(): string; - onAllChildJoinsComplete(): void; - onChannel(event: string, cb: (resp: any) => void): void; - onClose(reason: string): void; - onError(reason: string): void; - onJoin(resp: object): void; - onJoinComplete(resp: object, html: string, events: Array<[string, object]>): void; - onJoinError(resp: object): void; - onLivePatch(redir: object): void; - onLiveRedirect(redir: object): void; - onRedirect(redir: object): void; - ownsElement(el: HTMLElement): boolean; - performPatch(patch: any, pruneCids: boolean): boolean; - pushEvent(type: string, el: HTMLElement, targetCtx: object | null, phxEvent: string, meta: object): void; - pushFileProgress(fileEl: HTMLElement, entryRef: string, progress: number, onReply?: () => void): void; - pushFormRecovery(form: HTMLElement, callback: any): void; - pushFormSubmit(formEl: HTMLElement, targetCtx: object | null, phxEvent: string, onReply: any): void; - pushHookEvent(targetCtx: object | null, event: string, payload: object, onReply: any): void; - pushInput(inputEl: HTMLElement, targetCtx: object | null, phxEvent: string, callback: any): void; - pushKey(keyElement: HTMLElement, targetCtx: object | null, kind: string, phxEvent: string, meta: object): void; - pushLinkPatch(href: string, targetEl: HTMLElement, callback: any): void; - pushWithReply(refGenerator: any, event: string, payload: object, onReply?: () => void): any; - putRef(elements: HTMLElement[], event: string): [number, HTMLElement[]]; - renderContainer(diff: any, kind: string): string; - scheduleSubmit(formEl: HTMLElement, ref: number, callback: any): boolean; - setContainerClasses(...classes: string[]): void; - showLoader(timeout?: number): void; - submitForm(form: HTMLElement, targetCtx: object | null, phxEvent: string): void; - targetComponentID(target: HTMLElement, targetCtx?: object): number | null; - triggerAwaitingSubmit(formEl: HTMLElement): void; - triggerBeforeUpdateHook(fromEl: HTMLElement, toEl: HTMLElement): any; - triggerReconnected(): void; - undoRefs(ref: number): void; - update(diff: any, events: Array<[string, object]>): void; - uploadFiles(formEl: HTMLElement, targetCtx: object | null, ref: number, cid: number, onComplete: any): void; - withinTargets(phxTarget: string, callback: any): void; -} - -export interface LiveViewFile extends File { - _phxRef?: string | undefined; -} - -export class UploadEntry { - constructor(fileEl: HTMLInputElement, file: LiveViewFile, view: View); - - fileEl: HTMLInputElement; - file: LiveViewFile; - view: View; - meta: object | null; - metadata: () => object | null; - progress: (progress: number) => void; - cancel: () => void; - isDone: () => boolean; - error: (reason: string) => void; -} - -export interface LiveViewUploaderMeta { - path: string; - ref: string; - name: string; - type: string; - size: number; - last_modified?: number | undefined; -} - -export function debug(view: View, kind: string, msg: object, obj: object): void; - -export namespace Browser { - function canPushState(): boolean; - function dropLocal(namespace: string, subkey: string): any; - function fetchPage(href: string, callback: (status: number, resp?: string) => any): any; - function getCookie(name: string): string; - function getHashTargetEl(maybeHash: any): HTMLElement | null; - function getLocal(namespace: string, subkey: string): any; - function localKey(namespace: string, subkey: string): string; - function pushState(kind: string, meta: object, to: string): void; - function redirect(toURL: string, flash: string): void; - function setCookie(name: string, value: string): void; - function updateCurrentState(callback: any): void; - function updateLocal(namespace: string, subkey: string, initial: any, func: (current: any) => any): any; -} - -export namespace DOM { - function all(node: Node, query: string, callback: (el: HTMLElement) => HTMLElement): HTMLElement[]; - // eslint-disable-next-line @typescript-eslint/no-invalid-void-type - function byId(id: string): HTMLElement | void; - function childNodeLength(html: string): number; - function cleanChildNodes(container: Node, phxUpdate: string): void; - function cloneNode(node: Node, html: string): Node; - function copyPrivates(target: HTMLElement, source: HTMLElement): void; - function debounce( - el: HTMLElement, - event: Event, - phxDebounce: string, - defaultDebounce: string | null, - phxThrottle: string, - defaultThrottle: string | null, - callback: () => any, - ): any; - function deletePrivate(el: HTMLElement, key: string): void; - function discardError(container: Node, el: HTMLElement, phxFeedbackFor: string): void; - function dispatchEvent(target: Node, eventString: string, detail?: object): void; - function filterWithinSameLiveView(nodes: Node[], parent: any): any; - function findComponentNode(node: Node, cid: number): HTMLElement[]; - function findParentCIDs(node: Node, cids: number[]): Set; - function findPhxChildren(el: HTMLElement, parentId: string): HTMLElement[]; - function findPhxChildrenInFragment(html: string, parentId: string): HTMLElement[]; - // eslint-disable-next-line @typescript-eslint/no-invalid-void-type - function findUploadInputs(node: Node): void | any[]; - function hasSelectionRange(el: HTMLElement): boolean; - function incCycle(el: HTMLElement, key: string, trigger?: any): number; - function isFormInput(el: HTMLElement): boolean; - function isIgnored(el: HTMLElement, phxUpdate: string): boolean; - function isNowTriggerFormExternal(el: HTMLElement, phxTriggerExternal: string): boolean; - function isPhxChild(el: Node): boolean; - function isPhxDestroyed(node: Node): boolean; - function isPhxUpdate(el: Node, phxUpdate: string, updateTypes: string[]): boolean; - function isTextualInput(el: HTMLElement): boolean; - function isUploadInput(el: HTMLElement): boolean; - function markPhxChildDestroyed(el: HTMLElement): void; - function mergeAttrs(target: HTMLElement, source: HTMLElement, exclude?: string[]): void; - function mergeFocusedInput(target: HTMLElement, source: HTMLElement): void; - function once(el: HTMLElement, key: string): boolean; - function private(el: HTMLElement, key: string): any; - function putPrivate(el: HTMLElement, key: string, value: any): void; - function putTitle(str: string): void; - function removeClass(el: HTMLElement, className: string): void; - function restoreFocus(focused: HTMLElement, selectionStart: number, selectionEnd: number): void; - function showError(inputEl: HTMLElement, phxFeedbackFor: string): void; - function syncAttrsToProps(el: HTMLElement): void; - function syncPendingRef(fromEl: HTMLElement, toEl: HTMLElement, disableWith: string): boolean; - function triggerCycle(el: HTMLElement, key: string, currentCycle?: number): void; - function withinSameLiveView(node: Node, parent: Node): boolean; +import { Hook, HooksOptions, ViewHookInterface } from "./hooks"; +import LiveSocket, { isUsedInput } from "./live_socket"; +import SocketOptions, { Defaults, DomOptions, EventMetadata } from "./socket_options"; +import UploadEntry, { Uploader, UploadersOptions } from "./upload_entry"; +import ViewHook from "./view_hook"; + +/** Creates a ViewHook instance for the given element and callbacks. + * + * @param {HTMLElement} el - The element to associate with the hook. + * @param {object} [callbacks] - The list of hook callbacks, such as mounted, + * updated, destroyed, etc. + * + * @example + * + * class MyComponent extends HTMLElement { + * connectedCallback(){ + * let onLiveViewMounted = () => this.hook.pushEvent(...)) + * this.hook = createHook(this, {mounted: onLiveViewMounted}) + * } + * } + * + * *Note*: `createHook` must be called from the `connectedCallback` lifecycle + * which is triggered after the element has been added to the DOM. If you try + * to call `createHook` from the constructor, an error will be logged. + * + * @returns {ViewHook} Returns the ViewHook instance for the custom element. + */ +export function createHook(el: HTMLElement, callbacks?: object): ViewHook; + +export { + Defaults, + DomOptions, + EventMetadata, + Hook, + HooksOptions, + isUsedInput, + LiveSocket, + SocketOptions, + UploadEntry, + Uploader, + UploadersOptions, + ViewHookInterface, +}; + +declare global { + interface Window { + liveSocket?: LiveSocket; + } } diff --git a/types/phoenix_live_view/live_socket.d.ts b/types/phoenix_live_view/live_socket.d.ts new file mode 100644 index 00000000000000..4467de0bc85335 --- /dev/null +++ b/types/phoenix_live_view/live_socket.d.ts @@ -0,0 +1,127 @@ +import SocketOptions from "./socket_options"; + +export function isUsedInput(el: any): any; +export default class LiveSocket { + constructor(url: any, phxSocket: any, opts: Partial); + unloaded: boolean; + socket: any; + bindingPrefix: any; + opts: any; + params: any; + viewLogger: any; + metadataCallbacks: any; + defaults: any; + activeElement: any; + prevActive: Element | null; + silenced: boolean; + main: View | null; + outgoingMainEl: any; + clickStartedAtTarget: any; + linkRef: number; + roots: any; + href: string; + pendingLink: any; + currentLocation: any; + hooks: any; + uploaders: any; + loaderTimeout: any; + reloadWithJitterTimer: number | null; + maxReloads: any; + reloadJitterMin: any; + reloadJitterMax: any; + failsafeJitter: any; + localStorage: any; + sessionStorage: any; + boundTopLevelEvents: boolean; + boundEventNames: Set; + serverCloseRef: any; + domCallbacks: any; + transitions: TransitionSet; + currentHistoryPosition: number; + version(): any; + isProfileEnabled(): boolean; + isDebugEnabled(): boolean; + isDebugDisabled(): boolean; + enableDebug(): void; + enableProfiling(): void; + disableDebug(): void; + disableProfiling(): void; + enableLatencySim(upperBoundMs: any): void; + disableLatencySim(): void; + getLatencySim(): number | null; + getSocket(): any; + connect(): void; + disconnect(callback: any): void; + replaceTransport(transport: any): void; + execJS(el: HTMLElement, encodedJS: any, eventType?: string): void; // DT + execJSHookPush(el: HTMLElement, phxEvent: any, data: any, callback: any): void; + unload(): void; + triggerDOM(kind: any, args: any): void; + time(name: any, func: any): any; + log(view: any, kind: any, msgCallback: any): void; + requestDOMUpdate(callback: any): void; + transition(time: any, onStart: any, onDone?: () => void): void; + onChannel(channel: any, event: any, cb: any): void; + reloadWithJitter(view: any, log: any): void; + getHookCallbacks(name: any): any; + isUnloaded(): boolean; + isConnected(): any; + getBindingPrefix(): any; + binding(kind: any): string; + channel(topic: any, params: any): any; + joinDeadView(): void; + joinRootViews(): boolean; + redirect(to: any, flash: any, reloadToken: any): void; + replaceMain(href: any, flash: any, callback?: null, linkRef?: number): void; + transitionRemoves(elements: any, skipSticky: any, callback: any): void; + isPhxView(el: any): any; + newRootView(el: any, flash: any, liveReferer: any): View; + owner(childEl: any, callback: any): any; + withinOwners(childEl: any, callback: any): void; + getViewByEl(el: any): any; + getRootById(id: any): any; + destroyAllViews(): void; + destroyViewByEl(el: any): void; + getActiveElement(): Element | null; + dropActiveElement(view: any): void; + restorePreviouslyActiveFocus(): void; + blurActiveElement(): void; + bindTopLevelEvents(dead?: object): void; // DT + eventMeta(eventName: any, e: any, targetEl: any): any; + setPendingLink(href: any): number; + resetReloadStatus(): void; + commitPendingLink(linkRef: any): boolean; + getHref(): string; + hasPendingLink(): boolean; + bind(events: any, callback: any): void; + bindClicks(): void; + bindClick(eventName: any, bindingName: any): void; + dispatchClickAway(e: any, clickStartedAt: any): void; + bindNav(): void; + maybeScroll(scroll: any): void; + dispatchEvent(event: any, payload?: {}): void; + dispatchEvents(events: any): void; + withPageLoading(info: any, callback: any): any; + pushHistoryPatch(e: any, href: any, linkState: any, targetEl: any): void; + historyPatch(href: any, linkState: any, linkRef?: number): void; + historyRedirect(e: any, href: any, linkState: any, flash: any, targetEl: any): void; + replaceRootHistory(): void; + registerNewLocation(newLocation: any): boolean; + bindForms(): void; + debounce(el: any, event: any, eventType: any, callback: any): any; + silenceEvents(callback: any): void; + on(event: any, callback: any): void; + jsQuerySelectorAll(sourceEl: any, query: any, defaultQuery: any): any; +} +import View from "./view"; +declare class TransitionSet { + transitions: Set; + pendingOps: any[]; + reset(): void; + after(callback: any): void; + addTransition(time: any, onStart: any, onDone: any): void; + pushPendingOp(op: any): void; + size(): number; + flushPendingOps(): void; +} +export {}; diff --git a/types/phoenix_live_view/package.json b/types/phoenix_live_view/package.json index 2bce4cb53ea8b8..38c99f726801ce 100644 --- a/types/phoenix_live_view/package.json +++ b/types/phoenix_live_view/package.json @@ -1,20 +1,20 @@ { - "private": true, - "name": "@types/phoenix_live_view", - "version": "0.20.9999", - "projects": [ - "https://github.com/phoenixframework/phoenix_live_view" - ], - "dependencies": { - "@types/phoenix": "*" - }, - "devDependencies": { - "@types/phoenix_live_view": "workspace:." - }, - "owners": [ - { - "name": "Peter Zingg", - "githubUsername": "pzingg" - } - ] + "private": true, + "name": "@types/phoenix_live_view", + "version": "1.0.9999", + "projects": [ + "https://github.com/phoenixframework/phoenix_live_view" + ], + "dependencies": { + "@types/phoenix": "*" + }, + "devDependencies": { + "@types/phoenix_live_view": "workspace:." + }, + "owners": [ + { + "name": "Peter Zingg", + "githubUsername": "pzingg" + } + ] } diff --git a/types/phoenix_live_view/phoenix_live_view-tests.ts b/types/phoenix_live_view/phoenix_live_view-tests.ts index 46fd1847caa414..65baa9584c4177 100644 --- a/types/phoenix_live_view/phoenix_live_view-tests.ts +++ b/types/phoenix_live_view/phoenix_live_view-tests.ts @@ -1,63 +1,90 @@ import { Socket } from "phoenix"; -import { HooksOptions, LiveSocket, SocketOptions, UploadEntry, ViewHook } from "phoenix_live_view"; +import { DomOptions, EventMetadata, Hook, LiveSocket, SocketOptions, UploadEntry, Uploader } from "phoenix_live_view"; function test_socket() { - // Hooks - const testHook: ViewHook = { - mounted() { - const hook = this; - console.log("TestHook mounted", { element: this.el, viewName: this.viewName }); - hook.pushEvent("hook-mounted", { name: "testHook" }, (reply, ref) => { - console.log(`Got hook-mounted reply ${JSON.stringify(reply)} ref ${ref}`); - }); - }, - }; - - const testHookWithExtendedPrototype: ViewHook<{ handleClick: (event: MouseEvent) => void }> = { - mounted() { - this.handleClick = (event: MouseEvent) => { - console.log("click", event); - this.pushEvent("click", { x: event.clientX, y: event.clientY }); - }; - document.addEventListener("click", this.handleClick); - }, - destroyed() { - document.removeEventListener("click", this.handleClick); - }, - }; - - // Uploaders - function testUploader(entries: UploadEntry[], _onViewError: any) { - entries.forEach((entry) => { - console.log(`file: ${entry.file.name}`); - console.log(`meta: ${JSON.stringify(entry.meta)}`); - }); - } - - const MyHooks: HooksOptions = { - test: testHook, - testWithExtendedPrototype: testHookWithExtendedPrototype, - }; - - const MyUploaders = { - test: testUploader, - }; - - const opts: SocketOptions = { - params: { - _csrf_token: "1234", - }, - hooks: MyHooks, - uploaders: MyUploaders, - }; - - const liveSocket = new LiveSocket("/live", Socket, opts); - liveSocket.enableDebug(); - liveSocket.enableProfiling(); - liveSocket.connect(); - - const element = "dummyElement" as unknown as HTMLElement; - // $ExpectType void - liveSocket.execJS(element, "[[\"patch\",{\"href\":\"/\",\"replace\":false}]]"); - liveSocket.execJS(element, "[[\"navigate\",{\"href\":\"/\",\"replace\":false}]]", "submit"); + // Hook + const testHook: Hook = { + mounted() { + const hook = this; + console.log("TestHook mounted", { element: this.el }); + hook.pushEvent("hook-mounted", { name: "testHook" }, (reply: any, ref: number) => { + console.log(`Got hook-mounted reply ${JSON.stringify(reply)} ref ${ref}`); + }); + }, + }; + + // Hook with click handler + const testHookWithExtendedPrototype: Hook<{ handleClick: (event: MouseEvent) => void }> = { + mounted() { + this.handleClick = (event: MouseEvent) => { + console.log("click", event); + this.pushEvent("click", { x: event.clientX, y: event.clientY }); + }; + document.addEventListener("click", this.handleClick); + }, + destroyed() { + document.removeEventListener("click", this.handleClick); + }, + }; + + // Uploaders + const testUploader: Uploader = (entries: UploadEntry[], onViewError: (cb: () => void) => void) => { + onViewError(() => console.log("uploader view error")); + + entries.forEach((entry) => { + console.log(`file: ${entry.file.name}`); + console.log(`meta: ${JSON.stringify(entry.meta)}`); + }); + }; + + // Metadata + const metadata: Partial = { + click: (e: PointerEvent, el: HTMLElement) => { + return { + ctrlKey: e.ctrlKey, + metaKey: e.metaKey, + detail: e.detail || 1, + }; + }, + }; + + // DOM + const dom: Partial = { + onBeforeElUpdated(from: HTMLElement, to: HTMLElement) { + for (const attr of from.attributes) { + if (attr.name.startsWith("data-js-")) { + to.setAttribute(attr.name, attr.value); + } + } + }, + }; + + const opts: Partial = { + longPollFallbackMs: 2500, + reconnectAfterMs: (tries: number) => tries * tries, + params: { + _csrf_token: "1234", + }, + hooks: { + test: testHook, + testWithExtendedPrototype: testHookWithExtendedPrototype, + }, + uploaders: { + test: testUploader, + }, + metadata: metadata, + dom: dom, + }; + + const liveSocket = new LiveSocket("/live", Socket, opts); + window.liveSocket = liveSocket; + + liveSocket.enableDebug(); + liveSocket.enableProfiling(); + liveSocket.connect(); + + const element = "dummyElement" as unknown as HTMLElement; + // $ExpectType void + liveSocket.execJS(element, "[[\"patch\",{\"href\":\"/\",\"replace\":false}]]"); + liveSocket.execJS(element, "[[\"navigate\",{\"href\":\"/\",\"replace\":false}]]", "submit"); } diff --git a/types/phoenix_live_view/rendered.d.ts b/types/phoenix_live_view/rendered.d.ts new file mode 100644 index 00000000000000..4ab82a100297e6 --- /dev/null +++ b/types/phoenix_live_view/rendered.d.ts @@ -0,0 +1,41 @@ +export function modifyRoot(html: any, attrs: any, clearInnerHTML: any): any[]; +export default class Rendered { + static extract(diff: any): { + diff: any; + title: any; + reply: any; + events: any; + }; + constructor(viewId: any, rendered: any); + viewId: any; + rendered: {}; + magicId: number; + parentViewId(): any; + toString(onlyCids: any): (string | Set)[]; + recursiveToString( + rendered: any, + components: any, + onlyCids: any, + changeTracking: any, + rootAttrs: any, + ): (string | Set)[]; + componentCIDs(diff: any): number[]; + isComponentOnlyDiff(diff: any): boolean; + getComponent(diff: any, cid: any): any; + resetRender(cid: any): void; + mergeDiff(diff: any): void; + cachedFindComponent(cid: any, cdiff: any, oldc: any, newc: any, cache: any): any; + mutableMerge(target: any, source: any): any; + doMutableMerge(target: any, source: any): void; + cloneMerge(target: any, source: any, pruneMagicId: any): any; + componentToString(cid: any): any[]; + pruneCIDs(cids: any): void; + get(): {}; + isNewFingerprint(diff?: {}): boolean; + templateStatic(part: any, templates: any): any; + nextMagicID(): string; + toOutputBuffer(rendered: any, templates: any, output: any, changeTracking: any, rootAttrs?: {}): void; + comprehensionToBuffer(rendered: any, templates: any, output: any): void; + dynamicToBuffer(rendered: any, templates: any, output: any, changeTracking: any): void; + recursiveCIDToString(components: any, cid: any, onlyCids: any): (string | Set)[]; +} diff --git a/types/phoenix_live_view/socket_options.d.ts b/types/phoenix_live_view/socket_options.d.ts new file mode 100644 index 00000000000000..f3f9b0a21ddc3d --- /dev/null +++ b/types/phoenix_live_view/socket_options.d.ts @@ -0,0 +1,205 @@ +import { HooksOptions } from "./hooks"; +import { UploadersOptions } from "./upload_entry"; +import View from "./view"; + +export interface Defaults { + debounce: number; + throttle: number; +} + +export interface EventMetadata { + click: (e: PointerEvent, el: HTMLElement) => any; + keyup: (e: KeyboardEvent, el: HTMLElement) => any; + keydown: (e: KeyboardEvent, el: HTMLElement) => any; + blur: (e: FocusEvent, el: HTMLElement) => any; + focus: (e: FocusEvent, el: HTMLElement) => any; + focusout: (e: FocusEvent, el: HTMLElement) => any; + focusin: (e: FocusEvent, el: HTMLElement) => any; +} + +export interface DomOptions { + jsQuerySelectorAll: (sourceEl: HTMLElement, query: any, defaultQuery: () => Node[]) => Node[]; + onPatchStart: (container: Node) => void; + onPatchEnd: (container: Node) => void; + onNodeAdded: (node: Node) => void; + onBeforeElUpdated: (fromEl: HTMLElement, toEl: HTMLElement) => void; +} + +export default interface SocketOptions { + // Phoenix Socket options + /* + * @param {Function} [opts.transport] - The Websocket Transport, for example WebSocket or Phoenix.LongPoll. + * + * Defaults to WebSocket with automatic LongPoll fallback if WebSocket is not defined. + * To fallback to LongPoll when WebSocket attempts fail, use `longPollFallbackMs: 2500`. + */ + transport: new(endpoint: string) => object; + /* + * @param {Function} [opts.longPollFallbackMs] - The millisecond time to attempt the primary transport + * before falling back to the LongPoll transport. Disabled by default. + */ + longPollFallbackMs: number; + /* + * @param {Function} [opts.debug] - When true, enables debug logging. Default false. + */ + debug: boolean; + /* + * @param {Function} [opts.encode] - The function to encode outgoing messages. + * Defaults to JSON encoder. + */ + encode: (payload: object, callback: (encoded: any) => any) => any; + /* + * @param {Function} [opts.decode] - The function to decode incoming messages. + * Defaults to JSON: + * + * ```javascript + * (payload, callback) => callback(JSON.parse(payload)) + * ``` + */ + decode: (payload: string, callback: (decoded: any) => any) => any; + /* + * @param {number} [opts.timeout] - The default timeout in milliseconds to trigger push timeouts. + * Defaults to `DEFAULT_TIMEOUT` + */ + timeout: number; + /* + * @param {number} [opts.heartbeatIntervalMs] - The millisec interval to send a heartbeat message + */ + heartbeatIntervalMs: number; + /* + * @param {number} [opts.reconnectAfterMs] - The optional function that returns the millisec + * socket reconnect interval. Defaults to stepped backoff of: + * + * ```javascript + * function(tries){ + * return [10, 50, 100, 150, 200, 250, 500, 1000, 2000][tries - 1] || 5000 + * } + * ```` + */ + reconnectAfterMs: (tries: number) => number; + /* + * @param {number} [opts.rejoinAfterMs] - The optional function that returns the millisec + * rejoin interval for individual channels. + * + * ```javascript + * function(tries){ + * return [1000, 2000, 5000][tries - 1] || 10000 + * } + * ```` + */ + rejoinAfterMs: (tries: number) => number; + /* + * @param {Function} [opts.logger] - The optional function for specialized logging, ie: + * + * ```javascript + * function(kind, msg, data) { + * console.log(`${kind}: ${msg}`, data) + * } + * ``` + */ + logger: (kind: string, msg: any, data: any) => void; + /* + * @param {number} [opts.longpollerTimeout] - The maximum timeout of a long poll AJAX request. + * Defaults to 20s (double the server long poll timer). + */ + longpollerTimeout: number; + /* + * @param {string} [opts.binaryType] - The binary type to use for binary WebSocket frames. + * Defaults to "arraybuffer" + */ + binaryType: "arraybuffer" | "blob"; + /* + * @param {vsn} [opts.vsn] - The serializer's protocol version to send on connect. + * Defaults to DEFAULT_VSN. + */ + vsn: string; + // LiveSocket - specific options + /* + * @param {object} [opts.defaults] - The optional defaults to use for various bindings, + * such as `phx-debounce`. Supports the following keys: + * + * - debounce - the millisecond phx-debounce time. Defaults 300 + * - throttle - the millisecond phx-throttle time. Defaults 300 + */ + defaults: Partial; + /* + * @param {Function} [opts.params] - The optional function for passing connect params. + * The function receives the element associated with a given LiveView. For example: + * + * (el) => {view: el.getAttribute("data-my-view-name", token: window.myToken} + */ + params: object | ((el: HTMLElement) => object); + /* + * @param {string} [opts.bindingPrefix] - The optional prefix to use for all phx DOM annotations. + * Defaults to "phx-". + */ + bindingPrefix: string; + /* + * @param {object} [opts.hooks] - The optional object for referencing LiveView hook callbacks. + */ + hooks: HooksOptions; + /* + * @param {object} [opts.uploaders] - The optional object for referencing LiveView uploader callbacks. + */ + uploaders: UploadersOptions; + /* + * @param {integer} [opts.loaderTimeout] - The optional delay in milliseconds to wait before apply + * loading states. + */ + loaderTimeout: number; + /* + * @param {integer} [opts.maxReloads] - The maximum reloads before entering failsafe mode. + */ + maxReloads: number; + /* + * @param {integer} [opts.reloadJitterMin] - The minimum time between normal reload attempts. + */ + reloadJitterMin: number; + /* + * @param {integer} [opts.reloadJitterMax] - The maximum time between normal reload attempts. + */ + reloadJitterMax: number; + /* + * @param {integer} [opts.failsafeJitter] - The time between reload attempts in failsafe mode. + */ + failsafeJitter: number; + /* + * @param {Function} [opts.viewLogger] - The optional function to log debug information. For example: + * + * (view, kind, msg, obj) => console.log(`${view.id} ${kind}: ${msg} - `, obj) + */ + viewLogger: (view: View, kind: string, msg: any, obj: any) => void; + /* + * @param {object} [opts.metadata] - The optional object mapping event names to functions for + * populating event metadata. For example: + * + * metadata: { + * click: (e, el) => { + * return { + * ctrlKey: e.ctrlKey, + * metaKey: e.metaKey, + * detail: e.detail || 1, + * } + * } + * } + */ + metadata: Partial; + /* + * @param {object} [opts.sessionStorage] - An optional Storage compatible object + * Useful when LiveView won't have access to `sessionStorage`. For example, This could + * happen if a site loads a cross-domain LiveView in an iframe. + */ + sessionStorage: object; + /* + * @param {object} [opts.localStorage] - An optional Storage compatible object + * Useful for when LiveView won't have access to `localStorage`. + * See `opts.sessionStorage` for examples. + */ + localStorage: object; + /* + * @param {object} [opts.dom] - For integration with client-side libraries which + * require a broader access to full DOM management, the LiveSocket constructor + * accepts a `dom` option with an `onBeforeElUpdated` callback. + */ + dom: Partial; +} diff --git a/types/phoenix_live_view/tsconfig.json b/types/phoenix_live_view/tsconfig.json index 3ce2b246fdbbd4..34371c46aacfa9 100644 --- a/types/phoenix_live_view/tsconfig.json +++ b/types/phoenix_live_view/tsconfig.json @@ -1,20 +1,21 @@ { - "compilerOptions": { - "module": "node16", - "lib": [ - "dom", - "es6" - ], - "noImplicitAny": true, - "noImplicitThis": true, - "strictFunctionTypes": true, - "strictNullChecks": true, - "types": [], - "noEmit": true, - "forceConsistentCasingInFileNames": true - }, - "files": [ - "index.d.ts", - "phoenix_live_view-tests.ts" - ] + "compilerOptions": { + "module": "node16", + "lib": [ + "dom", + "dom.iterable", + "es6" + ], + "noImplicitAny": true, + "noImplicitThis": true, + "strictFunctionTypes": true, + "strictNullChecks": true, + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true + }, + "files": [ + "index.d.ts", + "phoenix_live_view-tests.ts" + ] } diff --git a/types/phoenix_live_view/upload_entry.d.ts b/types/phoenix_live_view/upload_entry.d.ts new file mode 100644 index 00000000000000..6c7158de6d5f59 --- /dev/null +++ b/types/phoenix_live_view/upload_entry.d.ts @@ -0,0 +1,60 @@ +import LiveSocket from "./live_socket"; +import View from "./view"; + +export interface LiveViewFile extends File { + meta?: () => object; + _phxRef?: string; +} + +export type Uploader = ( + entries: UploadEntry[], + onError: (cb: () => void) => void, + resp: any, + liveSocket: LiveSocket, +) => any; + +export type UploadersOptions = Record; + +export default class UploadEntry { + static isActive(fileEl: HTMLInputElement, file: LiveViewFile): boolean; + static isPreflighted(fileEl: HTMLInputElement, file: LiveViewFile): boolean; + static isPreflightInProgress(file: LiveViewFile): boolean; + static markPreflightInProgress(file: LiveViewFile): void; + constructor(fileEl: HTMLInputElement, file: LiveViewFile, view: View, autoUpload: any); + + ref: string; // DT + fileEl: HTMLInputElement; // DT + file: LiveViewFile; // DT + view: View; // DT + meta: any; + _isCancelled: boolean; + _isDone: boolean; + _progress: number; + _lastProgressSent: number; + _onDone: () => void; + _onElUpdated: () => void; + autoUpload: any; + metadata(): any; + progress(progress: number): void; // DT + isCancelled(): boolean; + cancel(): void; + isDone(): boolean; + error(reason?: string): void; + isAutoUpload(): any; + onDone(callback: any): void; + onElUpdated(): void; + toPreflightPayload(): { + last_modified: number; + name: string; + relative_path: string; + size: number; + type: string; + ref: string; + meta: any; + }; + uploader(uploaders: UploadersOptions): { + name: string; + callback: Uploader; + }; + zipPostFlight(resp: any): void; +} diff --git a/types/phoenix_live_view/view.d.ts b/types/phoenix_live_view/view.d.ts new file mode 100644 index 00000000000000..f34c65fe39473d --- /dev/null +++ b/types/phoenix_live_view/view.d.ts @@ -0,0 +1,138 @@ +import Rendered from "./rendered"; + +export function prependFormDataKey(key: any, prefix: any): any; +export default class View { + static closestView(el: any): any; + constructor(el: any, liveSocket: any, parentView: any, flash: any, liveReferer: any); + isDead: boolean; + liveSocket: any; + flash: any; + parent: any; + root: any; + el: any; + id: any; + ref: number; + lastAckRef: any; + childJoins: number; + loaderTimer: number | null; + pendingDiffs: any[]; + pendingForms: Set; + redirect: boolean; + href: any; + joinCount: number; + joinAttempts: number; + joinPending: boolean; + destroyed: boolean; + joinCallback: (onDone: any) => void; + stopCallback: () => void; + pendingJoinOps: never[] | null; + viewHooks: {}; + formSubmits: any[]; + children: {} | null; + formsForRecovery: {}; + channel: any; + setHref(href: any): void; + setRedirect(href: any): void; + isMain(): any; + connectParams(liveReferer: any): any; + isConnected(): any; + getSession(): any; + getStatic(): any; + destroy(callback?: () => void): void; + setContainerClasses(...classes: any[]): void; + showLoader(timeout: any): void; + execAll(binding: any): void; + hideLoader(): void; + triggerReconnected(): void; + log(kind: any, msgCallback: any): void; + transition(time: any, onStart: any, onDone?: () => void): void; + withinTargets(phxTarget: any, callback: any, dom: Document | undefined, viewEl: any): any; + applyDiff(type: any, rawDiff: any, callback: any): void; + onJoin(resp: any): void; + rendered: Rendered | undefined; + dropPendingRefs(): void; + onJoinComplete( + { live_patch }: { + live_patch: any; + }, + html: any, + streams: any, + events: any, + ): void; + attachTrueDocEl(): void; + execNewMounted(parent?: any): void; + applyJoinPatch(live_patch: any, html: any, streams: any, events: any): void; + triggerBeforeUpdateHook(fromEl: any, toEl: any): any; + maybeMounted(el: any): void; + maybeAddNewHook(el: any): void; + performPatch(patch: any, pruneCids: any, isJoinPatch?: boolean): boolean; + afterElementsRemoved(elements: any, pruneCids: any): void; + joinNewChildren(): void; + maybeRecoverForms(html: any, callback: any): any; + getChildById(id: any): any; + getDescendentByEl(el: any): any; + destroyDescendent(id: any): any; + joinChild(el: any): true | undefined; + isJoinPending(): boolean; + ackJoin(_child: any): void; + onAllChildJoinsComplete(): void; + update(diff: any, events: any): number | undefined; + renderContainer(diff: any, kind: any): any; + componentPatch(diff: any, cid: any): boolean; + getHook(el: any): any; + addHook(el: any): any; + destroyHook(hook: any): void; + applyPendingUpdates(): void; + eachChild(callback: any): void; + onChannel(event: any, cb: any): void; + bindChannel(): void; + destroyAllChildren(): void; + onLiveRedirect(redir: any): void; + onLivePatch(redir: any): void; + expandURL(to: any): any; + onRedirect({ to, flash, reloadToken }: { + to: any; + flash: any; + reloadToken: any; + }): void; + isDestroyed(): boolean; + joinDead(): void; + joinPush(): () => /*elided*/ any; + join(callback: any): void; + onJoinError(resp: any): void; + onClose(reason: any): any; + onError(reason: any): void; + displayError(classes: any): void; + wrapPush(callerPush: any, receives: any): void; + pushWithReply(refGenerator: any, event: any, payload: any): Promise; + undoRefs(ref: any, phxEvent: any, onlyEls: any): void; + undoElRef(el: any, ref: any, phxEvent: any): void; + refSrc(): any; + putRef(elements: any, phxEvent: any, eventType: any, opts?: {}): any[]; + isAcked(ref: any): boolean; + componentID(el: any): number | null; + targetComponentID(target: any, targetCtx: any, opts?: {}): any; + closestComponentID(targetCtx: any): any; + pushHookEvent(el: any, targetCtx: any, event: any, payload: any, onReply: any): any; + extractMeta(el: any, meta: any, value: any): any; + pushEvent(type: any, el: any, targetCtx: any, phxEvent: any, meta: any, opts: {} | undefined, onReply: any): void; + pushFileProgress(fileEl: any, entryRef: any, progress: any, onReply?: () => void): void; + pushInput(inputEl: any, targetCtx: any, forceCid: any, phxEvent: any, opts: any, callback: any): void; + triggerAwaitingSubmit(formEl: any, phxEvent: any): void; + getScheduledSubmit(formEl: any): any; + scheduleSubmit(formEl: any, ref: any, opts: any, callback: any): true | undefined; + cancelSubmit(formEl: any, phxEvent: any): void; + disableForm(formEl: any, phxEvent: any, opts?: {}): any[]; + pushFormSubmit(formEl: any, targetCtx: any, phxEvent: any, submitter: any, opts: any, onReply: any): true | undefined; + uploadFiles(formEl: any, phxEvent: any, targetCtx: any, ref: any, cid: any, onComplete: any): void; + handleFailedEntryPreflight(uploadRef: any, reason: any, uploader: any): void; + dispatchUploads(targetCtx: any, name: any, filesOrBlobs: any): void; + targetCtxElement(targetCtx: any): any; + pushFormRecovery(oldForm: any, newForm: any, templateDom: any, callback: any): void; + pushLinkPatch(e: any, href: any, targetEl: any, callback: any): void; + getFormsForRecovery(): any; + maybePushComponentsDestroyed(destroyedCIDs: any): void; + ownsElement(el: any): any; + submitForm(form: any, targetCtx: any, phxEvent: any, submitter: any, opts?: {}): void; + binding(kind: any): any; +} diff --git a/types/phoenix_live_view/view_hook.d.ts b/types/phoenix_live_view/view_hook.d.ts new file mode 100644 index 00000000000000..1544e007808784 --- /dev/null +++ b/types/phoenix_live_view/view_hook.d.ts @@ -0,0 +1,32 @@ +import { CallbackRef, OnReply, ViewHookInterface } from "./hooks"; +import LiveSocket from "./live_socket"; +import View from "./view"; + +export default class ViewHook implements ViewHookInterface { + static makeID(): number; + static elementID(el: HTMLElement): any; // DT + constructor(view: View, el: HTMLElement, callbacks: any); // DT + el: HTMLElement; // DT + + __callbacks: any; + __listeners: Set; + __isDisconnected: boolean; + __attachView(view: any): void; + __view: (() => any) | (() => never) | undefined; + liveSocket: LiveSocket; // DT + __mounted(): void; + __updated(): void; + __beforeUpdate(): void; + __destroyed(): void; + __reconnected(): void; + __disconnected(): void; + + js(): object; + pushEvent(event: string, payload: any, onReply?: OnReply): any; // DT + pushEventTo(phxTarget: any, event: string, payload: object, onReply?: OnReply): any; // DT + handleEvent(event: string, callback: any): CallbackRef; // DT + removeHandleEvent(callbackRef: CallbackRef): void; // DT + upload(name: any, files: any): any; + uploadTo(phxTarget: any, name: any, files: any): any; + __cleanup__(): void; +}