From e8c30dd32b2c0ca86785390445f611bdee74fd5a Mon Sep 17 00:00:00 2001 From: c00872571 Date: Fri, 11 Jul 2025 11:22:55 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9Einula-adapter?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E9=80=82=E9=85=8Dvue=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + packages/inula-adapter/.gitignore | 5 + packages/inula-adapter/.prettierrc.js | 32 + packages/inula-adapter/README.md | 7 + packages/inula-adapter/babel.config.js | 49 ++ .../inula-adapter/docs/README_global_api.md | 267 ++++++++ packages/inula-adapter/docs/README_pinia.md | 101 +++ packages/inula-adapter/docs/README_vue.md | 10 + packages/inula-adapter/docs/README_vuex.md | 188 +++++ .../examples/$children/FruitItem.jsx | 25 + .../examples/$children/FruitItem.vue | 14 + .../examples/$children/FruitList.jsx | 28 + .../examples/$children/FruitList.vue | 18 + .../examples/$el/TypewriterInput.jsx | 18 + .../examples/$el/TypewriterInput.vue | 19 + .../examples/$forceUpdate/clock.jsx | 15 + .../examples/$forceUpdate/clock.vue | 9 + .../examples/$nextTick/nextTick.jsx | 18 + .../examples/$nextTick/nextTick.vue | 12 + .../examples/$parent/list-item.jsx | 25 + .../examples/$parent/list-item.vue | 19 + .../inula-adapter/examples/$parent/list.jsx | 27 + .../inula-adapter/examples/$parent/list.vue | 27 + .../inula-adapter/examples/$refs/refresh.jsx | 24 + .../inula-adapter/examples/$refs/refresh.vue | 16 + .../inula-adapter/examples/$root/root.jsx | 13 + .../inula-adapter/examples/$root/root.vue | 14 + .../examples/$root/themeChanger.jsx | 19 + .../examples/$root/themeChanger.vue | 11 + .../inula-adapter/examples/$set/fruits.jsx | 60 ++ .../inula-adapter/examples/$set/fruits.vue | 29 + .../examples/$t - plugins/App.jsx | 7 + .../examples/$t - plugins/App.vue | 7 + .../examples/$t - plugins/i18n.js | 18 + .../examples/$t - plugins/main.js | 7 + .../examples/custom functions/main.js | 21 + .../examples/emitter/emitter.jsx | 16 + .../examples/emitter/emitter.vue | 9 + .../examples/emitter/listener.jsx | 5 + .../examples/emitter/listener.vue | 7 + packages/inula-adapter/npm/pinia/package.json | 5 + packages/inula-adapter/npm/vuex/package.json | 5 + packages/inula-adapter/package.json | 63 ++ packages/inula-adapter/scripts/build-types.js | 64 ++ .../inula-adapter/scripts/rollup.config.js | 113 ++++ packages/inula-adapter/src/pinia/index.ts | 16 + packages/inula-adapter/src/pinia/pinia.ts | 208 ++++++ packages/inula-adapter/src/pinia/types.ts | 103 +++ packages/inula-adapter/src/vue/Teleport.ts | 16 + packages/inula-adapter/src/vue/compare.ts | 54 ++ packages/inula-adapter/src/vue/condition.tsx | 52 ++ packages/inula-adapter/src/vue/directive.tsx | 116 ++++ .../src/vue/dynamicComponent.tsx | 81 +++ packages/inula-adapter/src/vue/globalAPI.tsx | 336 +++++++++ packages/inula-adapter/src/vue/helper.tsx | 120 ++++ packages/inula-adapter/src/vue/index.ts | 28 + .../inula-adapter/src/vue/injectProvide.tsx | 29 + .../src/vue/keepAlive/context.ts | 3 + .../inula-adapter/src/vue/keepAlive/index.ts | 4 + .../src/vue/keepAlive/keepAlive.tsx | 112 +++ .../src/vue/keepAlive/keeper.tsx | 59 ++ .../src/vue/keepAlive/lifeCycleHooks.ts | 34 + .../inula-adapter/src/vue/keepAlivePro.tsx | 166 +++++ packages/inula-adapter/src/vue/lifecycle.ts | 69 ++ packages/inula-adapter/src/vue/props.tsx | 130 ++++ .../src/vue/semiControlledComponent.tsx | 115 ++++ packages/inula-adapter/src/vue/sfcAPI.tsx | 94 +++ packages/inula-adapter/src/vue/types.ts | 16 + packages/inula-adapter/src/vue/useScoped.ts | 195 ++++++ packages/inula-adapter/src/vue/vShow.ts | 38 ++ packages/inula-adapter/src/vuex/index.ts | 17 + packages/inula-adapter/src/vuex/maps.ts | 161 +++++ packages/inula-adapter/src/vuex/types.ts | 108 +++ packages/inula-adapter/src/vuex/vuex.ts | 346 ++++++++++ .../tests/pinia/pinia.actions.test.ts | 139 ++++ .../tests/pinia/pinia.getters.test.ts | 97 +++ .../tests/pinia/pinia.state.test.ts | 142 ++++ .../tests/pinia/pinia.store.test.ts | 122 ++++ .../tests/pinia/pinia.storeSetup.test.ts | 156 +++++ .../tests/pinia/pinia.storeToRefs.test.ts | 141 ++++ .../inula-adapter/tests/utils/globalSetup.js | 27 + .../tests/utils/testingLibrary.js | 81 +++ .../inula-adapter/tests/vue/attrs.test.tsx | 370 ++++++++++ .../tests/vue/condition.test.tsx | 208 ++++++ .../tests/vue/directive.test.tsx | 262 +++++++ .../tests/vue/dynamicComponent.test.tsx | 68 ++ .../tests/vue/globalAPI.test.tsx | 368 ++++++++++ .../tests/vue/injectProvide.test.tsx | 111 +++ .../tests/vue/keepAlive.test.tsx | 271 ++++++++ .../tests/vue/keepAlivePro.test.tsx | 263 +++++++ .../tests/vue/liftcycle.test.tsx | 191 ++++++ .../inula-adapter/tests/vue/props.test.tsx | 194 ++++++ .../inula-adapter/tests/vue/sfcAPI.test.tsx | 129 ++++ .../tests/vuex/vuex.component.test.tsx | 116 ++++ .../tests/vuex/vuex.maps.test.tsx | 640 ++++++++++++++++++ .../tests/vuex/vuex.modules.test.ts | 480 +++++++++++++ .../tests/vuex/vuex.store.test.ts | 343 ++++++++++ packages/inula-adapter/tsconfig.json | 35 + packages/inula-adapter/tsconfig.pinia.json | 10 + packages/inula-adapter/tsconfig.vue.json | 10 + packages/inula-adapter/tsconfig.vuex.json | 10 + packages/inula-adapter/vitest.config.ts | 32 + 102 files changed, 9309 insertions(+) create mode 100644 packages/inula-adapter/.gitignore create mode 100644 packages/inula-adapter/.prettierrc.js create mode 100644 packages/inula-adapter/README.md create mode 100644 packages/inula-adapter/babel.config.js create mode 100644 packages/inula-adapter/docs/README_global_api.md create mode 100644 packages/inula-adapter/docs/README_pinia.md create mode 100644 packages/inula-adapter/docs/README_vue.md create mode 100644 packages/inula-adapter/docs/README_vuex.md create mode 100644 packages/inula-adapter/examples/$children/FruitItem.jsx create mode 100644 packages/inula-adapter/examples/$children/FruitItem.vue create mode 100644 packages/inula-adapter/examples/$children/FruitList.jsx create mode 100644 packages/inula-adapter/examples/$children/FruitList.vue create mode 100644 packages/inula-adapter/examples/$el/TypewriterInput.jsx create mode 100644 packages/inula-adapter/examples/$el/TypewriterInput.vue create mode 100644 packages/inula-adapter/examples/$forceUpdate/clock.jsx create mode 100644 packages/inula-adapter/examples/$forceUpdate/clock.vue create mode 100644 packages/inula-adapter/examples/$nextTick/nextTick.jsx create mode 100644 packages/inula-adapter/examples/$nextTick/nextTick.vue create mode 100644 packages/inula-adapter/examples/$parent/list-item.jsx create mode 100644 packages/inula-adapter/examples/$parent/list-item.vue create mode 100644 packages/inula-adapter/examples/$parent/list.jsx create mode 100644 packages/inula-adapter/examples/$parent/list.vue create mode 100644 packages/inula-adapter/examples/$refs/refresh.jsx create mode 100644 packages/inula-adapter/examples/$refs/refresh.vue create mode 100644 packages/inula-adapter/examples/$root/root.jsx create mode 100644 packages/inula-adapter/examples/$root/root.vue create mode 100644 packages/inula-adapter/examples/$root/themeChanger.jsx create mode 100644 packages/inula-adapter/examples/$root/themeChanger.vue create mode 100644 packages/inula-adapter/examples/$set/fruits.jsx create mode 100644 packages/inula-adapter/examples/$set/fruits.vue create mode 100644 packages/inula-adapter/examples/$t - plugins/App.jsx create mode 100644 packages/inula-adapter/examples/$t - plugins/App.vue create mode 100644 packages/inula-adapter/examples/$t - plugins/i18n.js create mode 100644 packages/inula-adapter/examples/$t - plugins/main.js create mode 100644 packages/inula-adapter/examples/custom functions/main.js create mode 100644 packages/inula-adapter/examples/emitter/emitter.jsx create mode 100644 packages/inula-adapter/examples/emitter/emitter.vue create mode 100644 packages/inula-adapter/examples/emitter/listener.jsx create mode 100644 packages/inula-adapter/examples/emitter/listener.vue create mode 100644 packages/inula-adapter/npm/pinia/package.json create mode 100644 packages/inula-adapter/npm/vuex/package.json create mode 100644 packages/inula-adapter/package.json create mode 100644 packages/inula-adapter/scripts/build-types.js create mode 100644 packages/inula-adapter/scripts/rollup.config.js create mode 100644 packages/inula-adapter/src/pinia/index.ts create mode 100644 packages/inula-adapter/src/pinia/pinia.ts create mode 100644 packages/inula-adapter/src/pinia/types.ts create mode 100644 packages/inula-adapter/src/vue/Teleport.ts create mode 100644 packages/inula-adapter/src/vue/compare.ts create mode 100644 packages/inula-adapter/src/vue/condition.tsx create mode 100644 packages/inula-adapter/src/vue/directive.tsx create mode 100644 packages/inula-adapter/src/vue/dynamicComponent.tsx create mode 100644 packages/inula-adapter/src/vue/globalAPI.tsx create mode 100644 packages/inula-adapter/src/vue/helper.tsx create mode 100644 packages/inula-adapter/src/vue/index.ts create mode 100644 packages/inula-adapter/src/vue/injectProvide.tsx create mode 100644 packages/inula-adapter/src/vue/keepAlive/context.ts create mode 100644 packages/inula-adapter/src/vue/keepAlive/index.ts create mode 100644 packages/inula-adapter/src/vue/keepAlive/keepAlive.tsx create mode 100644 packages/inula-adapter/src/vue/keepAlive/keeper.tsx create mode 100644 packages/inula-adapter/src/vue/keepAlive/lifeCycleHooks.ts create mode 100644 packages/inula-adapter/src/vue/keepAlivePro.tsx create mode 100644 packages/inula-adapter/src/vue/lifecycle.ts create mode 100644 packages/inula-adapter/src/vue/props.tsx create mode 100644 packages/inula-adapter/src/vue/semiControlledComponent.tsx create mode 100644 packages/inula-adapter/src/vue/sfcAPI.tsx create mode 100644 packages/inula-adapter/src/vue/types.ts create mode 100644 packages/inula-adapter/src/vue/useScoped.ts create mode 100644 packages/inula-adapter/src/vue/vShow.ts create mode 100644 packages/inula-adapter/src/vuex/index.ts create mode 100644 packages/inula-adapter/src/vuex/maps.ts create mode 100644 packages/inula-adapter/src/vuex/types.ts create mode 100644 packages/inula-adapter/src/vuex/vuex.ts create mode 100644 packages/inula-adapter/tests/pinia/pinia.actions.test.ts create mode 100644 packages/inula-adapter/tests/pinia/pinia.getters.test.ts create mode 100644 packages/inula-adapter/tests/pinia/pinia.state.test.ts create mode 100644 packages/inula-adapter/tests/pinia/pinia.store.test.ts create mode 100644 packages/inula-adapter/tests/pinia/pinia.storeSetup.test.ts create mode 100644 packages/inula-adapter/tests/pinia/pinia.storeToRefs.test.ts create mode 100644 packages/inula-adapter/tests/utils/globalSetup.js create mode 100644 packages/inula-adapter/tests/utils/testingLibrary.js create mode 100644 packages/inula-adapter/tests/vue/attrs.test.tsx create mode 100644 packages/inula-adapter/tests/vue/condition.test.tsx create mode 100644 packages/inula-adapter/tests/vue/directive.test.tsx create mode 100644 packages/inula-adapter/tests/vue/dynamicComponent.test.tsx create mode 100644 packages/inula-adapter/tests/vue/globalAPI.test.tsx create mode 100644 packages/inula-adapter/tests/vue/injectProvide.test.tsx create mode 100644 packages/inula-adapter/tests/vue/keepAlive.test.tsx create mode 100644 packages/inula-adapter/tests/vue/keepAlivePro.test.tsx create mode 100644 packages/inula-adapter/tests/vue/liftcycle.test.tsx create mode 100644 packages/inula-adapter/tests/vue/props.test.tsx create mode 100644 packages/inula-adapter/tests/vue/sfcAPI.test.tsx create mode 100644 packages/inula-adapter/tests/vuex/vuex.component.test.tsx create mode 100644 packages/inula-adapter/tests/vuex/vuex.maps.test.tsx create mode 100644 packages/inula-adapter/tests/vuex/vuex.modules.test.ts create mode 100644 packages/inula-adapter/tests/vuex/vuex.store.test.ts create mode 100644 packages/inula-adapter/tsconfig.json create mode 100644 packages/inula-adapter/tsconfig.pinia.json create mode 100644 packages/inula-adapter/tsconfig.vue.json create mode 100644 packages/inula-adapter/tsconfig.vuex.json create mode 100644 packages/inula-adapter/vitest.config.ts 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 ( +
+ + +
+ ); +} 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 @@ + + + \ 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 @@ + + + \ 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 ( + + + + + + + {filteredIdems.map(item => ( + + ))} +
IDProduct nameProduct type
+ ); +} 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 @@ + + \ 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 ( +
+ + +
+ ); +} 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 @@ + + + \ 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 ( + + ); +} 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 @@ + + + \ 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 ( +
+ +

+ + +

+
+ ); +} 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 @@ + + + \ 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 ( + + ); +} 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 @@ + + + \ 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 @@ + + + \ 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写法: