chore: lint fixed

This commit is contained in:
1ncounter 2024-04-10 17:32:38 +08:00
parent 6b8d0c13bc
commit 764e841336
207 changed files with 2699 additions and 659 deletions

1
.npmrc Normal file
View File

@ -0,0 +1 @@
git-checks=false

View File

@ -1,23 +1,18 @@
import stylistic from '@stylistic/eslint-plugin'; import stylistic from '@stylistic/eslint-plugin';
import tseslint from 'typescript-eslint' import tseslint from 'typescript-eslint';
import js from '@eslint/js'; import js from '@eslint/js';
import react from 'eslint-plugin-react' import react from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks'; import reactHooks from 'eslint-plugin-react-hooks';
import reactRefresh from 'eslint-plugin-react-refresh'; import globals from 'globals';
import globals from 'globals'
export default tseslint.config({ export default tseslint.config({
files: ['packages/*/src/**/*.{ts?(x),js?(x)}'], files: ['packages/*/{src,__tests__}/**/*.{ts?(x),js?(x)}'],
ignores: ["**/*.test.ts"], ignores: ['**/*.test.ts'],
extends: [ extends: [js.configs.recommended, ...tseslint.configs.recommended],
js.configs.recommended,
...tseslint.configs.recommended,
],
plugins: { plugins: {
'@stylistic': stylistic, '@stylistic': stylistic,
react, react,
'react-hooks': reactHooks, 'react-hooks': reactHooks,
'react-refresh': reactRefresh
}, },
languageOptions: { languageOptions: {
parserOptions: { parserOptions: {
@ -28,18 +23,22 @@ export default tseslint.config({
globals: { globals: {
...globals.browser, ...globals.browser,
...globals.nodeBuiltin, ...globals.nodeBuiltin,
...globals.jest ...globals.jest,
}, },
}, },
rules: { rules: {
'@stylistic/indent': ['error', 2], '@stylistic/indent': ['error', 2],
'@stylistic/indent-binary-ops': ['error', 2], '@stylistic/indent-binary-ops': ['error', 2],
'@stylistic/max-len': ['error', { tabWidth: 2, "ignoreStrings": true }], '@stylistic/max-len': ['error', { code: 100, tabWidth: 2, ignoreStrings: true, ignoreComments: true }],
'@stylistic/no-tabs': 'error', '@stylistic/no-tabs': 'error',
'@stylistic/quotes': ['error', 'single'], '@stylistic/quotes': ['error', 'single'],
'@stylistic/jsx-pascal-case': [2], '@stylistic/jsx-pascal-case': [2],
'@stylistic/jsx-indent': [2, 2, { checkAttributes: true, indentLogicalExpressions: true }], '@stylistic/jsx-indent': [2, 2, { checkAttributes: true, indentLogicalExpressions: true }],
'@stylistic/semi': ['error', 'always'], '@stylistic/semi': ['error', 'always'],
'@stylistic/eol-last': ['error', 'always'],
'@stylistic/jsx-quotes': ['error', 'prefer-double'],
"@typescript-eslint/ban-ts-comment": ["error", { 'ts-expect-error': 'allow-with-description' }],
'react/jsx-no-undef': 'error', 'react/jsx-no-undef': 'error',
'react/jsx-uses-vars': 'error', 'react/jsx-uses-vars': 'error',
@ -50,7 +49,5 @@ export default tseslint.config({
'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks 'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks
'react-hooks/exhaustive-deps': 'warn', // Checks effect dependencies 'react-hooks/exhaustive-deps': 'warn', // Checks effect dependencies
'react-refresh/only-export-components': 'warn',
}, },
}) });

View File

@ -4,8 +4,8 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"playground": "pnpm --filter playground dev", "playground": "pnpm --filter playground dev",
"test": "pnpm -r test",
"build": "node ./scripts/build.js", "build": "node ./scripts/build.js",
"test": "vitest",
"clean": "rimraf ./packages/*/dist", "clean": "rimraf ./packages/*/dist",
"clean:lib": "rimraf ./node_modules ./packages/*/node_modules", "clean:lib": "rimraf ./node_modules ./packages/*/node_modules",
"lint": "eslint . --cache", "lint": "eslint . --cache",
@ -35,7 +35,6 @@
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-plugin-react": "^7.34.1", "eslint-plugin-react": "^7.34.1",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6",
"globals": "^15.0.0", "globals": "^15.0.0",
"husky": "^9.0.11", "husky": "^9.0.11",
"less": "^4.2.0", "less": "^4.2.0",

View File

@ -11,6 +11,10 @@
"import": "./dist/low-code-designer.js", "import": "./dist/low-code-designer.js",
"require": "./dist/low-code-designer.cjs", "require": "./dist/low-code-designer.cjs",
"types": "./dist/index.d.ts" "types": "./dist/index.d.ts"
},
"./dist/": {
"import": "./dist/",
"require": "./dist/"
} }
}, },
"files": [ "files": [

View File

@ -50,8 +50,8 @@ function getTitle(title: string | IPublicTypeI18nData | ReactElement) {
export class BorderContainer extends Component<{ export class BorderContainer extends Component<{
host: BuiltinSimulatorHost; host: BuiltinSimulatorHost;
}, { }, {
target?: INode; target?: INode;
}> { }> {
state = {} as any; state = {} as any;
@computed get scale() { @computed get scale() {
@ -70,7 +70,7 @@ export class BorderContainer extends Component<{
const { host } = this.props; const { host } = this.props;
host.designer.editor.eventBus.on('designer.dropLocation.change', (loc: DropLocation) => { host.designer.editor.eventBus.on('designer.dropLocation.change', (loc: DropLocation) => {
let { target } = this.state; const { target } = this.state;
if (target === loc?.target) return; if (target === loc?.target) return;
this.setState({ this.setState({
target: loc?.target, target: loc?.target,

View File

@ -1225,9 +1225,9 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
const childrenCanMove = const childrenCanMove =
onChildMoveHook && parentContainerNode && typeof onChildMoveHook === 'function' onChildMoveHook && parentContainerNode && typeof onChildMoveHook === 'function'
? onChildMoveHook( ? onChildMoveHook(
node!.internalToShellNode(), node!.internalToShellNode(),
(parentContainerNode as any).internalToShellNode(), (parentContainerNode as any).internalToShellNode(),
) )
: true; : true;
return canMove && childrenCanMove; return canMove && childrenCanMove;
@ -1313,9 +1313,9 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
const inst = instances const inst = instances
? instances.length > 1 ? instances.length > 1
? instances.find( ? instances.find(
(_inst) => (_inst) =>
this.getClosestNodeInstance(_inst, container.id)?.instance === containerInstance, this.getClosestNodeInstance(_inst, container.id)?.instance === containerInstance,
) )
: instances[0] : instances[0]
: null; : null;
const rect = inst const rect = inst

View File

@ -197,8 +197,8 @@ export class LiveEditing {
export type SpecificRule = (target: EditingTarget) => export type SpecificRule = (target: EditingTarget) =>
| (IPublicTypeLiveTextEditingConfig & { | (IPublicTypeLiveTextEditingConfig & {
propElement?: HTMLElement; propElement?: HTMLElement;
}) })
| null; | null;
export interface SaveHandler { export interface SaveHandler {

View File

@ -81,19 +81,19 @@ export default class InstanceNodeSelector extends React.Component<IProps, IState
onMouseOver = onMouseOver =
(node: INode) => (node: INode) =>
(_: any, flag = true) => { (_: any, flag = true) => {
if (node && typeof node.hover === 'function') { if (node && typeof node.hover === 'function') {
node.hover(flag); node.hover(flag);
} }
}; };
onMouseOut = onMouseOut =
(node: INode) => (node: INode) =>
(_: any, flag = false) => { (_: any, flag = false) => {
if (node && typeof node.hover === 'function') { if (node && typeof node.hover === 'function') {
node.hover(flag); node.hover(flag);
} }
}; };
renderNodes = () => { renderNodes = () => {
const nodes = this.state.parentNodes; const nodes = this.state.parentNodes;

View File

@ -226,10 +226,10 @@ export class ComponentMeta implements IComponentMeta {
this._title = this._title =
typeof title === 'string' typeof title === 'string'
? { ? {
type: 'i18n', type: 'i18n',
'en-US': this.componentName, 'en-US': this.componentName,
'zh-CN': title, 'zh-CN': title,
} }
: title; : title;
} }

View File

@ -45,7 +45,7 @@ export class GlobalContextMenuActions {
event.preventDefault(); event.preventDefault();
const actions: IPublicTypeContextMenuAction[] = []; const actions: IPublicTypeContextMenuAction[] = [];
let contextMenu: ContextMenuActions = this.contextMenuActionsMap.values().next().value; const contextMenu: ContextMenuActions = this.contextMenuActionsMap.values().next().value;
this.contextMenuActionsMap.forEach((contextMenu) => { this.contextMenuActionsMap.forEach((contextMenu) => {
actions.push(...contextMenu.actions); actions.push(...contextMenu.actions);
}); });

View File

@ -55,9 +55,9 @@ export interface DesignerProps {
onDragstart?: (e: IPublicModelLocateEvent) => void; onDragstart?: (e: IPublicModelLocateEvent) => void;
onDrag?: (e: IPublicModelLocateEvent) => void; onDrag?: (e: IPublicModelLocateEvent) => void;
onDragend?: ( onDragend?: (
e: { dragObject: IPublicModelDragObject; copy: boolean }, e: { dragObject: IPublicModelDragObject; copy: boolean },
loc?: DropLocation, loc?: DropLocation,
) => void; ) => void;
} }
export class Designer { export class Designer {
@ -406,8 +406,8 @@ export class Designer {
if (components) { if (components) {
// 合并 assets // 合并 assets
let assets = this.editor.get('assets') || {}; const assets = this.editor.get('assets') || {};
let newAssets = mergeAssets(assets, incrementalAssets); const newAssets = mergeAssets(assets, incrementalAssets);
// 对于 assets 存在需要二次网络下载的过程,必须 await 等待结束之后,再进行事件触发 // 对于 assets 存在需要二次网络下载的过程,必须 await 等待结束之后,再进行事件触发
await this.editor.set('assets', newAssets); await this.editor.set('assets', newAssets);
} }

View File

@ -37,10 +37,10 @@ function getSettingFieldCollectorKey(
// @ts-ignore // @ts-ignore
export interface ISettingField export interface ISettingField
extends ISettingPropEntry, extends ISettingPropEntry,
Omit< Omit<
IBaseModelSettingField<ISettingTopEntry, ISettingField, IComponentMeta, INode>, IBaseModelSettingField<ISettingTopEntry, ISettingField, IComponentMeta, INode>,
'setValue' | 'key' | 'node' 'setValue' | 'key' | 'node'
> { > {
readonly isSettingField: true; readonly isSettingField: true;
readonly isRequired: boolean; readonly isRequired: boolean;

View File

@ -21,7 +21,7 @@ function generateSessionId(nodes: INode[]) {
export interface ISettingTopEntry export interface ISettingTopEntry
extends ISettingEntry, extends ISettingEntry,
IPublicModelSettingTopEntry<INode, ISettingField> { IPublicModelSettingTopEntry<INode, ISettingField> {
readonly top: ISettingTopEntry; readonly top: ISettingTopEntry;
readonly parent: ISettingTopEntry; readonly parent: ISettingTopEntry;

View File

@ -11,9 +11,9 @@ function getHotterFromSetter(setter: any) {
function getTransducerFromSetter(setter: any) { function getTransducerFromSetter(setter: any) {
return ( return (
(setter && (setter &&
(setter.transducer || (setter.transducer ||
setter.Transducer || setter.Transducer ||
(setter.type && (setter.type.transducer || setter.type.Transducer)))) || (setter.type && (setter.type.transducer || setter.type.Transducer)))) ||
null null
); // eslint-disable-line ); // eslint-disable-line
} }

View File

@ -46,14 +46,14 @@ import { EDITOR_EVENT } from '../types';
export type GetDataType<T, NodeType> = T extends undefined export type GetDataType<T, NodeType> = T extends undefined
? NodeType extends { ? NodeType extends {
schema: infer R; schema: infer R;
} }
? R ? R
: any : any
: T; : T;
export class DocumentModel export class DocumentModel
implements implements
Omit< Omit<
IPublicModelDocumentModel< IPublicModelDocumentModel<
ISelection, ISelection,

View File

@ -41,10 +41,10 @@ export class History<T = IPublicTypeNodeSchema> implements IHistory {
private timeGap: number = 1000; private timeGap: number = 1000;
constructor( constructor(
dataFn: () => T | null, dataFn: () => T | null,
private redoer: (data: T) => void, private redoer: (data: T) => void,
private document?: IDocumentModel, private document?: IDocumentModel,
) { ) {
this.session = new Session(0, null, this.timeGap); this.session = new Session(0, null, this.timeGap);
this.records = [this.session]; this.records = [this.session];

View File

@ -11,10 +11,10 @@ export interface IOnChangeOptions {
} }
export class NodeChildren implements Omit<IPublicModelNodeChildren<INode>, export class NodeChildren implements Omit<IPublicModelNodeChildren<INode>,
'importSchema' | 'importSchema' |
'exportSchema' | 'exportSchema' |
'isEmpty' | 'isEmpty' |
'notEmpty' 'notEmpty'
> { > {
@obx.shallow children: INode[]; @obx.shallow children: INode[];
@ -46,9 +46,9 @@ export class NodeChildren implements Omit<IPublicModelNodeChildren<INode>,
} }
constructor( constructor(
readonly owner: INode, readonly owner: INode,
data: IPublicTypeNodeData | IPublicTypeNodeData[], data: IPublicTypeNodeData | IPublicTypeNodeData[],
) { ) {
makeObservable(this); makeObservable(this);
this.children = (Array.isArray(data) ? data : [data]).filter(child => !!child).map((child) => { this.children = (Array.isArray(data) ? data : [data]).filter(child => !!child).map((child) => {
return this.owner.document?.createNode(child); return this.owner.document?.createNode(child);

View File

@ -97,7 +97,7 @@ export interface IBaseNode extends Node {}
* hidden * hidden
*/ */
export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema> export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
implements implements
Omit< Omit<
IBaseModelNode< IBaseModelNode<
IDocumentModel, IDocumentModel,
@ -191,7 +191,7 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
} }
@computed get title(): string | IPublicTypeI18nData | ReactElement { @computed get title(): string | IPublicTypeI18nData | ReactElement {
let t = this.getExtraProp('title'); const t = this.getExtraProp('title');
// TODO: 暂时走不到这个分支 // TODO: 暂时走不到这个分支
// if (!t && this.componentMeta.descriptor) { // if (!t && this.componentMeta.descriptor) {
// t = this.getProp(this.componentMeta.descriptor, false); // t = this.getProp(this.componentMeta.descriptor, false);
@ -325,17 +325,17 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
@action @action
private initBuiltinProps() { private initBuiltinProps() {
this.props.has(getConvertedExtraKey('hidden')) || this.props.has(getConvertedExtraKey('hidden')) ||
this.props.add(false, getConvertedExtraKey('hidden')); this.props.add(false, getConvertedExtraKey('hidden'));
this.props.has(getConvertedExtraKey('title')) || this.props.has(getConvertedExtraKey('title')) ||
this.props.add('', getConvertedExtraKey('title')); this.props.add('', getConvertedExtraKey('title'));
this.props.has(getConvertedExtraKey('isLocked')) || this.props.has(getConvertedExtraKey('isLocked')) ||
this.props.add(false, getConvertedExtraKey('isLocked')); this.props.add(false, getConvertedExtraKey('isLocked'));
this.props.has(getConvertedExtraKey('condition')) || this.props.has(getConvertedExtraKey('condition')) ||
this.props.add(true, getConvertedExtraKey('condition')); this.props.add(true, getConvertedExtraKey('condition'));
this.props.has(getConvertedExtraKey('conditionGroup')) || this.props.has(getConvertedExtraKey('conditionGroup')) ||
this.props.add('', getConvertedExtraKey('conditionGroup')); this.props.add('', getConvertedExtraKey('conditionGroup'));
this.props.has(getConvertedExtraKey('loop')) || this.props.has(getConvertedExtraKey('loop')) ||
this.props.add(undefined, getConvertedExtraKey('loop')); this.props.add(undefined, getConvertedExtraKey('loop'));
} }
@action @action
@ -1164,7 +1164,7 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
const isRGLContainerNode = this.isRGLContainer; const isRGLContainerNode = this.isRGLContainer;
const isRGLNode = this.getParent()?.isRGLContainer as boolean; const isRGLNode = this.getParent()?.isRGLContainer as boolean;
const isRGL = isRGLContainerNode || (isRGLNode && (!isContainerNode || !isEmptyNode)); const isRGL = isRGLContainerNode || (isRGLNode && (!isContainerNode || !isEmptyNode));
let rglNode = isRGLContainerNode ? this : isRGL ? this?.getParent() : null; const rglNode = isRGLContainerNode ? this : isRGL ? this?.getParent() : null;
return { isContainerNode, isEmptyNode, isRGLContainerNode, isRGLNode, isRGL, rglNode }; return { isContainerNode, isEmptyNode, isRGLContainerNode, isRGLNode, isRGL, rglNode };
} }

View File

@ -646,7 +646,7 @@ export class Prop implements IProp, IPropParent {
} }
} }
const prop = isProp(value) ? value : new Prop(this, value, key); const prop = isProp(value) ? value : new Prop(this, value, key);
let items = this._items! || []; const items = this._items! || [];
if (this.type === 'list') { if (this.type === 'list') {
if (!isValidArrayIndex(key)) { if (!isValidArrayIndex(key)) {
return null; return null;

View File

@ -138,13 +138,13 @@ export class Props implements Omit<IBaseModelProps<IProp>, | 'getExtraProp' | 'g
if (this.items.length < 1) { if (this.items.length < 1) {
return {}; return {};
} }
let allProps = {} as any; const allProps = {} as any;
let props: any = {}; let props: any = {};
const extras: any = {}; const extras: any = {};
if (this.type === 'list') { if (this.type === 'list') {
props = []; props = [];
this.items.forEach((item) => { this.items.forEach((item) => {
let value = item.export(stage); const value = item.export(stage);
let name = item.key as string; let name = item.key as string;
if (name && typeof name === 'string' && name.startsWith(EXTRA_KEY_PREFIX)) { if (name && typeof name === 'string' && name.startsWith(EXTRA_KEY_PREFIX)) {
name = getOriginalExtraKey(name); name = getOriginalExtraKey(name);
@ -159,9 +159,9 @@ export class Props implements Omit<IBaseModelProps<IProp>, | 'getExtraProp' | 'g
}); });
} else { } else {
this.items.forEach((item) => { this.items.forEach((item) => {
let name = item.key as string; const name = item.key as string;
if (name == null || item.isUnset() || item.isVirtual()) return; if (name == null || item.isUnset() || item.isVirtual()) return;
let value = item.export(stage); const value = item.export(stage);
if (value != null) { if (value != null) {
allProps[name] = value; allProps[name] = value;
} }

View File

@ -14,7 +14,7 @@ function propertyNameRequiresQuotes(propertyName: string) {
} }
function quoteString(str: string, { doubleQuote }: any) { function quoteString(str: string, { doubleQuote }: any) {
return doubleQuote ? `"${str.replace(/"/gu, '\\"')}"` : `'${str.replace(/'/gu, "\\'")}'`; return doubleQuote ? `"${str.replace(/"/gu, '\\"')}"` : `'${str.replace(/'/gu, '\\\'')}'`;
} }
export function valueToSource( export function valueToSource(
@ -96,12 +96,12 @@ export function valueToSource(
const itemsStayOnTheSameLine = value.every( const itemsStayOnTheSameLine = value.every(
item => typeof item === 'object' && item => typeof item === 'object' &&
item && item &&
!(item instanceof Date) && !(item instanceof Date) &&
!(item instanceof Map) && !(item instanceof Map) &&
!(item instanceof RegExp) && !(item instanceof RegExp) &&
!(item instanceof Set) && !(item instanceof Set) &&
(Object.keys(item).length || value.length === 1), (Object.keys(item).length || value.length === 1),
); );
let previousIndex: number | null = null; let previousIndex: number | null = null;

View File

@ -52,9 +52,9 @@ export default class PluginContext implements
command: IPublicApiCommand; command: IPublicApiCommand;
constructor( constructor(
options: IPluginContextOptions, options: IPluginContextOptions,
contextApiAssembler: ILowCodePluginContextApiAssembler, contextApiAssembler: ILowCodePluginContextApiAssembler,
) { ) {
const { pluginName = 'anonymous', meta = {} } = options; const { pluginName = 'anonymous', meta = {} } = options;
contextApiAssembler.assembleApis(this, pluginName, meta); contextApiAssembler.assembleApis(this, pluginName, meta);
this.pluginEvent = createModuleEventBus(pluginName, 200); this.pluginEvent = createModuleEventBus(pluginName, 200);
@ -71,7 +71,7 @@ export default class PluginContext implements
const getPreferenceValue = ( const getPreferenceValue = (
key: string, key: string,
defaultValue?: IPublicTypePreferenceValueType, defaultValue?: IPublicTypePreferenceValueType,
): IPublicTypePreferenceValueType | undefined => { ): IPublicTypePreferenceValueType | undefined => {
if (!isValidPreferenceKey(key, preferenceDeclaration)) { if (!isValidPreferenceKey(key, preferenceDeclaration)) {
return undefined; return undefined;
} }

View File

@ -16,24 +16,24 @@ import { ISimulatorHost } from '../simulator';
export interface IProject extends Omit<IBaseApiProject< export interface IProject extends Omit<IBaseApiProject<
IDocumentModel IDocumentModel
>, >,
'simulatorHost' | 'simulatorHost' |
'importSchema' | 'importSchema' |
'exportSchema' | 'exportSchema' |
'openDocument' | 'openDocument' |
'getDocumentById' | 'getDocumentById' |
'getCurrentDocument' | 'getCurrentDocument' |
'addPropsTransducer' | 'addPropsTransducer' |
'onRemoveDocument' | 'onRemoveDocument' |
'onChangeDocument' | 'onChangeDocument' |
'onSimulatorHostReady' | 'onSimulatorHostReady' |
'onSimulatorRendererReady' | 'onSimulatorRendererReady' |
'setI18n' | 'setI18n' |
'setConfig' | 'setConfig' |
'currentDocument' | 'currentDocument' |
'selection' | 'selection' |
'documents' | 'documents' |
'createDocument' | 'createDocument' |
'getDocumentByFileName' 'getDocumentByFileName'
> { > {
get designer(): IDesigner; get designer(): IDesigner;

View File

@ -34,7 +34,7 @@ export function normalizeTriggers(triggers: string[]) {
/** /**
* make a handler that listen all sensors:document, avoid frame lost * make a handler that listen all sensors:document, avoid frame lost
*/ */
export function makeEventsHandler( export function makeEventsHandler(
boostEvent: MouseEvent | DragEvent, boostEvent: MouseEvent | DragEvent,
sensors: ISimulatorHost[], sensors: ISimulatorHost[],
): (fn: (sdoc: Document) => void) => void { ): (fn: (sdoc: Document) => void) => void {

View File

@ -11,6 +11,10 @@
".": { ".": {
"import": "./dist/low-code-editor-core.js", "import": "./dist/low-code-editor-core.js",
"types": "./dist/index.d.ts" "types": "./dist/index.d.ts"
},
"./dist/": {
"import": "./dist/",
"require": "./dist/"
} }
}, },
"sideEffects": [ "sideEffects": [

View File

@ -202,9 +202,9 @@ export class Editor extends EventEmitter implements IEditor {
Array.isArray(d) Array.isArray(d)
? setArrayAssets(d, exportName, subName) ? setArrayAssets(d, exportName, subName)
: setAssetsComponent(d, { : setAssetsComponent(d, {
exportName, exportName,
subName, subName,
}); });
}); });
} }
if ((window as any)[exportName]) { if ((window as any)[exportName]) {

View File

@ -75,7 +75,7 @@ const KEYCODE_MAP: KeyMap = {
219: '[', 219: '[',
220: '\\', 220: '\\',
221: ']', 221: ']',
222: "'", 222: '\'',
}; };
const SHIFT_MAP: CtrlKeyMap = { const SHIFT_MAP: CtrlKeyMap = {
@ -93,7 +93,7 @@ const SHIFT_MAP: CtrlKeyMap = {
_: '-', _: '-',
'+': '=', '+': '=',
':': ';', ':': ';',
'"': "'", '"': '\'',
'<': ',', '<': ',',
'>': '.', '>': '.',
'?': '/', '?': '/',

View File

@ -61,7 +61,7 @@ class GlobalLocale {
} }
if (!result) { if (!result) {
// store 2: config from window // store 2: config from window
let localeFromConfig: string = getConfig('locale'); const localeFromConfig: string = getConfig('locale');
if (localeFromConfig) { if (localeFromConfig) {
result = languageMap[localeFromConfig] || localeFromConfig.replace('_', '-'); result = languageMap[localeFromConfig] || localeFromConfig.replace('_', '-');
logger.debug(`getting locale from config: ${result}`); logger.debug(`getting locale from config: ${result}`);
@ -147,6 +147,6 @@ function hasLocalStorage(obj: any): obj is WindowLocalStorage {
return obj.localStorage; return obj.localStorage;
} }
let globalLocale = new GlobalLocale(); const globalLocale = new GlobalLocale();
export { globalLocale }; export { globalLocale };

View File

@ -59,7 +59,7 @@ export class Title extends Component<IPublicTypeTitleProps> {
} }
renderLabel = (label: string | IPublicTypeI18nData | ReactNode) => { renderLabel = (label: string | IPublicTypeI18nData | ReactNode) => {
let { match, keywords } = this.props; const { match, keywords } = this.props;
if (!label) { if (!label) {
return null; return null;

View File

@ -0,0 +1,5 @@
import { defineProject } from 'vitest/config'
export default defineProject({
test: {}
})

View File

@ -11,6 +11,10 @@
"import": "./dist/low-code-editor-skeleton.js", "import": "./dist/low-code-editor-skeleton.js",
"require": "./dist/low-code-editor-skeleton.cjs", "require": "./dist/low-code-editor-skeleton.cjs",
"types": "./dist/index.d.ts" "types": "./dist/index.d.ts"
},
"./dist/": {
"import": "./dist/",
"require": "./dist/"
} }
}, },
"sideEffects": [ "sideEffects": [

View File

@ -139,8 +139,8 @@ class SettingFieldView extends Component<SettingFieldViewProps, SettingFieldView
const { setter } = this.field; const { setter } = this.field;
let setterProps: let setterProps:
| ({ | ({
setters?: (ReactNode | string)[]; setters?: (ReactNode | string)[];
} & Record<string, unknown>) } & Record<string, unknown>)
| IPublicTypeDynamicProps = {}; | IPublicTypeDynamicProps = {};
let setterType: any; let setterType: any;
let initialValue: any = null; let initialValue: any = null;
@ -251,8 +251,8 @@ class SettingFieldView extends Component<SettingFieldViewProps, SettingFieldView
const value = this.value; const value = this.value;
let onChangeAPI = extraProps?.onChange; const onChangeAPI = extraProps?.onChange;
let stageName = this.stageName; const stageName = this.stageName;
return createField( return createField(
{ {
@ -269,47 +269,47 @@ class SettingFieldView extends Component<SettingFieldViewProps, SettingFieldView
...extraProps, ...extraProps,
}, },
!stageName && !stageName &&
this.setters?.createSetterContent(setterType, { this.setters?.createSetterContent(setterType, {
...shallowIntl(setterProps), ...shallowIntl(setterProps),
forceInline: extraProps.forceInline, forceInline: extraProps.forceInline,
key: field.id, key: field.id,
// === injection // === injection
prop: field.internalToShellField(), // for compatible vision prop: field.internalToShellField(), // for compatible vision
selected: field.top?.getNode()?.internalToShellNode(), selected: field.top?.getNode()?.internalToShellNode(),
field: field.internalToShellField(), field: field.internalToShellField(),
// === IO // === IO
value, // reaction point value, // reaction point
initialValue, initialValue,
onChange: (value: any) => { onChange: (value: any) => {
this.setState({ this.setState({
fromOnChange: true, fromOnChange: true,
// eslint-disable-next-line react/no-unused-state // eslint-disable-next-line react/no-unused-state
value, value,
}); });
field.setValue(value, true); field.setValue(value, true);
if (onChangeAPI) onChangeAPI(value, field.internalToShellField()); if (onChangeAPI) onChangeAPI(value, field.internalToShellField());
}, },
onInitial: () => { onInitial: () => {
if (initialValue == null) { if (initialValue == null) {
return; return;
} }
const value = const value =
typeof initialValue === 'function' typeof initialValue === 'function'
? initialValue(field.internalToShellField()) ? initialValue(field.internalToShellField())
: initialValue; : initialValue;
this.setState({ this.setState({
// eslint-disable-next-line react/no-unused-state // eslint-disable-next-line react/no-unused-state
value, value,
}); });
field.setValue(value, true); field.setValue(value, true);
}, },
removeProp: () => { removeProp: () => {
if (field.name) { if (field.name) {
field.parent.clearPropValue(field.name); field.parent.clearPropValue(field.name);
} }
}, },
}), }),
extraProps.forceInline ? 'plain' : extraProps.display, extraProps.forceInline ? 'plain' : extraProps.display,
); );
} }

View File

@ -104,29 +104,29 @@ export class SettingsPrimaryPane extends Component<
l === 2 l === 2
? {} ? {}
: { : {
onMouseOver: hoverNode.bind(null, _node, true), onMouseOver: hoverNode.bind(null, _node, true),
onMouseOut: hoverNode.bind(null, _node, false), onMouseOut: hoverNode.bind(null, _node, false),
onClick: () => { onClick: () => {
if (!_node) { if (!_node) {
return; return;
} }
selectNode.call(null, _node); selectNode.call(null, _node);
const getName = (node: any) => { const getName = (node: any) => {
const npm = node?.componentMeta?.npm; const npm = node?.componentMeta?.npm;
return ( return (
[npm?.package, npm?.componentName].filter((item) => !!item).join('-') || [npm?.package, npm?.componentName].filter((item) => !!item).join('-') ||
node?.componentMeta?.componentName || node?.componentMeta?.componentName ||
'' ''
); );
}; };
const selected = getName(current); const selected = getName(current);
const target = getName(_node); const target = getName(_node);
editor?.eventBus.emit('skeleton.settingsPane.Breadcrumb', { editor?.eventBus.emit('skeleton.settingsPane.Breadcrumb', {
selected, selected,
target, target,
}); });
}, },
}; };
items.unshift( items.unshift(
<Breadcrumb.Item {...props} key={node.id}> <Breadcrumb.Item {...props} key={node.id}>
<Title title={node.title} /> <Title title={node.title} />

View File

@ -5,9 +5,9 @@ function getHotterFromSetter(setter: any) {
function getTransducerFromSetter(setter: any) { function getTransducerFromSetter(setter: any) {
return ( return (
(setter && (setter &&
(setter.transducer || (setter.transducer ||
setter.Transducer || setter.Transducer ||
(setter.type && (setter.type.transducer || setter.type.Transducer)))) || (setter.type && (setter.type.transducer || setter.type.Transducer)))) ||
null null
); // eslint-disable-line ); // eslint-disable-line
} }

View File

@ -52,7 +52,7 @@ class Contents extends Component<{ area: Area; itemClassName?: string }> {
right.push(content); right.push(content);
} }
}); });
let children = []; const children = [];
if (left && left.length) { if (left && left.length) {
children.push( children.push(
<div className="lc-workspace-sub-top-area-left lc-sub-top-area-left">{left}</div>, <div className="lc-workspace-sub-top-area-left lc-sub-top-area-left">{left}</div>,

View File

@ -47,23 +47,23 @@ export enum SkeletonEvents {
export interface ISkeleton extends Skeleton {} export interface ISkeleton extends Skeleton {}
export class Skeleton implements Omit<IPublicApiSkeleton, export class Skeleton implements Omit<IPublicApiSkeleton,
'showPanel' | 'showPanel' |
'hidePanel' | 'hidePanel' |
'showWidget' | 'showWidget' |
'enableWidget' | 'enableWidget' |
'hideWidget' | 'hideWidget' |
'disableWidget' | 'disableWidget' |
'showArea' | 'showArea' |
'onShowPanel' | 'onShowPanel' |
'onHidePanel' | 'onHidePanel' |
'onShowWidget' | 'onShowWidget' |
'onHideWidget' | 'onHideWidget' |
'remove' | 'remove' |
'hideArea' | 'hideArea' |
'add' | 'add' |
'getAreaItems' | 'getAreaItems' |
'onDisableWidget' | 'onDisableWidget' |
'onEnableWidget' 'onEnableWidget'
> { > {
private panels = new Map<string, Panel>(); private panels = new Map<string, Panel>();
@ -425,7 +425,10 @@ export class Skeleton implements Omit<IPublicApiSkeleton,
return this.configTransducers; return this.configTransducers;
} }
add(config: IPublicTypeSkeletonConfig, extraConfig?: Record<string, any>): IWidget | Widget | Panel | Stage | Dock | PanelDock | undefined { add(
config: IPublicTypeSkeletonConfig,
extraConfig?: Record<string, any>
): IWidget | Widget | Panel | Stage | Dock | PanelDock | undefined {
const registeredTransducers = this.getRegisteredConfigTransducers(); const registeredTransducers = this.getRegisteredConfigTransducers();
const parsedConfig = registeredTransducers.reduce((prevConfig, current) => { const parsedConfig = registeredTransducers.reduce((prevConfig, current) => {

View File

@ -221,23 +221,23 @@ export default function (
setValue(field: IPublicModelSettingField, eventData) { setValue(field: IPublicModelSettingField, eventData) {
const { eventDataList, eventList } = eventData; const { eventDataList, eventList } = eventData;
Array.isArray(eventList) && Array.isArray(eventList) &&
eventList.map((item) => { eventList.map((item) => {
field.parent.clearPropValue(item.name); field.parent.clearPropValue(item.name);
return item; return item;
}); });
Array.isArray(eventDataList) && Array.isArray(eventDataList) &&
eventDataList.map((item) => { eventDataList.map((item) => {
field.parent.setPropValue(item.name, { field.parent.setPropValue(item.name, {
type: 'JSFunction', type: 'JSFunction',
// 需要传下入参 // 需要传下入参
value: `function(){return this.${ value: `function(){return this.${
item.relatedEventName item.relatedEventName
}.apply(this,Array.prototype.slice.call(arguments).concat([${ }.apply(this,Array.prototype.slice.call(arguments).concat([${
item.paramStr ? item.paramStr : '' item.paramStr ? item.paramStr : ''
}])) }`, }])) }`,
});
return item;
}); });
return item;
});
}, },
}, },
], ],

View File

@ -20,7 +20,7 @@ function transformStringToFunction(str: string) {
if (leadingFnNameRe.test(str) && !leadingFnRe.test(str)) { if (leadingFnNameRe.test(str) && !leadingFnRe.test(str)) {
str = `function ${str}`; str = `function ${str}`;
} }
let fnBody = ` const fnBody = `
return function() { return function() {
const self = this; const self = this;
try { try {

View File

@ -11,6 +11,10 @@
"import": "./dist/ali-low-code-engine.js", "import": "./dist/ali-low-code-engine.js",
"require": "./dist/ali-low-code-engine.cjs", "require": "./dist/ali-low-code-engine.cjs",
"types": "./dist/index.d.ts" "types": "./dist/index.d.ts"
},
"./dist/": {
"import": "./dist/",
"require": "./dist/"
} }
}, },
"files": [ "files": [

View File

@ -1,5 +1,6 @@
import { createElement } from 'react'; import { createElement } from 'react';
import { createRoot, type Root } from 'react-dom/client'; import { createRoot, type Root } from 'react-dom/client';
import { isPlainObject } from '@alilc/lowcode-utils';
import { import {
globalContext, globalContext,
Editor, Editor,
@ -28,16 +29,13 @@ import {
PluginPreference, PluginPreference,
IDesigner, IDesigner,
} from '@alilc/lowcode-designer'; } from '@alilc/lowcode-designer';
import { import { Skeleton as InnerSkeleton, registerDefaults } from '@alilc/lowcode-editor-skeleton';
Skeleton as InnerSkeleton,
registerDefaults,
} from '@alilc/lowcode-editor-skeleton';
import { import {
Workspace as InnerWorkspace, Workspace as InnerWorkspace,
Workbench as WorkSpaceWorkbench, Workbench as WorkSpaceWorkbench,
IWorkspace, IWorkspace,
} from './workspace'; } from './workspace';
import { import {
Hotkey, Hotkey,
Project, Project,
@ -54,10 +52,10 @@ import {
CommonUI, CommonUI,
Command, Command,
} from './shell'; } from './shell';
import { isPlainObject } from '@alilc/lowcode-utils';
import './modules/live-editing'; import './modules/live-editing';
import * as classes from './modules/classes'; import * as classes from './modules/classes';
import symbols from './modules/symbols'; import symbols from './modules/symbols';
import { componentMetaParser } from './inner-plugins/component-meta-parser'; import { componentMetaParser } from './inner-plugins/component-meta-parser';
import { setterRegistry } from './inner-plugins/setter-registry'; import { setterRegistry } from './inner-plugins/setter-registry';
import { defaultPanelRegistry } from './inner-plugins/default-panel-registry'; import { defaultPanelRegistry } from './inner-plugins/default-panel-registry';
@ -66,13 +64,19 @@ import { builtinHotkey } from './inner-plugins/builtin-hotkey';
import { defaultContextMenu } from './inner-plugins/default-context-menu'; import { defaultContextMenu } from './inner-plugins/default-context-menu';
import { CommandPlugin } from '@alilc/lowcode-plugin-command'; import { CommandPlugin } from '@alilc/lowcode-plugin-command';
import { OutlinePlugin } from '@alilc/lowcode-plugin-outline-pane'; import { OutlinePlugin } from '@alilc/lowcode-plugin-outline-pane';
import { version } from '../package.json' import { version } from '../package.json';
import '@alilc/lowcode-editor-skeleton/dist/style.css';
export * from './modules/skeleton-types'; export * from './modules/skeleton-types';
export * from './modules/designer-types'; export * from './modules/designer-types';
export * from './modules/lowcode-types'; export * from './modules/lowcode-types';
async function registryInnerPlugin(designer: IDesigner, editor: IEditor, plugins: IPublicApiPlugins): Promise<IPublicTypeDisposable> { async function registryInnerPlugin(
designer: IDesigner,
editor: IEditor,
plugins: IPublicApiPlugins
): Promise<IPublicTypeDisposable> {
// 注册一批内置插件 // 注册一批内置插件
const componentMetaParserPlugin = componentMetaParser(designer); const componentMetaParserPlugin = componentMetaParser(designer);
const defaultPanelRegistryPlugin = defaultPanelRegistry(editor); const defaultPanelRegistryPlugin = defaultPanelRegistry(editor);
@ -97,9 +101,13 @@ async function registryInnerPlugin(designer: IDesigner, editor: IEditor, plugins
}; };
} }
const innerWorkspace: IWorkspace = new InnerWorkspace(registryInnerPlugin, shellModelFactory); const innerWorkspace: IWorkspace = new InnerWorkspace(
registryInnerPlugin,
shellModelFactory
);
const workspace: IPublicApiWorkspace = new Workspace(innerWorkspace); const workspace: IPublicApiWorkspace = new Workspace(innerWorkspace);
const editor = new Editor(); const editor = new Editor();
globalContext.register(editor, Editor); globalContext.register(editor, Editor);
globalContext.register(editor, 'editor'); globalContext.register(editor, 'editor');
globalContext.register(innerWorkspace, 'workspace'); globalContext.register(innerWorkspace, 'workspace');
@ -121,24 +129,30 @@ const skeleton = new Skeleton(innerSkeleton, 'any', false);
const innerSetters = new InnerSetters(); const innerSetters = new InnerSetters();
const setters = new Setters(innerSetters); const setters = new Setters(innerSetters);
const innerCommand = new InnerCommand(); const innerCommand = new InnerCommand();
const command = new Command(innerCommand, engineContext as IPublicModelPluginContext); const command = new Command(
innerCommand,
engineContext as IPublicModelPluginContext
);
const material = new Material(editor); const material = new Material(editor);
const commonUI = new CommonUI(editor); const commonUI = new CommonUI(editor);
editor.set('project', project); editor.set('project', project);
editor.set('setters' as any, setters); editor.set('setters' as any, setters);
editor.set('material', material); editor.set('material', material);
editor.set('innerHotkey', innerHotkey); editor.set('innerHotkey', innerHotkey);
const config = new Config(engineConfig); const config = new Config(engineConfig);
const event = new Event(commonEvent, { prefix: 'common' }); const event = new Event(commonEvent, { prefix: 'common' });
const logger = new Logger({ level: 'warn', bizName: 'common' }); const logger = new Logger({ level: 'warn', bizName: 'common' });
const common = new Common(editor, innerSkeleton); const common = new Common(editor, innerSkeleton);
const canvas = new Canvas(editor); const canvas = new Canvas(editor);
let plugins: Plugins;
const pluginContextApiAssembler: ILowCodePluginContextApiAssembler = { const pluginContextApiAssembler: ILowCodePluginContextApiAssembler = {
// eslint-disable-next-line @typescript-eslint/no-unused-vars assembleApis: (
assembleApis: (context: ILowCodePluginContextPrivate, pluginName: string, meta: IPublicTypePluginMeta) => { context: ILowCodePluginContextPrivate,
pluginName: string,
meta: IPublicTypePluginMeta
) => {
context.hotkey = hotkey; context.hotkey = hotkey;
context.project = project; context.project = project;
context.skeleton = new Skeleton(innerSkeleton, pluginName, false); context.skeleton = new Skeleton(innerSkeleton, pluginName, false);
@ -154,9 +168,10 @@ const pluginContextApiAssembler: ILowCodePluginContextApiAssembler = {
context.logger = new Logger({ level: 'warn', bizName: `plugin:${pluginName}` }); context.logger = new Logger({ level: 'warn', bizName: `plugin:${pluginName}` });
context.workspace = workspace; context.workspace = workspace;
context.commonUI = commonUI; context.commonUI = commonUI;
context.command = new Command(innerCommand, context as IPublicModelPluginContext, { context.command = new Command(
commandScope, innerCommand, context as IPublicModelPluginContext, {
}); commandScope,
});
context.registerLevel = IPublicEnumPluginRegisterLevel.Default; context.registerLevel = IPublicEnumPluginRegisterLevel.Default;
context.isPluginRegisteredInWorkspace = false; context.isPluginRegisteredInWorkspace = false;
editor.set('pluginContext', context); editor.set('pluginContext', context);
@ -164,7 +179,7 @@ const pluginContextApiAssembler: ILowCodePluginContextApiAssembler = {
}; };
const innerPlugins = new LowCodePluginManager(pluginContextApiAssembler); const innerPlugins = new LowCodePluginManager(pluginContextApiAssembler);
plugins = new Plugins(innerPlugins).toProxy(); const plugins = new Plugins(innerPlugins).toProxy();
editor.set('innerPlugins' as any, innerPlugins); editor.set('innerPlugins' as any, innerPlugins);
editor.set('plugins' as any, plugins); editor.set('plugins' as any, plugins);
@ -198,19 +213,25 @@ export {
commonUI, commonUI,
command, command,
}; };
// declare this is open-source version // declare this is open-source version
/**
* @deprecated
*/
export const isOpenSource = true; export const isOpenSource = true;
engineConfig.set('isOpenSource', isOpenSource);
engineConfig.set('ENGINE_VERSION', version);
export { version };
/**
* @deprecated
*/
export const __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = { export const __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = {
symbols, symbols,
classes, classes,
}; };
engineConfig.set('isOpenSource', isOpenSource);
// container which will host LowCodeEngine DOM
let engineContainer: HTMLElement;
export { version }
engineConfig.set('ENGINE_VERSION', version);
const pluginPromise = registryInnerPlugin(designer, editor, plugins); const pluginPromise = registryInnerPlugin(designer, editor, plugins);
@ -219,10 +240,14 @@ let root: Root | undefined;
export async function init( export async function init(
container?: HTMLElement, container?: HTMLElement,
options?: IPublicTypeEngineOptions, options?: IPublicTypeEngineOptions,
pluginPreference?: PluginPreference, pluginPreference?: PluginPreference
) { ) {
await destroy(); await destroy();
// container which will host LowCodeEngine DOM
let engineContainer: HTMLElement;
let engineOptions = null; let engineOptions = null;
if (isPlainObject(container)) { if (isPlainObject(container)) {
engineOptions = container; engineOptions = container;
engineContainer = document.createElement('div'); engineContainer = document.createElement('div');
@ -237,6 +262,7 @@ export async function init(
document.body.appendChild(engineContainer); document.body.appendChild(engineContainer);
} }
} }
engineConfig.setEngineOptions(engineOptions as any); engineConfig.setEngineOptions(engineOptions as any);
const { Workbench } = common.skeletonCabin; const { Workbench } = common.skeletonCabin;
@ -245,15 +271,15 @@ export async function init(
disposeFun && disposeFun(); disposeFun && disposeFun();
if (!root) { if (!root) {
root = createRoot( root = createRoot(engineContainer);
engineContainer, root.render(
createElement(WorkSpaceWorkbench, {
workspace: innerWorkspace,
// skeleton: workspace.skeleton,
className: 'engine-main',
topAreaItemClassName: 'engine-actionitem',
})
); );
root.render(createElement(WorkSpaceWorkbench, {
workspace: innerWorkspace,
// skeleton: workspace.skeleton,
className: 'engine-main',
topAreaItemClassName: 'engine-actionitem',
}))
} }
innerWorkspace.enableAutoOpenFirstWindow = engineConfig.get('enableAutoOpenFirstWindow', true); innerWorkspace.enableAutoOpenFirstWindow = engineConfig.get('enableAutoOpenFirstWindow', true);
@ -267,12 +293,14 @@ export async function init(
await plugins.init(pluginPreference as any); await plugins.init(pluginPreference as any);
if (!root) { if (!root) {
root = createRoot(engineContainer) root = createRoot(engineContainer);
root.render(createElement(Workbench, { root.render(
skeleton: innerSkeleton, createElement(Workbench, {
className: 'engine-main', skeleton: innerSkeleton,
topAreaItemClassName: 'engine-actionitem', className: 'engine-main',
})) topAreaItemClassName: 'engine-actionitem',
})
);
} }
} }
@ -280,7 +308,9 @@ export async function destroy() {
// remove all documents // remove all documents
const { documents } = project; const { documents } = project;
if (Array.isArray(documents) && documents.length > 0) { if (Array.isArray(documents) && documents.length > 0) {
documents.forEach(((doc: IPublicModelDocumentModel) => project.removeDocument(doc))); documents.forEach(
(doc: IPublicModelDocumentModel) => project.removeDocument(doc)
);
} }
// TODO: delete plugins except for core plugins // TODO: delete plugins except for core plugins

View File

@ -1,6 +1,7 @@
import { version } from './engine-core'; import { version } from './engine-core';
export * from './engine-core'; export * from './engine-core';
console.log( console.log(
`%c AliLowCodeEngine %c v${version} `, `%c AliLowCodeEngine %c v${version} `,
'padding: 2px 1px; border-radius: 3px 0 0 3px; color: #fff; background: #606060; font-weight: bold;', 'padding: 2px 1px; border-radius: 3px 0 0 3px; color: #fff; background: #606060; font-weight: bold;',

View File

@ -328,7 +328,7 @@ export const builtinHotkey = (ctx: IPublicModelPluginContext) => {
if (!target) { if (!target) {
return; return;
} }
let canAddComponentsTree = componentsTree.filter((node: IPublicModelNode) => { const canAddComponentsTree = componentsTree.filter((node: IPublicModelNode) => {
const dragNodeObject: IPublicTypeDragNodeObject = { const dragNodeObject: IPublicTypeDragNodeObject = {
type: IPublicEnumDragObjectType.Node, type: IPublicEnumDragObjectType.Node,
nodes: [node], nodes: [node],

View File

@ -129,7 +129,7 @@ export const defaultContextMenu = (ctx: IPublicModelPluginContext) => {
return; return;
} }
if (parent) { if (parent) {
let canAddNodes = nodeSchema.filter((nodeSchema: IPublicTypeNodeSchema) => { const canAddNodes = nodeSchema.filter((nodeSchema: IPublicTypeNodeSchema) => {
const dragNodeObject: IPublicTypeDragNodeDataObject = { const dragNodeObject: IPublicTypeDragNodeDataObject = {
type: IPublicEnumDragObjectType.NodeData, type: IPublicEnumDragObjectType.NodeData,
data: nodeSchema, data: nodeSchema,
@ -177,7 +177,7 @@ export const defaultContextMenu = (ctx: IPublicModelPluginContext) => {
if (nodeSchema.length === 0) { if (nodeSchema.length === 0) {
return; return;
} }
let canAddNodes = nodeSchema.filter((nodeSchema: IPublicTypeNodeSchema) => { const canAddNodes = nodeSchema.filter((nodeSchema: IPublicTypeNodeSchema) => {
const dragNodeObject: IPublicTypeDragNodeDataObject = { const dragNodeObject: IPublicTypeDragNodeDataObject = {
type: IPublicEnumDragObjectType.NodeData, type: IPublicEnumDragObjectType.NodeData,
data: nodeSchema, data: nodeSchema,

View File

@ -6,11 +6,13 @@ export const setterRegistry = (ctx: IPublicModelPluginContext) => {
init() { init() {
const { config } = ctx; const { config } = ctx;
if (config.get('disableDefaultSetters')) return; if (config.get('disableDefaultSetters')) return;
// todo: 互相依赖
// const builtinSetters = require('@alilc/lowcode-engine-ext')?.setters; // const builtinSetters = require('@alilc/lowcode-engine-ext')?.setters;
// if (builtinSetters) { // @ts-expect-error: todo remove
// ctx.setters.registerSetter(builtinSetters); const builtinSetters = window.AliLowCodeEngineExt?.setters;
// } if (builtinSetters) {
ctx.setters.registerSetter(builtinSetters);
}
}, },
}; };
}; };

View File

@ -13,4 +13,3 @@ export {
SkeletonItem, SkeletonItem,
} from '../shell'; } from '../shell';
export { Node as InnerNode } from '@alilc/lowcode-designer'; export { Node as InnerNode } from '@alilc/lowcode-designer';

View File

@ -26,13 +26,6 @@ export class Hotkey implements IPublicApiHotkey {
return this[hotkeySymbol].callBacks; return this[hotkeySymbol].callBacks;
} }
/**
* @deprecated
*/
get callBacks() {
return this.callbacks;
}
/** /**
* *
* @param combos ['command + s'] ['ctrl + shift + s'] * @param combos ['command + s'] ['ctrl + shift + s']
@ -41,10 +34,10 @@ export class Hotkey implements IPublicApiHotkey {
* @returns * @returns
*/ */
bind( bind(
combos: string[] | string, combos: string[] | string,
callback: IPublicTypeHotkeyCallback, callback: IPublicTypeHotkeyCallback,
action?: string, action?: string,
): IPublicTypeDisposable { ): IPublicTypeDisposable {
this[hotkeySymbol].bind(combos, callback, action); this[hotkeySymbol].bind(combos, callback, action);
return () => { return () => {
this[hotkeySymbol].unbind(combos, callback, action); this[hotkeySymbol].unbind(combos, callback, action);

View File

@ -141,7 +141,7 @@ export class Material implements IPublicApiMaterial {
getComponentMetasMap(): Map<string, IPublicModelComponentMeta> { getComponentMetasMap(): Map<string, IPublicModelComponentMeta> {
const map = new Map<string, IPublicModelComponentMeta>(); const map = new Map<string, IPublicModelComponentMeta>();
const originalMap = this[designerSymbol].getComponentMetasMap(); const originalMap = this[designerSymbol].getComponentMetasMap();
for (let componentName of originalMap.keys()) { for (const componentName of originalMap.keys()) {
map.set(componentName, this.getComponentMeta(componentName)!); map.set(componentName, this.getComponentMeta(componentName)!);
} }
return map; return map;

View File

@ -44,8 +44,8 @@ export class Plugins implements IPublicApiPlugins {
} }
getPluginPreference( getPluginPreference(
pluginName: string, pluginName: string,
): Record<string, IPublicTypePreferenceValueType> | null | undefined { ): Record<string, IPublicTypePreferenceValueType> | null | undefined {
return this[pluginsSymbol].getPluginPreference(pluginName); return this[pluginsSymbol].getPluginPreference(pluginName);
} }

View File

@ -164,9 +164,9 @@ export class Project implements IPublicApiProject {
* @param stage * @param stage
*/ */
addPropsTransducer( addPropsTransducer(
transducer: IPublicTypePropsTransducer, transducer: IPublicTypePropsTransducer,
stage: IPublicEnumTransformStage, stage: IPublicEnumTransformStage,
): void { ): void {
this[projectSymbol].designer.addPropsReducer(transducer, stage); this[projectSymbol].designer.addPropsReducer(transducer, stage);
} }
@ -177,9 +177,9 @@ export class Project implements IPublicApiProject {
*/ */
onRemoveDocument(fn: (data: { id: string}) => void): IPublicTypeDisposable { onRemoveDocument(fn: (data: { id: string}) => void): IPublicTypeDisposable {
return this[editorSymbol].eventBus.on( return this[editorSymbol].eventBus.on(
'designer.document.remove', 'designer.document.remove',
(data: { id: string }) => fn(data), (data: { id: string }) => fn(data),
); );
} }
/** /**

View File

@ -33,10 +33,10 @@ export class Skeleton implements IPublicApiSkeleton {
} }
constructor( constructor(
skeleton: ISkeleton, skeleton: ISkeleton,
pluginName: string, pluginName: string,
readonly workspaceMode: boolean = false, readonly workspaceMode: boolean = false,
) { ) {
this[innerSkeletonSymbol] = skeleton; this[innerSkeletonSymbol] = skeleton;
this.pluginName = pluginName; this.pluginName = pluginName;
} }

View File

@ -14,9 +14,9 @@ export class Clipboard implements IPublicModelClipboard {
} }
waitPasteData( waitPasteData(
keyboardEvent: KeyboardEvent, keyboardEvent: KeyboardEvent,
cb: (data: any, clipboardEvent: ClipboardEvent) => void, cb: (data: any, clipboardEvent: ClipboardEvent) => void,
): void { ): void {
this[clipboardSymbol].waitPasteData(keyboardEvent, cb); this[clipboardSymbol].waitPasteData(keyboardEvent, cb);
} }
} }

View File

@ -130,14 +130,14 @@ export class ComponentMeta implements IPublicModelComponentMeta {
* @returns * @returns
*/ */
checkNestingDown( checkNestingDown(
my: IPublicModelNode | IPublicTypeNodeData, my: IPublicModelNode | IPublicTypeNodeData,
target: IPublicTypeNodeSchema | IPublicModelNode | IPublicTypeNodeSchema[], target: IPublicTypeNodeSchema | IPublicModelNode | IPublicTypeNodeSchema[],
) { ) {
const curNode = (my as any)?.isNode ? (my as any)[nodeSymbol] : my; const curNode = (my as any)?.isNode ? (my as any)[nodeSymbol] : my;
return this[componentMetaSymbol].checkNestingDown( return this[componentMetaSymbol].checkNestingDown(
curNode as any, curNode as any,
(target as any)[nodeSymbol] || target, (target as any)[nodeSymbol] || target,
); );
} }
refreshMetadata(): void { refreshMetadata(): void {

View File

@ -111,8 +111,8 @@ export class DocumentModel implements IPublicModelDocumentModel {
this._focusNode = node; this._focusNode = node;
this[editorSymbol].eventBus.emit( this[editorSymbol].eventBus.emit(
'shell.document.focusNodeChanged', 'shell.document.focusNodeChanged',
{ document: this, focusNode: node }, { document: this, focusNode: node },
); );
} }
/** /**
@ -121,7 +121,7 @@ export class DocumentModel implements IPublicModelDocumentModel {
*/ */
get nodesMap(): Map<string, IPublicModelNode> { get nodesMap(): Map<string, IPublicModelNode> {
const map = new Map<string, IPublicModelNode>(); const map = new Map<string, IPublicModelNode>();
for (let id of this[documentSymbol].nodesMap.keys()) { for (const id of this[documentSymbol].nodesMap.keys()) {
map.set(id, this.getNodeById(id)!); map.set(id, this.getNodeById(id)!);
} }
return map; return map;
@ -227,14 +227,14 @@ export class DocumentModel implements IPublicModelDocumentModel {
* @returns boolean * @returns boolean
*/ */
checkNesting( checkNesting(
dropTarget: IPublicModelNode, dropTarget: IPublicModelNode,
dragObject: IPublicTypeDragNodeObject | IPublicTypeDragNodeDataObject, dragObject: IPublicTypeDragNodeObject | IPublicTypeDragNodeDataObject,
): boolean { ): boolean {
let innerDragObject = dragObject; const innerDragObject = dragObject;
if (isDragNodeObject(dragObject)) { if (isDragNodeObject(dragObject)) {
innerDragObject.nodes = innerDragObject.nodes?.map( innerDragObject.nodes = innerDragObject.nodes?.map(
(node: IPublicModelNode) => ((node as any)[nodeSymbol] || node), (node: IPublicModelNode) => ((node as any)[nodeSymbol] || node),
); );
} }
return this[documentSymbol].checkNesting( return this[documentSymbol].checkNesting(
((dropTarget as any)[nodeSymbol] || dropTarget) as any, ((dropTarget as any)[nodeSymbol] || dropTarget) as any,

View File

@ -41,9 +41,9 @@ export class Dragon implements IPublicModelDragon {
} }
static create( static create(
dragon: IDragon | null, dragon: IDragon | null,
workspaceMode: boolean, workspaceMode: boolean,
): IPublicModelDragon | null { ): IPublicModelDragon | null {
if (!dragon) { if (!dragon) {
return null; return null;
} }

View File

@ -70,7 +70,7 @@ export class ModalNodesManager implements IPublicModelModalNodesManager {
* *
* @param node Node * @param node Node
*/ */
setInvisible(node: IPublicModelNode): void { setInvisible(node: IPublicModelNode): void {
this[modalNodesManagerSymbol].setInvisible((node as any)[nodeSymbol]); this[modalNodesManagerSymbol].setInvisible((node as any)[nodeSymbol]);
} }
} }

View File

@ -518,9 +518,9 @@ export class Node implements IPublicModelNode {
* @returns * @returns
*/ */
exportSchema( exportSchema(
stage: IPublicEnumTransformStage = IPublicEnumTransformStage.Render, stage: IPublicEnumTransformStage = IPublicEnumTransformStage.Render,
options?: any, options?: any,
): IPublicTypeNodeSchema { ): IPublicTypeNodeSchema {
return this[nodeSymbol].export(stage, options); return this[nodeSymbol].export(stage, options);
} }
@ -531,15 +531,15 @@ export class Node implements IPublicModelNode {
* @param useMutator * @param useMutator
*/ */
insertBefore( insertBefore(
node: IPublicModelNode, node: IPublicModelNode,
ref?: IPublicModelNode | undefined, ref?: IPublicModelNode | undefined,
useMutator?: boolean, useMutator?: boolean,
): void { ): void {
this[nodeSymbol].insertBefore( this[nodeSymbol].insertBefore(
(node as any)[nodeSymbol] || node, (node as any)[nodeSymbol] || node,
(ref as any)?.[nodeSymbol], (ref as any)?.[nodeSymbol],
useMutator, useMutator,
); );
} }
/** /**
@ -549,15 +549,15 @@ export class Node implements IPublicModelNode {
* @param useMutator * @param useMutator
*/ */
insertAfter( insertAfter(
node: IPublicModelNode, node: IPublicModelNode,
ref?: IPublicModelNode | undefined, ref?: IPublicModelNode | undefined,
useMutator?: boolean, useMutator?: boolean,
): void { ): void {
this[nodeSymbol].insertAfter( this[nodeSymbol].insertAfter(
(node as any)[nodeSymbol] || node, (node as any)[nodeSymbol] || node,
(ref as any)?.[nodeSymbol], (ref as any)?.[nodeSymbol],
useMutator, useMutator,
); );
} }
/** /**

View File

@ -56,7 +56,7 @@ import { IEditorWindow } from '../window';
export interface IBasicContext extends BasicContext {} export interface IBasicContext extends BasicContext {}
export class BasicContext export class BasicContext
implements implements
Omit< Omit<
IPublicModelPluginContext, IPublicModelPluginContext,
'workspace' | 'commonUI' | 'command' | 'isPluginRegisteredInWorkspace' | 'editorWindow' 'workspace' | 'commonUI' | 'command' | 'isPluginRegisteredInWorkspace' | 'editorWindow'

View File

@ -15,9 +15,9 @@ export class Workbench extends Component<{
className?: string; className?: string;
topAreaItemClassName?: string; topAreaItemClassName?: string;
}, { }, {
workspaceEmptyComponent: any; workspaceEmptyComponent: any;
theme?: string; theme?: string;
}> { }> {
constructor(props: any) { constructor(props: any) {
super(props); super(props);
const { config, components, workspace } = this.props; const { config, components, workspace } = this.props;

View File

@ -1,8 +1,5 @@
import { defineConfig } from 'vitest/config' import { defineProject } from 'vitest/config'
export default defineConfig({ export default defineProject({
test: { test: {}
include: ['tests/*.spec.ts'],
environment: 'jsdom'
}
}) })

View File

@ -195,7 +195,7 @@ export class PaneController implements IPublicModelSensor, ITreeBoard, IPublicTy
if ( if (
originLoc && originLoc &&
((pos && pos === 'unchanged') || ((pos && pos === 'unchanged') ||
(irect && globalY >= irect.top && globalY <= irect.bottom)) && (irect && globalY >= irect.top && globalY <= irect.bottom)) &&
dragObject dragObject
) { ) {
const loc = originLoc.clone(e); const loc = originLoc.clone(e);

View File

@ -66,7 +66,7 @@ export class TreeMaster {
'en-US': enUS, 'en-US': enUS,
'zh-CN': zhCN, 'zh-CN': zhCN,
}); });
let _pluginContext: IOutlinePanelPluginContext = Object.assign(pluginContext, { const _pluginContext: IOutlinePanelPluginContext = Object.assign(pluginContext, {
intl, intl,
intlNode, intlNode,
getLocale, getLocale,

View File

@ -13,8 +13,8 @@ export class Pane extends PureComponent<{
controller: PaneController; controller: PaneController;
hideFilter?: boolean; hideFilter?: boolean;
}, { }, {
tree: Tree | null; tree: Tree | null;
}> { }> {
private controller; private controller;
private simulatorRendererReadyDispose: IPublicTypeDisposable; private simulatorRendererReadyDispose: IPublicTypeDisposable;

View File

@ -69,10 +69,10 @@ interface ITreeNodeChildrenState {
dropDetail: IPublicTypeLocationChildrenDetail | undefined | null; dropDetail: IPublicTypeLocationChildrenDetail | undefined | null;
} }
class TreeNodeChildren extends PureComponent<{ class TreeNodeChildren extends PureComponent<{
treeNode: TreeNode; treeNode: TreeNode;
isModal?: boolean; isModal?: boolean;
treeChildren: TreeNode[] | null; treeChildren: TreeNode[] | null;
}, ITreeNodeChildrenState> { }, ITreeNodeChildrenState> {
state: ITreeNodeChildrenState = { state: ITreeNodeChildrenState = {
filterWorking: false, filterWorking: false,
matchSelf: false, matchSelf: false,
@ -96,7 +96,7 @@ class TreeNodeChildren extends PureComponent<{
filterWorking: newFilterWorking, filterWorking: newFilterWorking,
matchSelf: newMatchChild, matchSelf: newMatchChild,
keywords: newKeywords, keywords: newKeywords,
} = treeNode.filterReult; } = treeNode.filterReult;
this.setState({ this.setState({
filterWorking: newFilterWorking, filterWorking: newFilterWorking,
matchSelf: newMatchChild, matchSelf: newMatchChild,
@ -104,10 +104,10 @@ class TreeNodeChildren extends PureComponent<{
}); });
}); });
this.offLocationChanged = project.currentDocument?.onDropLocationChanged( this.offLocationChanged = project.currentDocument?.onDropLocationChanged(
() => { () => {
this.setState({ dropDetail: treeNode.dropDetail }); this.setState({ dropDetail: treeNode.dropDetail });
}, },
); );
} }
componentWillUnmount(): void { componentWillUnmount(): void {
this.offLocationChanged && this.offLocationChanged(); this.offLocationChanged && this.offLocationChanged();
@ -188,8 +188,8 @@ class TreeNodeChildren extends PureComponent<{
} }
class TreeNodeSlots extends PureComponent<{ class TreeNodeSlots extends PureComponent<{
treeNode: TreeNode; treeNode: TreeNode;
}> { }> {
render() { render() {
const { treeNode } = this.props; const { treeNode } = this.props;
if (!treeNode.hasSlots()) { if (!treeNode.hasSlots()) {

View File

@ -103,21 +103,21 @@ export default class TreeNodeView extends PureComponent<{
matchChild: boolean; matchChild: boolean;
matchSelf: boolean; matchSelf: boolean;
} = { } = {
expanded: false, expanded: false,
selected: false, selected: false,
hidden: false, hidden: false,
locked: false, locked: false,
detecting: false, detecting: false,
isRoot: false, isRoot: false,
highlight: false, highlight: false,
dropping: false, dropping: false,
conditionFlow: false, conditionFlow: false,
expandable: false, expandable: false,
treeChildren: [], treeChildren: [],
filterWorking: false, filterWorking: false,
matchChild: false, matchChild: false,
matchSelf: false, matchSelf: false,
}; };
eventOffCallbacks: Array<IPublicTypeDisposable | undefined> = []; eventOffCallbacks: Array<IPublicTypeDisposable | undefined> = [];
constructor(props: any) { constructor(props: any) {
@ -232,7 +232,7 @@ export default class TreeNodeView extends PureComponent<{
'condition-flow': this.state.conditionFlow, 'condition-flow': this.state.conditionFlow,
highlight: this.state.highlight, highlight: this.state.highlight,
}); });
let shouldShowModalTreeNode: boolean = this.shouldShowModalTreeNode(); const shouldShowModalTreeNode: boolean = this.shouldShowModalTreeNode();
// filter 处理 // filter 处理
const { filterWorking, matchChild, matchSelf } = this.state; const { filterWorking, matchChild, matchSelf } = this.state;

View File

@ -52,12 +52,12 @@ export default class TreeTitle extends PureComponent<{
keywords: string; keywords: string;
matchSelf: boolean; matchSelf: boolean;
} = { } = {
editing: false, editing: false,
title: '', title: '',
filterWorking: false, filterWorking: false,
keywords: '', keywords: '',
matchSelf: false, matchSelf: false,
}; };
private lastInput?: HTMLInputElement; private lastInput?: HTMLInputElement;

View File

@ -30,8 +30,8 @@ export default class TreeView extends PureComponent<{
state: { state: {
root: TreeNode | null; root: TreeNode | null;
} = { } = {
root: null, root: null,
}; };
private hover(e: ReactMouseEvent) { private hover(e: ReactMouseEvent) {
const { project } = this.props.tree.pluginContext; const { project } = this.props.tree.pluginContext;

View File

@ -1,6 +1,6 @@
{ {
"name": "@alilc/lowcode-react-renderer", "name": "@alilc/lowcode-react-renderer",
"version": "2.0.0-beta.0", "version": "2.0.0-alpha.0",
"description": "react renderer for ali lowcode engine", "description": "react renderer for ali lowcode engine",
"type": "module", "type": "module",
"bugs": "https://github.com/alibaba/lowcode-engine/issues", "bugs": "https://github.com/alibaba/lowcode-engine/issues",
@ -18,7 +18,7 @@
}, },
"scripts": { "scripts": {
"build:target": "vite build", "build:target": "vite build",
"build:dts": "tsc -p tsconfig.declaration.json && node ../../scripts/rollup-dts.mjs", "build:dts": "tsc -p tsconfig.declaration.json && node ../../scripts/rollup-dts.js",
"test": "vitest" "test": "vitest"
}, },
"dependencies": { "dependencies": {

View File

@ -3,3 +3,5 @@ import { createComponent as internalCreate, ComponentOptions } from '../componen
export function createComponent(options: ComponentOptions) { export function createComponent(options: ComponentOptions) {
return internalCreate(options); return internalCreate(options);
} }
export type { ComponentOptions };

View File

@ -81,7 +81,7 @@ export function compile(tokens: Token[], values: Record<string, any> | any[] = {
break; break;
case 'unknown': case 'unknown':
if (process.env.NODE_ENV !== 'production') { if (process.env.NODE_ENV !== 'production') {
console.warn(`Detect 'unknown' type of token!`); console.warn('Detect \'unknown\' type of token!');
} }
break; break;
} }

View File

@ -98,7 +98,7 @@ async function appendExternalCss(
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let el: HTMLLinkElement = document.createElement('link'); const el: HTMLLinkElement = document.createElement('link');
el.rel = 'stylesheet'; el.rel = 'stylesheet';
el.href = url; el.href = url;
@ -122,7 +122,7 @@ export async function appendExternalStyle(
root: HTMLElement = document.head, root: HTMLElement = document.head,
): Promise<HTMLElement> { ): Promise<HTMLElement> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let el: HTMLStyleElement = document.createElement('style'); const el: HTMLStyleElement = document.createElement('style');
el.innerText = cssText; el.innerText = cssText;
el.addEventListener( el.addEventListener(

View File

@ -1,9 +1,9 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'; import { describe, it, expect, vi, beforeEach } from 'vitest';
import { useEvent, createHookStore, type HookStore } from '../../src/utils/hook'; import { createEvent, createHookStore, type HookStore } from '../../src/utils/hook';
describe('event', () => { describe('event', () => {
it("event's listener ops", () => { it('event\'s listener ops', () => {
const event = useEvent(); const event = createEvent();
const fn = () => {}; const fn = () => {};
event.add(fn); event.add(fn);

View File

@ -1,17 +1,18 @@
{ {
"name": "@alilc/lowcode-renderer-core", "name": "@alilc/lowcode-renderer-core",
"version": "2.0.0-beta.0", "version": "2.0.0-alpha.0",
"description": "", "description": "core package for lowCode renderer",
"type": "module", "type": "module",
"bugs": "https://github.com/alibaba/lowcode-engine/issues", "bugs": "https://github.com/alibaba/lowcode-engine/issues",
"homepage": "https://github.com/alibaba/lowcode-engine/#readme", "homepage": "https://github.com/alibaba/lowcode-engine/#readme",
"license": "MIT", "license": "MIT",
"main": "dist/low-code-renderer-core.js", "main": "dist/low-code-renderer-core.cjs",
"module": "dist/low-code-renderer-core.js", "module": "dist/low-code-renderer-core.js",
"types": "dist/index.d.ts", "types": "dist/index.d.ts",
"exports": { "exports": {
".": { ".": {
"import": "./dist/low-code-renderer-core.js", "import": "./dist/low-code-renderer-core.js",
"require": "./dist/low-code-renderer-core.cjs",
"types": "./dist/index.d.ts" "types": "./dist/index.d.ts"
} }
}, },
@ -22,7 +23,7 @@
], ],
"scripts": { "scripts": {
"build:target": "vite build", "build:target": "vite build",
"build:dts": "tsc -p tsconfig.declaration.json && node ../../scripts/rollup-dts.mjs", "build:dts": "tsc -p tsconfig.declaration.json && node ../../scripts/rollup-dts.js",
"test": "vitest --run", "test": "vitest --run",
"test:watch": "vitest" "test:watch": "vitest"
}, },

View File

@ -5,7 +5,7 @@ export { createCodeRuntime, createScope } from './code-runtime';
export { definePlugin } from './plugin'; export { definePlugin } from './plugin';
export { createWidget } from './widget'; export { createWidget } from './widget';
export { createContainer } from './container'; export { createContainer } from './container';
export { createHookStore, useEvent } from './utils/hook'; export { createHookStore, createEvent } from './utils/hook';
export * from './utils/type-guard'; export * from './utils/type-guard';
export * from './utils/value'; export * from './utils/value';
export * from './widget'; export * from './widget';

View File

@ -8,7 +8,7 @@ export class RuntimeError extends Error {
message: string, message: string,
) { ) {
super(message); super(message);
appBoosts.hookStore.call(`app:error`, this); appBoosts.hookStore.call('app:error', this);
} }
} }

View File

@ -2,7 +2,7 @@ import type { AnyFunction } from '../types';
export type EventName = string | number | symbol; export type EventName = string | number | symbol;
export function useEvent<T = AnyFunction>() { export function createEvent<T = AnyFunction>() {
let events: T[] = []; let events: T[] = [];
function add(fn: T) { function add(fn: T) {
@ -31,7 +31,7 @@ export function useEvent<T = AnyFunction>() {
}; };
} }
export type Event<F = AnyFunction> = ReturnType<typeof useEvent<F>>; export type Event<F = AnyFunction> = ReturnType<typeof createEvent<F>>;
export type HookCallback = (...args: any) => Promise<any> | any; export type HookCallback = (...args: any) => Promise<any> | any;
@ -93,7 +93,7 @@ export function createHookStore<
let hooks = hooksMap.get(name); let hooks = hooksMap.get(name);
if (!hooks) { if (!hooks) {
hooks = useEvent(); hooks = createEvent();
hooksMap.set(name, hooks); hooksMap.set(name, hooks);
} }

View File

@ -4,5 +4,6 @@ import baseConfigFn from '../../vite.base.config'
export default defineConfig(async () => { export default defineConfig(async () => {
return baseConfigFn({ return baseConfigFn({
name: 'LowCodeRendererCore', name: 'LowCodeRendererCore',
defaultFormats: ['es', 'cjs']
}) })
}); });

View File

@ -0,0 +1,7 @@
import { createRouterMatcher } from '../src/matcher';
describe('RouterMatcher', () => {
it('', () => {
});
});

View File

@ -1,22 +1,24 @@
{ {
"name": "@alilc/lowcode-renderer-router", "name": "@alilc/lowcode-renderer-router",
"version": "1.0.0-beta.0", "version": "1.0.0-alpha.0",
"description": "", "description": "router for lowCode renderer",
"type": "module", "type": "module",
"bugs": "https://github.com/alibaba/lowcode-engine/issues", "bugs": "https://github.com/alibaba/lowcode-engine/issues",
"homepage": "https://github.com/alibaba/lowcode-engine/#readme", "homepage": "https://github.com/alibaba/lowcode-engine/#readme",
"license": "MIT", "license": "MIT",
"main": "dist/low-code-runtime-router.cjs",
"module": "dist/low-code-runtime-router.js", "module": "dist/low-code-runtime-router.js",
"types": "dist/index.d.ts", "types": "dist/index.d.ts",
"exports": { "exports": {
".": { ".": {
"import": "./dist/low-code-runtime-router.js", "import": "./dist/low-code-runtime-router.js",
"require": "./dist/low-code-runtime-router.cjs",
"types": "./dist/index.d.ts" "types": "./dist/index.d.ts"
} }
}, },
"scripts": { "scripts": {
"build:target": "vite build", "build:target": "vite build",
"build:dts": "tsc -p tsconfig.declaration.json && node ../../scripts/rollup-dts.mjs", "build:dts": "tsc -p tsconfig.declaration.json && node ../../scripts/rollup-dts.js",
"test": "vitest" "test": "vitest"
}, },
"dependencies": { "dependencies": {

View File

@ -1,4 +1,4 @@
import { useEvent } from '@alilc/lowcode-renderer-core'; import { createEvent } from '@alilc/lowcode-renderer-core';
export type HistoryState = History['state']; export type HistoryState = History['state'];
export type HistoryLocation = string; export type HistoryLocation = string;
@ -166,8 +166,8 @@ export function createBrowserHistory(base?: string): RouterHistory {
currentLocation = to; currentLocation = to;
} }
let listeners = useEvent<NavigationCallback>(); const listeners = createEvent<NavigationCallback>();
let teardowns = useEvent<() => void>(); const teardowns = createEvent<() => void>();
let pauseState: HistoryLocation | null = null; let pauseState: HistoryLocation | null = null;
@ -285,7 +285,7 @@ function createCurrentLocation(base: string, location: Location) {
// hash bases like #, /#, #/, #!, #!/, /#!/, or even /folder#end // hash bases like #, /#, #/, #!, #!/, /#!/, or even /folder#end
const hashPos = base.indexOf('#'); const hashPos = base.indexOf('#');
if (hashPos > -1) { if (hashPos > -1) {
let slicePos = hash.includes(base.slice(hashPos)) ? base.slice(hashPos).length : 1; const slicePos = hash.includes(base.slice(hashPos)) ? base.slice(hashPos).length : 1;
let pathFromHash = hash.slice(slicePos); let pathFromHash = hash.slice(slicePos);
// prepend the starting slash to hash so the url starts with /# // prepend the starting slash to hash so the url starts with /#
if (pathFromHash[0] !== '/') pathFromHash = '/' + pathFromHash; if (pathFromHash[0] !== '/') pathFromHash = '/' + pathFromHash;
@ -348,7 +348,7 @@ export function createMemoryHistory(base = ''): RouterHistory {
historyStack.push({ location, state }); historyStack.push({ location, state });
} }
const listeners = useEvent<NavigationCallback>(); const listeners = createEvent<NavigationCallback>();
function triggerListeners( function triggerListeners(
to: HistoryLocation, to: HistoryLocation,

View File

@ -1,9 +1,11 @@
// refer from https://github.com/vuejs/router/blob/main/packages/router/src/matcher/index.ts
import { type PlainObject, type RawLocation } from '@alilc/lowcode-renderer-core'; import { type PlainObject, type RawLocation } from '@alilc/lowcode-renderer-core';
import { pick } from 'lodash-es'; import { pick } from 'lodash-es';
import { createRouteRecordMatcher, type RouteRecordMatcher } from './utils/record-matcher'; import { createRouteRecordMatcher, type RouteRecordMatcher } from './utils/record-matcher';
import { type PathParserOptions } from './utils/path-parser'; import { type PathParserOptions, type PathParams, comparePathParserScore } from './utils/path-parser';
import type { RouteRecord, RouteParams, RouteLocationNormalized } from './types'; import type { RouteRecord, RouteLocationNormalized } from './types';
export interface RouteRecordNormalized { export interface RouteRecordNormalized {
/** /**
@ -58,7 +60,9 @@ export interface RouterMatcher {
* @param location - MatcherLocationRaw to resolve to a url * @param location - MatcherLocationRaw to resolve to a url
* @param currentLocation - MatcherLocation of the current location * @param currentLocation - MatcherLocation of the current location
*/ */
resolve: (location: RawLocation, currentLocation: MatcherLocation) => MatcherLocation; resolve: (
location: RawLocation, currentLocation: MatcherLocation
) => MatcherLocation;
} }
export function createRouterMatcher( export function createRouterMatcher(
@ -83,7 +87,7 @@ export function createRouterMatcher(
const parentPath = parent.record.path; const parentPath = parent.record.path;
const connectingSlash = parentPath[parentPath.length - 1] === '/' ? '' : '/'; const connectingSlash = parentPath[parentPath.length - 1] === '/' ? '' : '/';
normalizedRecord.path = parent.record.path + (path && connectingSlash + path); normalizedRecord.path = parent.record.path + (path ? connectingSlash + path : '');
} }
const matcher = createRouteRecordMatcher(normalizedRecord, parent, options); const matcher = createRouteRecordMatcher(normalizedRecord, parent, options);
@ -96,7 +100,16 @@ export function createRouterMatcher(
} }
if (matcher.record.path) { if (matcher.record.path) {
matchers.push(matcher); let i = 0;
while (
i < matchers.length &&
comparePathParserScore(matcher, matchers[i]) >= 0 &&
(matcher.record.path !== matchers[i].record.path ||
!isRecordChildOf(matcher, matchers[i]))
) {
i++;
}
matchers.splice(i, 0, matcher);
if (matcher.record.name) { if (matcher.record.name) {
matcherMap.set(matcher.record.name, matcher); matcherMap.set(matcher.record.name, matcher);
@ -126,9 +139,12 @@ export function createRouterMatcher(
return matcherMap.get(name); return matcherMap.get(name);
} }
function resolve(location: RawLocation, currentLocation: MatcherLocation): MatcherLocation { function resolve(
location: RawLocation,
currentLocation: MatcherLocation
): MatcherLocation {
let matcher: RouteRecordMatcher | undefined; let matcher: RouteRecordMatcher | undefined;
let params: RouteParams = {}; let params: PathParams = {};
let path: MatcherLocation['path']; let path: MatcherLocation['path'];
let name: MatcherLocation['name']; let name: MatcherLocation['name'];
@ -136,7 +152,9 @@ export function createRouterMatcher(
matcher = matcherMap.get(location.name); matcher = matcherMap.get(location.name);
if (!matcher) { if (!matcher) {
throw new Error(`Router error: no match for ${JSON.stringify(location)}`); throw new Error(`
Router error: no match for ${JSON.stringify(location)}
`);
} }
name = matcher.record.name; name = matcher.record.name;
@ -145,19 +163,20 @@ export function createRouterMatcher(
paramsFromLocation( paramsFromLocation(
currentLocation.params ?? {}, currentLocation.params ?? {},
matcher.keys matcher.keys
.filter((k) => { .filter(k => !k.optional)
return !(k.modifier === '?' || k.modifier === '*'); .concat(
}) matcher.parent ? matcher.parent.keys.filter(k => k.optional) : []
)
.map((k) => k.name), .map((k) => k.name),
), ),
location.params location.params
? paramsFromLocation( ? paramsFromLocation(
location.params, location.params,
matcher.keys.map((k) => k.name), matcher.keys.map((k) => k.name),
) )
: {}, : {},
); );
// throws if cannot be stringified
path = matcher.stringify(params); path = matcher.stringify(params);
} else if ('path' in location) { } else if ('path' in location) {
path = location.path; path = location.path;
@ -214,8 +233,8 @@ export function createRouterMatcher(
}; };
} }
function paramsFromLocation(params: RouteParams, keys: (string | number)[]): RouteParams { function paramsFromLocation(params: PathParams, keys: (string | number)[]): PathParams {
const newParams = {} as RouteParams; const newParams = {} as PathParams;
for (const key of keys) { for (const key of keys) {
if (key in params) newParams[key] = params[key]; if (key in params) newParams[key] = params[key];
} }
@ -233,3 +252,12 @@ export function normalizeRouteRecord(record: RouteRecord): RouteRecordNormalized
children: record.children || [], children: record.children || [],
}; };
} }
function isRecordChildOf(
record: RouteRecordMatcher,
parent: RouteRecordMatcher
): boolean {
return parent.children.some(
child => child === record || isRecordChildOf(record, child)
);
}

View File

@ -2,7 +2,7 @@ import {
type RouterApi, type RouterApi,
type RouterConfig, type RouterConfig,
type RouteLocation, type RouteLocation,
useEvent, createEvent,
type RawRouteLocation, type RawRouteLocation,
type RawLocationOptions, type RawLocationOptions,
} from '@alilc/lowcode-renderer-core'; } from '@alilc/lowcode-renderer-core';
@ -14,10 +14,10 @@ import {
type HistoryState, type HistoryState,
} from './history'; } from './history';
import { createRouterMatcher } from './matcher'; import { createRouterMatcher } from './matcher';
import { type PathParserOptions } from './utils/path-parser'; import { type PathParserOptions, type PathParams } from './utils/path-parser';
import { parseURL, stringifyURL } from './utils/url'; import { parseURL, stringifyURL } from './utils/url';
import { isSameRouteLocation } from './utils/helper'; import { isSameRouteLocation } from './utils/helper';
import type { RouteParams, RouteRecord, RouteLocationNormalized } from './types'; import type { RouteRecord, RouteLocationNormalized } from './types';
import { type NavigationHookAfter, type NavigationGuard, guardToPromiseFn } from './guard'; import { type NavigationHookAfter, type NavigationGuard, guardToPromiseFn } from './guard';
export interface RouterOptions extends RouterConfig, PathParserOptions { export interface RouterOptions extends RouterConfig, PathParserOptions {
@ -72,8 +72,8 @@ export function createRouter(options: RouterOptions = defaultRouterOptions): Rou
? createMemoryHistory(baseName) ? createMemoryHistory(baseName)
: createBrowserHistory(baseName); : createBrowserHistory(baseName);
const beforeGuards = useEvent<NavigationGuard>(); const beforeGuards = createEvent<NavigationGuard>();
const afterGuards = useEvent<NavigationHookAfter>(); const afterGuards = createEvent<NavigationHookAfter>();
let currentLocation: RouteLocationNormalized = START_LOCATION; let currentLocation: RouteLocationNormalized = START_LOCATION;
let pendingLocation = currentLocation; let pendingLocation = currentLocation;
@ -114,9 +114,9 @@ export function createRouter(options: RouterOptions = defaultRouterOptions): Rou
matcherLocation = { matcherLocation = {
...rawLocation, ...rawLocation,
params: rawLocation.params as RouteParams, params: rawLocation.params as PathParams,
}; };
currentLocation.params = currentLocation.params; currentLocation.params = matcherLocation.params;
} }
const matchedRoute = matcher.resolve(matcherLocation, currentLocation); const matchedRoute = matcher.resolve(matcherLocation, currentLocation);
@ -256,7 +256,7 @@ export function createRouter(options: RouterOptions = defaultRouterOptions): Rou
to: RouteLocationNormalized, to: RouteLocationNormalized,
from: RouteLocationNormalized, from: RouteLocationNormalized,
): Promise<any> { ): Promise<any> {
let guards: ((...args: any[]) => Promise<any>)[] = []; const guards: ((...args: any[]) => Promise<any>)[] = [];
const canceledNavigationCheck = async (): Promise<any> => { const canceledNavigationCheck = async (): Promise<any> => {
if (pendingLocation !== to) { if (pendingLocation !== to) {
@ -265,19 +265,14 @@ export function createRouter(options: RouterOptions = defaultRouterOptions): Rou
return Promise.resolve(); return Promise.resolve();
}; };
try { const beforeGuardsList = beforeGuards.list();
guards = [];
const beforeGuardsList = beforeGuards.list();
for (const guard of beforeGuardsList) { for (const guard of beforeGuardsList) {
guards.push(guardToPromiseFn(guard, to, from)); guards.push(guardToPromiseFn(guard, to, from));
}
if (beforeGuardsList.length > 0) guards.push(canceledNavigationCheck);
return guards.reduce((promise, guard) => promise.then(() => guard()), Promise.resolve());
} catch (err) {
throw err;
} }
if (beforeGuardsList.length > 0) guards.push(canceledNavigationCheck);
return guards.reduce((promise, guard) => promise.then(() => guard()), Promise.resolve());
} }
function finalizeNavigation( function finalizeNavigation(

View File

@ -18,5 +18,3 @@ export interface RouteRecord extends RouterRecordSpec, PathParserOptions {
export interface RouteLocationNormalized extends RouteLocation { export interface RouteLocationNormalized extends RouteLocation {
matched: RouteRecord[]; matched: RouteRecord[];
} }
export type RouteParams = Record<string, string | string[]>;

View File

@ -1,53 +0,0 @@
import {
pathToRegexp,
match,
compile,
type Key,
type TokensToRegexpOptions,
} from 'path-to-regexp';
import type { RouteParams } from '../types';
export interface PathParser {
re: RegExp;
/**
* optional = token.modifier === "?" || token.modifier === "*";
* repeat = token.modifier === "*" || token.modifier === "+";
*/
keys: Key[];
/**
*
*/
parse: (path: string) => RouteParams | undefined;
stringify: (params: RouteParams) => string;
}
export type PathParserOptions = Pick<
TokensToRegexpOptions,
'end' | 'strict' | 'sensitive'
>;
export function createPathParser(path: string, options: PathParserOptions) {
if (!path.startsWith('/')) {
throw new Error(
`Route paths should start with a "/": "${path}" should be "/${path}".`
);
}
const keys: Key[] = [];
const re = pathToRegexp(path, keys, options);
const parse = match(path);
const stringify = compile(path, { encode: encodeURIComponent });
return {
re,
keys,
parse: (path: string) => {
const parsed = parse(path);
if (!parsed) return undefined;
return parsed.params as RouteParams;
},
stringify: (params: RouteParams) => {
return stringify(params);
},
};
}

View File

@ -0,0 +1,9 @@
import { type PathParserOptions, tokensToParser } from './parser-ranker';
import { tokenizePath } from './path-tokenizer';
export function createPathParser(path: string, options?: PathParserOptions) {
return tokensToParser(tokenizePath(path), options);
}
export { comparePathParserScore } from './parser-ranker';
export type { PathParser, PathParams, PathParserOptions } from './parser-ranker';

View File

@ -0,0 +1,370 @@
// fork from https://github.com/vuejs/router/blob/main/packages/router/src/matcher/pathParserRanker.ts
import { Token, TokenType } from './path-tokenizer';
export type PathParams = Record<string, string | string[]>;
/**
* A param in a url like `/users/:id`
*/
interface PathParserParamKey {
name: string
repeatable: boolean
optional: boolean
}
export interface PathParser {
/**
* The regexp used to match a url
*/
re: RegExp
/**
* The score of the parser
*/
score: Array<number[]>
/**
* Keys that appeared in the path
*/
keys: PathParserParamKey[]
/**
* Parses a url and returns the matched params or null if it doesn't match. An
* optional param that isn't preset will be an empty string. A repeatable
* param will be an array if there is at least one value.
*
* @param path - url to parse
* @returns a Params object, empty if there are no params. `null` if there is
* no match
*/
parse(path: string): PathParams | null
/**
* Creates a string version of the url
*
* @param params - object of params
* @returns a url
*/
stringify(params: PathParams): string
}
/**
* @internal
*/
export interface _PathParserOptions {
/**
* Makes the RegExp case-sensitive.
*
* @defaultValue `false`
*/
sensitive?: boolean
/**
* Whether to disallow a trailing slash or not.
*
* @defaultValue `false`
*/
strict?: boolean
/**
* Should the RegExp match from the beginning by prepending a `^` to it.
* @internal
*
* @defaultValue `true`
*/
start?: boolean
/**
* Should the RegExp match until the end by appending a `$` to it.
*
* @defaultValue `true`
*/
end?: boolean
}
export type PathParserOptions = Pick<
_PathParserOptions,
'end' | 'sensitive' | 'strict'
>;
// default pattern for a param: non-greedy everything but /
const BASE_PARAM_PATTERN = '[^/]+?';
const BASE_PATH_PARSER_OPTIONS: Required<_PathParserOptions> = {
sensitive: false,
strict: false,
start: true,
end: true,
};
// Scoring values used in tokensToParser
const enum PathScore {
_multiplier = 10,
Root = 9 * _multiplier, // just /
Segment = 4 * _multiplier, // /a-segment
SubSegment = 3 * _multiplier, // /multiple-:things-in-one-:segment
Static = 4 * _multiplier, // /static
Dynamic = 2 * _multiplier, // /:someId
BonusCustomRegExp = 1 * _multiplier, // /:someId(\\d+)
BonusWildcard = -4 * _multiplier - BonusCustomRegExp, // /:namedWildcard(.*) we remove the bonus added by the custom regexp
BonusRepeatable = -2 * _multiplier, // /:w+ or /:w*
BonusOptional = -0.8 * _multiplier, // /:w? or /:w*
// these two have to be under 0.1 so a strict /:page is still lower than /:a-:b
BonusStrict = 0.07 * _multiplier, // when options strict: true is passed, as the regex omits \/?
BonusCaseSensitive = 0.025 * _multiplier, // when options strict: true is passed, as the regex omits \/?
}
// Special Regex characters that must be escaped in static tokens
const REGEX_CHARS_RE = /[.+*?^${}()[\]/\\]/g;
/**
* Creates a path parser from an array of Segments (a segment is an array of Tokens)
*
* @param segments - array of segments returned by tokenizePath
* @param extraOptions - optional options for the regexp
* @returns a PathParser
*/
export function tokensToParser(
segments: Array<Token[]>,
extraOptions?: _PathParserOptions
): PathParser {
const options = Object.assign({}, BASE_PATH_PARSER_OPTIONS, extraOptions);
// the amount of scores is the same as the length of segments except for the root segment "/"
const score: Array<number[]> = [];
// the regexp as a string
let pattern = options.start ? '^' : '';
// extracted keys
const keys: PathParserParamKey[] = [];
for (const segment of segments) {
// the root segment needs special treatment
const segmentScores: number[] = segment.length ? [] : [PathScore.Root];
// allow trailing slash
if (options.strict && !segment.length) pattern += '/';
for (let tokenIndex = 0; tokenIndex < segment.length; tokenIndex++) {
const token = segment[tokenIndex];
// resets the score if we are inside a sub-segment /:a-other-:b
let subSegmentScore: number =
PathScore.Segment +
(options.sensitive ? PathScore.BonusCaseSensitive : 0);
if (token.type === TokenType.Static) {
// prepend the slash if we are starting a new segment
if (!tokenIndex) pattern += '/';
pattern += token.value.replace(REGEX_CHARS_RE, '\\$&');
subSegmentScore += PathScore.Static;
} else if (token.type === TokenType.Param) {
const { value, repeatable, optional, regexp } = token;
keys.push({
name: value,
repeatable,
optional,
});
const re = regexp ? regexp : BASE_PARAM_PATTERN;
// the user provided a custom regexp /:id(\\d+)
if (re !== BASE_PARAM_PATTERN) {
subSegmentScore += PathScore.BonusCustomRegExp;
// make sure the regexp is valid before using it
try {
new RegExp(`(${re})`);
} catch (err) {
throw new Error(
`Invalid custom RegExp for param "${value}" (${re}): ` +
(err as Error).message
);
}
}
// when we repeat we must take care of the repeating leading slash
let subPattern = repeatable ? `((?:${re})(?:/(?:${re}))*)` : `(${re})`;
// prepend the slash if we are starting a new segment
if (!tokenIndex)
subPattern =
// avoid an optional / if there are more segments e.g. /:p?-static
// or /:p?-:p2
optional && segment.length < 2
? `(?:/${subPattern})`
: '/' + subPattern;
if (optional) subPattern += '?';
pattern += subPattern;
subSegmentScore += PathScore.Dynamic;
if (optional) subSegmentScore += PathScore.BonusOptional;
if (repeatable) subSegmentScore += PathScore.BonusRepeatable;
if (re === '.*') subSegmentScore += PathScore.BonusWildcard;
}
segmentScores.push(subSegmentScore);
}
// an empty array like /home/ -> [[{home}], []]
// if (!segment.length) pattern += '/'
score.push(segmentScores);
}
// only apply the strict bonus to the last score
if (options.strict && options.end) {
const i = score.length - 1;
score[i][score[i].length - 1] += PathScore.BonusStrict;
}
// TODO: dev only warn double trailing slash
if (!options.strict) pattern += '/?';
if (options.end) pattern += '$';
// allow paths like /dynamic to only match dynamic or dynamic/... but not dynamic_something_else
else if (options.strict) pattern += '(?:/|$)';
const re = new RegExp(pattern, options.sensitive ? '' : 'i');
function parse(path: string): PathParams | null {
const match = path.match(re);
const params: PathParams = {};
if (!match) return null;
for (let i = 1; i < match.length; i++) {
const value: string = match[i] || '';
const key = keys[i - 1];
params[key.name] = value && key.repeatable ? value.split('/') : value;
}
return params;
}
function stringify(params: PathParams): string {
let path = '';
// for optional parameters to allow to be empty
let avoidDuplicatedSlash: boolean = false;
for (const segment of segments) {
if (!avoidDuplicatedSlash || !path.endsWith('/')) path += '/';
avoidDuplicatedSlash = false;
for (const token of segment) {
if (token.type === TokenType.Static) {
path += token.value;
} else if (token.type === TokenType.Param) {
const { value, repeatable, optional } = token;
const param: string | readonly string[] =
value in params ? params[value] : '';
if (Array.isArray(param) && !repeatable) {
throw new Error(
`Provided param "${value}" is an array but it is not repeatable (* or + modifiers)`
);
}
const text: string = Array.isArray(param)
? (param as string[]).join('/')
: (param as string);
if (!text) {
if (optional) {
// if we have more than one optional param like /:a?-static we don't need to care about the optional param
if (segment.length < 2) {
// remove the last slash as we could be at the end
if (path.endsWith('/')) path = path.slice(0, -1);
// do not append a slash on the next iteration
else avoidDuplicatedSlash = true;
}
} else throw new Error(`Missing required param "${value}"`);
}
path += text;
}
}
}
// avoid empty path when we have multiple optional params
return path || '/';
}
return {
re,
score,
keys,
parse,
stringify,
};
}
/**
* Compares an array of numbers as used in PathParser.score and returns a
* number. This function can be used to `sort` an array
*
* @param a - first array of numbers
* @param b - second array of numbers
* @returns 0 if both are equal, < 0 if a should be sorted first, > 0 if b
* should be sorted first
*/
function compareScoreArray(a: number[], b: number[]): number {
let i = 0;
while (i < a.length && i < b.length) {
const diff = b[i] - a[i];
// only keep going if diff === 0
if (diff) return diff;
i++;
}
// if the last subsegment was Static, the shorter segments should be sorted first
// otherwise sort the longest segment first
if (a.length < b.length) {
return a.length === 1 && a[0] === PathScore.Static + PathScore.Segment
? -1
: 1;
} else if (a.length > b.length) {
return b.length === 1 && b[0] === PathScore.Static + PathScore.Segment
? 1
: -1;
}
return 0;
}
/**
* Compare function that can be used with `sort` to sort an array of PathParser
*
* @param a - first PathParser
* @param b - second PathParser
* @returns 0 if both are equal, < 0 if a should be sorted first, > 0 if b
*/
export function comparePathParserScore(a: PathParser, b: PathParser): number {
let i = 0;
const aScore = a.score;
const bScore = b.score;
while (i < aScore.length && i < bScore.length) {
const comp = compareScoreArray(aScore[i], bScore[i]);
// do not return if both are equal
if (comp) return comp;
i++;
}
if (Math.abs(bScore.length - aScore.length) === 1) {
if (isLastScoreNegative(aScore)) return 1;
if (isLastScoreNegative(bScore)) return -1;
}
// if a and b share the same score entries but b has more, sort b first
return bScore.length - aScore.length;
// this is the ternary version
// return aScore.length < bScore.length
// ? 1
// : aScore.length > bScore.length
// ? -1
// : 0
}
/**
* This allows detecting splats at the end of a path: /home/:id(.*)*
*
* @param score - score to check
* @returns true if the last entry is negative
*/
function isLastScoreNegative(score: PathParser['score']): boolean {
const last = score[score.length - 1];
return score.length > 0 && last[last.length - 1] < 0;
}

View File

@ -0,0 +1,200 @@
// fork from https://github.com/vuejs/router/blob/main/packages/router/src/matcher/pathTokenizer.ts
export const enum TokenType {
Static,
Param,
Group,
}
const enum TokenizerState {
Static,
Param,
ParamRegExp, // custom re for a param
ParamRegExpEnd, // check if there is any ? + *
EscapeNext,
}
interface TokenStatic {
type: TokenType.Static
value: string
}
interface TokenParam {
type: TokenType.Param
regexp?: string
value: string
optional: boolean
repeatable: boolean
}
interface TokenGroup {
type: TokenType.Group
value: Exclude<Token, TokenGroup>[]
}
export type Token = TokenStatic | TokenParam | TokenGroup;
const ROOT_TOKEN: Token = {
type: TokenType.Static,
value: '',
};
const VALID_PARAM_RE = /[a-zA-Z0-9_]/;
// After some profiling, the cache seems to be unnecessary because tokenizePath
// (the slowest part of adding a route) is very fast
// const tokenCache = new Map<string, Token[][]>()
export function tokenizePath(path: string): Array<Token[]> {
if (!path) return [[]];
if (path === '/') return [[ROOT_TOKEN]];
if (!path.startsWith('/')) {
throw new Error(
`Route paths should start with a "/": "${path}" should be "/${path}".`
);
}
// if (tokenCache.has(path)) return tokenCache.get(path)!
function crash(message: string) {
throw new Error(`ERR (${state})/"${buffer}": ${message}`);
}
let state: TokenizerState = TokenizerState.Static;
let previousState: TokenizerState = state;
const tokens: Array<Token[]> = [];
// the segment will always be valid because we get into the initial state
// with the leading /
let segment!: Token[];
function finalizeSegment() {
if (segment) tokens.push(segment);
segment = [];
}
// index on the path
let i = 0;
// char at index
let char: string;
// buffer of the value read
let buffer: string = '';
// custom regexp for a param
let customRe: string = '';
function consumeBuffer() {
if (!buffer) return;
if (state === TokenizerState.Static) {
segment.push({
type: TokenType.Static,
value: buffer,
});
} else if (
state === TokenizerState.Param ||
state === TokenizerState.ParamRegExp ||
state === TokenizerState.ParamRegExpEnd
) {
if (segment.length > 1 && (char === '*' || char === '+'))
crash(
`A repeatable param (${buffer}) must be alone in its segment. eg: '/:ids+.`
);
segment.push({
type: TokenType.Param,
value: buffer,
regexp: customRe,
repeatable: char === '*' || char === '+',
optional: char === '*' || char === '?',
});
} else {
crash('Invalid state to consume buffer');
}
buffer = '';
}
function addCharToBuffer() {
buffer += char;
}
while (i < path.length) {
char = path[i++];
if (char === '\\' && state !== TokenizerState.ParamRegExp) {
previousState = state;
state = TokenizerState.EscapeNext;
continue;
}
switch (state) {
case TokenizerState.Static:
if (char === '/') {
if (buffer) {
consumeBuffer();
}
finalizeSegment();
} else if (char === ':') {
consumeBuffer();
state = TokenizerState.Param;
} else {
addCharToBuffer();
}
break;
case TokenizerState.EscapeNext:
addCharToBuffer();
state = previousState;
break;
case TokenizerState.Param:
if (char === '(') {
state = TokenizerState.ParamRegExp;
} else if (VALID_PARAM_RE.test(char)) {
addCharToBuffer();
} else {
consumeBuffer();
state = TokenizerState.Static;
// go back one character if we were not modifying
if (char !== '*' && char !== '?' && char !== '+') i--;
}
break;
case TokenizerState.ParamRegExp:
// TODO: is it worth handling nested regexp? like :p(?:prefix_([^/]+)_suffix)
// it already works by escaping the closing )
// https://paths.esm.dev/?p=AAMeJbiAwQEcDKbAoAAkP60PG2R6QAvgNaA6AFACM2ABuQBB#
// is this really something people need since you can also write
// /prefix_:p()_suffix
if (char === ')') {
// handle the escaped )
if (customRe[customRe.length - 1] == '\\')
customRe = customRe.slice(0, -1) + char;
else state = TokenizerState.ParamRegExpEnd;
} else {
customRe += char;
}
break;
case TokenizerState.ParamRegExpEnd:
// same as finalizing a param
consumeBuffer();
state = TokenizerState.Static;
// go back one character if we were not modifying
if (char !== '*' && char !== '?' && char !== '+') i--;
customRe = '';
break;
default:
crash('Unknown state');
break;
}
}
if (state === TokenizerState.ParamRegExp)
crash(`Unfinished custom RegExp for param "${buffer}"`);
consumeBuffer();
finalizeSegment();
// tokenCache.set(path, tokens)
return tokens;
}

View File

@ -4,5 +4,6 @@ import baseConfigFn from '../../vite.base.config'
export default defineConfig(async () => { export default defineConfig(async () => {
return baseConfigFn({ return baseConfigFn({
name: 'LowCodeRuntimeRouter', name: 'LowCodeRuntimeRouter',
defaultFormats: ['es', 'cjs']
}) })
}); });

View File

@ -0,0 +1,5 @@
import { defineProject } from 'vitest/config'
export default defineProject({
test: {}
})

View File

@ -30,9 +30,9 @@ export interface IPublicApiCommonUtils {
* @returns {(IPublicTypeNodeSchema | undefined)} * @returns {(IPublicTypeNodeSchema | undefined)}
*/ */
getNodeSchemaById( getNodeSchemaById(
schema: IPublicTypeNodeSchema, schema: IPublicTypeNodeSchema,
nodeId: string, nodeId: string,
): IPublicTypeNodeSchema | undefined; ): IPublicTypeNodeSchema | undefined;
// TODO: add comments // TODO: add comments
getConvertedExtraKey(key: string): string; getConvertedExtraKey(key: string): string;

View File

@ -18,10 +18,10 @@ export interface IPublicApiHotkey {
* @param action * @param action
*/ */
bind( bind(
combos: string[] | string, combos: string[] | string,
callback: IPublicTypeHotkeyCallback, callback: IPublicTypeHotkeyCallback,
action?: string, action?: string,
): IPublicTypeDisposable; ): IPublicTypeDisposable;
/** /**
* *

View File

@ -112,9 +112,9 @@ export interface IPublicApiMaterial {
* @param handle * @param handle
*/ */
modifyBuiltinComponentAction( modifyBuiltinComponentAction(
actionName: string, actionName: string,
handle: (action: IPublicTypeComponentAction) => void, handle: (action: IPublicTypeComponentAction) => void,
): void; ): void;
/** /**
* assets * assets

View File

@ -30,8 +30,8 @@ export interface IPublicApiPlugins {
* use this to get preference config for this plugin when engine.init() called * use this to get preference config for this plugin when engine.init() called
*/ */
getPluginPreference( getPluginPreference(
pluginName: string, pluginName: string,
): Record<string, IPublicTypePreferenceValueType> | null | undefined; ): Record<string, IPublicTypePreferenceValueType> | null | undefined;
/** /**
* *

Some files were not shown because too many files have changed in this diff Show More