From fbd0e6d6ac213ca5f04e9cb0b5f6218b195ffa4f Mon Sep 17 00:00:00 2001 From: leonbahley <110975585+leonbahley@users.noreply.github.com> Date: Tue, 28 Oct 2025 14:14:38 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=A4=96=20Merge=20PR=20#73885=20Add=20@typ?= =?UTF-8?q?es/bullbone=20type=20definitions=20by=20@leonbahley?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- types/bullbone/.npmignore | 5 + types/bullbone/bull.events.d.ts | 41 ++++ types/bullbone/bull.factory.d.ts | 38 ++++ types/bullbone/bull.view.d.ts | 379 +++++++++++++++++++++++++++++++ types/bullbone/bullbone-tests.ts | 162 +++++++++++++ types/bullbone/index.d.ts | 5 + types/bullbone/package.json | 30 +++ types/bullbone/tsconfig.json | 20 ++ 8 files changed, 680 insertions(+) create mode 100644 types/bullbone/.npmignore create mode 100644 types/bullbone/bull.events.d.ts create mode 100644 types/bullbone/bull.factory.d.ts create mode 100644 types/bullbone/bull.view.d.ts create mode 100644 types/bullbone/bullbone-tests.ts create mode 100644 types/bullbone/index.d.ts create mode 100644 types/bullbone/package.json create mode 100644 types/bullbone/tsconfig.json diff --git a/types/bullbone/.npmignore b/types/bullbone/.npmignore new file mode 100644 index 00000000000000..93e307400a5456 --- /dev/null +++ b/types/bullbone/.npmignore @@ -0,0 +1,5 @@ +* +!**/*.d.ts +!**/*.d.cts +!**/*.d.mts +!**/*.d.*.ts diff --git a/types/bullbone/bull.events.d.ts b/types/bullbone/bull.events.d.ts new file mode 100644 index 00000000000000..208c838c55fd89 --- /dev/null +++ b/types/bullbone/bull.events.d.ts @@ -0,0 +1,41 @@ +/** + * An Events mixin. + */ +declare class Events { + /** + * Subscribe to an event. + */ + on(name: string, callback: (...args: any[]) => any, context?: object): this; + + /** + * Subscribe to an event of other object. + */ + listenTo(other: object, name: string, callback: (...args: any[]) => any): this; + + /** + * Unsubscribe from an event or all events. + */ + off(name?: string, callback?: (...args: any[]) => any, context?: object): this; + + /** + * Stop listening to other object. No arguments will remove all listeners. + */ + stopListening(other?: object, name?: string, callback?: (...args: any[]) => any): this; + + /** + * Subscribe to an event. Fired once. + */ + once(name: string, callback: (...args: any[]) => any, context?: object): this; + + /** + * Subscribe to an event of other object. Fired once. Will be automatically unsubscribed on view removal. + */ + listenToOnce(other: object, name: string, callback: (...args: any[]) => any): this; + + /** + * Trigger an event. + */ + trigger(name: string, ...parameters: Array): this; +} + +export default Events; diff --git a/types/bullbone/bull.factory.d.ts b/types/bullbone/bull.factory.d.ts new file mode 100644 index 00000000000000..74267472f1adc6 --- /dev/null +++ b/types/bullbone/bull.factory.d.ts @@ -0,0 +1,38 @@ +import View, { type BullViewOptions } from "./bull.view"; + +declare class Factory { + defaultViewName: string; + + constructor(options?: { + defaultViewName?: string; + customLoader?: object; + customRenderer?: object; + customLayouter?: object; + customTemplator?: object; + helper?: object; + viewLoader?: (name: string, callback: (view: View) => void) => void; + resources?: { + loaders?: { + template?: (name: string, callback: (content: string) => void) => void; + layoutTemplate?: (name: string, callback: (content: string) => void) => void; + }; + }; + preCompiledTemplates?: Record any>; + }); + + /** + * Create a view. + */ + create( + viewName: string, + options?: BullViewOptions, + callback?: (view: View) => void, + ): void; + + /** + * Prepare a view instance. + */ + prepare(view: View, callback?: (view: View) => void): void; +} + +export default Factory; diff --git a/types/bullbone/bull.view.d.ts b/types/bullbone/bull.view.d.ts new file mode 100644 index 00000000000000..7ac3a0b64c8c9e --- /dev/null +++ b/types/bullbone/bull.view.d.ts @@ -0,0 +1,379 @@ +/// +import Events from "./bull.events"; + +/** + * Nested view definitions. + */ +interface BullViewNestedViewItem { + view: string; + selector?: string; + fullSelector?: string; + el?: string; +} + +type BullModel = Events; + +type BullCollection = Events; + +/** + * DOM event listeners. + */ +type BullViewDomEvents = Record void>; + +/** + * View options passed to a view on creation. + */ +interface BullViewOptions { + selector?: string; + fullSelector?: string; + optionsToPass?: Array; + data?: ((obj: object) => object) | object; + template?: string; + templateContent?: string; + layoutDefs?: object; + layoutData?: object; + notToRender?: boolean; + views?: Record; + name?: string; + model?: BullModel; + collection?: BullCollection; + events?: BullViewDomEvents; + setViewBeforeCallback?: boolean; + [key: string]: any; +} + +declare class View { + $el: JQuery; + /** + * An ID, unique among all views. + */ + cid: string; + + /** + * A DOM element. + */ + element: HTMLElement; + + /** + * Passed options. + */ + options: Record; + + /** + * A template name/path. + */ + template?: string | null; + + /** + * Template content. Alternative to specifying a template name/path. + */ + templateContent: string | null; + + /** + * DOM event listeners. Recommended to use `addHandler` method instead. + */ + events: BullViewDomEvents; + + /** + * Not to render a view automatically when a view tree is built (ready). + * Afterward, it can be rendered manually. + */ + notToRender: boolean; + + /** + * Whether the view is ready for rendering (all necessary data is loaded). + */ + isReady: boolean; + + /** + * Definitions for nested views that should be automatically created. + * Format: viewKey => view defs. + * + * Example: ``` + * { + * body: { + * view: 'view/path/body', + * selector: '> .body', + * } + * } + * ``` + */ + views: Record | null; + + /** + * A list of options to be automatically passed to child views. + */ + optionsToPass: Array | null; + + /** + * Nested views. + * @internal + */ + nestedViews: Record; + + model?: BullModel; + collection?: BullCollection; + + /** + * Layout data. + */ + layoutData: object | null; + + /** + * Is component. Components does not require a DOM container defined by a parent view. + * Should have one root DOM element. + * + * An experimental feature. + */ + isComponent: boolean; + + /** + * @param {Object.} [options] + */ + constructor(options?: BullViewOptions); + + /** + * Initialize the view. Is invoked before #setup. + */ + init(): void; + + /** + * Set up the view. Is invoked after #init. + */ + setup(): void; + + /** + * Additional setup. Is invoked after #setup. + */ + setupFinal(): void; + + /** + * Compose template data. A key => value result will be passed to a template. + */ + data(): Record; + + /** + * Set a DOM element selector. + */ + setElement(selector: string): void; + + /** + * Removes all view's delegated events. Useful if you want to disable + * or remove a view from the DOM temporarily. + */ + undelegateEvents(): void; + + /** + * Add a DOM event handler. To be called in `setup` method. + */ + addHandler(type: string, selector: string, handler: ((...args: any[]) => any) | string): void; + + /** + * Set a view container element if it doesn't exist yet. It will call setElement after render. + */ + setElementInAdvance(fullSelector: string): void; + + /** + * Get a full DOM element selector. + */ + getSelector(): string | null; + + /** + * Set a full DOM element selector. + */ + setSelector(selector: string): void; + + /** + * Checks whether the view has been already rendered + */ + isRendered(): boolean; + + /** + * Checks whether the view has been fully rendered (afterRender has been executed). + */ + isFullyRendered(): boolean; + + /** + * Whether the view is being rendered at the moment. + */ + isBeingRendered(): boolean; + + /** + * Whether the view is removed. + */ + isRemoved(): boolean; + + /** + * Cancel rendering. + */ + cancelRender(): void; + + /** + * Un-cancel rendering. + */ + uncancelRender(): void; + + /** + * Render the view. + */ + render(callback?: (...args: any[]) => any): Promise; + + /** + * Re-render the view. + */ + reRender(options?: any): Promise; + + /** + * Executed after render. + */ + afterRender(): void; + + /** + * Proceed when rendered. + */ + whenRendered(): Promise; + + /** + * Provides the ability to modify template data right before render. + */ + handleDataBeforeRender(data: Record): void; + + /** + * Called each time before render. Should be extended as async. + */ + prepareRender(): Promise | undefined; + + /** + * Whether the view has a nested view. + */ + hasView(key: string): boolean; + + /** + * Get a nested view. + */ + getView(key: string): View | null; + + /** + * Get a nested view key by a view instance. + */ + getViewKey(view: View): string | null; + + /** + * Assign a view instance as nested. + */ + assignView(key: string, view: View, selector?: string): Promise; + + /** + * Create a nested view. The important method. + */ + createView( + key: string, + viewName: string, + options: BullViewOptions, + callback?: (...args: any[]) => any, + wait?: boolean, + ): Promise; + + /** + * Set a nested view. + */ + setView(key: string, view: View, fullSelector?: string): void; + + /** + * Clear a nested view. Initiates removal of the nested view. + */ + clearView(key: string): void; + + /** + * Removes a nested view for cases when it's supposed that this view can be re-used in future. + */ + unchainView(key: string): void; + + /** + * Get a parent view. + */ + getParentView(): View; + + /** + * Has a parent view. + */ + hasParentView(): boolean; + + /** + * Add a condition for the view getting ready. + */ + addReadyCondition(condition: ((...args: any[]) => any) | boolean): void; + + /** + * Wait for a nested view. + */ + waitForView(key: string): void; + + /** + * Makes the view to wait for a promise (if a Promise is passed as a parameter). + */ + wait(wait: Promise | boolean | ((...args: any[]) => any)): Promise | undefined; + + /** + * Remove the view and all nested tree. Removes an element from DOM. Triggers the 'remove' event. + */ + remove(dontEmpty?: boolean): this; + + /** + * Called on view removal. + */ + onRemove(): void; + + /** + * Propagate an event to nested views. + */ + propagateEvent(...parameters: Array): void; + + /** + * Set a template. Experimental. + */ + setTemplate(template?: string): void; + + /** + * Set template content. Experimental. + */ + setTemplateContent(templateContent: string): void; + + /** + * Subscribe to an event. + */ + on(name: string, callback: (...args: any[]) => any, context?: any): any; + + /** + * Subscribe to an event. Fired once. + */ + once(name: string, callback: (...args: any[]) => any, context?: any): any; + + /** + * Unsubscribe from an event or all events. + */ + off(name?: string, callback?: (...args: any[]) => any, context?: any): any; + + /** + * Subscribe to an event of other object. + */ + listenTo(other: any, name: string, callback: (...args: any[]) => any): any; + + /** + * Subscribe to an event of other object. Fired once. Will be automatically unsubscribed on view removal. + */ + listenToOnce(other: any, name: string, callback: (...args: any[]) => any): any; + + /** + * Stop listening to other object. No arguments will remove all listeners. + */ + stopListening(other?: any, name?: string, callback?: (...args: any[]) => any): any; + + /** + * Trigger an event. + */ + trigger(name: string, ...parameters: Array): any; +} + +export { type BullViewOptions }; +export default View; diff --git a/types/bullbone/bullbone-tests.ts b/types/bullbone/bullbone-tests.ts new file mode 100644 index 00000000000000..63ed6ffe644c45 --- /dev/null +++ b/types/bullbone/bullbone-tests.ts @@ -0,0 +1,162 @@ +import { Events, Factory, View } from "bullbone"; + +// Events tests +const events = new Events(); + +// $ExpectType Events +events.on("test", (data: string) => console.log(data)); + +// $ExpectType Events +events.once("click", (e: Event) => {}); + +// $ExpectType Events +events.trigger("test", "hello", 123); + +// $ExpectType Events +events.off("test"); + +// $ExpectType Events +events.listenTo(events, "change", (value: any) => {}); + +// $ExpectType Events +events.stopListening(); + +// Factory tests +const factory = new Factory({ + defaultViewName: "DefaultView", + customLoader: {}, + viewLoader: (name: string, callback: (view: View) => void) => { + callback(new View()); + }, + resources: { + loaders: { + template: (name: string, callback: (content: string) => void) => { + callback("
"); + }, + }, + }, +}); + +// $ExpectType string +factory.defaultViewName; + +factory.create("TestView", { template: "test" }, (view: View) => { + // $ExpectType View + view; +}); + +factory.prepare(new View(), (view: View) => { + // $ExpectType View + view; +}); + +// View tests +const viewOptions = { + template: "my-template", + selector: ".container", + data: { title: "Test" }, + events: { + "click .button": (e: JQuery.Event) => {}, + }, +}; + +const view = new View(viewOptions); + +// $ExpectType string +view.cid; + +// $ExpectType HTMLElement +view.element; + +// $ExpectType boolean +view.isReady; + +// $ExpectType JQuery +view.$el; + +// Lifecycle methods +view.init(); +view.setup(); +view.setupFinal(); + +// $ExpectType Record +view.data(); + +// Rendering +// $ExpectType Promise +view.render((renderedView: View) => {}); + +// $ExpectType Promise +view.reRender(); + +// $ExpectType Promise +view.whenRendered(); + +// Element management +view.setElement(".new-selector"); +view.undelegateEvents(); + +// Event handlers +view.addHandler("click", ".button", (e: Event) => {}); +view.addHandler("click", ".button", "methodName"); + +// View state +// $ExpectType boolean +view.isRendered(); + +// $ExpectType boolean +view.isFullyRendered(); + +// Nested views +// $ExpectType Promise +view.createView("child", "ChildView", { template: "child" }); + +// $ExpectType boolean +view.hasView("child"); + +// $ExpectType View | null +view.getView("child"); + +view.setView("child", new View()); +view.clearView("child"); + +// Parent/child relationships +// $ExpectType boolean +view.hasParentView(); + +// Ready conditions +view.addReadyCondition((data: any) => true); +view.addReadyCondition(true); + +// Waiting +view.waitForView("child"); + +// $ExpectType Promise | undefined +view.wait(Promise.resolve()); + +// $ExpectType Promise | undefined +view.wait(true); + +// Removal +// $ExpectType View +view.remove(); + +view.onRemove(); + +// Events (inherited from Events) +// $ExpectType any +view.on("render", (data: any) => {}); + +// $ExpectType any +view.trigger("custom:event", "data"); + +// Template management +view.setTemplate("new-template"); +view.setTemplateContent("
content
"); + +// Error cases +// @ts-expect-error +view.addHandler("click"); + +// @ts-expect-error +factory.create(); diff --git a/types/bullbone/index.d.ts b/types/bullbone/index.d.ts new file mode 100644 index 00000000000000..549027c6c74a81 --- /dev/null +++ b/types/bullbone/index.d.ts @@ -0,0 +1,5 @@ +import Events from "./bull.events"; +import Factory from "./bull.factory"; +import View from "./bull.view"; + +export { Events, Factory, View }; diff --git a/types/bullbone/package.json b/types/bullbone/package.json new file mode 100644 index 00000000000000..da04b2f1137119 --- /dev/null +++ b/types/bullbone/package.json @@ -0,0 +1,30 @@ +{ + "private": true, + "name": "@types/bullbone", + "version": "1.3.9999", + "nonNpm": true, + "nonNpmDescription": "A front-end library developed for EspoCRM.", + "projects": [ + "https://github.com/espocrm/bullbone" + ], + "dependencies": { + "@types/jquery": "*" + }, + "devDependencies": { + "@types/bullbone": "workspace:." + }, + "owners": [ + { + "name": "leonbahley", + "githubUsername": "leonbahley" + }, + { + "name": "TarasOP", + "githubUsername": "TarasOP" + }, + { + "name": "malikalimoekhamedov", + "githubUsername": "malikalimoekhamedov" + } + ] +} diff --git a/types/bullbone/tsconfig.json b/types/bullbone/tsconfig.json new file mode 100644 index 00000000000000..b065ffcbcf8998 --- /dev/null +++ b/types/bullbone/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "module": "node16", + "lib": [ + "es6", + "dom" + ], + "noImplicitAny": true, + "noImplicitThis": true, + "strictFunctionTypes": true, + "strictNullChecks": true, + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true + }, + "files": [ + "index.d.ts", + "bullbone-tests.ts" + ] +}