1class AIChatButton extends HTMLElement { 2 #root = null; 3 #dialog = null; 4 5 6 constructor() { 7 super(); 8 this.#root = this.attachShadow({mode: 'open'}); 9 this.#root.innerHTML = ` 10 <button class="toggle start"> 11 <svg viewBox="0 0 24 24"><path d="M12,3C17.5,3 22,6.58 22,11C22,15.42 17.5,19 12,19C10.76,19 9.57,18.82 8.47,18.5C5.55,21 2,21 2,21C4.33,18.67 4.7,17.1 4.75,16.5C3.05,15.07 2,13.13 2,11C2,6.58 6.5,3 12,3Z" /></svg> 12 </button> 13 <dialog> 14 <div> 15 <header> 16 <button class="fs" title="Fullscreen"> 17 <svg viewBox="0 0 24 24"><path d="M12 5.5L10 8H14L12 5.5M18 10V14L20.5 12L18 10M6 10L3.5 12L6 14V10M14 16H10L12 18.5L14 16M21 3H3C1.9 3 1 3.9 1 5V19C1 20.1 1.9 21 3 21H21C22.1 21 23 20.1 23 19V5C23 3.9 22.1 3 21 3M21 19H3V5H21V19Z" /></svg> 18 </button> 19 <h1>AI Chat</h1> 20 <button class="toggle" title="Close"> 21 <svg viewBox="0 0 24 24"><path d="M13.46,12L19,17.54V19H17.54L12,13.46L6.46,19H5V17.54L10.54,12L5,6.46V5H6.46L12,10.54L17.54,5H19V6.46L13.46,12Z" /></svg> 22 </button> 23 </header> 24 <main> 25 <slot></slot> 26 </main> 27 </div> 28 </dialog> 29 `; 30 31 this.#root.appendChild(this.getStyle()); 32 this.#dialog = this.#root.querySelector('dialog'); 33 34 const toggleButtons = this.#root.querySelectorAll('button.toggle'); 35 toggleButtons.forEach(function (button) { 36 button.addEventListener('click', this.toggleDialog.bind(this)) 37 }.bind(this)); 38 39 this.#dialog.querySelector('button.fs').addEventListener('click', function() { 40 this.#dialog.classList.toggle('fullscreen'); 41 }.bind(this)); 42 } 43 44 /** 45 * Called when the DOM has been connected 46 * 47 * We initialize the attribute based states here 48 */ 49 connectedCallback() { 50 this.#root.querySelector('button.start').title = this.getAttribute('label') || 'AI Chat'; 51 this.#dialog.querySelector('header h1').textContent = this.getAttribute('label') || 'AI Chat'; 52 } 53 54 /** 55 * Define the web component's internal styles 56 * 57 * @returns {HTMLStyleElement} 58 */ 59 getStyle() { 60 const style = document.createElement('style'); 61 style.textContent = ` 62 :host { 63 --color-chat-icon: #4881bf; 64 --icon-size: 2em; 65 } 66 button { 67 background: none; 68 border: none; 69 cursor: pointer; 70 } 71 :host > button svg { 72 fill: var(--color-chat-icon); 73 } 74 svg { 75 width: 2em; 76 height: 2em; 77 } 78 button.start svg { 79 width: var(--icon-size); 80 height: var(--icon-size); 81 } 82 dialog { 83 width: 500px; 84 max-width: 90vw; 85 height: 800px; 86 max-height: 90vh; 87 88 position: fixed; 89 top: 1em; 90 right: 1em; 91 left: auto; 92 z-index: 9999; 93 94 padding: 0.5em; 95 96 box-shadow: 0 4px 5px rgb(0 0 0 / 30%); 97 border-radius: 8px; 98 border: 1px solid #fff; 99 } 100 dialog.fullscreen { 101 width: 100%; 102 height: 100%; 103 left: 1em; 104 right: 1em; 105 106 } 107 dialog > div { 108 display: flex; 109 flex-direction: column; 110 height: 100%; 111 } 112 dialog header { 113 display: flex; 114 justify-content: space-between; 115 align-items: flex-start; 116 } 117 dialog main { 118 overflow: auto; 119 flex-grow: 1; 120 } 121 `; 122 return style; 123 } 124 125 toggleDialog() { 126 if (this.#dialog.open) { 127 this.#dialog.close(); 128 } else { 129 this.#dialog.show(); 130 } 131 } 132} 133 134window.customElements.define('aichat-button', AIChatButton); 135