/**
* ATtiny85 — Web Component (DIP-8 package, Digispark-style)
*
* Exposes the standard `pinInfo` array used by the wire system to position
* wire endpoints on real pins instead of the component corner.
*
* Pin layout (DIP-8):
* Left side (top to bottom): PB5 (RST), PB3, PB4, GND
* Right side (top to bottom): VCC, PB2, PB1, PB0
*
* The visual layout matches the older React component
* (`Attiny85.tsx` → `Attiny85`), but here we own the DOM so wires can read
* `element.pinInfo`. The `led1` attribute mirrors the PB1 (Digispark LED)
* pin state.
*/
// DIP-8 dimensions (must match BoardOnCanvas BOARD_SIZE = 160 × 100)
const W = 160;
const H = 100;
const BX = 30; // chip body left
const BY = 10; // chip body top
const BW = 100; // chip body width
const BH = 80; // chip body height
const PIN_W = 28; // pin stub length
// Vertical pin centres
const PIN_STARTS_Y = [BY + 10, BY + 30, BY + 50, BY + 70];
const PIN_LABELS_LEFT = ['PB5', 'PB3', 'PB4', 'GND'];
const PIN_LABELS_RIGHT = ['VCC', 'PB2', 'PB1', 'PB0'];
/**
* Pin tip coordinates (where wires must attach), in SVG pixels relative to
* the element's top-left corner. The wire system reads these via the
* `pinInfo` property on the rendered DOM element.
*/
const PIN_INFO: ReadonlyArray<{ name: string; x: number; y: number; description?: string }> = [
// Left column — pin tip = BX - PIN_W
{ name: 'PB5', x: BX - PIN_W, y: PIN_STARTS_Y[0], description: 'PB5 / RST' },
{ name: 'PB3', x: BX - PIN_W, y: PIN_STARTS_Y[1] },
{ name: 'PB4', x: BX - PIN_W, y: PIN_STARTS_Y[2] },
{ name: 'GND', x: BX - PIN_W, y: PIN_STARTS_Y[3] },
// Right column — pin tip = BX + BW + PIN_W
{ name: 'VCC', x: BX + BW + PIN_W, y: PIN_STARTS_Y[0] },
{ name: 'PB2', x: BX + BW + PIN_W, y: PIN_STARTS_Y[1] },
{ name: 'PB1', x: BX + BW + PIN_W, y: PIN_STARTS_Y[2], description: 'PB1 / built-in LED' },
{ name: 'PB0', x: BX + BW + PIN_W, y: PIN_STARTS_Y[3] },
];
class Attiny85Element extends HTMLElement {
static get observedAttributes(): string[] {
return ['led1'];
}
private _led1 = false;
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.render();
}
attributeChangedCallback(name: string, _old: string | null, value: string | null) {
if (name === 'led1') {
this._led1 = value !== null && value !== 'false';
this.updateLed();
}
}
/** Wires use this to find pin coordinates. */
get pinInfo() {
return PIN_INFO;
}
/** Property setter so React's `useEffect` path can drive the LED. */
set led1(v: boolean) {
this._led1 = !!v;
this.updateLed();
}
get led1(): boolean {
return this._led1;
}
private updateLed() {
const led = this.shadowRoot?.getElementById('attiny-led1') as unknown as SVGElement | null;
if (!led) return;
if (this._led1) {
led.setAttribute('fill', '#ffee44');
led.setAttribute('stroke', '#ffcc00');
led.style.filter = 'drop-shadow(0 0 4px #ffcc00)';
} else {
led.setAttribute('fill', '#333');
led.setAttribute('stroke', '#555');
led.style.filter = 'none';
}
}
private render() {
if (!this.shadowRoot) return;
// Build pin stubs + labels
const leftStubs = PIN_STARTS_Y.map(
(py) => ``,
).join('');
const rightStubs = PIN_STARTS_Y.map(
(py) => ``,
).join('');
const leftLabels = PIN_LABELS_LEFT.map(
(label, i) =>
`${label === 'PB5' ? 'PB5/RST' : label}`,
).join('');
const rightLabels = PIN_LABELS_RIGHT.map(
(label, i) =>
`${label}`,
).join('');
// Built-in LED on PB1 (right side, 3rd from top → PIN_STARTS_Y[2])
const ledFill = this._led1 ? '#ffee44' : '#333';
const ledStroke = this._led1 ? '#ffcc00' : '#555';
const ledFilter = this._led1 ? 'drop-shadow(0 0 4px #ffcc00)' : 'none';
this.shadowRoot.innerHTML = `
`;
}
}
if (!customElements.get('velxio-attiny85')) {
customElements.define('velxio-attiny85', Attiny85Element);
}
export {};