diff --git a/apps/demo/project.json b/apps/demo/project.json index 7ee2240..7683b09 100644 --- a/apps/demo/project.json +++ b/apps/demo/project.json @@ -16,7 +16,7 @@ "polyfills": ["zone.js"], "tsConfig": "apps/demo/tsconfig.app.json", "assets": ["apps/demo/src/favicon.ico", "apps/demo/src/assets"], - "styles": ["node_modules/gridjs/dist/theme/mermaid.min.css"], + "styles": ["apps/demo/src/styles.css"], "scripts": [] }, "configurations": { diff --git a/apps/demo/src/app/app.component.css b/apps/demo/src/app/app.component.css new file mode 100644 index 0000000..e69de29 diff --git a/apps/demo/src/app/app.component.html b/apps/demo/src/app/app.component.html new file mode 100644 index 0000000..a57ab4e --- /dev/null +++ b/apps/demo/src/app/app.component.html @@ -0,0 +1,2 @@ + + diff --git a/apps/demo/src/app/app.component.ts b/apps/demo/src/app/app.component.ts index 8a4f449..394c181 100644 --- a/apps/demo/src/app/app.component.ts +++ b/apps/demo/src/app/app.component.ts @@ -1,38 +1,19 @@ import { Component } from '@angular/core'; +import { RouterModule } from '@angular/router'; import { GridJsAngularComponent } from 'gridjs-angular'; -import { faker } from '@faker-js/faker'; -import { TData } from 'gridjs/dist/src/types'; +import 'gridjs/dist/theme/mermaid.css'; @Component({ - selector: 'gridjs-angular-root', standalone: true, - imports: [GridJsAngularComponent], - template: ``, + imports: [GridJsAngularComponent, RouterModule], + selector: 'gridjs-angular-root', + templateUrl: './app.component.html', + styleUrl: './app.component.css', }) export class AppComponent { - onLoad = (event: any) => console.log('Grid loaded', event); - onBeforeLoad = (event: any) => console.log('Before grid loaded', event); - onReady = (event: any) => console.log('Grid ready', event); - onCellClick = (event: any) => console.log('Grid cell clicked', event); - onRowClick = (event: any) => console.log('Grid row clicked', event); - columns = ['Name', 'Email', 'Phone Number']; - data: TData = new Array(20) - .fill(undefined) - .map(() => [ - faker.person.fullName(), - faker.internet.email(), - faker.phone.number(), - ]); + data = [ + ['John', 'john@example.com', '(353) 01 222 3333'], + ['Mark', 'mark@gmail.com', '(01) 22 888 4444'], + ]; } diff --git a/apps/demo/src/app/app.config.ts b/apps/demo/src/app/app.config.ts new file mode 100644 index 0000000..ed40494 --- /dev/null +++ b/apps/demo/src/app/app.config.ts @@ -0,0 +1,7 @@ +import { ApplicationConfig } from '@angular/core'; +import { provideRouter } from '@angular/router'; +import { appRoutes } from './app.routes'; + +export const appConfig: ApplicationConfig = { + providers: [provideRouter(appRoutes)], +}; diff --git a/apps/demo/src/app/app.routes.ts b/apps/demo/src/app/app.routes.ts new file mode 100644 index 0000000..8762dfe --- /dev/null +++ b/apps/demo/src/app/app.routes.ts @@ -0,0 +1,3 @@ +import { Route } from '@angular/router'; + +export const appRoutes: Route[] = []; diff --git a/apps/demo/src/main.ts b/apps/demo/src/main.ts index 57c05db..514c89a 100644 --- a/apps/demo/src/main.ts +++ b/apps/demo/src/main.ts @@ -1,6 +1,7 @@ import { bootstrapApplication } from '@angular/platform-browser'; +import { appConfig } from './app/app.config'; import { AppComponent } from './app/app.component'; -bootstrapApplication(AppComponent).catch((err) => +bootstrapApplication(AppComponent, appConfig).catch((err) => console.error(err) ); diff --git a/apps/demo/src/styles.css b/apps/demo/src/styles.css new file mode 100644 index 0000000..90d4ee0 --- /dev/null +++ b/apps/demo/src/styles.css @@ -0,0 +1 @@ +/* You can add global styles to this file, and also import other style files */ diff --git a/package.json b/package.json index b9244c0..5e1a79b 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,6 @@ "@angular/cli": "~17.1.2", "@angular/compiler-cli": "~17.1.2", "@angular/language-service": "~17.1.2", - "@faker-js/faker": "^8.4.0", "@nx/devkit": "17.3.1", "@nx/eslint": "17.3.1", "@nx/eslint-plugin": "17.3.1", @@ -50,7 +49,6 @@ "@typescript-eslint/eslint-plugin": "^6.20.0", "@typescript-eslint/parser": "^6.20.0", "autoprefixer": "^10.4.17", - "change-case": "^5.4.2", "eslint": "~8.56.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-playwright": "^0.22.1", @@ -58,7 +56,6 @@ "jest-environment-jsdom": "^29.7.0", "jest-preset-angular": "~14.0.0", "jsonc-eslint-parser": "^2.4.0", - "mustache": "^4.2.0", "ng-packagr": "~17.1.2", "nx": "17.3.1", "postcss": "^8.4.33", diff --git a/packages/gridjs-angular/README.md b/packages/gridjs-angular/README.md index e642626..e0fcd27 100644 --- a/packages/gridjs-angular/README.md +++ b/packages/gridjs-angular/README.md @@ -2,9 +2,6 @@ Angular wrapper for [Grid.js](https://github.com/grid-js/gridjs) -[![gridjs-angular repository on GitHub](https://img.shields.io/badge/github-gridjs--angular-green?logo=github&link=https%3A%2F%2Fgithub.com%2Fgrid-js%2Fgridjs-angular)](https://github.com/grid-js/gridjs-angular) -![GridJS peer Dependency Version](https://img.shields.io/npm/dependency-version/gridjs-angular/peer/gridjs) - ## Install ```bash @@ -30,7 +27,7 @@ In your component template ```ts import { Component } from '@angular/core'; -import { Config } from 'gridjs'; +import { UserConfig } from 'gridjs'; @Component({ template: ` @@ -44,7 +41,7 @@ import { Config } from 'gridjs'; ` }) class ExampleComponent { - public gridConfig: Config = { + public gridConfig: UserConfig = { columns: ['Name', 'Email', 'Phone Number'], data: [ ['John', 'john@example.com', '(353) 01 222 3333'], @@ -73,10 +70,13 @@ class ExampleComponent { } ``` -Finally don't forget to add gridjs theme to your `angular.json` file, or import it some other way. +Finally don't forget to add gridjs theme in your index.html -```json -styles: ["node_modules/gridjs/dist/theme/mermaid.min.css"] +```html + ``` ## Inputs @@ -89,7 +89,7 @@ styles: ["node_modules/gridjs/dist/theme/mermaid.min.css"] ## Outputs -- You can bind to all Grid.js events as outputs. Additionally, the `load` event can also be accessed via `gridLoad` (to avoid conflict with the native DOM `load` event). See [Grid.js Events](https://gridjs.io/docs/examples/event-handler) +- You can pass all Grid.js events as outputs with a little difference `load` event renamed to `beforeLoad`. See [Grid.js Events](https://gridjs.io/docs/examples/event-handler) ### Can I Grid.js rendering helpers? Yes @@ -114,19 +114,4 @@ styles: ["node_modules/gridjs/dist/theme/mermaid.min.css"] } ``` -### Can I use Angular template syntax in plugins, formatters, etc? - -Not currently. - -You can't use Angular template syntax in Grid.js plugins, formatters, etc. because they cannot be connected to Angular's change detection system. You can use `h` function or `html` function to create custom HTML for your grid. - -## Development - -The `gridjs-angular` repository is a monorepo that uses [Nx](https://nx.dev) and [pnpm](https://pnpm.io/). - -### Useful commands - -- `pnpm install` - Install all dependencies -- `nx serve demo` - Run demo app -- `nx migrate latest` - Update Nx to the latest version, and upgrade all packages from package.json to their latest version -- `nx update-bindings gridjs-angular` - Update the input and output bindings from GridJS to the Angular component. This command should be run after updating the GridJS version. +### Can I use Angular components in plugins, formatters, etc? Not yet diff --git a/packages/gridjs-angular/package.json b/packages/gridjs-angular/package.json index ee495b9..add10c2 100644 --- a/packages/gridjs-angular/package.json +++ b/packages/gridjs-angular/package.json @@ -14,8 +14,8 @@ "repository": "https://github.com/grid-js/gridjs-angular", "license": "MIT", "peerDependencies": { - "@angular/common": ">=17", - "@angular/core": ">=17", + "@angular/common": "^17.1.2", + "@angular/core": "^17.1.2", "gridjs": "^6.1.1" }, "dependencies": { diff --git a/packages/gridjs-angular/project.json b/packages/gridjs-angular/project.json index f791540..cb65ece 100644 --- a/packages/gridjs-angular/project.json +++ b/packages/gridjs-angular/project.json @@ -31,13 +31,6 @@ }, "lint": { "executor": "@nx/eslint:lint" - }, - "update-bindings": { - "executor": "nx:run-commands", - "outputs": ["{workspaceRoot}/packages/gridjs-angular/src/lib/gridjs-binding-base.ts"], - "options": { - "command": "node scripts/update-bindings.mjs" - } } } } diff --git a/packages/gridjs-angular/src/index.ts b/packages/gridjs-angular/src/index.ts index 96e1656..2a1c485 100644 --- a/packages/gridjs-angular/src/index.ts +++ b/packages/gridjs-angular/src/index.ts @@ -1,2 +1,2 @@ +export * from './lib/constants'; export * from './lib/gridjs-angular.component'; -export { GRID_EVENTS as GRID_JS_EVENTS } from './lib/gridjs-binding-base'; diff --git a/packages/gridjs-angular/src/lib/constants.ts b/packages/gridjs-angular/src/lib/constants.ts new file mode 100644 index 0000000..7402548 --- /dev/null +++ b/packages/gridjs-angular/src/lib/constants.ts @@ -0,0 +1,35 @@ +import type { Config } from 'gridjs'; +import { GridEvents } from 'gridjs/dist/src/events'; + +export const GRID_JS_EVENTS: (keyof GridEvents)[] = [ + 'beforeLoad', + 'cellClick', + 'load', + 'rowClick', + 'ready', +]; + +export const GRID_JS_PROPS: (keyof Config)[] = [ + 'eventEmitter', + 'plugin', + 'data', + 'server', + 'header', + 'from', + 'storage', + 'pipeline', + 'autoWidth', + 'width', + 'height', + 'translator', + 'style', + 'className', + 'fixedHeader', + 'columns', + 'search', + 'pagination', + 'sort', + 'language', + 'plugins', + 'processingThrottleMs', +]; diff --git a/packages/gridjs-angular/src/lib/gridjs-angular.component.ts b/packages/gridjs-angular/src/lib/gridjs-angular.component.ts index 357e5c2..4cce13c 100644 --- a/packages/gridjs-angular/src/lib/gridjs-angular.component.ts +++ b/packages/gridjs-angular/src/lib/gridjs-angular.component.ts @@ -3,73 +3,131 @@ import { Component, ElementRef, EventEmitter, + Input, + OnChanges, OnDestroy, Output, + ViewEncapsulation, } from '@angular/core'; +import { CommonModule } from '@angular/common'; import { Config, Grid } from 'gridjs'; -import { GRID_EVENTS, GridJsAngularBindingBase } from './gridjs-binding-base'; -import { GridEvents } from 'gridjs/dist/src/events'; - -/** only properties that exist on the Config interface (not the Config class) */ -type EventName = keyof GridEvents; -type EventHandler = (...args: any[]) => void; +import { GRID_JS_EVENTS, GRID_JS_PROPS } from './constants'; +type GridJsAngularComponentProps = Omit< + Partial, + 'instance' | 'store' | 'assign' | 'update' +>; @Component({ selector: 'gridjs-angular', - standalone: true, template: '', + standalone: true, + imports: [CommonModule], + encapsulation: ViewEncapsulation.None, }) export class GridJsAngularComponent - extends GridJsAngularBindingBase - implements AfterViewInit, OnDestroy + implements AfterViewInit, OnChanges, OnDestroy, GridJsAngularComponentProps { - private readonly listeners = new Map(); + private nativeElement: HTMLElement; + private instance?: Grid; + private initialized = false; + private listeners: Map void> = new Map(); + @Input() config?: Partial; + // TODO: auto generate Inputs/Output to easily sync with grid-js main package + // props + @Input() plugins: Config['plugins'] = []; + @Input() eventEmitter?: Config['eventEmitter']; + @Input() plugin?: Config['plugin']; + @Input() data: Config['data']; + @Input() server: Config['server']; + @Input() header: Config['header']; + @Input() from?: Config['from']; + @Input() storage?: Config['storage']; + @Input() pipeline?: Config['pipeline']; + @Input() autoWidth?: Config['autoWidth']; + @Input() width?: Config['width']; + @Input() height?: Config['height']; + @Input() translator?: Config['translator']; + @Input() style: Config['style']; + @Input() className: Config['className']; + @Input() fixedHeader?: Config['fixedHeader']; + @Input() columns?: Config['columns']; + @Input() search?: Config['search']; + @Input() pagination?: Config['pagination']; + @Input() sort?: Config['sort']; + @Input() language?: Config['language']; + @Input() resizable?: Config['resizable']; + @Input() processingThrottleMs?: Config['processingThrottleMs']; - /** alias of `load` event due to possible conflict with native load event */ - @Output() readonly gridLoad = this.load; + // events + @Output() beforeLoad: EventEmitter = new EventEmitter(true); + // renamed load event to avoid conflict with native load event + @Output() gridLoad: EventEmitter = new EventEmitter(true); + @Output() cellClick: EventEmitter = new EventEmitter(true); + @Output() rowClick: EventEmitter = new EventEmitter(true); + @Output() ready: EventEmitter = new EventEmitter(true); - constructor(private readonly host: ElementRef) { - super(); + constructor(private elementDef: ElementRef) { + this.nativeElement = this.elementDef.nativeElement; } ngAfterViewInit(): void { - const instance = new Grid(this.config()); - this.instance.set(instance); + this.instance = new Grid(this.getConfig(this.config ?? {})); this.registerEvents(); - instance.render(this.host.nativeElement); + this.instance.render(this.nativeElement); + this.initialized = true; } - ngOnDestroy(): void { - if (this.instance()) { - this.unregisterEvents(); - this.instance.set(undefined); + ngOnChanges(): void { + if (this.initialized) { + this.updateConfig(this.config); } } + ngOnDestroy(): void { + if (this.initialized) { + if (this.instance) { + this.unregisterEvents(); + this.instance = undefined; + } + } + } // public api to interact with grid instance getGridInstance() { - return this.instance(); + return this.instance; } updateConfig(config: Partial = {}) { - this.gridConfig.set(config); + this.instance?.updateConfig(this.getConfig(config)).forceRender(); } private registerEvents() { - for (const event of GRID_EVENTS) { - const emitter = (this)[event] as EventEmitter; - if (!emitter) { - continue; - } + for (const event of GRID_JS_EVENTS) { + const emitter = + event === 'load' + ? this.gridLoad + : >(this)[event]; const listener = (...args: any[]) => emitter.emit(args); this.listeners.set(event, listener); - this.instance()?.on(event, listener); + if (emitter) { + this.instance?.on(event as any, listener); + } } } private unregisterEvents() { for (const [event, listener] of this.listeners.entries()) { - this.instance()?.off(event, listener); + this.instance?.off(event as any, listener); + } + } + + private getConfig(config: Partial = {}) { + const newConfig = structuredClone(config); + for (const [key, value] of Object.entries(this)) { + if (GRID_JS_PROPS.includes(key as any)) { + (newConfig as any)[key] = value; + } } + this.config = newConfig; + return newConfig; } } diff --git a/packages/gridjs-angular/src/lib/gridjs-binding-base.ts b/packages/gridjs-angular/src/lib/gridjs-binding-base.ts deleted file mode 100644 index 307294d..0000000 --- a/packages/gridjs-angular/src/lib/gridjs-binding-base.ts +++ /dev/null @@ -1,486 +0,0 @@ -// This file is generated automatically using "nx update-bindings gridjs-angular" -// Do not edit this file manually -import { Config } from 'gridjs'; -import { GridEvents } from 'gridjs/dist/src/events'; -import { Component, Input, Output, EventEmitter, signal, computed, effect } from '@angular/core'; -import 'preact'; - -type GridEventsEmitter = Record>; - -export const GRID_EVENTS: Array = [ - 'beforeLoad', - 'load', - 'ready', - 'cellClick', - 'rowClick', -]; - -@Component({ template: '' }) -export abstract class GridJsAngularBindingBase implements GridEventsEmitter { - constructor() { - effect(() => { - const instanceVal = this.instance(); - const instance = this.instance(); - if (instanceVal === undefined || !instance) { - return; - } - instance.updateConfig({ instance: instanceVal }); - instance.forceRender(); - }); - effect(() => { - const storeVal = this.store(); - const instance = this.instance(); - if (storeVal === undefined || !instance) { - return; - } - instance.updateConfig({ store: storeVal }); - instance.forceRender(); - }); - effect(() => { - const eventEmitterVal = this.eventEmitter(); - const instance = this.instance(); - if (eventEmitterVal === undefined || !instance) { - return; - } - instance.updateConfig({ eventEmitter: eventEmitterVal }); - instance.forceRender(); - }); - effect(() => { - const pluginVal = this.plugin(); - const instance = this.instance(); - if (pluginVal === undefined || !instance) { - return; - } - instance.updateConfig({ plugin: pluginVal }); - instance.forceRender(); - }); - effect(() => { - const containerVal = this.container(); - const instance = this.instance(); - if (containerVal === undefined || !instance) { - return; - } - instance.updateConfig({ container: containerVal }); - instance.forceRender(); - }); - effect(() => { - const tableRefVal = this.tableRef(); - const instance = this.instance(); - if (tableRefVal === undefined || !instance) { - return; - } - instance.updateConfig({ tableRef: tableRefVal }); - instance.forceRender(); - }); - effect(() => { - const dataVal = this.data(); - const instance = this.instance(); - if (dataVal === undefined || !instance) { - return; - } - instance.updateConfig({ data: dataVal }); - instance.forceRender(); - }); - effect(() => { - const serverVal = this.server(); - const instance = this.instance(); - if (serverVal === undefined || !instance) { - return; - } - instance.updateConfig({ server: serverVal }); - instance.forceRender(); - }); - effect(() => { - const headerVal = this.header(); - const instance = this.instance(); - if (headerVal === undefined || !instance) { - return; - } - instance.updateConfig({ header: headerVal }); - instance.forceRender(); - }); - effect(() => { - const fromVal = this.from(); - const instance = this.instance(); - if (fromVal === undefined || !instance) { - return; - } - instance.updateConfig({ from: fromVal }); - instance.forceRender(); - }); - effect(() => { - const storageVal = this.storage(); - const instance = this.instance(); - if (storageVal === undefined || !instance) { - return; - } - instance.updateConfig({ storage: storageVal }); - instance.forceRender(); - }); - effect(() => { - const processingThrottleMsVal = this.processingThrottleMs(); - const instance = this.instance(); - if (processingThrottleMsVal === undefined || !instance) { - return; - } - instance.updateConfig({ processingThrottleMs: processingThrottleMsVal }); - instance.forceRender(); - }); - effect(() => { - const pipelineVal = this.pipeline(); - const instance = this.instance(); - if (pipelineVal === undefined || !instance) { - return; - } - instance.updateConfig({ pipeline: pipelineVal }); - instance.forceRender(); - }); - effect(() => { - const autoWidthVal = this.autoWidth(); - const instance = this.instance(); - if (autoWidthVal === undefined || !instance) { - return; - } - instance.updateConfig({ autoWidth: autoWidthVal }); - instance.forceRender(); - }); - effect(() => { - const widthVal = this.width(); - const instance = this.instance(); - if (widthVal === undefined || !instance) { - return; - } - instance.updateConfig({ width: widthVal }); - instance.forceRender(); - }); - effect(() => { - const heightVal = this.height(); - const instance = this.instance(); - if (heightVal === undefined || !instance) { - return; - } - instance.updateConfig({ height: heightVal }); - instance.forceRender(); - }); - effect(() => { - const paginationVal = this.pagination(); - const instance = this.instance(); - if (paginationVal === undefined || !instance) { - return; - } - instance.updateConfig({ pagination: paginationVal }); - instance.forceRender(); - }); - effect(() => { - const sortVal = this.sort(); - const instance = this.instance(); - if (sortVal === undefined || !instance) { - return; - } - instance.updateConfig({ sort: sortVal }); - instance.forceRender(); - }); - effect(() => { - const translatorVal = this.translator(); - const instance = this.instance(); - if (translatorVal === undefined || !instance) { - return; - } - instance.updateConfig({ translator: translatorVal }); - instance.forceRender(); - }); - effect(() => { - const fixedHeaderVal = this.fixedHeader(); - const instance = this.instance(); - if (fixedHeaderVal === undefined || !instance) { - return; - } - instance.updateConfig({ fixedHeader: fixedHeaderVal }); - instance.forceRender(); - }); - effect(() => { - const resizableVal = this.resizable(); - const instance = this.instance(); - if (resizableVal === undefined || !instance) { - return; - } - instance.updateConfig({ resizable: resizableVal }); - instance.forceRender(); - }); - effect(() => { - const columnsVal = this.columns(); - const instance = this.instance(); - if (columnsVal === undefined || !instance) { - return; - } - instance.updateConfig({ columns: columnsVal }); - instance.forceRender(); - }); - effect(() => { - const searchVal = this.search(); - const instance = this.instance(); - if (searchVal === undefined || !instance) { - return; - } - instance.updateConfig({ search: searchVal }); - instance.forceRender(); - }); - effect(() => { - const languageVal = this.language(); - const instance = this.instance(); - if (languageVal === undefined || !instance) { - return; - } - instance.updateConfig({ language: languageVal }); - instance.forceRender(); - }); - effect(() => { - const pluginsVal = this.plugins(); - const instance = this.instance(); - if (pluginsVal === undefined || !instance) { - return; - } - instance.updateConfig({ plugins: pluginsVal }); - instance.forceRender(); - }); - effect(() => { - const styleVal = this.style(); - const instance = this.instance(); - if (styleVal === undefined || !instance) { - return; - } - instance.updateConfig({ style: styleVal }); - instance.forceRender(); - }); - effect(() => { - const classNameVal = this.className(); - const instance = this.instance(); - if (classNameVal === undefined || !instance) { - return; - } - instance.updateConfig({ className: classNameVal }); - instance.forceRender(); - }); - } - - readonly instance = signal(undefined); - @Input({alias: 'instance'}) - set _instance(value: Config['instance'] | undefined) { - this.instance.set(value); - } - - readonly store = signal(undefined); - @Input({alias: 'store'}) - set _store(value: Config['store'] | undefined) { - this.store.set(value); - } - - readonly eventEmitter = signal(undefined); - @Input({alias: 'eventEmitter'}) - set _eventEmitter(value: Config['eventEmitter'] | undefined) { - this.eventEmitter.set(value); - } - - readonly plugin = signal(undefined); - @Input({alias: 'plugin'}) - set _plugin(value: Config['plugin'] | undefined) { - this.plugin.set(value); - } - - readonly container = signal(undefined); - @Input({alias: 'container'}) - set _container(value: Config['container'] | undefined) { - this.container.set(value); - } - - readonly tableRef = signal(undefined); - @Input({alias: 'tableRef'}) - set _tableRef(value: Config['tableRef'] | undefined) { - this.tableRef.set(value); - } - - readonly data = signal(undefined); - @Input({alias: 'data'}) - set _data(value: Config['data'] | undefined) { - this.data.set(value); - } - - readonly server = signal(undefined); - @Input({alias: 'server'}) - set _server(value: Config['server'] | undefined) { - this.server.set(value); - } - - readonly header = signal(undefined); - @Input({alias: 'header'}) - set _header(value: Config['header'] | undefined) { - this.header.set(value); - } - - readonly from = signal(undefined); - @Input({alias: 'from'}) - set _from(value: Config['from'] | undefined) { - this.from.set(value); - } - - readonly storage = signal(undefined); - @Input({alias: 'storage'}) - set _storage(value: Config['storage'] | undefined) { - this.storage.set(value); - } - - readonly processingThrottleMs = signal(undefined); - @Input({alias: 'processingThrottleMs'}) - set _processingThrottleMs(value: Config['processingThrottleMs'] | undefined) { - this.processingThrottleMs.set(value); - } - - readonly pipeline = signal(undefined); - @Input({alias: 'pipeline'}) - set _pipeline(value: Config['pipeline'] | undefined) { - this.pipeline.set(value); - } - - readonly autoWidth = signal(undefined); - @Input({alias: 'autoWidth'}) - set _autoWidth(value: Config['autoWidth'] | undefined) { - this.autoWidth.set(value); - } - - readonly width = signal(undefined); - @Input({alias: 'width'}) - set _width(value: Config['width'] | undefined) { - this.width.set(value); - } - - readonly height = signal(undefined); - @Input({alias: 'height'}) - set _height(value: Config['height'] | undefined) { - this.height.set(value); - } - - readonly pagination = signal(undefined); - @Input({alias: 'pagination'}) - set _pagination(value: Config['pagination'] | undefined) { - this.pagination.set(value); - } - - readonly sort = signal(undefined); - @Input({alias: 'sort'}) - set _sort(value: Config['sort'] | undefined) { - this.sort.set(value); - } - - readonly translator = signal(undefined); - @Input({alias: 'translator'}) - set _translator(value: Config['translator'] | undefined) { - this.translator.set(value); - } - - readonly fixedHeader = signal(undefined); - @Input({alias: 'fixedHeader'}) - set _fixedHeader(value: Config['fixedHeader'] | undefined) { - this.fixedHeader.set(value); - } - - readonly resizable = signal(undefined); - @Input({alias: 'resizable'}) - set _resizable(value: Config['resizable'] | undefined) { - this.resizable.set(value); - } - - readonly columns = signal(undefined); - @Input({alias: 'columns'}) - set _columns(value: Config['columns'] | undefined) { - this.columns.set(value); - } - - readonly search = signal(undefined); - @Input({alias: 'search'}) - set _search(value: Config['search'] | undefined) { - this.search.set(value); - } - - readonly language = signal(undefined); - @Input({alias: 'language'}) - set _language(value: Config['language'] | undefined) { - this.language.set(value); - } - - readonly plugins = signal(undefined); - @Input({alias: 'plugins'}) - set _plugins(value: Config['plugins'] | undefined) { - this.plugins.set(value); - } - - readonly style = signal(undefined); - @Input({alias: 'style'}) - set _style(value: Config['style'] | undefined) { - this.style.set(value); - } - - readonly className = signal(undefined); - @Input({alias: 'className'}) - set _className(value: Config['className'] | undefined) { - this.className.set(value); - } - - readonly gridConfig = signal | undefined>(undefined); - @Input({alias: 'gridConfig'}) - set _gridConfig(value: Partial | undefined) { - this.gridConfig.set(value); - } - - readonly config = computed>(() => { - const configValue: Partial = { - instance: this.instance(), - store: this.store(), - eventEmitter: this.eventEmitter(), - plugin: this.plugin(), - container: this.container(), - tableRef: this.tableRef(), - data: this.data(), - server: this.server(), - header: this.header(), - from: this.from(), - storage: this.storage(), - processingThrottleMs: this.processingThrottleMs(), - pipeline: this.pipeline(), - autoWidth: this.autoWidth(), - width: this.width(), - height: this.height(), - pagination: this.pagination(), - sort: this.sort(), - translator: this.translator(), - fixedHeader: this.fixedHeader(), - resizable: this.resizable(), - columns: this.columns(), - search: this.search(), - language: this.language(), - plugins: this.plugins(), - style: this.style(), - className: this.className(), - }; - for(let key in configValue) { - const keyName = key as keyof Config; - if (configValue[keyName] === undefined) { - delete configValue[keyName]; - } - } - return { - ...this.gridConfig(), - ...configValue - }; - }); - - @Output() - readonly beforeLoad = new EventEmitter(); - @Output() - readonly load = new EventEmitter(); - @Output() - readonly ready = new EventEmitter(); - @Output() - readonly cellClick = new EventEmitter(); - @Output() - readonly rowClick = new EventEmitter(); -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cf618b4..90a8d7c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -73,9 +73,6 @@ devDependencies: '@angular/language-service': specifier: ~17.1.2 version: 17.1.2 - '@faker-js/faker': - specifier: ^8.4.0 - version: 8.4.0 '@nx/devkit': specifier: 17.3.1 version: 17.3.1(nx@17.3.1) @@ -127,9 +124,6 @@ devDependencies: autoprefixer: specifier: ^10.4.17 version: 10.4.17(postcss@8.4.33) - change-case: - specifier: ^5.4.2 - version: 5.4.2 eslint: specifier: ~8.56.0 version: 8.56.0 @@ -151,9 +145,6 @@ devDependencies: jsonc-eslint-parser: specifier: ^2.4.0 version: 2.4.0 - mustache: - specifier: ^4.2.0 - version: 4.2.0 ng-packagr: specifier: ~17.1.2 version: 17.1.2(@angular/compiler-cli@17.1.2)(tslib@2.6.2)(typescript@5.3.3) @@ -3324,11 +3315,6 @@ packages: resolution: {integrity: sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - /@faker-js/faker@8.4.0: - resolution: {integrity: sha512-htW87352wzUCdX1jyUQocUcmAaFqcR/w082EC8iP/gtkF0K+aKcBp0hR5Arb7dzR8tQ1TrhE9DNa5EbJELm84w==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0, npm: '>=6.14.13'} - dev: true - /@fastify/busboy@2.1.0: resolution: {integrity: sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==} engines: {node: '>=14'} @@ -6010,10 +5996,6 @@ packages: resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - /change-case@5.4.2: - resolution: {integrity: sha512-WB3UiTDpT+vrTilAWaJS4gaIH/jc1He4H9f6erQvraUYas90uWT0JOYFkG1imdNv710XJ6gJvqynrgOHc4ihDA==} - dev: true - /char-regex@1.0.2: resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} engines: {node: '>=10'} @@ -9109,11 +9091,6 @@ packages: dns-packet: 5.6.1 thunky: 1.1.0 - /mustache@4.2.0: - resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} - hasBin: true - dev: true - /mute-stream@1.0.0: resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} diff --git a/scripts/gridjs-binding-base.mustache b/scripts/gridjs-binding-base.mustache deleted file mode 100644 index fd3d7a4..0000000 --- a/scripts/gridjs-binding-base.mustache +++ /dev/null @@ -1,80 +0,0 @@ -// This file is generated automatically using "nx update-bindings gridjs-angular" -// Do not edit this file manually -{{#inputTypes}} -import { {{typeName}} } from '{{&importPath}}'; -{{/inputTypes}} -{{#outputTypes}} -import { {{typeName}} } from '{{&importPath}}'; -{{/outputTypes}} -import { Component, Input, Output, EventEmitter, signal, computed, effect } from '@angular/core'; -import 'preact'; - -{{#outputTypes}} -type {{typeName}}Emitter = Record>; -{{/outputTypes}} - -{{#outputTypes}} -export const {{#constantCase}}{{typeName}}{{/constantCase}}: Array = [ - {{#members}} - '{{.}}', - {{/members}} -]; -{{/outputTypes}} - -@Component({ template: '' }) -export abstract class GridJsAngularBindingBase implements GridEventsEmitter { - constructor() { - {{#inputTypes}}{{#members}} - effect(() => { - const {{.}}Val = this.{{.}}(); - const instance = this.instance(); - if ({{.}}Val === undefined || !instance) { - return; - } - instance.updateConfig({ {{.}}: {{.}}Val }); - instance.forceRender(); - }); - {{/members}}{{/inputTypes}} - } - -{{#inputTypes}} - {{#members}} - readonly {{.}} = signal<{{typeName}}['{{.}}'] | undefined>(undefined); - @Input({alias: '{{.}}'}) - set _{{.}}(value: {{typeName}}['{{.}}'] | undefined) { - this.{{.}}.set(value); - } - - {{/members}} - readonly gridConfig = signal | undefined>(undefined); - @Input({alias: 'gridConfig'}) - set _gridConfig(value: Partial<{{typeName}}> | undefined) { - this.gridConfig.set(value); - } - - readonly {{#camelCase}}{{typeName}}{{/camelCase}} = computed>(() => { - const {{#camelCase}}{{typeName}}Value{{/camelCase}}: Partial<{{typeName}}> = { - {{#members}} - {{.}}: this.{{.}}(), - {{/members}} - }; - for(let key in {{#camelCase}}{{typeName}}Value{{/camelCase}}) { - const keyName = key as keyof {{typeName}}; - if ({{#camelCase}}{{typeName}}Value{{/camelCase}}[keyName] === undefined) { - delete {{#camelCase}}{{typeName}}Value{{/camelCase}}[keyName]; - } - } - return { - ...this.gridConfig(), - ...{{#camelCase}}{{typeName}}Value{{/camelCase}} - }; - }); -{{/inputTypes}} - -{{#outputTypes}} - {{#members}} - @Output() - readonly {{.}} = new EventEmitter(); - {{/members}} -{{/outputTypes}} -} diff --git a/scripts/update-bindings.mjs b/scripts/update-bindings.mjs deleted file mode 100644 index d97f330..0000000 --- a/scripts/update-bindings.mjs +++ /dev/null @@ -1,103 +0,0 @@ -import ts from 'typescript'; -import { readFileSync, writeFileSync } from 'fs'; -import Mustache from 'mustache'; -import { camelCase, constantCase } from 'change-case'; - -const config = { - sourceTypings: [ - { - path: 'node_modules/gridjs/dist/src/config.d.ts', - importPath: 'gridjs', - bindingTypes: 'input', - }, - { - path: 'node_modules/gridjs/dist/src/events.d.ts', - importPath: 'gridjs/dist/src/events', - bindingTypes: 'output', - }, - ], - bindingClassTemplate: 'scripts/gridjs-binding-base.mustache', - outputPath: 'packages/gridjs-angular/src/lib/gridjs-binding-base.ts', -}; - -const mustacheHelpers = { - camelCase: () => (text, render) => camelCase(render(text)), - constantCase: () => (text, render) => constantCase(render(text)), - noTrailingComma: () => (text, render) => { - const result = render(text); - return result.endsWith(',') ? result.slice(0, -1) : result; - }, -}; -const template = readFileSync(config.bindingClassTemplate, 'utf-8'); - -const types = extractTypeInformation(config.sourceTypings); - -const contents = Mustache.render(template, { - inputTypes: types.filter((t) => t.bindingTypes === 'input'), - outputTypes: types.filter((t) => t.bindingTypes === 'output'), - ...mustacheHelpers, -}); -writeFileSync(config.outputPath, contents); - -function extractTypeInformation(sourceTypings) { - return sourceTypings.map(({ path, bindingTypes, importPath }) => { - const program = ts.createProgram({ - rootNames: [path], - options: {}, - }); - const checker = program.getTypeChecker(); - const sourceFile = program.getSourceFile(path); - if (!sourceFile) { - return; - } - - return ts.forEachChild(sourceFile, (node) => { - if (ts.isInterfaceDeclaration(node)) { - const symbol = checker.getSymbolAtLocation(node.name); - if (!symbol) { - return; - } - - const name = symbol.getName(); - const members = [...symbol.members.entries()] - .map((m) => ({ - name: m[0], - valueDeclaration: m[1].valueDeclaration, - })) - .filter( - (m) => m.valueDeclaration?.kind === ts.SyntaxKind.PropertySignature, - ) - .map((m) => m.name); - return { - typeName: name, - bindingTypes, - importPath, - members, - }; - } else if (ts.isTypeAliasDeclaration(node)) { // export type GridEvents = ContainerEvents & TableEvents; - const srcType = checker.getTypeAtLocation(node); - if (srcType.isIntersection()) { - const members = []; - srcType.types.forEach((t) => { - const symbol = t.getSymbol(); - if (!symbol) { - console.warn('No symbol found for type', t); - return; - } - const declaration = symbol.getDeclarations()?.[0]; - if (ts.isInterfaceDeclaration(declaration)) { // ContainerEvents & TableEvents are both interfaces - declaration.members.forEach(m => members.push(m.name?.getText())); - } - }); - - return { - typeName: node.name.getText(), - bindingTypes, - importPath, - members, - }; - } - } - }); - }); -}