From 54d9099ed4dd176c66d8037e616eb6ae6a9a5e85 Mon Sep 17 00:00:00 2001 From: TechQuery Date: Sun, 7 Jan 2024 07:05:03 +0800 Subject: [PATCH 1/2] [optimize] replace Web Field mixin with Form Field decorator [fix] JSX props & children types --- .npmignore | 1 + .parcelrc | 8 ++++ Migrating.md | 8 ++-- ReadMe.md | 40 ++++++++-------- package.json | 8 ++-- pnpm-lock.yaml | 86 +++++++++++++++++----------------- preview/Clock.tsx | 2 +- preview/Field.tsx | 29 ++++++++++++ preview/Home.tsx | 20 +++++++- {legacy => source}/WebField.ts | 62 +++++++++++------------- source/decorator.ts | 5 +- source/index.ts | 1 + 12 files changed, 164 insertions(+), 106 deletions(-) create mode 100644 .parcelrc create mode 100644 preview/Field.tsx rename {legacy => source}/WebField.ts (51%) diff --git a/.npmignore b/.npmignore index 78f5ce2..2fedc85 100644 --- a/.npmignore +++ b/.npmignore @@ -1,3 +1,4 @@ +.parcelrc .eslintrc.json jest.config.ts test/ diff --git a/.parcelrc b/.parcelrc new file mode 100644 index 0000000..a8f22fc --- /dev/null +++ b/.parcelrc @@ -0,0 +1,8 @@ +{ + "extends": "@parcel/config-default", + "transformers": { + "*.{ts,tsx}": [ + "@parcel/transformer-typescript-tsc" + ] + } +} diff --git a/Migrating.md b/Migrating.md index f6267f7..238babf 100644 --- a/Migrating.md +++ b/Migrating.md @@ -63,8 +63,9 @@ npm install mobx On the other hand, [`mobx-web-cell` adapter][6] has been merged into the core package. And cause of replacing **Prototype Overwrite** with **Class Inheritance** to refactor **Class Mixins**, `@observer` decorator should follow strict order to make observation work: ```diff ++import { JsxProps } from 'dom-renderer'; import { - WebCellProps, +- WebCellProps, component, attribute, - watch, @@ -76,7 +77,8 @@ import { -import { observer } from 'mobx-web-cell'; +import { observable } from 'mobx'; -export interface MyTagProps extends WebCellProps { +-export interface MyTagProps extends WebCellProps { ++export interface MyTagProps extends JsxProps { count?: number } @@ -183,7 +185,7 @@ import { [JSDoc's `@deprecated` hints][7] will lead your way to rename them: 1. `mixin()` => `HTMLElement` & its Sub-classes -2. `mixinForm()` => `WebField()` +2. `mixinForm()` => `HTMLElement` & `@formField` 3. `@watch` => `@observable accessor` ## Appendix: v3 prototype diff --git a/ReadMe.md b/ReadMe.md index 3c3d347..d8fc061 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -45,7 +45,7 @@ Demo & **GitHub template**: https://web-cell.dev/scaffold/ ```shell npm init -y npm install dom-renderer mobx web-cell -npm install parcel @parcel/config-default "@parcel/transformer-typescript-tsc" -D +npm install parcel @parcel/config-default @parcel/transformer-typescript-tsc -D ``` #### `package.json` @@ -102,12 +102,12 @@ npm install parcel @parcel/config-default "@parcel/transformer-typescript-tsc" - `source/SubTag.tsx` -```jsx -import { WebCellProps, component } from 'web-cell'; +```tsx +import { component, FC, PropsWithChildren } from 'web-cell'; -export function InlineTag({ children }: WebCellProps) { - return {children}; -} +export const InlineTag: FC = ({ children }) => ( + {children} +); @component({ tagName: 'sub-tag' @@ -124,12 +124,14 @@ export class SubTag extends HTMLElement { `source/TestTag.tsx` ```tsx -import { WebCellProps, component, attribute, observer, on } from 'web-cell'; +import { JsxProps } from 'dom-renderer'; import { observable } from 'mobx'; +import { component, attribute, observer, on } from 'web-cell'; +import { stringifyCSS } from 'web-utility'; import { SubTag } from './SubTag'; -export interface TestTagProps extends WebCellProps { +export interface TestTagProps extends JsxProps { topic?: string; } @@ -152,6 +154,15 @@ export class TestTag extends HTMLElement { state = new State(); + style = stringifyCSS({ + '.topic': { + color: 'lightblue' + }, + '.topic.active': { + color: 'lightpink' + } + }); + onClick = () => (this.topic = 'Example'); @on('click', ':host h1') @@ -160,21 +171,12 @@ export class TestTag extends HTMLElement { } render() { - const { topic } = this, + const { style, topic } = this, { status } = this.state; return ( <> - +

{topic} diff --git a/package.json b/package.json index a31a8d6..a1a79f2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "web-cell", - "version": "3.0.0-rc.2", + "version": "3.0.0-rc.3", "description": "Web Components engine based on VDOM, JSX, MobX & TypeScript", "keywords": [ "web", @@ -27,7 +27,7 @@ "types": "dist/index.d.ts", "dependencies": { "@swc/helpers": "^0.5.3", - "dom-renderer": "^2.0.2", + "dom-renderer": "^2.0.4", "mobx": ">=6.11", "regenerator-runtime": "^0.14.1", "web-utility": "^4.1.3" @@ -45,8 +45,8 @@ "@parcel/transformer-typescript-tsc": "~2.11.0", "@parcel/transformer-typescript-types": "~2.11.0", "@types/jest": "^29.5.11", - "@typescript-eslint/eslint-plugin": "^6.17.0", - "@typescript-eslint/parser": "^6.17.0", + "@typescript-eslint/eslint-plugin": "^6.18.0", + "@typescript-eslint/parser": "^6.18.0", "core-js": "^3.35.0", "element-internals-polyfill": "^1.3.10", "eslint": "^8.56.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5eb06fa..686931f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,8 +9,8 @@ dependencies: specifier: ^0.5.3 version: 0.5.3 dom-renderer: - specifier: ^2.0.2 - version: 2.0.2(typescript@5.3.3) + specifier: ^2.0.4 + version: 2.0.4(typescript@5.3.3) mobx: specifier: '>=6.11' version: 6.12.0 @@ -41,11 +41,11 @@ devDependencies: specifier: ^29.5.11 version: 29.5.11 '@typescript-eslint/eslint-plugin': - specifier: ^6.17.0 - version: 6.17.0(@typescript-eslint/parser@6.17.0)(eslint@8.56.0)(typescript@5.3.3) + specifier: ^6.18.0 + version: 6.18.0(@typescript-eslint/parser@6.18.0)(eslint@8.56.0)(typescript@5.3.3) '@typescript-eslint/parser': - specifier: ^6.17.0 - version: 6.17.0(eslint@8.56.0)(typescript@5.3.3) + specifier: ^6.18.0 + version: 6.18.0(eslint@8.56.0)(typescript@5.3.3) core-js: specifier: ^3.35.0 version: 3.35.0 @@ -2067,8 +2067,8 @@ packages: '@types/yargs-parser': 21.0.3 dev: true - /@typescript-eslint/eslint-plugin@6.17.0(@typescript-eslint/parser@6.17.0)(eslint@8.56.0)(typescript@5.3.3): - resolution: {integrity: sha512-Vih/4xLXmY7V490dGwBQJTpIZxH4ZFH6eCVmQ4RFkB+wmaCTDAx4dtgoWwMNGKLkqRY1L6rPqzEbjorRnDo4rQ==} + /@typescript-eslint/eslint-plugin@6.18.0(@typescript-eslint/parser@6.18.0)(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-3lqEvQUdCozi6d1mddWqd+kf8KxmGq2Plzx36BlkjuQe3rSTm/O98cLf0A4uDO+a5N1KD2SeEEl6fW97YHY+6w==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha @@ -2079,11 +2079,11 @@ packages: optional: true dependencies: '@eslint-community/regexpp': 4.10.0 - '@typescript-eslint/parser': 6.17.0(eslint@8.56.0)(typescript@5.3.3) - '@typescript-eslint/scope-manager': 6.17.0 - '@typescript-eslint/type-utils': 6.17.0(eslint@8.56.0)(typescript@5.3.3) - '@typescript-eslint/utils': 6.17.0(eslint@8.56.0)(typescript@5.3.3) - '@typescript-eslint/visitor-keys': 6.17.0 + '@typescript-eslint/parser': 6.18.0(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/scope-manager': 6.18.0 + '@typescript-eslint/type-utils': 6.18.0(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/utils': 6.18.0(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/visitor-keys': 6.18.0 debug: 4.3.4 eslint: 8.56.0 graphemer: 1.4.0 @@ -2096,8 +2096,8 @@ packages: - supports-color dev: true - /@typescript-eslint/parser@6.17.0(eslint@8.56.0)(typescript@5.3.3): - resolution: {integrity: sha512-C4bBaX2orvhK+LlwrY8oWGmSl4WolCfYm513gEccdWZj0CwGadbIADb0FtVEcI+WzUyjyoBj2JRP8g25E6IB8A==} + /@typescript-eslint/parser@6.18.0(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-v6uR68SFvqhNQT41frCMCQpsP+5vySy6IdgjlzUWoo7ALCnpaWYcz/Ij2k4L8cEsL0wkvOviCMpjmtRtHNOKzA==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0 @@ -2106,10 +2106,10 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': 6.17.0 - '@typescript-eslint/types': 6.17.0 - '@typescript-eslint/typescript-estree': 6.17.0(typescript@5.3.3) - '@typescript-eslint/visitor-keys': 6.17.0 + '@typescript-eslint/scope-manager': 6.18.0 + '@typescript-eslint/types': 6.18.0 + '@typescript-eslint/typescript-estree': 6.18.0(typescript@5.3.3) + '@typescript-eslint/visitor-keys': 6.18.0 debug: 4.3.4 eslint: 8.56.0 typescript: 5.3.3 @@ -2117,16 +2117,16 @@ packages: - supports-color dev: true - /@typescript-eslint/scope-manager@6.17.0: - resolution: {integrity: sha512-RX7a8lwgOi7am0k17NUO0+ZmMOX4PpjLtLRgLmT1d3lBYdWH4ssBUbwdmc5pdRX8rXon8v9x8vaoOSpkHfcXGA==} + /@typescript-eslint/scope-manager@6.18.0: + resolution: {integrity: sha512-o/UoDT2NgOJ2VfHpfr+KBY2ErWvCySNUIX/X7O9g8Zzt/tXdpfEU43qbNk8LVuWUT2E0ptzTWXh79i74PP0twA==} engines: {node: ^16.0.0 || >=18.0.0} dependencies: - '@typescript-eslint/types': 6.17.0 - '@typescript-eslint/visitor-keys': 6.17.0 + '@typescript-eslint/types': 6.18.0 + '@typescript-eslint/visitor-keys': 6.18.0 dev: true - /@typescript-eslint/type-utils@6.17.0(eslint@8.56.0)(typescript@5.3.3): - resolution: {integrity: sha512-hDXcWmnbtn4P2B37ka3nil3yi3VCQO2QEB9gBiHJmQp5wmyQWqnjA85+ZcE8c4FqnaB6lBwMrPkgd4aBYz3iNg==} + /@typescript-eslint/type-utils@6.18.0(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-ZeMtrXnGmTcHciJN1+u2CigWEEXgy1ufoxtWcHORt5kGvpjjIlK9MUhzHm4RM8iVy6dqSaZA/6PVkX6+r+ChjQ==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0 @@ -2135,8 +2135,8 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 6.17.0(typescript@5.3.3) - '@typescript-eslint/utils': 6.17.0(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/typescript-estree': 6.18.0(typescript@5.3.3) + '@typescript-eslint/utils': 6.18.0(eslint@8.56.0)(typescript@5.3.3) debug: 4.3.4 eslint: 8.56.0 ts-api-utils: 1.0.3(typescript@5.3.3) @@ -2145,13 +2145,13 @@ packages: - supports-color dev: true - /@typescript-eslint/types@6.17.0: - resolution: {integrity: sha512-qRKs9tvc3a4RBcL/9PXtKSehI/q8wuU9xYJxe97WFxnzH8NWWtcW3ffNS+EWg8uPvIerhjsEZ+rHtDqOCiH57A==} + /@typescript-eslint/types@6.18.0: + resolution: {integrity: sha512-/RFVIccwkwSdW/1zeMx3hADShWbgBxBnV/qSrex6607isYjj05t36P6LyONgqdUrNLl5TYU8NIKdHUYpFvExkA==} engines: {node: ^16.0.0 || >=18.0.0} dev: true - /@typescript-eslint/typescript-estree@6.17.0(typescript@5.3.3): - resolution: {integrity: sha512-gVQe+SLdNPfjlJn5VNGhlOhrXz4cajwFd5kAgWtZ9dCZf4XJf8xmgCTLIqec7aha3JwgLI2CK6GY1043FRxZwg==} + /@typescript-eslint/typescript-estree@6.18.0(typescript@5.3.3): + resolution: {integrity: sha512-klNvl+Ql4NsBNGB4W9TZ2Od03lm7aGvTbs0wYaFYsplVPhr+oeXjlPZCDI4U9jgJIDK38W1FKhacCFzCC+nbIg==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: typescript: '*' @@ -2159,8 +2159,8 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/types': 6.17.0 - '@typescript-eslint/visitor-keys': 6.17.0 + '@typescript-eslint/types': 6.18.0 + '@typescript-eslint/visitor-keys': 6.18.0 debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 @@ -2172,8 +2172,8 @@ packages: - supports-color dev: true - /@typescript-eslint/utils@6.17.0(eslint@8.56.0)(typescript@5.3.3): - resolution: {integrity: sha512-LofsSPjN/ITNkzV47hxas2JCsNCEnGhVvocfyOcLzT9c/tSZE7SfhS/iWtzP1lKNOEfLhRTZz6xqI8N2RzweSQ==} + /@typescript-eslint/utils@6.18.0(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-wiKKCbUeDPGaYEYQh1S580dGxJ/V9HI7K5sbGAVklyf+o5g3O+adnS4UNJajplF4e7z2q0uVBaTdT/yLb4XAVA==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0 @@ -2181,9 +2181,9 @@ packages: '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0) '@types/json-schema': 7.0.15 '@types/semver': 7.5.6 - '@typescript-eslint/scope-manager': 6.17.0 - '@typescript-eslint/types': 6.17.0 - '@typescript-eslint/typescript-estree': 6.17.0(typescript@5.3.3) + '@typescript-eslint/scope-manager': 6.18.0 + '@typescript-eslint/types': 6.18.0 + '@typescript-eslint/typescript-estree': 6.18.0(typescript@5.3.3) eslint: 8.56.0 semver: 7.5.4 transitivePeerDependencies: @@ -2191,11 +2191,11 @@ packages: - typescript dev: true - /@typescript-eslint/visitor-keys@6.17.0: - resolution: {integrity: sha512-H6VwB/k3IuIeQOyYczyyKN8wH6ed8EwliaYHLxOIhyF0dYEIsN8+Bk3GE19qafeMKyZJJHP8+O1HiFhFLUNKSg==} + /@typescript-eslint/visitor-keys@6.18.0: + resolution: {integrity: sha512-1wetAlSZpewRDb2h9p/Q8kRjdGuqdTAQbkJIOUMLug2LBLG+QOjiWoSj6/3B/hA9/tVTFFdtiKvAYoYnSRW/RA==} engines: {node: ^16.0.0 || >=18.0.0} dependencies: - '@typescript-eslint/types': 6.17.0 + '@typescript-eslint/types': 6.18.0 eslint-visitor-keys: 3.4.3 dev: true @@ -2870,8 +2870,8 @@ packages: esutils: 2.0.3 dev: true - /dom-renderer@2.0.2(typescript@5.3.3): - resolution: {integrity: sha512-IoP7yRXg8+wN1HaBdaFNYt/pgvSCdz/rn5p+STG18FQvQ1LfTroEOrN09lI3JF1H0QitNa+c9QSfO1UqE50hJA==} + /dom-renderer@2.0.4(typescript@5.3.3): + resolution: {integrity: sha512-mV5EXc1+amGBUVN3YVbob30lmyavSzCgKv4Wcq9uisBsyCFtdL6H9PBWrg9bqQhr9nBPSuDoIH15yEvqPWKKDQ==} dependencies: tslib: 2.6.2 web-utility: 4.1.3(typescript@5.3.3) diff --git a/preview/Clock.tsx b/preview/Clock.tsx index e7acf0d..a3c1614 100644 --- a/preview/Clock.tsx +++ b/preview/Clock.tsx @@ -39,7 +39,7 @@ export class ClassClock extends HTMLElement implements CustomElement { clearInterval(this.timer); } - @reaction((that: ClassClock) => that.time) + @reaction(({ time }) => time) handleReaction(newValue: Date, oldValue: Date, reaction: IReactionPublic) { console.log(newValue, oldValue, reaction); } diff --git a/preview/Field.tsx b/preview/Field.tsx new file mode 100644 index 0000000..66428ce --- /dev/null +++ b/preview/Field.tsx @@ -0,0 +1,29 @@ +import { HTMLFieldProps } from 'web-utility'; +import { component, formField, observer } from '../source'; + +@component({ + tagName: 'test-field', + mode: 'open' +}) +@formField +@observer +export class TestField extends HTMLElement { + declare props: HTMLFieldProps; + + declare name: string; + declare value: string; + + render() { + const { name } = this; + + return ( + + (this.value = value) + } + /> + ); + } +} diff --git a/preview/Home.tsx b/preview/Home.tsx index 08b9614..b169320 100644 --- a/preview/Home.tsx +++ b/preview/Home.tsx @@ -1,8 +1,16 @@ +import { formToJSON } from 'web-utility'; +import { FC, PropsWithChildren } from '../source'; + import { ClassClock, FunctionClock } from './Clock'; +import { TestField } from './Field'; + +const Hello: FC = ({ children }) => ( +

Hello {children}!

+); export const HomePage = () => ( <> -

Hello Vanilla!

+ WebCell
We use the same configuration as Parcel to bundle this sandbox, you can find more info about Parcel @@ -23,5 +31,15 @@ export const HomePage = () => ( +
+ alert(JSON.stringify(formToJSON(currentTarget))) + } + > + + + + ); diff --git a/legacy/WebField.ts b/source/WebField.ts similarity index 51% rename from legacy/WebField.ts rename to source/WebField.ts index b44cc31..4c1ac39 100644 --- a/legacy/WebField.ts +++ b/source/WebField.ts @@ -1,34 +1,28 @@ import { observable } from 'mobx'; -import { - Constructor, - CustomFormElement, - CustomFormElementClass -} from 'web-utility'; - -import { WebCell, WebCellComponent } from '../source/WebCell'; -import { attribute, reaction } from '../source/decorator'; -import { WebCellProps } from './utility'; - -export type WebFieldProps = - WebCellProps; - -export type WebFieldComponent

= - CustomFormElement & WebCellComponent

; - -export type WebFieldClass

= Pick< - CustomFormElementClass, - 'observedAttributes' | 'formAssociated' -> & - Constructor>; - -export function WebField< - P extends WebFieldProps = WebFieldProps ->(): WebFieldClass

{ - class WebField extends WebCell

() implements WebFieldComponent

{ +import { CustomFormElement } from 'web-utility'; + +import { ComponentClass } from './WebCell'; +import { attribute, reaction } from './decorator'; + +/** + * `class` decorator of Form associated Web components + */ +export function formField( + Class: T, + _: ClassDecoratorContext +) { + class WebField + extends (Class as ComponentClass) + implements CustomFormElement + { + /** + * Defined in {@link component} + */ + declare internals: ElementInternals; static formAssociated = true; - @reaction((element: WebFieldComponent

) => element.value) - protected setValue(value: string) { + @reaction(({ value }) => value) + setValue(value: string) { this.internals.setFormValue(value); } @@ -38,22 +32,22 @@ export function WebField< @attribute @observable - name: string; + accessor name: string; @observable - value: string; + accessor value: string; @attribute @observable - required: boolean; + accessor required: boolean; @attribute @observable - disabled: boolean; + accessor disabled: boolean; @attribute @observable - autofocus: boolean; + accessor autofocus: boolean; set defaultValue(raw: string) { this.setAttribute('value', raw); @@ -84,5 +78,5 @@ export function WebField< return this.internals.reportValidity(); } } - return WebField; + return WebField as unknown as T; } diff --git a/source/decorator.ts b/source/decorator.ts index eac9ede..8ada531 100644 --- a/source/decorator.ts +++ b/source/decorator.ts @@ -1,4 +1,4 @@ -import { DOMRenderer, DataObject, VNode } from 'dom-renderer'; +import { DOMRenderer, DataObject, VNode, JsxChildren } from 'dom-renderer'; import { IReactionDisposer, IReactionPublic, @@ -15,6 +15,9 @@ import { import { ComponentClass } from './WebCell'; +export type PropsWithChildren

= P & { + children?: JsxChildren; +}; export type FunctionComponent

= (props: P) => VNode; export type FC

= FunctionComponent

; diff --git a/source/index.ts b/source/index.ts index 01e8821..86e9dd5 100644 --- a/source/index.ts +++ b/source/index.ts @@ -1,2 +1,3 @@ export * from './decorator'; export * from './WebCell'; +export * from './WebField'; From 12cb2a6a7d189dced40a253721f950661d46e900 Mon Sep 17 00:00:00 2001 From: TechQuery Date: Mon, 8 Jan 2024 06:03:27 +0800 Subject: [PATCH 2/2] [optimize] upgrade Async Box to Modern architecture [optimize] some Type details [optimize] update Upstream packages [remove] legacy vDOM type module --- .eslintrc.json | 1 + legacy/utility/index.ts | 2 - legacy/utility/vDOM.ts | 64 ---------------------- package.json | 6 +-- pnpm-lock.yaml | 54 +++++++++++++++---- preview/Field.tsx | 7 ++- {legacy => source}/Async.tsx | 38 ++++++++----- source/WebCell.tsx | 24 ++++++--- source/WebField.ts | 12 +++-- source/decorator.ts | 11 ++-- source/index.ts | 1 + {legacy/utility => source}/polyfill.ts | 2 - test/renderer.spec.tsx | 75 -------------------------- 13 files changed, 106 insertions(+), 191 deletions(-) delete mode 100644 legacy/utility/index.ts delete mode 100644 legacy/utility/vDOM.ts rename {legacy => source}/Async.tsx (51%) rename {legacy/utility => source}/polyfill.ts (82%) delete mode 100644 test/renderer.spec.tsx diff --git a/.eslintrc.json b/.eslintrc.json index bbbc189..f76f4a9 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -25,6 +25,7 @@ "@typescript-eslint/ban-ts-comment": "warn", "@typescript-eslint/ban-types": "warn", "@typescript-eslint/no-explicit-any": "warn", + "@typescript-eslint/no-unsafe-declaration-merging": "off", "@typescript-eslint/no-namespace": "warn", "@typescript-eslint/no-unused-vars": "warn", "@typescript-eslint/explicit-module-boundary-types": "off" diff --git a/legacy/utility/index.ts b/legacy/utility/index.ts deleted file mode 100644 index 268deb5..0000000 --- a/legacy/utility/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './vDOM'; -export * from './MobX'; diff --git a/legacy/utility/vDOM.ts b/legacy/utility/vDOM.ts deleted file mode 100644 index f7209db..0000000 --- a/legacy/utility/vDOM.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { HTMLProps, SVGProps } from 'web-utility'; -import { Key, VNode, JsxVNodeChildren, Fragment } from 'snabbdom'; - -import { ComponentMeta } from '../../source/decorator'; -import { WebCellClass, WebCellComponent } from '../../source/WebCell'; - -interface VDOMExtra { - key?: Key; - ref?: (node: T) => any; -} -interface VDOMContainer { - defaultSlot?: JsxVNodeChildren; -} - -export type VDOMData = HTMLProps & - VDOMExtra; - -export type WebCellProps = VDOMData & - VDOMContainer & { - is?: ComponentMeta['tagName']; - }; - -export type FunctionComponent

= ( - props: P -) => VNode; - -export type ComponentTag = - | string - | typeof Fragment - | FunctionComponent - | WebCellClass; - -type HTMLTags = { - [tagName in keyof HTMLElementTagNameMap]: WebCellProps< - HTMLElementTagNameMap[tagName] - >; -} & { - [tagName: string]: WebCellProps; -}; - -type SVGTags = { - [tagName in keyof SVGElementTagNameMap]: VDOMContainer & - SVGProps & - VDOMExtra; -}; - -declare global { - namespace JSX { - // @ts-ignore - interface IntrinsicElements - extends HTMLTags, - Omit {} - - interface ElementAttributesProperty { - props: WebCellProps; - } - type Element = VNode; - - interface ElementChildrenAttribute { - defaultSlot: JsxVNodeChildren; - } - type ElementClass = WebCellComponent; - } -} diff --git a/package.json b/package.json index a1a79f2..2cecb07 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "web-cell", - "version": "3.0.0-rc.3", + "version": "3.0.0-rc.4", "description": "Web Components engine based on VDOM, JSX, MobX & TypeScript", "keywords": [ "web", @@ -54,7 +54,7 @@ "husky": "^8.0.3", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", - "jsdom": "^23.1.0", + "jsdom": "^23.2.0", "lint-staged": "^15.2.0", "open-cli": "^8.0.0", "parcel": "~2.11.0", @@ -63,7 +63,7 @@ "ts-jest": "^29.1.1", "ts-node": "^10.9.2", "typedoc": "^0.25.6", - "typedoc-plugin-mdn-links": "^3.1.10", + "typedoc-plugin-mdn-links": "^3.1.11", "typescript": "~5.3.3" }, "scripts": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 686931f..efe41a3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -68,8 +68,8 @@ devDependencies: specifier: ^29.7.0 version: 29.7.0 jsdom: - specifier: ^23.1.0 - version: 23.1.0 + specifier: ^23.2.0 + version: 23.2.0 lint-staged: specifier: ^15.2.0 version: 15.2.0 @@ -95,8 +95,8 @@ devDependencies: specifier: ^0.25.6 version: 0.25.6(typescript@5.3.3) typedoc-plugin-mdn-links: - specifier: ^3.1.10 - version: 3.1.10(typedoc@0.25.6) + specifier: ^3.1.11 + version: 3.1.11(typedoc@0.25.6) typescript: specifier: ~5.3.3 version: 5.3.3 @@ -116,6 +116,14 @@ packages: '@jridgewell/trace-mapping': 0.3.20 dev: true + /@asamuzakjp/dom-selector@2.0.1: + resolution: {integrity: sha512-QJAJffmCiymkv6YyQ7voyQb5caCth6jzZsQncYCpHXrJ7RqdYG5y43+is8mnFcYubdOkr7cn1+na9BdFMxqw7w==} + dependencies: + bidi-js: 1.0.3 + css-tree: 2.3.1 + is-potential-custom-element-name: 1.0.1 + dev: true + /@babel/code-frame@7.23.5: resolution: {integrity: sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==} engines: {node: '>=6.9.0'} @@ -2430,6 +2438,12 @@ packages: safe-buffer: 5.2.1 dev: true + /bidi-js@1.0.3: + resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + dependencies: + require-from-string: 2.0.2 + dev: true + /boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} dev: true @@ -2722,6 +2736,14 @@ packages: source-map: 0.6.1 dev: true + /css-tree@2.3.1: + resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + dependencies: + mdn-data: 2.0.30 + source-map-js: 1.0.2 + dev: true + /css-what@6.1.0: resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} engines: {node: '>= 6'} @@ -4236,8 +4258,8 @@ packages: - utf-8-validate dev: true - /jsdom@23.1.0: - resolution: {integrity: sha512-wRscu8dBFxi7O65Cvi0jFRDv0Qa7XEHPix8Qg/vlXHLAMQsRWV1EDeQHBermzXf4Dt7JtFgBLbva3iTcBZDXEQ==} + /jsdom@23.2.0: + resolution: {integrity: sha512-L88oL7D/8ufIES+Zjz7v0aes+oBMh2Xnh3ygWvL0OaICOomKEPKuPnIfBJekiXr+BHbbMjrWn/xqrDQuxFTeyA==} engines: {node: '>=18'} peerDependencies: canvas: ^2.11.2 @@ -4245,6 +4267,7 @@ packages: canvas: optional: true dependencies: + '@asamuzakjp/dom-selector': 2.0.1 cssstyle: 4.0.1 data-urls: 5.0.0 decimal.js: 10.4.3 @@ -4253,7 +4276,6 @@ packages: http-proxy-agent: 7.0.0 https-proxy-agent: 7.0.2 is-potential-custom-element-name: 1.0.1 - nwsapi: 2.2.7 parse5: 7.1.2 rrweb-cssom: 0.6.0 saxes: 6.0.0 @@ -4567,6 +4589,10 @@ packages: resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} dev: true + /mdn-data@2.0.30: + resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} + dev: true + /meow@12.1.1: resolution: {integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==} engines: {node: '>=16.10'} @@ -5067,6 +5093,11 @@ packages: engines: {node: '>=0.10.0'} dev: true + /require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + dev: true + /requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} dev: true @@ -5232,6 +5263,11 @@ packages: is-fullwidth-code-point: 5.0.0 dev: true + /source-map-js@1.0.2: + resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + engines: {node: '>=0.10.0'} + dev: true + /source-map-support@0.5.13: resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} dependencies: @@ -5595,8 +5631,8 @@ packages: engines: {node: '>=14.16'} dev: true - /typedoc-plugin-mdn-links@3.1.10(typedoc@0.25.6): - resolution: {integrity: sha512-jwf08RS71CMLZ31bDqhW3Y+JRYIS5eHlVD/XwfEuk86prIlBvZqIWN4EuYtK7N757oV30yzSSZOsOdYmcp9Plw==} + /typedoc-plugin-mdn-links@3.1.11(typedoc@0.25.6): + resolution: {integrity: sha512-WmEt+FD6HKXCkcbQEmdVKtkEEJb9SLmUnGnKwKM9U5grocy9TOUQ1GmO2cXnzMBQ2i/o/wF8LK4JQf5Vch3DKg==} peerDependencies: typedoc: '>= 0.23.14 || 0.24.x || 0.25.x' dependencies: diff --git a/preview/Field.tsx b/preview/Field.tsx index 66428ce..b815db0 100644 --- a/preview/Field.tsx +++ b/preview/Field.tsx @@ -1,5 +1,7 @@ import { HTMLFieldProps } from 'web-utility'; -import { component, formField, observer } from '../source'; +import { WebField, component, formField, observer } from '../source'; + +export interface TestField extends WebField {} @component({ tagName: 'test-field', @@ -10,9 +12,6 @@ import { component, formField, observer } from '../source'; export class TestField extends HTMLElement { declare props: HTMLFieldProps; - declare name: string; - declare value: string; - render() { const { name } = this; diff --git a/legacy/Async.tsx b/source/Async.tsx similarity index 51% rename from legacy/Async.tsx rename to source/Async.tsx index 58073c3..1199dca 100644 --- a/legacy/Async.tsx +++ b/source/Async.tsx @@ -1,32 +1,42 @@ import { observable } from 'mobx'; +import { HTMLProps } from 'web-utility'; -import { ComponentTag, WebCellProps, FunctionComponent } from './utility'; -import { WebCellClass, WebCell } from '../source/WebCell'; -import { component, observer, reaction } from '../source/decorator'; -import { createCell } from './renderer'; +import { ClassComponent, WebCell, component } from './WebCell'; +import { + FunctionComponent, + WebCellComponent, + observer, + reaction +} from './decorator'; + +export type ComponentTag = string | WebCellComponent; + +export type WebCellProps = HTMLProps; export interface AsyncBoxProps extends WebCellProps { loader: () => Promise; delegatedProps?: WebCellProps; } +export interface AsyncBox extends WebCell {} + @component({ tagName: 'async-box' }) @observer -export class AsyncBox extends WebCell() { +export class AsyncBox extends HTMLElement { + declare props: AsyncBoxProps; + @observable - loader: AsyncBoxProps['loader']; + accessor loader: AsyncBoxProps['loader']; @observable - component?: ComponentTag; + accessor component: ComponentTag; @observable - delegatedProps?: AsyncBoxProps['delegatedProps']; + accessor delegatedProps: AsyncBoxProps['delegatedProps']; connectedCallback() { - super.connectedCallback(); - this.load(); } @@ -40,20 +50,20 @@ export class AsyncBox extends WebCell() { render() { const { component: Tag, props, delegatedProps } = this; - const { defaultSlot, ...data } = { ...props, ...delegatedProps }; + const { children, ...data } = { ...props, ...delegatedProps }; - return Tag && {defaultSlot}; + return Tag && {children}; } } type GetAsyncProps = T extends () => Promise<{ - default: FunctionComponent | WebCellClass; + default: FunctionComponent | ClassComponent; }> ? P : {}; export function lazy< - T extends () => Promise<{ default: FunctionComponent | WebCellClass }> + T extends () => Promise<{ default: FunctionComponent | ClassComponent }> >(loader: T) { return (props: GetAsyncProps) => ( void; + emit: (event: string, detail?: any, option?: EventInit) => boolean; +} interface DelegatedEvent { type: keyof HTMLElementEventMap; @@ -25,18 +33,18 @@ const eventMap = new WeakMap(); * `class` decorator of Web components */ export function component(meta: ComponentMeta) { - return ( + return ( Class: T, - { addInitializer }: ClassDecoratorContext + { addInitializer }: ClassDecoratorContext ) => { class RendererComponent - extends (Class as ComponentClass) - implements CustomElement + extends (Class as ClassComponent) + implements WebCell { - protected internals = this.attachInternals(); - protected renderer = new DOMRenderer(); + internals = this.attachInternals(); + renderer = new DOMRenderer(); - get root() { + get root(): ParentNode { return this.internals.shadowRoot || this; } diff --git a/source/WebField.ts b/source/WebField.ts index 4c1ac39..a2c70fb 100644 --- a/source/WebField.ts +++ b/source/WebField.ts @@ -1,18 +1,20 @@ import { observable } from 'mobx'; import { CustomFormElement } from 'web-utility'; -import { ComponentClass } from './WebCell'; +import { ClassComponent, WebCell } from './WebCell'; import { attribute, reaction } from './decorator'; +export interface WebField extends CustomFormElement, WebCell {} + /** * `class` decorator of Form associated Web components */ -export function formField( +export function formField( Class: T, _: ClassDecoratorContext ) { - class WebField - extends (Class as ComponentClass) + class FormFieldComponent + extends (Class as ClassComponent) implements CustomFormElement { /** @@ -78,5 +80,5 @@ export function formField( return this.internals.reportValidity(); } } - return WebField as unknown as T; + return FormFieldComponent as unknown as T; } diff --git a/source/decorator.ts b/source/decorator.ts index 8ada531..4a2845a 100644 --- a/source/decorator.ts +++ b/source/decorator.ts @@ -13,7 +13,7 @@ import { toHyphenCase } from 'web-utility'; -import { ComponentClass } from './WebCell'; +import { ClassComponent } from './WebCell'; export type PropsWithChildren

= P & { children?: JsxChildren; @@ -45,9 +45,9 @@ interface ReactionItem { } const reactionMap = new WeakMap(); -function wrapClass(Component: T) { +function wrapClass(Component: T) { class ObserverComponent - extends (Component as ComponentClass) + extends (Component as ClassComponent) implements CustomElement { protected disposers: IReactionDisposer[] = []; @@ -62,7 +62,8 @@ function wrapClass(Component: T) { } connectedCallback() { - const names: string[] = this.constructor['observedAttributes'], + const names: string[] = + this.constructor['observedAttributes'] || [], reactions = reactionMap.get(this) || []; this.disposers.push( @@ -109,7 +110,7 @@ function wrapClass(Component: T) { return ObserverComponent as unknown as T; } -export type WebCellComponent = FunctionComponent | ComponentClass; +export type WebCellComponent = FunctionComponent | ClassComponent; /** * `class` decorator of Web components for MobX diff --git a/source/index.ts b/source/index.ts index 86e9dd5..99184dd 100644 --- a/source/index.ts +++ b/source/index.ts @@ -1,3 +1,4 @@ export * from './decorator'; export * from './WebCell'; export * from './WebField'; +export * from './Async'; diff --git a/legacy/utility/polyfill.ts b/source/polyfill.ts similarity index 82% rename from legacy/utility/polyfill.ts rename to source/polyfill.ts index cd8c76e..269444f 100644 --- a/legacy/utility/polyfill.ts +++ b/source/polyfill.ts @@ -1,5 +1,3 @@ -import 'core-js/es/object/from-entries'; -import 'core-js/es/array/flat'; import 'element-internals-polyfill'; import { JSDOM } from 'jsdom'; diff --git a/test/renderer.spec.tsx b/test/renderer.spec.tsx deleted file mode 100644 index 94e1ffa..0000000 --- a/test/renderer.spec.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { WebCellProps, FunctionComponent } from '../source'; -import { - createCell, - Fragment, - render, - renderToStaticMarkup -} from '../source/renderer'; - -describe('Renderer', () => { - it('should render HTML attributes, CSS Styles/Classes & Dataset', () => { - render( - - Test - - ); - - expect(document.body.innerHTML.trim()).toBe( - 'Test' - ); - }); - - it('should call Function while DOM rendering', () => { - const hook = jest.fn(); - const Test = jest.fn(() => ) as FunctionComponent< - { prop1: number } & WebCellProps - >; - render(test); - - expect(hook).toBeCalledTimes(1); - expect(Test).toBeCalledWith({ prop1: 1, defaultSlot: ['test'] }); - }); - - it('should render SVG attributes, CSS Styles/Classes', () => { - render( - - - - ); - - expect(document.body.innerHTML.trim()).toBe( - '' - ); - }); - - it('should render VDOM to Markup', () => { - const source = renderToStaticMarkup( - <> -

- test -
- example -
- sample - - ); - - expect(source).toBe( - '
test
example
sample' - ); - }); -});