├── packages
├── jeact
│ ├── index.ts
│ ├── index.js
│ ├── src
│ │ ├── index.ts
│ │ ├── utils.ts
│ │ ├── v-node.ts
│ │ ├── component.ts
│ │ ├── dom.ts
│ │ └── diff.ts
│ ├── package.json
│ └── README.MD
└── jeact-components
│ ├── index.ts
│ ├── index.js
│ ├── src
│ ├── index.ts
│ ├── modal
│ │ ├── index.less
│ │ └── index.tsx
│ ├── pm-selector
│ │ ├── index.less
│ │ └── index.tsx
│ ├── picture-selector
│ │ ├── index.less
│ │ └── index.tsx
│ ├── single-cascader
│ │ ├── index.less
│ │ └── index.tsx
│ └── cascader
│ │ ├── index.less
│ │ └── index.tsx
│ └── package.json
├── .prettierignore
├── .eslintrc.js
├── .prettierrc.js
├── scripts
├── dev.js
├── utils.js
├── build.js
└── release.js
├── .gitignore
├── .stylelintrc
├── LICENSE
├── tsconfig.json
├── README.MD
├── package.json
├── rollup.config.js
├── docs
├── index.html
└── js
│ └── jeact.iife.js
└── CHANGELOG.md
/packages/jeact/index.ts:
--------------------------------------------------------------------------------
1 | export * from './src';
2 |
--------------------------------------------------------------------------------
/packages/jeact-components/index.ts:
--------------------------------------------------------------------------------
1 | export * from './src';
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | **/*.md
2 | **/*.svg
3 | **/*.ejs
4 | **/*.html
5 | package.json
6 |
--------------------------------------------------------------------------------
/packages/jeact/index.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | module.exports = require('./dist/jeact.umd.js')
4 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: [require.resolve('@umijs/fabric/dist/eslint')],
3 | };
4 |
--------------------------------------------------------------------------------
/packages/jeact-components/index.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | module.exports = require('./dist/jqvm-components.umd.js')
4 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | const fabric = require('@umijs/fabric');
2 | module.exports = {
3 | ...fabric.prettier,
4 | };
5 |
6 |
--------------------------------------------------------------------------------
/packages/jeact-components/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './cascader';
2 | export * from './single-cascader';
3 | export * from './pm-selector';
4 | export * from './picture-selector';
5 | export * from './modal';
6 |
--------------------------------------------------------------------------------
/packages/jeact/src/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 简易的MVVM插件
3 | * 可直接嵌入到任何JS代码中,把某个DOM和其子节点设置为MVVM管理
4 | *
5 | * author mifan
6 | */
7 |
8 | export * from './utils';
9 | export * from './component';
10 | export * from './v-node';
11 |
--------------------------------------------------------------------------------
/packages/jeact/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jeact",
3 | "version": "0.2.0",
4 | "description": "jeact-类react嵌入式UI库",
5 | "main": "index.js",
6 | "module": "src/index.ts",
7 | "author": "fanzicai",
8 | "license": "MIT",
9 | "homepage": "https://github.com/qqabcv520/jeact",
10 | "bugs": "https://github.com/qqabcv520/jeact/issues",
11 | "repository": {
12 | "url": "https://github.com/qqabcv520/jeact",
13 | "type": "git"
14 | },
15 | "files": [
16 | "index.js",
17 | "src/**/*.d.ts",
18 | "dist",
19 | "README.MD"
20 | ],
21 | "peerDependencies": {
22 | "jquery": "^3.5.1"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/packages/jeact-components/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@jeact/component",
3 | "version": "0.2.0",
4 | "description": "@jeact/component",
5 | "main": "index.js",
6 | "module": "dist/jeact-component.es.js",
7 | "author": "fanzicai",
8 | "license": "MIT",
9 | "dependencies": {
10 | "classname": "^0.0.0",
11 | "sortablejs": "^1.10.2",
12 | "jeact": "~0.2.0",
13 | "jquery": "^3.5.0"
14 | },
15 | "devDependencies": {
16 | "@types/sortablejs": "^1.10.4"
17 | },
18 | "files": [
19 | "index.js",
20 | "src/**/*.d.ts",
21 | "dist"
22 | ],
23 | "peerDependencies": {
24 | "jquery": "^3.5.0"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/scripts/dev.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | const execa = require('execa')
4 | const { fuzzyMatchTarget } = require('./utils')
5 | const args = require('minimist')(process.argv.slice(2))
6 | const target = args._.length ? fuzzyMatchTarget(args._)[0] : 'jeact'
7 | const sourceMap = args.sourcemap || args.s || true
8 | const formats = args.format || args.f
9 |
10 | execa(
11 | 'rollup',
12 | [
13 | '-wc',
14 | '--environment',
15 | [
16 | `TARGET:${target}`,
17 | `FORMATS:${formats || 'iife'}`,
18 | sourceMap ? `SOURCE_MAP:true` : ``
19 | ]
20 | .filter(Boolean)
21 | .join(',')
22 | ],
23 | {
24 | stdio: 'inherit'
25 | }
26 | )
27 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /tmp
5 | /out-tsc
6 | /**/dist/
7 | *.d.ts
8 |
9 | # dependencies
10 | /node_modules
11 |
12 | # IDEs and editors
13 | /.idea
14 | *.iml
15 | .project
16 | .classpath
17 | .c9/
18 | *.launch
19 | .settings/
20 | .vscode
21 | *.sublime-workspace
22 |
23 | # IDE - VSCode
24 | .vscode/*
25 | !.vscode/settings.json
26 | !.vscode/tasks.json
27 | !.vscode/launch.json
28 | !.vscode/extensions.json
29 |
30 | # misc
31 | /.sass-cache
32 | /connect.lock
33 | /coverage
34 | /libpeerconnection.log
35 | npm-debug.log
36 | yarn-error.log
37 | testem.log
38 | /typings
39 |
40 | # System Files
41 | .DS_Store
42 | Thumbs.db
43 | .history
44 |
--------------------------------------------------------------------------------
/packages/jeact-components/src/modal/index.less:
--------------------------------------------------------------------------------
1 | .bgx-modal {
2 | position: fixed;
3 | left: 0;
4 | top: 0;
5 | right: 0;
6 | bottom: 0;
7 | overflow: scroll;
8 | background-color: rgba(0, 0, 0, 0.4);
9 | z-index: 1000;
10 | &-wrapper {
11 | margin: 40px auto;
12 | border-radius: 4px;
13 | overflow: hidden;
14 | width: 1000px;
15 | }
16 | &-title {
17 | background-color: #fff;
18 | padding: 8px;
19 | border-bottom: 1px #eee solid;
20 | }
21 | &-close {
22 | float: right;
23 | cursor: pointer;
24 | }
25 | &-mask {
26 | position: fixed;
27 | top: 0;
28 | bottom: 0;
29 | width: 100%;
30 | }
31 | &-buttons {
32 | height: 50px;
33 | background-color: #fff;
34 | display: flex;
35 | align-items: center;
36 | flex-direction: row-reverse;
37 | & > * {
38 | margin-right: 8px;
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/.stylelintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [ "stylelint-config-standard" ],
3 | "plugins": [
4 | "stylelint-declaration-block-no-ignored-properties"
5 | ],
6 | "rules": {
7 | "plugin/declaration-block-no-ignored-properties": true,
8 | "selector-max-id": 0,
9 | "selector-class-pattern": ["^[a-z\\-]+\\(?", {
10 | "resolveNestedSelectors": true
11 | }],
12 | "rule-empty-line-before": ["always", {
13 | "except": "inside-block",
14 | "ignore": "after-comment"
15 | }],
16 | "selector-pseudo-element-no-unknown": [
17 | true,
18 | {
19 | "ignorePseudoElements": ["ng-deep"]
20 | }
21 | ],
22 | "no-descending-specificity": null,
23 | "selector-type-no-unknown": null,
24 | "function-name-case": null,
25 | "function-whitespace-after": null
26 | },
27 | "ignoreFiles": ["src/assets/**/*"]
28 | }
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2018-present, Fanzicai
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "noImplicitAny": false, //强类型检查
5 | "noImplicitThis": true, // 禁止this为any类型
6 | "module": "esnext", //组织代码方式
7 | "target": "ES6", //编译目标平台
8 | "allowJs": true, //允许使用js
9 | "allowSyntheticDefaultImports": true, //允许从没有设置默认导出的模块中默认导入。这并不影响代码的显示,仅为了类型检查。
10 | "forceConsistentCasingInFileNames": true,
11 | "noImplicitReturns": true,
12 | "suppressImplicitAnyIndexErrors": true,
13 | "noUnusedLocals": true,
14 | "skipLibCheck": true,
15 | "experimentalDecorators": true,
16 | "strict": false,
17 | "esModuleInterop": true,
18 | "moduleResolution": "node",
19 | "types": ["node", "jquery"],
20 | "jsx": "react",
21 | "jsxFactory": "createVNode",
22 | "lib": [
23 | "es2015",
24 | "es2016.array.include",
25 | "esnext.array",
26 | "dom"
27 | ],
28 | "rootDir": ".",
29 | "paths": {
30 | "@jeact/*": ["packages/*"],
31 | "jeact": ["packages/jeact"]
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/scripts/utils.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const chalk = require('chalk')
3 |
4 | const packagesName = fs.readdirSync('packages').filter(f => {
5 | if (!fs.statSync(`packages/${f}`).isDirectory()) {
6 | return false
7 | }
8 | if (!fs.existsSync(`packages/${f}/package.json`)) {
9 | return false
10 | }
11 | const pkg = require(`../packages/${f}/package.json`)
12 | if (pkg.private && !pkg.buildOptions) {
13 | return false
14 | }
15 | return true
16 | })
17 |
18 | exports.packagesName = packagesName;
19 |
20 | exports.fuzzyMatchTarget = (partialTargets, includeAllMatching) => {
21 | const matched = []
22 | partialTargets.forEach(partialTarget => {
23 | for (const target of packagesName) {
24 | if (target.match(partialTarget)) {
25 | matched.push(target)
26 | if (!includeAllMatching) {
27 | break
28 | }
29 | }
30 | }
31 | })
32 | if (matched.length) {
33 | return matched
34 | } else {
35 | console.log()
36 | console.error(
37 | ` ${chalk.bgRed.white(' ERROR ')} ${chalk.red(
38 | `Target ${chalk.underline(partialTargets)} not found!`
39 | )}`
40 | )
41 | console.log()
42 |
43 | process.exit(1)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/scripts/build.js:
--------------------------------------------------------------------------------
1 | const execa = require('execa')
2 | const fs = require('fs-extra');
3 | const path = require('path')
4 | const { fuzzyMatchTarget, packagesName: allTargets } = require('./utils')
5 | const args = require('minimist')(process.argv.slice(2))
6 |
7 | const targets = args._
8 | const formats = args.formats || args.f
9 | const buildAllMatching = args.all || args.a
10 | const sourceMap = args.sourcemap || args.s
11 | const type = args.type || args.t
12 | const prod = args.prod || args.p
13 |
14 |
15 |
16 | if (!targets.length) {
17 | buildAll(allTargets)
18 | } else {
19 | buildAll(fuzzyMatchTarget(targets, buildAllMatching))
20 | }
21 |
22 |
23 | async function buildAll(names) {
24 | for (const packageName of names) {
25 | await build(packageName)
26 | }
27 | }
28 |
29 |
30 | async function build(target) {
31 | execa(
32 | 'rollup',
33 | [
34 | '-c',
35 | '--environment',
36 | [
37 | `NODE_ENV:${prod ? 'production' : 'development'}`,
38 | `TARGET:${target}`,
39 | formats ? `FORMATS:${formats}` : ``,
40 | type ? `TYPES:true` : ``,
41 | sourceMap ? `SOURCE_MAP:true` : ``
42 | ]
43 | .filter(Boolean)
44 | .join(',')
45 | ],
46 | { stdio: 'inherit' }
47 | )
48 | }
49 |
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
1 | # jeact
2 | 
3 |
4 | 在JQuery环境下用MVVM开发复杂组件,仅几kb超小体积。
5 |
6 | 定义一个switch组件,挂载到jquery后,`$('#el').switch()`就可以将#el变成一个开关组件,并且`$('#el').val()`就能获取值(js也能取),放进form也能直接提交,专门解决老旧jquery项目维护难的问题。
7 |
8 |
9 | ## 支持环境
10 |
11 | | Edge | Firefox | Chrome |
12 | | --- | --- | --- |
13 | | last 2 versions | last 2 versions | last 2 versions |
14 |
15 | ## 安装
16 |
17 | npm
18 | ```shell script
19 | npm i jeact --save
20 | ```
21 | yarn
22 | ```shell script
23 | yarn add jeact
24 | ```
25 | 或者直接引入
26 | ```html
27 |
28 | ```
29 |
30 |
31 | ## 使用
32 |
33 |
34 | [在线演示](https://qqabcv520.github.io/jeact/)
35 |
36 | 定义一个表单组件并挂载到jquery
37 |
38 | ```jsx
39 | import { ValueComponent, createVNode } from 'jeact';
40 | // 定义组件
41 | class SwitchComponent extends ValueComponent {
42 |
43 | // xxx
44 |
45 | render () {
46 | return (
47 |
48 | {/*xxx*/}
49 |
50 | );
51 | }
52 | }
53 | // 挂载到jquery,名称为customSwitch
54 | mountComponent({
55 | name: 'customSwitch',
56 | componentType: SwitchComponent,
57 | })
58 | ```
59 |
60 | 直接在HTML中使用刚刚挂载的`customSwitch`组件,初始值为`true`
61 | ```html
62 |
63 | ```
64 |
65 | 使用jquery初始化`customSwitch`组件
66 |
67 | ```html
68 |
69 | ```
70 | ```js
71 | $('#switchInput').customSwitch(); // 初始化组件
72 | $('#switchInput').customSwitch('setOpen', true) // 调用组件方法,设置open为true
73 | $('#switchInput').on('change', function() { // 监听事件
74 | console.log($(this).val());
75 | })
76 | ```
77 |
78 | 完整用法看[这里](https://github.com/qqabcv520/jeact/blob/master/packages/jeact/README.MD)
79 |
80 | ## License
81 |
82 | [MIT](http://opensource.org/licenses/MIT)
83 |
84 | Copyright (c) 2018-present, Fanzica
85 |
--------------------------------------------------------------------------------
/packages/jeact/src/utils.ts:
--------------------------------------------------------------------------------
1 | import { Component, Type } from './component';
2 |
3 | // 判空
4 | export function isEmpty(value: any) {
5 | return value == null || value.length === 0;
6 | }
7 |
8 | // 驼峰/下划线 转 短横线命名(kebab-case)
9 | export function getKebabCase(str: string): string {
10 | const reg = /^([A-Z$]+)/g;
11 | const reg2 = /_([a-zA-Z$]+)/g;
12 | const reg3 = /([A-Z$]+)/g;
13 | return str
14 | .replace(reg, ($, $1) => $1.toLowerCase())
15 | .replace(reg2, ($, $1) => '-' + $1.toLowerCase())
16 | .replace(reg3, ($, $1) => '-' + $1.toLowerCase());
17 | }
18 |
19 | export interface MountComponentArgs {
20 | name: string;
21 | componentType: Type;
22 | props: string[];
23 | $?: any;
24 | }
25 |
26 | // 挂载为jquery插件
27 | export function mountComponent({
28 | name,
29 | componentType,
30 | props,
31 | $ = window['jQuery'],
32 | }: MountComponentArgs) {
33 | if ($ == null) {
34 | return;
35 | }
36 | $.fn[name] = function (...args: any[]) {
37 | if (typeof args[0] === 'string') {
38 | const [propName, ...methodArgs] = args;
39 | const component = this.data(name);
40 | if (!component) {
41 | $.error(`节点不是一个 ${name} 组件`);
42 | }
43 | if (typeof component[propName] === 'function') {
44 | component[propName](...methodArgs);
45 | } else if (methodArgs != null && methodArgs.length === 1) {
46 | return (component[propName] = methodArgs[0]);
47 | } else {
48 | return component[propName];
49 | }
50 | } else if (args[0] == null || typeof args[0] === 'object') {
51 | const methodArgs = args[0];
52 | const component = Component.create(componentType, methodArgs, this[0]);
53 | this.data(name, component);
54 | } else {
55 | $.error('第一个参数只能是string或object');
56 | }
57 | return this;
58 | };
59 |
60 | $(function () {
61 | $(`[${getKebabCase(name)}]`).each(function (this) {
62 | const $selected = $(this);
63 | const propsValue = (props || []).reduce((pre, curr) => {
64 | pre[curr] = $selected.attr(getKebabCase(curr));
65 | return pre;
66 | }, {});
67 | $selected[name](propsValue);
68 | });
69 | });
70 | }
71 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "private": true,
4 | "workspaces": [
5 | "packages/*"
6 | ],
7 | "scripts": {
8 | "build": "node scripts/build.js",
9 | "dev": "node scripts/dev.js",
10 | "release": "node scripts/release.js",
11 | "prettier": "prettier --write \"packages/*/src/**/*\"",
12 | "lint": "npm run eslint && npm run stylelint",
13 | "lint:fix": "npm run eslint:fix && npm run stylelint:fix",
14 | "eslint": "eslint \"packages/*/src/**/*.{ts,tsx,js,jsx}\"",
15 | "eslint:fix": "eslint \"packages/*/src/**/*.{ts,tsx,js,jsx}\" --fix",
16 | "stylelint": "stylelint \"packages/*/src/**/*.less\" --syntax less",
17 | "stylelint:fix": "stylelint \"packages/*/src/**/*.less\" --syntax less --fix",
18 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0"
19 | },
20 | "author": "fanzicai",
21 | "license": "MIT",
22 | "devDependencies": {
23 | "@commitlint/cli": "^8.2.0",
24 | "@commitlint/config-conventional": "^8.2.0",
25 | "@rollup/plugin-commonjs": "^13.0.0",
26 | "@rollup/plugin-json": "^4.1.0",
27 | "@rollup/plugin-node-resolve": "^8.1.0",
28 | "@rollup/plugin-replace": "^2.3.3",
29 | "@types/core-js": "^2.5.1",
30 | "@types/jquery": "^3.3.38",
31 | "@types/node": "^10.12.18",
32 | "@umijs/fabric": "^2.4.11",
33 | "autoprefixer": "^9.8.4",
34 | "chalk": "^4.1.0",
35 | "codelyzer": "^6.0.0",
36 | "conventional-changelog-cli": "^2.0.31",
37 | "cross-env": "^7.0.2",
38 | "cssnano": "^4.1.10",
39 | "enquirer": "^2.3.5",
40 | "execa": "^4.0.2",
41 | "fs-extra": "^9.0.1",
42 | "husky": "^4.2.5",
43 | "less": "^3.11.3",
44 | "optimize-css-assets-webpack-plugin": "^5.0.1",
45 | "prettier": "^2.2.1",
46 | "rollup": "^2.18.1",
47 | "rollup-plugin-postcss": "^3.1.2",
48 | "rollup-plugin-terser": "^6.1.0",
49 | "rollup-plugin-typescript2": "^0.27.1",
50 | "semver": "^7.3.2",
51 | "stylelint": "^13.6.1",
52 | "stylelint-config-standard": "^19.0.0",
53 | "stylelint-declaration-block-no-ignored-properties": "^2.3.0",
54 | "tslint": "~6.1.2",
55 | "typescript": "^3.9.3"
56 | },
57 | "dependencies": {},
58 | "browserslist": [
59 | "last 2 Chrome versions",
60 | "last 2 Edge versions",
61 | "last 2 Firefox versions"
62 | ],
63 | "lint-staged": {
64 | "packages/*/src/**/*.ts": [
65 | "npm run lint:ts",
66 | "git add"
67 | ],
68 | "packages/*/src/**/*.less": [
69 | "npm run lint:style",
70 | "git add"
71 | ]
72 | },
73 | "commitlint": {
74 | "extends": [
75 | "@commitlint/config-conventional"
76 | ]
77 | },
78 | "husky": {
79 | "hooks": {
80 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import path from 'path'
2 | import typescript from 'rollup-plugin-typescript2'
3 | import json from '@rollup/plugin-json'
4 | import { terser } from "rollup-plugin-terser";
5 | import postcss from 'rollup-plugin-postcss'
6 | import autoprefixer from 'autoprefixer'
7 | import commonjs from '@rollup/plugin-commonjs'
8 | import { nodeResolve } from '@rollup/plugin-node-resolve'
9 |
10 | if (!process.env.TARGET) {
11 | throw new Error('TARGET package must be specified via --environment flag.')
12 | }
13 |
14 | const packagesDir = path.resolve(__dirname, 'packages')
15 | const packageDir = path.resolve(packagesDir, process.env.TARGET)
16 | const name = path.basename(packageDir)
17 | const resolve = p => path.resolve(packageDir, p)
18 | const pkg = require(resolve(`package.json`))
19 |
20 | // 对多个formats,只执行一次检查
21 | let hasTSChecked = false
22 |
23 | const defaultFormats = ['es', 'iife']
24 | const inlineFormats = process.env.FORMATS && process.env.FORMATS.split(',')
25 | const packageFormats = inlineFormats || defaultFormats
26 |
27 | const enableProd = process.env.NODE_ENV === 'production'
28 | const enableSourceMap = !!process.env.SOURCE_MAP
29 | const enableType = !!process.env.TYPES
30 |
31 |
32 | export default packageFormats.map(format => createConfig(format))
33 |
34 | function createConfig(format) {
35 |
36 | const output = {
37 | file: resolve(`dist/${name}.${format}.js`),
38 | sourcemap: enableSourceMap,
39 | externalLiveBindings: false,
40 | format,
41 | name: getUpperCamelCase(name),
42 | }
43 |
44 | const isBrowser = ['umd', 'iife', 'amd', 'system'].includes(format)
45 |
46 | const shouldEmitDeclarations = enableType && !hasTSChecked
47 |
48 | hasTSChecked = true
49 |
50 | const external = [...Object.keys(pkg.peerDependencies || {})]
51 | if (!isBrowser) {
52 | external.push(...Object.keys(pkg.dependencies || {}))
53 | }
54 |
55 | const plugins = [
56 | json({
57 | namedExports: false
58 | }),
59 | typescript({
60 | check: enableProd && !hasTSChecked,
61 | tsconfig: path.resolve(__dirname, 'tsconfig.json'),
62 | cacheRoot: path.resolve(__dirname, 'node_modules/.rts2_cache'),
63 | tsconfigOverride: {
64 | compilerOptions: {
65 | sourceMap: enableSourceMap,
66 | declaration: shouldEmitDeclarations,
67 | },
68 | include: [
69 | `./packages/${name}/src/**/*`
70 | ]
71 | },
72 | useTsconfigDeclarationDir: true,
73 | }),
74 | postcss({
75 | plugins: [autoprefixer],
76 | extract: true,
77 | sourceMap: enableSourceMap,
78 | minimize: enableProd
79 | }),
80 | commonjs({
81 | sourceMap: false
82 | }),
83 | nodeResolve({
84 | preferBuiltins: true
85 | }),
86 | ];
87 | if (enableProd) {
88 | plugins.push(
89 | terser({
90 | module: format === 'es',
91 | compress: {
92 | ecma: 2015,
93 | pure_getters: true
94 | }
95 | })
96 | );
97 | }
98 | return {
99 | input: resolve('index.ts'),
100 | plugins,
101 | external,
102 | output,
103 | }
104 | }
105 |
106 |
107 | /**
108 | * 短横线/下划线/小驼峰 转 大驼峰命名(UpperCamelCase)
109 | */
110 | export function getUpperCamelCase(str) {
111 | const reg = /(^|-|_)(\w)/g;
112 | return str.replace(reg, ($, $1, $2) => $2.toUpperCase());
113 | }
114 |
--------------------------------------------------------------------------------
/packages/jeact/src/v-node.ts:
--------------------------------------------------------------------------------
1 | import { Component, FunctionComponent, Type } from './component';
2 |
3 | export abstract class VNode {
4 | el: Node;
5 | key: any;
6 | }
7 |
8 | export class VElement extends VNode {
9 | el: HTMLElement;
10 | key: any;
11 | ref: string;
12 | constructor(
13 | public type: string | Function | Type,
14 | public attributes: { [key: string]: any } = {},
15 | public handles: { [key: string]: () => void } = {},
16 | public children: VNode[] = [],
17 | ) {
18 | super();
19 | }
20 | }
21 |
22 | export interface VFunction {
23 | el: Node;
24 | key: any;
25 | ref: string;
26 | type: () => VNode;
27 | component: FunctionComponent;
28 | attributes: { [key: string]: any };
29 | handles: { [key: string]: () => void };
30 | children: VNode[];
31 | }
32 |
33 | export interface VDom {
34 | el: HTMLElement;
35 | key: any;
36 | ref: string;
37 | type: string;
38 | attributes: { [key: string]: any };
39 | handles: { [key: string]: () => void };
40 | children: VNode[];
41 | }
42 |
43 | export interface VComponent {
44 | el: Node;
45 | key: any;
46 | ref: string;
47 | type: Type;
48 | component: T;
49 | attributes: { [key: string]: any };
50 | handles: { [key: string]: () => void };
51 | children: VNode[];
52 | }
53 |
54 | export class VText extends VNode {
55 | el: Text;
56 | content: string;
57 | key: any;
58 | constructor(content: any) {
59 | super();
60 | this.content = String(content || content === 0 ? content : '');
61 | }
62 | }
63 |
64 | export function createVNode(
65 | type: string | Function | Type,
66 | props: { [key: string]: any },
67 | ...children: any[]
68 | ) {
69 | let handle = {};
70 | let attribute = {};
71 | if (props) {
72 | handle = Object.keys(props)
73 | .filter((value) => value.startsWith('on'))
74 | .reduce((pre, curr) => {
75 | pre[curr] = props[curr];
76 | return pre;
77 | }, {});
78 | attribute = Object.keys(props)
79 | .filter((value) => !value.startsWith('on'))
80 | .reduce((pre, curr) => {
81 | pre[curr] = props[curr];
82 | return pre;
83 | }, {});
84 | }
85 | const vNodeChildren = children.flat(2).map((value) => {
86 | return isVElement(value) ? value : new VText(value);
87 | });
88 | return new VElement(type, attribute, handle, vNodeChildren);
89 | }
90 |
91 | export function isVNode(vNode: any): vNode is VNode {
92 | return vNode instanceof VNode;
93 | }
94 |
95 | export function isVElement(vNode: VNode): vNode is VElement {
96 | return vNode && (vNode).type != null;
97 | }
98 |
99 | export function isVText(vNode: VNode): vNode is VText {
100 | return vNode && (vNode).content !== undefined;
101 | }
102 |
103 | export function isVDom(vNode: VNode): vNode is VDom {
104 | return isVElement(vNode) && typeof vNode.type === 'string';
105 | }
106 |
107 | export function isVComponent(vNode: VNode): vNode is VComponent {
108 | return (
109 | isVElement(vNode) &&
110 | typeof vNode.type === 'function' &&
111 | vNode.type.prototype &&
112 | vNode.type.prototype.render
113 | );
114 | }
115 |
116 | export function isVFunction(vNode: VNode): vNode is VFunction {
117 | return (
118 | isVElement(vNode) &&
119 | typeof vNode.type === 'function' &&
120 | !(vNode.type.prototype && vNode.type.prototype.render)
121 | );
122 | }
123 |
--------------------------------------------------------------------------------
/packages/jeact-components/src/modal/index.tsx:
--------------------------------------------------------------------------------
1 | import './index.less';
2 | import type { Type, VNode, ComponentProps } from 'jeact';
3 | import { Component, isVNode, createVNode } from 'jeact';
4 |
5 | export type ModalComponentProps = {
6 | content: Type;
7 | width: string;
8 | contentProps: any;
9 | } & ComponentProps
10 |
11 | export class ModalComponent extends Component {
12 | content: Type | (() => VNode);
13 | style: string;
14 | contentProps: Partial;
15 |
16 | maskCloseable: boolean;
17 | title: VNode | string;
18 |
19 | buttons: VNode | VNode[];
20 | onCancel: (instance: T) => void;
21 | onOk: (instance: T) => Promise | boolean | void;
22 |
23 | constructor(args: ModalComponentProps) {
24 | super(args);
25 | }
26 |
27 | readonly maskClick = (e: any) => {
28 | if (e.target === this.refs.modal && this.maskCloseable) {
29 | this.dom.removeChild(this.el, this.vNode.el);
30 | }
31 | };
32 |
33 | readonly cancelClick = () => {
34 | this.close();
35 | };
36 |
37 | readonly closeClick = () => {
38 | this.close();
39 | };
40 |
41 | readonly onOkClick = async () => {
42 | if (this.onOk) {
43 | let result: boolean | void;
44 | try {
45 | result = await this.onOk(this.refs.instance as T);
46 | } catch (e) {}
47 | if (result !== false) {
48 | this.close();
49 | }
50 | }
51 | };
52 |
53 | close() {
54 | this.dom.removeChild(this.el, this.vNode.el);
55 | if (this.onCancel) {
56 | this.onCancel(this.refs.instance as T);
57 | }
58 | }
59 |
60 | render() {
61 | const Title = () => {
62 | if (typeof this.title === 'string') {
63 | return (
64 |
65 | {this.title || '未命名'}
66 |
67 |
68 | );
69 | } if (isVNode(this.title)) {
70 | return this.title;
71 | }
72 | };
73 | const Content = this.content;
74 | const Buttons = () => {
75 | return this.buttons !== undefined ? (
76 | this.buttons
77 | ) : (
78 |
79 |
82 |
85 |
86 | );
87 | };
88 | return (
89 |
90 |
91 | {Title &&
}
92 | {Content && }
93 | {Buttons && }
94 |
95 |
96 | );
97 | }
98 | }
99 |
100 | export function mountModal(args: MountModalArgs) {
101 | const { name, $ = jQuery, ...restProp } = args;
102 | $[name] = function (contentProps: Partial) {
103 | return Component.create>(
104 | ModalComponent,
105 | {
106 | ...restProp,
107 | ...contentProps,
108 | },
109 | document.body,
110 | );
111 | };
112 | }
113 |
114 | export type MountModalArgs = {
115 | name: string;
116 | title: string;
117 | $?: JQueryStatic;
118 | } & Partial>
119 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Title
6 |
7 |
8 | 简单例子:
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | 挂载为jquery组件,可直接获取value:
20 |
21 |
22 |
23 |
29 |
30 |
31 |
32 |
33 |
143 |
144 |
--------------------------------------------------------------------------------
/packages/jeact/README.MD:
--------------------------------------------------------------------------------
1 | # jeact
2 | 
3 |
4 | 在JQuery环境下用MVVM开发复杂组件,仅几kb超小体积。
5 |
6 | ## 支持环境
7 |
8 | | Edge | Firefox | Chrome |
9 | | --- | --- | --- |
10 | | last 2 versions | last 2 versions | last 2 versions |
11 |
12 | ## 安装
13 |
14 | npm
15 | ```shell script
16 | npm i jeact --save
17 | ```
18 | yarn
19 | ```shell script
20 | yarn add jeact
21 | ```
22 | 或者直接引入
23 | ```html
24 |
25 | ```
26 |
27 |
28 | ## 使用
29 |
30 |
31 | [在线演示](https://qqabcv520.github.io/jeact/)
32 |
33 | js创建组件
34 |
35 | ```js
36 |
37 | // 定义functional组件
38 | function Counter({count}) {
39 | return el('span', {},
40 | el('span', {}, ' 点击了:'),
41 | el('span', {}, count),
42 | el('span', {}, '次')
43 | )
44 | }
45 |
46 | // 定义class组件
47 | class TestComponent extends Component {
48 | constructor (props) {
49 | super(props);
50 | this.count = 0;
51 | }
52 | buttonClick = () => {
53 | this.count++;
54 | this.update();
55 | }
56 | render () {
57 | return el('div', {},
58 | el('button', {onclick: this.buttonClick}, '按钮'),
59 | el(Counter, {count: this.count}),
60 | )
61 | }
62 | }
63 |
64 | // 挂载到ID为el的节点上
65 | const vm = Component.create(TestComponent, {}, '#el')
66 | ```
67 |
68 | jsx创建组件
69 |
70 | ```jsx
71 | import { Component, createVNode } from 'jeact';
72 |
73 | class TestComponent extends Component {
74 | constructor (props) {
75 | super(props);
76 | this.count = 0;
77 | }
78 | buttonClick = () => {
79 | this.count++;
80 | this.update();
81 | }
82 | render () {
83 | return (
84 |
85 |
86 |
87 |
88 | )
89 | }
90 | }
91 | ```
92 |
93 | 定义一个表单组件并挂载到jquery
94 |
95 | ```jsx
96 | import { ValueComponent, createVNode } from 'jeact';
97 |
98 | class SwitchComponent extends ValueComponent {
99 |
100 | constructor (props) {
101 | super(props);
102 | this.open = false;
103 | }
104 |
105 | setOpen(val) {
106 | this.open = val;
107 | this.onChange(this.open);
108 | this.update(); // 当data变化之后,需要调用update方法,将该组件加到更新队列中
109 | }
110 | // 必须实现这个抽象方法,用于将input的value和组件的vm绑定
111 | writeValue(value) {
112 | this.open = value && value !== 'false';
113 | }
114 |
115 | buttonClick = () => {
116 | this.setOpen(!this.open);
117 | }
118 | render () {
119 | const switchWrapperStyle = {
120 | userSelect: 'none',
121 | cursor: 'pointer',
122 | border: '1px solid #ccc',
123 | display: 'inline-block',
124 | borderRadius: '10px',
125 | height: '20px',
126 | width: '35px',
127 | }
128 | const switchHandlerStyle = {
129 | width: '20px',
130 | height: '100%',
131 | backgroundColor: '#3587ff',
132 | borderRadius: '10px',
133 | transition: 'margin 0.15s, background-color 0.3s'
134 | }
135 | if (this.open) {
136 | switchHandlerStyle.marginLeft = '15px';
137 | switchHandlerStyle.backgroundColor = '#3587ff';
138 | } else {
139 | switchHandlerStyle.marginLeft = '0';
140 | switchHandlerStyle.backgroundColor = '#999';
141 | }
142 | return (
143 |
146 | );
147 | }
148 | }
149 | // 挂载到jquery,名称为customSwitch
150 | mountComponent({
151 | name: 'customSwitch',
152 | componentType: SwitchComponent,
153 | })
154 | ```
155 |
156 | 直接在HTML中使用刚刚挂载的`customSwitch`组件,初始值为`true`
157 | ```html
158 |
159 | ```
160 |
161 | 使用jquery初始化`customSwitch`组件
162 |
163 | ```html
164 |
165 | ```
166 | ```js
167 | $('#switchInput').customSwitch(); // 初始化组件
168 | $('#switchInput').customSwitch('setOpen', true) // 调用组件方法,设置open为true
169 | $('#switchInput').on('change', function() { // 监听事件
170 | console.log($(this).val());
171 | })
172 | ```
173 |
--------------------------------------------------------------------------------
/packages/jeact/src/component.ts:
--------------------------------------------------------------------------------
1 | import { VNode } from './v-node';
2 | import { DomOperate } from './dom';
3 | import { Differentiator } from './diff';
4 |
5 | export type Type = new (...args: any[]) => T;
6 |
7 | export interface ComponentProps {
8 | el?: HTMLElement;
9 | children?: VNode[];
10 | rootUpdate: () => void;
11 | }
12 |
13 | export interface ValueComponentProps extends ComponentProps {
14 | valueChange: (options: T) => void;
15 | }
16 |
17 | export abstract class Component {
18 | private updateFlag = false;
19 | protected readonly dom = new DomOperate(this);
20 | protected readonly diff = new Differentiator(this.dom);
21 | el: HTMLElement;
22 | readonly refs: {
23 | [key: string]: Element | Component;
24 | } = {};
25 |
26 | readonly rootUpdate?: () => void;
27 | children?: VNode[];
28 | vNode: VNode | null;
29 |
30 | constructor(args: ComponentProps) {
31 | if (args) {
32 | Object.assign(this, args);
33 | }
34 | }
35 |
36 | static create(
37 | componentType: Type,
38 | props: Partial,
39 | el?: HTMLElement | string,
40 | ): T {
41 | const dom = typeof el === 'string' ? document.querySelector(el) : el;
42 | const component = new componentType({ ...props });
43 | component.el = dom as HTMLElement;
44 | component.beforeMount();
45 | component.mount();
46 | component.mounted();
47 | return component;
48 | }
49 |
50 | protected mount() {
51 | this.vNode = this.render();
52 | const node = this.dom.createElement(this.vNode, this.update.bind(this));
53 | this.appendToEl(node);
54 | }
55 |
56 | appendToEl(node: Node) {
57 | if (this.el && node) {
58 | this.dom.appendChild(this.el, node);
59 | }
60 | }
61 |
62 | reappendToEl(oldNode: Node, newNode: Node) {
63 | if (oldNode === newNode || this.el == null) {
64 | return;
65 | }
66 | const parentNode = this.dom.parentNode(oldNode);
67 | if (parentNode == null) {
68 | return;
69 | }
70 | this.dom.removeChild(parentNode, oldNode);
71 | this.appendToEl(newNode);
72 | }
73 |
74 | update() {
75 | if (this.updateFlag) {
76 | return;
77 | }
78 | this.updateFlag = true;
79 | Promise.resolve().then(() => {
80 | this.updateFlag = false;
81 | if (this.rootUpdate) {
82 | this.rootUpdate();
83 | return;
84 | }
85 | this.runDiff();
86 | });
87 | }
88 |
89 | runDiff() {
90 | if (this.vNode == null || this.vNode.el == null) {
91 | return null;
92 | }
93 | const newVNode = this.render();
94 | this.diff.patch(this.vNode, newVNode, this.update.bind(this));
95 | this.reappendToEl(this.vNode.el, newVNode.el);
96 | this.vNode = newVNode;
97 | return newVNode;
98 | }
99 |
100 | beforeMount() {}
101 |
102 | mounted() {}
103 |
104 | beforeUpdate() {}
105 |
106 | updated() {}
107 |
108 | destroy() {}
109 |
110 | render(): VNode | null {
111 | return null;
112 | }
113 | }
114 |
115 | export abstract class ValueComponent extends Component {
116 | readonly el: HTMLInputElement;
117 | protected valueChange: (options: T) => void;
118 |
119 | abstract writeValue(value: string): void;
120 |
121 | protected constructor(props: ValueComponentProps) {
122 | super(props);
123 | this.valueChange = props.valueChange;
124 | }
125 |
126 | mount() {
127 | if (this.el) {
128 | this.writeValue(this.el.value);
129 | }
130 | super.mount();
131 | }
132 |
133 | readValue(value: any): string {
134 | return value != null ? String(value) : '';
135 | }
136 |
137 | onChange(value: T) {
138 | if (this.valueChange) {
139 | this.valueChange(value);
140 | }
141 | if (this.el) {
142 | this.el.value = this.readValue(value);
143 | this.el.dispatchEvent(new InputEvent('input'));
144 | this.el.dispatchEvent(new UIEvent('change'));
145 | }
146 | }
147 |
148 | appendToEl(node: HTMLElement) {
149 | const parentNode = this.el && this.dom.parentNode(this.el);
150 | if (parentNode && node) {
151 | this.dom.insertBefore(parentNode, node, this.el);
152 | this.el.hidden = true;
153 | }
154 | }
155 | }
156 |
157 | export class FunctionComponent extends Component {
158 | functionProps: T;
159 | renderFunction: (T) => VNode;
160 |
161 | render(): VNode {
162 | return this.renderFunction(this.functionProps);
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # [0.2.0](https://github.com/qqabcv520/jeact/compare/v0.1.1...v0.2.0) (2020-08-08)
2 |
3 |
4 | ### Bug Fixes
5 |
6 | * lint代码修复 ([5d5b93c](https://github.com/qqabcv520/jeact/commit/5d5b93c2b6cc58483432e6e00836f1ab99709044))
7 | * 修复ref对component无效的问题 ([0379293](https://github.com/qqabcv520/jeact/commit/037929325068faeb1152a6b08c79dcba0c4ebb0d))
8 | * 修复ValueComponent作为子组件使用的问题 ([ba3c1bf](https://github.com/qqabcv520/jeact/commit/ba3c1bf809418de00f78eb87af10b7ba9d97bdcc))
9 | * 修复挂载顺序导致的ValueComponent初始值无效的问题 ([786e370](https://github.com/qqabcv520/jeact/commit/786e37095d6a2c7cfda401fc73c18aa605262228))
10 |
11 |
12 | ### Performance Improvements
13 |
14 | * release.js自动更新monorepo依赖 ([fd8b250](https://github.com/qqabcv520/jeact/commit/fd8b25049afae3374b334bfe139eeca9850a329e))
15 | * 图片选择控件添加限制属性 ([f72f3c3](https://github.com/qqabcv520/jeact/commit/f72f3c3786a48e7c901969c6f0133110fc2cda21))
16 | * 图片选择控件限制触发弹窗 ([c71f6ed](https://github.com/qqabcv520/jeact/commit/c71f6edf265e67cebed2fe278a9ad52244a02649))
17 | * 完善图片选择控件 ([624e249](https://github.com/qqabcv520/jeact/commit/624e249ce34bdf7b8f57b181b732bedacd068fb3))
18 |
19 |
20 |
21 | ## [0.1.1](https://github.com/qqabcv520/jeact/compare/v0.1.0...v0.1.1) (2020-06-27)
22 |
23 |
24 | ### Bug Fixes
25 |
26 | * 修复函数式组件更新问题 ([a98ce8e](https://github.com/qqabcv520/jeact/commit/a98ce8e5fe4b9b6c4c2fd1821586b5fa126ebfa9))
27 | * 修复函数式组件更新问题 ([4ac8a66](https://github.com/qqabcv520/jeact/commit/4ac8a664c90f8331f4cbad6260b858b8ba005043))
28 |
29 |
30 |
31 | # [0.1.0](https://github.com/qqabcv520/jeact/compare/0.1.0...v0.1.0) (2020-06-17)
32 |
33 |
34 | ### Bug Fixes
35 |
36 | * popup关闭事件冒泡被拦截,改为捕获阶段 ([b463e8c](https://github.com/qqabcv520/jeact/commit/b463e8cc3ed51798b5c8b29ab8b62a8727d386ba))
37 | * 修复option初始化parent字段设置错误 ([70b5845](https://github.com/qqabcv520/jeact/commit/70b5845f40722c22869930edc02eba6d93788c82))
38 | * 多选级联组件bug修复 ([fce1a57](https://github.com/qqabcv520/jeact/commit/fce1a57cc22eade8d935dc2ef303c4dc95be5cfa))
39 | * 多选级联组件bug修复 ([e6832fd](https://github.com/qqabcv520/jeact/commit/e6832fd7cdf72ffbf366276dc4494649c4affcbd))
40 | * 样式前缀修复 ([e179009](https://github.com/qqabcv520/jeact/commit/e17900921580bd6383ea79b25eb2ddeee7dbdf1d))
41 | * 经理控件bug修复 ([9d493b8](https://github.com/qqabcv520/jeact/commit/9d493b82a81e1f8c868329d8119b24c08053ce3c))
42 |
43 |
44 | ### Features
45 |
46 | * mvvm初始化 ([37f24e6](https://github.com/qqabcv520/jeact/commit/37f24e6866a2b79f1fbbee563a9ef7dabc0c55f6))
47 | * mvvm初始化,基础功能 ([39f7b79](https://github.com/qqabcv520/jeact/commit/39f7b79c97b1d5d4064e6e231f8e3f159cd2a96b))
48 | * mvvm基础功能 ([2f79c7f](https://github.com/qqabcv520/jeact/commit/2f79c7fe3fb40f453943ffa644976606a3ee705d))
49 | * mvvm基础功能 ([85f5826](https://github.com/qqabcv520/jeact/commit/85f5826994a840565d79636177fd49d62d58a1b5))
50 | * mvvm基础功能 ([66afa73](https://github.com/qqabcv520/jeact/commit/66afa732e5b03f0c11965cf2fcbe6ef219ad86d5))
51 | * mvvm基础功能 ([de60862](https://github.com/qqabcv520/jeact/commit/de608622ef2c5009978ec3a5a3e08931c8ac3ae0))
52 | * mvvm基础功能 ([c38cae9](https://github.com/qqabcv520/jeact/commit/c38cae952878aeb21e3d17018bb57c616bd42174))
53 | * 多选级联组件 ([cb77571](https://github.com/qqabcv520/jeact/commit/cb77571d8c5a1c4ebc7492229561f2291fffb189))
54 | * 多选级联组件 ([7d35591](https://github.com/qqabcv520/jeact/commit/7d3559146a548c7c1c932f4e1517352786f5a73d))
55 | * 多选级联组件 ([073afaa](https://github.com/qqabcv520/jeact/commit/073afaa239cc376af9e9650216eac3c404a2fda1))
56 | * 多选级联组件 ([f481f62](https://github.com/qqabcv520/jeact/commit/f481f6274ae51cebe99f7ad9d5fc4cc559083261))
57 | * 常用选择添加cacheName ([3c8e062](https://github.com/qqabcv520/jeact/commit/3c8e0626921feb65acb0e68c352de59d89e49830))
58 | * 常用选择添加cacheName ([6facc3c](https://github.com/qqabcv520/jeact/commit/6facc3c30264cb3bf4665264b15771ebc0aba4a3))
59 | * 新增single-cascader组件 ([01117ea](https://github.com/qqabcv520/jeact/commit/01117ea9f2d80e83737555499fc1cca0c7bcac65))
60 | * 新增single-cascader组件 ([95c2f53](https://github.com/qqabcv520/jeact/commit/95c2f5345f71a5ec7edddb833da67c37ae27289d))
61 | * 新增single-cascader组件 ([7d905fc](https://github.com/qqabcv520/jeact/commit/7d905fce124ca882bc6643fb4275fd98fd21524b))
62 | * 级联组件 ([d0c770c](https://github.com/qqabcv520/jeact/commit/d0c770caaff572771753c9fc2ca53bc2e0fad2f7))
63 | * 级联组件 ([0b71469](https://github.com/qqabcv520/jeact/commit/0b714694fd87d2ce4438c67d360588830f5e6f73))
64 | * 级联组件 ([3d6a68f](https://github.com/qqabcv520/jeact/commit/3d6a68f3df9bf494a222342a4faa75eb7dea5ac6))
65 | * 级联组件bug修复 ([f4f075f](https://github.com/qqabcv520/jeact/commit/f4f075fafbb4102c63ce70639638ae9e996ad6a7))
66 | * 级联组件优化 ([8c51a16](https://github.com/qqabcv520/jeact/commit/8c51a168f6632d8086d65bb2c6c4ee1550f3c443))
67 | * 级联组件问题修复 ([07bdfff](https://github.com/qqabcv520/jeact/commit/07bdfffabe2ad423a78593b1397721aab8708311))
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/packages/jeact-components/src/pm-selector/index.less:
--------------------------------------------------------------------------------
1 | .ps-selector {
2 | display: inline-block;
3 | width: 150px;
4 | position: relative;
5 | .ps-label {
6 | display: inline-flex;
7 | align-items: center;
8 | margin: 8px 0;
9 | }
10 | input[type='checkbox'].ps-checkbox {
11 | margin: 4px;
12 | position: relative;
13 | width: 14px;
14 | height: 14px;
15 | cursor: pointer;
16 | }
17 | input[type='checkbox'].ps-checkbox::before {
18 | content: '';
19 | line-height: 14px;
20 | font-size: 10px;
21 | color: #fff;
22 | background-color: #fff;
23 | display: inline-block;
24 | width: 14px;
25 | height: 14px;
26 | border: 1px #ccc solid;
27 | border-radius: 2px;
28 | vertical-align: top;
29 | }
30 | input[type='checkbox'].ps-checkbox:checked::before {
31 | background-color: #2599ab;
32 | border-color: transparent;
33 | }
34 | input[type='checkbox'].ps-checkbox::after {
35 | content: '';
36 | }
37 | input[type='checkbox'].ps-checkbox:checked::after {
38 | position: absolute;
39 | top: 4px;
40 | left: 3px;
41 | width: 8px;
42 | height: 5px;
43 | border-left: 2px solid #fff;
44 | border-bottom: 2px solid #fff;
45 | transform: rotateZ(-45deg);
46 | }
47 | .ps-close {
48 | font-family: '黑体', serif;
49 | font-weight: bold;
50 | cursor: pointer;
51 | color: #666;
52 | }
53 | .ps-close:hover {
54 | color: #888;
55 | }
56 | .ps-close:active {
57 | color: #333;
58 | }
59 | &::-webkit-scrollbar {
60 | width: 8px;
61 | height: 8px;
62 | }
63 | &::-webkit-scrollbar-button {
64 | width: 0;
65 | height: 0;
66 | }
67 | &::-webkit-scrollbar-thumb {
68 | background: #e1e1e1;
69 | border: none;
70 | border-radius: 0;
71 | }
72 | &::-webkit-scrollbar-thumb:hover {
73 | background: #ccc;
74 | }
75 | &::-webkit-scrollbar-thumb:active {
76 | background: #999;
77 | }
78 | &::-webkit-scrollbar-track {
79 | border: 0 none #fff;
80 | border-radius: 0;
81 | }
82 | &::-webkit-scrollbar-track:hover {
83 | background: #eee;
84 | }
85 | &::-webkit-scrollbar-corner {
86 | background: transparent;
87 | }
88 | .ps-input.form-control[readonly] {
89 | background-color: #fff;
90 | box-shadow: none;
91 | cursor: pointer;
92 | }
93 | .ps-popup {
94 | font-size: 12px;
95 | top: 30px;
96 | left: 0;
97 | position: absolute;
98 | border: 1px #ddd solid;
99 | width: 724px;
100 | z-index: 1000;
101 | box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1);
102 | }
103 | .ps-search-bar {
104 | background-color: #fff;
105 | padding: 8px;
106 | display: flex;
107 | justify-content: flex-end;
108 | }
109 | .ps-search-input {
110 | width: 300px;
111 | margin-right: 8px;
112 | position: relative;
113 | }
114 | .ps-search-input > input.form-control {
115 | width: 100%;
116 | }
117 | .ps-commonly-used {
118 | background-color: #eee;
119 | padding: 4px 0;
120 | }
121 | .ps-commonly-used-options {
122 | display: flex;
123 | flex-wrap: wrap;
124 | min-height: 60px;
125 | }
126 | .ps-commonly-used-option {
127 | margin-top: 0;
128 | font-weight: normal;
129 | width: 20%;
130 | white-space: nowrap;
131 | overflow: hidden;
132 | text-overflow: ellipsis;
133 | }
134 | .ps-options {
135 | display: flex;
136 | height: 250px;
137 | width: 100%;
138 | overflow: auto;
139 | background-color: #fff;
140 | }
141 | .ps-column {
142 | border-right: 1px #eee solid;
143 | flex-shrink: 0;
144 | width: 120px;
145 | overflow: auto;
146 | }
147 | .ps-option {
148 | margin: 1px 0;
149 | padding: 4px 0;
150 | cursor: pointer;
151 | position: relative;
152 | display: flex;
153 | align-items: center;
154 | }
155 | .ps-option:hover {
156 | background-color: #eee;
157 | }
158 | .ps-option-selected {
159 | background-color: #eee;
160 | }
161 | .ps-option.ps-option-next {
162 | padding-right: 20px;
163 | }
164 | .ps-option.ps-option-next::after {
165 | position: absolute;
166 | right: 0;
167 | top: 10px;
168 | content: '';
169 | width: 14px;
170 | height: 10px;
171 | border: 5px transparent solid;
172 | border-left: 7px #999 solid;
173 | }
174 | .ps-option-text {
175 | flex-grow: 1;
176 | white-space: nowrap;
177 | overflow: hidden;
178 | text-overflow: ellipsis;
179 | }
180 | .ps-selected-cnt {
181 | padding: 6px 6px 0;
182 | background-color: #fff;
183 | }
184 | .ps-selected-label {
185 | font-weight: bold;
186 | margin-right: 5px;
187 | }
188 | .ps-selected-tags {
189 | display: flex;
190 | flex-wrap: wrap;
191 | padding: 4px;
192 | max-height: 300px;
193 | overflow: auto;
194 | background-color: #fff;
195 | }
196 | .ps-selected-tag {
197 | font-size: 13px;
198 | padding: 3px;
199 | width: auto;
200 | border: 1px #ccc solid;
201 | margin: 4px;
202 | background-color: #eee;
203 | color: #555;
204 | border-radius: 2px;
205 | }
206 | .ps-search-popup {
207 | position: absolute;
208 | background-color: #fff;
209 | top: 30px;
210 | width: 100%;
211 | max-height: 380px;
212 | overflow: auto;
213 | border: 1px #eee solid;
214 | padding: 4px;
215 | z-index: 200;
216 | }
217 | .ps-search-options {
218 | display: flex;
219 | flex-wrap: wrap;
220 | min-height: 60px;
221 | }
222 | .ps-search-option {
223 | font-size: 12px;
224 | margin-top: 0;
225 | font-weight: normal;
226 | width: 33.333333%;
227 | white-space: nowrap;
228 | overflow: hidden;
229 | text-overflow: ellipsis;
230 | }
231 | }
232 |
--------------------------------------------------------------------------------
/packages/jeact-components/src/picture-selector/index.less:
--------------------------------------------------------------------------------
1 | // 禁止选中
2 | .no-select {
3 | -webkit-user-select: none;
4 | -moz-user-select: none;
5 | -ms-user-select: none;
6 | user-select: none;
7 | }
8 |
9 | .bgx-picture-selector {
10 | width: 1000px;
11 | background-color: #fff;
12 | .bgx-pic-tabs {
13 | display: flex;
14 | padding: 6px 4px 0 4px;
15 | border-bottom: 1px solid #ddd;
16 | overflow-x: auto;
17 | overflow-y: hidden;
18 | .bgx-pic-tab {
19 | position: relative;
20 | padding: 8px 16px;
21 | border: 1px solid #ddd;
22 | border-bottom-color: transparent;
23 | border-top-left-radius: 4px;
24 | border-top-right-radius: 4px;
25 | margin-left: 2px;
26 | margin-right: 2px;
27 | margin-bottom: -1px;
28 | cursor: pointer;
29 | background-color: #eee;
30 | .no-select;
31 | }
32 | .bgx-pic-tab-selected {
33 | border-bottom-color: #fff;
34 | background-color: #fff;
35 | }
36 | }
37 | .bgx-pic-selected {
38 | &-images {
39 | height: 160px;
40 | padding: 8px 0 8px 8px;
41 | overflow-y: scroll;
42 | display: flex;
43 | flex-wrap: wrap;
44 | }
45 | &-operation {
46 | position: absolute;
47 | left: 0;
48 | right: 0;
49 | top: 0;
50 | bottom: 0;
51 | opacity: 0;
52 | text-align: center;
53 | background-color: rgba(0, 0, 0, 0.5);
54 | transition: opacity 0.2s;
55 | &:hover {
56 | opacity: 1;
57 | }
58 | }
59 | &-icon {
60 | color: #fff;
61 | cursor: pointer;
62 | line-height: 110px;
63 | font-size: 18px;
64 | }
65 | &-close {
66 | cursor: pointer;
67 | position: absolute;
68 | right: -8px;
69 | top: -8px;
70 | color: #ff4d4d;
71 | font-size: 18px;
72 | }
73 | &-option-wrapper {
74 | width: 112px;
75 | height: 112px;
76 | border: 1px solid #ddd;
77 | margin-right: 8px;
78 | margin-bottom: 8px;
79 | position: relative;
80 | }
81 | &-img {
82 | width: 100%;
83 | height: 100%;
84 | }
85 | }
86 | .bgx-pic-selector-content {
87 | margin-top: 8px;
88 | border: 1px solid #ddd;
89 | border-radius: 4px;
90 | }
91 | .bgx-pic-toolbar {
92 | padding: 8px;
93 | border-bottom: 1px solid #ddd;
94 | display: flex;
95 | &-input-group {
96 | width: 340px;
97 | }
98 | &-input {
99 | width: 170px;
100 | }
101 | &-space {
102 | flex: 1;
103 | }
104 | & > * {
105 | margin-right: 8px;
106 | }
107 | }
108 | .bgx-pic-images {
109 | padding: 4px 0 4px 4px;
110 | min-height: 300px;
111 | max-height: 490px;
112 | overflow: auto;
113 | display: flex;
114 | flex-wrap: wrap;
115 | &-option {
116 | margin: 4px;
117 | color: #666;
118 | line-height: 20px;
119 | cursor: pointer;
120 | height: 130px;
121 | .no-select;
122 | &-wrapper {
123 | position: relative;
124 | overflow: hidden;
125 | border-radius: 4px;
126 | border: 1px solid #ddd;
127 | width: 106px;
128 | height: 106px;
129 | }
130 | &-img {
131 | width: 100%;
132 | height: 100%;
133 | }
134 | &-mask {
135 | position: absolute;
136 | top: 0;
137 | left: 0;
138 | right: 0;
139 | bottom: 0;
140 | background-color: rgba(0, 0, 0, 0.5);
141 | opacity: 0;
142 | &:hover {
143 | opacity: 1;
144 | }
145 | }
146 | &-icon {
147 | color: #fff;
148 | font-size: 18px;
149 | position: absolute;
150 | right: 1px;
151 | top: 0;
152 | }
153 | &-preview {
154 | color: #fff;
155 | font-size: 18px;
156 | position: absolute;
157 | left: 50%;
158 | top: 50%;
159 | transform: translate(-50%, -50%);
160 | }
161 | &:hover {
162 | .bgx-pic-images-option-wrapper {
163 | border: 1px solid #999;
164 | }
165 | span {
166 | color: #000;
167 | }
168 | }
169 | &-selected {
170 | .bgx-pic-images-option-mask {
171 | opacity: 1;
172 | }
173 | }
174 | }
175 | }
176 |
177 | .preview-icon() {
178 | position: fixed;
179 | top: 50%;
180 | transform: translateY(-50%);
181 | background-color: rgba(0, 0, 0, 0.5);
182 | color: #fff;
183 | font-size: 30px;
184 | width: 50px;
185 | height: 50px;
186 | line-height: 50px;
187 | cursor: pointer;
188 | .no-select;
189 | }
190 | .preview-min {
191 | .preview-icon;
192 |
193 | left: 50px;
194 | text-align: left;
195 | }
196 | .preview-add {
197 | .preview-icon;
198 |
199 | right: 50px;
200 | text-align: right;
201 | }
202 | .preview-selected {
203 | font-size: 40px;
204 | color: #fff;
205 | position: absolute;
206 | top: 50%;
207 | left: 50%;
208 | transform: translate(-50%, -50%);
209 | }
210 | .preview-mask {
211 | content: '';
212 | display: block;
213 | position: absolute;
214 | top: 0;
215 | left: 0;
216 | width: 100%;
217 | height: 100%;
218 | background-color: rgba(0, 0, 0, 0.5);
219 | }
220 | }
221 |
222 | .preview() {
223 | position: absolute;
224 | top: 400px;
225 | font-size: 25px;
226 | cursor: pointer;
227 | color: #fff;
228 | background-color: rgba(0, 0, 0, 0.2);
229 | width: 50px;
230 | height: 50px;
231 | line-height: 50px;
232 | text-align: center;
233 | user-select: none;
234 | &:hover {
235 | background-color: rgba(0, 0, 0, 0.3);
236 | }
237 | }
238 |
239 | .preview-left {
240 | left: 5px;
241 | .preview();
242 | }
243 |
244 | .preview-right {
245 | right: 5px;
246 | .preview();
247 | }
248 |
--------------------------------------------------------------------------------
/packages/jeact-components/src/single-cascader/index.less:
--------------------------------------------------------------------------------
1 | .bgx-single-cascader {
2 | display: inline-block;
3 | vertical-align: middle;
4 | width: 100%;
5 | position: relative;
6 | .input-group {
7 | width: 100%;
8 | }
9 | .bgx-label {
10 | display: inline-flex;
11 | align-items: center;
12 | margin: 0;
13 | }
14 | input[type='checkbox'].bgx-checkbox {
15 | margin: 4px;
16 | position: relative;
17 | width: 14px;
18 | height: 14px;
19 | cursor: pointer;
20 | }
21 | input[type='checkbox'].bgx-checkbox::before {
22 | content: '';
23 | line-height: 14px;
24 | font-size: 10px;
25 | color: #fff;
26 | background-color: #fff;
27 | display: inline-block;
28 | width: 14px;
29 | height: 14px;
30 | border: 1px #ccc solid;
31 | border-radius: 2px;
32 | vertical-align: top;
33 | }
34 | input[type='checkbox'].bgx-checkbox:checked::before {
35 | background-color: #2599ab;
36 | border-color: transparent;
37 | }
38 | input[type='checkbox'].bgx-checkbox::after {
39 | content: '';
40 | }
41 | input[type='checkbox'].bgx-checkbox:checked::after {
42 | position: absolute;
43 | top: 4px;
44 | left: 3px;
45 | width: 8px;
46 | height: 5px;
47 | border-left: 2px solid #fff;
48 | border-bottom: 2px solid #fff;
49 | transform: rotateZ(-45deg);
50 | }
51 | .bgx-close {
52 | font-family: '黑体', serif;
53 | font-weight: bold;
54 | cursor: pointer;
55 | color: #666;
56 | }
57 | .bgx-close:hover {
58 | color: #888;
59 | }
60 | .bgx-close:active {
61 | color: #333;
62 | }
63 | .bgx-selector {
64 | width: 150px;
65 | position: relative;
66 | }
67 | .bgx-selector ::-webkit-scrollbar {
68 | width: 8px;
69 | height: 8px;
70 | }
71 | .bgx-selector ::-webkit-scrollbar-button {
72 | width: 0;
73 | height: 0;
74 | }
75 | .bgx-selector ::-webkit-scrollbar-thumb {
76 | background: #e1e1e1;
77 | border: none;
78 | border-radius: 0;
79 | }
80 | .bgx-selector ::-webkit-scrollbar-thumb:hover {
81 | background: #ccc;
82 | }
83 | .bgx-selector ::-webkit-scrollbar-thumb:active {
84 | background: #999;
85 | }
86 | .bgx-selector ::-webkit-scrollbar-track {
87 | border: 0 none #fff;
88 | border-radius: 0;
89 | }
90 | .bgx-selector ::-webkit-scrollbar-track:hover {
91 | background: #eee;
92 | }
93 | .bgx-selector ::-webkit-scrollbar-corner {
94 | background: transparent;
95 | }
96 | .bgx-input.form-control[readonly] {
97 | background-color: #fff;
98 | box-shadow: none;
99 | cursor: pointer;
100 | }
101 | .bgx-popup {
102 | font-size: 12px;
103 | top: 30px;
104 | left: 0;
105 | position: absolute;
106 | border: 1px #ddd solid;
107 | min-width: 600px;
108 | max-width: 960px;
109 | z-index: 1000;
110 | box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1);
111 | }
112 | .bgx-search-bar {
113 | background-color: #fff;
114 | padding: 8px;
115 | display: flex;
116 | justify-content: flex-end;
117 | }
118 | .bgx-search-input {
119 | width: 450px;
120 | margin-right: 8px;
121 | position: relative;
122 | }
123 | .bgx-search-input > input.form-control {
124 | width: 100%;
125 | }
126 | .bgx-commonly-used {
127 | background-color: #fff;
128 | border: #eee 1px solid;
129 | padding: 4px;
130 | }
131 | .bgx-commonly-used-head {
132 | display: flex;
133 | justify-content: space-between;
134 | align-items: center;
135 | & > label {
136 | margin: 0;
137 | }
138 | & > i {
139 | flex: 1;
140 | font-size: 20px;
141 | color: #333;
142 | text-align: right;
143 | padding-right: 10px;
144 | cursor: pointer;
145 | }
146 | }
147 | .bgx-commonly-used-options {
148 | display: flex;
149 | flex-wrap: wrap;
150 | }
151 | .bgx-commonly-used-option {
152 | font-weight: normal;
153 | cursor: pointer;
154 | white-space: nowrap;
155 | overflow: hidden;
156 | text-overflow: ellipsis;
157 | padding: 4px 8px;
158 | border: 1px solid #eee;
159 | border-radius: 4px;
160 | margin: 2px;
161 | &:hover {
162 | background-color: #eee;
163 | }
164 | }
165 | .bgx-options {
166 | display: flex;
167 | height: 250px;
168 | width: 100%;
169 | overflow: auto;
170 | background-color: #fff;
171 | }
172 | .bgx-column {
173 | border-right: 1px #eee solid;
174 | flex-shrink: 0;
175 | overflow: auto;
176 | }
177 | .bgx-option {
178 | margin: 1px 0;
179 | padding: 6px 4px;
180 | cursor: pointer;
181 | position: relative;
182 | display: flex;
183 | align-items: center;
184 | }
185 | .bgx-option:hover {
186 | background-color: #eee;
187 | }
188 | .bgx-option-selected {
189 | background-color: #eee;
190 | }
191 | .bgx-option.bgx-option-next {
192 | padding-right: 20px;
193 | }
194 | .bgx-option.bgx-option-next::after {
195 | position: absolute;
196 | right: 0;
197 | top: 10px;
198 | content: '';
199 | width: 14px;
200 | height: 10px;
201 | border: 5px transparent solid;
202 | border-left: 7px #999 solid;
203 | }
204 | .bgx-option-text {
205 | flex-grow: 1;
206 | white-space: nowrap;
207 | overflow: hidden;
208 | text-overflow: ellipsis;
209 | }
210 | .bgx-search-popup {
211 | position: absolute;
212 | background-color: #fff;
213 | top: 30px;
214 | width: 100%;
215 | max-height: 380px;
216 | overflow: auto;
217 | border: 1px #eee solid;
218 | padding: 4px;
219 | z-index: 200;
220 | .no-search-result {
221 | padding: 20px 0;
222 | text-align: center;
223 | color: #999;
224 | }
225 | }
226 | .bgx-search-options {
227 | display: flex;
228 | flex-wrap: wrap;
229 | flex-direction: column;
230 | }
231 | .bgx-search-option {
232 | font-size: 12px;
233 | margin-top: 0;
234 | margin-bottom: 0;
235 | padding: 4px 8px;
236 | font-weight: normal;
237 | cursor: pointer;
238 | &:hover {
239 | background-color: #eee;
240 | }
241 | }
242 | }
243 |
--------------------------------------------------------------------------------
/scripts/release.js:
--------------------------------------------------------------------------------
1 | const args = require('minimist')(process.argv.slice(2))
2 | const fs = require('fs')
3 | const path = require('path')
4 | const chalk = require('chalk')
5 | const semver = require('semver')
6 | const currentVersion = require('../package.json').version
7 | const { prompt } = require('enquirer')
8 | const execa = require('execa')
9 | const prereleaseVersion = semver.prerelease(currentVersion)
10 | const preId = args.preid || (prereleaseVersion && prereleaseVersion[0]) || 'alpha'
11 | const isDryRun = args.dry
12 | const skipBuild = args.skipBuild
13 | const packages = fs
14 | .readdirSync(path.resolve(__dirname, '../packages'))
15 | .filter(p => !p.endsWith('.ts') && !p.startsWith('.'))
16 |
17 | const skippedPackages = []
18 |
19 | const versionIncrements = [
20 | 'patch',
21 | 'minor',
22 | 'major',
23 | 'prepatch',
24 | 'preminor',
25 | 'premajor',
26 | 'prerelease'
27 | ]
28 |
29 | const corePkgName = 'jeact'
30 | const pkgName = '@jeact'
31 |
32 | const inc = i => semver.inc(currentVersion, i, preId)
33 | const bin = name => path.resolve(__dirname, '../node_modules/.bin/' + name)
34 | const run = (bin, args, opts = {}) =>
35 | execa(bin, args, { stdio: 'inherit', ...opts })
36 | const dryRun = (bin, args, opts = {}) =>
37 | console.log(chalk.blue(`[dryrun] ${bin} ${args.join(' ')}`), opts)
38 | const runIfNotDry = isDryRun ? dryRun : run
39 | const getPkgRoot = pkg => path.resolve(__dirname, '../packages/' + pkg)
40 | const step = msg => console.log(chalk.cyan(msg))
41 |
42 | async function main() {
43 | let targetVersion = args._[0]
44 |
45 | if (!targetVersion) {
46 | // no explicit version, offer suggestions
47 | const { release } = await prompt({
48 | type: 'select',
49 | name: 'release',
50 | message: 'Select release type',
51 | choices: versionIncrements.map(i => `${i} (${inc(i)})`).concat(['custom'])
52 | })
53 |
54 | if (release === 'custom') {
55 | targetVersion = (await prompt({
56 | type: 'input',
57 | name: 'version',
58 | message: 'Input custom version',
59 | initial: currentVersion
60 | })).version
61 | } else {
62 | targetVersion = release.match(/\((.*)\)/)[1]
63 | }
64 | }
65 |
66 | if (!semver.valid(targetVersion)) {
67 | throw new Error(`invalid target version: ${targetVersion}`)
68 | }
69 |
70 | const { yes } = await prompt({
71 | type: 'confirm',
72 | name: 'yes',
73 | message: `Releasing v${targetVersion}. Confirm?`
74 | })
75 |
76 | if (!yes) {
77 | return
78 | }
79 |
80 | // update all package versions and inter-dependencies
81 | step('\nUpdating cross dependencies...')
82 | updateVersions(targetVersion)
83 |
84 | // build all packages with types
85 | step('\nBuilding all packages...')
86 | if (!skipBuild && !isDryRun) {
87 | await run('yarn', ['build', '--release'])
88 | } else {
89 | console.log(`(skipped)`)
90 | }
91 |
92 | // generate changelog
93 | await run(`yarn`, ['changelog'])
94 |
95 | const { stdout } = await run('git', ['diff'], { stdio: 'pipe' })
96 | if (stdout) {
97 | step('\nCommitting changes...')
98 | await runIfNotDry('git', ['add', '-A'])
99 | await runIfNotDry('git', ['commit', '-m', `chore: v${targetVersion}`])
100 | } else {
101 | console.log('No changes to commit.')
102 | }
103 |
104 | // publish packages
105 | step('\nPublishing packages...')
106 | for (const pkg of packages) {
107 | await publishPackage(pkg, targetVersion, runIfNotDry)
108 | }
109 |
110 | // push to GitHub
111 | step('\nPushing to GitHub...')
112 | await runIfNotDry('git', ['tag', `v${targetVersion}`])
113 | await runIfNotDry('git', ['push', 'origin', `refs/tags/v${targetVersion}`])
114 | await runIfNotDry('git', ['push'])
115 |
116 | if (isDryRun) {
117 | console.log(`\nDry run finished - run git diff to see package changes.`)
118 | }
119 |
120 | if (skippedPackages.length) {
121 | console.log(
122 | chalk.yellow(
123 | `The following packages are skipped and NOT published:\n- ${skippedPackages.join(
124 | '\n- '
125 | )}`
126 | )
127 | )
128 | }
129 | console.log()
130 | }
131 |
132 | function updateVersions(version) {
133 | // 1. update root package.json
134 | updatePackage(path.resolve(__dirname, '..'), version)
135 | // 2. update all packages
136 | packages.forEach(p => updatePackage(getPkgRoot(p), version))
137 | }
138 |
139 | function updatePackage(pkgRoot, version) {
140 | const pkgPath = path.resolve(pkgRoot, 'package.json')
141 | const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
142 | pkg.version = version
143 | updateDeps(pkg, 'dependencies', version)
144 | updateDeps(pkg, 'peerDependencies', version)
145 | fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n')
146 | }
147 |
148 | function updateDeps(pkg, depType, version) {
149 | const deps = pkg[depType]
150 | if (!deps) return
151 | Object.keys(deps).forEach(dep => {
152 | if (
153 | dep === corePkgName ||
154 | (dep.startsWith(pkgName) && packages.includes(dep.replace(RegExp(`^${pkgName}\/`), '')))
155 | ) {
156 | console.log(
157 | chalk.yellow(`${pkg.name} -> ${depType} -> ${dep}@${version}`)
158 | )
159 | deps[dep] = version
160 | }
161 | })
162 | }
163 |
164 | async function publishPackage(pkgName, version, runIfNotDry) {
165 | if (skippedPackages.includes(pkgName)) {
166 | return
167 | }
168 | const pkgRoot = getPkgRoot(pkgName)
169 | const pkgPath = path.resolve(pkgRoot, 'package.json')
170 | const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
171 | if (pkg.private) {
172 | return
173 | }
174 |
175 | step(`Publishing ${pkgName}...`)
176 | try {
177 | await runIfNotDry(
178 | 'yarn',
179 | [
180 | 'publish',
181 | '--new-version',
182 | version,
183 | '--access',
184 | 'public'
185 | ],
186 | {
187 | cwd: pkgRoot,
188 | stdio: 'pipe'
189 | }
190 | )
191 | console.log(chalk.green(`Successfully published ${pkgName}@${version}`))
192 | } catch (e) {
193 | if (e.stderr.match(/previously published/)) {
194 | console.log(chalk.red(`Skipping already published: ${pkgName}`))
195 | } else {
196 | throw e
197 | }
198 | }
199 | }
200 |
201 | main().catch(err => {
202 | console.error(err)
203 | })
204 |
--------------------------------------------------------------------------------
/packages/jeact-components/src/cascader/index.less:
--------------------------------------------------------------------------------
1 | .bgx-cascader {
2 | display: inline-block;
3 | vertical-align: middle;
4 | width: 150px;
5 | position: relative;
6 | .bgx-label {
7 | display: inline-flex;
8 | align-items: center;
9 | margin: 8px 0;
10 | }
11 | input[type='checkbox'].bgx-checkbox {
12 | margin: 4px;
13 | position: relative;
14 | width: 14px;
15 | height: 14px;
16 | cursor: pointer;
17 | }
18 | input[type='checkbox'].bgx-checkbox::before {
19 | content: '';
20 | line-height: 14px;
21 | font-size: 10px;
22 | color: #fff;
23 | background-color: #fff;
24 | display: inline-block;
25 | width: 14px;
26 | height: 14px;
27 | border: 1px #ccc solid;
28 | border-radius: 2px;
29 | vertical-align: top;
30 | }
31 | input[type='checkbox'].bgx-checkbox:checked::before {
32 | background-color: #2599ab;
33 | border-color: transparent;
34 | }
35 | input[type='checkbox'].bgx-checkbox::after {
36 | content: '';
37 | }
38 | input[type='checkbox'].bgx-checkbox:checked::after {
39 | position: absolute;
40 | top: 4px;
41 | left: 3px;
42 | width: 8px;
43 | height: 5px;
44 | border-left: 2px solid #fff;
45 | border-bottom: 2px solid #fff;
46 | transform: rotateZ(-45deg);
47 | }
48 | .bgx-close {
49 | font-family: '黑体', serif;
50 | font-weight: bold;
51 | cursor: pointer;
52 | color: #666;
53 | }
54 | .bgx-close:hover {
55 | color: #888;
56 | }
57 | .bgx-close:active {
58 | color: #333;
59 | }
60 | &::-webkit-scrollbar {
61 | width: 8px;
62 | height: 8px;
63 | }
64 | &::-webkit-scrollbar-button {
65 | width: 0;
66 | height: 0;
67 | }
68 | &::-webkit-scrollbar-thumb {
69 | background: #e1e1e1;
70 | border: none;
71 | border-radius: 0;
72 | }
73 | &::-webkit-scrollbar-thumb:hover {
74 | background: #ccc;
75 | }
76 | &::-webkit-scrollbar-thumb:active {
77 | background: #999;
78 | }
79 | &::-webkit-scrollbar-track {
80 | border: 0 none #fff;
81 | border-radius: 0;
82 | }
83 | &::-webkit-scrollbar-track:hover {
84 | background: #eee;
85 | }
86 | &::-webkit-scrollbar-corner {
87 | background: transparent;
88 | }
89 | .bgx-input.form-control[readonly] {
90 | background-color: #fff;
91 | box-shadow: none;
92 | cursor: pointer;
93 | }
94 | .bgx-popup {
95 | font-size: 12px;
96 | top: 30px;
97 | left: 0;
98 | position: absolute;
99 | border: 1px #ddd solid;
100 | width: 724px;
101 | z-index: 1000;
102 | box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1);
103 | }
104 | .bgx-search-bar {
105 | background-color: #fff;
106 | padding: 8px;
107 | display: flex;
108 | justify-content: flex-end;
109 | }
110 | .bgx-search-input {
111 | width: 450px;
112 | margin-right: 8px;
113 | position: relative;
114 | }
115 | .bgx-search-input > input.form-control {
116 | width: 100%;
117 | }
118 | .bgx-commonly-used {
119 | background-color: #eee;
120 | padding: 4px 0;
121 | }
122 | .bgx-commonly-used-head {
123 | display: flex;
124 | justify-content: space-between;
125 | align-items: center;
126 | & > label {
127 | margin: 0;
128 | }
129 | & > i {
130 | flex: 1;
131 | font-size: 20px;
132 | color: #333;
133 | text-align: right;
134 | padding-right: 10px;
135 | cursor: pointer;
136 | }
137 | }
138 | .bgx-commonly-used-options {
139 | display: flex;
140 | flex-wrap: wrap;
141 | }
142 | .bgx-commonly-used-option {
143 | margin-top: 0;
144 | font-weight: normal;
145 | width: 20%;
146 | white-space: nowrap;
147 | overflow: hidden;
148 | text-overflow: ellipsis;
149 | }
150 | .bgx-options {
151 | display: flex;
152 | height: 250px;
153 | width: 100%;
154 | overflow: auto;
155 | background-color: #fff;
156 | }
157 | .bgx-column {
158 | border-right: 1px #eee solid;
159 | flex-shrink: 0;
160 | width: 120px;
161 | overflow: auto;
162 | }
163 | .bgx-option {
164 | margin: 1px 0;
165 | padding: 4px 0;
166 | cursor: pointer;
167 | position: relative;
168 | display: flex;
169 | align-items: center;
170 | }
171 | .bgx-option:hover {
172 | background-color: #eee;
173 | }
174 | .bgx-option-selected {
175 | background-color: #eee;
176 | }
177 | .bgx-option.bgx-option-next {
178 | padding-right: 20px;
179 | }
180 | .bgx-option.bgx-option-next::after {
181 | position: absolute;
182 | right: 0;
183 | top: 10px;
184 | content: '';
185 | width: 14px;
186 | height: 10px;
187 | border: 5px transparent solid;
188 | border-left: 7px #999 solid;
189 | }
190 | .bgx-option-text {
191 | flex-grow: 1;
192 | white-space: nowrap;
193 | overflow: hidden;
194 | text-overflow: ellipsis;
195 | }
196 | .bgx-selected-cnt {
197 | padding: 6px 6px 0;
198 | background-color: #fff;
199 | }
200 | .bgx-selected-label {
201 | font-weight: bold;
202 | margin-right: 5px;
203 | }
204 | .bgx-selected-tags {
205 | display: flex;
206 | flex-wrap: wrap;
207 | padding: 4px;
208 | max-height: 300px;
209 | overflow: auto;
210 | background-color: #fff;
211 | }
212 | .bgx-selected-tag {
213 | font-size: 13px;
214 | padding: 3px;
215 | width: auto;
216 | border: 1px #ccc solid;
217 | margin: 4px;
218 | background-color: #eee;
219 | color: #555;
220 | border-radius: 2px;
221 | }
222 | .bgx-search-popup {
223 | position: absolute;
224 | background-color: #fff;
225 | top: 30px;
226 | width: 100%;
227 | max-height: 380px;
228 | overflow: auto;
229 | border: 1px #eee solid;
230 | padding: 4px;
231 | z-index: 200;
232 | .no-search-result {
233 | padding: 20px 0;
234 | text-align: center;
235 | color: #999;
236 | }
237 | }
238 | .bgx-search-head {
239 | display: flex;
240 | & > label {
241 | margin: 0 8px;
242 | }
243 | }
244 | .bgx-search-options {
245 | display: flex;
246 | flex-wrap: wrap;
247 | flex-direction: column;
248 | }
249 | .bgx-search-option {
250 | font-size: 12px;
251 | margin-top: 0;
252 | margin-bottom: 0;
253 | padding: 4px 8px;
254 | font-weight: normal;
255 | cursor: pointer;
256 | &:hover {
257 | background-color: #eee;
258 | }
259 | }
260 | }
261 |
--------------------------------------------------------------------------------
/packages/jeact/src/dom.ts:
--------------------------------------------------------------------------------
1 | import {
2 | isVComponent,
3 | isVDom,
4 | isVElement,
5 | isVFunction,
6 | isVText,
7 | VDom,
8 | VNode,
9 | VText,
10 | } from './v-node';
11 | import { Component, FunctionComponent } from './component';
12 |
13 | export class DomOperate {
14 | constructor(private context: Component) {}
15 |
16 | createElement(vNode: VText, rootUpdate: () => void): Text;
17 | createElement(vNode: VDom, rootUpdate: () => void): HTMLElement;
18 | createElement(vNode: VNode, rootUpdate: () => void): Node;
19 | createElement(vNode: VNode, rootUpdate: () => void): any {
20 | if (isVComponent(vNode)) {
21 | vNode.component = Component.create(vNode.type, {
22 | ...vNode.attributes,
23 | ...vNode.handles,
24 | children: vNode.children,
25 | rootUpdate,
26 | });
27 | vNode.el = vNode.component.vNode?.el;
28 | Object.keys(vNode.attributes).forEach((key) => {
29 | const value = vNode.attributes[key];
30 | this.setAttribute(vNode.component, key, value);
31 | });
32 | return vNode.el;
33 | } else if (isVFunction(vNode)) {
34 | vNode.component = Component.create>(FunctionComponent, {
35 | renderFunction: vNode.type,
36 | functionProps: {
37 | ...vNode.attributes,
38 | ...vNode.handles,
39 | children: vNode.children,
40 | },
41 | rootUpdate,
42 | });
43 | vNode.el = vNode.component.vNode?.el;
44 | return vNode.el;
45 | } else if (isVDom(vNode)) {
46 | const el: HTMLElement = document.createElement(vNode.type);
47 | vNode.el = el;
48 | Object.keys(vNode.handles).forEach((key) => {
49 | const value = vNode.handles[key];
50 | const handleName = key.toLowerCase().replace(/^on/, '');
51 | el.addEventListener(handleName, value);
52 | });
53 | Object.keys(vNode.attributes).forEach((key) => {
54 | const value = vNode.attributes[key];
55 | this.setAttribute(el, key, value);
56 | });
57 | vNode.children?.forEach((value) => {
58 | const node = this.createElement(value, rootUpdate);
59 | if (node) {
60 | el.appendChild(node);
61 | }
62 | });
63 | return el;
64 | } else if (isVText(vNode)) {
65 | vNode.el = document.createTextNode(vNode.content);
66 | return vNode.el;
67 | }
68 | }
69 |
70 | /**
71 | *
72 | * @param el VNode对应的真是dom
73 | * @param newVNode
74 | * @param oldVNode
75 | */
76 | updateVDom(el: Node, newVNode: VDom, oldVNode: VDom) {
77 | if (isVDom(newVNode) && isVDom(oldVNode) && el instanceof HTMLElement) {
78 | Object.keys(oldVNode.handles).forEach((key) => {
79 | if (!newVNode.handles.hasOwnProperty(key)) {
80 | const value = oldVNode.handles[key];
81 | const handleName = key.toLowerCase().replace(/^on/, '');
82 | el.removeEventListener(handleName, value);
83 | }
84 | });
85 | Object.keys(newVNode.handles).forEach((key) => {
86 | const handleName = key.toLowerCase().replace(/^on/, '');
87 | const value = newVNode.handles[key];
88 | const oldValue = oldVNode.handles[key];
89 | if (value === oldValue) {
90 | return;
91 | }
92 | if (oldVNode.handles.hasOwnProperty(key)) {
93 | el.removeEventListener(handleName, oldValue);
94 | }
95 | el.addEventListener(handleName, value);
96 | });
97 | Object.keys(oldVNode.attributes).forEach((key) => {
98 | if (!newVNode.attributes.hasOwnProperty(key)) {
99 | this.removeAttribute(el, key, oldVNode.attributes[key]);
100 | }
101 | });
102 |
103 | Object.keys(newVNode.attributes).forEach((key) => {
104 | const value = newVNode.attributes[key];
105 | const oldValue = oldVNode.attributes[key];
106 | if (!(value instanceof Object) && value === oldValue) {
107 | // 避免引用类型被修改后不更新
108 | return;
109 | } else if (value && value !== 0) {
110 | this.setAttribute(el, key, value, oldValue);
111 | } else {
112 | this.removeAttribute(el, key, oldValue);
113 | }
114 | });
115 | } else if (isVText(newVNode) && isVText(oldVNode) && newVNode.content !== oldVNode.content) {
116 | newVNode.el.data = newVNode.content;
117 | }
118 | }
119 |
120 | updateVText(el: Node, newVNode: VText, oldVNode: VText) {
121 | if (newVNode.content !== oldVNode.content) {
122 | newVNode.el.data = newVNode.content;
123 | }
124 | }
125 |
126 | setAttribute(el: Element | Component, attrName: string, attrValue: any, oldValue: any = {}) {
127 | if (el instanceof HTMLInputElement && el.type === 'checkbox' && attrName === 'checked') {
128 | el['checked'] = attrValue;
129 | return;
130 | }
131 | if (el instanceof HTMLInputElement && attrName === 'value') {
132 | el['value'] = attrValue;
133 | return;
134 | }
135 | if (el instanceof HTMLElement && attrName === 'dangerouslySetInnerHTML') {
136 | el.innerHTML = attrValue;
137 | return;
138 | }
139 | if (typeof attrValue !== 'string' && el instanceof HTMLElement && attrName === 'style') {
140 | Object.keys(oldValue).forEach((key) => {
141 | if (!attrValue.hasOwnProperty(key)) {
142 | el.style[key] = '';
143 | }
144 | });
145 | Object.keys(attrValue).forEach((key) => {
146 | const value = attrValue[key];
147 | if (value === oldValue[key]) {
148 | return;
149 | } else if (value && value !== 0) {
150 | el.style[key] = value;
151 | } else {
152 | el.style[key] = '';
153 | }
154 | });
155 | return;
156 | }
157 | if (attrName === 'ref') {
158 | this.context.refs[attrValue] = el;
159 | }
160 | if (el instanceof HTMLElement && attrValue != null) {
161 | if (attrValue === true) {
162 | el.setAttribute(attrName, '');
163 | } else if (attrValue === false) {
164 | el.removeAttribute(attrName);
165 | } else {
166 | el.setAttribute(attrName, String(attrValue));
167 | }
168 | }
169 | }
170 |
171 | removeAttribute(el: HTMLElement, attrName: string, oldValue: any = {}) {
172 | if (el instanceof HTMLInputElement && el.type === 'checkbox' && attrName === 'checked') {
173 | el[attrName] = false;
174 | return;
175 | }
176 | if (el instanceof HTMLInputElement && attrName === 'value') {
177 | el[attrName] = '';
178 | return;
179 | }
180 | if (attrName === 'dangerouslySetInnerHTML') {
181 | el.innerHTML = '';
182 | return;
183 | }
184 | if (el instanceof HTMLElement && attrName === 'style') {
185 | Object.keys(oldValue).forEach((key) => {
186 | el.style[key] = '';
187 | });
188 | return;
189 | }
190 | el.removeAttribute(attrName);
191 | }
192 |
193 | // 移除el的所有子节点
194 | removeChildren(el: Node) {
195 | const children = el.childNodes;
196 | for (let i = children.length - 1; i >= 0; i--) {
197 | this.removeChild(el, children[i]);
198 | }
199 | }
200 |
201 | appendChild(parentNode: Node, childNode: Node) {
202 | parentNode.appendChild(childNode);
203 | }
204 |
205 | removeChild(parentNode: Node, childNode: Node) {
206 | parentNode.removeChild(childNode);
207 | }
208 |
209 | insertBefore(parentNode: Node, newNode: Node, referenceNode: Node) {
210 | parentNode.insertBefore(newNode, referenceNode);
211 | }
212 |
213 | parentNode(node: Node): Node {
214 | return node.parentNode;
215 | }
216 |
217 | nextSibling(node: Node) {
218 | return node.nextSibling;
219 | }
220 |
221 | removeVNode(vNode: VNode) {
222 | if (isVComponent(vNode) || isVFunction(vNode)) {
223 | this.callDestroy(vNode);
224 | }
225 | const pNode = this.parentNode(vNode.el);
226 | if (pNode) {
227 | this.removeChild(pNode, vNode.el);
228 | }
229 | }
230 |
231 | // 递归销毁所有子节点
232 | callDestroy(vnode: VNode) {
233 | if (isVElement(vnode)) {
234 | for (let i = 0; i < vnode.children.length; ++i) {
235 | this.callDestroy(vnode.children[i]);
236 | }
237 | }
238 | if (isVComponent(vnode)) {
239 | if (vnode.component?.vNode) {
240 | this.callDestroy(vnode.component?.vNode);
241 | vnode.component.destroy();
242 | }
243 | }
244 | }
245 | }
246 |
--------------------------------------------------------------------------------
/packages/jeact/src/diff.ts:
--------------------------------------------------------------------------------
1 | import { isVComponent, isVDom, isVFunction, isVText, VElement, VNode } from './v-node';
2 | import { isEmpty } from './utils';
3 | import { DomOperate } from './dom';
4 |
5 | export class Differentiator {
6 | constructor(private dom: DomOperate) {}
7 |
8 | /**
9 | * 判断input是否有相同的type
10 | * @param a
11 | * @param b
12 | */
13 | sameInputType(a: VElement, b: VElement) {
14 | if (a.type !== 'input') {
15 | return true;
16 | }
17 | const aType = a.attributes.type;
18 | const bType = b.attributes.type;
19 | if (aType == null && bType == null) {
20 | return true;
21 | }
22 | return aType === bType;
23 | }
24 |
25 | /**
26 | * 判断是否是相同的VNode
27 | * @param a
28 | * @param b
29 | */
30 | sameVNode(a: VNode, b: VNode) {
31 | if (a.key !== b.key) {
32 | return false;
33 | }
34 | if (isVDom(a) && isVDom(b)) {
35 | return (
36 | a.type === b.type && this.sameInputType(a, b) // 当标签是的时候,type必须相同
37 | );
38 | }
39 | if (isVComponent(a) && isVComponent(b) && a.type === b.type) {
40 | return true;
41 | }
42 | if (isVFunction(a) && isVFunction(b)) {
43 | return true;
44 | }
45 | return isVText(a) && isVText(b);
46 | }
47 |
48 | /**
49 | * 根据vNode的key生成map
50 | * @param children 待生成的vNode数组
51 | * @param beginIdx 生成范围
52 | * @param endIdx 生成范围
53 | */
54 | createIndexMap(children: VNode[], beginIdx: number, endIdx: number) {
55 | let i, key;
56 | const map = new Map();
57 | for (i = beginIdx; i <= endIdx; ++i) {
58 | key = children[i].key;
59 | if (key != null) {
60 | map.set(key, i);
61 | }
62 | }
63 | return map;
64 | }
65 |
66 | findVNodeIndex(vNodes: VNode[], targetNode: VNode, start: number, end: number) {
67 | for (let i = start; i < end; i++) {
68 | const currVNode = vNodes[i];
69 | if (currVNode != null && this.sameVNode(targetNode, currVNode)) {
70 | return i;
71 | }
72 | }
73 | return null;
74 | }
75 |
76 | updateChildren(
77 | parentEl: Node,
78 | oldChildren: VNode[],
79 | newChildren: VNode[],
80 | rootUpdate?: () => void,
81 | ) {
82 | let oldStartIdx = 0;
83 | let oldStartVNode = oldChildren[0];
84 | let newStartIdx = 0;
85 | let newStartVNode = newChildren[0];
86 | let oldEndIdx = oldChildren.length - 1;
87 | let oldEndVNode = oldChildren[oldEndIdx];
88 | let newEndIdx = newChildren.length - 1;
89 | let newEndVNode = newChildren[newEndIdx];
90 | let oldKeyToIdx: Map;
91 | let idxInOld: number;
92 | let vNodeToMove: VNode;
93 |
94 | while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
95 | if (oldStartVNode == null) {
96 | // 对于vnode.key的比较,会把oldVnode = null
97 | oldStartVNode = oldChildren[++oldStartIdx];
98 | } else if (oldEndVNode == null) {
99 | oldEndVNode = oldChildren[--oldEndIdx];
100 | } else if (this.sameVNode(oldStartVNode, newStartVNode)) {
101 | this.patchVNode(oldStartVNode, newStartVNode);
102 | oldStartVNode = oldChildren[++oldStartIdx];
103 | newStartVNode = newChildren[++newStartIdx];
104 | } else if (this.sameVNode(oldEndVNode, newEndVNode)) {
105 | this.patchVNode(oldEndVNode, newEndVNode);
106 | oldEndVNode = oldChildren[--oldEndIdx];
107 | newEndVNode = newChildren[--newEndIdx];
108 | } else if (this.sameVNode(oldStartVNode, newEndVNode)) {
109 | // VNode 右移
110 | this.patchVNode(oldStartVNode, newEndVNode);
111 | this.dom.insertBefore(parentEl, oldStartVNode.el, this.dom.nextSibling(newEndVNode.el));
112 | oldStartVNode = oldChildren[++oldStartIdx];
113 | newEndVNode = newChildren[--newEndIdx];
114 | } else if (this.sameVNode(oldEndVNode, newStartVNode)) {
115 | // VNode 左移
116 | this.patchVNode(oldEndVNode, newStartVNode);
117 | this.dom.insertBefore(parentEl, oldEndVNode.el, newStartVNode.el);
118 | oldEndVNode = oldChildren[--oldEndIdx];
119 | newStartVNode = newChildren[++newStartIdx];
120 | } else {
121 | if (oldKeyToIdx === undefined) {
122 | oldKeyToIdx = this.createIndexMap(oldChildren, oldStartIdx, oldEndIdx);
123 | }
124 | // 根据新vNode的key在oldVNode中寻找符合条件的
125 | idxInOld =
126 | newStartVNode.key != null
127 | ? oldKeyToIdx.get(newStartVNode.key)
128 | : this.findVNodeIndex(oldChildren, newStartVNode, oldStartIdx, oldEndIdx);
129 | if (idxInOld == null) {
130 | // New element
131 | newStartVNode.el = this.dom.createElement(newStartVNode, rootUpdate);
132 | this.dom.insertBefore(parentEl, newStartVNode.el, oldStartVNode.el);
133 | } else {
134 | vNodeToMove = oldChildren[idxInOld];
135 | if (this.sameVNode(vNodeToMove, newStartVNode)) {
136 | this.patchVNode(vNodeToMove, newStartVNode);
137 | oldChildren[idxInOld] = undefined;
138 | this.dom.insertBefore(parentEl, vNodeToMove.el, oldStartVNode.el);
139 | } else {
140 | // key相同但是element不同
141 | newStartVNode.el = this.dom.createElement(newStartVNode, rootUpdate);
142 | this.dom.insertBefore(parentEl, newStartVNode.el, oldStartVNode.el);
143 | }
144 | }
145 | newStartVNode = newChildren[++newStartIdx];
146 | }
147 | }
148 | if (oldStartIdx > oldEndIdx) {
149 | const ref = newChildren[newEndIdx + 1];
150 | const refEl = isVDom(ref) ? ref.el : null;
151 | for (; newStartIdx <= newEndIdx; newStartIdx++) {
152 | const el = this.dom.createElement(newChildren[newStartIdx], rootUpdate);
153 | newChildren[newStartIdx].el = el;
154 | this.dom.insertBefore(parentEl, el, refEl);
155 | }
156 | } else if (newStartIdx > newEndIdx) {
157 | for (; oldStartIdx <= oldEndIdx; ++oldStartIdx) {
158 | const child = oldChildren[oldStartIdx];
159 | if (child != null) {
160 | this.dom.removeVNode(child);
161 | }
162 | }
163 | }
164 | }
165 |
166 | /**
167 | * 对类型相同的的两个node同步
168 | * @param oldVNode
169 | * @param newVNode
170 | * @param rootUpdate
171 | */
172 | patchVNode(oldVNode: VNode, newVNode: VNode, rootUpdate?: () => void) {
173 | const el = (newVNode.el = oldVNode.el);
174 | if (oldVNode === newVNode) {
175 | return;
176 | }
177 | if (isVText(oldVNode) && isVText(newVNode)) {
178 | this.dom.updateVText(el, newVNode, oldVNode);
179 | return;
180 | }
181 | if (isVDom(oldVNode) && isVDom(newVNode)) {
182 | this.dom.updateVDom(el, newVNode, oldVNode);
183 | const oldChildren = oldVNode.children;
184 | const newChildren = newVNode.children;
185 | if (!isEmpty(oldChildren) && !isEmpty(newChildren) && oldChildren !== newChildren) {
186 | this.updateChildren(el, oldChildren, newChildren, rootUpdate);
187 | } else if (!isEmpty(newChildren)) {
188 | newChildren.forEach((value) =>
189 | this.dom.appendChild(el, this.dom.createElement(value, rootUpdate)),
190 | );
191 | } else if (!isEmpty(oldChildren)) {
192 | this.dom.removeChildren(el);
193 | }
194 | return;
195 | }
196 | if (isVComponent(oldVNode) && isVComponent(newVNode)) {
197 | newVNode.component = oldVNode.component;
198 | const v = oldVNode.component.runDiff();
199 | oldVNode.el = v && v.el;
200 | return;
201 | }
202 | if (isVFunction(oldVNode) && isVFunction(newVNode)) {
203 | newVNode.component = oldVNode.component;
204 | newVNode.component.functionProps = {
205 | ...newVNode.attributes,
206 | ...newVNode.handles,
207 | children: newVNode.children,
208 | };
209 | const v = oldVNode.component.runDiff();
210 | oldVNode.el = v && v.el;
211 | return;
212 | }
213 | }
214 |
215 | patch(oldVNode: VNode, newVNode: VNode, rootUpdate: () => void) {
216 | if (this.sameVNode(oldVNode, newVNode)) {
217 | this.patchVNode(oldVNode, newVNode);
218 | } else {
219 | const oldEl = oldVNode.el; // 当前oldVnode对应的真实元素节点
220 | const parentEl = oldEl.parentNode; // 父元素
221 | newVNode.el = this.dom.createElement(newVNode, rootUpdate); // 根据Vnode生成新元素
222 | this.dom.insertBefore(parentEl, newVNode.el, oldEl);
223 | this.dom.removeChild(parentEl, oldEl); // 将新元素添加进父元素
224 | }
225 | }
226 | }
227 |
--------------------------------------------------------------------------------
/packages/jeact-components/src/picture-selector/index.tsx:
--------------------------------------------------------------------------------
1 | import './index.less';
2 | import classname from 'classname';
3 | import Sortable from 'sortablejs';
4 | import { ModalComponent, mountModal } from '../modal';
5 | import { Component, createVNode } from 'jeact';
6 |
7 | type ImageInfo = {
8 | url: string;
9 | width?: number;
10 | height?: number;
11 | type?: string;
12 | propertyCode?: string;
13 | platform?: string;
14 | }
15 |
16 | export class PictureSelectorComponent extends Component {
17 | imageList: ImageInfo[] = [];
18 |
19 | poaList: { code: string; label: string }[] = [];
20 | poaFilter: string;
21 | platformList: { value: string; label: string }[] = [];
22 | platformFilter: string;
23 | currentCode: string;
24 | selectedImg: Record = { [this.currentCode]: [] };
25 |
26 | bgxCode: string;
27 | bgxSubCode: string;
28 | bgxMaxImageNum: number;
29 | bgxMaxImageSize: number;
30 | bgxMinImageSize: number;
31 | bgxMustSquare: boolean;
32 |
33 | bgxLoadImageByCode: (code) => Promise;
34 | bgxOnOk: (imageList: Record) => void;
35 |
36 | set bgxInitImg(value: Record) {
37 | this.currentCode = Object.keys(value)[0] || '';
38 | Object.keys(value).forEach((code) => {
39 | this.getImageInfos(value[code].map((value1) => ({ url: value1 }))).then((res) => {
40 | this.selectedImg[code] = res;
41 | });
42 | });
43 | }
44 |
45 | get displayImage() {
46 | return this.imageList.filter(
47 | (value) =>
48 | (!this.poaFilter || value.propertyCode === this.poaFilter) &&
49 | (!this.platformFilter || value.platform === this.platformFilter),
50 | );
51 | }
52 |
53 | loadPoaList() {
54 | const poaList = [
55 | ...new Set(this.imageList.map((value) => value.propertyCode).filter((value) => value)),
56 | ];
57 | return poaList.map(function (value) {
58 | return {
59 | code: value,
60 | label: value,
61 | };
62 | });
63 | }
64 |
65 | loadPlatformList() {
66 | const platformList = [
67 | ...new Set(this.imageList.map((value) => value.platform).filter((value) => value)),
68 | ];
69 | return platformList.map(function (value) {
70 | return {
71 | value,
72 | label: value,
73 | };
74 | });
75 | }
76 |
77 | getImageInfos(images: ImageInfo[]) {
78 | return Promise.all(images.map((image) => this.getImgInfo(image)));
79 | }
80 |
81 | getImgInfo(image: ImageInfo): Promise {
82 | const newImage: ImageInfo = { ...image };
83 | return new Promise((resolve, reject) => {
84 | const img = new Image();
85 | img.src = image.url;
86 | img.onerror = function (e) {
87 | resolve(newImage);
88 | };
89 | const arr = newImage.url.split('.');
90 | newImage.type = arr[arr.length - 1].toUpperCase();
91 | if (img.complete) {
92 | newImage.width = img.width;
93 | newImage.height = img.height;
94 | resolve(newImage);
95 | } else {
96 | img.onload = function () {
97 | newImage.width = img.width;
98 | newImage.height = img.height;
99 | img.onload = null; // 避免重复加载
100 | resolve(newImage);
101 | };
102 | }
103 | });
104 | }
105 |
106 | constructor(args) {
107 | super(args);
108 | }
109 |
110 | mounted() {
111 | super.mounted();
112 |
113 | const wrapper = this.refs.sortWrapper as HTMLElement;
114 | // tslint:disable-next-line:no-unused-expression
115 | new Sortable(wrapper, {
116 | animation: 150,
117 | onUpdate: (event) => {
118 | const item = this.selectedImg[this.currentCode].splice(event.oldIndex, 1)[0];
119 | this.selectedImg[this.currentCode].splice(event.newIndex, 0, item);
120 | this.update();
121 | },
122 | });
123 | this.loadByCode();
124 | }
125 |
126 | async loadByCode() {
127 | if (this.bgxLoadImageByCode && this.bgxCode) {
128 | try {
129 | this.imageList = await this.bgxLoadImageByCode(this.bgxCode);
130 | } catch (e) {
131 | this.imageList = [];
132 | }
133 | this.update();
134 | this.poaList = this.loadPoaList();
135 | if (this.bgxSubCode && this.poaList.some((value) => value.code === this.bgxSubCode)) {
136 | this.poaFilter = this.bgxSubCode;
137 | this.bgxSubCode = null;
138 | }
139 | this.platformList = this.loadPlatformList();
140 | this.imageList = await this.getImageInfos(this.imageList);
141 | this.update();
142 | }
143 | }
144 |
145 | selectAll = (e) => {
146 | const num =
147 | this.bgxMaxImageNum != null
148 | ? this.bgxMaxImageNum - this.selectedImg[this.currentCode].length
149 | : this.selectedImg.length;
150 | const unselectedImg = this.displayImage.filter(
151 | (value) => !this.isSelected(value) && this.sizeCheck(value),
152 | );
153 | const newImgs = unselectedImg[this.currentCode].slice(0, num);
154 | this.selectedImg[this.currentCode] = this.selectedImg[this.currentCode].concat(newImgs);
155 | this.update();
156 | };
157 |
158 | isSelected = (item: ImageInfo) => {
159 | return (
160 | this.selectedImg &&
161 | this.selectedImg[this.currentCode].some((val) => val.url === (item && item.url))
162 | );
163 | };
164 |
165 | private clickImg(item: ImageInfo) {
166 | if (this.isSelected(item)) {
167 | this.removeImg(item);
168 | } else if (this.sizeCheck(item)) {
169 | this.selectImg(item);
170 | }
171 | this.update();
172 | }
173 |
174 | selectImg(item: ImageInfo) {
175 | if (this.selectedImg[this.currentCode].length >= this.bgxMaxImageNum) {
176 | alert(`最多选择${this.bgxMaxImageNum}张图片!`);
177 | return;
178 | }
179 | this.selectedImg[this.currentCode] = [...this.selectedImg[this.currentCode], item];
180 | }
181 |
182 | removeImg(item: ImageInfo) {
183 | const index = this.selectedImg[this.currentCode].findIndex((val) => val.url === item.url);
184 | this.selectedImg[this.currentCode].splice(index, 1);
185 | this.selectedImg[this.currentCode] = [...this.selectedImg[this.currentCode]];
186 | this.update();
187 | }
188 |
189 | preview(item: ImageInfo, imageList: ImageInfo[]) {
190 | let currItem = item;
191 |
192 | const modal = Component.create(
193 | ModalComponent,
194 | {
195 | title: '预览图片',
196 | content: () => {
197 | return (
198 |
199 |

200 |
lastImg()}>
201 |
202 |
203 |
nextImg()}>
204 |
205 |
206 |
207 | );
208 | },
209 | maskCloseable: true,
210 | style: 'width: 900px;',
211 | buttons: null,
212 | },
213 | document.body,
214 | );
215 | this.update();
216 |
217 | const nextImg = () => {
218 | const index = imageList.findIndex((value) => value === currItem);
219 | currItem = imageList[(index + 1) % imageList.length];
220 | modal.update();
221 | };
222 | const lastImg = () => {
223 | const index = imageList.findIndex((value) => value === currItem);
224 | const newIndex = index - 1 < 0 ? index + imageList.length : index - 1;
225 | currItem = imageList[newIndex];
226 | modal.update();
227 | };
228 | }
229 |
230 | async codeChange(value: any) {
231 | this.bgxCode = value;
232 | this.loadByCode();
233 | }
234 |
235 | poaFilterChange() {
236 | const that = this;
237 | return function (this: HTMLSelectElement) {
238 | that.poaFilter = this.value;
239 | that.loadByCode();
240 | };
241 | }
242 |
243 | platformFilterChange() {
244 | const that = this;
245 | return function (this: HTMLSelectElement) {
246 | that.platformFilter = this.value;
247 | that.loadByCode();
248 | };
249 | }
250 |
251 | render() {
252 | const SelectedImg = ({ item, imageList }: { item: ImageInfo; imageList: ImageInfo[] }) => {
253 | return (
254 |
255 |

256 |
257 | this.removeImg(item)}
260 | title="取消选择"
261 | />
262 | this.preview(item, imageList)}
265 | title="查看大图"
266 | />
267 |
268 |
269 | );
270 | };
271 |
272 | const DisplayImg = ({ item, imageList }: { item: ImageInfo; imageList: ImageInfo[] }) => {
273 | return (
274 | this.clickImg(item)}
276 | class={classname([
277 | 'bgx-pic-images-option',
278 | { 'bgx-pic-images-option-selected': this.isSelected(item) },
279 | ])}
280 | >
281 |
282 |

283 |
284 | {this.isSelected(item) && (
285 |
286 | )}
287 | {
290 | this.preview(item, imageList);
291 | e.stopPropagation();
292 | }}
293 | title="查看大图"
294 | />
295 |
296 |
297 |
298 |
299 | {item.width}*{item.height} {item.type}
300 |
301 |
302 |
303 | );
304 | };
305 |
306 | return (
307 |
308 |
309 | {Object.keys(this.selectedImg)
310 | .filter(Boolean)
311 | .map((code) => {
312 | return (
313 |
{
319 | this.currentCode = code;
320 | this.update();
321 | }}
322 | >
323 | {code}
324 |
325 | );
326 | })}
327 |
328 |
329 | {this.selectedImg[this.currentCode].map((item) => (
330 |
331 | ))}
332 |
333 |
334 |
377 |
378 | {this.displayImage.map((item) => (
379 |
380 | ))}
381 |
382 |
383 |
384 | );
385 | }
386 |
387 | private sizeCheck(value: ImageInfo) {
388 | if (
389 | this.bgxMaxImageSize != null &&
390 | value.height > this.bgxMaxImageSize &&
391 | value.width > this.bgxMaxImageSize
392 | ) {
393 | alert(`请选择长宽小于 ${this.bgxMaxImageSize} 的图片`);
394 | return false;
395 | }
396 | if (
397 | this.bgxMinImageSize != null &&
398 | value.height < this.bgxMinImageSize &&
399 | value.width < this.bgxMinImageSize
400 | ) {
401 | alert(`请选择长宽大于 ${this.bgxMinImageSize} 的图片`);
402 | return false;
403 | }
404 | if (this.bgxMustSquare && value.height !== value.width) {
405 | alert(`请选择长度宽度相等的图片`);
406 | return false;
407 | }
408 | return true;
409 | }
410 | }
411 |
412 | // 挂载为jquery插件
413 | mountModal({
414 | name: 'pictureSelector',
415 | title: '图片选择',
416 | content: PictureSelectorComponent,
417 | style: 'width: 1000px;',
418 | onOk(instance: PictureSelectorComponent) {
419 | instance?.bgxOnOk(
420 | Object.keys(instance.selectedImg).reduce((obj, key) => {
421 | obj[key] = instance.selectedImg[key].map((value) => value.url);
422 | return obj;
423 | }, {}),
424 | );
425 | },
426 | });
427 |
--------------------------------------------------------------------------------
/packages/jeact-components/src/pm-selector/index.tsx:
--------------------------------------------------------------------------------
1 | import './index.less';
2 | import { ValueComponentProps, ValueComponent, VNode, mountComponent, createVNode } from 'jeact';
3 |
4 | export interface PmSelectorOption {
5 | value: any;
6 | label: string;
7 | checked?: boolean;
8 | parent?: PmSelectorOption;
9 | children?: PmSelectorOption[];
10 | }
11 |
12 | export interface PmSelectorComponentProps extends ValueComponentProps {
13 | placeholder?: string;
14 | options?: any[];
15 | valueField?: string;
16 | labelField?: string;
17 | childrenField?: string;
18 | cacheName?: string;
19 | url: string;
20 | psPlaceholder?: string;
21 | psOptions?: any[];
22 | psValueField?: string;
23 | psLabelField?: string;
24 | psChildrenField?: string;
25 | psCacheName?: string;
26 | value?: any[];
27 | psUrl: string;
28 | }
29 |
30 | export class PmSelectorComponent extends ValueComponent {
31 | placeholder: string;
32 | valueField: string;
33 | labelField: string;
34 | childrenField: string;
35 | cacheName: string;
36 | value: any[];
37 | private _options: any[] = [];
38 | get options(): any[] {
39 | return this._options;
40 | }
41 | set options(value: any[]) {
42 | this._options = value;
43 | this.convertedOptions = this.convert(
44 | value,
45 | this.valueField,
46 | this.labelField,
47 | this.childrenField,
48 | null,
49 | this.value,
50 | );
51 | this.leafOptions = this.leafChildren(this.convertedOptions);
52 | this.loadCommonOption();
53 | this.update();
54 | }
55 | private _url: string;
56 | get url(): string {
57 | return this._url;
58 | }
59 |
60 | set url(value: string) {
61 | this._url = value;
62 | window['$'].get(value, (res) => {
63 | if (res && res.success) {
64 | this.options = res.result;
65 | }
66 | });
67 | }
68 |
69 | convertedOptions: PmSelectorOption[] = [];
70 | readonly saveCommonMax = 10;
71 | open = false;
72 | commonOptions: PmSelectorOption[] = [];
73 | commonCheckedAll = false;
74 | selectedIndexes: number[] = [];
75 | leafOptions: PmSelectorOption[] = [];
76 | searchText = '';
77 | searchOptions: PmSelectorOption[] = [];
78 | searchCheckedAll = false;
79 | showSearch = true;
80 |
81 | get columns(): PmSelectorOption[][] {
82 | let list = this.convertedOptions;
83 | const result = [list];
84 | for (let i = 0; this.selectedIndexes[i] != null; i++) {
85 | const selectedIndex = this.selectedIndexes[i];
86 | if (
87 | list[selectedIndex] &&
88 | list[selectedIndex].children &&
89 | list[selectedIndex].children.length > 0
90 | ) {
91 | list = list[selectedIndex].children;
92 | result.push(list);
93 | } else {
94 | break;
95 | }
96 | }
97 | return result;
98 | }
99 |
100 | // 获取被勾选的所有叶子节点
101 | get checkedOptions(): PmSelectorOption[] {
102 | let checkedOptions = [];
103 | let options = this.convertedOptions; // 待搜索列表
104 | while (options.length > 0) {
105 | // 新增放进待搜索列表
106 | const currChecked = options.filter(
107 | (value) => (!value.children || !value.children.length) && value.checked,
108 | );
109 | checkedOptions = checkedOptions.concat(currChecked);
110 | options = options
111 | .filter((value) => value.children && value.children.length)
112 | .flatMap((value) => value.children); // 搜索待搜索列表
113 | }
114 | return checkedOptions;
115 | }
116 |
117 | // 用于界面展示
118 | get checkedOptionStr(): string {
119 | return this.checkedOptions.map((value) => value.label).join(',');
120 | }
121 |
122 | constructor(args: PmSelectorComponentProps) {
123 | super(args);
124 | this.placeholder = args.psPlaceholder || args.placeholder;
125 | this.valueField = args.psValueField || args.valueField || 'value';
126 | this.labelField = args.psLabelField || args.labelField || 'label';
127 | this.childrenField = args.psChildrenField || args.childrenField || 'children';
128 | this.cacheName = args.psCacheName || args.cacheName || 'commonOptions';
129 | this.value = args.value || [];
130 | if (args.psOptions || args.options) {
131 | this.options = args.psOptions || args.options;
132 | } else if (args.psUrl || args.url) {
133 | this.url = args.psUrl || args.url;
134 | }
135 | }
136 |
137 | writeValue(value: string) {
138 | this.value = value ? value.split(',') : [];
139 | if (this.convertedOptions != null) {
140 | this.leafOptions.forEach((value1) => (value1.checked = this.value.includes(value1.value)));
141 | this.update();
142 | }
143 | }
144 |
145 | // 组件声明周期hook,当组件创建后调用,此时尚未挂载DOM
146 | beforeMount() {}
147 |
148 | // 组件声明周期hook,当组件挂载DOM后调用
149 | mounted() {
150 | document.addEventListener(
151 | 'click',
152 | (e: any) => {
153 | if (this.refs.popup) {
154 | const path = e.path || (e.composedPath && e.composedPath());
155 | if (
156 | !(this.refs.popup as HTMLElement).contains(e.target) &&
157 | !path.includes(this.refs.popup)
158 | ) {
159 | this.closePopup();
160 | }
161 | }
162 | if (this.refs.search) {
163 | const path = e.path || (e.composedPath && e.composedPath());
164 | if (
165 | !(this.refs.search as HTMLElement).contains(e.target) &&
166 | !path.includes(this.refs.search)
167 | ) {
168 | this.closeSearchPopup();
169 | }
170 | }
171 | },
172 | true,
173 | );
174 | }
175 |
176 | // 关闭搜索弹窗
177 | closeSearchPopup() {
178 | this.showSearch = false;
179 | this.update();
180 | }
181 |
182 | // 打开搜索弹窗
183 | openSearchPopup = () => {
184 | this.showSearch = true;
185 | this.update();
186 | };
187 |
188 | // 勾选
189 | checkOption(option: PmSelectorOption, checked: boolean) {
190 | option.checked = checked;
191 | this.checkChildren(option, checked);
192 | this.checkAll(option.parent);
193 | this.checkCommonOption();
194 | this.checkSearchOption();
195 | this.onChange(this.checkedOptions.map((value1) => value1.value));
196 | this.update();
197 | }
198 |
199 | commonCheckAll = (e: Event) => {
200 | const checked = e.target['checked'];
201 | this.commonOptions.forEach((value) => this.checkOption(value, checked));
202 | this.update();
203 | };
204 |
205 | leafChildren(options: PmSelectorOption[]): PmSelectorOption[] {
206 | const childrenLeaf = options.flatMap((value) => this.leafChildren(value.children));
207 | const leaf = options.filter((value) => !value.children || !value.children.length);
208 | return [...childrenLeaf, ...leaf];
209 | }
210 |
211 | clear = () => {
212 | this.searchText = '';
213 | this.commonCheckedAll = false;
214 | this.checkedOptions.forEach((value) => {
215 | value.checked = false;
216 | this.checkAll(value.parent);
217 | });
218 | this.update();
219 | this.onChange([]);
220 | };
221 |
222 | searchChange = (e: Event) => {
223 | this.searchText = e.target['value'];
224 | this.searchOptions = this.leafOptions.filter((value) => {
225 | return value.label && value.label.includes(this.searchText);
226 | });
227 | this.checkSearchOption();
228 | this.update();
229 | };
230 |
231 | searchCheckAll = (e) => {
232 | const checked = e.target['checked'];
233 | this.searchOptions.forEach((value) => this.checkOption(value, checked));
234 | this.update();
235 | };
236 |
237 | openPopup = (e: Event) => {
238 | e.stopPropagation();
239 | this.open = true;
240 | this.update();
241 | };
242 |
243 | closePopup() {
244 | this.open = false;
245 | this.searchText = '';
246 | this.update();
247 | }
248 |
249 | // 格式化外部option为内部option
250 | convert(
251 | options: any[],
252 | valueField,
253 | labelField,
254 | childrenField,
255 | parent: PmSelectorOption,
256 | values?: any[],
257 | ): PmSelectorOption[] {
258 | return (options || []).map((option) => {
259 | const obj: PmSelectorOption = {
260 | value: option[valueField],
261 | label: option[labelField],
262 | checked: (values || []).includes(String(option[valueField])),
263 | parent,
264 | };
265 | obj.children = this.convert(
266 | option[childrenField] || [],
267 | valueField,
268 | labelField,
269 | childrenField,
270 | obj,
271 | values,
272 | );
273 | return obj;
274 | });
275 | }
276 |
277 | optionChange(e: Event, option: PmSelectorOption) {
278 | const checked = e.target['checked'];
279 | this.checkOption(option, checked);
280 | this.update();
281 | }
282 |
283 | // 判断父节点是否需要勾选(当子节点全选时)
284 | checkAll(option: PmSelectorOption) {
285 | if (!option) {
286 | return;
287 | }
288 | const check =
289 | option.children &&
290 | option.children.length > 0 &&
291 | option.children.every((value) => value.checked);
292 | if (check !== option.checked) {
293 | option.checked = check;
294 | this.checkAll(option.parent);
295 | }
296 | }
297 |
298 | // 设置option的check状态,并递归更新子节点,然后saveCommonOption
299 | checkChildren(option: PmSelectorOption, check: boolean) {
300 | option.checked = check;
301 | if (option.children && option.children.length > 0) {
302 | option.children.forEach((value) => {
303 | this.checkChildren(value, check);
304 | });
305 | } else if (check) {
306 | // 如果是被选中的叶子节点
307 | this.saveCommonOption(option);
308 | }
309 | }
310 |
311 | // 展开下一级菜单
312 | nextLevel(level: number, index: number) {
313 | this.selectedIndexes = this.selectedIndexes.slice(0, level);
314 | this.selectedIndexes[level] = index;
315 | this.update();
316 | }
317 |
318 | // 保存常用选择到localStorage中
319 | saveCommonOption(option: PmSelectorOption) {
320 | if (this.commonOptions.includes(option)) {
321 | return;
322 | }
323 | this.commonOptions.unshift(option);
324 | if (this.commonOptions.length > this.saveCommonMax) {
325 | this.commonOptions = this.commonOptions.slice(0, this.saveCommonMax);
326 | }
327 | const commonOptions = this.commonOptions.map((value) => value.value);
328 | localStorage.setItem(this.cacheName, JSON.stringify(commonOptions));
329 | }
330 |
331 | // 加载localStorage中的常用选择
332 | loadCommonOption() {
333 | const commonOptions: string[] = JSON.parse(localStorage.getItem(this.cacheName)) || [];
334 | this.commonOptions = commonOptions
335 | .map((value) => this.leafOptions.find((value1) => value1.value === value))
336 | .filter((value) => value);
337 | }
338 |
339 | // 更新常用选择全选状态
340 | checkCommonOption() {
341 | this.commonCheckedAll =
342 | this.commonOptions &&
343 | this.commonOptions.length &&
344 | this.commonOptions.every((value) => value.checked);
345 | }
346 |
347 | // 更新搜索选择全选状态
348 | checkSearchOption() {
349 | this.searchCheckedAll =
350 | this.searchOptions &&
351 | this.searchOptions.length &&
352 | this.searchOptions.every((value) => value.checked);
353 | }
354 |
355 | render() {
356 | const checkedOptions = this.checkedOptions;
357 | let popup: VNode;
358 | if (this.open) {
359 | popup = (
360 |
479 | );
480 | }
481 |
482 | return (
483 |
484 |
485 |
494 |
495 | {this.checkedOptions.length}项
496 |
497 |
498 | {popup}
499 |
500 | );
501 | }
502 | }
503 |
504 | // 挂载为jquery插件
505 | mountComponent({
506 | name: 'pmSelector',
507 | componentType: PmSelectorComponent,
508 | props: [
509 | 'psValueField',
510 | 'psLabelField',
511 | 'psChildrenField',
512 | 'psPlaceholder',
513 | 'psUrl',
514 | 'psCacheName',
515 | ],
516 | });
517 |
--------------------------------------------------------------------------------
/packages/jeact-components/src/single-cascader/index.tsx:
--------------------------------------------------------------------------------
1 | import { ValueComponentProps, ValueComponent, VNode, mountComponent, createVNode } from 'jeact';
2 | import './index.less';
3 |
4 | export interface SingleCascaderOption {
5 | value: any;
6 | label: string;
7 | selected?: boolean;
8 | parent?: SingleCascaderOption;
9 | children?: SingleCascaderOption[];
10 | }
11 |
12 | export interface SingleCascaderComponentProps extends ValueComponentProps {
13 | placeholder?: string;
14 | options?: any[];
15 | valueField?: string;
16 | labelField?: string;
17 | childrenField?: string;
18 | cacheName?: string;
19 | value?: any;
20 | }
21 |
22 | export class SingleCascaderComponent extends ValueComponent {
23 | placeholder: string;
24 | valueField: string;
25 | labelField: string;
26 | childrenField: string;
27 | value: any;
28 | cacheName: string;
29 | private _options: any[] = [];
30 | get options(): any[] {
31 | return this._options;
32 | }
33 | set options(value: any[]) {
34 | this._options = value;
35 | this.convertedOptions = this.convert(
36 | value,
37 | this.valueField,
38 | this.labelField,
39 | this.childrenField,
40 | null,
41 | this.value,
42 | );
43 | this.leafOptions = this.leafChildren(this.convertedOptions);
44 | if (this.selectedOptions.length) {
45 | this.selectedIndexes = this.getSelectedIndexes(this.selectedOptions[0]);
46 | this.selectedString = this.getSelectedString(this.selectedOptions[0]);
47 | }
48 | this.loadCommonOption();
49 | this.update();
50 | }
51 |
52 | convertedOptions: SingleCascaderOption[];
53 | readonly saveCommonMax = 10;
54 | open = false;
55 | commonOptions: SingleCascaderOption[] = [];
56 | showCommon = true;
57 | selectedIndexes: number[] = [];
58 | selectedString = '';
59 | leafOptions: SingleCascaderOption[] = [];
60 | searchText = '';
61 | searchOptions: any[] = [];
62 | showSearch = false;
63 |
64 | get columns(): SingleCascaderOption[][] {
65 | let list = this.convertedOptions;
66 | const result = [list];
67 | for (let i = 0; this.selectedIndexes[i] != null; i++) {
68 | const selectedIndex = this.selectedIndexes[i];
69 | if (
70 | list[selectedIndex] &&
71 | list[selectedIndex].children &&
72 | list[selectedIndex].children.length > 0
73 | ) {
74 | list = list[selectedIndex].children;
75 | result.push(list);
76 | } else {
77 | break;
78 | }
79 | }
80 | return result;
81 | }
82 |
83 | get selectedOptions(): SingleCascaderOption[] {
84 | return this.leafOptions.filter((value) => value.selected);
85 | }
86 |
87 | constructor(args: SingleCascaderComponentProps) {
88 | super(args);
89 | }
90 |
91 | writeValue(value: string) {
92 | this.value = value;
93 | if (this.convertedOptions != null) {
94 | this.leafOptions.forEach(
95 | (value1) => (value1.selected = String(this.value) === String(value1.value)),
96 | );
97 | if (this.selectedOptions.length) {
98 | this.selectedIndexes = this.getSelectedIndexes(this.selectedOptions[0]);
99 | this.selectedString = this.getSelectedString(this.selectedOptions[0]);
100 | }
101 | this.update();
102 | }
103 | }
104 |
105 | // 组件声明周期hook,当组件创建后调用,此时尚未挂载DOM
106 | beforeMount() {}
107 |
108 | // 组件声明周期hook,当组件挂载DOM后调用
109 | mounted() {
110 | document.addEventListener(
111 | 'click',
112 | (e: any) => {
113 | if (this.refs.popup) {
114 | const path = e.path || (e.composedPath && e.composedPath());
115 | if (
116 | !(this.refs.popup as HTMLElement).contains(e.target) &&
117 | !path.includes(this.refs.popup)
118 | ) {
119 | this.closePopup();
120 | }
121 | }
122 | if (this.refs.search) {
123 | const path = e.path || (e.composedPath && e.composedPath());
124 | if (
125 | !(this.refs.search as HTMLElement).contains(e.target) &&
126 | !path.includes(this.refs.search)
127 | ) {
128 | this.closeSearchPopup();
129 | }
130 | }
131 | },
132 | true,
133 | );
134 | }
135 |
136 | // 选择
137 | selectOption(option: SingleCascaderOption, level?: number, index?: number) {
138 | if (level != null && index != null) {
139 | this.nextLevel(level, index);
140 | } else {
141 | this.selectedIndexes = this.getSelectedIndexes(option);
142 | }
143 | if (this.isLeaf(option)) {
144 | this.leafOptions.forEach((item) => (item.selected = false));
145 | option.selected = true;
146 | this.selectedString = this.getSelectedString(option);
147 | this.saveCommonOption(option);
148 | this.onChange(option.value);
149 | this.closePopup();
150 | }
151 | this.update();
152 | }
153 |
154 | // 展开下一级菜单
155 | nextLevel(level: number, index: number) {
156 | this.selectedIndexes = this.selectedIndexes.slice(0, level);
157 | this.selectedIndexes[level] = index;
158 | this.update();
159 | }
160 |
161 | getSelectedIndexes(option: SingleCascaderOption): number[] {
162 | const indexes = [];
163 | let selectedOption = option;
164 | while (selectedOption.parent) {
165 | const index = selectedOption.parent.children.findIndex(
166 | (val) => String(val.value) === String(selectedOption.value),
167 | );
168 | selectedOption = selectedOption.parent;
169 | indexes.unshift(index);
170 | }
171 | // 获取第一级index
172 | const firstIndex = this.convertedOptions.findIndex(
173 | (val) => String(val.value) === String(selectedOption.value),
174 | );
175 | indexes.unshift(firstIndex);
176 | return indexes;
177 | }
178 |
179 | getSelectedString(option: SingleCascaderOption): string {
180 | const stringArr = [];
181 | let selectedOption = option;
182 | while (selectedOption.parent) {
183 | const o = selectedOption.parent.children.find(
184 | (val) => String(val.value) === String(selectedOption.value),
185 | );
186 | selectedOption = selectedOption.parent;
187 | stringArr.unshift(o);
188 | }
189 | // 获取第一级index
190 | const firstOption = this.convertedOptions.find(
191 | (val) => String(val.value) === String(selectedOption.value),
192 | );
193 | stringArr.unshift(firstOption);
194 | return stringArr.map((val) => val.label).join(' > ');
195 | }
196 |
197 | clear = () => {
198 | this.searchText = '';
199 | this.searchOptions = [];
200 | this.selectedOptions.forEach((value) => {
201 | value.selected = false;
202 | });
203 | this.selectedIndexes = [];
204 | this.selectedString = '';
205 | this.update();
206 | this.onChange([]);
207 | };
208 |
209 | searchInput = (e: InputEvent) => {
210 | this.searchText = e.target['value'];
211 | this.update();
212 | };
213 |
214 | searchKeydown = (e: KeyboardEvent) => {
215 | if (e.code === 'Enter') {
216 | e.preventDefault();
217 | e.stopPropagation();
218 | this.searchChange();
219 | }
220 | };
221 |
222 | searchChange = () => {
223 | if (!this.searchText) {
224 | this.searchOptions = [];
225 | return;
226 | }
227 | const searchedOptionsLinked = this.searchChildren(this.convertedOptions, this.searchText);
228 | this.searchOptions = searchedOptionsLinked.map((value) => {
229 | const text = value.map((value1) => value1.label).join(' > ');
230 | return {
231 | text,
232 | html: text.replace(RegExp(this.searchText, 'ig'), (str) => str.fontcolor('red')),
233 | ids: value.map((value1) => value1.value),
234 | originOption: value[value.length - 1],
235 | };
236 | });
237 | this.openSearchPopup();
238 | this.update();
239 | };
240 |
241 | // 递归搜索children
242 | searchChildren(options: SingleCascaderOption[], searchText: string): SingleCascaderOption[][] {
243 | if (!options) {
244 | return [];
245 | }
246 | const lowerCaseSearchText = searchText.toLowerCase();
247 | const searchedOptions = options.filter((value) =>
248 | (value.label || '').toLowerCase().includes(lowerCaseSearchText),
249 | );
250 | const childrenOptionsLinked = this.leafChildrenLinked(searchedOptions);
251 |
252 | const notSearchedOptions = options.filter(
253 | (value) => !(value.label || '').toLowerCase().includes(lowerCaseSearchText),
254 | );
255 | const searchedOptionsLinked = notSearchedOptions
256 | .filter((value) => !this.isLeaf(value))
257 | .flatMap((value) => {
258 | return this.searchChildren(value.children, searchText).map((value1) => [value, ...value1]);
259 | });
260 | return [...searchedOptionsLinked, ...childrenOptionsLinked];
261 | }
262 |
263 | // options到叶子节点的数组的数组
264 | leafChildrenLinked(options: SingleCascaderOption[]): SingleCascaderOption[][] {
265 | if (!options) {
266 | return [];
267 | }
268 | const leafLinked = options.filter((value) => this.isLeaf(value)).map((value) => [value]);
269 | const childrenLeafLinked = options
270 | .filter((value) => !this.isLeaf(value))
271 | .flatMap((value) => {
272 | // 所有子节点和当前节点,拼成链
273 | return this.leafChildrenLinked(value.children).map((value1) => [value, ...value1]);
274 | });
275 | return [...leafLinked, ...childrenLeafLinked];
276 | }
277 |
278 | // 打开搜索弹窗
279 | openSearchPopup = () => {
280 | this.showSearch = true;
281 | this.update();
282 | };
283 |
284 | // 关闭搜索弹窗
285 | closeSearchPopup() {
286 | this.showSearch = false;
287 | this.update();
288 | }
289 |
290 | openPopup = (e: Event) => {
291 | e.stopPropagation();
292 | this.open = true;
293 | this.update();
294 | };
295 |
296 | closePopup() {
297 | this.open = false;
298 | this.searchText = '';
299 | this.searchOptions = [];
300 | this.closeSearchPopup();
301 | this.update();
302 | }
303 |
304 | switchCommon = () => {
305 | this.showCommon = !this.showCommon;
306 | this.update();
307 | };
308 |
309 | // 格式化外部option为内部option
310 | convert(
311 | options: any[],
312 | valueField,
313 | labelField,
314 | childrenField,
315 | parent: SingleCascaderOption,
316 | value?: any,
317 | ): SingleCascaderOption[] {
318 | return (options || []).map((option) => {
319 | const obj: SingleCascaderOption = {
320 | value: option[valueField],
321 | label: option[labelField],
322 | selected: String(value || '') === String(option[valueField]),
323 | parent,
324 | };
325 | obj.children = this.convert(
326 | option[childrenField] || [],
327 | valueField,
328 | labelField,
329 | childrenField,
330 | obj,
331 | value,
332 | );
333 | return obj;
334 | });
335 | }
336 |
337 | // 获取所有叶子节点
338 | leafChildren(options: SingleCascaderOption[]): SingleCascaderOption[] {
339 | const childrenLeaf = options.flatMap((value) => this.leafChildren(value.children));
340 | const leaf = options.filter((value) => this.isLeaf(value));
341 | return [...childrenLeaf, ...leaf];
342 | }
343 |
344 | isLeaf(option: SingleCascaderOption): boolean {
345 | return !option.children || !option.children.length;
346 | }
347 |
348 | // 保存常用选择到localStorage中
349 | saveCommonOption(option: SingleCascaderOption) {
350 | if (this.commonOptions.includes(option) || this.cacheName == null) {
351 | return;
352 | }
353 | this.commonOptions.unshift(option);
354 | if (this.commonOptions.length > this.saveCommonMax) {
355 | this.commonOptions = this.commonOptions.slice(0, this.saveCommonMax);
356 | }
357 | const commonOptions = this.commonOptions.map((value) => value.value);
358 | localStorage.setItem(this.cacheName, JSON.stringify(commonOptions));
359 | }
360 |
361 | // 加载localStorage中的常用选择
362 | loadCommonOption() {
363 | const commonOptions: string[] = JSON.parse(localStorage.getItem(this.cacheName)) || [];
364 | this.commonOptions = commonOptions
365 | .map((value) => this.leafOptions.find((value1) => value1.value === value))
366 | .filter((value) => value);
367 | }
368 |
369 | render() {
370 | let popup: VNode;
371 | if (this.open) {
372 | popup = (
373 |
470 | );
471 | }
472 |
473 | return (
474 |
475 |
476 |
485 |
486 | {popup}
487 |
488 | );
489 | }
490 | }
491 |
492 | // 挂载为jquery插件
493 | mountComponent({
494 | name: 'singleCascader',
495 | componentType: SingleCascaderComponent,
496 | props: ['valueField', 'labelField', 'childrenField', 'placeholder', 'cacheName'],
497 | });
498 |
--------------------------------------------------------------------------------
/packages/jeact-components/src/cascader/index.tsx:
--------------------------------------------------------------------------------
1 | import type { ValueComponentProps, VNode } from 'jeact';
2 | import { ValueComponent, createVNode, mountComponent } from 'jeact';
3 |
4 | export type CascaderOption = {
5 | value: any;
6 | label: string;
7 | checked?: boolean;
8 | parent?: CascaderOption;
9 | children?: CascaderOption[];
10 | }
11 |
12 | export type CascaderComponentProps = {
13 | placeholder?: string;
14 | options?: any[];
15 | valueField?: string;
16 | labelField?: string;
17 | childrenField?: string;
18 | cacheName?: string;
19 | value?: any[];
20 | } & ValueComponentProps
21 |
22 | export class CascaderComponent extends ValueComponent {
23 | placeholder: string;
24 | valueField: string;
25 | labelField: string;
26 | childrenField: string;
27 | cacheName: string;
28 | value: any[];
29 | private _options: any[] = [];
30 | get options(): any[] {
31 | return this._options;
32 | }
33 | set options(value: any[]) {
34 | this._options = value;
35 | this.convertedOptions = this.convert(
36 | value,
37 | this.valueField,
38 | this.labelField,
39 | this.childrenField,
40 | null,
41 | this.value,
42 | );
43 | this.leafOptions = this.leafChildren(this.convertedOptions);
44 | this.loadCommonOption();
45 | this.update();
46 | }
47 |
48 | convertedOptions: CascaderOption[] = [];
49 | readonly saveCommonMax = 10;
50 | open = false;
51 | commonOptions: CascaderOption[] = [];
52 | commonCheckedAll = false;
53 | showCommon = true;
54 | selectedIndexes: number[] = [];
55 | leafOptions: CascaderOption[] = [];
56 | searchText = '';
57 | searchOptions: any[] = [];
58 | searchCheckedAll = false;
59 | showSearch = false;
60 |
61 | get columns(): CascaderOption[][] {
62 | let list = this.convertedOptions;
63 | const result = [list];
64 | for (let i = 0; this.selectedIndexes[i] != null; i++) {
65 | const selectedIndex = this.selectedIndexes[i];
66 | if (
67 | list[selectedIndex] &&
68 | list[selectedIndex].children &&
69 | list[selectedIndex].children.length > 0
70 | ) {
71 | list = list[selectedIndex].children;
72 | result.push(list);
73 | } else {
74 | break;
75 | }
76 | }
77 | return result;
78 | }
79 |
80 | // 获取被勾选的所有叶子节点
81 | get checkedOptions(): CascaderOption[] {
82 | let checkedOptions = [];
83 | let options = this.convertedOptions; // 待搜索列表
84 | while (options.length > 0) {
85 | // 新增放进待搜索列表
86 | const currChecked = options.filter(
87 | (value) => (!value.children || !value.children.length) && value.checked,
88 | );
89 | checkedOptions = checkedOptions.concat(currChecked);
90 | options = options
91 | .filter((value) => value.children && value.children.length)
92 | .flatMap((value) => value.children); // 搜索待搜索列表
93 | }
94 | return checkedOptions;
95 | }
96 |
97 | // 用于界面展示
98 | get checkedOptionStr(): string {
99 | return this.checkedOptions.map((value) => value.label).join(',');
100 | }
101 |
102 | constructor(args: CascaderComponentProps) {
103 | super(args);
104 | }
105 |
106 | writeValue(value: string) {
107 | this.value = value ? value.split(',') : [];
108 | if (this.convertedOptions != null) {
109 | this.leafOptions.forEach((value1) => {
110 | const localValue = value1;
111 | localValue.checked = this.value.includes(value1.value);
112 | });
113 | this.update();
114 | }
115 | }
116 |
117 | // 组件声明周期hook,当组件创建后调用,此时尚未挂载DOM
118 | beforeMount() {}
119 |
120 | // 组件声明周期hook,当组件挂载DOM后调用
121 | mounted() {
122 | document.addEventListener(
123 | 'click',
124 | (e: any) => {
125 | if (this.refs.popup) {
126 | const path = e.path || (e.composedPath && e.composedPath());
127 | if (
128 | !(this.refs.popup as HTMLElement).contains(e.target) &&
129 | !path.includes(this.refs.popup)
130 | ) {
131 | this.closePopup();
132 | }
133 | }
134 | if (this.refs.search) {
135 | const path = e.path || (e.composedPath && e.composedPath());
136 | if (
137 | !(this.refs.search as HTMLElement).contains(e.target) &&
138 | !path.includes(this.refs.search)
139 | ) {
140 | this.closeSearchPopup();
141 | }
142 | }
143 | },
144 | true,
145 | );
146 | }
147 |
148 | // 勾选
149 | checkOption(option: CascaderOption, checked: boolean) {
150 | option.checked = checked;
151 | this.checkChildren(option, checked);
152 | this.checkAll(option.parent);
153 | this.checkCommonOption();
154 | this.checkSearchOption();
155 | this.onChange(this.checkedOptions.map((value1) => value1.value));
156 | this.update();
157 | }
158 |
159 | // 展开下一级菜单
160 | nextLevel(level: number, index: number) {
161 | this.selectedIndexes[level] = index;
162 | this.update();
163 | }
164 |
165 | clear = () => {
166 | this.searchText = '';
167 | this.searchOptions = [];
168 | this.searchCheckedAll = false;
169 | this.commonCheckedAll = false;
170 | this.checkedOptions.forEach((value) => {
171 | value.checked = false;
172 | this.checkAll(value.parent);
173 | });
174 | this.update();
175 | this.onChange([]);
176 | };
177 |
178 | searchInput = (e: InputEvent) => {
179 | this.searchText = (e.target as HTMLInputElement).value;
180 | this.update();
181 | };
182 |
183 | searchKeydown = (e: KeyboardEvent) => {
184 | if (e.code === 'Enter') {
185 | e.preventDefault();
186 | e.stopPropagation();
187 | this.searchChange();
188 | }
189 | };
190 |
191 | searchChange = () => {
192 | if (!this.searchText) {
193 | this.searchOptions = [];
194 | return;
195 | }
196 | const searchedOptionsLinked = this.searchChildren(this.convertedOptions, this.searchText);
197 | this.searchOptions = searchedOptionsLinked.map((value) => {
198 | const text = value.map((value1) => value1.label).join(' > ');
199 | return {
200 | text,
201 | html: text.replace(RegExp(this.searchText, 'ig'), (str) => str.fontcolor('red')),
202 | ids: value.map((value1) => value1.value),
203 | originOption: value[value.length - 1],
204 | };
205 | });
206 | this.checkSearchOption();
207 | this.openSearchPopup();
208 | this.update();
209 | };
210 |
211 | // 递归搜索children
212 | searchChildren(options: CascaderOption[], searchText: string): CascaderOption[][] {
213 | if (!options) {
214 | return [];
215 | }
216 | const lowerCaseSearchText = searchText.toLowerCase();
217 | const searchedOptions = options.filter((value) =>
218 | (value.label || '').toLowerCase().includes(lowerCaseSearchText),
219 | );
220 | const childrenOptionsLinked = this.leafChildrenLinked(searchedOptions);
221 |
222 | const notSearchedOptions = options.filter(
223 | (value) => !(value.label || '').toLowerCase().includes(lowerCaseSearchText),
224 | );
225 | const searchedOptionsLinked = notSearchedOptions
226 | .filter((value) => !this.isLeaf(value))
227 | .flatMap((value) => {
228 | return this.searchChildren(value.children, searchText).map((value1) => [value, ...value1]);
229 | });
230 | return [...searchedOptionsLinked, ...childrenOptionsLinked];
231 | }
232 |
233 | // options到叶子节点的数组的数组
234 | leafChildrenLinked(options: CascaderOption[]): CascaderOption[][] {
235 | if (!options) {
236 | return [];
237 | }
238 | const leafLinked = options.filter((value) => this.isLeaf(value)).map((value) => [value]);
239 | const childrenLeafLinked = options
240 | .filter((value) => !this.isLeaf(value))
241 | .flatMap((value) => {
242 | // 所有子节点和当前节点,拼成链
243 | return this.leafChildrenLinked(value.children).map((value1) => [value, ...value1]);
244 | });
245 | return [...leafLinked, ...childrenLeafLinked];
246 | }
247 |
248 | searchCheckAll = (e: InputEvent) => {
249 | const checked = (e.target as HTMLInputElement).checked;
250 | this.searchOptions.forEach((value) => this.checkOption(value.originOption, checked));
251 | this.update();
252 | };
253 |
254 | // 打开搜索弹窗
255 | openSearchPopup = () => {
256 | this.showSearch = true;
257 | this.update();
258 | };
259 |
260 | // 关闭搜索弹窗
261 | closeSearchPopup() {
262 | this.showSearch = false;
263 | this.update();
264 | }
265 |
266 | openPopup = (e: Event) => {
267 | e.stopPropagation();
268 | this.open = true;
269 | this.update();
270 | };
271 |
272 | closePopup() {
273 | this.open = false;
274 | this.searchText = '';
275 | this.searchOptions = [];
276 | this.searchCheckedAll = false;
277 | this.closeSearchPopup();
278 | this.update();
279 | }
280 |
281 | switchCommon = () => {
282 | this.showCommon = !this.showCommon;
283 | this.update();
284 | };
285 |
286 | commonCheckAll = (e: InputEvent) => {
287 | const checked = (e.target as HTMLInputElement).checked;
288 | this.commonOptions.forEach((value) => this.checkOption(value, checked));
289 | this.update();
290 | };
291 |
292 | // 格式化外部option为内部option
293 | convert(
294 | options: any[],
295 | valueField,
296 | labelField,
297 | childrenField,
298 | parent: CascaderOption,
299 | values?: any[],
300 | ): CascaderOption[] {
301 | return (options || []).map((option) => {
302 | const obj: CascaderOption = {
303 | value: option[valueField],
304 | label: option[labelField],
305 | checked: (values || []).includes(String(option[valueField])),
306 | parent,
307 | };
308 | obj.children = this.convert(
309 | option[childrenField] || [],
310 | valueField,
311 | labelField,
312 | childrenField,
313 | obj,
314 | values,
315 | );
316 | return obj;
317 | });
318 | }
319 |
320 | leafChildren(options: CascaderOption[]): CascaderOption[] {
321 | const childrenLeaf = options.flatMap((value) => this.leafChildren(value.children));
322 | const leaf = options.filter((value) => this.isLeaf(value));
323 | return [...childrenLeaf, ...leaf];
324 | }
325 |
326 | isLeaf(option: CascaderOption): boolean {
327 | return !option.children || !option.children.length;
328 | }
329 |
330 | optionChange(e: InputEvent, option: CascaderOption) {
331 | const checked = (e.target as HTMLInputElement).checked;
332 | this.checkOption(option, checked);
333 | this.update();
334 | }
335 |
336 | // 判断父节点是否需要勾选(当子节点全选时)
337 | checkAll(option: CascaderOption) {
338 | if (!option) {
339 | return;
340 | }
341 | const check = !this.isLeaf(option) && option.children.every((value) => value.checked);
342 | if (check !== option.checked) {
343 | option.checked = check;
344 | this.checkAll(option.parent);
345 | }
346 | }
347 |
348 | // 设置option的check状态,并递归更新子节点,然后saveCommonOption
349 | checkChildren(option: CascaderOption, check: boolean) {
350 | option.checked = check;
351 | if (!this.isLeaf(option)) {
352 | option.children.forEach((value) => {
353 | this.checkChildren(value, check);
354 | });
355 | } else if (check) {
356 | // 如果是被选中的叶子节点
357 | this.saveCommonOption(option);
358 | }
359 | }
360 |
361 | // 保存常用选择到localStorage中
362 | saveCommonOption(option: CascaderOption) {
363 | if (this.commonOptions.includes(option) || this.cacheName == null) {
364 | return;
365 | }
366 | this.commonOptions.unshift(option);
367 | if (this.commonOptions.length > this.saveCommonMax) {
368 | this.commonOptions = this.commonOptions.slice(0, this.saveCommonMax);
369 | }
370 | const commonOptions = this.commonOptions.map((value) => value.value);
371 | localStorage.setItem(this.cacheName, JSON.stringify(commonOptions));
372 | }
373 |
374 | // 加载localStorage中的常用选择
375 | loadCommonOption() {
376 | const commonOptions: string[] = JSON.parse(localStorage.getItem(this.cacheName)) || [];
377 | this.commonOptions = commonOptions
378 | .map((value) => this.leafOptions.find((value1) => value1.value === value))
379 | .filter((value) => value);
380 | }
381 |
382 | // 更新常用选择全选状态
383 | checkCommonOption() {
384 | this.commonCheckedAll =
385 | this.commonOptions &&
386 | this.commonOptions.length &&
387 | this.commonOptions.every((value) => value.checked);
388 | }
389 |
390 | // 更新搜索选择全选状态
391 | checkSearchOption() {
392 | this.searchCheckedAll =
393 | this.searchOptions?.length && this.searchOptions.every((value) => value.originOption.checked);
394 | }
395 |
396 | render() {
397 | let popup: VNode;
398 | if (this.open) {
399 | popup = (
400 |
540 | );
541 | }
542 |
543 | return (
544 |
545 |
546 |
555 |
556 | {this.checkedOptions.length}项
557 |
558 |
559 | {popup}
560 |
561 | );
562 | }
563 | }
564 |
565 | // 挂载为jquery插件
566 | mountComponent({
567 | name: 'cascader',
568 | componentType: CascaderComponent,
569 | props: ['valueField', 'labelField', 'childrenField', 'placeholder', 'cacheName'],
570 | });
571 |
--------------------------------------------------------------------------------
/docs/js/jeact.iife.js:
--------------------------------------------------------------------------------
1 | var Jeact = (function (exports) {
2 | 'use strict';
3 |
4 | class VNode {
5 | }
6 | class VElement extends VNode {
7 | constructor(type, attributes = {}, handles = {}, children = []) {
8 | super();
9 | this.type = type;
10 | this.attributes = attributes;
11 | this.handles = handles;
12 | this.children = children;
13 | }
14 | }
15 | class VText extends VNode {
16 | constructor(content) {
17 | super();
18 | this.content = String(content || content === 0 ? content : '');
19 | }
20 | }
21 | function createVNode(type, props, ...children) {
22 | let handle = {};
23 | let attribute = {};
24 | if (props) {
25 | handle = Object.keys(props).filter(value => value.startsWith('on')).reduce((pre, curr) => {
26 | pre[curr] = props[curr];
27 | return pre;
28 | }, {});
29 | attribute = Object.keys(props).filter(value => !value.startsWith('on')).reduce((pre, curr) => {
30 | pre[curr] = props[curr];
31 | return pre;
32 | }, {});
33 | }
34 | const vNodeChildren = children.flat(2).map(value => {
35 | return isVElement(value) ? value : new VText(value);
36 | });
37 | return new VElement(type, attribute, handle, vNodeChildren);
38 | }
39 | function isVNode(vNode) {
40 | return vNode instanceof VNode;
41 | }
42 | function isVElement(vNode) {
43 | return vNode && vNode.type != null;
44 | }
45 | function isVText(vNode) {
46 | return vNode && vNode.content !== undefined;
47 | }
48 | function isVDom(vNode) {
49 | return isVElement(vNode) && typeof vNode.type === 'string';
50 | }
51 | function isVComponent(vNode) {
52 | return isVElement(vNode) && typeof vNode.type === 'function' && vNode.type.prototype && vNode.type.prototype.render;
53 | }
54 | function isVFunction(vNode) {
55 | return isVElement(vNode) && typeof vNode.type === 'function' && !(vNode.type.prototype && vNode.type.prototype.render);
56 | }
57 |
58 | class DomOperate {
59 | constructor(context) {
60 | this.context = context;
61 | }
62 | createElement(vNode, rootUpdate) {
63 | var _a, _b, _c;
64 | if (isVComponent(vNode)) {
65 | vNode.component = Component.create(vNode.type, Object.assign(Object.assign(Object.assign({}, vNode.attributes), vNode.handles), { children: vNode.children, rootUpdate }));
66 | vNode.el = (_a = vNode.component.vNode) === null || _a === void 0 ? void 0 : _a.el;
67 | Object.keys(vNode.attributes).forEach(key => {
68 | const value = vNode.attributes[key];
69 | this.setAttribute(vNode.component, key, value);
70 | });
71 | return vNode.el;
72 | }
73 | else if (isVFunction(vNode)) {
74 | vNode.component = Component.create(FunctionComponent, {
75 | renderFunction: vNode.type,
76 | functionProps: Object.assign(Object.assign(Object.assign({}, vNode.attributes), vNode.handles), { children: vNode.children }),
77 | rootUpdate,
78 | });
79 | vNode.el = (_b = vNode.component.vNode) === null || _b === void 0 ? void 0 : _b.el;
80 | return vNode.el;
81 | }
82 | else if (isVDom(vNode)) {
83 | const el = document.createElement(vNode.type);
84 | vNode.el = el;
85 | Object.keys(vNode.handles).forEach(key => {
86 | const value = vNode.handles[key];
87 | const handleName = key.toLowerCase().replace(/^on/, '');
88 | el.addEventListener(handleName, value);
89 | });
90 | Object.keys(vNode.attributes).forEach(key => {
91 | const value = vNode.attributes[key];
92 | this.setAttribute(el, key, value);
93 | });
94 | (_c = vNode.children) === null || _c === void 0 ? void 0 : _c.forEach(value => {
95 | const node = this.createElement(value, rootUpdate);
96 | if (node) {
97 | el.appendChild(node);
98 | }
99 | });
100 | return el;
101 | }
102 | else if (isVText(vNode)) {
103 | vNode.el = document.createTextNode(vNode.content);
104 | return vNode.el;
105 | }
106 | }
107 | /**
108 | *
109 | * @param el VNode对应的真是dom
110 | * @param newVNode
111 | * @param oldVNode
112 | */
113 | updateVDom(el, newVNode, oldVNode) {
114 | if (isVDom(newVNode) && isVDom(oldVNode) && el instanceof HTMLElement) {
115 | Object.keys(oldVNode.handles).forEach(key => {
116 | if (!newVNode.handles.hasOwnProperty(key)) {
117 | const value = oldVNode.handles[key];
118 | const handleName = key.toLowerCase().replace(/^on/, '');
119 | el.removeEventListener(handleName, value);
120 | }
121 | });
122 | Object.keys(newVNode.handles).forEach(key => {
123 | const handleName = key.toLowerCase().replace(/^on/, '');
124 | const value = newVNode.handles[key];
125 | const oldValue = oldVNode.handles[key];
126 | if (value === oldValue) {
127 | return;
128 | }
129 | if (oldVNode.handles.hasOwnProperty(key)) {
130 | el.removeEventListener(handleName, oldValue);
131 | }
132 | el.addEventListener(handleName, value);
133 | });
134 | Object.keys(oldVNode.attributes).forEach(key => {
135 | if (!newVNode.attributes.hasOwnProperty(key)) {
136 | this.removeAttribute(el, key, oldVNode.attributes[key]);
137 | }
138 | });
139 | Object.keys(newVNode.attributes).forEach(key => {
140 | const value = newVNode.attributes[key];
141 | const oldValue = oldVNode.attributes[key];
142 | if (value === oldValue) {
143 | return;
144 | }
145 | else if (value && value !== 0) {
146 | this.setAttribute(el, key, value, oldValue);
147 | }
148 | else {
149 | this.removeAttribute(el, key, oldValue);
150 | }
151 | });
152 | }
153 | else if (isVText(newVNode) && isVText(oldVNode) && newVNode.content !== oldVNode.content) {
154 | newVNode.el.data = newVNode.content;
155 | }
156 | }
157 | updateVText(el, newVNode, oldVNode) {
158 | if (newVNode.content !== oldVNode.content) {
159 | newVNode.el.data = newVNode.content;
160 | }
161 | }
162 | setAttribute(el, attrName, attrValue, oldValue = {}) {
163 | if (el instanceof HTMLInputElement && el.type === 'checkbox' && attrName === 'checked') {
164 | el['checked'] = attrValue;
165 | return;
166 | }
167 | if (el instanceof HTMLInputElement && attrName === 'value') {
168 | el['value'] = attrValue;
169 | return;
170 | }
171 | if (el instanceof HTMLElement && attrName === 'dangerouslySetInnerHTML') {
172 | el.innerHTML = attrValue;
173 | return;
174 | }
175 | if (typeof attrValue !== 'string' && el instanceof HTMLElement && attrName === 'style') {
176 | Object.keys(oldValue).forEach(key => {
177 | if (!attrValue.hasOwnProperty(key)) {
178 | el.style[key] = '';
179 | }
180 | });
181 | Object.keys(attrValue).forEach(key => {
182 | const value = attrValue[key];
183 | if (value === oldValue[key]) {
184 | return;
185 | }
186 | else if (value && value !== 0) {
187 | el.style[key] = value;
188 | }
189 | else {
190 | el.style[key] = '';
191 | }
192 | });
193 | return;
194 | }
195 | if (attrName === 'ref') {
196 | this.context.refs[attrValue] = el;
197 | }
198 | if (el instanceof HTMLElement && attrValue != null) {
199 | if (attrValue === true) {
200 | el.setAttribute(attrName, '');
201 | }
202 | else if (attrValue === false) {
203 | el.removeAttribute(attrName);
204 | }
205 | else {
206 | el.setAttribute(attrName, String(attrValue));
207 | }
208 | }
209 | }
210 | removeAttribute(el, attrName, oldValue = {}) {
211 | if (el instanceof HTMLInputElement && el.type === 'checkbox' && attrName === 'checked') {
212 | el[attrName] = false;
213 | return;
214 | }
215 | if (el instanceof HTMLInputElement && attrName === 'value') {
216 | el[attrName] = '';
217 | return;
218 | }
219 | if (attrName === 'dangerouslySetInnerHTML') {
220 | el.innerHTML = '';
221 | return;
222 | }
223 | if (el instanceof HTMLElement && attrName === 'style') {
224 | Object.keys(oldValue).forEach(key => {
225 | el.style[key] = '';
226 | });
227 | return;
228 | }
229 | el.removeAttribute(attrName);
230 | }
231 | // 移除el的所有子节点
232 | removeChildren(el) {
233 | const children = el.childNodes;
234 | for (let i = children.length - 1; i >= 0; i--) {
235 | this.removeChild(el, children[i]);
236 | }
237 | }
238 | appendChild(parentNode, childNode) {
239 | parentNode.appendChild(childNode);
240 | }
241 | removeChild(parentNode, childNode) {
242 | parentNode.removeChild(childNode);
243 | }
244 | insertBefore(parentNode, newNode, referenceNode) {
245 | parentNode.insertBefore(newNode, referenceNode);
246 | }
247 | parentNode(node) {
248 | return node.parentNode;
249 | }
250 | nextSibling(node) {
251 | return node.nextSibling;
252 | }
253 | removeVNode(vNode) {
254 | if (isVComponent(vNode) || isVFunction(vNode)) {
255 | this.callDestroy(vNode);
256 | }
257 | const pNode = this.parentNode(vNode.el);
258 | if (pNode) {
259 | this.removeChild(pNode, vNode.el);
260 | }
261 | }
262 | // 递归销毁所有子节点
263 | callDestroy(vnode) {
264 | var _a, _b;
265 | if (isVElement(vnode)) {
266 | for (let i = 0; i < vnode.children.length; ++i) {
267 | this.callDestroy(vnode.children[i]);
268 | }
269 | }
270 | if (isVComponent(vnode)) {
271 | if ((_a = vnode.component) === null || _a === void 0 ? void 0 : _a.vNode) {
272 | this.callDestroy((_b = vnode.component) === null || _b === void 0 ? void 0 : _b.vNode);
273 | vnode.component.destroy();
274 | }
275 | }
276 | }
277 | }
278 |
279 | class Differentiator {
280 | constructor(dom) {
281 | this.dom = dom;
282 | }
283 | /**
284 | * 判断input是否有相同的type
285 | * @param a
286 | * @param b
287 | */
288 | sameInputType(a, b) {
289 | if (a.type !== 'input') {
290 | return true;
291 | }
292 | const aType = a.attributes.type;
293 | const bType = b.attributes.type;
294 | if (aType == null && bType == null) {
295 | return true;
296 | }
297 | return aType === bType;
298 | }
299 | /**
300 | * 判断是否是相同的VNode
301 | * @param a
302 | * @param b
303 | */
304 | sameVNode(a, b) {
305 | if (isVDom(a) && isVDom(b)) {
306 | return (a.key === b.key &&
307 | a.type === b.type &&
308 | this.sameInputType(a, b) // 当标签是的时候,type必须相同
309 | );
310 | }
311 | if (isVElement(a) && isVElement(b) && a.type === b.type) {
312 | return true;
313 | }
314 | return !!(isVText(a) && isVText(b));
315 | }
316 | /**
317 | * 根据vNode的key生成map
318 | * @param children 待生成的vNode数组
319 | * @param beginIdx 生成范围
320 | * @param endIdx 生成范围
321 | */
322 | createIndexMap(children, beginIdx, endIdx) {
323 | let i, key;
324 | const map = new Map();
325 | for (i = beginIdx; i <= endIdx; ++i) {
326 | key = children[i].key;
327 | if (key != null) {
328 | map.set(key, i);
329 | }
330 | }
331 | return map;
332 | }
333 | findVNodeIndex(vNodes, targetNode, start, end) {
334 | for (let i = start; i < end; i++) {
335 | const currVNode = vNodes[i];
336 | if (currVNode != null && this.sameVNode(targetNode, currVNode)) {
337 | return i;
338 | }
339 | }
340 | return null;
341 | }
342 | updateChildren(parentEl, oldChildren, newChildren, rootUpdate) {
343 | let oldStartIdx = 0;
344 | let oldStartVNode = oldChildren[0];
345 | let newStartIdx = 0;
346 | let newStartVNode = newChildren[0];
347 | let oldEndIdx = oldChildren.length - 1;
348 | let oldEndVNode = oldChildren[oldEndIdx];
349 | let newEndIdx = newChildren.length - 1;
350 | let newEndVNode = newChildren[newEndIdx];
351 | let oldKeyToIdx;
352 | let idxInOld;
353 | let vNodeToMove;
354 | while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
355 | if (oldStartVNode == null) { // 对于vnode.key的比较,会把oldVnode = null
356 | oldStartVNode = oldChildren[++oldStartIdx];
357 | }
358 | else if (oldEndVNode == null) {
359 | oldEndVNode = oldChildren[--oldEndIdx];
360 | }
361 | else if (this.sameVNode(oldStartVNode, newStartVNode)) {
362 | this.patchVNode(oldStartVNode, newStartVNode);
363 | oldStartVNode = oldChildren[++oldStartIdx];
364 | newStartVNode = newChildren[++newStartIdx];
365 | }
366 | else if (this.sameVNode(oldEndVNode, newEndVNode)) {
367 | this.patchVNode(oldEndVNode, newEndVNode);
368 | oldEndVNode = oldChildren[--oldEndIdx];
369 | newEndVNode = newChildren[--newEndIdx];
370 | }
371 | else if (this.sameVNode(oldStartVNode, newEndVNode)) { // VNode 右移
372 | this.patchVNode(oldStartVNode, newEndVNode);
373 | this.dom.insertBefore(parentEl, oldStartVNode.el, this.dom.nextSibling(newEndVNode.el));
374 | oldStartVNode = oldChildren[++oldStartIdx];
375 | newEndVNode = newChildren[--newEndIdx];
376 | }
377 | else if (this.sameVNode(oldEndVNode, newStartVNode)) { // VNode 左移
378 | this.patchVNode(oldEndVNode, newStartVNode);
379 | this.dom.insertBefore(parentEl, oldEndVNode.el, newStartVNode.el);
380 | oldEndVNode = oldChildren[--oldEndIdx];
381 | newStartVNode = newChildren[++newStartIdx];
382 | }
383 | else {
384 | if (oldKeyToIdx === undefined) {
385 | oldKeyToIdx = this.createIndexMap(oldChildren, oldStartIdx, oldEndIdx);
386 | }
387 | // 根据新vNode的key在oldVNode中寻找符合条件的
388 | idxInOld = newStartVNode.key != null
389 | ? oldKeyToIdx.get(newStartVNode.key)
390 | : this.findVNodeIndex(oldChildren, newStartVNode, oldStartIdx, oldEndIdx);
391 | if (idxInOld == null) { // New element
392 | newStartVNode.el = this.dom.createElement(newStartVNode, rootUpdate);
393 | this.dom.insertBefore(parentEl, newStartVNode.el, oldStartVNode.el);
394 | }
395 | else {
396 | vNodeToMove = oldChildren[idxInOld];
397 | if (this.sameVNode(vNodeToMove, newStartVNode)) {
398 | this.patchVNode(vNodeToMove, newStartVNode);
399 | oldChildren[idxInOld] = undefined;
400 | this.dom.insertBefore(parentEl, vNodeToMove.el, oldStartVNode.el);
401 | }
402 | else {
403 | // key相同但是element不同
404 | newStartVNode.el = this.dom.createElement(newStartVNode, rootUpdate);
405 | this.dom.insertBefore(parentEl, newStartVNode.el, oldStartVNode.el);
406 | }
407 | }
408 | newStartVNode = newChildren[++newStartIdx];
409 | }
410 | }
411 | if (oldStartIdx > oldEndIdx) {
412 | const ref = newChildren[newEndIdx + 1];
413 | const refEl = isVDom(ref) ? ref.el : null;
414 | for (; newStartIdx <= newEndIdx; newStartIdx++) {
415 | const el = this.dom.createElement(newChildren[newStartIdx], rootUpdate);
416 | newChildren[newStartIdx].el = el;
417 | this.dom.insertBefore(parentEl, el, refEl);
418 | }
419 | }
420 | else if (newStartIdx > newEndIdx) {
421 | for (; oldStartIdx <= oldEndIdx; ++oldStartIdx) {
422 | const child = oldChildren[oldStartIdx];
423 | if (child != null) {
424 | this.dom.removeVNode(child);
425 | }
426 | }
427 | }
428 | }
429 | /**
430 | * 对类型相同的的两个node同步
431 | * @param oldVNode
432 | * @param newVNode
433 | * @param rootUpdate
434 | */
435 | patchVNode(oldVNode, newVNode, rootUpdate) {
436 | const el = newVNode.el = oldVNode.el;
437 | if (oldVNode === newVNode) {
438 | return;
439 | }
440 | if (isVText(oldVNode) && isVText(newVNode)) {
441 | this.dom.updateVText(el, newVNode, oldVNode);
442 | return;
443 | }
444 | if (isVDom(oldVNode) && isVDom(newVNode)) {
445 | this.dom.updateVDom(el, newVNode, oldVNode);
446 | const oldChildren = oldVNode.children;
447 | const newChildren = newVNode.children;
448 | if (!isEmpty(oldChildren) && !isEmpty(newChildren) && oldChildren !== newChildren) {
449 | this.updateChildren(el, oldChildren, newChildren, rootUpdate);
450 | }
451 | else if (!isEmpty(newChildren)) {
452 | newChildren.forEach(value => this.dom.appendChild(el, this.dom.createElement(value, rootUpdate)));
453 | }
454 | else if (!isEmpty(oldChildren)) {
455 | this.dom.removeChildren(el);
456 | }
457 | return;
458 | }
459 | if (isVComponent(oldVNode) && isVComponent(newVNode)) {
460 | newVNode.component = oldVNode.component;
461 | const v = oldVNode.component.runDiff();
462 | oldVNode.el = v && v.el;
463 | return;
464 | }
465 | if (isVFunction(oldVNode) && isVFunction(newVNode)) {
466 | newVNode.component = oldVNode.component;
467 | newVNode.component.functionProps = Object.assign(Object.assign(Object.assign({}, newVNode.attributes), newVNode.handles), { children: newVNode.children });
468 | const v = oldVNode.component.runDiff();
469 | oldVNode.el = v && v.el;
470 | return;
471 | }
472 | }
473 | patch(oldVNode, newVNode, rootUpdate) {
474 | if (this.sameVNode(oldVNode, newVNode)) {
475 | this.patchVNode(oldVNode, newVNode);
476 | }
477 | else {
478 | const oldEl = oldVNode.el; // 当前oldVnode对应的真实元素节点
479 | const parentEl = oldEl.parentNode; // 父元素
480 | newVNode.el = this.dom.createElement(newVNode, rootUpdate); // 根据Vnode生成新元素
481 | this.dom.insertBefore(parentEl, newVNode.el, oldEl);
482 | this.dom.removeChild(parentEl, oldEl); // 将新元素添加进父元素
483 | }
484 | }
485 | }
486 |
487 | class Component {
488 | constructor(args) {
489 | this.updateFlag = false;
490 | this.dom = new DomOperate(this);
491 | this.diff = new Differentiator(this.dom);
492 | this.refs = {};
493 | if (args) {
494 | Object.assign(this, args);
495 | }
496 | }
497 | static create(componentType, props, el) {
498 | const dom = typeof el === 'string' ? document.querySelector(el) : el;
499 | const component = new componentType(Object.assign({}, props));
500 | component.el = dom;
501 | component.beforeMount();
502 | component.mount();
503 | component.mounted();
504 | return component;
505 | }
506 | mount() {
507 | this.vNode = this.render();
508 | const node = this.dom.createElement(this.vNode, this.update.bind(this));
509 | this.appendToEl(node);
510 | }
511 | appendToEl(node) {
512 | if (this.el && node) {
513 | this.dom.appendChild(this.el, node);
514 | }
515 | }
516 | reappendToEl(oldNode, newNode) {
517 | if (oldNode === newNode || this.el == null) {
518 | return;
519 | }
520 | const parentNode = this.dom.parentNode(oldNode);
521 | if (parentNode == null) {
522 | return;
523 | }
524 | this.dom.removeChild(parentNode, oldNode);
525 | this.appendToEl(newNode);
526 | }
527 | update() {
528 | if (this.updateFlag) {
529 | return;
530 | }
531 | this.updateFlag = true;
532 | Promise.resolve().then(() => {
533 | this.updateFlag = false;
534 | if (this.rootUpdate) {
535 | this.rootUpdate();
536 | return;
537 | }
538 | this.runDiff();
539 | });
540 | }
541 | runDiff() {
542 | if (this.vNode == null || this.vNode.el == null) {
543 | return null;
544 | }
545 | const newVNode = this.render();
546 | this.diff.patch(this.vNode, newVNode, this.update.bind(this));
547 | this.reappendToEl(this.vNode.el, newVNode.el);
548 | this.vNode = newVNode;
549 | return newVNode;
550 | }
551 | beforeMount() {
552 | }
553 | mounted() {
554 | }
555 | beforeUpdate() {
556 | }
557 | updated() {
558 | }
559 | destroy() {
560 | }
561 | render() {
562 | return null;
563 | }
564 | }
565 | class ValueComponent extends Component {
566 | constructor(props) {
567 | super(props);
568 | this.valueChange = props.valueChange;
569 | }
570 | mount() {
571 | if (this.el) {
572 | this.writeValue(this.el.value);
573 | }
574 | super.mount();
575 | }
576 | readValue(value) {
577 | return value != null ? String(value) : '';
578 | }
579 | onChange(value) {
580 | if (this.valueChange) {
581 | this.valueChange(value);
582 | }
583 | if (this.el) {
584 | this.el.value = this.readValue(value);
585 | this.el.dispatchEvent(new InputEvent('input'));
586 | this.el.dispatchEvent(new UIEvent('change'));
587 | }
588 | }
589 | appendToEl(node) {
590 | const parentNode = this.el && this.dom.parentNode(this.el);
591 | if (parentNode && node) {
592 | this.dom.insertBefore(parentNode, node, this.el);
593 | this.el.hidden = true;
594 | }
595 | }
596 | }
597 | class FunctionComponent extends Component {
598 | render() {
599 | return this.renderFunction(this.functionProps);
600 | }
601 | }
602 |
603 | // 判空
604 | function isEmpty(value) {
605 | return value == null || value.length === 0;
606 | }
607 | // 驼峰/下划线 转 短横线命名(kebab-case)
608 | function getKebabCase(str) {
609 | const reg = /^([A-Z$]+)/g;
610 | const reg2 = /_([a-zA-Z$]+)/g;
611 | const reg3 = /([A-Z$]+)/g;
612 | return str.replace(reg, ($, $1) => $1.toLowerCase())
613 | .replace(reg2, ($, $1) => '-' + $1.toLowerCase())
614 | .replace(reg3, ($, $1) => '-' + $1.toLowerCase());
615 | }
616 | // 挂载为jquery插件
617 | function mountComponent({ name, componentType, props, $ = window['jQuery'] }) {
618 | if ($ == null) {
619 | return;
620 | }
621 | $.fn[name] = function (...args) {
622 | if (typeof args[0] === 'string') {
623 | const [propName, ...methodArgs] = args;
624 | const component = this.data(name);
625 | if (!component) {
626 | $.error(`节点不是一个 ${name} 组件`);
627 | }
628 | if (typeof component[propName] === 'function') {
629 | component[propName](...methodArgs);
630 | }
631 | else if (methodArgs != null && methodArgs.length === 1) {
632 | return component[propName] = methodArgs[0];
633 | }
634 | else {
635 | return component[propName];
636 | }
637 | }
638 | else if (args[0] == null || typeof args[0] === 'object') {
639 | const methodArgs = args[0];
640 | const component = Component.create(componentType, methodArgs, this[0]);
641 | this.data(name, component);
642 | }
643 | else {
644 | $.error('第一个参数只能是string或object');
645 | }
646 | return this;
647 | };
648 | $(function () {
649 | $(`[${getKebabCase(name)}]`).each(function () {
650 | const $selected = $(this);
651 | const propsValue = (props || []).reduce((pre, curr) => {
652 | pre[curr] = $selected.attr(getKebabCase(curr));
653 | return pre;
654 | }, {});
655 | $selected[name](propsValue);
656 | });
657 | });
658 | }
659 |
660 | exports.Component = Component;
661 | exports.FunctionComponent = FunctionComponent;
662 | exports.VElement = VElement;
663 | exports.VNode = VNode;
664 | exports.VText = VText;
665 | exports.ValueComponent = ValueComponent;
666 | exports.createVNode = createVNode;
667 | exports.getKebabCase = getKebabCase;
668 | exports.isEmpty = isEmpty;
669 | exports.isVComponent = isVComponent;
670 | exports.isVDom = isVDom;
671 | exports.isVElement = isVElement;
672 | exports.isVFunction = isVFunction;
673 | exports.isVNode = isVNode;
674 | exports.isVText = isVText;
675 | exports.mountComponent = mountComponent;
676 |
677 | return exports;
678 |
679 | }({}));
680 | //# sourceMappingURL=jeact.iife.js.map
681 |
--------------------------------------------------------------------------------