- Replace HTML template lookups with programmatically created `<template>` elements. - Add component‑specific styles and markup for CanvasDisplay, including a grid overlay SVG. - Add SideMenu markup with toggle button, menu styling, and slot content.
143 lines
4.0 KiB
JavaScript
143 lines
4.0 KiB
JavaScript
// Side menu component logic
|
||
|
||
class SideMenu extends HTMLElement {
|
||
constructor() {
|
||
super();
|
||
|
||
// Attach a shadow root and clone the template defined in side-menu.html
|
||
this.attachShadow({ mode: "open" });
|
||
// Create a template element with the side‑menu markup directly in JS
|
||
const tmpl = document.createElement("template");
|
||
tmpl.innerHTML = `
|
||
<style>
|
||
:host {
|
||
display: block;
|
||
width: var(--side-menu-width, 250px);
|
||
height: 100%;
|
||
background: var(--side-menu-bg, #f9f9f9);
|
||
border-right: 1px solid #ccc;
|
||
box-sizing: border-box;
|
||
position: relative;
|
||
transition: transform 0.3s ease;
|
||
}
|
||
|
||
.menu {
|
||
height: 100%;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.toggle {
|
||
position: absolute;
|
||
top: 0.5rem;
|
||
right: -1.5rem;
|
||
background: var(--toggle-bg, #f0f0f0);
|
||
border: 1px solid #ccc;
|
||
border-left: none;
|
||
border-radius: 0 4px 4px 0;
|
||
padding: 0.25rem 0.5rem;
|
||
cursor: pointer;
|
||
user-select: none;
|
||
}
|
||
|
||
.collapsed {
|
||
transform: translateX(-100%);
|
||
}
|
||
</style>
|
||
|
||
<div class="toggle" part="toggle">☰</div>
|
||
<nav class="menu" part="menu">
|
||
<slot></slot>
|
||
<color-picker></color-picker>
|
||
<label id="grid-toggle-label"><input type="checkbox" id="grid-toggle"> Grid</label>
|
||
</nav>
|
||
`;
|
||
this.shadowRoot.appendChild(tmpl.content.cloneNode(true));
|
||
|
||
// Reference the toggle button and the menu container
|
||
this._toggleBtn = this.shadowRoot.querySelector(".toggle");
|
||
this._menu = this.shadowRoot.querySelector(".menu");
|
||
this._colorPicker = this.shadowRoot.querySelector("color-picker");
|
||
this._gridToggle = this.shadowRoot.querySelector("#grid-toggle");
|
||
|
||
// Bind event handlers
|
||
this._onToggle = this._onToggle.bind(this);
|
||
this._onColorChange = this._onColorChange.bind(this);
|
||
this._onGridToggle = this._onGridToggle.bind(this);
|
||
}
|
||
|
||
static get observedAttributes() {
|
||
return ["collapsed"];
|
||
}
|
||
|
||
connectedCallback() {
|
||
// Register click listener for the toggle button
|
||
this._toggleBtn?.addEventListener("click", this._onToggle);
|
||
this._colorPicker?.addEventListener("colorchange", this._onColorChange);
|
||
this._gridToggle?.addEventListener("change", this._onGridToggle);
|
||
|
||
// Initialise collapsed state from the attribute (if present)
|
||
if (this.hasAttribute("collapsed")) {
|
||
this._applyCollapsed(true);
|
||
}
|
||
}
|
||
|
||
disconnectedCallback() {
|
||
// Clean up listeners when the element is removed
|
||
this._toggleBtn?.removeEventListener("click", this._onToggle);
|
||
this._colorPicker?.removeEventListener("colorchange", this._onColorChange);
|
||
this._gridToggle?.removeEventListener("change", this._onGridToggle);
|
||
}
|
||
|
||
attributeChangedCallback(name, oldVal, newVal) {
|
||
if (name === "collapsed") {
|
||
const isCollapsed = this.hasAttribute("collapsed");
|
||
this._applyCollapsed(isCollapsed);
|
||
}
|
||
}
|
||
|
||
// Apply or remove the CSS class that slides the menu
|
||
_applyCollapsed(collapsed) {
|
||
const host = this.shadowRoot.host;
|
||
if (collapsed) {
|
||
host.classList.add("collapsed");
|
||
} else {
|
||
host.classList.remove("collapsed");
|
||
}
|
||
}
|
||
// Forward color picker change as bgcolorchange event
|
||
_onColorChange(e) {
|
||
const { color } = e.detail;
|
||
this.dispatchEvent(
|
||
new CustomEvent("bgcolorchange", {
|
||
detail: { color },
|
||
bubbles: true,
|
||
composed: true,
|
||
}),
|
||
);
|
||
}
|
||
|
||
// Forward grid toggle change as gridtoggle event
|
||
_onGridToggle(e) {
|
||
const enabled = e.target.checked;
|
||
this.dispatchEvent(
|
||
new CustomEvent("gridtoggle", {
|
||
detail: { enabled },
|
||
bubbles: true,
|
||
composed: true,
|
||
}),
|
||
);
|
||
}
|
||
|
||
// Toggle the collapsed attribute when the button is clicked
|
||
_onToggle() {
|
||
if (this.hasAttribute("collapsed")) {
|
||
this.removeAttribute("collapsed");
|
||
} else {
|
||
this.setAttribute("collapsed", "");
|
||
}
|
||
}
|
||
}
|
||
|
||
// Register the custom element; we follow the kebab‑case convention used elsewhere
|
||
customElements.define("side-menu-component", SideMenu);
|