Feat:主题外观功能优化 (#7988)

* feat: 外观自定义样式改版优化 (#7741)

* feat: 外观自定义样式改版优化

* fix: 代码优化

* fix: 代码优化&单测修复

* pref: 去掉language配置文件

---------

Co-authored-by: hongyang03 <hongyang03@baidu.com>

* feat: 分割线样式支持可视化配置

* feat: 容器/图片/page外观配置 (#7984)

* feat: 容器/图片/page外观配置

* fix: 单测修复

* fix: 单测修复

---------

Co-authored-by: hongyang03 <hongyang03@baidu.com>

* 更新快照

---------

Co-authored-by: HongYang <33488114+hy993658052@users.noreply.github.com>
Co-authored-by: hongyang03 <hongyang03@baidu.com>
Co-authored-by: qinhaoyan <30946345+qinhaoyan@users.noreply.github.com>
This commit is contained in:
qkiroc 2023-09-04 14:58:20 +08:00 committed by GitHub
parent df59d92a4d
commit 912cb83c53
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 1278 additions and 3908 deletions

View File

@ -32,8 +32,11 @@ order: 42
## 属性表
| 属性名 | 类型 | 默认值 | 说明 |
| --------- | -------- | ---------- | ----------------------------------- |
| type | `string` | | `"divider"` 指定为 分割线 渲染器 |
| className | `string` | | 外层 Dom 的类名 |
| lineStyle | `string` | `"dashed"` | 分割线的样式,支持`dashed`和`solid` |
| 属性名 | 类型 | 默认值 | 说明 |
| --------- | -------- | ------------ | ------------------------------------------ |
| type | `string` | | `"divider"` 指定为 分割线 渲染器 |
| className | `string` | | 外层 Dom 的类名 |
| lineStyle | `string` | `dashed` | 分割线的样式,支持`dashed`和`solid` |
| direction | `string` | `horizontal` | 分割线的方向,支持`horizontal`和`vertical` |
| color | `string` | | 分割线的颜色 |
| rotate | `number` | | 分割线的旋转角度 |

View File

@ -138,4 +138,4 @@
"printBasicPrototype": false
}
}
}
}

View File

@ -1,20 +1,29 @@
import {useEffect} from 'react';
import type {RendererEnv} from '../env';
import type {CustomStyleClassName} from '../utils/style-helper';
import {insertCustomStyle} from '../utils/style-helper';
import {insertCustomStyle, insertEditCustomStyle} from '../utils/style-helper';
interface CustomStyleProps {
config: {
themeCss: any;
classNames: CustomStyleClassName[];
themeCss?: any;
classNames?: CustomStyleClassName[];
id?: string;
defaultData?: any;
wrapperCustomStyle?: any;
componentId?: string;
};
env: RendererEnv;
}
export default function (props: CustomStyleProps) {
const {themeCss, classNames, id, defaultData} = props.config;
const {
themeCss,
classNames,
id,
defaultData,
wrapperCustomStyle,
componentId
} = props.config;
useEffect(() => {
insertCustomStyle(
themeCss,
@ -25,5 +34,9 @@ export default function (props: CustomStyleProps) {
);
}, [props.config.themeCss]);
useEffect(() => {
insertEditCustomStyle(wrapperCustomStyle, componentId);
}, [props.config.wrapperCustomStyle]);
return null;
}

View File

@ -954,7 +954,9 @@ export class FormItemWrap extends React.Component<FormItemProps> {
mobileUI,
translate: __,
static: isStatic,
staticClassName
staticClassName,
id,
wrapperCustomStyle
} = props;
// 强制不渲染 label 的话
@ -980,7 +982,10 @@ export class FormItemWrap extends React.Component<FormItemProps> {
[`is-error`]: model && !model.valid,
[`is-required`]: required
},
model?.errClassNames
model?.errClassNames,
wrapperCustomStyle
? `wrapperCustomStyle-${id?.replace('u:', '')}`
: ''
)}
style={style}
>
@ -1493,6 +1498,7 @@ export class FormItemWrap extends React.Component<FormItemProps> {
id,
labelClassName,
descriptionClassName,
wrapperCustomStyle,
env
} = this.props;
const mode = this.props.mode || formMode;
@ -1532,22 +1538,15 @@ export class FormItemWrap extends React.Component<FormItemProps> {
{
key: 'labelClassName',
value: labelClassName
}
],
id: id + '-label'
}}
env={env}
/>
<CustomStyle
config={{
themeCss: themeCss || css,
classNames: [
},
{
key: 'descriptionClassName',
value: descriptionClassName
}
],
id: id + '-description'
wrapperCustomStyle,
componentId: id,
id: id + '-item'
}}
env={env}
/>

View File

@ -1,6 +1,9 @@
import {PlainObject} from '../types';
import {uuid} from './helper';
import cloneDeep from 'lodash/cloneDeep';
import isObject from 'lodash/isObject';
import map from 'lodash/map';
import isEmpty from 'lodash/isEmpty';
export const valueMap: PlainObject = {
'marginTop': 'margin-top',
@ -55,6 +58,10 @@ export function insertStyle(style: string, id: string) {
// bca-disable-line
varStyleTag.innerHTML = style;
if (!style) {
varStyleTag.remove();
}
}
export function addStyle(style: string, id: string) {
@ -100,7 +107,7 @@ function handleInheritData(statusMap: any, data: any) {
export function formatStyle(
themeCss: any,
classNames: {
classNames?: {
key: string;
value?: string;
weights?: {
@ -113,7 +120,7 @@ export function formatStyle(
id?: string,
defaultData?: any
) {
if (!themeCss) {
if (!themeCss || !classNames) {
return {value: '', origin: []};
}
const res = [];
@ -135,12 +142,11 @@ export function formatStyle(
list?.forEach(n => {
if (
/(\S*[C|c]lassName-\S*)/.test(n) &&
n.includes('lassName-') &&
!!~n.indexOf(
id
?.replace('u:', '')
.replace('-label', '')
.replace('-description', '')
.replace('-item', '')
.replace('-addOn', '')
.replace('-icon', '')
.replace('-inner', '') || ''
@ -263,8 +269,8 @@ export interface CustomStyleClassName {
}
export function insertCustomStyle(
themeCss: any,
classNames: CustomStyleClassName[],
themeCss?: any,
classNames?: CustomStyleClassName[],
id?: string,
defaultData?: any,
customStyleClassPrefix?: string
@ -300,3 +306,51 @@ export function getValueByPath(path: string, data: any) {
return null;
}
}
// 递归处理嵌套的样式,转化成一维对象
function traverseStyle(style: any, path: string, result: any) {
for (let key in style) {
if (style.hasOwnProperty(key)) {
if (key === '$$id') {
continue;
}
if (isObject(style[key])) {
const nowPath = path ? `${path} ${key}` : key;
traverseStyle(style[key], nowPath, result);
} else if (path === '') {
!result[key] && (result[key] = {});
result[key] = style[key];
} else {
!result[path] && (result[path] = {});
result[path][key] = style[key];
}
}
}
}
/**
*
*/
export function insertEditCustomStyle(customStyle: any, id?: string) {
let styles: any = {};
traverseStyle(customStyle, '', styles);
let content = '';
if (!isEmpty(styles)) {
const className = `wrapperCustomStyle-${id?.replace('u:', '')}`;
for (let key in styles) {
if (styles.hasOwnProperty(key)) {
if (isObject(styles[key])) {
const res = map(styles[key], (value, key) => `${key}: ${value};`);
content += `\n.${className} ${key} {\n ${res.join('\n ')}\n}`;
} else {
content += `\n.${className} {\n ${key}: ${styles[key]}\n}`;
}
}
}
}
insertStyle(
content,
'wrapperCustomStyle-' + (id?.replace('u:', '') || uuid())
);
}

View File

@ -1,7 +1,17 @@
.ThemeCssCode {
position: relative;
&-button {
width: 100%;
position: absolute;
top: 4px;
right: 12px;
z-index: 100;
}
&-icon {
cursor: pointer;
color: #5c5f66;
&:hover {
color: #151b26;
}
}
.is-group {
overflow: auto;
@ -68,6 +78,12 @@
}
}
}
&-wrap {
border: 1px solid #e8e9eb;
.cxd-MonacoEditor-placeholder {
left: 1rem;
}
}
}
.editorPanel-inner {
.Theme-FontEditor {

View File

@ -211,9 +211,9 @@ export function JSONPipeOut(
* themeCss属性className加上name
*/
export function addStyleClassName(obj: Schema) {
const themeCss = obj.themeCss || obj.css;
const themeCss = obj.type === 'page' ? obj.themeCss : obj.themeCss || obj.css;
// page暂时不做处理
if (!themeCss || obj.type === 'page') {
if (!themeCss) {
return obj;
}
let toUpdate: any = {};

View File

@ -168,7 +168,7 @@ export class ButtonPlugin extends BasePlugin {
visibleOn: visibleOn,
editorThemePath: `button1.size.\${size}.body.border`
}),
getSchemaTpl('theme:size', {
getSchemaTpl('theme:select', {
label: '图标尺寸',
name: `themeCss.iconClassName.iconSize:${state}`,
visibleOn: visibleOn,
@ -364,7 +364,7 @@ export class ButtonPlugin extends BasePlugin {
]
},
{
title: '自定义样式',
title: '基本样式',
body: [
{
type: 'select',

View File

@ -216,131 +216,140 @@ export class CarouselPlugin extends BasePlugin {
},
{
title: '外观',
body: [
getSchemaTpl('switch', {
name: 'auto',
label: '自动轮播',
pipeIn: defaultValue(true)
}),
getSchemaTpl('valueFormula', {
rendererSchema: {
type: 'input-number'
},
name: 'interval',
label: '动画间隔(ms)',
valueType: 'number',
pipeIn: defaultValue(5000)
}),
body: getSchemaTpl('collapseGroup', [
{
name: 'duration',
type: 'input-number',
label: '动画时长(ms)',
min: 100,
step: 10,
size: 'sm',
pipeIn: defaultValue(500)
},
{
name: 'animation',
label: '动画效果',
type: 'button-group-select',
pipeIn: defaultValue('fade'),
options: [
title: '基本',
body: [
getSchemaTpl('switch', {
name: 'auto',
label: '自动轮播',
pipeIn: defaultValue(true)
}),
getSchemaTpl('valueFormula', {
rendererSchema: {
type: 'input-number'
},
name: 'interval',
label: '动画间隔(ms)',
valueType: 'number',
pipeIn: defaultValue(5000)
}),
{
label: 'fade',
value: 'fade'
name: 'duration',
type: 'input-number',
label: '动画时长(ms)',
min: 100,
step: 10,
size: 'sm',
pipeIn: defaultValue(500)
},
{
label: 'slide',
value: 'slide'
}
]
},
{
name: 'controlsTheme',
label: '控制按钮主题',
type: 'button-group-select',
pipeIn: defaultValue('light'),
options: [
{
label: 'light',
value: 'light'
name: 'animation',
label: '动画效果',
type: 'button-group-select',
pipeIn: defaultValue('fade'),
options: [
{
label: 'fade',
value: 'fade'
},
{
label: 'slide',
value: 'slide'
}
]
},
{
label: 'dark',
value: 'dark'
}
]
},
{
name: 'controls',
label: '控制显示',
type: 'button-group-select',
pipeIn: defaultValue('dots,arrows'),
multiple: true,
options: [
{
label: '底部圆点',
value: 'dots'
name: 'controlsTheme',
label: '控制按钮主题',
type: 'button-group-select',
pipeIn: defaultValue('light'),
options: [
{
label: 'light',
value: 'light'
},
{
label: 'dark',
value: 'dark'
}
]
},
{
label: '左右箭头',
value: 'arrows'
}
]
},
getSchemaTpl('switch', {
name: 'alwaysShowArrow',
label: '箭头一直显示',
clearValueOnHidden: true,
hiddenOn: '!~this.controls.indexOf("arrows")',
pipeIn: defaultValue(false)
}),
{
type: 'ae-switch-more',
bulk: true,
mode: 'normal',
name: 'multiple',
label: '多图展示',
formType: 'extend',
form: {
body: [
{
name: 'multiple.count',
label: '数量',
type: 'input-number',
min: 2,
step: 1
name: 'controls',
label: '控制显示',
type: 'button-group-select',
pipeIn: defaultValue('dots,arrows'),
multiple: true,
options: [
{
label: '底部圆点',
value: 'dots'
},
{
label: '左右箭头',
value: 'arrows'
}
]
},
getSchemaTpl('switch', {
name: 'alwaysShowArrow',
label: '箭头一直显示',
clearValueOnHidden: true,
hiddenOn: '!~this.controls.indexOf("arrows")',
pipeIn: defaultValue(false)
}),
{
type: 'ae-switch-more',
bulk: true,
mode: 'normal',
name: 'multiple',
label: '多图展示',
formType: 'extend',
form: {
body: [
{
name: 'multiple.count',
label: '数量',
type: 'input-number',
min: 2,
step: 1
}
]
}
]
}
},
{
name: 'width',
type: 'input-text',
label: '宽度',
validations: 'isNumeric',
addOn: {
type: 'button',
label: 'px'
}
},
{
name: 'height',
type: 'input-text',
label: '高度',
validations: 'isNumeric',
addOn: {
type: 'button',
label: 'px'
}
},
getSchemaTpl('className')
]
},
{
name: 'width',
type: 'input-text',
label: '宽度',
validations: 'isNumeric',
addOn: {
type: 'button',
label: 'px'
}
title: '显隐',
body: [getSchemaTpl('ref'), getSchemaTpl('visible')]
},
{
name: 'height',
type: 'input-text',
label: '高度',
validations: 'isNumeric',
addOn: {
type: 'button',
label: 'px'
}
},
getSchemaTpl('className')
]
},
{
title: '显隐',
body: [getSchemaTpl('ref'), getSchemaTpl('visible')]
getSchemaTpl('theme:base', {
title: '轮播图'
}),
getSchemaTpl('theme:cssCode')
])
}
])
];

View File

@ -30,14 +30,92 @@ export class DividerPlugin extends BasePlugin {
panelBody = getSchemaTpl('tabs', [
{
title: '外观',
body: [
getSchemaTpl('layout:originPosition', {value: 'left-top'}),
getSchemaTpl('layout:width:v2', {
visibleOn:
'data.style && data.style.position && (data.style.position === "fixed" || data.style.position === "absolute")'
}),
getSchemaTpl('className')
]
body: getSchemaTpl('collapseGroup', [
{
title: '基本样式',
body: [
getSchemaTpl('layout:originPosition', {value: 'left-top'}),
getSchemaTpl('layout:width:v2', {
visibleOn:
'data.style && data.style.position && (data.style.position === "fixed" || data.style.position === "absolute")'
}),
{
mode: 'horizontal',
type: 'button-group-select',
label: '类型',
name: 'lineStyle',
value: 'dashed',
options: [
{
value: 'dashed',
label: '虚线'
},
{
value: 'solid',
label: '实线'
}
]
},
{
mode: 'horizontal',
type: 'button-group-select',
label: '方向',
name: 'direction',
value: 'horizontal',
options: [
{
value: 'horizontal',
label: '水平'
},
{
value: 'vertical',
label: '垂直'
}
]
},
getSchemaTpl('theme:select', {
mode: 'horizontal',
label: '长度',
name: 'style.width',
placeholder: '100%',
visibleOn: 'direction !== "vertical"'
}),
getSchemaTpl('theme:select', {
mode: 'horizontal',
label: '长度',
name: 'style.height',
placeholder: 'var(--sizes-base-15)',
visibleOn: 'direction === "vertical"'
}),
getSchemaTpl('theme:select', {
mode: 'horizontal',
label: '宽度',
name: 'style.borderWidth',
placeholder: '1px'
}),
getSchemaTpl('theme:colorPicker', {
mode: 'horizontal',
label: '颜色',
name: 'color',
placeholder: 'var(--colors-neutral-line-8)',
labelMode: 'input',
needGradient: true
}),
getSchemaTpl('theme:paddingAndMargin', {
name: 'style',
hidePadding: true
}),
{
mode: 'horizontal',
type: 'input-number',
label: '角度',
name: 'rotate',
value: 0
}
]
}
])
},
{
title: '显隐',

View File

@ -459,7 +459,7 @@ export class ImageControlPlugin extends BasePlugin {
[
getSchemaTpl('style:formItem', {renderer: context.info.renderer}),
{
title: '自定义样式',
title: '基本样式',
body: [
{
type: 'select',
@ -498,12 +498,12 @@ export class ImageControlPlugin extends BasePlugin {
type: 'icon-select',
returnSvg: true
},
getSchemaTpl('theme:size', {
getSchemaTpl('theme:select', {
name: `${IconCssClassName}.font-size`,
label: '图标大小',
editorThemePath: `${editorPath}.default.body.icon-size`
}),
getSchemaTpl('theme:size', {
getSchemaTpl('theme:select', {
name: `${IconCssClassName}.margin-bottom`,
label: '图标底边距',
editorThemePath: `${editorPath}.default.body.icon-margin`

View File

@ -126,9 +126,9 @@ export class IconPlugin extends BasePlugin {
title: '外观',
body: getSchemaTpl('collapseGroup', [
{
title: '自定义样式',
title: '基本样式',
body: [
getSchemaTpl('theme:size', {
getSchemaTpl('theme:select', {
label: '尺寸',
name: 'themeCss.className.font.fontSize'
}),

View File

@ -139,11 +139,17 @@ export class ImagePlugin extends BasePlugin {
// name: 'showDimensions',
// label: '显示图片尺寸'
// }),
getSchemaTpl('layout:display', {
flexHide: true,
value: 'inline-block',
label: '显示类型'
}),
{
name: 'thumbMode',
visibleOn: 'imageMode === "thumb"',
type: 'select',
label: '缩略图展示模式',
label: '展示模式',
mode: 'horizontal',
labelAlign: 'left',
horizontal: {
@ -173,31 +179,42 @@ export class ImagePlugin extends BasePlugin {
}
]
},
getSchemaTpl('theme:size', {
label: '尺寸',
name: 'themeCss.imageControlClassName.size:default'
})
]
},
getSchemaTpl('theme:base', {
classname: 'imageControlClassName',
title: '图片'
}),
{
title: '其他',
body: [
getSchemaTpl('theme:font', {
label: '标题文字',
name: 'themeCss.titleControlClassName.font:default',
editorThemePath: 'image.image.default.normal.body.font'
}),
getSchemaTpl('theme:paddingAndMargin', {
label: '标题边距',
name: 'themeCss.titleControlClassName.padding-and-margin:default'
}),
getSchemaTpl('theme:paddingAndMargin', {
label: '描述边距',
name: 'themeCss.desControlClassName.padding-and-margin:default'
}),
{
name: 'thumbRatio',
type: 'button-group-select',
label: '缩略图比率',
size: 'sm',
pipeIn: defaultValue('1:1'),
options: [
{
label: '1:1',
value: '1:1'
},
{
label: '4:3',
value: '4:3'
},
{
label: '16:9',
value: '16:9'
}
]
name: 'themeCss.iconControlClassName.--image-image-normal-icon',
label: '放大图标',
type: 'icon-select',
returnSvg: true
}
]
},
getSchemaTpl('theme:cssCode'),
{
title: 'CSS类名',
body: [

View File

@ -211,6 +211,11 @@ export class ImagesPlugin extends BasePlugin {
}
]
},
getSchemaTpl('theme:base', {
classname: 'imagesControlClassName',
title: '图片集'
}),
getSchemaTpl('theme:cssCode'),
{
title: 'CSS类名',
body: [

View File

@ -293,7 +293,31 @@ export class PagePlugin extends BasePlugin {
className: 'p-none',
body: [
getSchemaTpl('collapseGroup', [
...getSchemaTpl('theme:common', {exclude: ['layout']})
...getSchemaTpl('theme:common', {
exclude: ['layout'],
classname: 'bodyControlClassName',
baseTitle: '内容区样式',
extra: [
getSchemaTpl('theme:base', {
classname: 'headerControlClassName',
title: '标题栏样式',
extra: [
getSchemaTpl('theme:font', {
label: '文字',
name: 'font'
})
]
}),
getSchemaTpl('theme:base', {
classname: 'toolbarControlClassName',
title: '工具栏样式'
}),
getSchemaTpl('theme:base', {
classname: 'asideControlClassName',
title: '边栏样式'
})
]
})
])
]
},

View File

@ -265,7 +265,20 @@ export class TooltipWrapperPlugin extends BasePlugin {
title: '外观',
className: 'p-none',
body: getSchemaTpl('collapseGroup', [
...getSchemaTpl('style:common'),
...getSchemaTpl('theme:common', {
layoutExtra: [
getSchemaTpl('theme:size', {
label: '尺寸',
name: 'themeCss.baseControlClassName.size:default'
})
],
extra: [
getSchemaTpl('theme:base', {
classname: 'tooltipControlClassName',
title: '浮层样式'
})
]
}),
{
title: 'CSS 类名',
body: [

View File

@ -242,7 +242,12 @@ export class TplPlugin extends BasePlugin {
body: getSchemaTpl('collapseGroup', [
...getSchemaTpl('theme:common', {
exclude: ['layout'],
include: ['font']
baseExtra: [
getSchemaTpl('theme:font', {
label: '文字',
name: 'font'
})
]
})
])
},

View File

@ -2,366 +2,118 @@
* +
*/
import React, {useEffect, useRef, useState} from 'react';
import {Button, Editor, Overlay, PopOver} from 'amis-ui';
import {FormControlProps, FormItem, styleMap} from 'amis-core';
import {Editor, Overlay, PopOver} from 'amis-ui';
import {FormControlProps, FormItem} from 'amis-core';
// @ts-ignore
import {parse as cssParse} from 'amis-postcss';
import {PlainObject} from './types';
import isObject from 'lodash/isObject';
import debounce from 'lodash/debounce';
import isEmpty from 'lodash/isEmpty';
import {Icon} from '../../icons/index';
import editorFactory from './themeLanguage';
import cx from 'classnames';
const valueMap: PlainObject = {};
for (let key in styleMap) {
valueMap[styleMap[key]] = key;
const editorPlaceholder = `自定义样式仅对当前组件生效。示例:
.text-color: {
color: #fff;
}
const fontStyle = [
'color',
'font-weight',
'font-size',
'font-style',
'text-decoration',
'text-align',
'vertical-align',
'font-family',
'line-height'
];
`;
interface CssNode {
value: string;
selector: string;
}
interface CssNodeTab {
name: string;
children: CssNode[];
}
function AmisThemeCssCodeEditor(props: FormControlProps) {
const {themeClass, data} = props;
const id = data.id.replace('u:', '');
const [cssNodes, setCssNodes] = useState<CssNodeTab[]>([]);
const [tabId, setTabId] = useState(0);
function getCssAndSetValue(themeClass: any[]) {
try {
const newCssNodes: CssNodeTab[] = [];
themeClass?.forEach(n => {
const classId = n.value ? id + '-' + n.value : id;
const state = n.state || ['default'];
const className = n.className || 'className';
const dom = document.getElementById(classId || '') || null;
const content = dom?.innerHTML || '';
const ast = cssParse(content);
const nodes: any[] = [];
ast.nodes.forEach((node: any) => {
const selector = node.selector;
if (!selector.endsWith('.hover') && !selector.endsWith('.active')) {
nodes.push(node);
}
});
const css: {selector: string; value: string; state: string}[] = [];
state.forEach((s: string) => {
css.push({
selector: `.${className}-${id}${s === 'default' ? '' : ':' + s}`,
state: s,
value: ''
});
});
nodes.forEach(node => {
const style = node.nodes.map((n: any) => `${n.prop}: ${n.value};`);
const item = css.find(c => {
if (
c.selector === node.selector ||
node.selector.endsWith(`:${c.state}`) ||
node.selector.split(' ').indexOf(c.selector) > -1
) {
return c;
}
return false;
})!;
item.value = style.join('\n');
});
newCssNodes.push({
name: n.name || '自定义样式',
children: css
});
});
setCssNodes(newCssNodes);
} catch (error) {
console.error(error);
}
}
useEffect(() => {
getCssAndSetValue(themeClass);
}, []);
const editorChange = debounce((nodeTabs: CssNodeTab[]) => {
try {
const {data, onBulkChange} = props;
const sourceCss = data.themeCss || data.css || {};
const newCss: any = {};
nodeTabs.forEach(tab => {
tab.children.forEach(node => {
const nodes = cssParse(node.value)
.nodes.map((node: any) => {
const {prop, value} = node;
return {
prop,
value
};
})
.filter((n: any) => n.value);
const selector = node.selector;
const nameEtr = /\.(.*)\-/.exec(selector);
const cssCode: PlainObject = {};
let name = nameEtr ? nameEtr[1] : '';
let state = 'default';
if (!!~selector.indexOf(':active')) {
state = 'active';
} else if (!!~selector.indexOf(':hover')) {
state = 'hover';
}
nodes.forEach((item: any) => {
const prop = item.prop;
const cssValue = item.value;
if (!!~prop.indexOf('radius')) {
const type = 'radius:' + state;
!cssCode[type] && (cssCode[type] = {});
const radius = cssValue.split(' ');
cssCode[type]['top-left-border-radius'] = radius[0];
cssCode[type]['top-right-border-radius'] = radius[1];
cssCode[type]['bottom-right-border-radius'] = radius[2];
cssCode[type]['bottom-left-border-radius'] = radius[3];
} else if (!!~prop.indexOf('border')) {
!cssCode['border:' + state] && (cssCode['border:' + state] = {});
cssCode['border:' + state][valueMap[prop] || prop] = cssValue;
} else if (
!!~prop.indexOf('padding') ||
!!~prop.indexOf('margin')
) {
!cssCode['padding-and-margin:' + state] &&
(cssCode['padding-and-margin:' + state] = {});
cssCode['padding-and-margin:' + state][valueMap[prop] || prop] =
cssValue;
} else if (fontStyle.includes(prop)) {
!cssCode['font:' + state] && (cssCode['font:' + state] = {});
cssCode['font:' + state][valueMap[prop] || prop] = cssValue;
} else {
cssCode[(valueMap[prop] || prop) + ':' + state] = cssValue;
}
});
if (newCss[name]) {
newCss[name] = Object.assign(newCss[name], cssCode);
} else {
newCss[name] = cssCode;
}
});
});
onBulkChange &&
onBulkChange({
themeCss: {
...sourceCss,
...newCss
}
});
} catch (error) {
console.error(error);
}
});
function handleChange(value: string, i: number, j: number) {
const newCssNodes = cssNodes;
newCssNodes[i].children[j].value = value;
setCssNodes(newCssNodes); // 好像不需要这个?
editorChange(newCssNodes);
}
function formateTitle(title: string) {
if (title.endsWith('hover')) {
return '悬浮态样式';
} else if (title.endsWith('active')) {
return '点击态样式';
} else if (title.endsWith('disabled')) {
return '禁用态样式';
}
return '常规态样式';
}
return (
<div className="ThemeCssCode-editor">
<div className="ThemeCssCode-editor-title"></div>
<div className="ThemeCssCode-editor-close">
<Button onClick={props.onHide} level="link">
<Icon icon="close" className="icon" />
</Button>
</div>
<div className="ThemeCssCode-editor-content">
<div className="ThemeCssCode-editor-content-header">
{cssNodes.map((node, index) => {
return (
<div
key={index}
onClick={() => setTabId(index)}
className={cx(
'ThemeCssCode-editor-content-header-title',
index === tabId &&
'ThemeCssCode-editor-content-header-title--active'
)}
>
{node.name}
</div>
);
})}
</div>
<div className="ThemeCssCode-editor-content-main">
{cssNodes.map((node, i) => {
const children = node.children;
return (
<div
key={i}
className={cx(
i !== tabId && 'ThemeCssCode-editor-content-body--hidden'
)}
>
{children.map((css, j) => {
return (
<div
className="ThemeCssCode-editor-content-body"
key={`${i}-${j}-${css.selector}`}
id={`${i}-${j}-${css.selector}`}
>
{children.length > 1 ? (
<div className="ThemeCssCode-editor-content-body-title">
{formateTitle(css.selector)}
</div>
) : null}
<div className="ThemeCssCode-editor-content-body-editor">
<Editor
value={css.value}
editorFactory={editorFactory}
options={{
onChange: (value: string) =>
handleChange(value, i, j)
}}
/>
</div>
</div>
);
})}
</div>
);
})}
</div>
</div>
</div>
);
}
function AmisStyleCodeEditor(props: FormControlProps) {
function ThemeCssCode(props: FormControlProps) {
const {data, onBulkChange} = props;
const {style} = data;
const {wrapperCustomStyle} = data;
const ref = useRef<HTMLDivElement>(null);
const [showEditor, setShowEditor] = useState(false);
const [value, setValue] = useState('');
function getCssAndSetValue(data: any) {
// 前面加上空格
function getSpaceByDep(dep: number) {
let spaces = '';
for (let i = 0; i < dep; i++) {
spaces += ' ';
}
return spaces;
}
function getCssAndSetValue(data: any, str: string, dep: number) {
if (isEmpty(data)) {
return '';
}
let str = '';
for (let key in data) {
if (key === 'radius') {
str += `border-radius: ${
data.radius['top-left-border-radius'] +
' ' +
data.radius['top-right-border-radius'] +
' ' +
data.radius['bottom-right-border-radius'] +
' ' +
data.radius['bottom-left-border-radius']
};\n`;
} else {
str += `${styleMap[key] || key}: ${data[key]};\n`;
if (data.hasOwnProperty(key)) {
if (isObject(data[key])) {
str += getSpaceByDep(dep) + `${key} {\n`;
str += getCssAndSetValue(data[key], '', dep + 1);
str += getSpaceByDep(dep) + `}\n`;
if (dep === 0) {
str += '\n';
}
} else {
str += getSpaceByDep(dep) + `${key}: ${data[key]};\n`;
}
}
}
return str;
}
useEffect(() => {
const res = getCssAndSetValue(style);
setValue(res);
setValue(getCssAndSetValue(wrapperCustomStyle, '', 0));
}, []);
function handleShowEditor() {
setShowEditor(true);
}
// 递归获取自定义样式
function getStyle(style: any, newStyle: PlainObject) {
if (isEmpty(style)) {
return;
}
style.nodes.forEach((node: any) => {
const {prop, value, selector} = node;
if (value) {
newStyle[prop] = value;
}
if (node.nodes) {
!newStyle[selector] && (newStyle[selector] = {});
getStyle(node, newStyle[selector]);
}
});
}
const editorChange = debounce((value: string) => {
const newStyle: PlainObject = {};
try {
const style = cssParse(value);
style.nodes.forEach((node: any) => {
const {prop, value} = node;
if (value) {
if (prop === 'border-radius') {
const radius = value.split(' ');
newStyle['radius'] = {
'top-left-border-radius': radius[0] || '',
'top-right-border-radius': radius[1] || '',
'bottom-right-border-radius': radius[2] || '',
'bottom-left-border-radius': radius[3] || ''
};
} else {
newStyle[valueMap[prop] || prop] = value;
}
}
});
getStyle(style, newStyle);
onBulkChange &&
onBulkChange({
style: newStyle
wrapperCustomStyle: newStyle
});
} catch (error) {
console.error(error);
}
} catch (error) {}
});
function handleChange(value: string) {
editorChange(value);
setValue(value);
}
return (
<div className="ThemeCssCode-editor">
<div className="ThemeCssCode-editor-title"></div>
<div className="ThemeCssCode-editor-close">
<Button onClick={props.onHide} level="link">
<Icon icon="close" className="icon" />
</Button>
</div>
<div className="ThemeCssCode-editor-content">
<Editor
value={value}
editorFactory={editorFactory}
options={{
onChange: handleChange
}}
/>
</div>
</div>
);
}
function ThemeCssCode(props: FormControlProps) {
const ref = useRef<HTMLDivElement>(null);
const [showEditor, setShowEditor] = useState(false);
function handleShowEditor() {
setShowEditor(true);
}
return (
<>
<div ref={ref} className="ThemeCssCode">
<Button onClick={handleShowEditor} className=":ThemeCssCode-button">
<Icon icon="theme-css" className="icon" />
</Button>
<a
onClick={handleShowEditor}
className="ThemeCssCode-button ThemeCssCode-icon"
>
<Icon icon="expand-alt" className="icon" />
</a>
<div className="ThemeCssCode-editor-wrap" style={{height: '120px'}}>
<Editor
value={value}
placeholder={editorPlaceholder}
language="scss"
onChange={handleChange}
/>
</div>
</div>
<Overlay
container={document.body}
@ -371,17 +123,30 @@ function ThemeCssCode(props: FormControlProps) {
rootClose={false}
>
<PopOver overlay onHide={() => setShowEditor(false)}>
{props.isLayout ? (
<AmisStyleCodeEditor
{...props}
onHide={() => setShowEditor(false)}
/>
) : (
<AmisThemeCssCodeEditor
{...props}
onHide={() => setShowEditor(false)}
/>
)}
<div className="ThemeCssCode-editor">
<div className="ThemeCssCode-editor-title"></div>
<div className="ThemeCssCode-editor-close">
<a
onClick={() => setShowEditor(false)}
className="ThemeCssCode-icon"
>
<Icon icon="close" className="icon" />
</a>
</div>
<div className="ThemeCssCode-editor-content">
<div
className="ThemeCssCode-editor-wrap"
style={{height: '460px'}}
>
<Editor
value={value}
placeholder={editorPlaceholder}
language="scss"
onChange={handleChange}
/>
</div>
</div>
</div>
</PopOver>
</Overlay>
</>

View File

@ -6,6 +6,7 @@ import {
} from 'amis-editor-core';
import isNumber from 'lodash/isNumber';
import isString from 'lodash/isString';
import compact from 'lodash/compact';
/**
*
@ -232,8 +233,9 @@ setSchemaTpl(
isFlexItem?: boolean;
pipeIn?: (value: any, data: any) => void;
pipeOut?: (value: any, data: any) => void;
flexHide?: boolean;
}) => {
const configOptions = [
const configOptions = compact([
{
label: '块级(block)',
icon: 'block-display',
@ -249,12 +251,12 @@ setSchemaTpl(
icon: 'inline-display',
value: 'inline'
},
{
!config?.flexHide && {
label: '弹性布局(flex)',
icon: 'flex-display',
value: 'flex'
}
];
]);
const configSchema = {
type: 'icon-button-group',
label:

View File

@ -426,39 +426,17 @@ export const styleTpl = {
*/
// css类名
setSchemaTpl(
'theme:cssCode',
({
themeClass = [],
isFormItem
}: {
themeClass?: any[];
isFormItem?: boolean;
} = {}) => {
if (isFormItem) {
themeClass.push(
...[
{
name: 'description',
value: 'description',
className: 'descriptionClassName'
},
{name: 'label', value: 'label', className: 'labelClassName'}
]
);
}
return {
title: '样式源码',
body: [
{
type: 'theme-cssCode',
label: false,
themeClass
}
]
};
}
);
setSchemaTpl('theme:cssCode', () => {
return {
title: '自定义样式',
body: [
{
type: 'theme-cssCode',
label: false
}
]
};
});
// form label
setSchemaTpl('theme:form-label', () => {
@ -499,13 +477,13 @@ setSchemaTpl('theme:form-description', () => {
};
});
// 尺寸选择器
// 带提示的值输入框
setSchemaTpl('theme:select', (option: any = {}) => {
return {
mode: 'default',
type: 'amis-theme-select',
label: '尺寸',
name: `themeCss.className.size:default`,
label: '大小',
name: `themeCss.className.select:default`,
options: '${sizesOptions}',
...option
};
@ -583,56 +561,131 @@ setSchemaTpl('theme:shadow', (option: any = {}) => {
// 尺寸选择器
setSchemaTpl('theme:size', (option: any = {}) => {
return {
type: 'amis-theme-select',
mode: 'default',
type: 'amis-theme-size-editor',
label: false,
name: `css.className.size`,
name: `themeCss.className.size:default`,
options: '${sizesOptions}',
hideMinWidth: true,
...option
};
});
setSchemaTpl(
'theme:base',
(option: {
collapsed?: boolean;
extra?: any[];
classname?: string;
title?: string;
}) => {
const {
collapsed = false,
extra = [],
classname = 'baseControlClassName',
title = '基本样式'
} = option;
const styleStateFunc = (visibleOn: string, state: string) => {
return [
getSchemaTpl('theme:border', {
visibleOn: visibleOn,
name: `themeCss.${classname}.border:${state}`
}),
getSchemaTpl('theme:radius', {
visibleOn: visibleOn,
name: `themeCss.${classname}.radius:${state}`
}),
getSchemaTpl('theme:paddingAndMargin', {
visibleOn: visibleOn,
name: `themeCss.${classname}.padding-and-margin:${state}`
}),
getSchemaTpl('theme:colorPicker', {
visibleOn: visibleOn,
name: `themeCss.${classname}.background:${state}`,
label: '背景',
needCustom: true,
needGradient: true,
needImage: true,
labelMode: 'input'
}),
getSchemaTpl('theme:shadow', {
visibleOn: visibleOn,
name: `themeCss.${classname}.boxShadow:${state}`
})
].concat(
extra.map(item => {
return {
...item,
visibleOn: visibleOn,
name: `themeCss.${classname}.${item.name}:${state}`
};
})
);
};
const styles = [
{
type: 'select',
name: 'editorState',
label: '状态',
selectFirst: true,
options: [
{
label: '常规',
value: 'default'
},
{
label: '悬浮',
value: 'hover'
},
{
label: '点击',
value: 'active'
}
]
},
...styleStateFunc(
"${editorState == 'default' || !editorState}",
'default'
),
...styleStateFunc("${editorState == 'hover'}", 'hover'),
...styleStateFunc("${editorState == 'active'}", 'active')
];
return {
title,
collapsed,
body: styles
};
}
);
setSchemaTpl(
'theme:common',
(option: {
exclude: string[] | string;
include: string[];
collapsed?: boolean;
extra?: any[];
baseExtra?: any[];
layoutExtra?: any[];
classname?: string;
baseTitle?: string;
}) => {
let {exclude, include, collapsed} = option || {};
let {
exclude,
collapsed,
extra = [],
baseExtra,
layoutExtra,
classname,
baseTitle
} = option || {};
const curCollapsed = collapsed ?? false; // 默认都展开
// key统一转换成Kebab caseeg: boxShadow => bos-shadow
exclude = (
exclude ? (Array.isArray(exclude) ? exclude : [exclude]) : []
).map((key: string) => kebabCase(key));
const moreStyle =
include?.map(key =>
getSchemaTpl(`theme:${key}`, {
name: 'style'
})
) || [];
const styles = moreStyle.concat([
getSchemaTpl('theme:border', {
name: 'style'
}),
getSchemaTpl('theme:radius', {
name: 'style.radius'
}),
getSchemaTpl('theme:paddingAndMargin', {
name: 'style'
}),
getSchemaTpl('theme:colorPicker', {
name: 'style.background',
label: '背景',
needCustom: true,
needGradient: true,
needImage: true,
labelMode: 'input'
}),
getSchemaTpl('theme:shadow', {
name: 'style.boxShadow'
})
]);
return [
{
header: '布局',
@ -644,21 +697,24 @@ setSchemaTpl(
label: false,
name: 'style'
}
].filter(comp => !~exclude.indexOf(comp.type.replace(/^style-/i, '')))
]
.filter(comp => !~exclude.indexOf(comp.type.replace(/^style-/i, '')))
.concat(layoutExtra || [])
},
getSchemaTpl('theme:base', {
collapsed: curCollapsed,
extra: baseExtra,
classname,
title: baseTitle
}),
...extra,
{
title: '自定义样式',
collapsed: curCollapsed,
body: styles
},
{
title: '样式源码',
collapsed: curCollapsed,
body: [
{
type: 'theme-cssCode',
label: false,
isLayout: true
label: false
}
]
}

View File

@ -3192,7 +3192,7 @@
--steps-dot-process-bg-color: var(--colors-brand-5);
--steps-dot-finish-bg-color: var(--colors-neutral-fill-11);
--steps-dot-error-bg-color: var(--colors-error-5);
--steps-simple-icon: '';
--steps-simple-icon: '<svg viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g stroke="none" stroke-width="1" fill="currentColor" fill-rule="evenodd"><polyline stroke="currentColor" transform="translate(5.496854, 8.006854) scale(-1, 1) rotate(-135.000000) translate(-5.496854, -8.006854) " points="1.49685425 4.00685425 9.49685425 4.00685425 9.49685425 12.0068542" stroke-width="1" fill="none" stroke-linecap="butt" stroke-linejoin="round"/></g></svg>';
--steps-simple-icon-size: var(--sizes-size-8);
--Steps-bg: var(--steps-status-wait-color);
@ -3305,6 +3305,7 @@
--image-image-description-color: var(--colors-neutral-text-2);
--image-image-description-fontSize: var(--fonts-size-8);
--image-image-description-marginTop: var(--fonts-size-0);
--image-image-normal-icon: '<svg viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g stroke="none" stroke-width="1" fill="currentColor" fill-rule="evenodd"><g><rect opacity="0" x="0.5" y="0.5" width="15" height="15"></rect><path d="M7.9999,3.0001 C11.9889,3.0001 14.9999,6.8731 14.9999,8.0001 C14.9999,8.8831 11.9889,13.0001 7.9999,13.0001 C3.9609,13.0001 0.9999,8.8831 0.9999,8.0001 C0.9999,6.8731 3.9609,3.0001 7.9999,3.0001 Z M7.9999,4.0001 C4.7329,4.0001 2.2179,7.0861 2.0089,7.9731 C2.2749,8.7711 4.7189,12.0001 7.9999,12.0001 C11.2099,12.0001 13.7339,8.7311 13.9929,7.9631 C13.8069,7.1261 11.2709,4.0001 7.9999,4.0001 Z M7.975,5.879 C9.08,5.879 9.975,6.775 9.975,7.879 C9.975,8.983 9.08,9.879 7.975,9.879 C6.871,9.879 5.975,8.983 5.975,7.879 C5.975,6.775 6.871,5.879 7.975,5.879 Z M7.975,6.879 C7.424,6.879 6.975,7.327 6.975,7.879 C6.975,8.43 7.424,8.879 7.975,8.879 C8.527,8.879 8.975,8.43 8.975,7.879 C8.975,7.327 8.527,6.879 7.975,6.879 Z" ></path></g></g></svg>';
--image-images-item-marginTop: var(--sizes-size-3);
--image-images-item-marginBottom: var(--sizes-size-3);

View File

@ -1,17 +1,38 @@
.#{$ns}Divider {
margin: var(--Divider-marginTop) var(--Divider-marginRight)
var(--Divider-marginBottom) var(--Divider-marginLeft);
border-bottom: var(--Divider-width) var(--Divider-style) var(--Divider-color);
height: px2rem(2px);
font-size: 0;
&--solid {
border-bottom-style: solid;
}
&--dashed {
border-bottom-style: dashed;
}
&--horizontal.#{$ns}Divider--solid {
border-bottom-style: solid;
}
&--horizontal.#{$ns}Divider--dashed {
border-bottom-style: dashed;
}
&--vertical.#{$ns}Divider--solid {
border-left-style: solid;
}
&--vertical.#{$ns}Divider--dashed {
border-left-style: dashed;
}
&--horizontal {
border-bottom: var(--Divider-width) var(--Divider-style)
var(--Divider-color);
height: px2rem(2px);
}
&--vertical {
border-left: var(--Divider-width) var(--Divider-style) var(--Divider-color);
height: var(--sizes-base-15);
display: inline-block;
}
}
/* 移动端样式调整 */

View File

@ -176,6 +176,11 @@
padding: 0 5px;
line-height: 1;
font-size: px2rem(16px);
svg {
width: px2rem(16px);
height: px2rem(16px);
}
}
}
@ -196,3 +201,7 @@
position: relative;
@include clearfix();
}
.Image-view-icon {
content: var(--image-image-normal-icon);
}

View File

@ -22,54 +22,6 @@
border-color: var(--Form-input-onFocused-borderColor);
}
.#{$ns}MonacoEditor {
position: relative;
&-header {
position: absolute;
right: px2rem(14px);
top: 0;
padding: 0;
width: px2rem(18px);
height: px2rem(18px);
z-index: 5;
display: flex;
flex-flow: row nowrap;
justify-content: center;
align-items: center;
background: var(--Modal-header-bg);
box-sizing: border-box;
}
&-fullscreen {
cursor: pointer;
color: var(--Modal-close-color);
line-height: inherit;
text-decoration: none;
text-align: center;
vertical-align: middle;
&:hover {
color: var(--Model-close-onHover-color);
svg {
fill: var(--Model-close-onHover-color);
}
}
}
&-placeholder {
pointer-events: none;
top: 0;
position: absolute;
left: px2rem(65px);
right: px2rem(16px);
white-space: break-spaces;
line-break: anywhere;
color: var(--Form-input-placeholderColor);
}
}
&--sm {
min-height: 100px;
@ -115,6 +67,54 @@
}
}
.#{$ns}MonacoEditor {
position: relative;
&-header {
position: absolute;
right: px2rem(14px);
top: 0;
padding: 0;
width: px2rem(18px);
height: px2rem(18px);
z-index: 5;
display: flex;
flex-flow: row nowrap;
justify-content: center;
align-items: center;
background: var(--Modal-header-bg);
box-sizing: border-box;
}
&-fullscreen {
cursor: pointer;
color: var(--Modal-close-color);
line-height: inherit;
text-decoration: none;
text-align: center;
vertical-align: middle;
&:hover {
color: var(--Model-close-onHover-color);
svg {
fill: var(--Model-close-onHover-color);
}
}
}
&-placeholder {
pointer-events: none;
top: 0;
position: absolute;
left: px2rem(65px);
right: px2rem(16px);
white-space: break-spaces;
line-break: anywhere;
color: var(--Form-input-placeholderColor);
}
}
.monaco-inputbox > .wrapper {
padding: 0;
}

View File

@ -290,8 +290,8 @@ export function Icon({
// 从css变量中获取icon
function refFn(dom: any) {
if (dom) {
const style = getComputedStyle(dom);
const svgStr = style.getPropertyValue('content');
const domStyle = getComputedStyle(dom);
const svgStr = domStyle.getPropertyValue('content');
const svg = /(<svg.*<\/svg>)/.exec(svgStr);
if (svg) {

View File

@ -223,7 +223,7 @@ test('Picker filter1', async () => {
fireEvent.click(pickerBtn);
await wait(500);
await wait(1000);
const a = container.querySelector('input[name="a"]')!;
const b = container.querySelector('input[name="b"]')!;

View File

@ -1631,7 +1631,7 @@ exports[`Renderer:combo with items & multiLine 1`] = `
/>
</div>
<div
class="cxd-Divider cxd-Divider--dashed"
class="cxd-Divider cxd-Divider--dashed cxd-Divider--horizontal"
/>
<div
class="cxd-Form-item cxd-Form-item--horizontal"

View File

@ -180,7 +180,7 @@ exports[`Renderers:Action all levels 1`] = `
</span>
</button>
<div
class="cxd-Divider cxd-Divider--dashed"
class="cxd-Divider cxd-Divider--dashed cxd-Divider--horizontal"
/>
<div
class="cxd-Button cxd-Button--link cxd-Button--size-default is-disabled"

View File

@ -3,6 +3,7 @@
exports[`Renderer:flex 1`] = `
<div>
<div
class="cxd-Flex"
style="display: flex; flex-direction: row; justify-content: center; align-items: stretch; align-content: center;"
>
<span
@ -62,6 +63,7 @@ exports[`Renderer:flex justify 1`] = `
</span>
</span>
<div
class="cxd-Flex"
style="display: flex; flex-direction: row; justify-content: center; align-items: stretch; align-content: center;"
>
<span
@ -103,6 +105,7 @@ exports[`Renderer:flex justify 1`] = `
</span>
</span>
<div
class="cxd-Flex"
style="display: flex; flex-direction: row; justify-content: flex-start; align-items: stretch; align-content: center;"
>
<span
@ -144,6 +147,7 @@ exports[`Renderer:flex justify 1`] = `
</span>
</span>
<div
class="cxd-Flex"
style="display: flex; flex-direction: row; justify-content: flex-end; align-items: stretch; align-content: center;"
>
<span
@ -185,6 +189,7 @@ exports[`Renderer:flex justify 1`] = `
</span>
</span>
<div
class="cxd-Flex"
style="display: flex; flex-direction: row; justify-content: space-around; align-items: stretch; align-content: center;"
>
<span
@ -226,6 +231,7 @@ exports[`Renderer:flex justify 1`] = `
</span>
</span>
<div
class="cxd-Flex"
style="display: flex; flex-direction: row; justify-content: space-between; align-items: stretch; align-content: center;"
>
<span
@ -267,6 +273,7 @@ exports[`Renderer:flex justify 1`] = `
</span>
</span>
<div
class="cxd-Flex"
style="display: flex; flex-direction: row; justify-content: space-evenly; align-items: stretch; align-content: center;"
>
<span

View File

@ -108,7 +108,7 @@ exports[`Renderer:gridnav 1`] = `
</div>
</div>
<div
class="cxd-Divider cxd-Divider--dashed"
class="cxd-Divider cxd-Divider--dashed cxd-Divider--horizontal"
/>
<div
class="cxd-GridNav cxd-GridNav-top u-hairline"

View File

@ -3,7 +3,7 @@
exports[`Renderer:iframe 1`] = `
<div>
<iframe
class="b-a"
class="cxd-IFrame b-a"
frameborder="0"
src="https://www.baidu.com"
style="width: 500px; height: 500px;"
@ -14,7 +14,7 @@ exports[`Renderer:iframe 1`] = `
exports[`Renderer:iframe-escape 1`] = `
<div>
<iframe
class="b-a"
class="cxd-IFrame b-a"
frameborder="0"
src="https://www.baidu.com/?s=%25f"
style="width: 500px; height: 500px;"
@ -38,7 +38,7 @@ exports[`Renderer:iframe-var 1`] = `
role="page-body"
>
<iframe
class="b-a"
class="cxd-IFrame b-a"
frameborder="0"
src="https://www.baidu.com"
style="width: 500px; height: 500px;"

View File

@ -472,7 +472,7 @@ exports[`Renderer:images images:basic 1`] = `
</div>
</div>
<div
class="cxd-Divider cxd-Divider--dashed"
class="cxd-Divider cxd-Divider--dashed cxd-Divider--horizontal"
/>
<div
class="cxd-ImagesField"

View File

@ -641,6 +641,7 @@ test('api:responseData2', async () => {
expect(fetcher).toHaveBeenCalledTimes(1);
expect(container.querySelector('input[name="id"]')).toBeInTheDocument();
expect((container.querySelector('input[name="id"]') as any).value).toBe('1');
await wait(500);
expect(container.querySelector('input[name="id2"]')).toBeInTheDocument();
expect((container.querySelector('input[name="id2"]') as any).value).toBe('2');

View File

@ -792,6 +792,7 @@ export class Action extends React.Component<ActionProps, ActionState> {
classPrefix: ns,
loadingConfig,
themeCss,
wrapperCustomStyle,
css,
id,
env
@ -860,7 +861,8 @@ export class Action extends React.Component<ActionProps, ActionState> {
<Button
loadingConfig={loadingConfig}
className={cx(className, {
[activeClassName || 'is-active']: isActive
[activeClassName || 'is-active']: isActive,
[`wrapperCustomStyle-${id?.replace('u:', '')}`]: wrapperCustomStyle
})}
style={style}
size={size}
@ -905,17 +907,7 @@ export class Action extends React.Component<ActionProps, ActionState> {
},
active: {suf: ':not(:disabled):not(.is-disabled)'}
}
}
],
id
}}
env={env}
/>
{/* button图标自定义样式 */}
<CustomStyle
config={{
themeCss: themeCss || css,
classNames: [
},
{
key: 'iconClassName',
value: iconClassName,
@ -934,6 +926,8 @@ export class Action extends React.Component<ActionProps, ActionState> {
}
}
],
wrapperCustomStyle,
componentId: id,
id
}}
env={env}

View File

@ -12,7 +12,8 @@ import {
createObject,
isObject,
isArrayChildrenModified,
getPropValue
getPropValue,
CustomStyle
} from 'amis-core';
import {ActionObject} from 'amis-core';
import {Icon} from 'amis-ui';
@ -457,7 +458,12 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
duration,
multiple,
alwaysShowArrow,
icons
icons,
id,
wrapperCustomStyle,
env,
themeCss,
baseControlClassName
} = this.props;
const {options, current, nextAnimation} = this.state;
@ -608,7 +614,11 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
className={cx(
`Carousel Carousel--${controlsTheme}`,
{['Carousel-arrow--always']: !!alwaysShowArrow},
className
className,
baseControlClassName,
wrapperCustomStyle
? `wrapperCustomStyle-${id?.replace('u:', '')}`
: ''
)}
style={carouselStyles}
>
@ -641,6 +651,20 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
)}
</div>
) : null}
<CustomStyle
config={{
wrapperCustomStyle,
componentId: id,
themeCss,
classNames: [
{
key: 'baseControlClassName',
value: baseControlClassName
}
]
}}
env={env}
/>
</div>
);
}

View File

@ -6,7 +6,8 @@ import {
Renderer,
RendererProps,
loadScript,
buildStyle
buildStyle,
CustomStyle
} from 'amis-core';
import {ServiceStore, IServiceStore} from 'amis-core';
@ -593,7 +594,12 @@ export class Chart extends React.Component<ChartProps> {
height,
classPrefix: ns,
unMountOnHidden,
data
data,
id,
wrapperCustomStyle,
env,
themeCss,
baseControlClassName
} = this.props;
let style = this.props.style || {};
style.width = style.width || width || '100%';
@ -601,7 +607,17 @@ export class Chart extends React.Component<ChartProps> {
const styleVar = buildStyle(style, data);
return (
<div className={cx(`${ns}Chart`, className)} style={styleVar}>
<div
className={cx(
`${ns}Chart`,
className,
baseControlClassName,
wrapperCustomStyle
? `wrapperCustomStyle-${id?.replace('u:', '')}`
: ''
)}
style={styleVar}
>
<LazyComponent
unMountOnHidden={unMountOnHidden}
placeholder="..." // 之前那个 spinner 会导致 sensor 失效
@ -609,6 +625,20 @@ export class Chart extends React.Component<ChartProps> {
<div className={`${ns}Chart-content`} ref={this.refFn}></div>
)}
/>
<CustomStyle
config={{
wrapperCustomStyle,
componentId: id,
themeCss,
classNames: [
{
key: 'baseControlClassName',
value: baseControlClassName
}
]
}}
env={env}
/>
</div>
);
}

View File

@ -6,7 +6,8 @@ import {
autobind,
buildStyle,
isPureVariable,
resolveVariableAndFilter
resolveVariableAndFilter,
CustomStyle
} from 'amis-core';
import {DndContainer as DndWrapper} from 'amis-ui';
import {BaseSchema, SchemaClassName, SchemaCollection} from '../Schema';
@ -188,7 +189,12 @@ export default class Container<T> extends React.Component<
style,
data,
draggable,
draggableConfig
draggableConfig,
id,
wrapperCustomStyle,
env,
themeCss,
baseControlClassName
} = this.props;
const finalDraggable: boolean = isPureVariable(draggable)
? resolveVariableAndFilter(draggable, data, '| raw')
@ -206,7 +212,11 @@ export default class Container<T> extends React.Component<
className={cx(
'Container',
size && size !== 'none' ? `Container--${size}` : '',
className
className,
baseControlClassName,
wrapperCustomStyle
? `wrapperCustomStyle-${id?.replace('u:', '')}`
: ''
)}
onClick={this.handleClick}
onMouseEnter={this.handleMouseEnter}
@ -214,6 +224,20 @@ export default class Container<T> extends React.Component<
style={buildStyle(style, data)}
>
{this.renderBody()}
<CustomStyle
config={{
wrapperCustomStyle,
componentId: id,
themeCss,
classNames: [
{
key: 'baseControlClassName',
value: baseControlClassName
}
]
}}
env={env}
/>
</Component>
);

View File

@ -9,6 +9,9 @@ import {BaseSchema} from '../Schema';
export interface DividerSchema extends BaseSchema {
type: 'divider';
lineStyle?: 'dashed' | 'solid';
direction?: 'horizontal' | 'vertical';
color?: string;
rotate?: number;
[propName: string]: any;
}
@ -23,15 +26,41 @@ export default class Divider extends React.Component<DividerProps, object> {
};
render() {
const {classnames: cx, className, style, lineStyle} = this.props;
const {
classnames: cx,
className,
style = {},
lineStyle,
direction,
color,
rotate
} = this.props;
const borderColor: any = {};
if (color) {
// 处理渐变色的情况
if (~color?.indexOf('linear-gradient')) {
borderColor.borderImage = color + ' 10';
} else {
borderColor.borderColor = color;
}
}
let transform;
if (rotate) {
transform = `${style?.transform || ''} rotate(${rotate}deg)`;
}
return (
<div
className={cx(
'Divider',
lineStyle ? `Divider--${lineStyle}` : '',
direction === 'vertical'
? 'Divider--vertical'
: 'Divider--horizontal',
className
)}
style={style}
style={{...style, ...borderColor, transform}}
/>
);
}

View File

@ -3,7 +3,7 @@
*/
import React from 'react';
import {buildStyle, Renderer, RendererProps} from 'amis-core';
import {buildStyle, Renderer, RendererProps, CustomStyle} from 'amis-core';
import {Schema} from 'amis-core';
import {BaseSchema, SchemaCollection, SchemaObject} from '../Schema';
@ -100,7 +100,13 @@ export default class Flex extends React.Component<FlexProps, object> {
className,
render,
disabled,
data
data,
id,
wrapperCustomStyle,
env,
themeCss,
baseControlClassName,
classnames: cx
} = this.props;
const styleVar = buildStyle(style, data);
const flexStyle = {
@ -121,7 +127,17 @@ export default class Flex extends React.Component<FlexProps, object> {
}
return (
<div style={flexStyle} className={className}>
<div
style={flexStyle}
className={cx(
'Flex',
className,
baseControlClassName,
wrapperCustomStyle
? ` wrapperCustomStyle-${id?.replace('u:', '')}`
: ''
)}
>
{(Array.isArray(items) ? items : items ? [items] : []).map(
(item, key) =>
render(`flexItem/${key}`, item, {
@ -129,6 +145,20 @@ export default class Flex extends React.Component<FlexProps, object> {
disabled: (item as SchemaObject)?.disabled ?? disabled
})
)}
<CustomStyle
config={{
wrapperCustomStyle,
componentId: id,
themeCss,
classNames: [
{
key: 'baseControlClassName',
value: baseControlClassName
}
]
}}
env={env}
/>
</div>
);
}

View File

@ -2001,16 +2001,7 @@ export default class ImageControl extends React.Component<
{
key: 'inputImageControlClassName',
value: inputImageControlClassName
}
],
id
}}
env={env}
/>
<CustomStyle
config={{
themeCss,
classNames: [
},
{
key: 'addBtnControlClassName',
value: addBtnControlClassName,
@ -2022,16 +2013,7 @@ export default class ImageControl extends React.Component<
suf: ':not(:disabled):not(.is-disabled)'
}
}
}
],
id: id + '-addOn'
}}
env={env}
/>
<CustomStyle
config={{
themeCss,
classNames: [
},
{
key: 'iconControlClassName',
value: iconControlClassName,
@ -2042,7 +2024,7 @@ export default class ImageControl extends React.Component<
}
}
],
id: id + '-icon'
id
}}
env={env}
/>

View File

@ -1,5 +1,11 @@
import React from 'react';
import {FormHorizontal, Renderer, RendererProps, buildStyle} from 'amis-core';
import {
FormHorizontal,
Renderer,
RendererProps,
buildStyle,
CustomStyle
} from 'amis-core';
import pick from 'lodash/pick';
import {BaseSchema, SchemaClassName, SchemaCollection} from '../Schema';
@ -201,7 +207,12 @@ export default class Grid<T> extends React.Component<GridProps & T, object> {
align: hAlign,
loading = false,
loadingConfig,
data
data,
id,
wrapperCustomStyle,
env,
themeCss,
baseControlClassName
} = this.props;
const styleVar = buildStyle(style, data);
return (
@ -213,12 +224,30 @@ export default class Grid<T> extends React.Component<GridProps & T, object> {
[`Grid--v${ucFirst(vAlign)}`]: vAlign,
[`Grid--h${ucFirst(hAlign)}`]: hAlign
},
className
className,
baseControlClassName,
wrapperCustomStyle
? `wrapperCustomStyle-${id?.replace('u:', '')}`
: ''
)}
style={styleVar}
>
{this.renderColumns(this.props.columns)}
<Spinner loadingConfig={loadingConfig} overlay show={loading} />
<CustomStyle
config={{
wrapperCustomStyle,
componentId: id,
themeCss,
classNames: [
{
key: 'baseControlClassName',
value: baseControlClassName
}
]
}}
env={env}
/>
</div>
);
}

View File

@ -4,7 +4,8 @@ import {
OnEventProps,
Renderer,
RendererProps,
runActions
runActions,
CustomStyle
} from 'amis-core';
import {filter} from 'amis-core';
import {autobind, createObject} from 'amis-core';
@ -222,7 +223,12 @@ export default class IFrame extends React.Component<IFrameProps, object> {
sandbox,
referrerpolicy,
translate: __,
env
id,
wrapperCustomStyle,
env,
themeCss,
baseControlClassName,
classnames: cx
} = this.props;
let tempStyle: any = {};
@ -252,18 +258,41 @@ export default class IFrame extends React.Component<IFrameProps, object> {
}
return (
<iframe
name={name}
className={className}
frameBorder={frameBorder}
style={style}
ref={this.IFrameRef}
onLoad={this.onLoad}
src={finalSrc}
allow={allow}
referrerPolicy={referrerpolicy}
sandbox={sandbox}
/>
<>
<iframe
name={name}
className={cx(
'IFrame',
className,
baseControlClassName,
wrapperCustomStyle
? ` wrapperCustomStyle-${id?.replace('u:', '')}`
: ''
)}
frameBorder={frameBorder}
style={style}
ref={this.IFrameRef}
onLoad={this.onLoad}
src={finalSrc}
allow={allow}
referrerPolicy={referrerpolicy}
sandbox={sandbox}
/>
<CustomStyle
config={{
wrapperCustomStyle,
componentId: id,
themeCss,
classNames: [
{
key: 'baseControlClassName',
value: baseControlClassName
}
]
}}
env={env}
/>
</>
);
}
}

View File

@ -1,5 +1,5 @@
import React from 'react';
import {Renderer, RendererProps} from 'amis-core';
import {Renderer, RendererProps, CustomStyle} from 'amis-core';
import {filter} from 'amis-core';
import {themeable, ThemeProps} from 'amis-core';
import {autobind, getPropValue} from 'amis-core';
@ -165,6 +165,10 @@ export interface ImageThumbProps
index?: number;
onLoad?: React.EventHandler<any>;
overlays?: JSX.Element;
imageControlClassName?: string;
titleControlClassName?: string;
desControlClassName?: string;
iconControlClassName?: string;
}
interface ImageThumbState {
@ -235,7 +239,11 @@ export class ImageThumb extends React.Component<
enlargeAble,
translate: __,
overlays,
imageMode
imageMode,
imageControlClassName,
titleControlClassName,
desControlClassName,
iconControlClassName
} = this.props;
const {imageLoading} = this.state;
@ -270,8 +278,13 @@ export class ImageThumb extends React.Component<
data-position="bottom"
target="_blank"
onClick={this.handleEnlarge}
className={iconControlClassName}
>
<Icon icon="view" className="icon" />
<Icon
icon="view"
className="icon"
iconContent="Image-view-icon"
/>
</a>
) : null}
{overlays}
@ -283,7 +296,8 @@ export class ImageThumb extends React.Component<
className={cx(
'Image',
imageMode === 'original' ? 'Image--original' : 'Image--thumb',
className
className,
imageControlClassName
)}
style={href ? undefined : style} // 避免重复设置style
>
@ -320,12 +334,18 @@ export class ImageThumb extends React.Component<
{title || caption ? (
<div key="caption" className={cx('Image-info')}>
{title ? (
<div className={cx('Image-title')} title={title}>
<div
className={cx('Image-title', titleControlClassName)}
title={title}
>
{title}
</div>
) : null}
{caption ? (
<div className={cx('Image-caption')} title={caption}>
<div
className={cx('Image-caption', desControlClassName)}
title={caption}
>
{caption}
</div>
) : null}
@ -467,7 +487,15 @@ export class ImageField extends React.Component<ImageFieldProps, object> {
placeholder,
originalSrc,
enlargeAble,
imageMode
imageMode,
wrapperCustomStyle,
id,
themeCss,
imageControlClassName,
titleControlClassName,
desControlClassName,
iconControlClassName,
env
} = this.props;
const finnalSrc = src ? filter(src, data, '| raw') : '';
@ -481,7 +509,10 @@ export class ImageField extends React.Component<ImageFieldProps, object> {
imageMode === 'original'
? 'ImageField--original'
: 'ImageField--thumb',
className
className,
wrapperCustomStyle
? `wrapperCustomStyle-${id?.replace('u:', '')}`
: ''
)}
style={style}
onClick={this.handleClick}
@ -507,6 +538,32 @@ export class ImageField extends React.Component<ImageFieldProps, object> {
) : (
<span className="text-muted">{placeholder}</span>
)}
<CustomStyle
config={{
wrapperCustomStyle,
componentId: id,
themeCss,
classNames: [
{
key: 'imageControlClassName',
value: imageControlClassName
},
{
key: 'titleControlClassName',
value: titleControlClassName
},
{
key: 'desControlClassName',
value: desControlClassName
},
{
key: 'iconControlClassName',
value: iconControlClassName
}
]
}}
env={env}
/>
</div>
);
}

View File

@ -1,5 +1,5 @@
import React from 'react';
import {Renderer, RendererProps} from 'amis-core';
import {Renderer, RendererProps, CustomStyle} from 'amis-core';
import {filter} from 'amis-core';
import {
resolveVariable,
@ -191,7 +191,12 @@ export class ImagesField extends React.Component<ImagesProps> {
options,
showToolbar,
toolbarActions,
imageGallaryClassName
imageGallaryClassName,
id,
wrapperCustomStyle,
env,
themeCss,
imagesControlClassName
} = this.props;
let value: any;
@ -217,7 +222,17 @@ export class ImagesField extends React.Component<ImagesProps> {
this.list = list;
return (
<div className={cx('ImagesField', className)} style={style}>
<div
className={cx(
'ImagesField',
className,
imagesControlClassName,
wrapperCustomStyle
? `wrapperCustomStyle-${id?.replace('u:', '')}`
: ''
)}
style={style}
>
{Array.isArray(list) ? (
<div className={cx('Images', listClassName)}>
{list.map((item: any, index: number) => (
@ -259,6 +274,20 @@ export class ImagesField extends React.Component<ImagesProps> {
) : (
placeholder
)}
<CustomStyle
config={{
wrapperCustomStyle,
componentId: id,
themeCss,
classNames: [
{
key: 'imagesControlClassName',
value: imagesControlClassName
}
]
}}
env={env}
/>
</div>
);
}

View File

@ -9,7 +9,8 @@ import {
ActionObject,
Location,
ApiObject,
FunctionPropertyNames
FunctionPropertyNames,
CustomStyle
} from 'amis-core';
import {filter, evalExpression} from 'amis-core';
import {
@ -781,6 +782,8 @@ export default class Page extends React.Component<PageProps> {
remark,
remarkPlacement,
headerClassName,
headerControlClassName,
toolbarControlClassName,
toolbarClassName,
toolbar,
render,
@ -803,7 +806,9 @@ export default class Page extends React.Component<PageProps> {
Array.isArray(regions) ? ~regions.indexOf('header') : title || subTitle
) {
header = (
<div className={cx(`Page-header`, headerClassName)}>
<div
className={cx(`Page-header`, headerClassName, headerControlClassName)}
>
{title ? (
<h2 className={cx('Page-title')}>
{render('title', title, subProps)}
@ -828,7 +833,13 @@ export default class Page extends React.Component<PageProps> {
if (Array.isArray(regions) ? ~regions.indexOf('toolbar') : toolbar) {
right = (
<div className={cx(`Page-toolbar`, toolbarClassName)}>
<div
className={cx(
`Page-toolbar`,
toolbarClassName,
toolbarControlClassName
)}
>
{render('toolbar', toolbar || '', subProps)}
</div>
);
@ -865,7 +876,15 @@ export default class Page extends React.Component<PageProps> {
pullRefresh,
mobileUI,
translate: __,
loadingConfig
loadingConfig,
id,
wrapperCustomStyle,
env,
themeCss,
bodyControlClassName,
headerControlClassName,
toolbarControlClassName,
asideControlClassName
} = this.props;
const subProps = {
@ -887,7 +906,10 @@ export default class Page extends React.Component<PageProps> {
<div className={cx('Page-main')}>
{this.renderHeader()}
{/* role 用于 editor 定位 Spinner */}
<div className={cx(`Page-body`, bodyClassName)} role="page-body">
<div
className={cx(`Page-body`, bodyClassName, bodyControlClassName)}
role="page-body"
>
<Spinner
size="lg"
overlay
@ -916,7 +938,14 @@ export default class Page extends React.Component<PageProps> {
return (
<div
className={cx(`Page`, hasAside ? `Page--withSidebar` : '', className)}
className={cx(
`Page`,
hasAside ? `Page--withSidebar` : '',
className,
wrapperCustomStyle
? `wrapperCustomStyle-${id?.replace('u:', '')}`
: ''
)}
onClick={this.handleClick}
style={styleVar}
>
@ -925,7 +954,8 @@ export default class Page extends React.Component<PageProps> {
className={cx(
`Page-aside`,
asideResizor ? 'relative' : 'Page-aside--withWidth',
asideClassName
asideClassName,
asideControlClassName
)}
>
<div className={cx(`Page-asideInner`)} ref={this.asideInner}>
@ -995,6 +1025,32 @@ export default class Page extends React.Component<PageProps> {
onQuery: initApi ? this.handleQuery : undefined
}
)}
<CustomStyle
config={{
wrapperCustomStyle,
componentId: id,
themeCss,
classNames: [
{
key: 'bodyControlClassName',
value: bodyControlClassName
},
{
key: 'headerControlClassName',
value: headerControlClassName
},
{
key: 'toolbarControlClassName',
value: toolbarControlClassName
},
{
key: 'asideControlClassName',
value: asideControlClassName
}
]
}}
env={env}
/>
</div>
);
}

View File

@ -3,7 +3,12 @@
*/
import React from 'react';
import {Renderer, RendererProps, resolveMappingObject} from 'amis-core';
import {
Renderer,
RendererProps,
resolveMappingObject,
CustomStyle
} from 'amis-core';
import {BaseSchema, SchemaObject} from '../Schema';
// 为了方便编辑器,目前考虑不区分 th 和 td但因为可以控制展现所以能实现一样的效果同时后续这个组件还承担复杂布局的功能不适合用 th
@ -261,16 +266,47 @@ export default class TableView extends React.Component<TableViewProps, object> {
}
render() {
const {width, trs = [], classnames: cx, className} = this.props;
const {
width,
trs = [],
classnames: cx,
className,
id,
wrapperCustomStyle,
env,
themeCss,
baseControlClassName
} = this.props;
return (
<table
className={cx('TableView', className)}
className={cx(
'TableView',
className,
baseControlClassName,
wrapperCustomStyle
? `wrapperCustomStyle-${id?.replace('u:', '')}`
: ''
)}
style={{width: width, borderCollapse: 'collapse'}}
>
{this.renderCaption()}
{this.renderCols()}
<tbody>{this.renderTrs(trs)}</tbody>
<CustomStyle
config={{
wrapperCustomStyle,
componentId: id,
themeCss,
classNames: [
{
key: 'baseControlClassName',
value: baseControlClassName
}
]
}}
env={env}
/>
</table>
);
}

View File

@ -1,5 +1,5 @@
import React from 'react';
import {Renderer, RendererProps} from 'amis-core';
import {Renderer, RendererProps, CustomStyle} from 'amis-core';
import {BaseSchema, SchemaCollection} from '../Schema';
import {filter} from 'amis-core';
import {escapeHtml} from 'amis-core';
@ -191,7 +191,10 @@ export default class TooltipWrapper extends React.Component<
inline,
style,
data,
wrap
wrap,
baseControlClassName,
wrapperCustomStyle,
id
} = this.props;
const Comp =
(wrapperComponent as keyof JSX.IntrinsicElements) ||
@ -199,9 +202,17 @@ export default class TooltipWrapper extends React.Component<
return (
<Comp
className={cx('TooltipWrapper', className, {
'TooltipWrapper--inline': inline
})}
className={cx(
'TooltipWrapper',
className,
{
'TooltipWrapper--inline': inline
},
baseControlClassName,
wrapperCustomStyle
? `wrapperCustomStyle-${id?.replace('u:', '')}`
: ''
)}
style={buildStyle(style, data)}
>
{render('body', body)}
@ -231,7 +242,12 @@ export default class TooltipWrapper extends React.Component<
enterable,
data,
env,
popOverContainer
popOverContainer,
wrapperCustomStyle,
id,
themeCss,
baseControlClassName,
tooltipControlClassName
} = this.props;
const tooltipObj: TooltipObject = {
@ -246,7 +262,9 @@ export default class TooltipWrapper extends React.Component<
? container
: popOverContainer || env?.getModalContainer,
tooltipTheme,
tooltipClassName,
tooltipClassName: tooltipControlClassName
? tooltipClassName + ' ' + tooltipControlClassName
: tooltipClassName,
mouseEnterDelay,
mouseLeaveDelay,
offset,
@ -257,9 +275,33 @@ export default class TooltipWrapper extends React.Component<
};
return (
<TooltipWrapperComp classPrefix={ns} classnames={cx} tooltip={tooltipObj}>
{this.renderBody()}
</TooltipWrapperComp>
<>
<TooltipWrapperComp
classPrefix={ns}
classnames={cx}
tooltip={tooltipObj}
>
{this.renderBody()}
</TooltipWrapperComp>
<CustomStyle
config={{
wrapperCustomStyle,
componentId: id,
themeCss,
classNames: [
{
key: 'baseControlClassName',
value: baseControlClassName
},
{
key: 'tooltipControlClassName',
value: tooltipControlClassName
}
]
}}
env={env}
/>
</>
);
}
}

View File

@ -1,5 +1,11 @@
import React from 'react';
import {autobind, createObject, Renderer, RendererProps} from 'amis-core';
import {
autobind,
createObject,
Renderer,
RendererProps,
CustomStyle
} from 'amis-core';
import {filter, asyncFilter} from 'amis-core';
import cx from 'classnames';
import {anyChanged, getPropValue} from 'amis-core';
@ -189,14 +195,25 @@ export class Tpl extends React.Component<TplProps, TplState> {
style,
showNativeTitle,
data,
env
id,
wrapperCustomStyle,
env,
themeCss,
baseControlClassName
} = this.props;
const Component = wrapperComponent || (inline ? 'span' : 'div');
const {content} = this.state;
return (
<Component
className={cx('TplField', className)}
className={cx(
'TplField',
className,
baseControlClassName,
wrapperCustomStyle
? `wrapperCustomStyle-${id?.replace('u:', '')}`
: ''
)}
style={buildStyle(style, data)}
{...(showNativeTitle ? {title: this.getTitle(content)} : {})}
onClick={this.handleClick}
@ -206,6 +223,20 @@ export class Tpl extends React.Component<TplProps, TplState> {
<span
dangerouslySetInnerHTML={{__html: env.filterHtml(content)}}
></span>
<CustomStyle
config={{
wrapperCustomStyle,
componentId: id,
themeCss,
classNames: [
{
key: 'baseControlClassName',
value: baseControlClassName
}
]
}}
env={env}
/>
</Component>
);
}