/** * Public handle for one component rendered by a DiagramEngine. * * A handle exposes only the operations used by the CMap editor. The drawing * object remains private to the engine so application code cannot depend on * the representation inherited from the original renderer. */ export class DiagramComponent { constructor(engine, component, attributeNames) { this.engine = engine; this.component = component; this.attributeNames = attributeNames; this.baseVisible = true; } /** Read or update the supported rendering attributes. */ attr(name, value) { if (name === undefined) { const attributes = {}; for (const attributeName of this.attributeNames) { attributes[attributeName] = this.component[attributeName](); } return attributes; } if (isPlainObject(name)) { for (const [attributeName, attributeValue] of Object.entries(name)) { this.attr(attributeName, attributeValue); } return this; } if (!this.attributeNames.includes(name)) return this; if (value === undefined) return this.component[name](); this.component[name](value); return this; } /** Remove this component from its diagram. */ remove() { this.engine.removeComponent(this); } /** Move this component to the front of its own rendering band. */ toFront() { this.engine.drawingSurface.toFront(this.component); return this; } /** Return the concrete element currently rendering this component. */ element() { return this.component.element(); } /** Immediately render the current component state. */ redraw() { this.component.redraw(); return this; } /** Read or change whether the component participates in the presentation. */ visible(value) { if (value === undefined) return this.component.visible !== false; this.baseVisible = Boolean(value); this.engine.applyFilter(this); return this.component.visible; } /** Register a callback invoked after this component has been rendered. */ onRendered(handler) { validateOptionalHandler(handler, "render"); this.component.renderedHandler = handler ? (element) => handler(this, element) : null; if (handler && this.component.element()) handler(this, this.component.element()); return this; } /** Read or change whether the component can be dragged. */ draggable(enabled) { if (enabled === undefined) { return this.engine.drawingSurface.dragEnabled(this.component); } if (enabled) this.engine.drawingSurface.enableDrag(this.component); else this.engine.drawingSurface.disableDrag(this.component); return this; } } /** Return a new object containing only supported input attributes. */ export function pickAttributes(attributes, names) { if (attributes === undefined) return {}; if (!isPlainObject(attributes)) throw new TypeError("Invalid component attributes"); const selected = {}; for (const name of names) { if (name in attributes) selected[name] = attributes[name]; } return selected; } /** Validate an optional event handler at the public engine boundary. */ export function validateOptionalHandler(handler, meaning) { if (handler !== null && handler !== undefined && typeof handler !== "function") { throw new TypeError(`Invalid ${meaning} handler`); } } /** Determine whether a value is a plain attributes object. */ function isPlainObject(value) { return typeof value === "object" && value !== null && Object.prototype.toString.call(value) === "[object Object]"; }