UNPKG

webserial-core

Version:

A strongly-typed, event-driven, abstract TypeScript library for the Web Serial API with custom parsers, command queue, handshake validation, and auto-reconnect.

1 lines 20.2 kB
import{S as e,_ as t,a as n,b as r,d as i,f as a,g as o,h as ee,i as s,l as c,m as te,n as l,o as ne,p as u,r as d,t as f,v as re,x as ie,y as ae}from"./demo-shared-BVQKG6NC.js";var oe=32,se=34,p=0,ce=30,le=3,ue=7,de=1,fe=0,pe=771,me=768,he=255,ge=8,_e=`none`,m=1,ve=[16,8,7,6,5],ye=[1,2],be=[`none`,`even`,`odd`],xe=[`none`,`odd`,`even`],Se=[1,1.5,2],h={usbControlInterfaceClass:2,usbTransferInterfaceClass:10,protocol:void 0};function g(e,t){let n=e.configurations[0];if(!n)return null;for(let e of n.interfaces)if(e.alternates[0]?.interfaceClass===t)return e;return null}function _(e,t){let n=e.configurations[0];if(!n)return null;for(let e of n.interfaces){let n=e.alternates[0];if(!n||n.interfaceClass!==t)continue;let r=n.endpoints.some(e=>e.direction===`in`),i=n.endpoints.some(e=>e.direction===`out`);if(r&&i)return e}return null}function v(e,t){let n=e.alternates[0];if(n){for(let e of n.endpoints)if(e.direction===t)return e}throw TypeError(`Interface ${e.interfaceNumber} does not have an ${t} endpoint.`)}function Ce(e,t){return t===2?`cdc_acm`:e.vendorId===4292?`cp210x`:`none`}var we=class{device_;endpoint_;onError_;constructor(e,t,n){this.device_=e,this.endpoint_=t,this.onError_=n}pull(e){(async()=>{let t=this.endpoint_.packetSize;try{let n=await this.device_.transferIn(this.endpoint_.endpointNumber,t);if(n.status!==`ok`){e.error(`USB error: ${n.status}`),this.onError_();return}if(n.data?.buffer&&n.data.byteLength>0){let t=new Uint8Array(n.data.buffer,n.data.byteOffset,n.data.byteLength);t.length>0&&e.enqueue(t)}}catch(t){e.error(String(t)),this.onError_()}})()}},Te=class{device_;endpoint_;onError_;constructor(e,t,n){this.device_=e,this.endpoint_=t,this.onError_=n}async write(e,t){try{let n=await this.device_.transferOut(this.endpoint_.endpointNumber,e.buffer);n.status!==`ok`&&(t.error(n.status),this.onError_())}catch(e){t.error(String(e)),this.onError_()}}},y=class{device_;protocol_;controlInterface_;transferInterface_;inEndpoint_;outEndpoint_;serialOptions_;readable_=null;writable_=null;cdcOutputSignals_={dataTerminalReady:!1,requestToSend:!1,break:!1};constructor(e,t){this.device_=e;let n={...h,...t};this.protocol_=n.protocol??Ce(e,n.usbControlInterfaceClass);let r=n.usbControlInterfaceClass,i=n.usbTransferInterfaceClass;if(r===i){let t=_(e,i);if(!t)throw TypeError(`Unable to find interface with class ${i} that has both IN and OUT endpoints.`);this.controlInterface_=t,this.transferInterface_=t}else{let t=g(e,r);if(!t)throw TypeError(`Unable to find control interface with class ${r}.`);let n=_(e,i)??g(e,i);if(!n)throw TypeError(`Unable to find transfer interface with class ${i}.`);this.controlInterface_=t,this.transferInterface_=n}this.inEndpoint_=v(this.transferInterface_,`in`),this.outEndpoint_=v(this.transferInterface_,`out`)}get readable(){return!this.readable_&&this.device_.opened&&(this.readable_=new ReadableStream(new we(this.device_,this.inEndpoint_,()=>{this.readable_=null}),{highWaterMark:this.serialOptions_?.bufferSize??he})),this.readable_}get writable(){return!this.writable_&&this.device_.opened&&(this.writable_=new WritableStream(new Te(this.device_,this.outEndpoint_,()=>{this.writable_=null}),new ByteLengthQueuingStrategy({highWaterMark:this.serialOptions_?.bufferSize??he}))),this.writable_}async open(e){this.serialOptions_=e,this.validateOptions();try{switch(await this.device_.open(),this.device_.configuration===null&&await this.device_.selectConfiguration(1),await this.device_.claimInterface(this.controlInterface_.interfaceNumber),this.controlInterface_!==this.transferInterface_&&await this.device_.claimInterface(this.transferInterface_.interfaceNumber),this.protocol_){case`cdc_acm`:await this.cdcInit();break;case`cp210x`:await this.cp210xInit();break;case`none`:break}}catch(e){throw this.device_.opened&&await this.device_.close(),Error(`Error setting up device: `+(e instanceof Error?e.message:String(e)),{cause:e})}}async close(){let e=[];if(this.readable_&&e.push(this.readable_.cancel()),this.writable_&&e.push(this.writable_.abort()),await Promise.all(e),this.readable_=null,this.writable_=null,this.device_.opened){switch(this.protocol_){case`cdc_acm`:await this.cdcSetSignals({dataTerminalReady:!1,requestToSend:!1});break;case`cp210x`:await this.cp210xDeinit();break}await this.device_.close()}}async forget(){return this.device_.forget()}getInfo(){return{usbVendorId:this.device_.vendorId,usbProductId:this.device_.productId}}async cdcInit(){await this.cdcSetLineCoding(),await this.cdcSetSignals({dataTerminalReady:!0})}async cdcSetSignals(e){if(this.cdcOutputSignals_={...this.cdcOutputSignals_,...e},e.dataTerminalReady!==void 0||e.requestToSend!==void 0){let e=(this.cdcOutputSignals_.dataTerminalReady?1:0)|(this.cdcOutputSignals_.requestToSend?2:0);await this.device_.controlTransferOut({requestType:`class`,recipient:`interface`,request:se,value:e,index:this.controlInterface_.interfaceNumber})}}async cdcSetLineCoding(){let e=new ArrayBuffer(7),t=new DataView(e);if(t.setUint32(0,this.serialOptions_.baudRate,!0),t.setUint8(4,Se.indexOf(this.serialOptions_.stopBits??m)),t.setUint8(5,xe.indexOf(this.serialOptions_.parity??_e)),t.setUint8(6,this.serialOptions_.dataBits??ge),(await this.device_.controlTransferOut({requestType:`class`,recipient:`interface`,request:oe,value:0,index:this.controlInterface_.interfaceNumber},e)).status!==`ok`)throw new DOMException(`Failed to set line coding.`,`NetworkError`)}async cp210xInit(){let e=this.controlInterface_.interfaceNumber;await this.device_.controlTransferOut({requestType:`vendor`,recipient:`interface`,request:p,value:de,index:e});let t=new ArrayBuffer(4);new DataView(t).setUint32(0,this.serialOptions_.baudRate,!0),await this.device_.controlTransferOut({requestType:`vendor`,recipient:`interface`,request:ce,value:0,index:e},t);let n=this.serialOptions_.dataBits??ge,r={none:0,odd:16,even:32}[this.serialOptions_.parity??_e]??0,i=({1:0,2:2}[this.serialOptions_.stopBits??m]??0)<<8|r|n;await this.device_.controlTransferOut({requestType:`vendor`,recipient:`interface`,request:le,value:i,index:e}),await this.device_.controlTransferOut({requestType:`vendor`,recipient:`interface`,request:ue,value:pe,index:e})}async cp210xDeinit(){let e=this.controlInterface_.interfaceNumber;await this.device_.controlTransferOut({requestType:`vendor`,recipient:`interface`,request:ue,value:me,index:e}),await this.device_.controlTransferOut({requestType:`vendor`,recipient:`interface`,request:p,value:fe,index:e})}validateOptions(){if(this.serialOptions_.baudRate%1!=0)throw RangeError(`Invalid baud rate: ${this.serialOptions_.baudRate}`);if(this.serialOptions_.dataBits!==void 0&&!ve.includes(this.serialOptions_.dataBits))throw RangeError(`Invalid dataBits: ${this.serialOptions_.dataBits}`);if(this.serialOptions_.stopBits!==void 0&&!ye.includes(this.serialOptions_.stopBits))throw RangeError(`Invalid stopBits: ${this.serialOptions_.stopBits}`);if(this.serialOptions_.parity!==void 0&&!be.includes(this.serialOptions_.parity))throw RangeError(`Invalid parity: ${this.serialOptions_.parity}`)}},Ee=class{options_;constructor(e){this.options_={...h,...e}}async requestPort(e,t){let n={...this.options_,...t},r=[];if(e?.filters&&e.filters.length>0)for(let t of e.filters){let e={};t.usbVendorId!==void 0&&(e.vendorId=t.usbVendorId),t.usbProductId!==void 0&&(e.productId=t.usbProductId),n.usbControlInterfaceClass!==void 0&&n.usbControlInterfaceClass!==255?e.classCode=n.usbControlInterfaceClass:e.vendorId===void 0&&e.productId===void 0&&(e.classCode=n.usbControlInterfaceClass??2),r.push(e)}else r.push({classCode:n.usbControlInterfaceClass??2});return new y(await navigator.usb.requestDevice({filters:r}),n)}async getPorts(e){let t={...this.options_,...e},n=await navigator.usb.getDevices(),r=[];for(let e of n)try{let n=new y(e,t);r.push(n)}catch{}return r}},b=[],x=[],De=class extends e{_hsCmd;_hsCmdMode;_hsExpect;_hsExpectMode;constructor(e,t,n,r,i){super(e),this._hsCmd=t,this._hsCmdMode=n,this._hsExpect=r,this._hsExpectMode=i}async handshake(){if(!this._hsCmd||(this._hsCmdMode===`hex`?await this.send(R(this._hsCmd)):await this.send(d(this._hsCmd)),!this._hsExpect))return!0;let e=this._hsExpect.trim();return new Promise(t=>{let n=r=>{if(this.off(`serial:data`,n),this._hsExpectMode===`hex`){let e=new TextEncoder().encode(String(r)),n=R(this._hsExpect);t(e.length===n.length&&e.every((e,t)=>e===n[t]))}else t(String(r).trim()===e)};this.on(`serial:data`,n)})}},S=e=>document.getElementById(e),C=S(`messages`),w=S(`btn-connect`),Oe=S(`btn-disconnect`),T=S(`btn-send`),E=S(`input-send`),D=S(`mode-toggle`),ke=S(`status-dot`),O=S(`status-text`),Ae=S(`console-dot`),k=S(`console-text`),A=S(`sidebar`),j=S(`code-panel`),je=S(`code-view`),Me=S(`code-tab`),Ne=S(`menu-btn`),Pe=S(`code-toggle-btn`),M=S(`theme-btn`),Fe=S(`clear-btn`),N=S(`copy-btn`),Ie=S(`dl-btn`),Le=S(`cfg-export-btn`),P=S(`cfg-import-input`);function F(){let e=e=>(S(e)?.value??``).trim(),t=(t,n)=>parseInt(e(t))||n,n=e=>S(e)?.value??``,r=e=>S(e)?.checked??!1,i=e(`cfg-vendor`),a=e(`cfg-product`),o=[];if(i||a){let e={};i&&(e.usbVendorId=parseInt(i,16)),a&&(e.usbProductId=parseInt(a,16)),o.push(e)}return{usbControlInterfaceClass:t(`cfg-ctrl-class`,255),usbTransferInterfaceClass:t(`cfg-xfer-class`,255),baudRate:t(`cfg-baud`,9600),dataBits:t(`cfg-databits`,8),stopBits:t(`cfg-stopbits`,1),parity:n(`cfg-parity`)||`none`,flowControl:n(`cfg-flow`)||`none`,bufferSize:t(`cfg-bufsize`,255),commandTimeout:t(`cfg-timeout`,3e3),autoReconnect:r(`cfg-autoreconnect`),autoReconnectInterval:t(`cfg-reconnect-ms`,1500),handshakeTimeout:t(`cfg-handshake`,2e3),delimiter:e(`cfg-delim`),prepend:e(`cfg-prepend`),append:e(`cfg-append`),hsCmd:e(`cfg-hs-cmd`),hsCmdMode:n(`cfg-hs-cmd-mode`)||`text`,hsExpect:e(`cfg-hs-expect`),hsExpectMode:n(`cfg-hs-expect-mode`)||`text`,filters:o}}function Re(e){let t=(e,t)=>{let n=document.getElementById(e);n&&(n instanceof HTMLInputElement&&n.type===`checkbox`?n.checked=!!t:(n instanceof HTMLInputElement||n instanceof HTMLSelectElement)&&(n.value=String(t??``)))};t(`cfg-ctrl-class`,e.usbControlInterfaceClass??255),t(`cfg-xfer-class`,e.usbTransferInterfaceClass??255),t(`cfg-baud`,e.baudRate??9600),t(`cfg-databits`,e.dataBits??8),t(`cfg-stopbits`,e.stopBits??1),t(`cfg-parity`,e.parity??`none`),t(`cfg-flow`,e.flowControl??`none`),t(`cfg-bufsize`,e.bufferSize??255),t(`cfg-timeout`,e.commandTimeout??3e3),t(`cfg-handshake`,e.handshakeTimeout??2e3),t(`cfg-reconnect-ms`,e.autoReconnectInterval??1500),t(`cfg-autoreconnect`,e.autoReconnect??!1),t(`cfg-vendor`,e.filters?.[0]?.usbVendorId==null?``:e.filters[0].usbVendorId.toString(16)),t(`cfg-product`,e.filters?.[0]?.usbProductId==null?``:e.filters[0].usbProductId.toString(16)),t(`cfg-delim`,e.delimiter??`\\n`),t(`cfg-prepend`,e.prepend??``),t(`cfg-append`,e.append??``),t(`cfg-hs-cmd`,e.hsCmd??``),t(`cfg-hs-cmd-mode`,e.hsCmdMode??`text`),t(`cfg-hs-expect`,e.hsExpect??``),t(`cfg-hs-expect-mode`,e.hsExpectMode??`text`)}function I(e){let t=e.replace(/[^a-zA-Z0-9_$]/g,``).replace(/^[^a-zA-Z_$]/,`C`);return t.charAt(0).toUpperCase()+t.slice(1)||`MyDevice`}function L(e){return Array.from(e).map(e=>e.toString(16).toUpperCase().padStart(2,`0`)).join(` `)}function R(e){let t=e.replace(/\s+/g,``);if(t.length%2!=0)throw Error(`Odd number of hex characters.`);let n=new Uint8Array(t.length/2);for(let e=0;e<t.length;e+=2)n[e/2]=parseInt(t.substring(e,e+2),16);return n}function z(e,t){[ke,Ae].forEach(t=>{t&&(t.className=`status-dot`,e!==`disconnected`&&t.classList.add(e))}),O&&(O.textContent=t),k&&(k.textContent=t)}var B=null;function V(){B&&clearTimeout(B),B=setTimeout(()=>{let e=F(),t=I((S(`dl-name`)?.value??`MyUsbDevice`).trim()),n=(document.querySelector(`input[name='dl-lang']:checked`)?.value??`ts`)===`ts`,r=n?`ts`:`js`;re(je,c(e,t,n,b,x)),Me&&(Me.textContent=`${t.substring(0,10).toLowerCase()}.${r}`)},180)}var ze=u();M&&(M.textContent=ze===`dark`?`☀️`:`🌙`),Ne?.addEventListener(`click`,()=>A.classList.toggle(`collapsed`)),Pe?.addEventListener(`click`,()=>j.classList.toggle(`collapsed`));var H=S(`resize-handle`);if(H){let e=0,t=0,n=n=>{let r=Math.max(180,Math.min(700,t+(e-n.clientX)));document.documentElement.style.setProperty(`--code-w`,`${r}px`)},r=()=>{H.classList.remove(`dragging`),document.removeEventListener(`pointermove`,n)};H.addEventListener(`pointerdown`,i=>{e=i.clientX,t=j.getBoundingClientRect().width,H.classList.add(`dragging`),document.addEventListener(`pointermove`,n),document.addEventListener(`pointerup`,r,{once:!0}),i.preventDefault()})}var U=S(`sidebar-resize-handle`);if(U){let e=0,t=0,n=n=>{let r=Math.max(200,Math.min(600,t+(n.clientX-e)));document.documentElement.style.setProperty(`--sidebar-w`,`${r}px`)},r=()=>{U.classList.remove(`dragging`),document.removeEventListener(`pointermove`,n)};U.addEventListener(`pointerdown`,i=>{e=i.clientX,t=A.getBoundingClientRect().width,U.classList.add(`dragging`),document.addEventListener(`pointermove`,n),document.addEventListener(`pointerup`,r,{once:!0}),i.preventDefault()})}window.innerWidth<=960&&j.classList.add(`collapsed`),window.innerWidth<=640&&A.classList.add(`collapsed`),M?.addEventListener(`click`,()=>{let e=ae();M&&(M.textContent=e===`dark`?`☀️`:`🌙`)}),Fe?.addEventListener(`click`,()=>l(C)),N?.addEventListener(`click`,async()=>{let e=je?.textContent??``;try{await navigator.clipboard.writeText(e),N&&(N.textContent=`Copied!`,N.classList.add(`copied`),setTimeout(()=>{N.textContent=`Copy`,N.classList.remove(`copied`)},1500))}catch{}}),Ie?.addEventListener(`click`,()=>{let e=F(),r=(S(`dl-name`)?.value??`my-usb-device`).trim(),i=I(r),a=(document.querySelector(`input[name='dl-lang']:checked`)?.value??`ts`)===`ts`,s=document.querySelector(`input[name='dl-type']:checked`)?.value??`project`,l=a?`ts`:`js`,u=c(e,i,a,b,x);s===`project`?ne([{name:`device.${l}`,content:u},{name:`package.json`,content:ee(r,a,`usb`)},{name:`index.html`,content:te(i,l,`WebUSB`)},{name:`README.md`,content:o(i,l,`WebUSB`,`Requires a Chromium browser. Common USB-to-serial chips: CP2102 (0x10c4/0xea60), CH340 (0x1a86/0x7523).`)},...a?[{name:`tsconfig.json`,content:t()}]:[]],`${r}-project-${l}`):n(`${r}.${l}`,u)}),Le?.addEventListener(`click`,()=>{let e=(S(`dl-name`)?.value??`my-usb-device`).trim(),t=I(e);s({$version:1,provider:`usb`,className:e,language:document.querySelector(`input[name='dl-lang']:checked`)?.value??`ts`,dlType:document.querySelector(`input[name='dl-type']:checked`)?.value??`file`,cfg:F(),commands:b,listeners:x},t+`-config`)}),P?.addEventListener(`change`,()=>{let e=P.files?.[0];if(!e)return;let t=new FileReader;t.onload=e=>{try{let t=JSON.parse(e.target?.result);if(t.$version!==1||t.provider!==`usb`)return;let n=S(`dl-name`);n&&(n.value=t.className),document.querySelectorAll(`input[name='dl-lang']`).forEach(e=>{e.checked=e.value===t.language}),document.querySelectorAll(`input[name='dl-type']`).forEach(e=>{e.checked=e.value===t.dlType}),Re(t.cfg),b=t.commands.map(e=>({...e,id:crypto.randomUUID()})),x=t.listeners.map(e=>({...e,id:crypto.randomUUID()})),Q(),$(),V()}catch{}P.value=``},t.readAsText(e)}),[`cfg-ctrl-class`,`cfg-xfer-class`,`cfg-baud`,`cfg-databits`,`cfg-stopbits`,`cfg-parity`,`cfg-flow`,`cfg-bufsize`,`cfg-timeout`,`cfg-handshake`,`cfg-reconnect-ms`,`cfg-autoreconnect`,`cfg-vendor`,`cfg-product`,`cfg-delim`,`cfg-prepend`,`cfg-append`,`cfg-hs-cmd`,`cfg-hs-cmd-mode`,`cfg-hs-expect`,`cfg-hs-expect-mode`,`dl-name`].forEach(e=>{let t=document.getElementById(e);t?.addEventListener(`change`,V),t?.addEventListener(`input`,V)}),document.querySelectorAll(`input[name='dl-lang']`).forEach(e=>e.addEventListener(`change`,V)),V();var W=`text`;D?.addEventListener(`click`,()=>{W=W===`text`?`hex`:`text`,D.textContent=W===`text`?`TXT`:`HEX`,E.placeholder=W===`text`?`Type a command, e.g. LED_ON`:`Hex bytes, e.g. FF 01 A3`});var G=null;function Be(e){T.disabled=!e,E.disabled=!e,Oe.disabled=!e,w.disabled=e}w?.addEventListener(`click`,async()=>{if(G){try{await G.disconnect()}catch{}G=null}let t=F();e.setProvider(new Ee({usbControlInterfaceClass:t.usbControlInterfaceClass,usbTransferInterfaceClass:t.usbTransferInterfaceClass}));let n=t.delimiter?d(t.delimiter):``;G=new De({baudRate:t.baudRate,dataBits:t.dataBits,stopBits:t.stopBits,parity:t.parity,flowControl:t.flowControl,bufferSize:t.bufferSize,commandTimeout:t.commandTimeout,parser:n?ie(n):r(),autoReconnect:t.autoReconnect,autoReconnectInterval:t.autoReconnectInterval,handshakeTimeout:t.handshakeTimeout,filters:t.filters??[]},t.hsCmd,t.hsCmdMode,t.hsExpect,t.hsExpectMode),G.on(`serial:connecting`,()=>{z(`connecting`,`Connecting…`),w.disabled=!0,f(C,`Connecting via WebUSB…`,{kind:`system`})}),G.on(`serial:connected`,()=>{z(`connected`,`Connected`),Be(!0),f(C,`Connected via WebUSB!`,{kind:`system`})}),G.on(`serial:disconnected`,()=>{z(`disconnected`,`Disconnected`),Be(!1),f(C,`Disconnected.`,{kind:`system`}),G=null}),G.on(`serial:data`,e=>{f(C,String(e),{kind:`received`,label:`Device`})}),G.on(`serial:error`,e=>{z(`error`,`Error`),f(C,`Error: ${e.message}`,{kind:`error`}),w.disabled=!1}),G.on(`serial:need-permission`,()=>{z(`error`,`Permission denied`),f(C,`Permission denied — allow access to the USB device.`,{kind:`error`}),w.disabled=!1}),G.on(`serial:timeout`,e=>{f(C,`Timeout: ${L(e)}`,{kind:`error`})}),G.on(`serial:reconnecting`,()=>{z(`connecting`,`Reconnecting…`),f(C,`Auto-reconnecting via WebUSB…`,{kind:`system`})});try{await G.connect()}catch{}}),Oe?.addEventListener(`click`,async()=>{await G?.disconnect()});async function Ve(){let e=E.value.trim();if(!e||!G)return;let t=F(),n=t.append?d(t.append):t.delimiter?d(t.delimiter):``;try{if(W===`hex`){let t=R(e);f(C,`HEX: ${L(t)}`,{kind:`sent`,label:`You`}),await G.send(t)}else{let r=t.prepend+e+n;f(C,e,{kind:`sent`,label:`You`}),await G.send(r)}E.value=``,E.focus()}catch(e){f(C,`Send error: ${e instanceof Error?e.message:String(e)}`,{kind:`error`})}}T?.addEventListener(`click`,Ve),E?.addEventListener(`keydown`,e=>{e.key===`Enter`&&Ve()});var K=S(`cmd-name`),q=S(`cmd-value`),He=S(`cmd-mode`),Ue=S(`cmd-add`),J=S(`cmd-list`),Y=S(`lst-name`),X=S(`lst-pattern`),We=S(`lst-match`),Ge=S(`lst-add`),Z=S(`lst-list`);function Q(){if(J){J.innerHTML=``;for(let e of b){let t=document.createElement(`div`);t.className=`chip`;let n=document.createElement(`span`);n.className=`chip-badge`,n.textContent=e.mode.toUpperCase();let r=document.createElement(`span`);r.className=`chip-name`,r.textContent=e.name;let i=document.createElement(`span`);i.className=`chip-val`,i.textContent=e.value;let a=document.createElement(`button`);a.className=`chip-send`,a.title=`Send now`,a.textContent=`▶`,a.addEventListener(`click`,()=>{if(G)if(e.mode===`hex`){let t=R(e.value);G.send(t).catch(()=>{}),f(C,`HEX: ${L(t)}`,{kind:`sent`,label:`You`})}else{let t=F(),n=t.append?d(t.append):t.delimiter?d(t.delimiter):``;G.send(t.prepend+e.value+n).catch(()=>{}),f(C,e.name,{kind:`sent`,label:`You`})}});let o=document.createElement(`button`);o.className=`chip-del`,o.title=`Remove`,o.textContent=`×`,o.addEventListener(`click`,()=>{b=b.filter(t=>t.id!==e.id),Q(),V()}),t.append(n,r,i,a,o),J.appendChild(t)}}}function $(){if(Z){Z.innerHTML=``;for(let e of x){let t=document.createElement(`div`);t.className=`chip`;let n=document.createElement(`span`);n.className=`chip-badge`,n.textContent=e.match;let r=document.createElement(`span`);r.className=`chip-name`,r.textContent=e.name;let i=document.createElement(`span`);i.className=`chip-val`,i.textContent=e.pattern;let a=document.createElement(`button`);a.className=`chip-del`,a.title=`Remove`,a.textContent=`×`,a.addEventListener(`click`,()=>{x=x.filter(t=>t.id!==e.id),$(),V()}),t.append(n,r,i,a),Z.appendChild(t)}}}Ue?.addEventListener(`click`,()=>{let e=K?.value.trim(),t=q?.value.trim();!e||!t||(b.push({id:crypto.randomUUID(),name:e,value:t,mode:He?.value??`text`}),K&&(K.value=``),q&&(q.value=``),Q(),V())}),Ge?.addEventListener(`click`,()=>{let e=Y?.value.trim(),t=X?.value.trim();!e||!t||(x.push({id:crypto.randomUUID(),name:e,pattern:t,match:We?.value??`exact`}),Y&&(Y.value=``),X&&(X.value=``),$(),V())}),f(C,`WebUSB demo ready — CP2102/ESP32 (0x10c4/0xea60), CH340/Arduino (0x1a86/0x7523). Configure and click Connect.`,{kind:`system`}),i(),a();