forked from opentiny/tiny-vue
feat(openinula): add openinula base structure (#1093)
* feat(openinula): add openinula base structure * feat(openinula): add openinula base structure
This commit is contained in:
parent
b5867dd81f
commit
89037170c0
|
@ -0,0 +1,13 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Opentiny openinula 组件调试</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"name": "@opentiny/openinula-docs",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc && vite build",
|
||||||
|
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@opentiny/openinula": "workspace:~",
|
||||||
|
"openinula": "^0.1.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "^18.2.14",
|
||||||
|
"@types/react-dom": "^18.2.6",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^6.12.0",
|
||||||
|
"@typescript-eslint/parser": "^6.12.0",
|
||||||
|
"@vitejs/plugin-react": "^4.0.1",
|
||||||
|
"eslint": "^8.44.0",
|
||||||
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
|
"postcss": "^8.4.16",
|
||||||
|
"typescript": "^5.0.2",
|
||||||
|
"vite": "^4.3.8",
|
||||||
|
"vite-plugin-svgr": "^3.2.0"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1,12 @@
|
||||||
|
import { Alert } from '@opentiny/openinula'
|
||||||
|
|
||||||
|
// 在这里导入组件,进行 api 调试
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<div className="app">
|
||||||
|
<Alert description="默认提示组件" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App
|
|
@ -0,0 +1,4 @@
|
||||||
|
.app {
|
||||||
|
margin: 10px;
|
||||||
|
width: 500px;
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
import Inula from 'openinula'
|
||||||
|
import App from './App.tsx'
|
||||||
|
import './main.css'
|
||||||
|
|
||||||
|
Inula.render(<App />, document.getElementById('root'))
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
import svgr from 'vite-plugin-svgr'
|
||||||
|
|
||||||
|
const alias = {
|
||||||
|
react: 'openinula',
|
||||||
|
'react-dom': 'openinula',
|
||||||
|
'react/jsx-dev-runtime': 'openinula/jsx-dev-runtime'
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [svgr(), react({ include: /\.(mdx|js|jsx|ts|tsx)$/ })],
|
||||||
|
resolve: {
|
||||||
|
alias
|
||||||
|
}
|
||||||
|
})
|
|
@ -41,7 +41,6 @@
|
||||||
"dev2:saas": "pnpm create:icon-saas && pnpm build:entry && pnpm -C examples/vue2 dev:saas",
|
"dev2:saas": "pnpm create:icon-saas && pnpm build:entry && pnpm -C examples/vue2 dev:saas",
|
||||||
"dev2.7": "pnpm build:entry && gulp themeConcat -w & pnpm -C examples/vue2.7 dev",
|
"dev2.7": "pnpm build:entry && gulp themeConcat -w & pnpm -C examples/vue2.7 dev",
|
||||||
"dev2.7:saas": "pnpm create:icon-saas && pnpm build:entry && pnpm -C examples/vue2.7 dev:saas",
|
"dev2.7:saas": "pnpm create:icon-saas && pnpm build:entry && pnpm -C examples/vue2.7 dev:saas",
|
||||||
"dev:react": "pnpm create:mapping-react && pnpm build:entry-react && pnpm -C examples/react-docs run dev",
|
|
||||||
"// ---------- 启动官网文档 ----------": "",
|
"// ---------- 启动官网文档 ----------": "",
|
||||||
"site": "pnpm build:entry && gulp themeConcat -w & pnpm -C examples/sites start",
|
"site": "pnpm build:entry && gulp themeConcat -w & pnpm -C examples/sites start",
|
||||||
"site:open": "pnpm build:entry && gulp themeConcat -w & pnpm -C examples/sites start:open",
|
"site:open": "pnpm build:entry && gulp themeConcat -w & pnpm -C examples/sites start:open",
|
||||||
|
@ -114,6 +113,8 @@
|
||||||
"ci:deployBeta": "pnpm build:ui",
|
"ci:deployBeta": "pnpm build:ui",
|
||||||
"postci:deployBeta": "lerna publish from-package --yes --dist-tag beta",
|
"postci:deployBeta": "lerna publish from-package --yes --dist-tag beta",
|
||||||
"analyse:depends": "pnpm --filter @opentiny/analyse_depends start",
|
"analyse:depends": "pnpm --filter @opentiny/analyse_depends start",
|
||||||
|
"// ---------- react 相关脚本命令 ----------": "",
|
||||||
|
"dev:react": "pnpm create:mapping-react && pnpm build:entry-react && pnpm -C examples/react-docs run dev",
|
||||||
"build:entry-react": "pnpm -C internals/cli build:entry-react",
|
"build:entry-react": "pnpm -C internals/cli build:entry-react",
|
||||||
"create:mapping-react": "pnpm -C internals/cli create:mapping-react",
|
"create:mapping-react": "pnpm -C internals/cli create:mapping-react",
|
||||||
"build:react": "pnpm -C internals/cli build:react",
|
"build:react": "pnpm -C internals/cli build:react",
|
||||||
|
@ -121,7 +122,9 @@
|
||||||
"pub:react": "pnpm --filter=\"./packages/dist-react/**\" publish --no-git-checks --access=public",
|
"pub:react": "pnpm --filter=\"./packages/dist-react/**\" publish --no-git-checks --access=public",
|
||||||
"dev:react-site": "pnpm --filter @opentiny/react-site start",
|
"dev:react-site": "pnpm --filter @opentiny/react-site start",
|
||||||
"build:react-site": "pnpm --filter @opentiny/react-site build",
|
"build:react-site": "pnpm --filter @opentiny/react-site build",
|
||||||
"prettier": "prettier --config .prettierrc --write ."
|
"prettier": "prettier --config .prettierrc --write .",
|
||||||
|
"// ---------- openinula 相关脚本命令 ----------": "",
|
||||||
|
"dev:openinula": "pnpm -C examples/openinula-docs run dev"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/composition-api": "1.2.2",
|
"@vue/composition-api": "1.2.2",
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
ignores:
|
||||||
|
- '@opentiny/openinula*'
|
|
@ -0,0 +1,9 @@
|
||||||
|
import Alert from '@opentiny/openinula-alert'
|
||||||
|
|
||||||
|
export const version = '1.0.0'
|
||||||
|
|
||||||
|
export { Alert }
|
||||||
|
|
||||||
|
export default {
|
||||||
|
Alert
|
||||||
|
} as any
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"name": "@opentiny/openinula",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.ts",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@opentiny/openinula-common": "workspace:~",
|
||||||
|
"@opentiny/openinula-alert": "workspace:~"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
import Alert from './src'
|
||||||
|
|
||||||
|
export default Alert
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"name": "@opentiny/openinula-alert",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.ts",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@opentiny/vue-renderless": "workspace:~",
|
||||||
|
"@opentiny/openinula-common": "workspace:~",
|
||||||
|
"@opentiny/openinula-icon": "workspace:~",
|
||||||
|
"@opentiny/vue-theme": "workspace:~",
|
||||||
|
"@opentiny/vue-theme-mobile": "workspace:~"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
import pc from './pc'
|
||||||
|
import mobile from './mobile'
|
||||||
|
import mobileFirst from './mobile-first'
|
||||||
|
|
||||||
|
export default function (props) {
|
||||||
|
const { tiny_mode = 'pc' } = props
|
||||||
|
|
||||||
|
const S = {
|
||||||
|
pc,
|
||||||
|
mobile,
|
||||||
|
'mobile-first': mobileFirst
|
||||||
|
}[tiny_mode]
|
||||||
|
|
||||||
|
return S(props)
|
||||||
|
}
|
|
@ -0,0 +1,185 @@
|
||||||
|
import { renderless, api } from '@opentiny/vue-renderless/alert/vue'
|
||||||
|
import { IconClose, IconSuccess, IconError, IconHelp, IconWarning, IconChevronDown } from '@opentiny/openinula-icon'
|
||||||
|
import { vc, If, Component, Slot, useSetup, useVm } from '@opentiny/openinula-common'
|
||||||
|
|
||||||
|
const $constants = {
|
||||||
|
ICON_MAP: {
|
||||||
|
success: IconSuccess,
|
||||||
|
error: IconError,
|
||||||
|
info: IconHelp,
|
||||||
|
warning: IconWarning
|
||||||
|
},
|
||||||
|
TITLE_MAP: {
|
||||||
|
success: 'ui.alert.success',
|
||||||
|
error: 'ui.alert.error',
|
||||||
|
info: 'ui.alert.info',
|
||||||
|
warning: 'ui.alert.warning'
|
||||||
|
},
|
||||||
|
CONTENT_MAXHEUGHT: 252
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Alert(props) {
|
||||||
|
const {
|
||||||
|
type = 'success',
|
||||||
|
size = 'normal',
|
||||||
|
center = false,
|
||||||
|
showIcon = true,
|
||||||
|
description = '',
|
||||||
|
slots = {},
|
||||||
|
_constants = $constants,
|
||||||
|
closable = true,
|
||||||
|
closeText,
|
||||||
|
title,
|
||||||
|
showFoldable = true,
|
||||||
|
singleLine,
|
||||||
|
scrolling
|
||||||
|
} = props
|
||||||
|
|
||||||
|
const defaultProps = {
|
||||||
|
type,
|
||||||
|
size,
|
||||||
|
center,
|
||||||
|
showIcon,
|
||||||
|
description,
|
||||||
|
slots,
|
||||||
|
_constants,
|
||||||
|
closable,
|
||||||
|
closeText,
|
||||||
|
title,
|
||||||
|
showFoldable
|
||||||
|
}
|
||||||
|
|
||||||
|
const { ref, current: vm, parent } = useVm()
|
||||||
|
|
||||||
|
const { state, handleHeaderClick } = useSetup({
|
||||||
|
props: defaultProps,
|
||||||
|
renderless,
|
||||||
|
api,
|
||||||
|
constants: _constants,
|
||||||
|
vm,
|
||||||
|
parent
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={ref}>
|
||||||
|
<If v-if={state.show}>
|
||||||
|
<div
|
||||||
|
className={vc([
|
||||||
|
'min-h-min flex py-2 sm:py-3 px-4 my-2 rounded box-border font-light sm:font-normal',
|
||||||
|
{ 'bg-color-info-secondary-subtle': type === 'info' || !type },
|
||||||
|
{ 'bg-color-error-subtler': type === 'error' },
|
||||||
|
{ 'bg-color-warning-subtler': type === 'warning' },
|
||||||
|
{ 'bg-color-success-subtler': type === 'success' },
|
||||||
|
{ 'text-center': center }
|
||||||
|
])}>
|
||||||
|
<Component
|
||||||
|
v-if={showIcon}
|
||||||
|
is={state.getIcon}
|
||||||
|
className={vc([
|
||||||
|
'h-4.5 w-4.5 mt-1 sm:mt-0.5 sm:h-6 sm:w-5 sm:h-5 fill-current',
|
||||||
|
{ 'text-color-info-secondary': type === 'info' || !type },
|
||||||
|
{ 'text-color-error': type === 'error' },
|
||||||
|
{ 'text-color-warning': type === 'warning' },
|
||||||
|
{ 'text-color-success': type === 'success' }
|
||||||
|
])}
|
||||||
|
/>
|
||||||
|
<If v-if={showFoldable}>
|
||||||
|
<div
|
||||||
|
className={vc(['flex-1 leading-6 text-sm overflow-hidden cursor-pointer', showIcon ? 'mx-2' : 'mr-2'])}>
|
||||||
|
<If v-if={size === 'large'}>
|
||||||
|
<div onClick={handleHeaderClick} className="inline-flex cursor-pointer font-medium">
|
||||||
|
<span>
|
||||||
|
<Slot name="title" slots={props.slots}>
|
||||||
|
{state.getTitle}
|
||||||
|
</Slot>
|
||||||
|
</span>
|
||||||
|
<span className="ml-2.5">
|
||||||
|
<IconChevronDown
|
||||||
|
className={vc([
|
||||||
|
'transition-transform duration-300 align-top my-1 fill-color-icon-placeholder',
|
||||||
|
state.contentVisible ? 'rotate-180' : 'rotate-0'
|
||||||
|
])}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</If>
|
||||||
|
<div
|
||||||
|
v_ref="ContentDescribe"
|
||||||
|
className={vc([
|
||||||
|
'transition-all duration-900 ease-linear',
|
||||||
|
{ 'hidden': size === 'large' && !description && !slots.description },
|
||||||
|
{ 'overflow-y-auto': state.scrollStatus },
|
||||||
|
state.contentVisible ? 'max-h-[theme(spacing.72)]' : 'max-h-0'
|
||||||
|
])}>
|
||||||
|
<div
|
||||||
|
className={vc([
|
||||||
|
{ 'truncate': singleLine },
|
||||||
|
{
|
||||||
|
'hover:animate-[leftMove_10s_linear_infinite] hover:overflow-visible cursor-pointer':
|
||||||
|
singleLine && scrolling
|
||||||
|
}
|
||||||
|
])}>
|
||||||
|
<Slot name="description" slots={props.slots}>
|
||||||
|
{description}
|
||||||
|
</Slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<If v-if={size === 'large' && (slots.default || props.children)}>
|
||||||
|
<div
|
||||||
|
v_ref="ContentDefault"
|
||||||
|
className={vc([
|
||||||
|
'transition-all duration-900 ease-linear',
|
||||||
|
{ 'pt-2': description && state.contentVisible },
|
||||||
|
{ 'overflow-y-auto': state.scrollStatus },
|
||||||
|
state.contentVisible ? 'max-h-[theme(spacing.72)]' : 'max-h-0'
|
||||||
|
])}>
|
||||||
|
<Slot {...props}></Slot>
|
||||||
|
</div>
|
||||||
|
</If>
|
||||||
|
</div>
|
||||||
|
</If>
|
||||||
|
<If v-if={!showFoldable}>
|
||||||
|
<div
|
||||||
|
className={vc(['flex-1 leading-6 text-sm overflow-hidden cursor-pointer', showIcon ? 'mx-2' : 'mr-2'])}>
|
||||||
|
<If v-if={size === 'large'}>
|
||||||
|
<div className="font-medium">
|
||||||
|
<Slot name="title" slots={props.slots}>
|
||||||
|
{state.getTitle}
|
||||||
|
</Slot>
|
||||||
|
</div>
|
||||||
|
</If>
|
||||||
|
<div className={vc([{ 'hidden': size === 'large' && !description && !slots.description }])}>
|
||||||
|
<div
|
||||||
|
className={vc([
|
||||||
|
{ 'truncate': singleLine },
|
||||||
|
{
|
||||||
|
'hover:animate-[leftMove_10s_linear_infinite] hover:overflow-visible cursor-pointer':
|
||||||
|
singleLine && scrolling
|
||||||
|
}
|
||||||
|
])}>
|
||||||
|
<Slot name="description" slots={props.slots}>
|
||||||
|
{description}
|
||||||
|
</Slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<If v-if={size === 'large' && slots.default}>
|
||||||
|
<div className="pt-2">
|
||||||
|
<Slot slots={props.slots}></Slot>
|
||||||
|
</div>
|
||||||
|
</If>
|
||||||
|
</div>
|
||||||
|
</If>
|
||||||
|
<If v-if={!closeText && closable}>
|
||||||
|
<IconClose className="h-4 w-4 mt-1 cursor-pointer fill-color-text-primary opacity-70" onClick={close} />
|
||||||
|
</If>
|
||||||
|
<If v-if={closeText && closable}>
|
||||||
|
<span onClick={close} className="leading-6 text-sm cursor-pointer">
|
||||||
|
{closeText}
|
||||||
|
</span>
|
||||||
|
</If>
|
||||||
|
</div>
|
||||||
|
</If>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
import { renderless, api } from '@opentiny/vue-renderless/alert/vue'
|
||||||
|
import { IconClose, IconSuccess, IconError, IconHelp, IconWarning } from '@opentiny/openinula-icon'
|
||||||
|
import { vc, If, Component, Slot, useSetup, useVm } from '@opentiny/openinula-common'
|
||||||
|
import '@opentiny/vue-theme-mobile/alert/index.less'
|
||||||
|
|
||||||
|
const $constants = {
|
||||||
|
ICON_MAP: {
|
||||||
|
success: IconSuccess,
|
||||||
|
error: IconError,
|
||||||
|
info: IconHelp,
|
||||||
|
warning: IconWarning
|
||||||
|
},
|
||||||
|
TITLE_MAP: {
|
||||||
|
success: 'ui.alert.success',
|
||||||
|
error: 'ui.alert.error',
|
||||||
|
info: 'ui.alert.info',
|
||||||
|
warning: 'ui.alert.warning'
|
||||||
|
},
|
||||||
|
CONTENT_MAXHEUGHT: 252
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Alert(props) {
|
||||||
|
const {
|
||||||
|
type = 'success',
|
||||||
|
size = 'normal',
|
||||||
|
showIcon = true,
|
||||||
|
closable = true,
|
||||||
|
closeText,
|
||||||
|
_constants = $constants,
|
||||||
|
description = ''
|
||||||
|
} = props
|
||||||
|
|
||||||
|
const defaultProps = Object.assign(
|
||||||
|
{
|
||||||
|
type,
|
||||||
|
size,
|
||||||
|
showIcon,
|
||||||
|
closable,
|
||||||
|
_constants
|
||||||
|
},
|
||||||
|
props
|
||||||
|
)
|
||||||
|
|
||||||
|
const { ref, current: vm, parent } = useVm()
|
||||||
|
|
||||||
|
const { state, handleClose } = useSetup({
|
||||||
|
props: defaultProps,
|
||||||
|
renderless,
|
||||||
|
api,
|
||||||
|
vm,
|
||||||
|
parent,
|
||||||
|
constants: _constants
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={ref}>
|
||||||
|
<If v-if={state.show}>
|
||||||
|
<div
|
||||||
|
className={vc([
|
||||||
|
'tiny-mobile-alert',
|
||||||
|
'tiny-mobile-alert--' + type,
|
||||||
|
'tiny-mobile-alert--' + size,
|
||||||
|
'is-center'
|
||||||
|
])}>
|
||||||
|
<Component v-if={showIcon} is={state.getIcon} className="tiny-mobile-alert__icon" />
|
||||||
|
<div className={vc([['tiny-mobile-alert__content', { 'is-hideicon': !showIcon }]])}>
|
||||||
|
<Slot {...props}>{description}</Slot>
|
||||||
|
</div>
|
||||||
|
<If v-if={!closeText && closable}>
|
||||||
|
<IconClose onClick={handleClose} className="tiny-mobile-alert__icon tiny-mobile-alert__close" />
|
||||||
|
</If>
|
||||||
|
<If v-if={closeText && closable}>
|
||||||
|
<span onClick={handleClose} className="is-custom">
|
||||||
|
{closeText}
|
||||||
|
</span>
|
||||||
|
</If>
|
||||||
|
</div>
|
||||||
|
</If>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
import { renderless, api } from '@opentiny/vue-renderless/alert/vue'
|
||||||
|
import { IconClose, IconSuccess, IconError, IconHelp, IconWarning } from '@opentiny/openinula-icon'
|
||||||
|
import '@opentiny/vue-theme/alert/index.less'
|
||||||
|
import { vc, If, Component, Slot, useSetup, useVm } from '@opentiny/openinula-common'
|
||||||
|
|
||||||
|
const $constants = {
|
||||||
|
ICON_MAP: {
|
||||||
|
success: IconSuccess,
|
||||||
|
error: IconError,
|
||||||
|
info: IconHelp,
|
||||||
|
warning: IconWarning
|
||||||
|
},
|
||||||
|
TITLE_MAP: {
|
||||||
|
success: 'ui.alert.success',
|
||||||
|
error: 'ui.alert.error',
|
||||||
|
info: 'ui.alert.info',
|
||||||
|
warning: 'ui.alert.warning'
|
||||||
|
},
|
||||||
|
CONTENT_MAXHEUGHT: 252
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Alert(props) {
|
||||||
|
const {
|
||||||
|
type = 'success',
|
||||||
|
size = 'normal',
|
||||||
|
center = false,
|
||||||
|
showIcon = true,
|
||||||
|
description = '',
|
||||||
|
slots = {},
|
||||||
|
_constants = $constants,
|
||||||
|
closable = true,
|
||||||
|
closeText,
|
||||||
|
title
|
||||||
|
} = props
|
||||||
|
|
||||||
|
const defaultProps = {
|
||||||
|
type,
|
||||||
|
size,
|
||||||
|
center,
|
||||||
|
showIcon,
|
||||||
|
description,
|
||||||
|
slots,
|
||||||
|
_constants,
|
||||||
|
closable,
|
||||||
|
closeText,
|
||||||
|
title
|
||||||
|
}
|
||||||
|
|
||||||
|
const { ref, current: vm, parent } = useVm()
|
||||||
|
|
||||||
|
const { state, handleClose } = useSetup({
|
||||||
|
props: defaultProps,
|
||||||
|
renderless,
|
||||||
|
api,
|
||||||
|
constants: _constants,
|
||||||
|
vm,
|
||||||
|
parent
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="tiny-transition-alert-fade" ref={ref}>
|
||||||
|
<If v-if={state.show}>
|
||||||
|
<div className={vc(['tiny-alert', 'tiny-alert--' + type, 'tiny-alert--' + size, center && 'is-center'])}>
|
||||||
|
<Component v-if={showIcon} is={state.getIcon} className="tiny-svg-size tiny-alert__icon" />
|
||||||
|
<div className="tiny-alert__content">
|
||||||
|
<If v-if={size === 'large'}>
|
||||||
|
<div className="tiny-alert__title">
|
||||||
|
<Slot name="title" slots={props.slots}>
|
||||||
|
{state.getTitle}
|
||||||
|
</Slot>
|
||||||
|
</div>
|
||||||
|
</If>
|
||||||
|
<div
|
||||||
|
className={vc([
|
||||||
|
'tiny-alert__description',
|
||||||
|
size === 'large' && !description && !slots.description && 'is-hide'
|
||||||
|
])}>
|
||||||
|
<Slot name="description" slots={props.slots}>
|
||||||
|
{description}
|
||||||
|
</Slot>
|
||||||
|
</div>
|
||||||
|
<If v-if={size === 'large'}>
|
||||||
|
<div className="tiny-alert__opration">
|
||||||
|
<Slot slots={props.slots} parent_child={props.children} />
|
||||||
|
</div>
|
||||||
|
</If>
|
||||||
|
</div>
|
||||||
|
<If v-if={!closeText && closable}>
|
||||||
|
<IconClose
|
||||||
|
className={vc(['tiny-svg-size', 'tiny-alert__icon', 'tiny-alert__close'])}
|
||||||
|
onClick={handleClose}
|
||||||
|
/>
|
||||||
|
</If>
|
||||||
|
<If v-if={closeText && closable}>
|
||||||
|
<span className="is-custom" onClick={handleClose}>
|
||||||
|
{closeText}
|
||||||
|
</span>
|
||||||
|
</If>
|
||||||
|
</div>
|
||||||
|
</If>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"name": "@opentiny/openinula-common",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "src/index.ts",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@opentiny/vue-renderless": "workspace:~",
|
||||||
|
"@opentiny/vue-theme": "workspace:~",
|
||||||
|
"classnames": "^2.3.2",
|
||||||
|
"openinula": "0.1.1",
|
||||||
|
"tailwind-merge": "^1.8.0",
|
||||||
|
"@vue/runtime-core": "^3.3.7"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
interface CssClassObject {
|
||||||
|
[k: string]: any
|
||||||
|
}
|
||||||
|
type CssClassArray = Array<string | CssClassObject>
|
||||||
|
export type CssClass = string | CssClassObject | CssClassArray
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简单合并 tailwind 类对象为字符串值
|
||||||
|
*
|
||||||
|
* @param cssClassObject tailwind 类对象
|
||||||
|
* @returns string
|
||||||
|
*/
|
||||||
|
const stringifyCssClassObject = (cssClassObject: CssClassObject): string => {
|
||||||
|
const allCssClass: Array<string> = []
|
||||||
|
|
||||||
|
Object.keys(cssClassObject).forEach((cssClass) => cssClassObject[cssClass] && allCssClass.push(cssClass))
|
||||||
|
|
||||||
|
return allCssClass.join('\u{20}')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简单合并 tailwind 类数组为字符串值
|
||||||
|
*
|
||||||
|
* @param cssClassArray tailwind 类数组
|
||||||
|
* @returns string
|
||||||
|
*/
|
||||||
|
const stringifyCssClassArray = (cssClassArray: CssClassArray): string => {
|
||||||
|
const allCssClass: Array<string> = []
|
||||||
|
|
||||||
|
cssClassArray.forEach((cssClass) => {
|
||||||
|
if (typeof cssClass === 'string') {
|
||||||
|
allCssClass.push(cssClass)
|
||||||
|
} else if (typeof cssClass === 'object') {
|
||||||
|
allCssClass.push(stringifyCssClassObject(cssClass))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return allCssClass.join('\u{20}')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简单合并 tailwind 类对象为字符串值,去重处理留给 tailwind-merge 处理
|
||||||
|
*
|
||||||
|
* @param {*} cssClasses tailwind 类集合
|
||||||
|
* @returns string
|
||||||
|
*/
|
||||||
|
export const stringifyCssClass = (cssClasses: Array<CssClass>): string => {
|
||||||
|
if (!cssClasses || (Array.isArray(cssClasses) && !cssClasses.length)) return ''
|
||||||
|
|
||||||
|
const allCssClass: Array<string> = []
|
||||||
|
|
||||||
|
cssClasses.forEach((cssClass) => {
|
||||||
|
if (cssClass) {
|
||||||
|
if (typeof cssClass === 'string') {
|
||||||
|
allCssClass.push(cssClass)
|
||||||
|
} else if (Array.isArray(cssClass)) {
|
||||||
|
allCssClass.push(stringifyCssClassArray(cssClass))
|
||||||
|
} else if (typeof cssClass === 'object') {
|
||||||
|
allCssClass.push(stringifyCssClassObject(cssClass))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return allCssClass.join('\u{20}')
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
import { eventBus } from './utils'
|
||||||
|
|
||||||
|
const $busMap = new Map()
|
||||||
|
|
||||||
|
export const emit =
|
||||||
|
(props) =>
|
||||||
|
(evName, ...args) => {
|
||||||
|
const openinulaEvName = 'on' + evName.substr(0, 1).toUpperCase() + evName.substr(1)
|
||||||
|
|
||||||
|
if (props[openinulaEvName] && typeof props[openinulaEvName] === 'function') {
|
||||||
|
props[openinulaEvName](...args)
|
||||||
|
} else {
|
||||||
|
const $bus = $busMap.get(props)
|
||||||
|
if ($bus) {
|
||||||
|
$bus.emit(evName, ...args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const on = (props) => (evName, callback) => {
|
||||||
|
if ($busMap.get(props)) {
|
||||||
|
const $bus = $busMap.get(props)
|
||||||
|
$bus.on(evName, callback)
|
||||||
|
} else {
|
||||||
|
const $bus = eventBus()
|
||||||
|
$bus.on(evName, callback)
|
||||||
|
$busMap.set(props, $bus)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const off = (props) => (evName, callback) => {
|
||||||
|
const $bus = $busMap.get(props)
|
||||||
|
if (!$bus) return
|
||||||
|
$bus.off(evName, callback)
|
||||||
|
}
|
||||||
|
export const once = (props) => (evName, callback) => {
|
||||||
|
let $bus = null
|
||||||
|
const onceCallback = (...args) => {
|
||||||
|
callback(...args)
|
||||||
|
$bus && $bus.off(evName, onceCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($busMap.get(props)) {
|
||||||
|
$bus = $busMap.get(props)
|
||||||
|
$bus.on(evName, onceCallback)
|
||||||
|
} else {
|
||||||
|
$bus = eventBus()
|
||||||
|
$bus.on(evName, onceCallback)
|
||||||
|
$busMap.set(props, $bus)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const emitEvent = (vm) => {
|
||||||
|
const broadcast = (vm, componentName, eventName, ...args) => {
|
||||||
|
const children = vm.$children
|
||||||
|
|
||||||
|
Array.isArray(children) &&
|
||||||
|
children.forEach((child) => {
|
||||||
|
const name = child.$options && child.$options.componentName
|
||||||
|
const component = child
|
||||||
|
|
||||||
|
if (name === componentName) {
|
||||||
|
component.emit(eventName, ...args)
|
||||||
|
// todo: 调研 component.$emitter
|
||||||
|
// component.$emitter && component.$emitter.emit(eventName, params)
|
||||||
|
} else {
|
||||||
|
broadcast(child, componentName, eventName, ...args)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
dispatch(componentName, eventName, ...args) {
|
||||||
|
let parent = vm.$parent
|
||||||
|
if (parent.type === null) return
|
||||||
|
let name = parent.$options && parent.$options.componentName
|
||||||
|
while (parent && parent.type && (!name || name !== componentName)) {
|
||||||
|
parent = parent.$parent
|
||||||
|
|
||||||
|
if (parent) name = parent.$options && parent.$options.componentName
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent) {
|
||||||
|
parent.emit(eventName, ...args)
|
||||||
|
// fix: VUE3下事件参数为数组,VUE2下事件参数不是数组,这里修改为和VUE2兼容
|
||||||
|
// parent.$emitter && parent.$emitter.emit(...[eventName].concat(params))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
broadcast(componentName, eventName, ...args) {
|
||||||
|
broadcast(vm, componentName, eventName, ...args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
import { useRef, useEffect, useState } from 'openinula'
|
||||||
|
import { compWhiteList } from './virtual-comp'
|
||||||
|
|
||||||
|
export function getFiberByDom(dom) {
|
||||||
|
const key = Object.keys(dom).find((key) => {
|
||||||
|
return (
|
||||||
|
key.startsWith('__openinulaFiber$') || // openinula 17+
|
||||||
|
key.startsWith('__openinulaInternalInstance$')
|
||||||
|
) // openinula <17
|
||||||
|
})
|
||||||
|
|
||||||
|
return dom[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultBreaker({ type }) {
|
||||||
|
if (type && typeof type !== 'string') {
|
||||||
|
return !compWhiteList.includes(type.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function traverseFiber(fiber, handler, breaker = defaultBreaker) {
|
||||||
|
if (!fiber) return
|
||||||
|
typeof handler === 'function' && handler(fiber)
|
||||||
|
Array.isArray(handler) &&
|
||||||
|
handler.forEach((task) => {
|
||||||
|
typeof task === 'function' && task(fiber)
|
||||||
|
})
|
||||||
|
traverseFiber(fiber.sibling, handler, breaker)
|
||||||
|
breaker(fiber) || traverseFiber(fiber.child, handler, breaker)
|
||||||
|
}
|
||||||
|
|
||||||
|
const parentMap = new WeakMap()
|
||||||
|
export function getParentFiber(fiber, isFirst = true, child = fiber) {
|
||||||
|
if (!fiber || !fiber.return) return null
|
||||||
|
if (parentMap.has(child)) return parentMap.get(child)
|
||||||
|
if (fiber.type && typeof fiber.type !== 'string' && !isFirst) {
|
||||||
|
parentMap.set(child, fiber)
|
||||||
|
return fiber
|
||||||
|
}
|
||||||
|
return getParentFiber(fiber.return, false, fiber)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function creatFiberCombine(fiber) {
|
||||||
|
if (!fiber) return
|
||||||
|
const refs = {}
|
||||||
|
const children = []
|
||||||
|
|
||||||
|
traverseFiber(fiber.child, [
|
||||||
|
(fiber) => {
|
||||||
|
if (typeof fiber.type === 'string' && fiber.stateNode.getAttribute('v_ref')) {
|
||||||
|
refs[fiber.stateNode.getAttribute('v_ref')] = fiber.stateNode
|
||||||
|
} else if (fiber.memoizedProps.v_ref) {
|
||||||
|
refs[fiber.memoizedProps.v_ref] = fiber
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(fiber) => {
|
||||||
|
if (fiber.type && typeof fiber.type !== 'string') {
|
||||||
|
children.push(fiber)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
return {
|
||||||
|
fiber,
|
||||||
|
refs,
|
||||||
|
children
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useFiber() {
|
||||||
|
const ref = useRef()
|
||||||
|
const [parent, setParent] = useState()
|
||||||
|
const [current, setCurrent] = useState()
|
||||||
|
useEffect(() => {
|
||||||
|
if (ref.current) {
|
||||||
|
const current_fiber = getFiberByDom(ref.current)
|
||||||
|
setParent(getParentFiber(current_fiber.return))
|
||||||
|
setCurrent(current_fiber.return)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return {
|
||||||
|
ref,
|
||||||
|
parent: creatFiberCombine(parent),
|
||||||
|
current: creatFiberCombine(current)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { useState, useRef } from 'openinula'
|
||||||
|
|
||||||
|
export function useExcuteOnce(cb, ...args) {
|
||||||
|
const isExcuted = useRef(false)
|
||||||
|
const result = useRef()
|
||||||
|
if (!isExcuted.current) {
|
||||||
|
isExcuted.current = true
|
||||||
|
result.current = cb(...args)
|
||||||
|
}
|
||||||
|
return result.current
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useReload() {
|
||||||
|
const [_, reload] = useState(0)
|
||||||
|
return () => reload((pre) => pre + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useOnceResult(func, ...args) {
|
||||||
|
const result = useRef()
|
||||||
|
if (!result.current) {
|
||||||
|
result.current = func(...args)
|
||||||
|
}
|
||||||
|
return result.current
|
||||||
|
}
|
|
@ -0,0 +1,149 @@
|
||||||
|
import { Svg } from './svg-render'
|
||||||
|
import { generateVueHooks, useVueLifeHooks } from './vue-hooks.js'
|
||||||
|
import { emitEvent } from './event.js'
|
||||||
|
import { If, Component, Slot, For, Transition } from './virtual-comp'
|
||||||
|
import { filterAttrs, vc, getElementCssClass, eventBus } from './utils.js'
|
||||||
|
import { useFiber } from './fiber.js'
|
||||||
|
import { useVm } from './vm.js'
|
||||||
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
import { stringifyCssClass } from './csscls.js'
|
||||||
|
import { useExcuteOnce, useReload, useOnceResult } from './hooks.js'
|
||||||
|
|
||||||
|
// 导入 vue 响应式系统
|
||||||
|
import { effectScope, nextTick, reactive } from '@vue/runtime-core'
|
||||||
|
import { useCreateVueInstance } from './vue-instance'
|
||||||
|
|
||||||
|
import '@opentiny/vue-theme/base/index.less'
|
||||||
|
|
||||||
|
// emitEvent, dispath, broadcast
|
||||||
|
export const $prefix = 'Tiny'
|
||||||
|
|
||||||
|
export const $props = {
|
||||||
|
'tiny_mode': String,
|
||||||
|
'tiny_mode_root': Boolean,
|
||||||
|
'tiny_template': [Function, Object],
|
||||||
|
'tiny_renderless': Function,
|
||||||
|
'tiny_theme': String,
|
||||||
|
'tiny_chart_theme': Object
|
||||||
|
}
|
||||||
|
|
||||||
|
export const mergeClass = (...cssClasses) => twMerge(stringifyCssClass(cssClasses))
|
||||||
|
|
||||||
|
const setup = ({ props, renderless, api, extendOptions = {}, classes = {}, constants, vm, parent, $bus }) => {
|
||||||
|
const render = typeof props.tiny_renderless === 'function' ? props.tiny_renderless : renderless
|
||||||
|
const { dispatch, broadcast } = emitEvent(vm)
|
||||||
|
|
||||||
|
const utils = {
|
||||||
|
vm,
|
||||||
|
parent,
|
||||||
|
emit: vm.$emit,
|
||||||
|
constants,
|
||||||
|
nextTick,
|
||||||
|
dispatch,
|
||||||
|
broadcast,
|
||||||
|
t() {},
|
||||||
|
mergeClass,
|
||||||
|
mode: props.tiny_mode
|
||||||
|
}
|
||||||
|
|
||||||
|
const sdk = render(
|
||||||
|
props,
|
||||||
|
{
|
||||||
|
...generateVueHooks({
|
||||||
|
$bus
|
||||||
|
})
|
||||||
|
},
|
||||||
|
utils,
|
||||||
|
extendOptions
|
||||||
|
)
|
||||||
|
|
||||||
|
const attrs = {
|
||||||
|
a: filterAttrs,
|
||||||
|
m: mergeClass,
|
||||||
|
vm: utils.vm,
|
||||||
|
gcls: (key) => getElementCssClass(classes, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(api)) {
|
||||||
|
api.forEach((name) => {
|
||||||
|
const value = sdk[name]
|
||||||
|
|
||||||
|
if (typeof value !== 'undefined') {
|
||||||
|
attrs[name] = value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return attrs
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useSetup = ({ props, renderless, api, extendOptions = {}, classes = {}, constants }) => {
|
||||||
|
const $bus = useOnceResult(() => eventBus())
|
||||||
|
|
||||||
|
// 刷新逻辑
|
||||||
|
const reload = useReload()
|
||||||
|
useExcuteOnce(() => {
|
||||||
|
// 1. 响应式触发 $bus 的事件
|
||||||
|
// 2. 事件响应触发组件更新
|
||||||
|
$bus.on('event:reload', reload)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 收集副作用,组件卸载自动清除副作用
|
||||||
|
const scope = useOnceResult(() => effectScope())
|
||||||
|
useExcuteOnce(() => {
|
||||||
|
$bus.on('hook:onBeforeUnmount', () => scope.stop())
|
||||||
|
})
|
||||||
|
|
||||||
|
// 创建响应式 props,每次刷新更新响应式 props
|
||||||
|
const reactiveProps = useOnceResult(() => reactive(props))
|
||||||
|
Object.assign(reactiveProps, props)
|
||||||
|
|
||||||
|
const { ref, vm } = useCreateVueInstance({
|
||||||
|
$bus,
|
||||||
|
props
|
||||||
|
})
|
||||||
|
|
||||||
|
// 执行一次 renderless
|
||||||
|
// renderless 作为 setup 的结果,最后要将结果挂在 vm 上
|
||||||
|
let setupResult = useExcuteOnce(() => {
|
||||||
|
let result
|
||||||
|
// 在 effectScope 里运行 renderless
|
||||||
|
scope.run(() => {
|
||||||
|
result = setup({
|
||||||
|
props: reactiveProps,
|
||||||
|
renderless,
|
||||||
|
api,
|
||||||
|
constants,
|
||||||
|
extendOptions,
|
||||||
|
classes,
|
||||||
|
vm,
|
||||||
|
parent,
|
||||||
|
$bus
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
|
||||||
|
// 触发生命周期
|
||||||
|
useVueLifeHooks($bus)
|
||||||
|
|
||||||
|
Object.keys(setupResult).forEach((key) => {
|
||||||
|
vm[key] = setupResult[key]
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
...setupResult,
|
||||||
|
_: {
|
||||||
|
ref,
|
||||||
|
vm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Svg, If, Component, Slot, For, Transition, vc, emitEvent, useVm, useFiber }
|
||||||
|
|
||||||
|
export * from './vue-hooks.js'
|
||||||
|
export * from './vue-props.js'
|
||||||
|
export * from './render-stack.js'
|
||||||
|
export * from './vue-instance.js'
|
||||||
|
export * from './hooks'
|
|
@ -0,0 +1,94 @@
|
||||||
|
import { useState, useRef } from 'openinula'
|
||||||
|
import { computed } from './vue-hooks'
|
||||||
|
|
||||||
|
// 响应式核心
|
||||||
|
const reactiveMap = new WeakMap()
|
||||||
|
const reactive = (staticObject, handler = {}, path = [], rootStaticObject = staticObject) => {
|
||||||
|
reactiveMap.has(staticObject) ||
|
||||||
|
reactiveMap.set(
|
||||||
|
staticObject,
|
||||||
|
new Proxy(staticObject, {
|
||||||
|
get(target, property, receiver) {
|
||||||
|
const targetVal = target[property]
|
||||||
|
if (targetVal && targetVal['v-hooks-type'] === computed) {
|
||||||
|
return targetVal.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const _path = [...path, property]
|
||||||
|
const res = typeof targetVal === 'object' ? reactive(targetVal, handler, _path, rootStaticObject) : targetVal
|
||||||
|
|
||||||
|
// 监听访问
|
||||||
|
handler.get &&
|
||||||
|
handler.get({
|
||||||
|
result: res,
|
||||||
|
root: rootStaticObject,
|
||||||
|
path: _path,
|
||||||
|
target,
|
||||||
|
property,
|
||||||
|
receiver
|
||||||
|
})
|
||||||
|
|
||||||
|
return res
|
||||||
|
},
|
||||||
|
set(target, property, value, receiver) {
|
||||||
|
const targetVal = target[property]
|
||||||
|
if (targetVal && targetVal['v-hooks-type'] === computed) {
|
||||||
|
targetVal.value = value
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const _path = [...path, property]
|
||||||
|
|
||||||
|
// 监听修改
|
||||||
|
handler.set &&
|
||||||
|
handler.set({
|
||||||
|
target,
|
||||||
|
property,
|
||||||
|
receiver,
|
||||||
|
root: rootStaticObject,
|
||||||
|
path: _path,
|
||||||
|
newVal: value,
|
||||||
|
oldVal: target[property]
|
||||||
|
})
|
||||||
|
|
||||||
|
target[property] = value
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
return reactiveMap.get(staticObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useReload = () => {
|
||||||
|
const setReload = useState(0)[1]
|
||||||
|
return () => setReload((pre) => pre + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const isObject = (val) => val !== null && typeof val === 'object'
|
||||||
|
|
||||||
|
// 用于从 hooks 链表中查找 reactive 生成的 state
|
||||||
|
export function Reactive(obj) {
|
||||||
|
Object.keys(obj).forEach((key) => {
|
||||||
|
this[key] = obj[key]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useReactive = (initalObject) => {
|
||||||
|
if (!isObject(initalObject)) {
|
||||||
|
return initalObject
|
||||||
|
}
|
||||||
|
const memoried = useRef()
|
||||||
|
const proxy = useRef()
|
||||||
|
const reload = useReload()
|
||||||
|
|
||||||
|
if (!memoried.current && !proxy.current) {
|
||||||
|
memoried.current = new Reactive(initalObject)
|
||||||
|
proxy.current = reactive(memoried.current, {
|
||||||
|
set() {
|
||||||
|
reload()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return proxy.current
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
const renderStack = []
|
||||||
|
|
||||||
|
export const getParent = () => renderStack[renderStack.length - 1] || {}
|
||||||
|
|
||||||
|
export const getRoot = () => renderStack[0] || {}
|
||||||
|
|
||||||
|
export const EnterStack = (props) => {
|
||||||
|
renderStack.push(props)
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LeaveStack = () => {
|
||||||
|
renderStack.pop()
|
||||||
|
return ''
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
// todo: 一个方法去拿到 props 身上的事件,以 on 为前缀
|
||||||
|
const openinulaEventPrefix = /^on[A-Z]/
|
||||||
|
export function getEventByopeninulaProps(props) {
|
||||||
|
const $listeners = {}
|
||||||
|
Object.keys(props)
|
||||||
|
.filter((propName) => {
|
||||||
|
return openinulaEventPrefix.test(propName) && typeof props[propName] === 'function'
|
||||||
|
})
|
||||||
|
.map((openinulaEvName) => {
|
||||||
|
return {
|
||||||
|
openinulaEvName,
|
||||||
|
vueEvName: openinulaEvName.substr(2).toLowerCase()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.forEach(({ openinulaEvName, vueEvName }) => {
|
||||||
|
Object.assign($listeners, {
|
||||||
|
[vueEvName]: props[openinulaEvName]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return $listeners
|
||||||
|
}
|
||||||
|
export function getAttrsByopeninulaProps(props) {
|
||||||
|
const $attrs = {}
|
||||||
|
Object.keys(props)
|
||||||
|
.filter((propName) => {
|
||||||
|
return !openinulaEventPrefix.test(propName) && !['children'].includes(propName)
|
||||||
|
})
|
||||||
|
.forEach((attr) => {
|
||||||
|
$attrs[attr] = props[attr]
|
||||||
|
})
|
||||||
|
return $attrs
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
import classNames from 'classnames'
|
||||||
|
import { If } from './virtual-comp'
|
||||||
|
|
||||||
|
export const Svg = ({ name = 'Icon', component: Icon }) => {
|
||||||
|
const funcObj = {
|
||||||
|
[name](props) {
|
||||||
|
const className = classNames('icon', 'tiny-svg', props.className)
|
||||||
|
const v_if = typeof props['v-if'] === 'boolean' ? props['v-if'] : true
|
||||||
|
const defaultProps = { ...props }
|
||||||
|
delete defaultProps['v-if']
|
||||||
|
return (
|
||||||
|
<If v-if={v_if}>
|
||||||
|
<Icon {...defaultProps} className={className} />
|
||||||
|
</If>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return funcObj[name]
|
||||||
|
}
|
|
@ -0,0 +1,132 @@
|
||||||
|
/**
|
||||||
|
* filterAttrs 属性过滤函数
|
||||||
|
* @param {object} attrs 由父组件传入,且没有被子组件声明为 props 的一些属性
|
||||||
|
* @param {Array} filters 过滤数组,元素可以为字符串,也可以为正则表达式
|
||||||
|
* @param {boolean} include 是否返回为被过滤的属性集合,如果为 false,filters 是过滤不要的属性
|
||||||
|
* @returns {object} 过滤后的属性对象
|
||||||
|
*/
|
||||||
|
export const filterAttrs = (attrs, filters, include) => {
|
||||||
|
const props = {}
|
||||||
|
|
||||||
|
for (let name in attrs) {
|
||||||
|
const find = filters.some((r) => new RegExp(r).test(name))
|
||||||
|
|
||||||
|
if ((include && find) || (!include && !find)) {
|
||||||
|
props[name] = attrs[name]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return props
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* event bus
|
||||||
|
* $bus.on
|
||||||
|
* $bus.off
|
||||||
|
* $bus.emit
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const eventBus = () => {
|
||||||
|
const $bus = {}
|
||||||
|
|
||||||
|
const on = (eventName, callback) => {
|
||||||
|
if (!$bus[eventName]) {
|
||||||
|
$bus[eventName] = []
|
||||||
|
}
|
||||||
|
|
||||||
|
$bus[eventName].push(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
const off = (eventName, callback) => {
|
||||||
|
if (!$bus[eventName]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$bus[eventName] = $bus[eventName].filter((subscriber) => subscriber !== callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
const emit = (eventName, ...args) => {
|
||||||
|
if (!$bus[eventName]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$bus[eventName].forEach((subscriber) => subscriber(...args))
|
||||||
|
}
|
||||||
|
|
||||||
|
const once = (eventName, callback) => {
|
||||||
|
const onceCallBack = (...args) => {
|
||||||
|
callback(...args)
|
||||||
|
off(eventName, onceCallBack)
|
||||||
|
}
|
||||||
|
on(eventName, onceCallBack)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
on,
|
||||||
|
emit,
|
||||||
|
off,
|
||||||
|
once
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实现 vue 中 :class 的用法
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function VueClassName(className) {
|
||||||
|
if (typeof className === 'string') {
|
||||||
|
return className
|
||||||
|
} else if (Array.isArray(className)) {
|
||||||
|
return className.reduce((pre, cur, index) => {
|
||||||
|
if (typeof cur === 'string') {
|
||||||
|
return `${pre}${index === 0 ? '' : ' '}${cur}`
|
||||||
|
} else {
|
||||||
|
return `${pre}${index === 0 ? '' : ' '}${VueClassName(cur)}`
|
||||||
|
}
|
||||||
|
}, '')
|
||||||
|
} else if (typeof className === 'object') {
|
||||||
|
return Object.keys(className).reduce((pre, key, index) => {
|
||||||
|
if (className[key]) {
|
||||||
|
return `${pre}${index === 0 ? '' : ' '}${key}`
|
||||||
|
} else {
|
||||||
|
return pre
|
||||||
|
}
|
||||||
|
}, '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const vc = VueClassName
|
||||||
|
|
||||||
|
export const getElementCssClass = (classes = {}, key) => {
|
||||||
|
if (typeof key === 'object') {
|
||||||
|
const keys = Object.keys(key)
|
||||||
|
let cls = ''
|
||||||
|
keys.forEach((k) => {
|
||||||
|
if (key[k] && classes[k]) cls += `${classes[k]} `
|
||||||
|
})
|
||||||
|
return cls
|
||||||
|
} else {
|
||||||
|
return classes[key] || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPropByPath(obj, path) {
|
||||||
|
let tempObj = obj
|
||||||
|
// 将a[b].c转换为a.b.c
|
||||||
|
path = path.replace(/\[(\w+)\]/g, '.$1')
|
||||||
|
// 将.a.b转换为a.b
|
||||||
|
path = path.replace(/^\./, '')
|
||||||
|
|
||||||
|
let keyArr = path.split('.')
|
||||||
|
let len = keyArr.length
|
||||||
|
|
||||||
|
for (let i = 0; i < len - 1; i++) {
|
||||||
|
let key = keyArr[i]
|
||||||
|
if (key in tempObj) {
|
||||||
|
tempObj = tempObj[key]
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tempObj[keyArr[keyArr.length - 1]]
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
export function If(props) {
|
||||||
|
if (props['v-if']) {
|
||||||
|
return props.children
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultVIfAsTrue(props) {
|
||||||
|
if (typeof props === 'object' && Object.hasOwnProperty.call(props, 'v-if')) {
|
||||||
|
return props['v-if']
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Component(props) {
|
||||||
|
const Is = props.is || (() => '')
|
||||||
|
return (
|
||||||
|
<If v-if={defaultVIfAsTrue(props)}>
|
||||||
|
<Is className={props.className} />
|
||||||
|
</If>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Slot(props) {
|
||||||
|
const { name = 'default', slots = {}, parent_children } = props
|
||||||
|
|
||||||
|
const EmptySlot = () => ''
|
||||||
|
|
||||||
|
const S = slots[name] || EmptySlot
|
||||||
|
|
||||||
|
return (
|
||||||
|
<If v-if={defaultVIfAsTrue(props)}>
|
||||||
|
<If v-if={name === 'default'}>{parent_children || props.children}</If>
|
||||||
|
<If v-if={name !== 'default'}>
|
||||||
|
<If v-if={S !== EmptySlot}>
|
||||||
|
<S {...props} />
|
||||||
|
</If>
|
||||||
|
<If v-if={S === EmptySlot}>{props.children}</If>
|
||||||
|
</If>
|
||||||
|
</If>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function For(props) {
|
||||||
|
const { item: Item, list = [] } = props
|
||||||
|
|
||||||
|
const listItems = list.map((item, index, list) => {
|
||||||
|
return <Item item={item} key={index} index={index} list={list} />
|
||||||
|
})
|
||||||
|
|
||||||
|
return <If v-if={defaultVIfAsTrue(props)}>{listItems}</If>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Transition(props) {
|
||||||
|
// todo: improve tarnsiton comp
|
||||||
|
return <If v-if={defaultVIfAsTrue(props)}>{props.children}</If>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const compWhiteList = ['If', 'Component', 'Slot', 'For', 'Transition']
|
|
@ -0,0 +1,106 @@
|
||||||
|
import { useFiber, getParentFiber, creatFiberCombine } from './fiber.js'
|
||||||
|
import { getEventByopeninulaProps, getAttrsByopeninulaProps } from './resolve-props.js'
|
||||||
|
import { Reactive } from './reactive'
|
||||||
|
import { emit, on, off, once } from './event'
|
||||||
|
|
||||||
|
const vmProxy = {
|
||||||
|
$parent: ({ fiber }) => {
|
||||||
|
const parentFiber = getParentFiber(fiber)
|
||||||
|
if (!parentFiber) return null
|
||||||
|
return createVmProxy(creatFiberCombine(parentFiber))
|
||||||
|
},
|
||||||
|
$el: ({ fiber }) => fiber.child?.stateNode,
|
||||||
|
$refs: ({ refs, fiber }) => createRefsProxy(refs, fiber.constructor),
|
||||||
|
$children: ({ children }) => children.map((fiber) => createVmProxy(creatFiberCombine(getParentFiber(fiber)))),
|
||||||
|
$listeners: ({ fiber }) => getEventByopeninulaProps(fiber.memoizedProps),
|
||||||
|
$attrs: ({ fiber }) => getAttrsByopeninulaProps(fiber.memoizedProps),
|
||||||
|
$slots: ({ fiber }) => fiber.memoizedProps.slots,
|
||||||
|
$scopedSlots: ({ fiber }) => fiber.memoizedProps.slots,
|
||||||
|
$options: ({ fiber }) => ({ componentName: fiber.type.name }),
|
||||||
|
$constants: ({ fiber }) => fiber.memoizedProps._constants,
|
||||||
|
$template: ({ fiber }) => fiber.memoizedProps.tiny_template,
|
||||||
|
$renderless: ({ fiber }) => fiber.memoizedProps.tiny_renderless,
|
||||||
|
$mode: () => 'pc',
|
||||||
|
state: ({ fiber }) => findStateInHooks(fiber.memoizedState),
|
||||||
|
$type: ({ fiber }) => fiber.type,
|
||||||
|
$service: (_, vm) => vm.state?.$service,
|
||||||
|
$emit: ({ fiber }) => emit(fiber.memoizedProps),
|
||||||
|
$on: ({ fiber }) => on(fiber.memoizedProps),
|
||||||
|
$once: ({ fiber }) => once(fiber.memoizedProps),
|
||||||
|
$off: ({ fiber }) => off(fiber.memoizedProps),
|
||||||
|
$set: () => (target, propName, value) => (target[propName] = value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const vmProxyMap = new WeakMap()
|
||||||
|
function createVmProxy(fiberCombine) {
|
||||||
|
if (!vmProxyMap.has(fiberCombine)) {
|
||||||
|
vmProxyMap.set(
|
||||||
|
fiberCombine,
|
||||||
|
new Proxy(fiberCombine, {
|
||||||
|
get(target, property, receiver) {
|
||||||
|
if (!vmProxy[property]) {
|
||||||
|
return target.fiber.memoizedProps[property]
|
||||||
|
}
|
||||||
|
return vmProxy[property](target, receiver)
|
||||||
|
},
|
||||||
|
set() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return vmProxyMap.get(fiberCombine)
|
||||||
|
}
|
||||||
|
|
||||||
|
function createEmptyProxy() {
|
||||||
|
return new Proxy(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
get() {
|
||||||
|
return undefined
|
||||||
|
},
|
||||||
|
set() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRefsProxy(refs, FiberNode) {
|
||||||
|
return new Proxy(refs, {
|
||||||
|
get(target, property) {
|
||||||
|
if (target[property] instanceof FiberNode) {
|
||||||
|
return createVmProxy(creatFiberCombine(target[property]))
|
||||||
|
} else {
|
||||||
|
return target[property]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function findStateInHooks(hookStart) {
|
||||||
|
let curHook = hookStart
|
||||||
|
// find state from hooks chain by Constructor reactive
|
||||||
|
while (curHook) {
|
||||||
|
const refCurrent = curHook.memoizedState && curHook.memoizedState.current
|
||||||
|
if (refCurrent instanceof Reactive) break
|
||||||
|
curHook = curHook.next
|
||||||
|
}
|
||||||
|
return curHook && curHook.memoizedState && curHook.memoizedState.current
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useVm() {
|
||||||
|
const { ref, current, parent } = useFiber()
|
||||||
|
if (!ref.current) {
|
||||||
|
return {
|
||||||
|
ref,
|
||||||
|
current: createEmptyProxy(),
|
||||||
|
parent: createEmptyProxy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
ref,
|
||||||
|
current: current.fiber && createVmProxy(current),
|
||||||
|
parent: parent.fiber && createVmProxy(parent)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,159 @@
|
||||||
|
import {
|
||||||
|
// 响应式:核心
|
||||||
|
ref,
|
||||||
|
computed,
|
||||||
|
reactive,
|
||||||
|
readonly,
|
||||||
|
watch,
|
||||||
|
watchEffect,
|
||||||
|
watchPostEffect,
|
||||||
|
watchSyncEffect,
|
||||||
|
// 响应式:工具
|
||||||
|
isRef,
|
||||||
|
unref,
|
||||||
|
toRef,
|
||||||
|
toValue,
|
||||||
|
toRefs,
|
||||||
|
isProxy,
|
||||||
|
isReactive,
|
||||||
|
isReadonly,
|
||||||
|
// 响应式:进阶
|
||||||
|
shallowRef,
|
||||||
|
triggerRef,
|
||||||
|
customRef,
|
||||||
|
shallowReactive,
|
||||||
|
shallowReadonly,
|
||||||
|
toRaw,
|
||||||
|
markRaw,
|
||||||
|
effectScope,
|
||||||
|
getCurrentScope,
|
||||||
|
onScopeDispose,
|
||||||
|
// 通用
|
||||||
|
nextTick
|
||||||
|
} from '@vue/runtime-core'
|
||||||
|
import { useExcuteOnce } from './hooks'
|
||||||
|
import { useEffect } from 'openinula'
|
||||||
|
|
||||||
|
// 通用
|
||||||
|
const inject = () => {}
|
||||||
|
const provide = () => {}
|
||||||
|
|
||||||
|
export function generateVueHooks({ $bus }) {
|
||||||
|
const reload = () => $bus.emit('event:reload')
|
||||||
|
|
||||||
|
function toPageLoad(reactiveHook, reload) {
|
||||||
|
return function (...args) {
|
||||||
|
const result = reactiveHook(...args)
|
||||||
|
nextTick(() => {
|
||||||
|
watch(
|
||||||
|
result,
|
||||||
|
() => {
|
||||||
|
typeof reload === 'function' && reload()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flush: 'sync'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
// 响应式:核心
|
||||||
|
ref: toPageLoad(ref, reload),
|
||||||
|
computed: toPageLoad(computed, reload),
|
||||||
|
reactive: toPageLoad(reactive, reload),
|
||||||
|
readonly,
|
||||||
|
watchEffect,
|
||||||
|
watchPostEffect,
|
||||||
|
watchSyncEffect,
|
||||||
|
watch,
|
||||||
|
// 响应式:工具
|
||||||
|
isRef,
|
||||||
|
unref,
|
||||||
|
toRef: toPageLoad(toRef, reload),
|
||||||
|
toValue,
|
||||||
|
toRefs,
|
||||||
|
isProxy,
|
||||||
|
isReactive,
|
||||||
|
isReadonly,
|
||||||
|
// 响应式:进阶
|
||||||
|
shallowRef: toPageLoad(shallowRef, reload),
|
||||||
|
triggerRef,
|
||||||
|
customRef: toPageLoad(customRef, reload),
|
||||||
|
shallowReactive: toPageLoad(shallowReactive, reload),
|
||||||
|
shallowReadonly,
|
||||||
|
toRaw,
|
||||||
|
markRaw,
|
||||||
|
effectScope,
|
||||||
|
getCurrentScope,
|
||||||
|
onScopeDispose,
|
||||||
|
// 依赖注入
|
||||||
|
inject,
|
||||||
|
provide,
|
||||||
|
// 生命周期函数
|
||||||
|
onBeforeUnmount() {
|
||||||
|
$bus.on('hook:onBeforeUnmount')
|
||||||
|
},
|
||||||
|
onMounted() {
|
||||||
|
$bus.on('hook:onMounted')
|
||||||
|
},
|
||||||
|
onUpdated() {
|
||||||
|
$bus.on('hook:onUpdated')
|
||||||
|
},
|
||||||
|
onUnmounted() {
|
||||||
|
$bus.on('hook:onUnmounted')
|
||||||
|
},
|
||||||
|
onBeforeMount() {
|
||||||
|
$bus.on('hook:onBeforeMount')
|
||||||
|
},
|
||||||
|
onBeforeUpdate() {
|
||||||
|
$bus.on('hook:onBeforeUpdate')
|
||||||
|
},
|
||||||
|
onErrorCaptured() {
|
||||||
|
$bus.on('hook:onErrorCaptured')
|
||||||
|
},
|
||||||
|
onRenderTracked() {
|
||||||
|
$bus.on('hook:onRenderTracked')
|
||||||
|
},
|
||||||
|
onRenderTriggered() {
|
||||||
|
$bus.on('hook:onRenderTriggered')
|
||||||
|
},
|
||||||
|
onActivated() {
|
||||||
|
$bus.on('hook:onActivated')
|
||||||
|
},
|
||||||
|
onDeactivated() {
|
||||||
|
$bus.on('hook:onDeactivated')
|
||||||
|
},
|
||||||
|
onServerPrefetch() {
|
||||||
|
$bus.on('hook:onServerPrefetch')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在这里出发生命周期钩子
|
||||||
|
export function useVueLifeHooks($bus) {
|
||||||
|
$bus.emit('hook:onBeforeUpdate')
|
||||||
|
nextTick(() => {
|
||||||
|
$bus.emit('hook:onUpdated')
|
||||||
|
})
|
||||||
|
|
||||||
|
useExcuteOnce(() => {
|
||||||
|
$bus.emit('hook:onBeforeMount')
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
$bus.emit('hook:onMounted')
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
// 卸载
|
||||||
|
$bus.emit('hook:onBeforeUnmount')
|
||||||
|
nextTick(() => {
|
||||||
|
$bus.emit('hook:onUnmounted')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
|
|
||||||
|
export * from '@vue/runtime-core'
|
|
@ -0,0 +1,88 @@
|
||||||
|
import { useRef } from 'openinula'
|
||||||
|
import { useExcuteOnce, useOnceResult } from './hooks'
|
||||||
|
import { reactive, nextTick, watch, computed } from '@vue/runtime-core'
|
||||||
|
import { getPropByPath } from './utils'
|
||||||
|
import { getParent, getRoot } from './render-stack'
|
||||||
|
import { getFiberByDom, traverseFiber } from './fiber'
|
||||||
|
|
||||||
|
const collectRefs = (rootEl, $children) => {
|
||||||
|
const refs = {}
|
||||||
|
if (!rootEl) return refs
|
||||||
|
const rootFiber = getFiberByDom(rootEl)
|
||||||
|
// 收集普通元素 ref
|
||||||
|
traverseFiber(rootFiber, (fiber) => {
|
||||||
|
if (typeof fiber.type === 'string' && fiber.stateNode.getAttribute('v-ref')) {
|
||||||
|
refs[fiber.stateNode.getAttribute('v-ref')] = fiber.stateNode
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// 收集组件元素 ref
|
||||||
|
$children.forEach((child) => {
|
||||||
|
if (child.$props['v-ref']) {
|
||||||
|
refs[child.$props['v-ref']] = child
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return refs
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useCreateVueInstance({ $bus, props }) {
|
||||||
|
const ref = useRef()
|
||||||
|
const vm = useOnceResult(() =>
|
||||||
|
reactive({
|
||||||
|
$el: undefined,
|
||||||
|
$options: props.$options || {},
|
||||||
|
$props: props,
|
||||||
|
$parent: getParent().vm || {},
|
||||||
|
$root: getRoot().vm || {},
|
||||||
|
$slots: props.slots,
|
||||||
|
$scopedSlots: props.slots,
|
||||||
|
$listeners: props.$listeners,
|
||||||
|
$attrs: props.$attrs,
|
||||||
|
// 通过 fiber 计算
|
||||||
|
$children: [],
|
||||||
|
$refs: computed(() => collectRefs(vm.$el, vm.$children)),
|
||||||
|
// 方法
|
||||||
|
$set: (target, property, value) => (target[property] = value),
|
||||||
|
$delete: (target, property) => delete target[property],
|
||||||
|
$watch: (expression, callback, options) => {
|
||||||
|
if (typeof expression === 'string') {
|
||||||
|
watch(() => getPropByPath(vm, expression), callback, options)
|
||||||
|
} else if (typeof expression === 'function') {
|
||||||
|
watch(expression, callback, options)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
$on: (event, callback) => $bus.on(event, callback),
|
||||||
|
$once: (event, callback) => $bus.once(event, callback),
|
||||||
|
$off: (event, callback) => $bus.off(event, callback),
|
||||||
|
$emit: (event, ...args) => $bus.emit(event, ...args),
|
||||||
|
$forceUpdate: () => $bus.emit('event:reload'),
|
||||||
|
$nextTick: nextTick,
|
||||||
|
$destroy: () => {},
|
||||||
|
$mount: () => {}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
useExcuteOnce(() => {
|
||||||
|
const { $listeners } = props
|
||||||
|
|
||||||
|
if ($listeners) {
|
||||||
|
Object.keys($listeners).forEach((eventName) => {
|
||||||
|
$bus.on(eventName, $listeners[eventName])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 给父的 $children 里 push 当前的 vm
|
||||||
|
const parent = vm.$parent
|
||||||
|
if (Array.isArray(parent.$children)) {
|
||||||
|
parent.$children.push(vm)
|
||||||
|
}
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
vm.$el = ref.current
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
ref,
|
||||||
|
vm
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
export function defineVueProps(propsOptions, props) {
|
||||||
|
const $props = {}
|
||||||
|
const $attrs = {}
|
||||||
|
const $listeners = {}
|
||||||
|
const openinulaEventPrefix = /^on[A-Z]/
|
||||||
|
|
||||||
|
const propsArray = Array.isArray(propsOptions) ? propsOptions : Object.keys(propsOptions)
|
||||||
|
Object.keys(props).forEach((key) => {
|
||||||
|
if (propsArray.includes(key)) {
|
||||||
|
$props[key] = props[key]
|
||||||
|
} else {
|
||||||
|
if (openinulaEventPrefix.test(key)) {
|
||||||
|
$listeners[key.substr(2).toLowerCase()] = props[key]
|
||||||
|
} else {
|
||||||
|
$attrs[key] = props[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (typeof propsOptions === 'object') {
|
||||||
|
Object.keys(propsOptions)
|
||||||
|
.filter((key) => !$props[key])
|
||||||
|
.forEach((key) => {
|
||||||
|
const options = propsOptions[key]
|
||||||
|
const defaultValue = typeof options.default === 'function' ? options.default() : options.default
|
||||||
|
defaultValue !== undefined && ($props[key] = defaultValue)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
$props,
|
||||||
|
$attrs,
|
||||||
|
$listeners
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
import IconLoading from './src/loading'
|
||||||
|
import IconAdd from './src/add'
|
||||||
|
import IconCheck from './src/check'
|
||||||
|
import IconCheckedSur from './src/check'
|
||||||
|
import IconChevronDown from './src/chevron-down'
|
||||||
|
import IconClose from './src/close'
|
||||||
|
import IconError from './src/error'
|
||||||
|
import IconHalfSelect from './src/half-select'
|
||||||
|
import IconHelp from './src/help'
|
||||||
|
import IconSuccess from './src/success'
|
||||||
|
import IconWarning from './src/warning'
|
||||||
|
|
||||||
|
export {
|
||||||
|
IconLoading,
|
||||||
|
IconAdd,
|
||||||
|
IconCheck,
|
||||||
|
IconCheckedSur,
|
||||||
|
IconChevronDown,
|
||||||
|
IconClose,
|
||||||
|
IconError,
|
||||||
|
IconHalfSelect,
|
||||||
|
IconHelp,
|
||||||
|
IconSuccess,
|
||||||
|
IconWarning
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
IconLoading,
|
||||||
|
IconAdd,
|
||||||
|
IconCheck,
|
||||||
|
IconCheckedSur,
|
||||||
|
IconChevronDown,
|
||||||
|
IconClose,
|
||||||
|
IconError,
|
||||||
|
IconHalfSelect,
|
||||||
|
IconHelp,
|
||||||
|
IconSuccess,
|
||||||
|
IconWarning
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"name": "@opentiny/openinula-icon",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.ts",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@opentiny/openinula-common": "workspace:~",
|
||||||
|
"@opentiny/vue-theme": "workspace:~"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2022 - present TinyVue Authors.
|
||||||
|
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license.
|
||||||
|
*
|
||||||
|
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
|
||||||
|
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
import { Svg } from '@opentiny/openinula-common'
|
||||||
|
import { ReactComponent as AddLoading } from '@opentiny/vue-theme/svgs/add.svg'
|
||||||
|
|
||||||
|
export default Svg({ name: 'AddLoading', component: AddLoading })
|
|
@ -0,0 +1,15 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2022 - present TinyVue Authors.
|
||||||
|
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license.
|
||||||
|
*
|
||||||
|
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
|
||||||
|
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
import { Svg } from '@opentiny/openinula-common'
|
||||||
|
import { ReactComponent as Check } from '@opentiny/vue-theme/svgs/check.svg'
|
||||||
|
|
||||||
|
export default Svg({ name: 'Check', component: Check })
|
|
@ -0,0 +1,15 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2022 - present TinyVue Authors.
|
||||||
|
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license.
|
||||||
|
*
|
||||||
|
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
|
||||||
|
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
import { Svg } from '@opentiny/openinula-common'
|
||||||
|
import { ReactComponent as CheckedSur } from '@opentiny/vue-theme/svgs/checked-sur.svg'
|
||||||
|
|
||||||
|
export default Svg({ name: 'CheckedSur', component: CheckedSur })
|
|
@ -0,0 +1,15 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2022 - present TinyVue Authors.
|
||||||
|
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license.
|
||||||
|
*
|
||||||
|
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
|
||||||
|
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
import { Svg } from '@opentiny/openinula-common'
|
||||||
|
import { ReactComponent as ChevronDown } from '@opentiny/vue-theme/svgs/chevron-down.svg'
|
||||||
|
|
||||||
|
export default Svg({ name: 'ChevronDown', component: ChevronDown })
|
|
@ -0,0 +1,15 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2022 - present TinyVue Authors.
|
||||||
|
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license.
|
||||||
|
*
|
||||||
|
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
|
||||||
|
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
import { Svg } from '@opentiny/openinula-common'
|
||||||
|
import { ReactComponent as Close } from '@opentiny/vue-theme/svgs/close.svg'
|
||||||
|
|
||||||
|
export default Svg({ name: 'Close', component: Close })
|
|
@ -0,0 +1,15 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2022 - present TinyVue Authors.
|
||||||
|
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license.
|
||||||
|
*
|
||||||
|
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
|
||||||
|
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
import { Svg } from '@opentiny/openinula-common'
|
||||||
|
import { ReactComponent as Error } from '@opentiny/vue-theme/svgs/error.svg'
|
||||||
|
|
||||||
|
export default Svg({ name: 'Error', component: Error })
|
|
@ -0,0 +1,15 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2022 - present TinyVue Authors.
|
||||||
|
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license.
|
||||||
|
*
|
||||||
|
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
|
||||||
|
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
import { Svg } from '@opentiny/openinula-common'
|
||||||
|
import { ReactComponent as HalfSelect } from '@opentiny/vue-theme/svgs/halfselect.svg'
|
||||||
|
|
||||||
|
export default Svg({ name: 'HalfSelect', component: HalfSelect })
|
|
@ -0,0 +1,15 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2022 - present TinyVue Authors.
|
||||||
|
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license.
|
||||||
|
*
|
||||||
|
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
|
||||||
|
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
import { Svg } from '@opentiny/openinula-common'
|
||||||
|
import { ReactComponent as Help } from '@opentiny/vue-theme/svgs/help.svg'
|
||||||
|
|
||||||
|
export default Svg({ name: 'Help', component: Help })
|
|
@ -0,0 +1,15 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2022 - present TinyVue Authors.
|
||||||
|
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license.
|
||||||
|
*
|
||||||
|
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
|
||||||
|
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
import { Svg } from '@opentiny/openinula-common'
|
||||||
|
import { ReactComponent as IconLoading } from '@opentiny/vue-theme/svgs/loading.svg'
|
||||||
|
|
||||||
|
export default Svg({ name: 'IconLoading', component: IconLoading })
|
|
@ -0,0 +1,15 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2022 - present TinyVue Authors.
|
||||||
|
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license.
|
||||||
|
*
|
||||||
|
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
|
||||||
|
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
import { Svg } from '@opentiny/openinula-common'
|
||||||
|
import { ReactComponent as Success } from '@opentiny/vue-theme/svgs/success.svg'
|
||||||
|
|
||||||
|
export default Svg({ name: 'Success', component: Success })
|
|
@ -0,0 +1,15 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2022 - present TinyVue Authors.
|
||||||
|
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license.
|
||||||
|
*
|
||||||
|
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
|
||||||
|
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
import { Svg } from '@opentiny/openinula-common'
|
||||||
|
import { ReactComponent as Warning } from '@opentiny/vue-theme/svgs/warning.svg'
|
||||||
|
|
||||||
|
export default Svg({ name: 'Warning', component: Warning })
|
Loading…
Reference in New Issue