diff --git a/ReadMe.md b/ReadMe.md index f386c47..6934228 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -48,6 +48,8 @@ npm i preact then configure your tool-chain: https://preactjs.com/guide/v10/getting-started#integrating-into-an-existing-pipeline +and write chart codes as this demo: https://idea2app.github.io/React-MobX-Bootstrap-ts/#/chart + #### DOM Renderer v2 & WebCell v3 ```shell @@ -81,6 +83,8 @@ export default defineConfig({ }); ``` +and write chart codes as this demo: https://idea2app.github.io/Vue-Prime-ts/#/chart + ## Simple example Origin: [ECharts official example][9] @@ -138,7 +142,7 @@ render( [4]: https://www.webcomponents.org/ [5]: https://libraries.io/npm/echarts-jsx [6]: https://github.com/idea2app/ECharts-JSX/actions/workflows/main.yml -[7]: https://github.com/ecomfe/awesome-echarts +[7]: https://github.com/ecomfe/awesome-echarts?tab=readme-ov-file#web-components [8]: https://nodei.co/npm/echarts-jsx/ [9]: https://echarts.apache.org/handbook/en/get-started/ [10]: https://codesandbox.io/p/devbox/echarts-jsx-1-0-demo-h2dz8t?file=%2Fsrc%2FBar.tsx&embed=1 diff --git a/package.json b/package.json index 32376dd..5e7280b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "echarts-jsx", - "version": "1.0.4", + "version": "1.1.0", "license": "LGPL-3.0", "author": "shiy2008@gmail.com", "description": "A real JSX wrapper for ECharts based on TypeScript & Web components", @@ -48,7 +48,7 @@ "rimraf": "^5.0.5", "typedoc": "^0.25.13", "typedoc-plugin-mdn-links": "^3.1.19", - "typescript": "~5.4.4" + "typescript": "~5.4.5" }, "prettier": { "singleQuote": true, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 863a95d..26066de 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,7 +7,7 @@ settings: dependencies: dom-renderer: specifier: ^2.1.4 - version: 2.1.4(typescript@5.4.4) + version: 2.1.4(typescript@5.4.5) echarts: specifier: '>=5' version: 5.4.3 @@ -19,7 +19,7 @@ dependencies: version: 4.17.21 web-utility: specifier: ^4.3.0 - version: 4.3.0(typescript@5.4.4) + version: 4.3.0(typescript@5.4.5) devDependencies: '@types/lodash': @@ -42,7 +42,7 @@ devDependencies: version: 8.0.0 parcel: specifier: ~2.12.0 - version: 2.12.0(typescript@5.4.4) + version: 2.12.0(typescript@5.4.5) prettier: specifier: ^3.2.5 version: 3.2.5 @@ -51,13 +51,13 @@ devDependencies: version: 5.0.5 typedoc: specifier: ^0.25.13 - version: 0.25.13(typescript@5.4.4) + version: 0.25.13(typescript@5.4.5) typedoc-plugin-mdn-links: specifier: ^3.1.19 version: 3.1.19(typedoc@0.25.13) typescript: - specifier: ~5.4.4 - version: 5.4.4 + specifier: ~5.4.5 + version: 5.4.5 packages: @@ -1559,7 +1559,7 @@ packages: - '@swc/helpers' dev: true - /@parcel/config-default@2.12.0(@parcel/core@2.12.0)(typescript@5.4.4): + /@parcel/config-default@2.12.0(@parcel/core@2.12.0)(typescript@5.4.5): resolution: {integrity: sha512-dPNe2n9eEsKRc1soWIY0yToMUPirPIa2QhxcCB3Z5RjpDGIXm0pds+BaiqY6uGLEEzsjhRO0ujd4v2Rmm0vuFg==} peerDependencies: '@parcel/core': ^2.12.0 @@ -1569,7 +1569,7 @@ packages: '@parcel/core': 2.12.0 '@parcel/namer-default': 2.12.0(@parcel/core@2.12.0) '@parcel/optimizer-css': 2.12.0(@parcel/core@2.12.0) - '@parcel/optimizer-htmlnano': 2.12.0(@parcel/core@2.12.0)(typescript@5.4.4) + '@parcel/optimizer-htmlnano': 2.12.0(@parcel/core@2.12.0)(typescript@5.4.5) '@parcel/optimizer-image': 2.12.0(@parcel/core@2.12.0) '@parcel/optimizer-svgo': 2.12.0(@parcel/core@2.12.0) '@parcel/optimizer-swc': 2.12.0(@parcel/core@2.12.0) @@ -1735,12 +1735,12 @@ packages: - '@swc/helpers' dev: true - /@parcel/optimizer-htmlnano@2.12.0(@parcel/core@2.12.0)(typescript@5.4.4): + /@parcel/optimizer-htmlnano@2.12.0(@parcel/core@2.12.0)(typescript@5.4.5): resolution: {integrity: sha512-MfPMeCrT8FYiOrpFHVR+NcZQlXAptK2r4nGJjfT+ndPBhEEZp4yyL7n1y7HfX9geg5altc4WTb4Gug7rCoW8VQ==} engines: {node: '>= 12.0.0', parcel: ^2.12.0} dependencies: '@parcel/plugin': 2.12.0(@parcel/core@2.12.0) - htmlnano: 2.1.0(svgo@2.8.0)(typescript@5.4.4) + htmlnano: 2.1.0(svgo@2.8.0)(typescript@5.4.5) nullthrows: 1.1.1 posthtml: 0.16.6 svgo: 2.8.0 @@ -3072,7 +3072,7 @@ packages: yaml: 1.10.2 dev: true - /cosmiconfig@8.3.6(typescript@5.4.4): + /cosmiconfig@8.3.6(typescript@5.4.5): resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} engines: {node: '>=14'} peerDependencies: @@ -3085,7 +3085,7 @@ packages: js-yaml: 4.1.0 parse-json: 5.2.0 path-type: 4.0.0 - typescript: 5.4.4 + typescript: 5.4.5 dev: true /cross-spawn@7.0.3: @@ -3277,11 +3277,11 @@ packages: engines: {node: '>=8'} dev: true - /dom-renderer@2.1.4(typescript@5.4.4): + /dom-renderer@2.1.4(typescript@5.4.5): resolution: {integrity: sha512-Aw/apnVyQonjHrKz1EiypOcyTQmWzXWSpNOjGEi9lZqrwEZ4YsxHe+xqyA0n3sKb7tCr8MP8efC+xsviVTP48g==} dependencies: tslib: 2.6.2 - web-utility: 4.3.0(typescript@5.4.4) + web-utility: 4.3.0(typescript@5.4.5) transitivePeerDependencies: - typescript dev: false @@ -3806,7 +3806,7 @@ packages: function-bind: 1.1.2 dev: true - /htmlnano@2.1.0(svgo@2.8.0)(typescript@5.4.4): + /htmlnano@2.1.0(svgo@2.8.0)(typescript@5.4.5): resolution: {integrity: sha512-jVGRE0Ep9byMBKEu0Vxgl8dhXYOUk0iNQ2pjsG+BcRB0u0oDF5A9p/iBGMg/PGKYUyMD0OAGu8dVT5Lzj8S58g==} peerDependencies: cssnano: ^6.0.0 @@ -3835,7 +3835,7 @@ packages: uncss: optional: true dependencies: - cosmiconfig: 8.3.6(typescript@5.4.4) + cosmiconfig: 8.3.6(typescript@5.4.5) posthtml: 0.16.6 svgo: 2.8.0 timsort: 0.3.0 @@ -4780,7 +4780,7 @@ packages: engines: {node: '>=6'} dev: true - /parcel@2.12.0(typescript@5.4.4): + /parcel@2.12.0(typescript@5.4.5): resolution: {integrity: sha512-W+gxAq7aQ9dJIg/XLKGcRT0cvnStFAQHPaI0pvD0U2l6IVLueUAm3nwN7lkY62zZNmlvNx6jNtE4wlbS+CyqSg==} engines: {node: '>= 12.0.0'} hasBin: true @@ -4788,7 +4788,7 @@ packages: '@parcel/core': optional: true dependencies: - '@parcel/config-default': 2.12.0(@parcel/core@2.12.0)(typescript@5.4.4) + '@parcel/config-default': 2.12.0(@parcel/core@2.12.0)(typescript@5.4.5) '@parcel/core': 2.12.0 '@parcel/diagnostic': 2.12.0 '@parcel/events': 2.12.0 @@ -6024,10 +6024,10 @@ packages: peerDependencies: typedoc: '>= 0.23.14 || 0.24.x || 0.25.x' dependencies: - typedoc: 0.25.13(typescript@5.4.4) + typedoc: 0.25.13(typescript@5.4.5) dev: true - /typedoc@0.25.13(typescript@5.4.4): + /typedoc@0.25.13(typescript@5.4.5): resolution: {integrity: sha512-pQqiwiJ+Z4pigfOnnysObszLiU3mVLWAExSPf+Mu06G/qsc3wzbuM56SZQvONhHLncLUhYzOVkjFFpFfL5AzhQ==} engines: {node: '>= 16'} hasBin: true @@ -6038,7 +6038,7 @@ packages: marked: 4.3.0 minimatch: 9.0.3 shiki: 0.14.7 - typescript: 5.4.4 + typescript: 5.4.5 dev: true /typescript@4.9.5: @@ -6047,8 +6047,8 @@ packages: hasBin: true dev: true - /typescript@5.4.4: - resolution: {integrity: sha512-dGE2Vv8cpVvw28v8HCPqyb08EzbBURxDpuhJvTrusShUfGnhHBafDsLdS1EhhxyL6BJQE+2cT3dDPAv+MQ6oLw==} + /typescript@5.4.5: + resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} engines: {node: '>=14.17'} hasBin: true @@ -6132,7 +6132,7 @@ packages: resolution: {integrity: sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==} dev: true - /web-utility@4.3.0(typescript@5.4.4): + /web-utility@4.3.0(typescript@5.4.5): resolution: {integrity: sha512-9GhmDtRx7xcqUWAo/Y6IUifPwM+DT/uprfov4Hd8uPkkuL5HM1Cfc5aJ331FADTkiPwh2gaoxBQ5QbxL/CSG1Q==} peerDependencies: typescript: '>=4.1' @@ -6140,7 +6140,7 @@ packages: '@swc/helpers': 0.5.6 element-internals-polyfill: 1.3.10 regenerator-runtime: 0.14.1 - typescript: 5.4.4 + typescript: 5.4.5 dev: false /which-boxed-primitive@1.0.2: diff --git a/preview/index.tsx b/preview/index.tsx index d641fac..265881c 100644 --- a/preview/index.tsx +++ b/preview/index.tsx @@ -6,7 +6,7 @@ import '../source/components/y-axis'; import '../source/charts/line'; new DOMRenderer().render( - + (); +import { + ZRElementEventName, + callBus, + unwrapEventHandler, + wrapEventHandler +} from './utility'; export abstract class ECOptionElement extends ProxyElement @@ -75,36 +78,21 @@ export abstract class ECOptionElement ? { series: [{ ...data, type: this.chartName }] } : { [this.chartTagName]: data }; - this.renderer.core.setOption(option); + this.renderer.setOption(option); }); addEventListener = callBus((name: string, handler: EventListener) => { - const wrapper: ZRElementEventHandler = detail => { - const event = new CustomEvent(name, { detail }), - meta = { enumerable: true, value: this }; - - Object.defineProperties(event, { - eventPhase: { ...meta, value: Event.AT_TARGET }, - srcElement: meta, - target: meta, - currentTarget: meta - }); - handler.call(this, event); - }; - - EventHandlerMap.set(handler, wrapper); - this.renderer.core.on( name as ZRElementEventName, this.eventSelector, - wrapper + wrapEventHandler.call(this, name, handler) ); }); removeEventListener = callBus((event: string, handler: EventListener) => { this.renderer.core.off( event as ZRElementEventName, - EventHandlerMap.get(handler) + unwrapEventHandler(handler) ); }); diff --git a/source/renderers/core.ts b/source/renderers/core.ts index d4e9bf8..a47528d 100644 --- a/source/renderers/core.ts +++ b/source/renderers/core.ts @@ -5,7 +5,13 @@ import { debounce } from 'lodash'; import { CustomElement, parseDOM } from 'web-utility'; import { ProxyElement } from '../Proxy'; -import { ZRElementEventHandler, ZRElementEventName } from '../utility'; +import { + ZRElementEventHandler, + ZRElementEventName, + callBus, + unwrapEventHandler, + wrapEventHandler +} from '../utility'; export type EChartsElementEventHandler = Partial< Record<`on${Capitalize}`, ZRElementEventHandler> @@ -31,9 +37,6 @@ export abstract class EChartsElement return this.#coreDefer.promise; } - #eventHandlers: [ZRElementEventName, ZRElementEventHandler][] = []; - #eventData = []; - get renderer() { const [_, type] = this.tagName.toLowerCase().split('-'); @@ -50,6 +53,9 @@ export abstract class EChartsElement this.attachShadow({ mode: 'open' }).append( parseDOM('
')[0] ); + this.setOption.start(); + this.removeEventListener.start(); + this.addEventListener.start(); } connectedCallback() { @@ -66,7 +72,7 @@ export abstract class EChartsElement this.core?.dispose(); } - async #init() { + #init() { var { theme, initOptions, ...props } = this.toJSON(); this.core = init( @@ -74,27 +80,17 @@ export abstract class EChartsElement theme, { ...initOptions, renderer: this.renderer } ); - this.setOption({ grid: {}, ...props }); - this.#coreDefer.resolve(); - for (const [event, handler] of this.#eventHandlers) - this.addEventListener(event, handler as unknown as EventListener); - - this.#eventHandlers.length = 0; - - for (const option of this.#eventData) this.setOption(option); - - this.#eventData.length = 0; + this.setOption({ grid: {}, ...props }); + this.setOption.run(); + this.removeEventListener.run(); + this.addEventListener.run(); } - async setOption(data: EChartsOption) { - if (!this.core) { - this.#eventData.push(data); - return; - } - this.core.setOption(data, false, true); - } + setOption = callBus((data: EChartsOption) => + this.core.setOption(data, false, true) + ); setProperty(key: string, value: any) { super.setProperty(key, value); @@ -102,32 +98,18 @@ export abstract class EChartsElement this.setOption(this.toJSON()); } - addEventListener(event: string, handler: EventListener) { - if (event === 'optionchange') - return super.addEventListener(event, handler); - - if (this.core) this.core.getZr().on(event, handler); - else - this.#eventHandlers.push([ - event as ZRElementEventName, - handler as unknown as ZRElementEventHandler - ]); - } - - removeEventListener(event: string, handler: EventListener) { - if (event === 'optionchange') - return super.removeEventListener(event, handler); - - if (this.core) this.core.getZr().off(event, handler); - else { - const index = this.#eventHandlers.findIndex( - item => - item[0] === event && - item[1] === (handler as unknown as ZRElementEventHandler) + addEventListener = callBus((name: string, handler: EventListener) => { + this.core + .getZr() + .on( + name as ZRElementEventName, + wrapEventHandler.call(this, name, handler) ); - if (index > -1) this.#eventHandlers.splice(index, 1); - } - } + }); + + removeEventListener = callBus((event: string, handler: EventListener) => { + this.core.getZr().off(event, unwrapEventHandler(handler)); + }); handleResize = debounce(() => this.core.resize(this.toJSON().resizeOptions) diff --git a/source/utility.ts b/source/utility.ts index ebd38f2..a8d4f6a 100644 --- a/source/utility.ts +++ b/source/utility.ts @@ -60,3 +60,31 @@ export const EventKeyPattern = /^on(\w+)/; export type ZRElementEventName = ECElementEvent['type']; export type ZRElementEventHandler = (event: ECElementEvent) => boolean | void; + +const EventHandlerMap = new WeakMap(); + +export function wrapEventHandler( + this: EventTarget, + name: string, + handler: EventListener +) { + const wrapper: ZRElementEventHandler = detail => { + const event = new CustomEvent(name, { detail }), + meta = { enumerable: true, value: this }; + + Object.defineProperties(event, { + eventPhase: { ...meta, value: Event.AT_TARGET }, + srcElement: meta, + target: meta, + currentTarget: meta + }); + handler.call(this, event); + }; + + EventHandlerMap.set(handler, wrapper); + + return wrapper; +} + +export const unwrapEventHandler = (handler: EventListener) => + EventHandlerMap.get(handler);