Basic controls are used to build user interfaces in custom dialogs or panels.
A component designed for CAD numeric input, supporting precision control, multi-selection state display, input validation, and more.
<input-number></input-number>
import { NumberInputComponent } from 'vjcad';
// Create numeric input component
const input = document.createElement('input-number') as NumberInputComponent;
// Set initial value
input.setValue(100.5);
// Set precision (number of decimal places)
input.luprec = 4;
// Listen for value changes
input.addEventListener('change', (e: CustomEvent) => {
console.log('New value:', e.detail.value);
});
// Add to DOM
container.appendChild(input);
| Property | Type | Default | Description |
|---|
value | number | 0 | Current numeric value |
luprec | number | 6 | Decimal precision |
disabled | boolean | false | Whether disabled |
allowZero | boolean | false | Whether zero is allowed |
allowMinus | boolean | false | Whether negative values are allowed |
theme | string | "dark" | Theme ("dark" or "light") |
| Method | Description |
|---|
setValue(value: number) | Set value and format display |
updateValue(value: number) | Update value and refresh UI |
setNumbers(values: number[]) | Set multiple values (shows *multiple* in multi-selection state) |
setDisabled(disabled: boolean) | Set disabled state |
invokeChanged() | Manually trigger change handling |
| Event | Description |
|---|
change | Fired when the value changes; event.detail.value contains the new value |
The component automatically adds CSS classes according to input state:
| CSS Class | Description |
|---|
valid | Input value is valid and modified |
invalid | Input value is invalid, such as a negative number when negatives are not allowed |
error | Input error |
import { LitElement, html, css } from 'vjcad';
import { NumberInputComponent } from 'vjcad';
class PropertyPanel extends LitElement {
render() {
return html`
<div class="property-row">
<label>X Coordinate:</label>
<input-number
.value=${this.x}
.luprec=${4}
.allowMinus=${true}
@change=${this.onXChange}
></input-number>
</div>
<div class="property-row">
<label>Y Coordinate:</label>
<input-number
.value=${this.y}
.luprec=${4}
.allowMinus=${true}
@change=${this.onYChange}
></input-number>
</div>
<div class="property-row">
<label>Radius:</label>
<input-number
.value=${this.radius}
.luprec=${4}
.allowZero=${false}
.allowMinus=${false}
@change=${this.onRadiusChange}
></input-number>
</div>
`;
}
}
A toolbar button component supporting icons, themes, and state switching.
<tool-bar-button></tool-bar-button>
// Use HTML
const button = document.createElement('tool-bar-button');
button.setAttribute('title', 'Draw Line');
button.setAttribute('execute', 'LINE');
button.setAttribute('src', '/images/commands/line.svg');
// Listen for click
button.addEventListener('execute', (e: CustomEvent) => {
console.log('Execute command:', e.detail.excuteStr);
});
| Property | Type | Description |
|---|
src | string | Icon path, such as /images/commands/line.svg |
svgContent | string | Inline SVG content, with higher priority than src |
title | string | Tooltip text |
execute | string | Associated command name |
theme | string | Theme ("dark" or "light") |
isOn | boolean | Whether active |
preSelectMode | string | Preselection mode ("true" or "false") |
| Event | Description |
|---|
execute | Fired on click; event.detail.excuteStr contains the command name |
<div class="toolbar">
<tool-bar-button
title="Line"
execute="LINE"
src="/images/commands/line.svg"
theme="dark"
></tool-bar-button>
<tool-bar-button
title="Circle"
execute="CIRCLE"
src="/images/commands/circle.svg"
theme="dark"
></tool-bar-button>
<tool-bar-button
title="Rectangle"
execute="RECTANGLE"
src="/images/commands/rectangle.svg"
theme="dark"
></tool-bar-button>
</div>
Used to visually preview linetypes, supporting both simple and complex linetypes with text and shapes.
<linetype-preview></linetype-preview>
const preview = document.createElement('linetype-preview');
preview.linetypeName = 'Hidden';
preview.width = 200;
preview.height = 20;
preview.scale = 1.0;
container.appendChild(preview);
| Property | Type | Default | Description |
|---|
linetypeName | string | "Continuous" | Linetype name |
width | number | 200 | Preview width in pixels |
height | number | 20 | Preview height in pixels |
scale | number | 1.0 | Display scale |
| Linetype Name | Effect |
|---|
Continuous | Continuous |
Hidden | Dashed (short dash) |
Center | Center line (long dash-short dash) |
Phantom | Phantom line |
import { LitElement, html } from 'vjcad';
class LinetypeList extends LitElement {
linetypes = ['Continuous', 'Hidden', 'Center', 'Phantom', 'Dashed'];
render() {
return html`
<div class="linetype-list">
${this.linetypes.map(lt => html`
<div class="linetype-item" @click=${() => this.select(lt)}>
<span class="name">${lt}</span>
<linetype-preview
.linetypeName=${lt}
.width=${150}
.height=${16}
></linetype-preview>
</div>
`)}
</div>
`;
}
select(linetype: string) {
this.dispatchEvent(new CustomEvent('select', {
detail: { linetype }
}));
}
}
Used to create undo checkpoints for multi-step operations so the whole operation can be undone as a unit.
import { startUndoMark, endUndoMark } from 'vjcad';
// Start undo mark
startUndoMark();
// Perform multiple operations...
entity1.move(dx, dy);
entity2.move(dx, dy);
entity3.move(dx, dy);
// End undo mark
endUndoMark();
// These operations can now be undone as one action
import { startUndoMark, endUndoMark, Engine } from 'vjcad';
/**
* Scale selected entities in batch
*/
function scaleSelectedEntities(factor: number) {
const selected = Engine.selection.getSelectedEntities();
if (selected.length === 0) return;
// Mark undo start
startUndoMark();
try {
// Calculate center point
const bounds = Engine.selection.getBounds();
const centerX = (bounds.minX + bounds.maxX) / 2;
const centerY = (bounds.minY + bounds.maxY) / 2;
// Scale each entity
for (const entity of selected) {
entity.scale(centerX, centerY, factor);
}
Engine.redraw();
} finally {
// Mark undo end
endUndoMark();
}
}
// Usage
scaleSelectedEntities(2.0); // Enlarge 2x
// User can press Ctrl+Z to undo all scale operations at once
startUndoMark() and endUndoMark() must be used in pairs- Use
try...finally to ensure endUndoMark() is always called - Do not nest undo marks
import { LitElement, html, css } from 'vjcad';
import {
NumberInputComponent,
startUndoMark,
endUndoMark,
Engine
} from 'vjcad';
class CircleEditor extends LitElement {
static properties = {
entity: { type: Object }
};
declare entity: any;
render() {
if (!this.entity) {
return html`<div>Please select a circle</div>`;
}
return html`
<div class="editor">
<h3>Circle Properties</h3>
<div class="row">
<label>Center X:</label>
<input-number
.value=${this.entity.center.x}
.luprec=${4}
.allowMinus=${true}
@change=${(e: CustomEvent) => this.updateCenterX(e.detail.value)}
></input-number>
</div>
<div class="row">
<label>Center Y:</label>
<input-number
.value=${this.entity.center.y}
.luprec=${4}
.allowMinus=${true}
@change=${(e: CustomEvent) => this.updateCenterY(e.detail.value)}
></input-number>
</div>
<div class="row">
<label>Radius:</label>
<input-number
.value=${this.entity.radius}
.luprec=${4}
.allowZero=${false}
.allowMinus=${false}
@change=${(e: CustomEvent) => this.updateRadius(e.detail.value)}
></input-number>
</div>
</div>
`;
}
updateCenterX(value: string) {
startUndoMark();
this.entity.center.x = parseFloat(value);
this.entity.update();
endUndoMark();
Engine.redraw();
}
updateCenterY(value: string) {
startUndoMark();
this.entity.center.y = parseFloat(value);
this.entity.update();
endUndoMark();
Engine.redraw();
}
updateRadius(value: string) {
startUndoMark();
this.entity.radius = parseFloat(value);
this.entity.update();
endUndoMark();
Engine.redraw();
}
static styles = css`
.editor {
padding: 16px;
background: var(--dialog-contents-color, #f5f7fa);
}
.row {
display: flex;
align-items: center;
margin-bottom: 8px;
}
label {
width: 80px;
font-size: 14px;
}
input-number {
flex: 1;
height: 28px;
}
`;
}
customElements.define('circle-editor', CircleEditor);
A lightweight toast utility for temporary operation feedback.
import { message } from 'vjcad';
// Show info message (blue)
message.info("Operation successful");
// Show error message (red)
message.error("Operation failed");
// Show warning message (orange)
message.warn("Please check carefully");
// Show success message (green)
message.success("Saved successfully");
// Multiple parameters (auto-concatenated)
message.info("Load completed", 100, "records");
// Displays: "Load completed 100 records"
message.info({
content: "Saving...",
duration: 0, // 0 means do not auto close
key: "saving" // Messages with same key overwrite each other
});
// Update the same message later
message.info({
content: "Saved successfully!",
duration: 3,
key: "saving"
});
| Parameter | Type | Default | Description |
|---|
content | string | - | Message content |
duration | number | 3 | Display duration in seconds, 0 means do not auto close |
key | string | - | Unique key; messages with the same key overwrite previous ones |
| Method | Description |
|---|
message.info(...args) | Show info message (blue) |
message.error(...args) | Show error message (red) |
message.warn(...args) | Show warning message (orange) |
message.success(...args) | Show success message (green) |
- Maximum 3 messages: at most 3 messages are shown simultaneously; when exceeded, the oldest slides up and disappears
- Auto dismiss: messages disappear automatically after 3 seconds by default
- Message overwrite: messages with the same
key overwrite previous ones - Top-centered: messages are shown at the top center of the page
import { message, Engine, CircleEnt } from 'vjcad';
// Create circle and give feedback
const circle = new CircleEnt([100, 100], 50);
circle.setDefaults();
Engine.addEntities(circle);
Engine.zoomExtents();
message.info("Circle created");
import { message } from 'vjcad';
// Start processing
message.info({
content: "Processing...",
duration: 0,
key: "progress"
});
// Update when done
setTimeout(() => {
message.info({
content: "Processing completed!",
duration: 3,
key: "progress"
});
}, 2000);
import { message } from 'vjcad';
try {
// Some operation that may fail
await loadFile(path);
message.info("File loaded successfully");
} catch (e) {
message.error({
content: `Load failed: ${e.message}`,
duration: 5
});
}