diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 76ea2fd..0000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,3 +0,0 @@ -custom: - - https://paypal.me/TechQuery - - https://tech-query.me/image/TechQuery-Alipay.jpg diff --git a/.github/pr-badge.yml b/.github/pr-badge.yml deleted file mode 100644 index 7400516..0000000 --- a/.github/pr-badge.yml +++ /dev/null @@ -1,17 +0,0 @@ -- icon: visualstudio - label: 'GitHub.dev' - message: 'PR-$prNumber' - color: 'blue' - url: 'https://github.dev/$owner/$repo/pull/$prNumber' - -- icon: github - label: 'GitHub codespaces' - message: 'PR-$prNumber' - color: 'black' - url: 'https://codespaces.new/$owner/$repo/pull/$prNumber' - -- icon: git - label: 'GitPod.io' - message: 'PR-$prNumber' - color: 'orange' - url: 'https://gitpod.io/?autostart=true#https://github.com/$owner/$repo/pull/$prNumber' diff --git a/.github/settings.yml b/.github/settings.yml deleted file mode 100644 index 72e1ca6..0000000 --- a/.github/settings.yml +++ /dev/null @@ -1,85 +0,0 @@ -# These settings are synced to GitHub by https://probot.github.io/apps/settings/ - -repository: - allow_merge_commit: false - - delete_branch_on_merge: true - - enable_vulnerability_alerts: true - -labels: - - name: bug - color: '#d73a4a' - description: Something isn't working - - - name: documentation - color: '#0075ca' - description: Improvements or additions to documentation - - - name: duplicate - color: '#cfd3d7' - description: This issue or pull request already exists - - - name: enhancement - color: '#a2eeef' - description: Some improvements - - - name: feature - color: '#16b33f' - description: New feature or request - - - name: good first issue - color: '#7057ff' - description: Good for newcomers - - - name: help wanted - color: '#008672' - description: Extra attention is needed - - - name: invalid - color: '#e4e669' - description: This doesn't seem right - - - name: question - color: '#d876e3' - description: Further information is requested - - - name: wontfix - color: '#ffffff' - description: This will not be worked on - -branches: - - name: main - # https://docs.github.com/en/rest/reference/repos#update-branch-protection - protection: - # Required. Require at least one approving review on a pull request, before merging. Set to null to disable. - required_pull_request_reviews: - # The number of approvals required. (1-6) - required_approving_review_count: 1 - # Dismiss approved reviews automatically when a new commit is pushed. - dismiss_stale_reviews: true - # Blocks merge until code owners have reviewed. - require_code_owner_reviews: true - # Specify which users and teams can dismiss pull request reviews. - # Pass an empty dismissal_restrictions object to disable. - # User and team dismissal_restrictions are only available for organization-owned repositories. - # Omit this parameter for personal repositories. - dismissal_restrictions: - # users: [] - # teams: [] - # Required. Require status checks to pass before merging. Set to null to disable - required_status_checks: - # Required. Require branches to be up to date before merging. - strict: true - # Required. The list of status checks to require in order to merge into this branch - contexts: [] - # Required. Enforce all configured restrictions for administrators. - # Set to true to enforce required status checks for repository administrators. - # Set to null to disable. - enforce_admins: true - # Prevent merge commits from being pushed to matching branches - required_linear_history: true - # Required. Restrict who can push to this branch. - # Team and user restrictions are only available for organization-owned repositories. - # Set to null to disable. - restrictions: null diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 3ab305e..0000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: CI & CD -on: - push: - tags: - - v* -jobs: - Build-and-Publish: - runs-on: ubuntu-latest - permissions: - contents: write - id-token: write - steps: - - uses: actions/checkout@v4 - - - uses: pnpm/action-setup@v4 - with: - version: 10 - - uses: actions/setup-node@v4 - with: - node-version: 22 - registry-url: https://registry.npmjs.org - cache: pnpm - - name: Install Dependencies - run: pnpm i --frozen-lockfile - - - name: Build & Publish - run: npm publish --access public --provenance - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - - name: Update document - uses: peaceiris/actions-gh-pages@v4 - with: - publish_dir: ./docs - personal_token: ${{ secrets.GITHUB_TOKEN }} - force_orphan: true - - - uses: amondnet/vercel-action@v25 - with: - vercel-token: ${{ secrets.VERCEL_TOKEN }} - github-token: ${{ secrets.GITHUB_TOKEN }} - vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} - vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} - working-directory: ./docs - vercel-args: --prod diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml deleted file mode 100644 index 834b9d2..0000000 --- a/.github/workflows/push.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Commit preview -on: - push: - branches-ignore: - - main -jobs: - Build-and-Deploy: - env: - VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} - VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} - VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: pnpm/action-setup@v4 - with: - version: 10 - - uses: actions/setup-node@v4 - with: - node-version: 22 - cache: pnpm - - name: Install & Build - run: | - pnpm i --frozen-lockfile - pnpm build - - - uses: amondnet/vercel-action@v25 - if: ${{ env.VERCEL_TOKEN && env.VERCEL_ORG_ID && env.VERCEL_PROJECT_ID }} - with: - vercel-token: ${{ secrets.VERCEL_TOKEN }} - github-token: ${{ secrets.GITHUB_TOKEN }} - vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} - vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} - working-directory: ./docs diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 8445b39..0000000 --- a/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -node_modules/ -package-lock.json -yarn.lock -dist/ -.parcel-cache/ -docs/ -.vscode/settings.json -.vercel diff --git a/.gitpod.yml b/.gitpod.yml deleted file mode 100644 index 168fada..0000000 --- a/.gitpod.yml +++ /dev/null @@ -1,21 +0,0 @@ -# This configuration file was automatically generated by Gitpod. -# Please adjust to your needs (see https://www.gitpod.io/docs/introduction/learn-gitpod/gitpod-yaml) -# and commit this file to your remote git repository to share the goodness with others. - -# Learn more from ready-to-use templates: https://www.gitpod.io/docs/introduction/getting-started/quickstart - -vscode: - extensions: - - yzhang.markdown-all-in-one - - redhat.vscode-yaml - - akamud.vscode-caniuse - - visualstudioexptteam.intellicode-api-usage-examples - - pflannery.vscode-versionlens - - christian-kohler.npm-intellisense - - esbenp.prettier-vscode - - eamodio.gitlens - - github.vscode-pull-request-github - - github.vscode-github-actions -tasks: - - init: pnpm i - command: npm test diff --git a/.husky/pre-commit b/.husky/pre-commit deleted file mode 100644 index 72c4429..0000000 --- a/.husky/pre-commit +++ /dev/null @@ -1 +0,0 @@ -npm test diff --git a/.husky/pre-push b/.husky/pre-push deleted file mode 100644 index d6cb288..0000000 --- a/.husky/pre-push +++ /dev/null @@ -1 +0,0 @@ -npm run build diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e2ac661 --- /dev/null +++ b/.nojekyll @@ -0,0 +1 @@ +TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. \ No newline at end of file diff --git a/.npmignore b/.npmignore deleted file mode 100644 index fcb235d..0000000 --- a/.npmignore +++ /dev/null @@ -1,7 +0,0 @@ -*.sh -.* -*.config.ts -test/ -preview/ -Contributing.md -docs/ \ No newline at end of file diff --git a/.npmrc b/.npmrc deleted file mode 100644 index f048ded..0000000 --- a/.npmrc +++ /dev/null @@ -1 +0,0 @@ -auto-install-peers = false diff --git a/.parcelrc b/.parcelrc deleted file mode 100644 index a8f22fc..0000000 --- a/.parcelrc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "@parcel/config-default", - "transformers": { - "*.{ts,tsx}": [ - "@parcel/transformer-typescript-tsc" - ] - } -} diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index 2999a2a..0000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "recommendations": [ - "yzhang.markdown-all-in-one", - "redhat.vscode-yaml", - "akamud.vscode-caniuse", - "visualstudioexptteam.intellicode-api-usage-examples", - "pflannery.vscode-versionlens", - "christian-kohler.npm-intellisense", - "esbenp.prettier-vscode", - "eamodio.gitlens", - "github.vscode-pull-request-github", - "github.vscode-github-actions", - "GitHub.copilot" - ] -} diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index d8ddc2b..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "name": "Debug Jest", - "type": "node", - "request": "launch", - "port": 9229, - "runtimeArgs": [ - "--inspect-brk", - "${workspaceRoot}/node_modules/jest/bin/jest.js", - "--runInBand" - ], - "console": "integratedTerminal", - "internalConsoleOptions": "neverOpen" - } - ] -} diff --git a/Contributing.html b/Contributing.html new file mode 100644 index 0000000..74e7f58 --- /dev/null +++ b/Contributing.html @@ -0,0 +1,4 @@ +
WebCell v3 受到了 MobX 的 本地可观察状态 思想 的深刻启发,不仅仅是 React,Web Components 可以更轻松地管理 内部状态和逻辑,无需任何复杂的操作:
+this.state 声明及其类型注解/断言this.setState() 方法的调用及其回调只需像管理 全局状态 一样声明一个 状态存储类,并在 this(即 Web Component 实例)上初始化它。然后像 MobX 一样使用并观察这些状态,一切就完成了。
import {
component,
+ observer,
- mixin,
- createCell,
- Fragment
} from 'web-cell';
+import { observable } from 'mobx';
-interface State {
+class State {
+ @observable
- key: string;
+ accessor key = '';
}
@component({ tagName: 'my-tag' })
+@observer
-export class MyTag extends mixin<{}, State>() {
+export class MyTag extends HTMLElement {
- state: Readonly<State> = {
- key: 'value'
- };
+ state = new State();
- render({}: any, { key }: State) {
+ render() {
+ const { key } = this.state;
return <>{value}</>;
}
}
+
+
+同时,shouldUpdate() {} 生命周期方法已被移除。你只需在 State 类的方法中,在状态改变之前控制逻辑即可。
DOM 属性 不同于 React 的 props,它们是 响应式的。它们不仅负责 更新组件视图,还会与 HTML 属性同步。
+MobX 的 @observable 和 reaction() 是实现上述功能的优秀 API,代码也非常清晰,因此我们添加了 mobx 包作为依赖:
npm install mobx
+
+
+另一方面,mobx-web-cell 适配器 已经合并到了核心包中。
import {
WebCellProps,
component,
attribute,
- watch,
+ observer,
- mixin,
- createCell,
- Fragment
} from 'web-cell';
-import { observer } from 'mobx-web-cell';
+import { observable } from 'mobx';
export interface MyTagProps extends WebCellProps {
count?: number
}
@component({ tagName: 'my-tag' })
@observer
-export class MyTag extends mixin<MyTagProps>() {
+export class MyTag extends HTMLElement {
+ declare props: MyTagProps;
@attribute
- @watch
+ @observable
- count = 0;
+ accessor count = 0;
- render({ count }: MyTagProps) {
+ render() {
+ const { count } = this;
return <>{count}</>;
}
}
+
+
+mode 选项控制渲染目标childrenimport {
component,
- mixin
} from 'web-cell';
@component({
tagName: 'my-tag',
- renderTarget: 'children'
})
-export class MyTag extends mixin() {
+export class MyTag extends HTMLElement {
}
+
+
+shadowRootimport {
component,
- mixin
} from 'web-cell';
@component({
tagName: 'my-tag',
- renderTarget: 'shadowRoot'
+ mode: 'open'
})
-export class MyTag extends mixin() {
+export class MyTag extends HTMLElement {
}
+
+
+render()这样使得 Shadow CSS 可以随着可观察数据的更新而响应。
++import { stringifyCSS } from 'web-utility';
import {
component,
- mixin
} from 'web-cell';
@component({
tagName: 'my-tag',
- renderTarget: 'shadowRoot',
+ mode: 'open',
- style: {
- ':host(.active)': {
- color: 'red'
- }
- }
})
-export class MyTag extends mixin() {
+export class MyTag extends HTMLElement {
render() {
return <>
+ <style>
+ {stringifyCSS({
+ ':host(.active)': {
+ color: 'red'
+ }
+ })}
+ </style>
test
</>;
}
}
+
+
+mixin() => HTMLElement 及其子类mixinForm() => HTMLElement 和 @formField@watch => @observable accessorWebCell v3 is heavily inspired by the Local Observable State idea of MobX, and not only React, Web Components can be much easier to manage the Inner State & Logic, without any complex things:
+this.state declaration & its type annotation/assertionthis.setState() method calling & its callbackJust declare a State Store class as what the Global State Managment does, and initial it on the this (a Web Component instance). Then use the state, and observe them, as MobX's usual, everything is done.
import {
component,
+ observer,
- mixin,
- createCell,
- Fragment
} from 'web-cell';
+import { observable } from 'mobx';
-interface State {
+class State {
+ @observable
- key: string;
+ accessor key = '';
}
@component({ tagName: 'my-tag' })
+@observer
-export class MyTag extends mixin<{}, State>() {
+export class MyTag extends HTMLElement {
- state: Readonly<State> = {
- key: 'value'
- };
+ state = new State();
- render({}: any, { key }: State) {
+ render() {
+ const { key } = this.state;
return <>{value}</>;
}
}
+
+
+At the same time, shouldUpdate() {} life-cycle has been dropped. You just need to control the logic before states changed in your State class methods.
DOM properties aren't like React's props, they're reactive. They are not only responsible to update Component views, but also synchronize with HTML attriutes.
+MobX's @observable & reaction() are awesome APIs to implement these above with clear codes, so we add mobx package as a dependency:
npm install mobx
+
+
+On the other hand, mobx-web-cell adapter has been merged into the core package.
import {
WebCellProps,
component,
attribute,
- watch,
+ observer,
- mixin,
- createCell,
- Fragment
} from 'web-cell';
-import { observer } from 'mobx-web-cell';
+import { observable } from 'mobx';
export interface MyTagProps extends WebCellProps {
count?: number
}
@component({ tagName: 'my-tag' })
@observer
-export class MyTag extends mixin<MyTagProps>() {
+export class MyTag extends HTMLElement {
+ declare props: MyTagProps;
@attribute
- @watch
+ @observable
- count = 0;
+ accessor count = 0;
- render({ count }: MyTagProps) {
+ render() {
+ const { count } = this;
return <>{count}</>;
}
}
+
+
+mode optionchildrenimport {
component,
- mixin
} from 'web-cell';
@component({
tagName: 'my-tag',
- renderTarget: 'children'
})
-export class MyTag extends mixin() {
+export class MyTag extends HTMLElement {
}
+
+
+shadowRootimport {
component,
- mixin
} from 'web-cell';
@component({
tagName: 'my-tag',
- renderTarget: 'shadowRoot'
+ mode: 'open'
})
-export class MyTag extends mixin() {
+export class MyTag extends HTMLElement {
}
+
+
+render()This makes Shadow CSS to react with Observable Data updating.
++import { stringifyCSS } from 'web-utility';
import {
component,
- mixin
} from 'web-cell';
@component({
tagName: 'my-tag',
- renderTarget: 'shadowRoot',
+ mode: 'open',
- style: {
- ':host(.active)': {
- color: 'red'
- }
- }
})
-export class MyTag extends mixin() {
+export class MyTag extends HTMLElement {
render() {
return <>
+ <style>
+ {stringifyCSS({
+ ':host(.active)': {
+ color: 'red'
+ }
+ })}
+ </style>
test
</>;
}
}
+
+
+mixin() => HTMLElement & its Sub-classesmixinForm() => HTMLElement & @formField@watch => @observable accessor| 1 | -A | -- - | -
| 2 | -B | -- - | -
| 3 | -C | -- - | -
Async Component in {children}
; -}); - -new DOMRenderer().render(OptionalmountedOptionalupdatedCalled at DOM tree updated
+Called every time the element is removed from the DOM.
+OptionalmountedCalled at first time of DOM tree updated
+OptionalupdatedCalled at DOM tree updated
+accessor decorator of MobX @observable for HTML attributes
class decorator of Web components
class decorator of Web components for MobX
class decorator of Web components for MobX
Method decorator of DOM Event delegation
+Method decorator of MobX reaction()
import { observable } from 'mobx';
import { component, observer, reaction } from 'web-cell';
@component({ tagName: 'my-tag' })
@observer
export class MyTag extends HTMLElement {
@observable
accessor count = 0;
@reaction(({ count }) => count)
handleCountChange(newValue: number, oldValue: number) {
console.log(`Count changed from ${oldValue} to ${newValue}`);
}
render() {
return (
<button onClick={() => this.count++}>
Up count {this.count}
</button>
);
}
}
+
+
+
简体中文 | English
+Web Components engine based on VDOM, JSX, MobX & TypeScript
+ + + + + +| feature | +WebCell 3 | +WebCell 2 | +React | +Vue | +
|---|---|---|---|---|
| JS language | +TypeScript 5 | +TypeScript 4 | +ECMAScript or TypeScript | +ECMAScript or TypeScript | +
| JS syntax | +ES decorator stage-3 | +ES decorator stage-2 | ++ | + |
| XML syntax | +JSX import | +JSX factory | +JSX factory/import | +HTML/Vue template or JSX (optional) | +
| DOM API | +Web components | +Web components | +HTML 5+ | +HTML 5+ | +
| view renderer | +DOM Renderer 2 | +SnabbDOM | +(built-in) | +SnabbDOM (forked) | +
| state API | +MobX @observable |
+this.state |
+this.state or useState() |
+this.$data or ref() |
+
| props API | +MobX @observable |
+@watch |
+this.props or props => {} |
+this.$props or defineProps() |
+
| state manager | +MobX 6+ | +MobX 4/5 | +Redux | +VueX | +
| page router | +JSX tags | +JSX tags + JSON data | +JSX tags | +JSON data | +
| asset bundler | +Parcel 2 | +Parcel 1 | +webpack | +Vite | +
npm install dom-renderer mobx web-cell
+
+
+npm install parcel @parcel/config-default @parcel/transformer-typescript-tsc -D
+
+
+package.json{
"scripts": {
"start": "parcel source/index.html --open",
"build": "parcel build source/index.html --public-url ."
}
}
+
+
+tsconfig.json{
"compilerOptions": {
"target": "ES6",
"module": "ES2020",
"moduleResolution": "Node",
"useDefineForClassFields": true,
"jsx": "react-jsx",
"jsxImportSource": "dom-renderer"
}
}
+
+
+.parcelrc{
"extends": "@parcel/config-default",
"transformers": {
"*.{ts,tsx}": ["@parcel/transformer-typescript-tsc"]
}
}
+
+
+source/index.html<script src="https://polyfill.web-cell.dev/feature/ECMAScript.js"></script>
<script src="https://polyfill.web-cell.dev/feature/WebComponents.js"></script>
<script src="https://polyfill.web-cell.dev/feature/ElementInternals.js"></script>
<script src="source/MyTag.tsx"></script>
<my-tag></my-tag>
+
+
+import { DOMRenderer } from 'dom-renderer';
import { FC, PropsWithChildren } from 'web-cell';
const Hello: FC<PropsWithChildren> = ({ children = 'World' }) => <h1>Hello, {children}!</h1>;
new DOMRenderer().render(<Hello>WebCell</Hello>);
+
+
+import { DOMRenderer } from 'dom-renderer';
import { component } from 'web-cell';
@component({
tagName: 'hello-world',
mode: 'open'
})
class Hello extends HTMLElement {
render() {
return (
<h1>
Hello, <slot />!
</h1>
);
}
}
new DOMRenderer().render(
<>
<Hello>WebCell</Hello>
{/* or */}
<hello-world>WebCell</hello-world>
</>
);
+
+
+import { DOMRenderer } from 'dom-renderer';
import { observable } from 'mobx';
import { WebCell, component, attribute, observer } from 'web-cell';
interface HelloProps {
name?: string;
}
interface Hello extends WebCell<HelloProps> {}
@component({ tagName: 'hello-world' })
@observer
class Hello extends HTMLElement implements WebCell<HelloProps> {
@attribute
@observable
accessor name = '';
render() {
return <h1>Hello, {this.name}!</h1>;
}
}
new DOMRenderer().render(<Hello name="WebCell" />);
// or for HTML tag props in TypeScript
declare global {
namespace JSX {
interface IntrinsicElements {
'hello-world': HelloProps;
}
}
}
new DOMRenderer().render(<hello-world name="WebCell" />);
+
+
+import { DOMRenderer } from 'dom-renderer';
import { observable } from 'mobx';
import { FC, observer } from 'web-cell';
class CounterModel {
@observable
accessor times = 0;
}
const couterStore = new CounterModel();
const Counter: FC = observer(() => (
<button onClick={() => (couterStore.times += 1)}>Counts: {couterStore.times}</button>
));
new DOMRenderer().render(<Counter />);
+
+
+import { DOMRenderer } from 'dom-renderer';
import { observable } from 'mobx';
import { component, observer } from 'web-cell';
@component({ tagName: 'my-counter' })
@observer
class Counter extends HTMLElement {
@observable
accessor times = 0;
handleClick = () => (this.times += 1);
render() {
return <button onClick={this.handleClick}>Counts: {this.times}</button>;
}
}
new DOMRenderer().render(<Counter />);
+
+
+import { component } from 'web-cell';
import { stringifyCSS } from 'web-utility';
@component({
tagName: 'my-button',
mode: 'open'
})
export class MyButton extends HTMLElement {
style = stringifyCSS({
'.btn': {
color: 'white',
background: 'lightblue'
}
});
render() {
return (
<>
<style>{this.style}</style>
<a className="btn">
<slot />
</a>
</>
);
}
}
+
+
+import { component } from 'web-cell';
@component({
tagName: 'my-button',
mode: 'open'
})
export class MyButton extends HTMLElement {
render() {
return (
<>
<link
rel="stylesheet"
href="https://unpkg.com/bootstrap@5.3.6/dist/css/bootstrap.min.css"
/>
<a className="btn">
<slot />
</a>
</>
);
}
}
+
+
+scoped.css.btn {
color: white;
background: lightblue;
}
+
+
+MyButton.tsximport { WebCell, component } from 'web-cell';
import styles from './scoped.css' assert { type: 'css' };
interface MyButton extends WebCell {}
@component({
tagName: 'my-button',
mode: 'open'
})
export class MyButton extends HTMLElement implements WebCell {
connectedCallback() {
this.root.adoptedStyleSheets = [styles];
}
render() {
return (
<a className="btn">
<slot />
</a>
);
}
}
+
+
+import { component, on } from 'web-cell';
@component({ tagName: 'my-table' })
export class MyTable extends HTMLElement {
@on('click', ':host td > button')
handleEdit(event: MouseEvent, { dataset: { id } }: HTMLButtonElement) {
console.log(`editing row: ${id}`);
}
render() {
return (
<table>
<tr>
<td>1</td>
<td>A</td>
<td>
<button data-id="1">edit</button>
</td>
</tr>
<tr>
<td>2</td>
<td>B</td>
<td>
<button data-id="2">edit</button>
</td>
</tr>
<tr>
<td>3</td>
<td>C</td>
<td>
<button data-id="3">edit</button>
</td>
</tr>
</table>
);
}
}
+
+
+import { observable } from 'mobx';
import { component, observer, reaction } from 'web-cell';
@component({ tagName: 'my-counter' })
@observer
export class Counter extends HTMLElement {
@observable
accessor times = 0;
handleClick = () => (this.times += 1);
@reaction(({ times }) => times)
echoTimes(newValue: number, oldValue: number) {
console.log(`newValue: ${newValue}, oldValue: ${oldValue}`);
}
render() {
return <button onClick={this.handleClick}>Counts: {this.times}</button>;
}
}
+
+
+import { DOMRenderer } from 'dom-renderer';
import { WebField, component, formField, observer } from 'web-cell';
interface MyField extends WebField {}
@component({
tagName: 'my-field',
mode: 'open'
})
@formField
@observer
class MyField extends HTMLElement implements WebField {
render() {
const { name } = this;
return (
<input name={name} onChange={({ currentTarget: { value } }) => (this.value = value)} />
);
}
}
new DOMRenderer().render(
<form method="POST" action="/api/data">
<MyField name="test" />
<button>submit</button>
</form>
);
+
+
+import { DOMRenderer } from 'dom-renderer';
import { observer, PropsWithChildren } from 'web-cell';
import { sleep } from 'web-utility';
const AsyncComponent = observer(async ({ children }: PropsWithChildren) => {
await sleep(1);
return <p>Async Component in {children}</p>;
});
new DOMRenderer().render(<AsyncComponent>WebCell</AsyncComponent>);
+
+
+AsyncTag.tsximport { FC } from 'web-cell';
const AsyncTag: FC = () => <div>Async</div>;
export default AsyncTag;
+
+
+index.tsximport { DOMRenderer } from 'dom-renderer';
import { lazy } from 'web-cell';
const AsyncTag = lazy(() => import('./AsyncTag'));
new DOMRenderer().render(<AsyncTag />);
+
+
+import { DOMRenderer } from 'dom-renderer';
new DOMRenderer().render(
<a>
<b>Async rendering</b>
</a>,
document.body,
'async'
);
+
+
+import { component } from 'web-cell';
@component({
tagName: 'async-renderer',
renderMode: 'async'
})
export class AsyncRenderer extends HTMLElement {
render() {
return (
<a>
<b>Async rendering</b>
</a>
);
}
}
+
+
+import { DOMRenderer } from 'dom-renderer';
import { AnimateCSS } from 'web-cell';
new DOMRenderer().render(
<AnimateCSS type="fadeIn" component={props => <h1 {...props}>Fade In</h1>} />
);
+
+
+npm install jsdom
+
+
+import 'web-cell/polyfill';
+
+
+https://github.com/EasyWebApp/DOM-Renderer?tab=readme-ov-file#nodejs--bun
+connectedCallbackdisconnectedCallbackattributeChangedCallbackadoptedCallbackupdatedCallbackmountedCallbackformAssociatedCallbackformDisabledCallbackformResetCallbackformStateRestoreCallbackWe recommend these libraries to use with WebCell:
+State management: MobX (also powered by TypeScript & Decorator)
+Router: Cell Router
+UI components
+HTTP request: KoAJAX (based on Koa-like middlewares)
+Utility: Web utility methods & types
+Event stream: Iterable Observer (Observable proposal)
MarkDown integration: Parcel MDX transformer (MDX Compiler plugin)
+OptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalonOptionalmountedCalled at first time of DOM tree updated
+OptionalupdatedCalled at DOM tree updated
+OptionalmountedCalled at first time of DOM tree updated
+OptionalupdatedCalled at DOM tree updated
+Async load: {children}
;\n\nexport default AsyncLoad;\n"],"names":["parcelRequire","$parcel$global","globalThis","parcelRegister","register","module","exports","Object","defineProperty","value","configurable","get","$5d5d6c86dc98af0a$export$2e2bcd8739ae039","set","s","enumerable","$ewYDA","children","jsxs"],"version":3,"file":"Async.872f5ccf.js.map"} \ No newline at end of file diff --git a/preview/Async.tsx b/preview/Async.tsx deleted file mode 100644 index 9b77808..0000000 --- a/preview/Async.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { FC, PropsWithChildren } from '../source'; - -const AsyncLoad: FCAsync load: {children}
; - -export default AsyncLoad; diff --git a/preview/Clock.tsx b/preview/Clock.tsx deleted file mode 100644 index 9738da2..0000000 --- a/preview/Clock.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { IReactionPublic, observable } from 'mobx'; -import { Second } from 'web-utility'; - -import { - attribute, - component, - observer, - on, - reaction, - WebCell -} from '../source'; -import { renderMode } from './utility'; - -class ClockModel { - @observable - accessor time = new Date(); - - constructor() { - setInterval(() => (this.time = new Date()), Second); - } -} - -const clockStore = new ClockModel(); - -export const FunctionClock = observer(() => { - const { time } = clockStore; - - return ( - - ); -}); - -export interface ClassClock extends WebCell {} - -@component({ - tagName: 'class-clock', - mode: 'open', - renderMode -}) -@observer -export class ClassClock extends HTMLElement implements WebCell { - @attribute - @observable - accessor time = new Date(); - - private timer: number; - - connectedCallback() { - this.timer = window.setInterval(() => (this.time = new Date()), Second); - } - - disconnectedCallback() { - clearInterval(this.timer); - } - - @reaction(({ time }) => time) - handleReaction(newValue: Date, oldValue: Date, reaction: IReactionPublic) { - console.info(newValue, oldValue, reaction); - } - - @on('click', 'time') - handleClick(event: MouseEvent, currentTarget: HTMLTimeElement) { - console.info(event, currentTarget); - } - - render() { - const { time } = this; - - return ( - - ); - } -} diff --git a/preview/Field.tsx b/preview/Field.tsx deleted file mode 100644 index 4855f6f..0000000 --- a/preview/Field.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { component, formField, observer, WebField } from '../source'; - -export interface TestField extends WebField {} - -@component({ - tagName: 'test-field', - mode: 'open' -}) -@formField -@observer -export class TestField extends HTMLElement implements WebField { - render() { - const { name } = this; - - return ( - - (this.value = (currentTarget as HTMLInputElement).value) - } - /> - ); - } -} diff --git a/preview/Home.tsx b/preview/Home.tsx deleted file mode 100644 index 243252a..0000000 --- a/preview/Home.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { formToJSON, sleep } from 'web-utility'; - -import { AnimateCSS, FC, lazy, observer, PropsWithChildren, WebCellProps } from '../source'; -import { ClassClock, FunctionClock } from './Clock'; -import { TestField } from './Field'; - -const AsyncLoad = lazy(() => import('./Async')); - -const Hello: FCAsync Component: {children}
; -}); - -export const HomePage = () => ( - <> -
Called at first time of DOM tree updated
+