{"version":3,"file":"index-46a6576f.js","sources":["../../node_modules/@lit/reactive-element/css-tag.js","../../node_modules/@lit/reactive-element/reactive-element.js","../../node_modules/lit-html/lit-html.js","../../node_modules/lit-element/lit-element.js","../../.11ty-vite/_assets/javascript/lightbox/index.js","../../.11ty-vite/_assets/javascript/modal/index.js","../../.11ty-vite/_assets/javascript/application/poll.js","../../.11ty-vite/_assets/javascript/application/scroll-to-hash.js","../../.11ty-vite/_assets/javascript/application/canvas-panel.js","../../.11ty-vite/_assets/javascript/application/soundcloud-api.min.js","../../node_modules/lunr/lunr.js","../../_plugins/search/search.js","../../.11ty-vite/_assets/javascript/application/index.js"],"sourcesContent":["/**\n * @license\n * Copyright 2019 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */\nconst t=window,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow)&&\"adoptedStyleSheets\"in Document.prototype&&\"replace\"in CSSStyleSheet.prototype,s=Symbol(),n=new WeakMap;class o{constructor(t,e,n){if(this._$cssResult$=!0,n!==s)throw Error(\"CSSResult is not constructable. Use `unsafeCSS` or `css` instead.\");this.cssText=t,this.t=e}get styleSheet(){let t=this.o;const s=this.t;if(e&&void 0===t){const e=void 0!==s&&1===s.length;e&&(t=n.get(s)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),e&&n.set(s,t))}return t}toString(){return this.cssText}}const r=t=>new o(\"string\"==typeof t?t:t+\"\",void 0,s),i=(t,...e)=>{const n=1===t.length?t[0]:e.reduce(((e,s,n)=>e+(t=>{if(!0===t._$cssResult$)return t.cssText;if(\"number\"==typeof t)return t;throw Error(\"Value passed to 'css' function must be a 'css' function result: \"+t+\". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.\")})(s)+t[n+1]),t[0]);return new o(n,t,s)},S=(s,n)=>{e?s.adoptedStyleSheets=n.map((t=>t instanceof CSSStyleSheet?t:t.styleSheet)):n.forEach((e=>{const n=document.createElement(\"style\"),o=t.litNonce;void 0!==o&&n.setAttribute(\"nonce\",o),n.textContent=e.cssText,s.appendChild(n)}))},c=e?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e=\"\";for(const s of t.cssRules)e+=s.cssText;return r(e)})(t):t;export{o as CSSResult,S as adoptStyles,i as css,c as getCompatibleStyle,e as supportsAdoptingStyleSheets,r as unsafeCSS};\n//# sourceMappingURL=css-tag.js.map\n","import{getCompatibleStyle as t,adoptStyles as i}from\"./css-tag.js\";export{CSSResult,adoptStyles,css,getCompatibleStyle,supportsAdoptingStyleSheets,unsafeCSS}from\"./css-tag.js\";\n/**\n * @license\n * Copyright 2017 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */var s;const e=window,r=e.trustedTypes,h=r?r.emptyScript:\"\",o=e.reactiveElementPolyfillSupport,n={toAttribute(t,i){switch(i){case Boolean:t=t?h:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t)}return t},fromAttribute(t,i){let s=t;switch(i){case Boolean:s=null!==t;break;case Number:s=null===t?null:Number(t);break;case Object:case Array:try{s=JSON.parse(t)}catch(t){s=null}}return s}},a=(t,i)=>i!==t&&(i==i||t==t),l={attribute:!0,type:String,converter:n,reflect:!1,hasChanged:a};class d extends HTMLElement{constructor(){super(),this._$Ei=new Map,this.isUpdatePending=!1,this.hasUpdated=!1,this._$El=null,this.u()}static addInitializer(t){var i;this.finalize(),(null!==(i=this.h)&&void 0!==i?i:this.h=[]).push(t)}static get observedAttributes(){this.finalize();const t=[];return this.elementProperties.forEach(((i,s)=>{const e=this._$Ep(s,i);void 0!==e&&(this._$Ev.set(e,s),t.push(e))})),t}static createProperty(t,i=l){if(i.state&&(i.attribute=!1),this.finalize(),this.elementProperties.set(t,i),!i.noAccessor&&!this.prototype.hasOwnProperty(t)){const s=\"symbol\"==typeof t?Symbol():\"__\"+t,e=this.getPropertyDescriptor(t,s,i);void 0!==e&&Object.defineProperty(this.prototype,t,e)}}static getPropertyDescriptor(t,i,s){return{get(){return this[i]},set(e){const r=this[t];this[i]=e,this.requestUpdate(t,r,s)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)||l}static finalize(){if(this.hasOwnProperty(\"finalized\"))return!1;this.finalized=!0;const t=Object.getPrototypeOf(this);if(t.finalize(),void 0!==t.h&&(this.h=[...t.h]),this.elementProperties=new Map(t.elementProperties),this._$Ev=new Map,this.hasOwnProperty(\"properties\")){const t=this.properties,i=[...Object.getOwnPropertyNames(t),...Object.getOwnPropertySymbols(t)];for(const s of i)this.createProperty(s,t[s])}return this.elementStyles=this.finalizeStyles(this.styles),!0}static finalizeStyles(i){const s=[];if(Array.isArray(i)){const e=new Set(i.flat(1/0).reverse());for(const i of e)s.unshift(t(i))}else void 0!==i&&s.push(t(i));return s}static _$Ep(t,i){const s=i.attribute;return!1===s?void 0:\"string\"==typeof s?s:\"string\"==typeof t?t.toLowerCase():void 0}u(){var t;this._$E_=new Promise((t=>this.enableUpdating=t)),this._$AL=new Map,this._$Eg(),this.requestUpdate(),null===(t=this.constructor.h)||void 0===t||t.forEach((t=>t(this)))}addController(t){var i,s;(null!==(i=this._$ES)&&void 0!==i?i:this._$ES=[]).push(t),void 0!==this.renderRoot&&this.isConnected&&(null===(s=t.hostConnected)||void 0===s||s.call(t))}removeController(t){var i;null===(i=this._$ES)||void 0===i||i.splice(this._$ES.indexOf(t)>>>0,1)}_$Eg(){this.constructor.elementProperties.forEach(((t,i)=>{this.hasOwnProperty(i)&&(this._$Ei.set(i,this[i]),delete this[i])}))}createRenderRoot(){var t;const s=null!==(t=this.shadowRoot)&&void 0!==t?t:this.attachShadow(this.constructor.shadowRootOptions);return i(s,this.constructor.elementStyles),s}connectedCallback(){var t;void 0===this.renderRoot&&(this.renderRoot=this.createRenderRoot()),this.enableUpdating(!0),null===(t=this._$ES)||void 0===t||t.forEach((t=>{var i;return null===(i=t.hostConnected)||void 0===i?void 0:i.call(t)}))}enableUpdating(t){}disconnectedCallback(){var t;null===(t=this._$ES)||void 0===t||t.forEach((t=>{var i;return null===(i=t.hostDisconnected)||void 0===i?void 0:i.call(t)}))}attributeChangedCallback(t,i,s){this._$AK(t,s)}_$EO(t,i,s=l){var e;const r=this.constructor._$Ep(t,s);if(void 0!==r&&!0===s.reflect){const h=(void 0!==(null===(e=s.converter)||void 0===e?void 0:e.toAttribute)?s.converter:n).toAttribute(i,s.type);this._$El=t,null==h?this.removeAttribute(r):this.setAttribute(r,h),this._$El=null}}_$AK(t,i){var s;const e=this.constructor,r=e._$Ev.get(t);if(void 0!==r&&this._$El!==r){const t=e.getPropertyOptions(r),h=\"function\"==typeof t.converter?{fromAttribute:t.converter}:void 0!==(null===(s=t.converter)||void 0===s?void 0:s.fromAttribute)?t.converter:n;this._$El=r,this[r]=h.fromAttribute(i,t.type),this._$El=null}}requestUpdate(t,i,s){let e=!0;void 0!==t&&(((s=s||this.constructor.getPropertyOptions(t)).hasChanged||a)(this[t],i)?(this._$AL.has(t)||this._$AL.set(t,i),!0===s.reflect&&this._$El!==t&&(void 0===this._$EC&&(this._$EC=new Map),this._$EC.set(t,s))):e=!1),!this.isUpdatePending&&e&&(this._$E_=this._$Ej())}async _$Ej(){this.isUpdatePending=!0;try{await this._$E_}catch(t){Promise.reject(t)}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){var t;if(!this.isUpdatePending)return;this.hasUpdated,this._$Ei&&(this._$Ei.forEach(((t,i)=>this[i]=t)),this._$Ei=void 0);let i=!1;const s=this._$AL;try{i=this.shouldUpdate(s),i?(this.willUpdate(s),null===(t=this._$ES)||void 0===t||t.forEach((t=>{var i;return null===(i=t.hostUpdate)||void 0===i?void 0:i.call(t)})),this.update(s)):this._$Ek()}catch(t){throw i=!1,this._$Ek(),t}i&&this._$AE(s)}willUpdate(t){}_$AE(t){var i;null===(i=this._$ES)||void 0===i||i.forEach((t=>{var i;return null===(i=t.hostUpdated)||void 0===i?void 0:i.call(t)})),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$Ek(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$E_}shouldUpdate(t){return!0}update(t){void 0!==this._$EC&&(this._$EC.forEach(((t,i)=>this._$EO(i,this[i],t))),this._$EC=void 0),this._$Ek()}updated(t){}firstUpdated(t){}}d.finalized=!0,d.elementProperties=new Map,d.elementStyles=[],d.shadowRootOptions={mode:\"open\"},null==o||o({ReactiveElement:d}),(null!==(s=e.reactiveElementVersions)&&void 0!==s?s:e.reactiveElementVersions=[]).push(\"1.6.1\");export{d as ReactiveElement,n as defaultConverter,a as notEqual};\n//# sourceMappingURL=reactive-element.js.map\n","/**\n * @license\n * Copyright 2017 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */\nvar t;const i=window,s=i.trustedTypes,e=s?s.createPolicy(\"lit-html\",{createHTML:t=>t}):void 0,o=\"$lit$\",n=`lit$${(Math.random()+\"\").slice(9)}$`,l=\"?\"+n,h=`<${l}>`,r=document,d=()=>r.createComment(\"\"),u=t=>null===t||\"object\"!=typeof t&&\"function\"!=typeof t,c=Array.isArray,v=t=>c(t)||\"function\"==typeof(null==t?void 0:t[Symbol.iterator]),a=\"[ \\t\\n\\f\\r]\",f=/<(?:(!--|\\/[^a-zA-Z])|(\\/?[a-zA-Z][^>\\s]*)|(\\/?$))/g,_=/-->/g,m=/>/g,p=RegExp(`>|${a}(?:([^\\\\s\"'>=/]+)(${a}*=${a}*(?:[^ \\t\\n\\f\\r\"'\\`<>=]|(\"|')|))|$)`,\"g\"),g=/'/g,$=/\"/g,y=/^(?:script|style|textarea|title)$/i,w=t=>(i,...s)=>({_$litType$:t,strings:i,values:s}),x=w(1),b=w(2),T=Symbol.for(\"lit-noChange\"),A=Symbol.for(\"lit-nothing\"),E=new WeakMap,C=r.createTreeWalker(r,129,null,!1),P=(t,i)=>{const s=t.length-1,l=[];let r,d=2===i?\"\":\"\");if(!Array.isArray(t)||!t.hasOwnProperty(\"raw\"))throw Error(\"invalid template strings array\");return[void 0!==e?e.createHTML(c):c,l]};class V{constructor({strings:t,_$litType$:i},e){let h;this.parts=[];let r=0,u=0;const c=t.length-1,v=this.parts,[a,f]=P(t,i);if(this.el=V.createElement(a,e),C.currentNode=this.el.content,2===i){const t=this.el.content,i=t.firstChild;i.remove(),t.append(...i.childNodes)}for(;null!==(h=C.nextNode())&&v.length0){h.textContent=s?s.emptyScript:\"\";for(let s=0;s2||\"\"!==s[0]||\"\"!==s[1]?(this._$AH=Array(s.length-1).fill(new String),this.strings=s):this._$AH=A}get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}_$AI(t,i=this,s,e){const o=this.strings;let n=!1;if(void 0===o)t=N(this,t,i,0),n=!u(t)||t!==this._$AH&&t!==T,n&&(this._$AH=t);else{const e=t;let l,h;for(t=o[0],l=0;l{var e,o;const n=null!==(e=null==s?void 0:s.renderBefore)&&void 0!==e?e:i;let l=n._$litPart$;if(void 0===l){const t=null!==(o=null==s?void 0:s.renderBefore)&&void 0!==o?o:null;n._$litPart$=l=new M(i.insertBefore(d(),t),t,void 0,null!=s?s:{})}return l._$AI(t),l};export{Z as _$LH,x as html,T as noChange,A as nothing,B as render,b as svg};\n//# sourceMappingURL=lit-html.js.map\n","import{ReactiveElement as t}from\"@lit/reactive-element\";export*from\"@lit/reactive-element\";import{render as e,noChange as i}from\"lit-html\";export*from\"lit-html\";\n/**\n * @license\n * Copyright 2017 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */var l,o;const r=t;class s extends t{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){var t,e;const i=super.createRenderRoot();return null!==(t=(e=this.renderOptions).renderBefore)&&void 0!==t||(e.renderBefore=i.firstChild),i}update(t){const i=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(t),this._$Do=e(i,this.renderRoot,this.renderOptions)}connectedCallback(){var t;super.connectedCallback(),null===(t=this._$Do)||void 0===t||t.setConnected(!0)}disconnectedCallback(){var t;super.disconnectedCallback(),null===(t=this._$Do)||void 0===t||t.setConnected(!1)}render(){return i}}s.finalized=!0,s._$litElement$=!0,null===(l=globalThis.litElementHydrateSupport)||void 0===l||l.call(globalThis,{LitElement:s});const n=globalThis.litElementPolyfillSupport;null==n||n({LitElement:s});const h={_$AK:(t,e,i)=>{t._$AK(e,i)},_$AL:t=>t._$AL};(null!==(o=globalThis.litElementVersions)&&void 0!==o?o:globalThis.litElementVersions=[]).push(\"3.3.1\");export{s as LitElement,r as UpdatingElement,h as _$LE};\n//# sourceMappingURL=lit-element.js.map\n","import { LitElement, html } from 'lit'\nimport { unsafeHTML } from 'lit-html/directives/unsafe-html.js'\n\n/**\n * Lightbox lit-element web component\n *\n * This component renders image slides with navigation UI.\n * It provides a default slot for passing markup to render:\n *\n * To display an element as a slide, provide it with a\n * `data-lightbox-slide` attribute set to any value\n * `data-lightbox-slide-id` attribute set to a unique id string\n *\n * This lightbox provides access to controls with the following data attributes:\n * - `data-lightbox-fullscreen` triggers fullscreen on click and indicates status\n * - `data-lightbox-next` triggers rendering of next slide on click\n * - `data-lightbox-previous` triggers rendering of previous slide on click\n *\n * It dynamically updates the content of elements with these data attributes:\n * - `data-lightbox-counter-current` displays the current slide index\n * - `data-lightbox-counter-total` displays total number of slides\n */\nclass Lightbox extends LitElement {\n static properties = {\n currentId: { attribute: 'current-id', type: String },\n }\n\n constructor() {\n super()\n this.setupFullscreenButton()\n this.setupNavigationButtons()\n this.setupKeyboardControls()\n }\n\n get counterCurrent() {\n return this.querySelector('[data-lightbox-counter-current]')\n }\n\n get counterTotal() {\n return this.querySelector('[data-lightbox-counter-total]')\n }\n\n get fullscreenButton() {\n return this.querySelector('[data-lightbox-fullscreen]')\n }\n\n get nextButton() {\n return this.querySelector('[data-lightbox-next]')\n }\n\n get previousButton() {\n return this.querySelector('[data-lightbox-previous]')\n }\n\n get slides() {\n return this.querySelectorAll('[data-lightbox-slide]')\n }\n\n get slideIds() {\n return Array\n .from(this.slides)\n .map((slide) => slide.dataset.lightboxSlideId)\n }\n\n get currentSlide() {\n if (!this.slides.length) return\n if (!this.currentId) return this.slides[0]\n\n return this.slides[this.currentSlideIndex]\n }\n\n get currentSlideIndex() {\n if (!this.slides.length) return\n if (!this.currentId) return 0\n\n return this.slideIds.findIndex((id) => id === this.currentId)\n }\n\n get fullscreen() {\n return document.fullscreen\n }\n\n next() {\n if (!this.slides.length) return\n\n const nextIndex = this.currentSlideIndex + 1\n const nextId = this.slideIds[nextIndex % this.slides.length]\n this.currentId = nextId\n }\n\n previous() {\n if (!this.slides.length) return\n\n const previousIndex = this.currentSlideIndex - 1\n const previousId = this.slideIds.slice(previousIndex)[0]\n this.currentId = previousId\n }\n\n setupFullscreenButton() {\n if (!this.fullscreenButton) return\n\n this.fullscreenButton.addEventListener('click', () => {\n this.toggleFullscreen()\n })\n }\n\n setupNavigationButtons() {\n if (!this.nextButton || !this.previousButton) return\n\n this.nextButton.addEventListener('click', () => {\n this.next()\n })\n this.previousButton.addEventListener('click', () => {\n this.previous()\n })\n }\n\n setupKeyboardControls() {\n document.addEventListener('keyup', ({ code }) => {\n if (this.slides.length > 1) {\n switch(code) {\n default:\n break\n case 'ArrowRight':\n this.next()\n break\n case 'ArrowLeft':\n this.previous()\n break\n }\n }\n })\n }\n\n toggleFullscreen() {\n const lightbox = this.renderRoot.firstElementChild\n\n if (this.fullscreen) {\n document.exitFullscreen()\n } else {\n lightbox.requestFullscreen()\n }\n\n this.updateFullscreenButton()\n }\n\n updateCounterElements() {\n if (!this.counterCurrent || !this.counterTotal) return\n\n this.counterCurrent.innerText = this.currentSlideIndex + 1\n this.counterTotal.innerText = this.slides.length\n }\n\n updateCurrentSlideElement() {\n if (!this.currentSlide) return\n\n this.slides.forEach((slide) => {\n if (slide.dataset.lightboxSlideId !== this.currentId)\n delete slide.dataset.lightboxCurrent\n })\n this.currentSlide.dataset.lightboxCurrent = true\n }\n\n updateFullscreenButton() {\n if (this.fullscreenButton) this.fullscreenButton.dataset.lightboxFullscreen = !this.fullscreen\n }\n\n render() {\n if (!this.slides.length) return ''\n this.currentId = this.slideIds[this.currentSlideIndex]\n this.updateCurrentSlideElement()\n this.updateCounterElements()\n\n return html`\n \n \n
\n `\n }\n}\n\ncustomElements.define('q-lightbox', Lightbox)\n","import { LitElement, html } from 'lit'\n\nclass Modal extends LitElement {\n static properties = {\n active: { type: Boolean },\n currentId: { attribute: 'current-id', type: String },\n }\n\n get closeButton() {\n return this.querySelector('[data-modal-close]')\n }\n\n get lightbox() {\n return this.querySelector('q-lightbox')\n }\n\n constructor() {\n super()\n this.setupKeyboardControls()\n this.setupModalTriggers()\n }\n\n close() {\n this.active = false\n this.currentId = null\n this.updateLightboxCurrentId()\n this.enableScrolling()\n }\n\n disableScrolling() {\n document.querySelector('html').style.overflow = 'hidden'\n }\n\n enableScrolling() {\n document.querySelector('html').style.overflow = 'auto'\n }\n\n getCurrentFigureId(event) {\n const { target } = event\n let currentFigure = target\n while (\n !currentFigure.matches('figure') &&\n !currentFigure.getAttribute('id')\n ) {\n currentFigure = currentFigure.parentNode\n }\n return currentFigure.getAttribute('id')\n }\n\n open(event) {\n this.currentId = this.getCurrentFigureId(event)\n this.active = true\n this.updateLightboxCurrentId()\n this.disableScrolling()\n }\n\n setupCloseButton() {\n if (!this.closeButton) return\n\n this.closeButton.addEventListener('click', () => {\n this.close()\n })\n }\n\n setupKeyboardControls() {\n document.addEventListener('keyup', ({ code }) => {\n if (this.active) {\n if(code === 'Escape') {\n this.close()\n }\n }\n })\n }\n\n setupModalTriggers() {\n document.querySelectorAll('.q-figure__modal-link').forEach((item) => {\n item.addEventListener('click', (event) => {\n event.preventDefault()\n this.open(event)\n })\n })\n }\n\n updateLightboxCurrentId() {\n this.lightbox && this.lightbox.setAttribute('current-id', this.currentId)\n }\n\n render() {\n this.dataset.modalActive = this.active\n this.setupCloseButton()\n\n return html`\n \n \n
\n `\n }\n}\n\ncustomElements.define('q-modal', Modal)\n","/**\n * Polling module\n * \n * @property {Function} callback Function to call if validate succeeds\n * @property {Number} interval Interval between tries\n * @property {Number} maxTries Maximum number of polling attempts\n * @property {Function} validate Function that evaluates to true to indicate that polling should end\n */\nexport default ({ callback, interval=200, maxTries=10, tries=0, validate }) => {\n const poll = () => {\n if (tries === maxTries) {\n console.error(`Polling reached maximum number of attempts`)\n return\n }\n\n tries++\n\n const valid = validate()\n\n if (valid) {\n callback()\n } else {\n setTimeout(poll, interval)\n }\n }\n poll()\n}\n","/**\n * scrollToHash\n * @description Scroll the #main area after each smoothState reload.\n * If a hash id is present, scroll to the location of that element,\n * taking into account the height of the navbar.\n */\n\n/**\n * scrollWindow\n * @description scroll viewport to a certain vertical offset, minus the height of the quire navbar\n * TODO add animation duration and style of easing function previously provided by jQuery `.animate()`\n */\nfunction scrollWindow(verticalOffset, animationDuration = null, animationStyle = null) {\n const navBar = document.querySelector('.quire-navbar')\n const extraSpace = 7\n const scrollDistance = navBar\n ? verticalOffset - navBar.clientHeight - extraSpace\n : verticalOffset - extraSpace\n // redundancy here to ensure all possible document properties with `scrollTop` are being set for cross-browser compatibility\n const documentProperties = [\n document.documentElement,\n document.body.parentNode,\n document.body\n ]\n documentProperties.forEach((element) => {\n element.scrollTop = scrollDistance\n })\n}\n\nexport default (hash) => {\n if (!hash) return\n // prefix all ':' and '.' in hash with '\\\\' to make them query-selectable\n hash = hash.replace(':', '\\\\:')\n hash = hash.replace('.', '\\\\.')\n // Figure out element to scroll to\n const target = document.querySelector(hash)\n // Does a scroll target exist?\n if (target) {\n const verticalOffset = target.getBoundingClientRect().top + window.scrollY\n scrollWindow(verticalOffset)\n // handle focus after scrolling\n setTimeout(() => target.focus())\n }\n}\n","import poll from './poll'\nimport scrollToHash from './scroll-to-hash'\n\n/**\n * Get annotation data from annotaitons UI input element\n * @param {HTML Element} input\n * @return {Object}\n */\nconst annotationData = (input) => {\n return {\n checked: input.checked,\n id: input.getAttribute('value'),\n input: input.getAttribute('type'),\n type: input.getAttribute('data-annotation-type')\n }\n}\n\n/**\n * Get canvas id or info.json from child web component\n * @param {HTML Element} element\n * @return {String} canvasId\n */\nconst getServiceId = (element) => {\n const canvasPanel = element.querySelector('canvas-panel')\n const imageSequence = element.querySelector('image-sequence')\n const imageService = element.querySelector('image-service')\n\n if (canvasPanel) {\n return canvasPanel.getAttribute('canvas-id')\n } else if (imageService) {\n return imageService.getAttribute('src')\n } else if (imageSequence) {\n return imageSequence.getAttribute('sequence-id')\n } else {\n console.error(`Element does not contain a canvas panel or image service component:`, element)\n return\n }\n}\n\n/**\n * Parse comma separated region string into target object\n * @param {String} region @example '100,200,100,100'\n * @return {Object} target\n * @property x {Number} starting x-coordinate\n * @property y {Number} starting y-coordinate\n * @property width {Number}\n * @property height {Number}\n */\nconst getTarget = (region) => {\n const [x, y, width, height] = region.split(',').map((x) => parseInt(x.trim()))\n return { x, y, width, height }\n}\n\n/**\n * Scroll to a figure, or go to figure slide in lightbox\n * Select annotations and/or region, and update the URL\n * @param {String} figureId The id of the figure in figures.yaml\n * @param {Array} annotationIds The IIIF ids of the annotations to select\n * @param {String} region The canvas region\n */\nconst goToFigureState = function ({ annotationIds=[], figureId, index, region }) {\n if (!figureId) {\n console.error(`goToFigureState called without an undefined figureId`)\n return\n }\n const figureSelector = `#${figureId}`\n const slideSelector = `[data-lightbox-slide-id=\"${figureId}\"]`\n const figure = document.querySelector(figureSelector)\n const figureSlide = document.querySelector(slideSelector)\n const serviceId = getServiceId(figure || figureSlide)\n\n if (!figure && !figureSlide) return\n\n const inputs = document.querySelectorAll(`#${figureId} .annotations-ui__input, [data-lightbox-slide-id=\"${figureId}\"] .annotations-ui__input`)\n const annotations = [...inputs].map((input) => {\n const id = input.getAttribute('data-annotation-id')\n input.checked = annotationIds.includes(id)\n return annotationData(input)\n })\n\n if (figureSlide) {\n const lightbox = figureSlide.closest('q-lightbox')\n lightbox.currentId = figureId\n }\n\n /**\n * Update figure state\n */\n update(serviceId, { annotations, index, region })\n\n /**\n * Build URL\n */\n const url = new URL(window.location.pathname, window.location.origin)\n url.hash = figureId\n scrollToHash(url.hash)\n const params = new URLSearchParams(\n annotationIds.map((id) => ['annotation-id', encodeURIComponent(id)]),\n )\n region ? params.set('region', encodeURIComponent(region)) : null\n const paramsString = params.toString()\n const urlParts = [url.pathname]\n if (paramsString) urlParts.push(paramsString)\n window.history.pushState({}, '', `${urlParts.join('?')}${url.hash}`)\n}\n\n/**\n * Handle UI changes on input select and call `update`\n */\nconst handleSelect = (element) => {\n const elementId = element.getAttribute('id')\n const serviceId = getServiceId(element.closest('.q-figure, .q-lightbox-slides__slide'))\n const inLightbox = document.querySelector('q-lightbox').contains(element)\n const annotation = annotationData(element)\n const { checked, input, type } = annotation\n /**\n * Two-way data binding for annotaion UI inputs in lightbox and inline\n */\n if (inLightbox) {\n const inlineInput = document.querySelector(`#${elementId.split('lightbox-')[1]}`)\n if (inlineInput) inlineInput.checked = element.checked\n } else {\n const lightboxInput = document.querySelector(`#${['lightbox', elementId].join('-')}`)\n if (lightboxInput) lightboxInput.checked = element.checked\n }\n /**\n * Prevent deselecting all layers if choices and checkboxes are used together\n */\n if (input === 'checkbox' && type === 'choice') {\n const form = element.closest('form')\n const checkedInputs = form.querySelectorAll('.annotations-ui__input[checked]')\n if (!checked && checkedInputs.length === 1) {\n checkedInputs[0].setAttribute('disabled', true)\n }\n if (checked) {\n const disabledInput = form.querySelector('[disabled]')\n disabledInput ? disabledInput.removeAttribute('disabled') : null\n }\n }\n /**\n * Update figure state\n */\n update(serviceId, { annotations: [annotation] })\n}\n\n/**\n * Handle annotation/choice selection with canvasPanel API\n * {@link https://iiif-canvas-panel.netlify.app/docs/examples/handling-choice}\n * @param {HTMLElement} element\n */\nconst selectAnnotation = (canvasPanel, annotation) => {\n const { checked, id, type } = annotation\n /**\n * Update annotation selection\n */\n switch (type) {\n case 'annotation':\n canvasPanel.applyStyles(id, { opacity: Number(!!checked) })\n break\n case 'choice':\n /**\n * `canvasPanel.makeChoice` is defined asynchronously on the web component,\n * and while the event model emits a 'choice' event when a choice is selected, it is noisy,\n * and since there is no 'done' event to indicate when this method is available,\n * we will use polling\n */\n poll({\n callback: () => selectChoice(canvasPanel, annotation),\n validate: () => !!canvasPanel.makeChoice\n })\n break\n default:\n break\n }\n}\n\nconst selectChoice = (canvasPanel, annotation) => {\n const { checked, id, input } = annotation\n /**\n * Note: It's necessary to update the attribute *and* call `makeChoice`\n * when updating choice and region at the same time\n */\n canvasPanel.setAttribute('choice-id', id)\n canvasPanel.makeChoice(id, {\n deselect: !checked,\n deselectOthers: input === 'radio'\n })\n}\n\n/**\n * Add event handlers to Annotations UI links and inputs\n */\nconst setUpUIEventHandlers = () => {\n /**\n * Add click handlers to annoRef shortcodes\n */\n const annoRefs = document.querySelectorAll('.annoref')\n for (const annoRef of annoRefs) {\n let annotationIds = annoRef.getAttribute('data-annotation-ids')\n annotationIds = annotationIds.length ? annotationIds.split(',') : undefined\n const figureId = annoRef.getAttribute('data-figure-id')\n const index = annoRef.getAttribute('data-index')\n /**\n * Annoref shortcode resets the region if none is provided\n */\n const region = annoRef.getAttribute('data-region')\n annoRef.addEventListener('click', ({ target }) => {\n goToFigureState({ annotationIds, figureId, index, region })\n })\n }\n\n /**\n * Add click handlers to UI inputs\n */\n const inputs = document.querySelectorAll('.annotations-ui__input')\n for (const input of inputs) {\n handleSelect(input)\n input.addEventListener('click', ({ target }) => handleSelect(target))\n }\n}\n\n/**\n * Update canvas panel or image-service properties\n *\n * @param {String} id Canvas ID or path to image-service info.json\n * @param {Object} data\n * @property {String} region comma-separated, @example \"x,y,width,height\"\n * @property {Array