Commit dc3067ee authored by haiyoucuv's avatar haiyoucuv

内置工具包built-in

埋点
parent 04f9c204
......@@ -37,5 +37,9 @@
}
},
"keywords": [],
"author": "haiyoucuv"
"author": "haiyoucuv",
"dependencies": {
"classnames": "^2.5.1",
"vite-plugin-lib-inject-css": "^2.1.1"
}
}
......@@ -48,6 +48,7 @@ export const Button = (props: IButtonProps): ReactElement => {
}
return <div
{...props}
className={className}
onTouchStart={touchStart}
onTouchEnd={touchEnd}
......
@class-prefix-toast: ~'__com_toast_';
.ToastRoot {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 999999;
display: flex;
align-items: center;
justify-content: center;
.ToastContent {
background-color: rgba(0, 0, 0, 0.7);
max-width: 80%;
padding: 16px;
color: white;
align-items: center;
justify-content: center;
display: inline-block;
position: relative;
top: 50%;
transform: translateY(-50%);
width: auto;
max-height: 70%;
overflow: auto;
word-break: break-all;
border-radius: 8px;
pointer-events: all;
font-size: var(--adm-font-size-7);
line-height: 1.5;
box-sizing: border-box;
text-align: initial;
}
}
\ No newline at end of file
import { FC, lazy, ReactNode } from 'react'
import React from 'react'
import styles from './Toast.module.less';
const classPrefix = '__com_toast_';
export interface ToastProps {
afterClose?: () => void
visible?: boolean
mask?: boolean
content?: ReactNode
duration?: number
}
export const InternalToast: FC<ToastProps> = (props) => {
const { mask, content, visible } = props;
return <div
className={styles.ToastRoot}
style={{
pointerEvents: mask ? 'auto' : 'none',
display: visible ? "flex" : "none",
}}
>
<div className={styles.ToastContent}>
{content}
</div>
</div>
}
import { clear, show } from './methods'
export type { ToastShowProps, ToastHandler } from './methods'
export const Toast = {
show,
clear,
}
import React from 'react'
import {
ImperativeHandler,
renderImperatively,
} from '../../utils/render-imperatively'
import { mergeProps } from '../../utils/with-default-props'
import { InternalToast, ToastProps } from './Toast'
let currentHandler: ImperativeHandler | null = null
let currentTimeout: number | null = null
export type ToastShowProps = Omit<ToastProps, 'visible'>
const defaultProps = {
duration: 2000,
position: 'center',
maskClickable: true,
}
export type ToastHandler = {
close: () => void
}
const ToastInner = (
props: ToastShowProps & {
onClose?: () => void
}
) => <InternalToast {...props} />
export function show(p: ToastShowProps | string) {
const props = mergeProps(
defaultProps,
typeof p === 'string' ? { content: p } : p
)
const element = (
<ToastInner
{...props}
onClose={() => {
currentHandler = null
}}
/>
)
if (currentHandler) {
if (currentHandler.isRendered?.()) {
currentHandler.replace(element)
} else {
currentHandler.close()
currentHandler = renderImperatively(element)
}
} else {
currentHandler = renderImperatively(element)
}
if (currentTimeout) {
window.clearTimeout(currentTimeout)
}
if (props.duration !== 0) {
currentTimeout = window.setTimeout(() => {
clear()
}, props.duration)
}
return currentHandler as ToastHandler
}
export function clear() {
currentHandler?.close()
currentHandler = null
}
@class-prefix-toast: ~'__com_toast_';
.@{class-prefix-toast}root {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 999999;
display: flex;
align-items: center;
justify-content: center;
.@{class-prefix-toast}content {
background-color: rgba(0, 0, 0, 0.7);
max-width: 80%;
padding: 16px;
color: white;
align-items: center;
justify-content: center;
display: inline-block;
position: relative;
top: 50%;
transform: translateY(-50%);
width: auto;
max-height: 70%;
overflow: auto;
word-break: break-all;
border-radius: 8px;
pointer-events: all;
font-size: var(--adm-font-size-7);
line-height: 1.5;
box-sizing: border-box;
text-align: initial;
}
}
\ No newline at end of file
export * from "./components/Button";
export * from "./components/Toast";
import type { ReactElement } from 'react'
import React, { useEffect, useImperativeHandle, useRef, useState } from 'react'
import { renderToBody } from './render-to-body'
type ImperativeProps = {
visible?: boolean
onClose?: () => void
afterClose?: () => void
}
type TargetElement = ReactElement<ImperativeProps>
export type ImperativeHandler = {
close: () => void
replace: (element: TargetElement) => void
isRendered?: () => boolean
}
export function renderImperatively(element: TargetElement) {
const Wrapper = React.forwardRef<ImperativeHandler>((_, ref) => {
const [visible, setVisible] = useState(false)
const closedRef = useRef(false)
const [elementToRender, setElementToRender] = useState(element)
const keyRef = useRef(0)
useEffect(() => {
if (!closedRef.current) {
setVisible(true)
} else {
afterClose()
}
}, [])
function onClose() {
closedRef.current = true
setVisible(false)
elementToRender.props.onClose?.()
}
function afterClose() {
unmount()
elementToRender.props.afterClose?.()
}
useImperativeHandle(ref, () => ({
close: onClose,
replace: element => {
keyRef.current++
elementToRender.props.afterClose?.()
setElementToRender(element)
},
}))
return React.cloneElement(elementToRender, {
...elementToRender.props,
key: keyRef.current,
visible,
onClose,
afterClose,
})
})
const wrapperRef = React.createRef<ImperativeHandler>()
const unmount = renderToBody(<Wrapper ref={wrapperRef} />)
return {
close: async () => {
if (!wrapperRef.current) {
// it means the wrapper is not mounted yet, call `unmount` directly
unmount()
// call `afterClose` to make sure the callback is called
element.props.afterClose?.()
} else {
wrapperRef.current?.close()
}
},
replace: element => {
wrapperRef.current?.replace(element)
},
isRendered: () => !!wrapperRef.current,
} as ImperativeHandler
}
import type { ReactElement } from 'react'
import { render, unmount as reactUnmount } from './render'
export function renderToBody(element: ReactElement) {
const container = document.createElement('div')
document.body.appendChild(container)
function unmount() {
const unmountResult = reactUnmount(container)
if (unmountResult && container.parentNode) {
container.parentNode.removeChild(container)
}
}
render(element, container)
return unmount
}
import type { ReactElement } from 'react'
import * as ReactDOM from 'react-dom'
import type { Root } from 'react-dom/client'
// 移植自rc-util: https://github.com/react-component/util/blob/master/src/React/render.ts
type CreateRoot = (container: ContainerType) => Root
// Let compiler not to search module usage
const fullClone = {
...ReactDOM,
} as typeof ReactDOM & {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED?: {
usingClientEntryPoint?: boolean
}
createRoot?: CreateRoot
}
const { version, render: reactRender, unmountComponentAtNode } = fullClone
let createRoot: CreateRoot
try {
const mainVersion = Number((version || '').split('.')[0])
if (mainVersion >= 18 && fullClone.createRoot) {
// eslint-disable-next-line @typescript-eslint/no-var-requires
createRoot = fullClone.createRoot
}
} catch (e) {
// Do nothing;
}
function toggleWarning(skip: boolean) {
const { __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED } = fullClone
if (
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED &&
typeof __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED === 'object'
) {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.usingClientEntryPoint =
skip
}
}
const MARK = '__antd_mobile_root__'
// ========================== Render ==========================
type ContainerType = (Element | DocumentFragment) & {
[MARK]?: Root
}
function legacyRender(node: ReactElement, container: ContainerType) {
reactRender(node, container)
}
function concurrentRender(node: ReactElement, container: ContainerType) {
toggleWarning(true)
const root = container[MARK] || createRoot(container)
toggleWarning(false)
root.render(node)
container[MARK] = root
}
export function render(node: ReactElement, container: ContainerType) {
if (createRoot as unknown) {
concurrentRender(node, container)
return
}
legacyRender(node, container)
}
// ========================== Unmount =========================
function legacyUnmount(container: ContainerType) {
return unmountComponentAtNode(container)
}
async function concurrentUnmount(container: ContainerType) {
// Delay to unmount to avoid React 18 sync warning
return Promise.resolve().then(() => {
container[MARK]?.unmount()
delete container[MARK]
})
}
export function unmount(container: ContainerType) {
if (createRoot as unknown) {
return concurrentUnmount(container)
}
return legacyUnmount(container)
}
export function mergeProps<A, B>(a: A, b: B): B & A
export function mergeProps<A, B, C>(a: A, b: B, c: C): C & B & A
export function mergeProps<A, B, C, D>(a: A, b: B, c: C, d: D): D & C & B & A
export function mergeProps(...items: any[]) {
const ret: any = {}
items.forEach(item => {
if (item) {
Object.keys(item).forEach(key => {
if (item[key] !== undefined) {
ret[key] = item[key]
}
})
}
})
return ret
}
/**
* Merge props and return the first non-undefined value.
* The later has higher priority. e.g. (10, 1, 5) => 5 wins.
* This is useful with legacy props that have been deprecated.
*/
export function mergeProp<T, DefaultT extends T = T>(
defaultProp: DefaultT,
...propList: T[]
): T | undefined {
for (let i = propList.length - 1; i >= 0; i -= 1) {
if (propList[i] !== undefined) {
return propList[i]
}
}
return defaultProp
}
......@@ -10,27 +10,37 @@ export default defineConfig(({ mode }): UserConfig => {
return {
build: {
cssCodeSplit: true,
sourcemap: isDev,
target: 'modules',
lib: {
entry: "./src/index.tsx",
name: 'UI',
fileName: 'index',
},
cssTarget: 'chrome61',
minify: false,
rollupOptions: {
// 确保外部化处理那些你不想打包进库的依赖
external: ['react', 'react-dom', "react/jsx-runtime"],
output: {
inlineDynamicImports: false,
// 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量
globals: {
React: 'react',
"react-dom": 'react-dom',
},
freeze: false,
preserveModules: true,
preserveModulesRoot:'src',
entryFileNames: "[name].js",
exports: "named",
},
},
},
plugins: [
react(),
// libInjectCss(),
dts({ tsconfigPath: './tsconfig.app.json' })
],
css: {
......
......@@ -16,6 +16,7 @@
"@grace/ui": "workspace:^",
"axios": "^1.7.7",
"chalk": "^5.3.0",
"classnames": "^2.5.1",
"crypto-js": "^4.2.0",
"duiba-utils": "^2.0.2",
"intersection-observer": "^0.12.2",
......
......@@ -3,7 +3,7 @@ import { observer } from 'mobx-react';
import './HomeDemo.less';
import { SvgaPlayer } from "@grace/svgaplayer";
import { Button } from "@grace/ui";
import { Button, Toast } from "@grace/ui";
import svga from "../../assets/LoadingPage/1红色标题.svga";
import png from "../../assets/LoadingPage/loadingIp.png";
......@@ -15,6 +15,11 @@ class HomeDemo extends React.Component {
}
clickBtn = () => {
console.log(123123)
Toast.show('我是Toast\n我是Toast我是Toast我是Toast我是Toast我是Toast我是Toast我是Toast我是Toast我是Toast');
}
render() {
return <div className="homeDemo md1">
<div className="homeImg" />
......@@ -22,7 +27,7 @@ class HomeDemo extends React.Component {
当前为活动首页
<SvgaPlayer className="svga" src={svga} />
<Button className="button md2" />
<Button className="button md2" onClick={this.clickBtn} />
</div>;
}
......
......@@ -7,6 +7,13 @@ settings:
importers:
.:
dependencies:
classnames:
specifier: ^2.5.1
version: 2.5.1
vite-plugin-lib-inject-css:
specifier: ^2.1.1
version: 2.1.1(vite@5.4.10(@types/node@22.7.9)(less@4.2.0)(terser@5.36.0))
devDependencies:
'@babel/plugin-proposal-class-properties':
specifier: ^7.18.6
......@@ -92,6 +99,9 @@ importers:
chalk:
specifier: ^5.3.0
version: 5.3.0
classnames:
specifier: ^2.5.1
version: 2.5.1
crypto-js:
specifier: ^4.2.0
version: 4.2.0
......@@ -211,6 +221,61 @@ packages:
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==, tarball: http://npm.dui88.com:80/@ampproject%2fremapping/-/remapping-2.3.0.tgz}
engines: {node: '>=6.0.0'}
'@ast-grep/napi-darwin-arm64@0.22.6':
resolution: {integrity: sha512-L9rEGJ8fNi5LxbZj860wbXxjX7DLNV799zcTaPOSzYadvNyhMY3LWvDXd45Vtx6Dh8QRtCoEMQmw8KaRCEjm9A==, tarball: http://npm.dui88.com:80/@ast-grep%2fnapi-darwin-arm64/-/napi-darwin-arm64-0.22.6.tgz}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
'@ast-grep/napi-darwin-x64@0.22.6':
resolution: {integrity: sha512-0iuM6iDJNhcPd6a/JJr64AallR7ttGW/MvUujfQdvJEZY5p9LK35xm23dULznW0tIMgwtMKPRaprgk8LPondKg==, tarball: http://npm.dui88.com:80/@ast-grep%2fnapi-darwin-x64/-/napi-darwin-x64-0.22.6.tgz}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
'@ast-grep/napi-linux-arm64-gnu@0.22.6':
resolution: {integrity: sha512-9PAqNJlAQfFm1RW0DVCM/S4gFHdppxUTWacB3qEeJZXgdLnoH0KGQa4z3Xo559SPYDKZy0VnY02mZ3XJ+v6/Vw==, tarball: http://npm.dui88.com:80/@ast-grep%2fnapi-linux-arm64-gnu/-/napi-linux-arm64-gnu-0.22.6.tgz}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@ast-grep/napi-linux-x64-gnu@0.22.6':
resolution: {integrity: sha512-nZf+gxXVrZqvP1LN6HwzOMA4brF3umBXfMequQzv8S6HeJ4c34P23F0Tw8mHtQpVYP9PQWJUvt3LJQ8Xvd5Hiw==, tarball: http://npm.dui88.com:80/@ast-grep%2fnapi-linux-x64-gnu/-/napi-linux-x64-gnu-0.22.6.tgz}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@ast-grep/napi-linux-x64-musl@0.22.6':
resolution: {integrity: sha512-gcJeBMgJQf2pZZo0lgH0Vg4ycyujM7Am8VlomXhavC/dPpkddA1tiHSIC4fCNneLU1EqHITy3ALSmM4GLdsjBw==, tarball: http://npm.dui88.com:80/@ast-grep%2fnapi-linux-x64-musl/-/napi-linux-x64-musl-0.22.6.tgz}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [musl]
'@ast-grep/napi-win32-arm64-msvc@0.22.6':
resolution: {integrity: sha512-YDDzvPIyl4ti8xZfjvGSGVCX9JJjMQjyWPlXcwRpiLRnHThtHTDL8PyE2yq+gAPuZ28QbrygMkP9EKXIyYFVcQ==, tarball: http://npm.dui88.com:80/@ast-grep%2fnapi-win32-arm64-msvc/-/napi-win32-arm64-msvc-0.22.6.tgz}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
'@ast-grep/napi-win32-ia32-msvc@0.22.6':
resolution: {integrity: sha512-w5P0MDcBD3bifC2K9nCDEFYacy8HQnXdf6fX6cIE/7xL8XEDs6D1lQjGewrZDcMAXVXUQfupj4P27ZsJRmuIoQ==, tarball: http://npm.dui88.com:80/@ast-grep%2fnapi-win32-ia32-msvc/-/napi-win32-ia32-msvc-0.22.6.tgz}
engines: {node: '>= 10'}
cpu: [ia32]
os: [win32]
'@ast-grep/napi-win32-x64-msvc@0.22.6':
resolution: {integrity: sha512-1aaHvgsCBwUP0tDf4HXPMpUV/nUwsOWgRCiBc2zIJjdEjT9TTk795EIX9Z1Nc0OMCrxVEceyiKcYTofXa0Fpxw==, tarball: http://npm.dui88.com:80/@ast-grep%2fnapi-win32-x64-msvc/-/napi-win32-x64-msvc-0.22.6.tgz}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
'@ast-grep/napi@0.22.6':
resolution: {integrity: sha512-kNF87HiI4omHC7VzyBZSvqOAXtMlSDRF2YX+O5ya0XKv/7/GYms1opLQ+BQ9twLLDj0WsSFX4MYg0TrinZTxTg==, tarball: http://npm.dui88.com:80/@ast-grep%2fnapi/-/napi-0.22.6.tgz}
engines: {node: '>= 10'}
'@babel/code-frame@7.25.9':
resolution: {integrity: sha512-z88xeGxnzehn2sqZ8UdGQEvYErF1odv2CftxInpSYJt6uHuPe9YjahKZITGs3l5LeI9d2ROG+obuDAoSlqbNfQ==, tarball: http://npm.dui88.com:80/@babel%2fcode-frame/-/code-frame-7.25.9.tgz}
engines: {node: '>=6.9.0'}
......@@ -1398,6 +1463,9 @@ packages:
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==, tarball: http://npm.dui88.com:80/chokidar/-/chokidar-3.6.0.tgz}
engines: {node: '>= 8.10.0'}
classnames@2.5.1:
resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==, tarball: http://npm.dui88.com:80/classnames/-/classnames-2.5.1.tgz}
color-convert@1.9.3:
resolution: {integrity: sha1-u3GFBpDh8TZWfeYp0tVHHe2kweg=, tarball: http://npm.dui88.com:80/color-convert/-/color-convert-1.9.3.tgz}
......@@ -2532,6 +2600,11 @@ packages:
vite:
optional: true
vite-plugin-lib-inject-css@2.1.1:
resolution: {integrity: sha512-RIMeVnqBK/8I0E9nnQWzws6pdj5ilRMPJSnXYb6nWxNR4EmDPnksnb/ACoR5Fy7QfzULqS4gtQMrjwnNCC9zoA==, tarball: http://npm.dui88.com:80/vite-plugin-lib-inject-css/-/vite-plugin-lib-inject-css-2.1.1.tgz}
peerDependencies:
vite: '*'
vite-plugin-mock@3.0.2:
resolution: {integrity: sha512-bD//HvkTygGmk+LsIAdf0jGNlCv4iWv0kZlH9UEgWT6QYoUwfjQAE4SKxHRw2tfLgVhbPQVv/+X3YlNWvueGUA==, tarball: http://npm.dui88.com:80/vite-plugin-mock/-/vite-plugin-mock-3.0.2.tgz}
engines: {node: '>=16.0.0'}
......@@ -2619,6 +2692,41 @@ snapshots:
'@jridgewell/gen-mapping': 0.3.5
'@jridgewell/trace-mapping': 0.3.25
'@ast-grep/napi-darwin-arm64@0.22.6':
optional: true
'@ast-grep/napi-darwin-x64@0.22.6':
optional: true
'@ast-grep/napi-linux-arm64-gnu@0.22.6':
optional: true
'@ast-grep/napi-linux-x64-gnu@0.22.6':
optional: true
'@ast-grep/napi-linux-x64-musl@0.22.6':
optional: true
'@ast-grep/napi-win32-arm64-msvc@0.22.6':
optional: true
'@ast-grep/napi-win32-ia32-msvc@0.22.6':
optional: true
'@ast-grep/napi-win32-x64-msvc@0.22.6':
optional: true
'@ast-grep/napi@0.22.6':
optionalDependencies:
'@ast-grep/napi-darwin-arm64': 0.22.6
'@ast-grep/napi-darwin-x64': 0.22.6
'@ast-grep/napi-linux-arm64-gnu': 0.22.6
'@ast-grep/napi-linux-x64-gnu': 0.22.6
'@ast-grep/napi-linux-x64-musl': 0.22.6
'@ast-grep/napi-win32-arm64-msvc': 0.22.6
'@ast-grep/napi-win32-ia32-msvc': 0.22.6
'@ast-grep/napi-win32-x64-msvc': 0.22.6
'@babel/code-frame@7.25.9':
dependencies:
'@babel/highlight': 7.25.9
......@@ -4078,6 +4186,8 @@ snapshots:
optionalDependencies:
fsevents: 2.3.3
classnames@2.5.1: {}
color-convert@1.9.3:
dependencies:
color-name: 1.1.3
......@@ -5164,6 +5274,13 @@ snapshots:
- rollup
- supports-color
vite-plugin-lib-inject-css@2.1.1(vite@5.4.10(@types/node@22.7.9)(less@4.2.0)(terser@5.36.0)):
dependencies:
'@ast-grep/napi': 0.22.6
magic-string: 0.30.12
picocolors: 1.1.1
vite: 5.4.10(@types/node@22.7.9)(less@4.2.0)(terser@5.36.0)
vite-plugin-mock@3.0.2(esbuild@0.21.5)(mockjs@1.1.0)(vite@5.4.10(@types/node@22.7.9)(less@4.2.0)(terser@5.36.0)):
dependencies:
bundle-require: 4.2.1(esbuild@0.21.5)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment