docs(claude): 添加新规则 - Freehand 元素属性设置需要自定义 callback
This commit is contained in:
1 parent
3ad84006e3
commit
e27bf28ec3
10 files changed
+99
-22
No files matched your search
@@ -1406,6 +1406,33 @@ function applyCursorStyle(board: PlaitBoard, size: number) {
|
||||
|
||||
**检查方法**: 如果 JavaScript 设置的样式不生效,在浏览器开发者工具中检查元素样式,查看是否有 `!important` 规则覆盖。
|
||||
|
||||
### Freehand 元素属性设置需要自定义 callback
|
||||
|
||||
**场景**: 修改 Freehand(手绘线条)元素的属性(如 strokeStyle、strokeColor)时
|
||||
|
||||
❌ **错误示例**:
|
||||
```typescript
|
||||
// 错误:直接使用 PropertyTransforms,Freehand 元素可能不被正确处理
|
||||
const setStrokeStyle = (style: StrokeStyle) => {
|
||||
PropertyTransforms.setStrokeStyle(board, style, { getMemorizeKey });
|
||||
};
|
||||
```
|
||||
|
||||
✅ **正确示例**:
|
||||
```typescript
|
||||
// 正确:使用 callback 确保所有选中元素都被处理
|
||||
export const setStrokeStyle = (board: PlaitBoard, strokeStyle: StrokeStyle) => {
|
||||
PropertyTransforms.setStrokeStyle(board, strokeStyle, {
|
||||
getMemorizeKey,
|
||||
callback: (element: PlaitElement, path: Path) => {
|
||||
Transforms.setNode(board, { strokeStyle }, path);
|
||||
},
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
**原因**: `PropertyTransforms` 的默认行为可能不会处理所有类型的元素(如自定义的 Freehand 元素)。通过提供 `callback` 函数,可以确保对所有选中的元素执行属性设置操作。颜色设置(`setStrokeColor`、`setFillColor`)也使用了相同的模式。
|
||||
|
||||
### 性能指南
|
||||
- 使用 `React.lazy` 对大型组件进行代码分割
|
||||
- 对图片实现懒加载和预加载
|
||||
|
||||
@@ -71,7 +71,7 @@ export const PopupFillButton: React.FC<PopupFillButtonProps> = ({
|
||||
<PopoverContent container={container}>
|
||||
<Island
|
||||
padding={4}
|
||||
className={classNames(`${ATTACHED_ELEMENT_CLASS_NAME}`)}
|
||||
className={classNames(`${ATTACHED_ELEMENT_CLASS_NAME}`, 'fill-setting')}
|
||||
>
|
||||
<UnifiedColorPicker
|
||||
value={currentColor}
|
||||
|
||||
@@ -41,4 +41,15 @@
|
||||
justify-content: space-between;
|
||||
padding: 0 8px;
|
||||
}
|
||||
// 限制拾色器宽度与文字设置面板一致
|
||||
.unified-color-picker {
|
||||
width: 280px;
|
||||
}
|
||||
}
|
||||
|
||||
// 填充拾色器宽度限制
|
||||
.fill-setting {
|
||||
.unified-color-picker {
|
||||
width: 280px;
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,17 @@ export const RecentColorsProvider: React.FC<RecentColorsProviderProps> = ({ chil
|
||||
kvStorageService.get<string[]>(RECENT_COLORS_KEY)
|
||||
.then((colors) => {
|
||||
if (colors && Array.isArray(colors)) {
|
||||
setRecentColors(colors);
|
||||
// 去重:使用 Set 并标准化颜色格式
|
||||
const seen = new Set<string>();
|
||||
const uniqueColors = colors.filter((color) => {
|
||||
const normalized = color.toUpperCase().replace(/\s/g, '');
|
||||
if (seen.has(normalized)) {
|
||||
return false;
|
||||
}
|
||||
seen.add(normalized);
|
||||
return true;
|
||||
});
|
||||
setRecentColors(uniqueColors);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
setAlphaToHex,
|
||||
removeAlphaFromHex,
|
||||
isValidHex,
|
||||
isLightColor,
|
||||
} from './utils';
|
||||
import './unified-color-picker.scss';
|
||||
|
||||
@@ -42,6 +43,9 @@ export const UnifiedColorPicker: React.FC<UnifiedColorPickerProps> = ({
|
||||
}) => {
|
||||
// 最近使用颜色
|
||||
const { recentColors, addRecentColor } = useRecentColors();
|
||||
|
||||
// 缓存首次渲染时的最近使用颜色,避免选择颜色时顺序变化
|
||||
const [initialRecentColors] = useState(() => [...recentColors]);
|
||||
|
||||
// 解析初始颜色
|
||||
const initialColor = value && isValidHex(value) ? value : DEFAULT_COLOR;
|
||||
@@ -201,23 +205,27 @@ export const UnifiedColorPicker: React.FC<UnifiedColorPickerProps> = ({
|
||||
)}
|
||||
|
||||
{/* 最近使用颜色 */}
|
||||
{showRecentColors && recentColors.length > 0 && (
|
||||
{showRecentColors && initialRecentColors.length > 0 && (
|
||||
<div className="unified-color-picker__recent">
|
||||
<div className="unified-color-picker__recent-label">最近使用</div>
|
||||
<div className="unified-color-picker__recent-colors">
|
||||
{recentColors.map((color, index) => (
|
||||
<button
|
||||
key={`${color}-${index}`}
|
||||
className={classNames('unified-color-picker__recent-item', {
|
||||
'unified-color-picker__recent-item--selected':
|
||||
removeAlphaFromHex(color).toUpperCase() === currentHex.toUpperCase(),
|
||||
})}
|
||||
style={{ backgroundColor: color }}
|
||||
onClick={() => handleRecentSelect(color)}
|
||||
disabled={disabled}
|
||||
title={color}
|
||||
/>
|
||||
))}
|
||||
{initialRecentColors.map((color, index) => {
|
||||
const isLight = isLightColor(color);
|
||||
return (
|
||||
<button
|
||||
key={`${color}-${index}`}
|
||||
className={classNames('unified-color-picker__recent-item', {
|
||||
'unified-color-picker__recent-item--selected':
|
||||
removeAlphaFromHex(color).toUpperCase() === currentHex.toUpperCase(),
|
||||
'unified-color-picker__recent-item--light': isLight,
|
||||
})}
|
||||
style={{ backgroundColor: color }}
|
||||
onClick={() => handleRecentSelect(color)}
|
||||
disabled={disabled}
|
||||
title={color}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
|
||||
// 变量 - 优化紧凑布局
|
||||
$ucp-panel-width: 220px;
|
||||
$ucp-panel-min-width: 200px;
|
||||
$ucp-panel-height: 140px;
|
||||
$ucp-slider-height: 10px;
|
||||
$ucp-indicator-size: 12px;
|
||||
@@ -22,11 +22,13 @@ $checkerboard-bg: repeating-conic-gradient(
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $ucp-gap;
|
||||
width: $ucp-panel-width;
|
||||
width: 100%;
|
||||
min-width: $ucp-panel-min-width;
|
||||
padding: 8px;
|
||||
background: var(--color-surface, #ffffff);
|
||||
border-radius: $ucp-border-radius;
|
||||
user-select: none;
|
||||
box-sizing: border-box;
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.6;
|
||||
@@ -173,6 +175,10 @@ $checkerboard-bg: repeating-conic-gradient(
|
||||
background: $checkerboard-bg !important;
|
||||
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
&--light {
|
||||
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
&__no-color-icon {
|
||||
@@ -207,10 +213,12 @@ $checkerboard-bg: repeating-conic-gradient(
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: 4px;
|
||||
height: $ucp-recent-size;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&-item {
|
||||
flex-shrink: 0;
|
||||
width: $ucp-recent-size;
|
||||
height: $ucp-recent-size;
|
||||
padding: 0;
|
||||
@@ -226,6 +234,10 @@ $checkerboard-bg: repeating-conic-gradient(
|
||||
&--selected {
|
||||
box-shadow: 0 0 0 2px var(--color-primary, #1890ff);
|
||||
}
|
||||
|
||||
&--light {
|
||||
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { DEFAULT_COLOR } from '@plait/core';
|
||||
import { NO_COLOR, WHITE } from '@aitu/utils';
|
||||
|
||||
export const CLASSIC_COLORS = [
|
||||
{ name: 'Topic Color', value: NO_COLOR },
|
||||
{ name: '无色', value: NO_COLOR },
|
||||
{ name: 'Basic Black', value: DEFAULT_COLOR },
|
||||
{ name: 'White', value: WHITE },
|
||||
{ name: 'Gray', value: '#808080' },
|
||||
|
||||
@@ -189,6 +189,10 @@ export const getStrokeColorByElement = (
|
||||
board: PlaitBoard,
|
||||
element: PlaitElement
|
||||
) => {
|
||||
// 如果明确设置为 'none',返回 'none'(无色)
|
||||
if (element.strokeColor === 'none') {
|
||||
return 'none';
|
||||
}
|
||||
const defaultColor = getFreehandDefaultStrokeColor(
|
||||
board.theme.themeColorMode
|
||||
);
|
||||
@@ -197,6 +201,10 @@ export const getStrokeColorByElement = (
|
||||
};
|
||||
|
||||
export const getFillByElement = (board: PlaitBoard, element: PlaitElement) => {
|
||||
// 如果明确设置为 'none',返回 'none'(无色)
|
||||
if (element.fill === 'none') {
|
||||
return 'none';
|
||||
}
|
||||
const defaultFill =
|
||||
Freehand.isFreehand(element) && isClosedCustomGeometry(board, element)
|
||||
? getFreehandDefaultFill(board.theme.themeColorMode)
|
||||
|
||||
@@ -54,7 +54,7 @@ export const setFillColor = (board: PlaitBoard, fillColor: string) => {
|
||||
const currentFill = getCurrentFill(board, element);
|
||||
const currentOpacity = hexAlphaToOpacity(currentFill);
|
||||
if (isNoColor(fillColor)) {
|
||||
Transforms.setNode(board, { fill: null }, path);
|
||||
Transforms.setNode(board, { fill: 'none' }, path);
|
||||
} else {
|
||||
if (
|
||||
isNullOrUndefined(currentOpacity) ||
|
||||
@@ -97,7 +97,7 @@ export const setStrokeColor = (board: PlaitBoard, newColor: string) => {
|
||||
const currentStrokeColor = getCurrentStrokeColor(board, element);
|
||||
const currentOpacity = hexAlphaToOpacity(currentStrokeColor);
|
||||
if (isNoColor(newColor)) {
|
||||
Transforms.setNode(board, { strokeColor: null }, path);
|
||||
Transforms.setNode(board, { strokeColor: 'none' }, path);
|
||||
} else {
|
||||
if (
|
||||
isNullOrUndefined(currentOpacity) ||
|
||||
|
||||
@@ -24,7 +24,8 @@ export const isClosedElement = (board: PlaitBoard, element: PlaitElement) => {
|
||||
return (
|
||||
MindElement.isMindElement(board, element) ||
|
||||
(PlaitDrawElement.isDrawElement(element) && isClosedDrawElement(element)) ||
|
||||
isClosedCustomGeometry(board, element)
|
||||
isClosedCustomGeometry(board, element) ||
|
||||
Freehand.isFreehand(element)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user