/** @license Copyright (c) 2025 trading_peter This program is available under Apache License Version 2.0 */ import '@tp/tp-timeout-strip/tp-timeout-strip.js'; import { LitElement, html, css } from 'lit'; class TpFlowNodePort extends LitElement { static get styles() { return [ css` :host { display: block; position: relative; } .wrapper { position: relative; width: 12px; height: 12px; transition: transform 0.15s ease; } /* Glow ring sits behind the shape, works for all shapes */ .wrapper::after { content: ''; position: absolute; top: 50%; left: 50%; width: 22px; height: 22px; transform: translate(-50%, -50%); border-radius: 50%; background: transparent; transition: background 0.15s ease, box-shadow 0.15s ease; z-index: -1; pointer-events: none; } .connectionPoint { width: 12px; height: 12px; background: var(--port-color, #666); cursor: pointer; position: relative; } /* Circle (control signals) */ .connectionPoint.shape-circle { border-radius: 50%; } /* Square (market data) */ .connectionPoint.shape-square { border-radius: 2px; } /* Diamond (trading signals) */ .connectionPoint.shape-diamond { transform: rotate(45deg); border-radius: 2px; } /* Triangle (computed values) */ .connectionPoint.shape-triangle { width: 0; height: 0; background: transparent; border-left: 6px solid transparent; border-right: 6px solid transparent; border-bottom: 12px solid var(--port-color, #666); } /* Hexagon (wildcard) */ .connectionPoint.shape-hexagon { clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%); border-radius: 0; } /* Hover: accent glow ring */ .wrapper:hover::after { background: color-mix(in srgb, var(--palette-accent, #3498db) 20%, transparent); box-shadow: 0 0 0 2px var(--palette-accent, #3498db); } .wrapper:hover .connectionPoint { filter: brightness(1.2); } :host([dimmed]) .wrapper { opacity: 0.2; pointer-events: none; } /* Compatible: scale up + accent glow ring */ :host([compatible]) .wrapper { transform: scale(1.4); } :host([compatible]) .wrapper::after { background: color-mix(in srgb, var(--palette-accent, #3498db) 25%, transparent); box-shadow: 0 0 0 2px var(--palette-accent, #3498db); } :host([compatible]) .connectionPoint { filter: brightness(1.15); } /* Compatible + hover: strongest signal */ :host([compatible]) .wrapper:hover::after { background: color-mix(in srgb, var(--palette-accent, #3498db) 35%, transparent); box-shadow: 0 0 0 3px var(--palette-accent, #3498db), 0 0 12px color-mix(in srgb, var(--palette-accent, #3498db) 50%, transparent); } .tag { position: absolute; background: #888; visibility: hidden; pointer-events: none; } :host([portType="input"]) .tag { left: 0; top: 0; transform: translateX(calc(-100% - 10px)); } :host([portType="output"]) .tag { right: 0; top: 0; transform: translateX(calc(100% + 10px)); } :host(:hover) .tag, .tag[visible] { visibility: visible; pointer-events: all; } ` ]; } render() { const { showTag, errorMsg, tagContent, shape } = this; return html`
${showTag || Boolean(tagContent) ? html`
${errorMsg ? html`
${errorMsg}
` : null} ${tagContent ? html`
${tagContent}
` : null}
` : null} `; } static get properties() { return { portType: { type: String, reflect: true }, portId: { type: Number, reflect: true }, portName: { type: String }, tagContent: { type: String }, errorMsg: { type: String }, showTag: { type: Boolean }, shape: { type: String }, color: { type: String }, }; } constructor() { super(); this.shape = 'circle'; } showConnectionError(msg, timeout = 0) { this.errorMsg = msg; this.showTag = true; if (timeout > 0) { this.updateComplete.then(() => { this.shadowRoot.querySelector('tp-timeout-strip').show(timeout); }); } } _onTimeout() { this.showTag = false; this.errorMsg = null; } } window.customElements.define('tp-flow-node-port', TpFlowNodePort);