基础控件(NumberInput / ToolBarButton / 线型预览)
文档:
vjcad/doc/ui/controls.md示例:
vjcad/example/ui/12modaldialogbase.js— 在对话框中使用 NumberInputvjcad/example/ui/13modelesspanelbase.js— 工具栏 / 自定义按钮vjcad/example/ui/04yesnodialog.js— 对话框基础
vjcad 把"和 CAD 习惯一致的小控件"做成 Web Components,可在自定义对话框 / 面板中复用。
1. NumberInputComponent (<input-number>)
<input-number
.value=${this.x}
.luprec=${4} precision: 小数位
.allowMinus=${true} 允许负数
.allowZero=${true} 允许 0
@change=${this.onXChange}>
</input-number>
属性:
| 属性 | 类型 | 说明 |
|---|---|---|
value |
number | 仅初始值:只在 connectedCallback 时转成显示文本,之后再改 .value 不会刷新显示,也不同步 numValue |
luprec |
number | 显示精度(小数位,默认 6) |
allowMinus |
boolean | 是否允许负数(默认 false) |
allowZero |
boolean | 是否允许 0(默认 false;坐标类输入两个都要开) |
disabled |
boolean | |
theme |
string | 'dark' | 'light' |
⚠️ 组件没有
min/max/step/placeholder属性——范围校验只能自己在 change 里做。 另有isAny/numValue/numStr/ANYSTR内部态和setValue/updateValue/setNumbers/setDisabled方法。 程序化改值用updateValue(v)(setValue只改内部态不刷新输入框);读当前数值用numValue(初始为 0,value不会写进它,直到用户提交或setValue/updateValue)。
事件:
host.addEventListener('change', e => {
const v = e.detail.value; // ⚠️ string(输入原文),数值化用 Number(v) 或读 e.target.numValue
});
// 每次 Enter、每次失焦(值合法时)都会触发——按 Enter 后再点别处会收到第二次 change,处理要幂等
// 没有向外抛 'input' 事件——input_callback 只做内部校验
实战:在属性面板里编辑端点:
class PointEditor extends LitElement {
@property({ type: Object }) point = { x: 0, y: 0 };
onXChange(e) { this.point = { ...this.point, x: Number(e.detail.value) }; this._emit(); }
onYChange(e) { this.point = { ...this.point, y: Number(e.detail.value) }; this._emit(); }
_emit() { this.dispatchEvent(new CustomEvent('change', { detail: this.point })); }
render() {
return html`
<div>X: <input-number .value=${this.point.x} .luprec=${4} .allowMinus=${true} .allowZero=${true}
@change=${this.onXChange}></input-number></div>
<div>Y: <input-number .value=${this.point.y} .luprec=${4} .allowMinus=${true} .allowZero=${true}
@change=${this.onYChange}></input-number></div>`;
// 外部改了 point 想刷新显示:this.renderRoot.querySelector('input-number').updateValue(newX)
}
}
2. ToolbarButton (<tool-bar-button>)
<tool-bar-button
title="直线"
src="images/commands/line.svg"
@click=${() => Engine.editor.executerWithOp('LINE')}>
</tool-bar-button>
属性(实现类 ButtonEle):
| 属性 | 说明 |
|---|---|
title |
tooltip + a11y label |
execute |
命令名字符串而已:按钮自身不绑定点击,只有内置工具条(如命令行工具区)读取它;自己用要 @click 里 executerWithOp |
src |
不是任意 URL:只认 [./]images/<分类>/<名>.svg 形式,从中解析出分类(commands/actbar/func-buttons/…)和名字去 IconRegistry 查;不匹配则不显示图标(不会发请求加载图片) |
svgContent |
直接给 SVG 字符串(优先于 src);自定义图标最稳的写法 |
theme |
'dark' | 'light' |
isOn |
声明了但 ButtonEle 渲染里没用到——无任何视觉效果 |
state |
状态字符串(配合 preSelectMode 用) |
preSelectMode |
预选模式标记 |
⚠️ 没有
disabled/selected属性;isOn也不产生高亮——按下/选中态请自己用外层 class 或svgContent换图标表现。
事件:
btn.addEventListener('click', () => Engine.editor.executerWithOp('LINE')); // execute 属性不会自动执行
// 没有 'select' 事件——按钮态切换自己处理
实战:自定义浮动工具栏(无需 createFloatingToolbar 的简化版):
class MyToolbar extends LitElement {
render() {
return html`
<div style="display:flex; gap:4px; padding:4px;">
<tool-bar-button title="直线" src="images/commands/line.svg"
@click=${() => Engine.editor.executerWithOp('LINE')}></tool-bar-button>
<tool-bar-button title="圆" src="images/commands/circle.svg"
@click=${() => Engine.editor.executerWithOp('CIRCLE')}></tool-bar-button>
<tool-bar-button title="弧" src="images/commands/arc.svg"
@click=${() => Engine.editor.executerWithOp('ARC')}></tool-bar-button>
</div>`;
}
}
customElements.define('my-toolbar', MyToolbar);
3. 线型预览(没有独立 <linetype-preview> 元素)
vjcad 没有导出独立的线型预览控件。可用的真实路径:
LinetypeViewerDialog(<linetype-viewer-dialog>):只读对话框,内部loadLinetypeContent()预览全部已加载线型layer-linetype-cell:图层管理表里渲染"线型"列的内部单元格组件(LayerLinetypeCell,name属性),绑定在图层表格流程里,不适合单独复用- 自定义下拉里的预览,用
Engine.linetypeManager.getLinetype(name)拿LinetypeDefinition(elements/patternLength)自己画<canvas>,十几行即可
// 只读查看全部已加载线型:
const viewer = new LinetypeViewerDialog();
await viewer.startDialog();
// 自己画缩略:取线型定义(注意是 Engine.linetypeManager,不是 currentDoc.lineTypes)
const lt = Engine.linetypeManager.getLinetype('Hidden'); // LinetypeDefinition | null
console.log(lt.elements, lt.patternLength);
4. 启动撤销标记
vjcad 在自定义按钮 / 面板中改 model 时,要把"一次操作"打成 undo 单元:
Engine.startUndoMark('修改坐标');
selected.forEach(e => {
Engine.markModified(e); // = undoManager.modEntity_undoMark(e):先记录修改前状态
e.startPoint = newPoint; // 再改
});
Engine.endUndoMark();
Engine.regen();
startUndoMark/endUndoMark 是 Engine 上的 static(也作为顶层函数导出),参数收 string | UndoDescriptor;Engine.undoManager(UndoManager)上的同名功能内部叫 start_undoMark,不要直接调。它们只负责把中间的撤销记录合成一组,本身不记录任何修改——直接赋值属性不会进撤销栈,必须在改之前逐个 Engine.markModified(e)(或 Engine.markModifiedBatch(arr))。
5. message / writeMessage 速记
import { message, writeMessage } from 'vjcad';
message.info({ content: '保存中…', duration: 0, key: 'save' });
await save();
message.success({ content: '保存成功', duration: 3, key: 'save' });
writeMessage('<br/>已添加 ' + n + ' 个实体');
详见 dialogs 第 1 节。
6. 主题相关 CSS 变量
没有 --input-bg / --button-bg 这类变量。控件直接读全局主题变量(THEME_COLOR_CSS_VARS 里的 --theme-color-*,组件内部不重定义,所以在祖先元素上设置即可继承):
| 控件 | 读取的变量 |
|---|---|
<input-number> |
--theme-color-input-bg(底色)、--theme-color-font(字色)、--theme-color-border(边框)、--theme-color-accent(valid 态边框) |
<tool-bar-button> |
--theme-color-toolbar-button-bg(回退 --theme-color-input-bg)、--theme-color-border、--theme-hover(悬停)、--pre-select-mode(预选态边框)、light 主题用 --theme-color-font 作底 |
// 推荐:全局改(写到 document.documentElement,内置 UI 一起变)
Engine.setThemeColors({ inputBg: '#1e1e1e', font: '#f0f0f0', border: '#3d3d3d',
accent: '#4589ee', toolbarButtonBg: '#3a3a3a', hover: '#4a4a4a' });
/* 只影响自己面板里的控件:在自己组件的 static styles 中给宿主设变量 */
:host { --theme-color-input-bg: #1e1e1e; --theme-color-toolbar-button-bg: #3a3a3a; }
红旗
<input-number>的change在每次 Enter、每次失焦(值合法时)都触发,Enter 后再失焦会触发两次;e.detail.value是字符串原文——用Number(e.detail.value)取值;输入中途没有input事件可监听;.value只是初始值,程序化改值用updateValue(v);默认allowZero/allowMinus都是 false<tool-bar-button>的execute不会自动执行、isOn没有视觉效果、src只认images/<分类>/<名>.svg注册表路径——点击自己绑@click,自定义图标用svgContent(没有disabled/selected属性)- 没有独立
<linetype-preview>元素——预览线型用LinetypeViewerDialog或自己画 - vjcad 控件都基于 Lit;自定义对话框
useShadowDOM=false时这些控件依旧用 ShadowDOM,外层 CSS 不能穿透 - 异步操作期间记得
message.key复用同一 toast,避免叠出多条
参考(官方文档 MCP)
- 文档:
vjcad/doc/ui/controls.md - 示例:
vjcad/example/ui/12modaldialogbase.js— 在对话框中使用 NumberInputvjcad/example/ui/13modelesspanelbase.js— 工具栏 / 自定义按钮vjcad/example/ui/04yesnodialog.js— 对话框基础