diff --git a/package.json b/package.json
index 087ef97f9..7972054ce 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,7 @@
"build:inula-intl": "pnpm -F inula-intl build",
"build:inula-request": "pnpm -F inula-request build",
"build:inula-router": "pnpm -F inula-router build",
+ "build:inula-adapter": "pnpm -F inula-adapter build",
"commitlint": "commitlint --config commitlint.config.js -e",
"version": "pnpm exec changeset version && node packages/inula/scripts/sync-version.js",
"postinstall": "husky install"
diff --git a/packages/inula-adapter/.gitignore b/packages/inula-adapter/.gitignore
new file mode 100644
index 000000000..3e75dddd8
--- /dev/null
+++ b/packages/inula-adapter/.gitignore
@@ -0,0 +1,5 @@
+/node_modules
+.idea
+.vscode
+package-lock.json
+/build
diff --git a/packages/inula-adapter/.prettierrc.js b/packages/inula-adapter/.prettierrc.js
new file mode 100644
index 000000000..c2622eaa5
--- /dev/null
+++ b/packages/inula-adapter/.prettierrc.js
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2023 Huawei Technologies Co.,Ltd.
+ *
+ * openInula is licensed under Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ *
+ * http://license.coscl.org.cn/MulanPSL2
+ *
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+
+'use strict';
+
+module.exports = {
+ printWidth: 120, // 一行120字符数,如果超过会进行换行
+ tabWidth: 2, // tab等2个空格
+ useTabs: false, // 用空格缩进行
+ semi: true, // 行尾使用分号
+ singleQuote: true, // 字符串使用单引号
+ quoteProps: 'as-needed', // 仅在需要时在对象属性添加引号
+ jsxSingleQuote: false, // 在JSX中使用双引号
+ trailingComma: 'es5', // 使用尾逗号(对象、数组等)
+ bracketSpacing: true, // 对象的括号间增加空格
+ bracketSameLine: false, // 将多行JSX元素的>放在最后一行的末尾
+ arrowParens: 'avoid', // 在唯一的arrow函数参数周围省略括号
+ vueIndentScriptAndStyle: false, // 不缩进Vue文件中的
\ No newline at end of file
diff --git a/packages/inula-adapter/examples/$children/FruitList.jsx b/packages/inula-adapter/examples/$children/FruitList.jsx
new file mode 100644
index 000000000..423a36631
--- /dev/null
+++ b/packages/inula-adapter/examples/$children/FruitList.jsx
@@ -0,0 +1,28 @@
+import FruitItem from './FruitItem.jsx';
+
+export default function (props) {
+ const fruits = ['apple', 'pear', 'motorcycle'];
+
+ function logFruit() {
+ console.log(
+ useInstance()
+ .$children.map(({ fruitName, isChecked }) => `${fruitName}:${isChecked ? '✓' : 'X'}`)
+ .join(', ') //returns string like "apple:✓, pear:X, motorcycle:✓"
+ );
+ }
+
+ return (
+
+
+ {fruits.map(fruit => (
+
+ ))}
+
+
{
+ logFruit();
+ }}
+ >
+
+ );
+}
diff --git a/packages/inula-adapter/examples/$children/FruitList.vue b/packages/inula-adapter/examples/$children/FruitList.vue
new file mode 100644
index 000000000..4dfbc958e
--- /dev/null
+++ b/packages/inula-adapter/examples/$children/FruitList.vue
@@ -0,0 +1,18 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/inula-adapter/examples/$el/TypewriterInput.jsx b/packages/inula-adapter/examples/$el/TypewriterInput.jsx
new file mode 100644
index 000000000..e3ca65d9d
--- /dev/null
+++ b/packages/inula-adapter/examples/$el/TypewriterInput.jsx
@@ -0,0 +1,18 @@
+import { playSound } from 'soundPlayer1';
+import { useInstance } from 'vue-inula';
+
+export default function (props) {
+ const instance = useInstance();
+ //use of $el must be delayed, because element representation is not available before render
+ setTimeout(() => {
+ instance.$el.addListener('keyDown', () => {
+ playSound('typewritterPush');
+ });
+
+ instance.$el.addListener('keyUp', () => {
+ playSound('typewritterRelease');
+ });
+ }, 100);
+
+ return ;
+}
diff --git a/packages/inula-adapter/examples/$el/TypewriterInput.vue b/packages/inula-adapter/examples/$el/TypewriterInput.vue
new file mode 100644
index 000000000..e376968af
--- /dev/null
+++ b/packages/inula-adapter/examples/$el/TypewriterInput.vue
@@ -0,0 +1,19 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/inula-adapter/examples/$forceUpdate/clock.jsx b/packages/inula-adapter/examples/$forceUpdate/clock.jsx
new file mode 100644
index 000000000..5496849a8
--- /dev/null
+++ b/packages/inula-adapter/examples/$forceUpdate/clock.jsx
@@ -0,0 +1,15 @@
+export default function (props) {
+ // useeffect and clearInterval operation mas no sense, but this is necessary
+ // to prevent problems between persistent vue components and temporary inula function components
+ useEffect(() => {
+ const [b, r] = useState(false); // by toggling this state object, force update is triggered
+ const interval = setInterval(() => {
+ r(!b);
+ }, 1000);
+
+ return () => {
+ clearInterval(interval);
+ };
+ });
+ return {Date.now()} ;
+}
diff --git a/packages/inula-adapter/examples/$forceUpdate/clock.vue b/packages/inula-adapter/examples/$forceUpdate/clock.vue
new file mode 100644
index 000000000..97a7c870d
--- /dev/null
+++ b/packages/inula-adapter/examples/$forceUpdate/clock.vue
@@ -0,0 +1,9 @@
+
+ {{ Date.now() }}
+
+
+
\ No newline at end of file
diff --git a/packages/inula-adapter/examples/$nextTick/nextTick.jsx b/packages/inula-adapter/examples/$nextTick/nextTick.jsx
new file mode 100644
index 000000000..b7e781dde
--- /dev/null
+++ b/packages/inula-adapter/examples/$nextTick/nextTick.jsx
@@ -0,0 +1,18 @@
+export default function (props) {
+ // inula rendering is synchronous, so this timeout appends this function at next asynchronous position to execute after render
+ useEffect(() => {
+ // this timeout should be wrapped as an adapter that way you can call only $nextClick( ... ) as usual
+ const timeout = setTimeout(() => {
+ // this must be wrapped, because at the time of render, this element does not exist yet
+ useInstance().$refs.inp.$el.attachEventListener('keyUp', e => {
+ console.log(e);
+ });
+ }, 1);
+
+ return () => {
+ clearTimeout(timeout);
+ };
+ });
+
+ return ;
+}
diff --git a/packages/inula-adapter/examples/$nextTick/nextTick.vue b/packages/inula-adapter/examples/$nextTick/nextTick.vue
new file mode 100644
index 000000000..13c348c00
--- /dev/null
+++ b/packages/inula-adapter/examples/$nextTick/nextTick.vue
@@ -0,0 +1,12 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/inula-adapter/examples/$parent/list-item.jsx b/packages/inula-adapter/examples/$parent/list-item.jsx
new file mode 100644
index 000000000..893ae9194
--- /dev/null
+++ b/packages/inula-adapter/examples/$parent/list-item.jsx
@@ -0,0 +1,25 @@
+import { useInstance } from 'vue-inula';
+
+export default function (props) {
+ const { productId, productName, productType } = props;
+
+ function filterType(type) {
+ useInstance().$parent.setFilterType(type);
+ }
+
+ return (
+
+ {productId}
+ {productName}
+
+ {
+ filterType(productType);
+ }}
+ >
+ {productType}
+
+
+
+ );
+}
diff --git a/packages/inula-adapter/examples/$parent/list-item.vue b/packages/inula-adapter/examples/$parent/list-item.vue
new file mode 100644
index 000000000..5a415b888
--- /dev/null
+++ b/packages/inula-adapter/examples/$parent/list-item.vue
@@ -0,0 +1,19 @@
+
+
+ {{ productId }}
+
+ {{ productName }}
+
+
+ {{ productType }}
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/inula-adapter/examples/$parent/list.jsx b/packages/inula-adapter/examples/$parent/list.jsx
new file mode 100644
index 000000000..7ce7b6c6b
--- /dev/null
+++ b/packages/inula-adapter/examples/$parent/list.jsx
@@ -0,0 +1,27 @@
+import TableItem from 'table-item';
+import { expose } from 'vue-inula';
+
+export default function (props) {
+ const { items } = props;
+
+ const filteredIdems = items;
+
+ function setFilterType(type) {
+ filteredItems = items.filter(item => item.type === type);
+ }
+
+ expose(setFilterType);
+
+ return (
+
+
+ ID
+ Product name
+ Product type
+
+ {filteredIdems.map(item => (
+
+ ))}
+
+ );
+}
diff --git a/packages/inula-adapter/examples/$parent/list.vue b/packages/inula-adapter/examples/$parent/list.vue
new file mode 100644
index 000000000..6d51694f5
--- /dev/null
+++ b/packages/inula-adapter/examples/$parent/list.vue
@@ -0,0 +1,27 @@
+
+
+
+
+ ID
+
+
+ Product name
+
+
+ Product type
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/inula-adapter/examples/$refs/refresh.jsx b/packages/inula-adapter/examples/$refs/refresh.jsx
new file mode 100644
index 000000000..f6fe4b1e5
--- /dev/null
+++ b/packages/inula-adapter/examples/$refs/refresh.jsx
@@ -0,0 +1,24 @@
+import { useInstance } from 'vue-inula';
+
+export default function (props) {
+ function validateAndSubmit() {
+ if (!useInstance().$refs[0].value) {
+ useInstance().$refs[0].focus();
+ } else {
+ submit();
+ }
+ }
+
+ return (
+
+
+ {
+ validateAndSubmit();
+ }}
+ >
+ submit
+
+
+ );
+}
diff --git a/packages/inula-adapter/examples/$refs/refresh.vue b/packages/inula-adapter/examples/$refs/refresh.vue
new file mode 100644
index 000000000..07238684a
--- /dev/null
+++ b/packages/inula-adapter/examples/$refs/refresh.vue
@@ -0,0 +1,16 @@
+
+
+
+ submit
+
+
+
+
\ No newline at end of file
diff --git a/packages/inula-adapter/examples/$root/root.jsx b/packages/inula-adapter/examples/$root/root.jsx
new file mode 100644
index 000000000..700753cf4
--- /dev/null
+++ b/packages/inula-adapter/examples/$root/root.jsx
@@ -0,0 +1,13 @@
+import { ref, defineExpose } from 'vue-inula';
+
+export default function (props) {
+ const theme = ref('light');
+
+ function changeTheme(color) {
+ theme.value = color;
+ }
+
+ defineExpose({ changeTheme });
+
+ return ;
+}
diff --git a/packages/inula-adapter/examples/$root/root.vue b/packages/inula-adapter/examples/$root/root.vue
new file mode 100644
index 000000000..72a5ec176
--- /dev/null
+++ b/packages/inula-adapter/examples/$root/root.vue
@@ -0,0 +1,14 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/inula-adapter/examples/$root/themeChanger.jsx b/packages/inula-adapter/examples/$root/themeChanger.jsx
new file mode 100644
index 000000000..89bf7d11a
--- /dev/null
+++ b/packages/inula-adapter/examples/$root/themeChanger.jsx
@@ -0,0 +1,19 @@
+import { useInstance } from 'vue-inula';
+
+export default function (props) {
+ const { theme } = props;
+
+ function changeTheme() {
+ useInstance().$root.changeTheme(theme);
+ }
+
+ return (
+ {
+ changeTheme();
+ }}
+ >
+ Change theme to {theme}
+
+ );
+}
diff --git a/packages/inula-adapter/examples/$root/themeChanger.vue b/packages/inula-adapter/examples/$root/themeChanger.vue
new file mode 100644
index 000000000..abdfcabf7
--- /dev/null
+++ b/packages/inula-adapter/examples/$root/themeChanger.vue
@@ -0,0 +1,11 @@
+
+ Change theme to {{ theme }}
+
+
+
\ No newline at end of file
diff --git a/packages/inula-adapter/examples/$set/fruits.jsx b/packages/inula-adapter/examples/$set/fruits.jsx
new file mode 100644
index 000000000..4b7bc20a4
--- /dev/null
+++ b/packages/inula-adapter/examples/$set/fruits.jsx
@@ -0,0 +1,60 @@
+import { reactive } from 'vue-inula';
+
+export default function (props) {
+ const fruits = reactive([
+ { name: 'apple', amount: 3 },
+ { name: 'pear', amount: 0 },
+ ]);
+
+ function decrement(fruit) {
+ fruit.amount--;
+ }
+ function increment(fruit) {
+ fruit.amount++;
+ }
+ function addFruit() {
+ const fruitName = useInstance().$refs.newFruit.value;
+ this.$set(fruits, fruits.length, { name: fruitName, amount: 0 }); // how should we do this? Will use of reactive handle this case automaticaly?
+ useInstance().$refs.newFruit.value = '';
+ }
+
+ return (
+
+
+
+
+ {
+ addFruit();
+ }}
+ >
+ Add fruit
+
+
+
+ );
+}
diff --git a/packages/inula-adapter/examples/$set/fruits.vue b/packages/inula-adapter/examples/$set/fruits.vue
new file mode 100644
index 000000000..50488f709
--- /dev/null
+++ b/packages/inula-adapter/examples/$set/fruits.vue
@@ -0,0 +1,29 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/inula-adapter/examples/$t - plugins/App.jsx b/packages/inula-adapter/examples/$t - plugins/App.jsx
new file mode 100644
index 000000000..238c7fdab
--- /dev/null
+++ b/packages/inula-adapter/examples/$t - plugins/App.jsx
@@ -0,0 +1,7 @@
+import { useInstance } from 'vue-inula';
+
+export default function (props) {
+ const introduction = useInstance().$t('introduction');
+
+ return {introduction}
;
+}
diff --git a/packages/inula-adapter/examples/$t - plugins/App.vue b/packages/inula-adapter/examples/$t - plugins/App.vue
new file mode 100644
index 000000000..cbd15cfc8
--- /dev/null
+++ b/packages/inula-adapter/examples/$t - plugins/App.vue
@@ -0,0 +1,7 @@
+
+ {{ introduction }}
+
+
+
\ No newline at end of file
diff --git a/packages/inula-adapter/examples/$t - plugins/i18n.js b/packages/inula-adapter/examples/$t - plugins/i18n.js
new file mode 100644
index 000000000..b138b0bce
--- /dev/null
+++ b/packages/inula-adapter/examples/$t - plugins/i18n.js
@@ -0,0 +1,18 @@
+import { createI18n } from 'vue-i18n';
+
+const messages = {
+ en: {
+ introduction: 'Hello, I am Dora.',
+ },
+ es: {
+ introduction: 'Ola, yo soy Dora.',
+ },
+};
+
+const i18n = createI18n({
+ locale: 'en',
+ fallbackLocale: 'en',
+ messages,
+});
+
+export default i18n;
diff --git a/packages/inula-adapter/examples/$t - plugins/main.js b/packages/inula-adapter/examples/$t - plugins/main.js
new file mode 100644
index 000000000..127eb9cac
--- /dev/null
+++ b/packages/inula-adapter/examples/$t - plugins/main.js
@@ -0,0 +1,7 @@
+import { createApp } from 'vue-inula'; //here original function is replaced with adapter!
+import App from 'App.vue';
+import i18n from './i18n';
+
+const app = createApp(App); //createst global object
+app.use(i18n); //attaches plugin to global object
+app.mount('#app'); //renders and mounts root
diff --git a/packages/inula-adapter/examples/custom functions/main.js b/packages/inula-adapter/examples/custom functions/main.js
new file mode 100644
index 000000000..aac8dc5ea
--- /dev/null
+++ b/packages/inula-adapter/examples/custom functions/main.js
@@ -0,0 +1,21 @@
+import store from './index';
+
+let installed = false;
+
+function hideLoading(oMsg) {
+ store.commit(removeLoadingMsg, oMsg);
+}
+
+export default {
+ install(app) {
+ if (installed) {
+ return;
+ }
+ installed = true;
+ // We need to decide how this can be processed to implement custom global functions
+ app.config.globalProperties.$hideLoading = hideLoading;
+ // Should we do something like this?
+ import { registerGlobal } from 'inula-vue';
+ registerGlobal('$hideLoading', hideLoading);
+ },
+};
diff --git a/packages/inula-adapter/examples/emitter/emitter.jsx b/packages/inula-adapter/examples/emitter/emitter.jsx
new file mode 100644
index 000000000..5dd300bee
--- /dev/null
+++ b/packages/inula-adapter/examples/emitter/emitter.jsx
@@ -0,0 +1,16 @@
+export default function (props) {
+ function clicked() {
+ useInstance().$refs.emitter.$emit('eventEmitterClicked');
+ }
+
+ return (
+ {
+ clicked();
+ }}
+ >
+ emit event
+
+ );
+}
diff --git a/packages/inula-adapter/examples/emitter/emitter.vue b/packages/inula-adapter/examples/emitter/emitter.vue
new file mode 100644
index 000000000..004b7fbbc
--- /dev/null
+++ b/packages/inula-adapter/examples/emitter/emitter.vue
@@ -0,0 +1,9 @@
+
+ emit event
+
+
+
\ No newline at end of file
diff --git a/packages/inula-adapter/examples/emitter/listener.jsx b/packages/inula-adapter/examples/emitter/listener.jsx
new file mode 100644
index 000000000..be6b52e29
--- /dev/null
+++ b/packages/inula-adapter/examples/emitter/listener.jsx
@@ -0,0 +1,5 @@
+export default function (props) {
+ useInstance().$refs.emitter.$on('eventEmitterClicked');
+
+ return emit event ;
+}
diff --git a/packages/inula-adapter/examples/emitter/listener.vue b/packages/inula-adapter/examples/emitter/listener.vue
new file mode 100644
index 000000000..d57c212d7
--- /dev/null
+++ b/packages/inula-adapter/examples/emitter/listener.vue
@@ -0,0 +1,7 @@
+
+ emit event
+
+
+
\ No newline at end of file
diff --git a/packages/inula-adapter/npm/pinia/package.json b/packages/inula-adapter/npm/pinia/package.json
new file mode 100644
index 000000000..0888b15d6
--- /dev/null
+++ b/packages/inula-adapter/npm/pinia/package.json
@@ -0,0 +1,5 @@
+{
+ "module": "./esm/pinia-adapter.js",
+ "main": "./cjs/pinia-adapter.js",
+ "types": "./@types/index.d.ts"
+}
diff --git a/packages/inula-adapter/npm/vuex/package.json b/packages/inula-adapter/npm/vuex/package.json
new file mode 100644
index 000000000..bf4972656
--- /dev/null
+++ b/packages/inula-adapter/npm/vuex/package.json
@@ -0,0 +1,5 @@
+{
+ "module": "./esm/vuex-adapter.js",
+ "main": "./cjs/vuex-adapter.js",
+ "types": "./@types/index.d.ts"
+}
diff --git a/packages/inula-adapter/package.json b/packages/inula-adapter/package.json
new file mode 100644
index 000000000..09bdb70dc
--- /dev/null
+++ b/packages/inula-adapter/package.json
@@ -0,0 +1,63 @@
+{
+ "name": "inula-adapter",
+ "version": "0.0.1",
+ "description": "vue adapter",
+ "main": "./vue/cjs/vue-adapter.js",
+ "module": "./vue/esm/vue-adapter.js",
+ "types": "./vue/@types/index.d.ts",
+ "files": [
+ "/vue",
+ "/pinia",
+ "/vuex",
+ "README.md"
+ ],
+ "scripts": {
+ "test-ui": "vitest --ui",
+ "test": "vitest",
+ "build": "rollup -c ./scripts/rollup.config.js && npm run build-types",
+ "build-types": "tsc -p tsconfig.vue.json && tsc -p tsconfig.pinia.json && tsc -p tsconfig.vuex.json && rollup -c ./scripts/build-types.js"
+ },
+ "dependencies": {
+ "openinula": "^0.1.14"
+ },
+ "devDependencies": {
+ "@babel/core": "7.21.3",
+ "@babel/plugin-proposal-class-properties": "7.16.7",
+ "@babel/plugin-proposal-nullish-coalescing-operator": "7.16.7",
+ "@babel/plugin-proposal-object-rest-spread": "7.16.7",
+ "@babel/plugin-proposal-optional-chaining": "7.16.7",
+ "@babel/plugin-syntax-jsx": "7.16.7",
+ "@babel/plugin-transform-arrow-functions": "7.16.7",
+ "@babel/plugin-transform-block-scoped-functions": "7.16.7",
+ "@babel/plugin-transform-block-scoping": "7.16.7",
+ "@babel/plugin-transform-classes": "7.16.7",
+ "@babel/plugin-transform-computed-properties": "7.16.7",
+ "@babel/plugin-transform-destructuring": "7.16.7",
+ "@babel/plugin-transform-for-of": "7.16.7",
+ "@babel/plugin-transform-literals": "7.16.7",
+ "@babel/plugin-transform-object-assign": "7.16.7",
+ "@babel/plugin-transform-object-super": "7.16.7",
+ "@babel/plugin-transform-parameters": "7.16.7",
+ "@babel/plugin-transform-react-jsx": "7.16.7",
+ "@babel/plugin-transform-react-jsx-source": "^7.16.7",
+ "@babel/plugin-transform-runtime": "7.16.7",
+ "@babel/plugin-transform-shorthand-properties": "7.16.7",
+ "@babel/plugin-transform-spread": "7.16.7",
+ "@babel/plugin-transform-template-literals": "7.16.7",
+ "@babel/preset-env": "7.16.7",
+ "@babel/preset-typescript": "^7.16.7",
+ "@rollup/plugin-babel": "^6.0.3",
+ "@rollup/plugin-node-resolve": "^15.1.0",
+ "@testing-library/user-event": "^12.1.10",
+ "@types/jest": "^29.5.14",
+ "@vitejs/plugin-react": "^4.2.1",
+ "@vitest/ui": "^0.34.5",
+ "jsdom": "^24.0.0",
+ "prettier": "2.8.8",
+ "rollup": "2.79.1",
+ "rollup-plugin-dts": "^6.0.1",
+ "rollup-plugin-terser": "^5.1.3",
+ "typescript": "4.9.3",
+ "vitest": "^0.34.5"
+ }
+}
diff --git a/packages/inula-adapter/scripts/build-types.js b/packages/inula-adapter/scripts/build-types.js
new file mode 100644
index 000000000..dbc9aa210
--- /dev/null
+++ b/packages/inula-adapter/scripts/build-types.js
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2023 Huawei Technologies Co.,Ltd.
+ *
+ * openInula is licensed under Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ *
+ * http://license.coscl.org.cn/MulanPSL2
+ *
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+
+import fs from 'fs';
+import path from 'path';
+import dts from 'rollup-plugin-dts';
+
+function deleteFolder(filePath) {
+ if (fs.existsSync(filePath)) {
+ if (fs.lstatSync(filePath).isDirectory()) {
+ const files = fs.readdirSync(filePath);
+ files.forEach(file => {
+ const nextFilePath = path.join(filePath, file);
+ const states = fs.lstatSync(nextFilePath);
+ if (states.isDirectory()) {
+ deleteFolder(nextFilePath);
+ } else {
+ fs.unlinkSync(nextFilePath);
+ }
+ });
+ fs.rmdirSync(filePath, { recursive: true });
+ } else if (fs.lstatSync(filePath).isFile()) {
+ fs.unlinkSync(filePath);
+ }
+ }
+}
+
+/**
+ * 删除非空文件夹
+ * @param folders {string[]}
+ * @returns {{buildEnd(): void, name: string}}
+ */
+export function cleanUp(folders) {
+ return {
+ name: 'clean-up',
+ buildEnd() {
+ folders.forEach(f => deleteFolder(f));
+ },
+ };
+}
+
+function buildTypeConfig(name) {
+ return {
+ input: [`./build/${name}/@types/${name}/index.d.ts`],
+ output: {
+ file: `./build/${name}/@types/index.d.ts`,
+ },
+ plugins: [dts(), cleanUp([`./build/${name}/@types/`])],
+ };
+}
+
+export default [buildTypeConfig('vue'), buildTypeConfig('pinia'), buildTypeConfig('vuex')];
diff --git a/packages/inula-adapter/scripts/rollup.config.js b/packages/inula-adapter/scripts/rollup.config.js
new file mode 100644
index 000000000..eb483d36c
--- /dev/null
+++ b/packages/inula-adapter/scripts/rollup.config.js
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2023 Huawei Technologies Co.,Ltd.
+ *
+ * openInula is licensed under Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ *
+ * http://license.coscl.org.cn/MulanPSL2
+ *
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+
+import path from 'path';
+import fs from 'fs';
+import babel from '@rollup/plugin-babel';
+import nodeResolve from '@rollup/plugin-node-resolve';
+import { terser } from 'rollup-plugin-terser';
+
+const rootDir = path.join(__dirname, '..');
+const outDir = path.join(rootDir, 'build');
+
+const extensions = ['.js', '.ts', '.tsx'];
+
+if (!fs.existsSync(outDir)) {
+ fs.mkdirSync(outDir, { recursive: true });
+}
+
+const getConfig = (mode, name) => {
+ const prod = mode.startsWith('prod');
+ const outputList = [
+ {
+ file: path.join(outDir, `${name}/cjs/${name}-adapter.${prod ? 'min.' : ''}js`),
+ sourcemap: 'true',
+ format: 'cjs',
+ },
+ {
+ file: path.join(outDir, `${name}/umd/${name}-adapter.${prod ? 'min.' : ''}js`),
+ name: 'VueAdapter',
+ sourcemap: 'true',
+ format: 'umd',
+ },
+ ];
+ if (!prod) {
+ outputList.push({
+ file: path.join(outDir, `${name}/esm/${name}-adapter.js`),
+ sourcemap: 'true',
+ format: 'esm',
+ });
+ }
+ return {
+ input: path.join(rootDir, `/src/${name}/index.ts`),
+ output: outputList,
+ plugins: [
+ nodeResolve({
+ extensions,
+ modulesOnly: true,
+ }),
+ babel({
+ exclude: 'node_modules/**',
+ configFile: path.join(rootDir, '/babel.config.js'),
+ babelHelpers: 'runtime',
+ extensions,
+ }),
+ prod && terser(),
+ name === 'vue'
+ ? copyFiles([
+ {
+ from: path.join(rootDir, 'package.json'),
+ to: path.join(outDir, 'package.json'),
+ },
+ {
+ from: path.join(rootDir, 'README.md'),
+ to: path.join(outDir, 'README.md'),
+ },
+ ])
+ : copyFiles([
+ {
+ from: path.join(rootDir, `npm/${name}/package.json`),
+ to: path.join(outDir, `${name}/package.json`),
+ },
+ ]),
+ ],
+ };
+};
+
+function copyFiles(copyPairs) {
+ return {
+ name: 'copy-files',
+ generateBundle() {
+ copyPairs.forEach(({ from, to }) => {
+ const destDir = path.dirname(to);
+ // 判断目标文件夹是否存在
+ if (!fs.existsSync(destDir)) {
+ // 目标文件夹不存在,创建它
+ fs.mkdirSync(destDir, { recursive: true });
+ }
+ fs.copyFileSync(from, to);
+ });
+ },
+ };
+}
+
+export default [
+ getConfig('dev', 'vue'),
+ getConfig('prod', 'vue'),
+ getConfig('dev', 'pinia'),
+ getConfig('prod', 'pinia'),
+ getConfig('dev', 'vuex'),
+ getConfig('prod', 'vuex'),
+];
diff --git a/packages/inula-adapter/src/pinia/index.ts b/packages/inula-adapter/src/pinia/index.ts
new file mode 100644
index 000000000..74e0da324
--- /dev/null
+++ b/packages/inula-adapter/src/pinia/index.ts
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2024 Huawei Technologies Co.,Ltd.
+ *
+ * openInula is licensed under Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ *
+ * http://license.coscl.org.cn/MulanPSL2
+ *
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+
+export * from './pinia';
diff --git a/packages/inula-adapter/src/pinia/pinia.ts b/packages/inula-adapter/src/pinia/pinia.ts
new file mode 100644
index 000000000..8a4c209b5
--- /dev/null
+++ b/packages/inula-adapter/src/pinia/pinia.ts
@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) 2024 Huawei Technologies Co.,Ltd.
+ *
+ * openInula is licensed under Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ *
+ * http://license.coscl.org.cn/MulanPSL2
+ *
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+import { createStore, StoreObj, vueReactive } from 'openinula';
+import {
+ FilterAction,
+ FilterComputed,
+ FilterState,
+ StoreDefinition,
+ StoreSetup,
+ Store,
+ AnyFunction,
+ ActionType,
+ StoreToRefsReturn,
+} from './types';
+
+const { ref, isRef, toRef, isReactive, isReadonly } = vueReactive;
+
+const storeMap = new Map();
+
+export function defineStore<
+ Id extends string,
+ S extends Record,
+ A extends Record,
+ C extends Record,
+>(definition: StoreDefinition): (pinia?: any) => Store;
+
+export function defineStore<
+ Id extends string,
+ S extends Record,
+ A extends Record,
+ C extends Record,
+>(id: Id, definition: Omit, 'id'>): (pinia?: any) => Store;
+
+export function defineStore>(
+ id: Id,
+ setup: StoreSetup
+): (pinia?: any) => Store, FilterAction, FilterComputed>;
+
+export function defineStore(idOrDef: any, setupOrDef?: any) {
+ let id: string;
+ let definition: StoreDefinition | StoreSetup;
+ let isSetup = false;
+
+ if (typeof idOrDef === 'string') {
+ isSetup = typeof setupOrDef === 'function';
+ id = idOrDef;
+ definition = setupOrDef;
+ } else {
+ id = idOrDef.id;
+ definition = idOrDef;
+ }
+
+ if (isSetup) {
+ return defineSetupStore(id, definition as StoreSetup);
+ } else {
+ return defineOptionsStore(id, definition as StoreDefinition);
+ }
+}
+
+/**
+ * createStore实现中会给actions增加第一个参数store,pinia不需要,所以需要去掉
+ * @param actions
+ */
+function enhanceActions(
+ actions?: ActionType, Record, Record>
+) {
+ if (!actions) {
+ return {};
+ }
+
+ return Object.fromEntries(
+ Object.entries(actions).map(([key, value]) => {
+ return [
+ key,
+ function (this: StoreObj, state: Record, ...args: any[]) {
+ return value.bind(this)(...args);
+ },
+ ];
+ })
+ );
+}
+
+function defineOptionsStore(id: string, definition: StoreDefinition) {
+ const state = definition.state ? definition.state() : {};
+ const computed = definition.getters || {};
+ const actions = enhanceActions(definition.actions) || {};
+
+ return () => {
+ if (storeMap.has(id)) {
+ return storeMap.get(id)!();
+ }
+
+ const useStore = createStore({
+ id,
+ state,
+ actions,
+ computed,
+ });
+
+ storeMap.set(id, useStore);
+
+ return useStore();
+ };
+}
+
+function defineSetupStore>(id: string, storeSetup: StoreSetup) {
+ return () => {
+ const data = storeSetup();
+ if (!data) {
+ return {};
+ }
+
+ if (storeMap.has(id)) {
+ return storeMap.get(id)!();
+ }
+
+ const state: Record = {};
+ const actions: Record = {};
+ const getters: Record = {};
+ for (const key in data) {
+ const prop = data[key];
+
+ if ((isRef(prop) && !isReadonly(prop)) || isReactive(prop)) {
+ // state
+ state[key] = prop;
+ } else if (typeof prop === 'function') {
+ // action
+ actions[key] = prop as AnyFunction;
+ } else if (isRef(prop) && isReadonly(prop)) {
+ // getters
+ getters[key] = (prop as any).getter as AnyFunction;
+ }
+ }
+
+ const useStore = createStore({
+ id,
+ state,
+ computed: getters,
+ actions: enhanceActions(actions),
+ });
+
+ storeMap.set(id, useStore);
+
+ return useStore();
+ };
+}
+
+export function mapStores<
+ S extends Record,
+ A extends Record,
+ C extends Record,
+>(...stores: (() => Store)[]): { [key: string]: () => Store } {
+ const result: { [key: string]: () => Store } = {};
+
+ stores.forEach((store: () => Store) => {
+ const expandedStore = store();
+ result[`${expandedStore.id}Store`] = () => expandedStore;
+ });
+
+ return result;
+}
+
+export function storeToRefs<
+ S extends Record,
+ A extends Record,
+ C extends Record,
+>(store: Store): StoreToRefsReturn {
+ const stateRefs = Object.fromEntries(
+ Object.entries(store.$s || {}).map(([key, value]) => {
+ return [key, ref(value)];
+ })
+ );
+
+ const getterRefs = Object.fromEntries(
+ Object.entries(store.$config.computed || {}).map(([key, value]) => {
+ const computeFn = (value as () => any).bind(store, store.$s);
+ return [key, toRef(computeFn)];
+ })
+ );
+
+ return { ...stateRefs, ...getterRefs } as StoreToRefsReturn;
+}
+
+export function createPinia() {
+ console.warn(
+ `The pinia-adapter in inula does not support the createPinia interface. Please modify your code accordingly.`
+ );
+
+ const result = {
+ install: (app: any) => {},
+ use: (plugin: any) => result,
+ state: {},
+ };
+
+ return result;
+}
diff --git a/packages/inula-adapter/src/pinia/types.ts b/packages/inula-adapter/src/pinia/types.ts
new file mode 100644
index 000000000..cf98faec9
--- /dev/null
+++ b/packages/inula-adapter/src/pinia/types.ts
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2024 Huawei Technologies Co.,Ltd.
+ *
+ * openInula is licensed under Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ *
+ * http://license.coscl.org.cn/MulanPSL2
+ *
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+
+import type { RefType, UnwrapRef, ComputedImpl } from 'openinula';
+
+export type StoreSetup> = () => R;
+
+export type AnyFunction = (...args: any[]) => any;
+
+// defineStore init type
+export interface StoreDefinition<
+ Id extends string = string,
+ S extends Record = Record,
+ A extends Record = Record,
+ C extends Record = Record,
+> {
+ id?: Id;
+ state?: () => S;
+ actions?: ActionType;
+ getters?: ComputedType;
+}
+
+// defineStore return type
+export type Store<
+ S extends Record,
+ A extends Record,
+ C extends Record,
+> = {
+ $s: S;
+ $state: S;
+ $a: ActionType;
+ $c: ComputedType;
+ $subscribe: (listener: Listener) => void;
+ $unsubscribe: (listener: Listener) => void;
+} & { [K in keyof S]: S[K] } & { [K in keyof ActionType]: ActionType [K] } & {
+ [K in keyof ComputedType]: ReturnType[K]>;
+};
+
+export type ActionType = A & ThisType & WithGetters>;
+
+type ComputedType = {
+ [K in keyof C]: AddFirstArg;
+} & ThisType & WithGetters>;
+type AddFirstArg = T extends (...args: infer A) => infer R
+ ? (state: S, ...args: A) => R
+ : T extends () => infer R
+ ? (state: S) => R
+ : T;
+
+// In Getter function, make this.xx can refer to other getters
+export type WithGetters = {
+ readonly [k in keyof G]: G[k] extends (...args: any[]) => infer R ? R : UnwrapRef;
+};
+
+type Listener = (change: any) => void;
+
+// Filter state properties
+export type FilterState> = {
+ [K in FilterStateProperties]: UnwrapRef;
+};
+type FilterStateProperties> = {
+ [K in keyof T]: T[K] extends ComputedImpl
+ ? never
+ : T[K] extends RefType
+ ? K
+ : T[K] extends Record // Reactive类型
+ ? K
+ : never;
+}[keyof T];
+
+// Filter action properties
+export type FilterAction> = {
+ [K in FilterFunctionProperties]: T[K] extends AnyFunction ? T[K] : never;
+};
+type FilterFunctionProperties> = {
+ [K in keyof T]: T[K] extends AnyFunction ? K : never;
+}[keyof T];
+
+// Filter computed properties
+export type FilterComputed> = {
+ [K in FilterComputedProperties]: T[K] extends ComputedImpl ? (T extends AnyFunction ? T : never) : never;
+};
+type FilterComputedProperties> = {
+ [K in keyof T]: T[K] extends ComputedImpl ? K : never;
+}[keyof T];
+
+export type StoreToRefsReturn, C extends Record> = {
+ [K in keyof S]: RefType;
+} & {
+ [K in keyof ComputedType]: Readonly[K]>>>;
+};
diff --git a/packages/inula-adapter/src/vue/Teleport.ts b/packages/inula-adapter/src/vue/Teleport.ts
new file mode 100644
index 000000000..502d876cb
--- /dev/null
+++ b/packages/inula-adapter/src/vue/Teleport.ts
@@ -0,0 +1,16 @@
+import { useState, useEffect, createPortal } from 'openinula';
+export function Teleport({ to, children }) {
+ const container = useState(() => {
+ document.createElement('div');
+ });
+
+ useEffect(() => {
+ to.appendChild(container);
+
+ return () => {
+ to.removeChild(container);
+ };
+ }, [to, container]);
+
+ return createPortal(children, container);
+}
diff --git a/packages/inula-adapter/src/vue/compare.ts b/packages/inula-adapter/src/vue/compare.ts
new file mode 100644
index 000000000..0008c5eaa
--- /dev/null
+++ b/packages/inula-adapter/src/vue/compare.ts
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2023 Huawei Technologies Co.,Ltd.
+ *
+ * openInula is licensed under Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ *
+ * http://license.coscl.org.cn/MulanPSL2
+ *
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+
+/**
+ * 兼容IE浏览器没有Object.is
+ */
+export function isSame(x: any, y: any) {
+ if (!(typeof Object.is === 'function')) {
+ if (x === y) {
+ // +0 != -0
+ return x !== 0 || 1 / x === 1 / y;
+ } else {
+ // NaN == NaN
+ return x !== x && y !== y;
+ }
+ } else {
+ return Object.is(x, y);
+ }
+}
+
+export function shallowCompare(paramX: any, paramY: any): boolean {
+ if (isSame(paramX, paramY)) {
+ return true;
+ }
+
+ // 对比对象
+ if (typeof paramX === 'object' && typeof paramY === 'object' && paramX !== null && paramY !== null) {
+ const keysX = Object.keys(paramX);
+ const keysY = Object.keys(paramY);
+
+ // key长度不相等时直接返回不相等
+ if (keysX.length !== keysY.length) {
+ return false;
+ }
+
+ return keysX.every(
+ (key, i) => Object.prototype.hasOwnProperty.call(paramY, key) && isSame(paramX[key], paramY[keysX[i]])
+ );
+ }
+
+ return false;
+}
diff --git a/packages/inula-adapter/src/vue/condition.tsx b/packages/inula-adapter/src/vue/condition.tsx
new file mode 100644
index 000000000..06df086c3
--- /dev/null
+++ b/packages/inula-adapter/src/vue/condition.tsx
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2024 Huawei Technologies Co.,Ltd.
+ *
+ * openInula is licensed under Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ *
+ * http://license.coscl.org.cn/MulanPSL2
+ *
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+
+import Inula, { isValidElement, Children, FC } from 'openinula';
+
+interface ConditionalProps {
+ children?: any;
+ condition: boolean;
+}
+
+export const If: FC = ({ children, condition }) => {
+ return condition ? <>{children}> : null;
+};
+
+export const ElseIf: FC = If;
+
+export const Else: FC> = ({ children }) => {
+ return <>{children}>;
+};
+
+interface ConditionalRendererProps {
+ children?: any;
+}
+
+export const ConditionalRenderer: FC = ({ children }) => {
+ const childrenArray = Children.toArray(children);
+ const renderedChild = childrenArray.find(child => {
+ if (isValidElement(child)) {
+ if (child.type === If || child.type === ElseIf) {
+ return child.props.condition;
+ }
+ if (child.type === Else) {
+ return true;
+ }
+ }
+ return false;
+ });
+
+ return renderedChild ? <>{renderedChild}> : null;
+};
diff --git a/packages/inula-adapter/src/vue/directive.tsx b/packages/inula-adapter/src/vue/directive.tsx
new file mode 100644
index 000000000..1b651952a
--- /dev/null
+++ b/packages/inula-adapter/src/vue/directive.tsx
@@ -0,0 +1,116 @@
+import { ComponentType, useCallback, useEffect, useLayoutEffect, useMemo, createElement, vueReactive } from 'openinula';
+import { useDirectives } from './globalAPI';
+
+const { useInstance } = vueReactive;
+
+/**
+ * Vue写法: