first commit
This commit is contained in:
66
uni_modules/tdesign-uniapp/components/common/src/control.js
Normal file
66
uni_modules/tdesign-uniapp/components/common/src/control.js
Normal file
@@ -0,0 +1,66 @@
|
||||
const defaultOption = {
|
||||
valueKey: 'value',
|
||||
defaultValueKey: 'defaultValue',
|
||||
changeEventName: 'change',
|
||||
strict: true,
|
||||
};
|
||||
/**
|
||||
* 受控函数
|
||||
* 用法示例:
|
||||
* {
|
||||
* attached() {
|
||||
* this.control = useControl.call(this);
|
||||
* }
|
||||
* }
|
||||
* 注意事项:
|
||||
* 1:命名规范:约束value等命名,一般不需要改。内部属性统一命名以_开头。
|
||||
* 2:value默认值:小程序number类型未传值(undefined)会初始化为0,导致无法判断。建议默认值设置为null
|
||||
* 3:prop变化:需要开发者自己监听,observers = { value(val):{ this.control.set(val) } }
|
||||
* @param this 页面实例
|
||||
* @param option 配置项 参见ControlOption
|
||||
* @returns
|
||||
*/
|
||||
function useControl(option) {
|
||||
const { valueKey, defaultValueKey, changeEventName, strict } = {
|
||||
...defaultOption,
|
||||
...option,
|
||||
};
|
||||
const props = this.properties || {};
|
||||
const value = props[valueKey];
|
||||
// 半受控时,不需要defaultValueKey,默认值与valueKey相同
|
||||
const defaultValue = props[strict ? defaultValueKey : valueKey];
|
||||
let controlled = false;
|
||||
// 完全受控模式:检查受控属性,判断是否受控
|
||||
if (strict && typeof value !== 'undefined' && value !== null) {
|
||||
controlled = true;
|
||||
}
|
||||
const set = (newVal, extObj, fn) => {
|
||||
this.setData(
|
||||
{
|
||||
[`_${valueKey}`]: newVal,
|
||||
...extObj,
|
||||
},
|
||||
fn,
|
||||
);
|
||||
};
|
||||
return {
|
||||
controlled,
|
||||
initValue: controlled ? value : defaultValue,
|
||||
set,
|
||||
get: () => this.data[`_${valueKey}`],
|
||||
change: (newVal, customChangeData, customUpdateFn) => {
|
||||
this.$emit(changeEventName, typeof customChangeData !== 'undefined' ? customChangeData : newVal);
|
||||
// 完全受控模式,使用了受控属性,必须配合change事件来更新
|
||||
if (controlled) {
|
||||
return;
|
||||
}
|
||||
if (typeof customUpdateFn === 'function') {
|
||||
customUpdateFn();
|
||||
} else {
|
||||
set(newVal);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export { useControl };
|
||||
99
uni_modules/tdesign-uniapp/components/common/src/flatTool.js
Normal file
99
uni_modules/tdesign-uniapp/components/common/src/flatTool.js
Normal file
@@ -0,0 +1,99 @@
|
||||
import { isObject } from '../validator';
|
||||
|
||||
/** ****************************************************************
|
||||
MIT License https://github.com/qiu8310/minapp/blob/v1.0.0-alpha.1/packages/minapp-core/src/system/util/object.ts
|
||||
Author Mora <qiuzhongleiabc@126.com> (https://github.com/qiu8310)
|
||||
****************************************************************** */
|
||||
|
||||
export const getPrototypeOf = function (obj) {
|
||||
return Object.getPrototypeOf ? Object.getPrototypeOf(obj) : obj.__proto__;
|
||||
};
|
||||
|
||||
/**
|
||||
* 遍历继承关系类的 prototype
|
||||
*
|
||||
* @export
|
||||
* @param {Function} callback - 回调函数,函数参数是遍历的每个实例的 prototype,函数如果返回 false,会终止遍历
|
||||
* @param {any} fromCtor - 要遍历的起始 class 或 prototype
|
||||
* @param {any} toCtor - 要遍历的结束 class 或 prototype
|
||||
* @param {boolean} [includeToCtor=true] - 是否要包含结束 toCtor 本身
|
||||
*
|
||||
* @example
|
||||
* A -> B -> C
|
||||
*
|
||||
* 在 C 中调用: iterateInheritedPrototype(fn, A, C, true)
|
||||
* 则,fn 会被调用三次,分别是 fn(A.prototype) fn(B.prototype) fn(C.prototype)
|
||||
*/
|
||||
export const iterateInheritedPrototype = function iterateInheritedPrototype(
|
||||
callback,
|
||||
fromCtor,
|
||||
toCtor,
|
||||
includeToCtor = true,
|
||||
) {
|
||||
let proto = fromCtor.prototype || fromCtor;
|
||||
const toProto = toCtor.prototype || toCtor;
|
||||
|
||||
while (proto) {
|
||||
if (!includeToCtor && proto === toProto) break;
|
||||
if (callback(proto) === false) break;
|
||||
if (proto === toProto) break;
|
||||
proto = getPrototypeOf(proto);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* 将一个可能包含原型链的对象扁平化成单个对象
|
||||
*
|
||||
* 如,现有这样的类的继承关系 A -> B -> C,当创建一个实例 a = new A() 时
|
||||
*
|
||||
* a 实例会存有 B、C 的原型链,使用此函数 newa = toObject(a) 之后,
|
||||
* newa 就会变成一个 PlainObject,但它有 A、B、C 上的所有属性和方法,
|
||||
* 当然不包括静态属性或方法
|
||||
*
|
||||
* 注意1:用此方法的话,尽量避免在类中使用胖函数,胖函数的 this 死死的绑定
|
||||
* 在原对象中,无法重新绑定
|
||||
*
|
||||
* 注意2:类继承的时候不要在函数中调用 super,toObject 之后是扁平的,没有 super 之说
|
||||
*/
|
||||
export const toObject = function toObject(
|
||||
something,
|
||||
options = {},
|
||||
) {
|
||||
const obj = {};
|
||||
if (!isObject(something)) return obj;
|
||||
|
||||
const excludes = options.excludes || ['constructor'];
|
||||
const { enumerable = true, configurable = 0, writable = 0 } = options;
|
||||
const defaultDesc = {};
|
||||
if (enumerable !== 0) defaultDesc.enumerable = enumerable;
|
||||
if (configurable !== 0) defaultDesc.configurable = configurable;
|
||||
if (writable !== 0) defaultDesc.writable = writable;
|
||||
|
||||
iterateInheritedPrototype(
|
||||
(proto) => {
|
||||
Object.getOwnPropertyNames(proto).forEach((key) => {
|
||||
if (excludes.indexOf(key) >= 0) return;
|
||||
if (Object.prototype.hasOwnProperty.call(obj, key)) return;
|
||||
const desc = Object.getOwnPropertyDescriptor(proto, key);
|
||||
|
||||
const fnKeys = ['get', 'set', 'value'];
|
||||
fnKeys.forEach((k) => {
|
||||
if (typeof desc[k] === 'function') {
|
||||
const oldFn = desc[k];
|
||||
desc[k] = function (...args) {
|
||||
return oldFn.apply(Object.prototype.hasOwnProperty.call(options, 'bindTo') ? options.bindTo : this, args);
|
||||
};
|
||||
}
|
||||
});
|
||||
Object.defineProperty(obj, key, { ...desc, ...defaultDesc });
|
||||
});
|
||||
},
|
||||
something,
|
||||
options.till || Object,
|
||||
false,
|
||||
);
|
||||
|
||||
return obj;
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from './superComponent';
|
||||
export * from './flatTool';
|
||||
export * from './instantiationDecorator';
|
||||
export * from './control';
|
||||
@@ -0,0 +1,251 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import { isPlainObject } from '../validator';
|
||||
import { canUseVirtualHost } from '../version';
|
||||
import { toCamel, toPascal, hyphenate } from '../utils';
|
||||
|
||||
const getInnerControlledValue = key => `data${key.replace(/^(\w)/, (e, t) => t.toUpperCase())}`;
|
||||
const getDefaultKey = key => `default${key.replace(/^(\w)/, (e, t) => t.toUpperCase())}`;
|
||||
|
||||
const ARIAL_PROPS = [
|
||||
{ key: 'ariaHidden', type: Boolean },
|
||||
{ key: 'ariaRole', type: String },
|
||||
{ key: 'ariaLabel', type: String },
|
||||
{ key: 'ariaLabelledby', type: String },
|
||||
{ key: 'ariaDescribedby', type: String },
|
||||
{ key: 'ariaBusy', type: Boolean },
|
||||
];
|
||||
|
||||
const getPropsDefault = (type, disableBoolean = false) => {
|
||||
if (type === Boolean && !disableBoolean) {
|
||||
return false;
|
||||
}
|
||||
if (type === String) {
|
||||
return '';
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const COMMON_PROPS = {
|
||||
...ARIAL_PROPS.reduce((acc, item) => ({
|
||||
...acc,
|
||||
[item.key]: {
|
||||
type: item.type,
|
||||
default: getPropsDefault(item.type),
|
||||
},
|
||||
}), {}),
|
||||
|
||||
customStyle: { type: [String, Object], default: '' },
|
||||
};
|
||||
|
||||
|
||||
export const toComponent = function (options) {
|
||||
if (!options.properties && options.props) {
|
||||
options.properties = options.props;
|
||||
}
|
||||
|
||||
if (options.properties) {
|
||||
Object.keys(options.properties).forEach((k) => {
|
||||
let opt = options.properties[k];
|
||||
// 如果不是 Object 类型,则默认指定 type = options.properties[k];
|
||||
if (!isPlainObject(opt)) {
|
||||
opt = { type: opt };
|
||||
}
|
||||
options.properties[k] = opt;
|
||||
});
|
||||
}
|
||||
|
||||
if (!options.methods) options.methods = {};
|
||||
|
||||
if (!options.lifetimes) options.lifetimes = {};
|
||||
|
||||
const oldCreated = options.created;
|
||||
const { controlledProps = [] } = options;
|
||||
|
||||
options.created = function (...args) {
|
||||
if (oldCreated) {
|
||||
oldCreated.apply(this, args);
|
||||
}
|
||||
controlledProps.forEach(({ key }) => {
|
||||
const defaultKey = getDefaultKey(key);
|
||||
const tDataKey = getInnerControlledValue(key);
|
||||
this[tDataKey] = this[key];
|
||||
|
||||
if (this[key] == null) {
|
||||
this._selfControlled = true;
|
||||
}
|
||||
|
||||
if (this[key] == null && this[defaultKey] != null) {
|
||||
this[tDataKey] = this[defaultKey];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
options.methods._trigger = function (evtName, detail, opts) {
|
||||
const target = controlledProps.find(item => item.event === evtName);
|
||||
|
||||
if (target) {
|
||||
const { key } = target;
|
||||
if (this._selfControlled) {
|
||||
const tDataKey = getInnerControlledValue(key);
|
||||
this[tDataKey] = detail[key];
|
||||
}
|
||||
this.$emit(
|
||||
`update:${key}`,
|
||||
detail[key],
|
||||
opts,
|
||||
);
|
||||
}
|
||||
|
||||
this.$emit(
|
||||
evtName,
|
||||
detail,
|
||||
opts,
|
||||
);
|
||||
};
|
||||
return options;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 将一个继承了 BaseComponent 的类转化成 小程序 Component 的调用
|
||||
* 根据最新的微信 d.ts 描述文件,Component 在实例化的时候,会忽略不支持的自定义属性
|
||||
*/
|
||||
export const wxComponent = function wxComponent() {
|
||||
return function (baseConstructor) {
|
||||
class WxComponent extends baseConstructor {
|
||||
// 暂时移除了冗余的代码,后续补充
|
||||
}
|
||||
|
||||
const current = new WxComponent();
|
||||
|
||||
current.options = current.options || {};
|
||||
|
||||
// 所有组件默认都开启css作用域
|
||||
// 写到这里是为了防止组件设置 options 时无意覆盖掉了 addGlobalClass
|
||||
// 使用 "styleIsolation": "apply-shared" 代替 addGlobalClass: true,see https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/glass-easel/migration.html#JSON-%E9%85%8D%E7%BD%AE
|
||||
// if (current.options.addGlobalClass === undefined) {
|
||||
// current.options.addGlobalClass = true;
|
||||
// }
|
||||
|
||||
if (canUseVirtualHost() && current.options.virtualHost == null) {
|
||||
current.options.virtualHost = true;
|
||||
}
|
||||
|
||||
const obj = toComponent(current);
|
||||
|
||||
return obj;
|
||||
};
|
||||
};
|
||||
|
||||
function sortPropsType(type) {
|
||||
if (!Array.isArray(type)) {
|
||||
return type;
|
||||
}
|
||||
type.sort((a, b) => {
|
||||
if (a === Boolean) {
|
||||
return -1;
|
||||
}
|
||||
if (b === Boolean) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
return type;
|
||||
}
|
||||
|
||||
function filterProps(props, controlledProps) {
|
||||
const newProps = {};
|
||||
const emits = [];
|
||||
const reg = /^on[A-Z][a-z]/;
|
||||
const controlledKeys = Object.values(controlledProps).map(item => item.key);
|
||||
const unControlledKeys = controlledKeys.map(key => getDefaultKey(key));
|
||||
|
||||
Object.keys(props).forEach((key) => {
|
||||
const curType = props[key].type || props[key];
|
||||
|
||||
if (reg.test(key) && props[key].type === Function) {
|
||||
const str = key.replace(/^on/, '');
|
||||
const eventName = str.charAt(0).toLowerCase() + str.slice(1);
|
||||
emits.push(...[hyphenate(eventName), eventName]);
|
||||
} else if (controlledKeys.indexOf(key) > -1
|
||||
|| unControlledKeys.indexOf(key) > -1
|
||||
) {
|
||||
const newType = Array.isArray(curType) ? curType : [curType];
|
||||
newProps[key] = {
|
||||
type: [null, ...newType],
|
||||
default: null,
|
||||
};
|
||||
} else if (
|
||||
[Boolean, String].indexOf(props[key].type) > -1
|
||||
&& props[key].default === undefined
|
||||
) {
|
||||
newProps[key] = {
|
||||
...props[key],
|
||||
default: getPropsDefault(props[key].type, true),
|
||||
};
|
||||
} else {
|
||||
newProps[key] = {
|
||||
...(typeof props[key] === 'object' ? props[key] : {}),
|
||||
type: sortPropsType(curType),
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
newProps,
|
||||
emits,
|
||||
};
|
||||
}
|
||||
|
||||
const getEmitsByControlledProps = controlledProps => Object.values(controlledProps).map(item => `update:${item.key}`);
|
||||
|
||||
export const uniComponent = function (info) {
|
||||
const { newProps, emits } = filterProps(info.props || {}, info.controlledProps || {});
|
||||
info.props = {
|
||||
...getExternalClasses(info),
|
||||
...newProps,
|
||||
...COMMON_PROPS,
|
||||
};
|
||||
info.emits = Array.from(new Set([
|
||||
...(info.emits || []),
|
||||
|
||||
...(getEmitsByControlledProps(info.controlledProps || {})),
|
||||
...emits,
|
||||
]));
|
||||
|
||||
info.options = {
|
||||
...(info.options || {}),
|
||||
multipleSlots: true,
|
||||
};
|
||||
|
||||
if (canUseVirtualHost() && info.options.virtualHost == null) {
|
||||
info.options.virtualHost = true;
|
||||
}
|
||||
|
||||
if (!info.options.styleIsolation) {
|
||||
info.options.styleIsolation = 'shared';
|
||||
}
|
||||
if (info.name) {
|
||||
info.name = toPascal(info.name);
|
||||
}
|
||||
|
||||
const obj = toComponent(info);
|
||||
return obj;
|
||||
};
|
||||
|
||||
|
||||
export function getExternalClasses(info) {
|
||||
if (!info.externalClasses) {
|
||||
return {};
|
||||
}
|
||||
const { externalClasses } = info;
|
||||
const list = Array.isArray(externalClasses) ? externalClasses : [externalClasses];
|
||||
|
||||
return list.reduce((acc, item) => ({
|
||||
...acc,
|
||||
[toCamel(item)]: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
}), {});
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export class SuperComponent {
|
||||
constructor() {
|
||||
this.app = getApp();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user