[
  {
    "path": ".github/workflows/publish.yml",
    "content": "name: Publish\n\non:\n  release:\n    types: [created]\n\njobs:\n  publish-npm:\n    name: Publish to npm\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n      id-token: write\n    steps:\n      - uses: actions/checkout@v6\n      - uses: actions/setup-node@v6\n        with:\n          node-version: 24\n          registry-url: https://registry.npmjs.org/\n          cache: npm\n      - run: npm install -g npm@latest\n      - run: npm install\n      - run: npm run build --if-present\n      - run: npm version ${TAG_NAME} --git-tag-version=false\n        env:\n          TAG_NAME: ${{ github.event.release.tag_name }}\n      - run: npm publish --provenance --access public\n"
  },
  {
    "path": ".github/workflows/static.yml",
    "content": "# Simple workflow for deploying static content to GitHub Pages\nname: Deploy static content to Pages\n\non:\n  # Runs on pushes targeting the default branch\n  push:\n    branches: [\"main\"]\n\n  # Allows you to run this workflow manually from the Actions tab\n  workflow_dispatch:\n\n# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages\npermissions:\n  contents: read\n  pages: write\n  id-token: write\n\n# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.\n# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.\nconcurrency:\n  group: \"pages\"\n  cancel-in-progress: false\n\njobs:\n  # Single deploy job since we're just deploying\n  deploy:\n    environment:\n      name: github-pages\n      url: ${{ steps.deployment.outputs.page_url }}\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n      - name: Setup Pages\n        uses: actions/configure-pages@v4\n      - name: Upload artifact\n        uses: actions/upload-pages-artifact@v3\n        with:\n          # Upload entire repository\n          path: '.'\n      - name: Deploy to GitHub Pages\n        id: deployment\n        uses: actions/deploy-pages@v4\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\ni-html.min.js\n"
  },
  {
    "path": "README.md",
    "content": "## i-html\n\n[i-html](https://github.com/keithamus/i-html) is a drop in tag that allows for dynamically _importing_ html,\n_inline_. It's a bit like an `<iframe>`, except the html gets adopted into the page.\n\n[Visit the website to learn more](https://keithcirkel.co.uk/i-html).\n"
  },
  {
    "path": "example-responses/ajax-form.html",
    "content": "<!DOCTYPE html>\n<html>\n\t<body>\n    <form\n      action=\"example-responses/ajax-form.html\"\n      method=\"get\"\n      target=\"ajax-form-target-example\"\n    >\n      <label>A form label:\n        <input type=\"search\" />\n      </label>\n      <button type=\"submit\">Submit</button>\n    </form>\n\n    <p>\n      Something would have saved to a server if I had stretched to pay for one,\n      but you'll just have to believe me.\n    </p>\n  </body>\n</html>\n\n"
  },
  {
    "path": "example-responses/dsd.html",
    "content": "<!DOCTYPE html>\n<html>\n\t<body>\n    <div>\n      <template shadowrootmode=\"open\">\n        <p><slot></slot></p>\n\n        <style>\n          :host {\n            font-family: Georgia, Times, 'Times New Roman', serif;\n            font-style: italic;\n            border-block-end: 2px dashed currentColor;\n          }\n        </style>\n      </template>\n      This will be formatted according to the stylesheet in shadow DOM.\n    </div>\n  </body>\n</html>\n"
  },
  {
    "path": "example-responses/empty.html",
    "content": "<!DOCTYPE html>\n<html>\n\t<body>\n    <span></span>\n  </body>\n</html>\n"
  },
  {
    "path": "example-responses/form-delete.html",
    "content": "<!DOCTYPE html>\n<html>\n\t<body>\n    <p>Whatever was saved has now been deleted.</p>\n  </body>\n</html>\n"
  },
  {
    "path": "example-responses/form-save.html",
    "content": "<!DOCTYPE html>\n<html>\n\t<body>\n    <p>Your form would have been saved if I was't too cheap to spring for a server.</p>\n  </body>\n</html>\n"
  },
  {
    "path": "example-responses/hello.html",
    "content": "<!DOCTYPE html>\n<html>\n\t<body>\n    <p>Hello world!</p>\n  </body>\n</html>\n"
  },
  {
    "path": "example-responses/how-are-you.html",
    "content": "<!DOCTYPE html>\n<html>\n\t<body>\n    <p>How are you?</p>\n  </body>\n</html>\n"
  },
  {
    "path": "example-responses/lazy.html",
    "content": "<!DOCTYPE html>\n<html>\n\t<body>\n    <p>This loaded in very lazily... but you might not have seen (try looking in network devtools)</p>\n  </body>\n</html>\n"
  },
  {
    "path": "example-responses/prepend-list.html",
    "content": "<!DOCTYPE html>\n<html>\n\t<body>\n    <ul>\n      <li>Butter</li>\n    </ul>\n  </body>\n</html>\n\n"
  },
  {
    "path": "example-responses/refresh-tock.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta http-equiv=\"refresh\" content=\"1;url=refresh.html\">\n  </head>\n\t<body>\n    <p>Tock!</p>\n  </body>\n</html>\n"
  },
  {
    "path": "example-responses/refresh.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta http-equiv=\"refresh\" content=\"1;url=refresh-tock.html\">\n  </head>\n\t<body>\n    <p>Tick!</p>\n  </body>\n</html>\n"
  },
  {
    "path": "example-responses/sanitization.html",
    "content": "<!DOCTYPE html>\n<html>\n\t<body>\n    <p>This response contained an <code>&lt;i-html></code> element, but it was sanitized out!</p>\n    <i-html>You should not see this!</i-html>\n    <p>This repsonse also contained an image of a cat. This was explicitly allowed!</p>\n    <img src=\"https://placecats.com/150/150\"/>\n  </body>\n</html>\n"
  },
  {
    "path": "example-responses/theme-gray.html",
    "content": "<!DOCTYPE html>\n<html>\n\t<body>\n    <style>\n      :root {\n        --background: #f6f8fa !important;\n        --color: #1f2328 !important;\n        --link-color: #393f46 !important;\n        --link-visited-color: #59636e !important;\n        --border-color: #dae0e7 !important;\n      }\n    </style>\n  </body>\n</html>\n"
  },
  {
    "path": "example-responses/theme-pink.html",
    "content": "<!DOCTYPE html>\n<html>\n\t<body>\n    <style>\n      :root {\n        --background: #fff0f6 !important;\n        --color: #a61e4d !important;\n        --link-color: #364fc7 !important;\n        --link-visited-color: #5f3dc4 !important;\n        --border-color: #a61e4d !important;\n      }\n    </style>\n  </body>\n</html>\n"
  },
  {
    "path": "example-responses/theme-yellow.html",
    "content": "<!DOCTYPE html>\n<html>\n\t<body>\n    <style>\n      :root {\n        --background: #fff9db !important;\n        --color: #d9480f !important;\n        --link-color: #364fc7 !important;\n        --link-visited-color: #5f3dc4 !important;\n        --border-color: #e67700 !important;\n      }\n    </style>\n  </body>\n</html>\n"
  },
  {
    "path": "example-responses/two-paragraphs.html",
    "content": "<!DOCTYPE html>\n<html>\n\t<body>\n    <p>\n      This was the first paragraph from the response.\n    </p>\n    <p>\n      This was the second paragraph from the response.\n    </p>\n  </body>\n</html>\n\n"
  },
  {
    "path": "i-html.js",
    "content": "// A small polyfill for CSSStateSet\nclass StateSet extends Set {\n  #el = null\n  #existing = null\n  constructor(el, existing) {\n    super()\n    this.#el = el\n    this.#existing = existing\n  }\n  add(state) {\n    super.add(state)\n    const existing = this.#existing\n    if (existing) {\n      try {\n        existing.add(state)\n      } catch {\n        existing.add(`--${state}`)\n      }\n    } else {\n      this.#el.setAttribute(`state-${state}`, '')\n    }\n  }\n  delete(state) {\n    super.delete(state)\n    const existing = this.#existing\n    if (existing) {\n      existing.delete(state)\n      existing.delete(`--${state}`)\n    } else {\n      this.#el.removeAttribute(`state-${state}`)\n    }\n  }\n  has(state) {\n    return super.has(state)\n  }\n  clear() {\n    for(const state of this) this.delete(state)\n  }\n}\n\nconst queueATask = () => new Promise(resolve => setTimeout(resolve, 0))\n\nconst styles = new CSSStyleSheet()\nstyles.replace(`\n  :host {\n    display: contents;\n  }\n`)\n\nexport class RequestEvent extends Event {\n  request = null\n  constructor(request) {\n    super('loadstart', {})\n    this.request = request\n  }\n}\n\nexport class InsertEvent extends Event {\n  content = null\n  constructor(name, content, init) {\n    super(name, init)\n    this.content = content\n  }\n}\n\nfunction handleLinkTargets(event) {\n  const el = event.target.closest('a[target]')\n  const base = event.target.ownerDocument.head.querySelector('base[target]')\n  const target = el && el.target ? document.getElementById(el.target) : base && base.target ? document.getElementById(base.target) : null\n  if (!target || !(target instanceof IHTMLElement)) return\n  target.src = el.href\n  event.preventDefault()\n}\n\nconst textMime = /^text\\/([^+]+\\+)?plain\\s*(?:;.*)?$/\nconst htmlMime = /^text\\/([^+]+\\+)?html\\s*(?:;.*)?$/\nconst svgMime = /^image\\/(svg\\+)xml\\s*(?:;.*)?$/\nconst xmlMime = /^application\\/([^+]+\\+)?xml\\s*(?:;.*)?$/\nconst eventStreamMime = /^text\\/([^+]+\\+)?event-stream\\s*(?:;.*)?$/\nconst wildcardMime = /^\\*\\/(?:[^+]+\\+)?\\*\\s*(?:;.*)?$/\n\nconst validAllow = new Set(['refresh', 'iframe', 'i-html', 'media', 'script', 'style', 'cross-origin'])\n\nexport class IHTMLElement extends HTMLElement {\n  static observedAttributes = ['src', 'accept', 'loading', 'allow']\n\n  get src() {\n    return new URL(this.getAttribute('src') || '', window.location.href).toString()\n  }\n\n  set src(val) {\n    this.setAttribute('src', val)\n  }\n\n  get credentials(){\n    let credentials = this.getAttribute('credentials')\n    if (credentials == 'include' || credentials == 'omit') return credentials\n    return 'same-origin'\n  }\n\n  set credentials(value) {\n    this.setAttribute(credentials, value)\n  }\n\n  get #defaultTarget() {\n    if (svgMime.test(this.accept)) return 'svg'\n    return 'body > *'\n  }\n\n  get target() {\n    const target = this.getAttribute('target') || this.#defaultTarget\n    try {\n      this.matches(target)\n      return target\n    } catch {\n      return this.#defaultTarget\n    }\n  }\n\n  set target(value) {\n    this.setAttribute('target', value)\n  }\n\n  #allow = new Set()\n  get allow() {\n    return Array.from(this.#allow).join(' ')\n  }\n\n  set allow(value) {\n    this.setAttribute('allow', value)\n    if (value == '*') value = [...validAllow].join(' ')\n    this.#allow = new Set(String(value).split(/ /g).filter(allow => validAllow.has(allow)))\n  }\n\n  get insert() {\n    const insert = this.getAttribute('insert')\n    if (insert === 'append') return 'append'\n    if (insert === 'prepend') return 'prepend'\n    return 'replace'\n  }\n\n  set insert(value) {\n    this.setAttribute('insert', value)\n  }\n\n  get loading() {\n    if (this.getAttribute('loading') === 'lazy') return 'lazy'\n    if (this.getAttribute('loading') === 'none') return 'none'\n    return 'eager'\n  }\n\n  set loading(value) {\n    this.setAttribute('loading', value)\n  }\n\n  get accept() {\n    const accept = this.getAttribute('accept') || ''\n    if (textMime.test(accept)) return accept\n    if (htmlMime.test(accept)) return accept\n    if (svgMime.test(accept)) return accept\n    if (xmlMime.test(accept)) return accept\n    if (wildcardMime.test(accept)) return accept\n    return 'text/html'\n  }\n\n  set accept(val) {\n    this.setAttribute('accept', val)\n  }\n\n  #internals = this.attachInternals()\n  #fetchController = new AbortController()\n  #observer = new IntersectionObserver(\n    entries => {\n      for (const entry of entries) {\n        if (entry.isIntersecting) {\n          const {target} = entry\n          this.#observer.unobserve(target)\n          if (!this.shadowRoot.contains(target)) return\n          if (this.loading === 'lazy') {\n            this.#load()\n          }\n        }\n      }\n    },\n    {\n      // Currently the threshold is set to 256px from the bottom of the viewport\n      // with a threshold of 0.1. This means the element will not load until about\n      // 2 keyboard-down-arrow presses away from being visible in the viewport,\n      // giving us some time to fetch it before the contents are made visible\n      rootMargin: '0px 0px 256px 0px',\n      threshold: 0.01,\n    },\n  )\n\n  constructor() {\n    super()\n    Object.defineProperty(this.#internals, 'states', {value: new StateSet(this, this.#internals.states)})\n    if (!this.shadowRoot) {\n      this.attachShadow({mode: 'open'})\n      this.shadowRoot.adoptedStyleSheets.push(styles)\n      this.shadowRoot.append(document.createElement('slot'))\n      this.shadowRoot.append(document.createElement('span'))\n    }\n    this.#internals.states.add('waiting')\n    this.#internals.role = 'presentation'\n  }\n\n  attributeChangedCallback(name, old, value) {\n    if (name === 'src' || name === 'accept') {\n      if (this.isConnected && this.loading === 'eager') {\n        this.#load()\n      } else if (this.loading !== 'eager') {\n        this.#fetchController?.abort()\n      }\n    } else if (name === 'loading') {\n      if (this.isConnected && old !== 'eager' && value === 'eager') {\n        this.#load()\n      } else if (this.isConnected && value === 'lazy') {\n        this.#observe()\n      }\n    } else if (name === 'allow') {\n      this.#allow = new Set(String(value).split(/ /g).filter(allow => validAllow.has(allow)))\n    }\n  }\n\n  connectedCallback() {\n    if (this.src && this.loading === 'eager') {\n      this.#load()\n    }\n    this.#observe()\n    this.addEventListener('command', this)\n    this.ownerDocument.addEventListener('click', handleLinkTargets, true)\n    this.ownerDocument.addEventListener('submit', this, true)\n  }\n\n  handleEvent(event) {\n    if (event.type == 'command') {\n      if (event.command == '--load') {\n        this.#load()\n      } else if (event.command == '--stop') {\n        clearTimeout(this.#refreshTimer)\n        this.#fetchController?.abort('stop')\n      }\n    } else if (event.type === 'submit') {\n      const form = event.target\n      if (form.tagName !== 'FORM') return\n      const base = form.ownerDocument.head.querySelector('base[target]')\n      const targetId = form.target || base?.target\n      if (document.getElementById(targetId) !== this) return\n      const action = event.submitter.getAttribute('formaction') || form.action\n      const method = (event.submitter.getAttribute('formmethod') || form.method || 'GET').toUpperCase()\n      const formData = new FormData(form)\n      if (method === 'GET') {\n        const url = new URL(action, window.location.href)\n        url.search = new URLSearchParams(formData).toString()\n        this.src = url.toString()\n      } else {\n        this.#load(action, {method, body: new URLSearchParams(formData)})\n      }\n      event.preventDefault()\n    }\n  }\n\n  disconnectedCallback() {\n    this.#fetchController?.abort('disconnected')\n  }\n\n  #observe() {\n    this.#observer.observe(this.shadowRoot.querySelector('span'))\n  }\n\n  #refreshTimer = null\n  #setupRefresh(refresh) {\n    if (!this.#allow.has('refresh')) return\n    let [time, url] = String(refresh).split(/;\\s*url=/) || []\n    time = time ? Number(time) : -1\n    clearTimeout(this.#refreshTimer)\n    if (time > -1 && time < Number.MAX_SAFE_INTEGER) {\n      this.#refreshTimer = setTimeout(() => this.#load(url), Number(time) * 1000)\n    }\n  }\n\n  async #load(src, options) {\n    if (!src && !this.hasAttribute('src')) return\n    src = new URL(src || this.src, this.src || window.location.href)\n    if (!this.#allow.has('cross-origin') && src.origin !== window.location.origin) {\n      console.log(src, window.location.origin);\n      throw new Error(`i-html failed to load cross origin resource ${src} without allow=cross-origin`)\n    }\n    if (!options && !this.#fetchController?.signal.aborted && this.#fetchController?.src == src.toString()) return\n    clearTimeout(this.#refreshTimer)\n    this.#fetchController.abort()\n    this.#fetchController = new AbortController()\n    this.#fetchController.src = src.toString()\n    this.#internals.states.delete('error')\n    this.#internals.states.delete('waiting')\n    this.#internals.states.add('loading')\n    // We mimic the same event order as <img>, including the spec\n    // which states events must be dispatched after \"queue a task\".\n    // https://www.w3.org/TR/html52/semantics-embedded-content.html#the-img-element\n    await queueATask()\n    let error = false\n    try {\n      const method = options?.method || 'GET'\n      const fetchOptions = {\n        method,\n        credentials: this.credentials,\n        headers: {\n          Accept: this.accept,\n        },\n      }\n      if (options?.body) {\n        fetchOptions.body = options.body\n        fetchOptions.headers['Content-Type'] = 'application/x-www-form-urlencoded'\n      }\n      const request = new RequestEvent(new Request(src, fetchOptions))\n      if (eventStreamMime.test(this.accept)) {\n        await this.#stream(request.request)\n      } else {\n        await this.#loadOnce(request.request)\n      }\n    } catch (e) {\n      error = e\n    } finally {\n      this.#fetchController.abort()\n      // We mimic the same event order as <img>, including the spec\n      // which states events must be dispatched after \"queue a task\".\n      // https://www.w3.org/TR/html52/semantics-embedded-content.html#the-img-element\n      await queueATask()\n      this.#internals.states.delete('loading')\n      this.#internals.states.delete('streaming')\n      this.#internals.states.add(error ? 'error' : 'loaded')\n      this.dispatchEvent(new Event(error ? 'error' : 'load'))\n      this.dispatchEvent(new Event('loadend'))\n      if (error) throw error\n    }\n  }\n\n  async #stream(request) {\n    const source = new EventSource(request.src)\n    this.#fetchController.signal.addEventListener('abort', () => {\n      source.close()\n    })\n    let open = false\n    source.addEventListener('message', e => {\n      if (!open) return\n      this.#parseAndInject(message.data, 'text/html')\n    })\n    await new Promise((resolve, reject) => {\n      source.addEventListener('open', resolve, {once: true})\n      source.addEventListener('error', reject, {once: true})\n    })\n    open = true\n    this.dispatchEvent(new Event('open'))\n    this.#internals.states.delete('loading')\n    this.#internals.states.add('streaming')\n    await new Promise((resolve, reject) => {\n      source.addEventListener('close', resolve, {once: true})\n      source.addEventListener('error', reject, {once: true})\n    })\n  }\n\n  async #loadOnce(request) {\n    const signal = this.#fetchController.signal\n    let response\n    try {\n      response = await fetch(request, {signal})\n    } catch (e) {\n      if (e.code == DOMException.ABORT_ERR) return\n      throw e\n    }\n    if (!response) {\n      throw new Error(`Failed to load response`)\n    }\n\n    this.#setupRefresh(response.headers.get('Refresh') || '')\n    const ct = response.headers.get('Content-Type') || ''\n    const accept = this.accept\n    if (!wildcardMime.test(accept)) {\n      let ctParts = (ct.match(htmlMime) || ct.match(textMime) || ct.match(xmlMime) || ct.match(svgMime) || [])[1]\n      let acceptParts = (accept.match(htmlMime) || accept.match(xmlMime) || accept.match(svgMime) || [])[1]\n      if (ctParts && ctParts !== acceptParts) {\n        throw new Error(`Failed to load resource: expected ${accept} but was ${ct}`)\n      }\n    }\n\n    let resolvedCt = htmlMime.test(ct) ? 'text/html' : textMime.test(ct) ? 'text/plain' : xmlMime.test(ct) ? 'application/xml' : svgMime.test(ct) ? 'image/svg+xml' : null\n    if (!resolvedCt) {\n      throw new Error(`Failed to load resource: expected mime to be like 'text/html', 'application/xml' or 'image/svg+xml', but got ${ct || '(empty string)'}`)\n    }\n    this.#parseAndInject(await response.text(), resolvedCt)\n  }\n\n  #parseAndInject(responseText, mime) {\n    let children;\n    if (mime == 'text/plain') {\n      const doc = new DOMParser().parseFromString('<!DOCTYPE html>', 'text/html')\n      const span = document.createElement('span')\n      span.textContent = responseText;\n      doc.body.append(span)\n      children = doc.querySelectorAll('span')\n    } else {\n      let doc\n      if (mime == 'text/html' && 'parseHTMLUnsafe' in Document) {\n        doc = Document.parseHTMLUnsafe(responseText)\n      } else {\n        doc = new DOMParser().parseFromString(responseText, mime)\n      }\n      this.#setupRefresh(doc.querySelector('meta[http-equiv=\"refresh\"]')?.content || '')\n      children = this.#sanitize(doc).querySelectorAll(this.target)\n    }\n    const beforeInsert = new InsertEvent('beforeinsert', children, { cancelable: true })\n    const shouldContinue = this.dispatchEvent(beforeInsert) && children.length\n    if (!shouldContinue) {\n      return\n    }\n    const oldActiveElement = this.ownerDocument.activeElement\n    if (this.insert === 'append') {\n      this.append(...beforeInsert.content)\n    } else if (this.insert === 'prepend') {\n      this.prepend(...beforeInsert.content)\n    } else {\n      this.replaceChildren(...beforeInsert.content)\n    }\n    const activeElement = this.ownerDocument.activeElement\n    if (activeElement != oldActiveElement) {\n      activeElement.focus({ preventScroll: true })\n    }\n    this.dispatchEvent(new InsertEvent('inserted', this.childNodes))\n  }\n\n  #sanitize(doc) {\n    let removes = []\n    const allows = this.#allow\n    if (!allows.has('iframe')) removes.push('iframe')\n    if (!allows.has('i-html')) removes.push('i-html')\n    if (!allows.has('script')) removes.push('script')\n    if (!allows.has('style')) removes.push('style', 'link[rel=stylesheet]')\n    if (!allows.has('media')) removes.push('img', 'picture', 'video', 'audio', 'object')\n    if (removes.length) {\n      for(const el of doc.querySelectorAll(removes.join(', '))) el.remove()\n    }\n    return doc\n  }\n}\n\ncustomElements.define('i-html', IHTMLElement)\n"
  },
  {
    "path": "index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <title>i-html, an inline-html import element</title>\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />\n    <script type=\"module\" src=\"./i-html.js\"></script>\n    <script type=\"module\" src=\"https://unpkg.com/invokers-polyfill@0.4.2/invoker.js\"></script>\n  <style>\n    :root {\n      --background: #e7ecec;\n      --color: #262a2a;\n      --link-color: #3566b8;\n      --link-visited-color: #7652ac;\n      --border-color: #8d9292;\n    }\n    html {\n      font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\";\n      max-width: 70ch;\n      margin: auto;\n      line-height: 1.5;\n      font-size: 1.125em;\n      overflow-wrap: break-word;\n      background: var(--background);\n      color: var(--color);\n    }\n    a { color: var(--link-color) }\n    a:visited { color: var(--link-visited-color) }\n    p {\n      max-width: 70ch;\n      margin: 0.5rem 0;\n    }\n    h1, h2, h3, h4 {\n      margin: 2rem 0 0 0;\n    }\n    .demo {\n      border: 1px solid var(--border-color);\n      border-radius: 8px;\n      max-width: 70ch;\n      min-height: 1ch;\n      padding: 1rem;\n    }\n    mark {background:#ead79b}\n    mark[a] {background:#a5e9ca}\n    mark[b] {background:#d1d1ff}\n    @media (prefers-color-scheme: dark) {\n      :root {\n        --background: #232626;\n        --color: #cacfcf;\n        --link-color: #3b86ff;\n        --link-visited-color: #a167f1;\n        --border-color: #8d9292;\n      }\n    }\n  </style>\n  </head>\n  <body>\n    <main>\n      <h1>i-html</h1>\n      <p>\n        <a href=\"https://github.com/keithamus/i-html\">i-html</a> is a drop in\n        tag that allows for dynamically <em>importing</em> html,\n        <em>inline</em>. It's a bit like an <code>&lt;iframe></code>, except the\n        html gets adopted into the page.\n      </p>\n      <p>\n        You might have used something similar before, it might seem familiar to\n        other techniques such as\n        <a href=\"https://turbo.hotwired.dev/\">hotwired turbo</a> or similar. You\n        might have even used an element very close to this one, for example the\n        popular\n        <a href=\"https://github.com/github/include-fragment-element\"\n          >include-fragment-element</a\n        >\n        by GitHub. This element is a spiritual successor to that one (more on\n        that below). But this one is <code>&lt;i-html></code>. Let's talk about\n        it.\n      </p>\n      <p>\n        Inside the box below is a demonstration of the element. The box is there\n        to help you see it, but the element is inside. If JavaScript is enabled\n        the contents of the box should read \"Hello world!\". The source page is\n        just an HTML page. There's nothing special about it. Look, go see for\n        yourself: <a href=\"example-responses/hello.html\">hello.html</a>.\n      </p>\n\n      <pre>\n        <code>\n  &lt;i-html <mark>src=\"example-responses/hello.html\"</mark>>Loading...&lt;/i-html>\n        </code>\n      </pre>\n\n      <div class=\"demo\">\n        <i-html src=\"example-responses/hello.html\">Loading...</i-html>\n      </div>\n\n      <p>\n        The <code>&lt;i-html></code> tag can be placed on a page, as an empty\n        container. It is completely unstyled (well almost, it has\n        <code>display:contents</code>). Whenever the <code>src=</code> attribute\n        changes, it will fetch the requested resource as\n        <code>text/html</code> and replace its inner contents with the parsed\n        contents of the response. That's essentially all it does. Kind of.\n      </p>\n\n      <p>\n        Setting <code>src=</code> and leaving it alone does not really\n        demonstrate the full utility, however, because it is far more capable of\n        interesting things when utilised correctly. People do lots of novel\n        things with <code>&lt;img></code> tags, and <code>&lt;i-html></code>\n        should be no different.\n      </p>\n\n      <h2 id=\"getting-started\">Getting started</h2>\n\n      <p>\n        The easiest way to start is to include the minified script from the CDN:\n      </p>\n\n      <pre>\n        <code>\n  &lt;script src=\"https://cdn.jsdelivr.net/npm/i-html-element/i-html.min.js\" defer>&lt;/script>\n        </code>\n      </pre>\n\n      <p>\n        or run <code>npm i i-html-element</code> and use the local module:\n      </p>\n\n      <pre>\n        <code>\n  &lt;script type=\"module\" src=\"node_modules/i-html-element/i-html.js\">&lt;/script>\n        </code>\n      </pre>\n\n      <h2 id=\"features\">Features</h2>\n\n      <h3 id=\"link-target\">Targeting with link</h3>\n\n      <p>\n        Like an iframe, <code>&lt;i-html></code> respects the\n        <code>target=</code> attribute on links. If an\n        <code>&lt;a target=></code> points to the <code>&lt;i-html></code>, then\n        the <code>src=</code> is switched with that of the link. A\n        demonstration:\n      </p>\n\n      <pre>\n        <code>\n  &lt;a href=\"example-responses/hello.html\" <mark>target=\"link-target-example\"</mark>>\n    Load hello.html\n  &lt;/a>\n  &lt;a href=\"example-responses/how-are-you.html\" <mark>target=\"link-target-example\"</mark>>\n    Load how-are-you.html\n  &lt;/a>&lt;br>\n  &lt;i-html <mark>id=\"link-target-example\"</mark>>&lt;/i-html>\n        </code>\n      </pre>\n\n      <div class=\"demo\">\n        <a href=\"example-responses/hello.html\" target=\"link-target-example\">\n          Load hello.html\n        </a>\n        <a href=\"example-responses/how-are-you.html\" target=\"link-target-example\">\n          Load how-are-you.html\n        </a><br>\n        <i-html id=\"link-target-example\"></i-html>\n      </div>\n\n      <h3 id=\"form-target\">Targeting with form</h3>\n\n      <p>\n        Also like an iframe, <code>target=</code> on forms is also respected.\n        This means forms can submit to an <code>&lt;i-html></code> element which\n        will then load in new content. Here's a dummy form, for example:\n      </p>\n\n      <pre>\n        <code>\n  &lt;form action=\"example-responses/form-save.html\" method=\"get\" <mark>target=\"form-target-example\"</mark>>\n    &lt;label>\n      A form label:\n      &lt;input type=\"search\" />\n    &lt;/label>\n    &lt;button type=\"submit\">Submit&lt;/button>\n  &lt;/form>\n\n  &lt;p>Submitting the form will change the content below:&lt;/p>\n\n  &lt;i-html <mark>id=\"form-target-example\"</mark>>Nothing yet&lt;/i-html>\n        </code>\n      </pre>\n\n      <div class=\"demo\">\n        <form action=\"example-responses/form-save.html\" method=\"get\" target=\"form-target-example\">\n          <label>\n             A form label:\n            <input type=\"search\" />\n          </label>\n          <button type=\"submit\">Submit</button>\n        </form>\n\n        <p>Submitting the form will change the content below:</p>\n\n        <i-html id=\"form-target-example\">Nothing yet</i-html>\n      </div>\n\n      <h3 id=\"form-wrap\">Wrapping a form</h3>\n\n      <p>\n        Wrap a <code>&lt;form></code> tag in an <code>&lt;i-html></code> element\n        and you'll have a form that replaces itself. Serve the same form back\n        and you have an \"AJAX style\" form with no effort.\n      </p>\n      \n      <pre>\n        <code>\n  &lt;i-html <mark>id=\"ajax-form-target-example\"</mark>>\n    &lt;form action=\"example-responses/ajax-form.html\" method=\"get\" <mark>target=\"ajax-form-target-example\"</mark>>\n      &lt;label>\n        A form label:\n        &lt;input type=\"search\" />\n      &lt;/label>\n      &lt;button type=\"submit\">Submit&lt;/button>\n    &lt;/form>\n\n    &lt;p>Submitting the form will change the whole form&lt;/p>\n  &lt;/i-html>\n        </code>\n      </pre>\n\n      <div class=\"demo\">\n        <i-html id=\"ajax-form-target-example\">\n          <form action=\"example-responses/ajax-form.html\" method=\"get\" target=\"ajax-form-target-example\">\n            <label>\n              A form label:\n              <input type=\"search\" />\n            </label>\n            <button type=\"submit\">Submit</button>\n          </form>\n\n          <p>Submitting the form will change the whole form</p>\n        </i-html>\n      </div>\n\n      <h3 id=\"form-actions\">Form actions</h3>\n\n      <p>\n        Of course because <code>&lt;forms></code> work, so does\n        <code>&lt;button formaction=</code>. Example:\n      </p>\n\n      <pre>\n        <code>\n  &lt;form method=\"get\" <mark>target=\"formaction-example\"</mark>>\n    &lt;button <mark a>formaction=\"example-responses/form-delete.html\"</mark> type=\"submit\">\n      Delete\n    &lt;/button>\n    &lt;button <mark a>formaction=\"example-responses/form-save.html\"</mark> type=\"submit\">\n      Save\n    &lt;/button>\n  &lt;/form>\n\n  &lt;p>Submitting the form will change the content below:&lt;/p>\n\n  &lt;i-html <mark>id=\"formaction-example\"</mark>>Nothing yet&lt;/i-html>\n        </code>\n      </pre>\n\n      <div class=\"demo\">\n        <form method=\"get\" target=\"formaction-example\">\n          <button formaction=\"example-responses/form-delete.html\" type=\"submit\">\n            Delete\n          </button>\n          <button formaction=\"example-responses/form-save.html\" type=\"submit\">\n            Save\n          </button>\n        </form>\n\n        <p>Submitting the form will change the content below:</p>\n\n        <i-html id=\"formaction-example\">Nothing yet</i-html>\n      </div>\n\n      <h3 id=\"targeting-response\">Targeting the response</h3>\n\n      <p>\n        By default <code>&lt;i-html></code> will parse the response as HTML,\n        and inject the <code>&lt;body></code> element's contents into itself,\n        ignoring any unwrapped text nodes. The selection of content to be\n        injected can be customized by setting the <code>target=</code> attribute\n        on <code>&lt;i-html></code> itself (which defaults to <code>'body>*'</code>\n        (or <code>'svg'</code> if <code>accept=</code> is an SVG mime)). If you\n        find you want to take a different element from the response body, set\n        <code>target=</code> to any valid <code>querySelector</code>. If\n        <code>target=</code> is an invalid <code>querySelector</code> it'll\n        revert to <code>'body>*'</code>. You can check if a\n        <code>target=</code> select is valid by using JavaScript to set\n        <code>.target</code> and then read the value back out, for example:\n      </p>\n\n      <pre>\n        <code>\n  myEl.target = '/not a valid selector/' // this won't set:\n  console.assert(myEl.target === 'body')\n\n  myEl.accept = '.this[is]:valid' // this will set:\n  console.assert(myEl.target === '.this[is]:valid')\n        </code>\n      </pre>\n\n      <p>\n        In the below example, these two links fetch the same HTML, which has two\n        paragraphs. However the two links target two different\n        <code>&lt;i-html></code> elements, the first one with a\n        <code>target=\"p:first-child\"</code>, the second with a\n        <code>target=\"p:last-child\"</code>.\n      </p>\n      \n      <pre>\n        <code>\n  &lt;a href=\"example-responses/two-paragraphs.html\" <mark>target=\"two-paragraphs-a\"</mark>>\n    Load two-paragraphs.html into first target\n  &lt;/a>\n  &lt;a href=\"example-responses/two-paragraphs.html\" <mark>target=\"two-paragraphs-b\"</mark>>\n    Load two-paragraphs.html into first target\n  &lt;/a>\n  &lt;p>&lt;i-html <mark>id=\"two-paragraphs-a\"</mark> <mark a>target=\"p:first-child\"</mark>>&lt;/i-html>&lt;/p>\n  &lt;p>&lt;i-html <mark>id=\"two-paragraphs-b\"</mark> <mark a>target=\"p:last-child\"</mark>>&lt;/i-html>&lt;/p>\n        </code>\n      </pre>\n\n      <div class=\"demo\">\n        <a href=\"example-responses/two-paragraphs.html\" target=\"two-paragraphs-a\">\n          Load two-paragraphs.html into first target\n        </a>\n        <a href=\"example-responses/two-paragraphs.html\" target=\"two-paragraphs-b\">\n          Load two-paragraphs.html into first target\n        </a>\n        <p><i-html id=\"two-paragraphs-a\" target=\"p:first-child\"></i-html></p>\n        <p><i-html id=\"two-paragraphs-b\" target=\"p:last-child\"></i-html></p>\n      </div>\n\n      <h3 id=\"re-fetching\">Refetching</h3>\n\n      <p>\n        If you want to refetch the contents there are several options. The\n        simplest is to use an <code>&lt;a href></code> element, as clicking the\n        link will cause <code>&lt;i-html></code> to load each time.\n      </p>\n\n      <p>\n        That's not always the easiest though, and so another option is to use a\n        <code>&lt;button command=\"--load\" commandfor=\"..\"></code> element,\n        which will cause the <code>&lt;i-html></code> to load the\n        <code>src</code> it has (regardless of the <code>loading=</code> value,\n        see <a href=\"#deferring-loading\">Deferring Loading</a> for more on\n        that).\n      </p>\n\n      <details>\n        <summary>(For older browsers that don't support command/commandfor buttons)</summary>\n        <p>\n          You'll need to drop in the \"invokers-polyfill\" package to polyfill\n          this in older browsers.\n        </p>\n        <pre><code>\n          &lt;script type=\"module\" src=\"https://unpkg.com/invokers-polyfill@0.4.2/invoker.js\">\n          &lt;/script>\n        </code></pre>\n      </details>\n\n      <pre>\n        <code>\n  &lt;button <mark>command=\"--load\" commandfor=\"command-load-example\"</mark>>Load&lt;/button>\n  &lt;i-html <mark>id=\"command-load-example\"</mark> src=\"example-responses/hello.html\">&lt;/i-html>\n        </code>\n      </pre>\n\n      <div class=\"demo\">\n        <button command=\"--load\" commandfor=\"command-load-example\">Load</button>\n        <i-html id=\"command-load-example\" src=\"example-responses/hello.html\"></i-html>\n      </div>\n\n      <p>\n        Responses can indicate to the client when the content should refresh by\n        adding a `Refresh` header (or a <code>&lt;meta http-equiv=refresh></code>\n        meta tag), telling <code>&lt;i-html></code> to refresh after N seconds.\n        This is <em>opt-in</em> though, and needs the <code>allow=refresh</code>\n        attribute on the element.\n      </p>\n      <p>\n        The `Refresh` header can also have a URL to traverse to. This is also\n        respected, which can make for some interesting properties such as the\n        example below. Lastly, this can be stopped with\n        <code>&lt;button command=\"--stop\"</code>:\n      </p>\n\n      <pre>\n        <code>\n  &lt;button <mark>command=\"--stop\" commandfor=\"refresh-example\"</mark>>Stop&lt;/button>\n  &lt;i-html <mark b>allow=\"refresh\"</mark> <mark>id=\"refresh-example\"</mark> src=\"example-responses/refresh.html\">&lt;/i-html>\n        </code>\n      </pre>\n\n      <div class=\"demo\">\n        <button command=\"--stop\" commandfor=\"refresh-example\">Stop</button>\n        <i-html allow=\"refresh\" loading=\"lazy\" id=\"refresh-example\" src=\"example-responses/refresh.html\"></i-html>\n      </div>\n\n\n      <h3 id=\"content-negotiation\">Content negotiation</h3>\n\n      <p>\n        By default <code>&lt;i-html></code> will make a request using the header\n        <code>Accept: text/html</code>. You can customise this by setting the\n        <code>accept=</code> attribute, but it is limited to certain values:\n      </p>\n      <ul>\n        <li><code>text/plain</code>.</li>\n        <li><code>text/html</code>.</li>\n        <li><code>image/svg+xml</code>.</li>\n        <li><code>application/xml</code>.</li>\n      </ul>\n      <p>\n        You can slightly customise these types by setting the \"subtype combination\"\n        or \"parameter values\", and it'll be smart and make the request with those\n        extras. This means <code>application/xhtml+xml</code> will work, and the\n        expected return content-type must be either <code>application/xml</code> or\n        <code>application/xhtml+xml</code> (but an entirely different sub-type like\n        <code>application/atom+xml</code> will fail). Also more complex mime\n        types like <code>text/fragment+html; charset=utf-8</code> will work, and\n        the server can respond with the generic <code>text/html</code> or the matching\n        sub-type of <code>text/fragment+html</code>.\n      </p>\n      <p>\n        Invalid mimes such as <code>application/json</code> will revert to\n        <code>text/html</code>. (If you want to get JSON you don't need this element).\n      </p>\n      <p>\n        You can check what type works by using JavaScript to set <code>.accept</code>\n        and then read the value back out, for example:\n      </p>\n\n      <pre>\n        <code>\n  myEl.accept = 'application/json' // this won't set:\n  console.assert(myEl.accept === 'text/html')\n\n  myEl.accept = 'text/fragment+html; charset=utf-8' // this will set:\n  console.assert(myEl.accept === 'text/fragment+html; charset=utf-8')\n        </code>\n      </pre>\n\n      <p>\n        The mime types <code>text/html</code>, <code>image/svg+xml</code>, and\n        <code>application/xml</code> will all use <code>DOMParser</code> to\n        parse the full response body. <code>text/event-stream</code> uses a\n        different streaming mode...\n      </p>\n\n      <pre>\n        <code>\n  &lt;a href=\"star-solid.svg\" <mark>target=\"svg-example\"</mark>>\n    &lt;i-html <mark>id=\"svg-example\"</mark> src=\"example-responses/star.svg\" <mark a>accept=\"image/svg+xml\"</mark>>&lt;/i-html>\n  &lt;/a>\n        </code>\n      </pre>\n\n      <div class=\"demo\">\n        <center>\n          <a href=\"example-responses/star-solid.svg\" target=\"svg-example\">\n            <i-html id=\"svg-example\" src=\"example-responses/star.svg\" accept=\"image/svg+xml\"></i-html>\n          </a>\n        </center>\n      </div>\n\n\n      <h3 id=\"streaming-mode\">Streaming Mode</h3>\n\n      <p>\n        With the <code>accept=</code> attribute set to\n        <code>text/event-stream</code>, it will use an\n        <code>EventSource</code> to listen for\n        <a\n          href=\"https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events\"\n          >Server-Side Events</a\n        >. A connection will be open and for each event fired, the response will\n        be converted using <code>DOMParser</code> and replaced. This allows for\n        many replacements with one request (this could be useful, for example,\n        to show a notification count for a user). The connection will be kept\n        open and replacing contents until either the server drops out, the\n        browser closes the <code>EventSource</code>, the element is removed from\n        the DOM, or the <code>src=</code> or <code>accept=</code> attributes\n        change value.\n      </p>\n\n      <h3 id=\"insertion-mode\">Insertion Mode</h3>\n\n      <p>\n        Whether in streaming mode or one-shot mode, the default operation is to\n        replace the contents of the element with the newly downloaded contents.\n        While this is useful <em>most of the time</em>, some of the time it can\n        be even more useful to switch to an append mode. Changing to\n        <code>insert=append</code> will cause new content to be added after all\n        the current children, and using <code>insert=prepend</code> will cause\n        new content to be added before the current children:\n      </p>\n\n      <pre>\n        <code>\n  &lt;a href=\"example-responses/prepend-list.html\" <mark>target=\"prepend-example\"</mark>>\n    Prepend to this list:\n  &lt;/a>\n  &lt;ul>\n    &lt;i-html <mark>id=\"prepend-example\"</mark> <mark a>target=\"li\"</mark> <mark b>insert=\"prepend\"</mark>>\n      &lt;li>Milk&lt;/li>\n      &lt;li>Eggs&lt;/li>\n    &lt;/i-html>\n  &lt;/ul>\n        </code>\n      </pre>\n\n      <div class=\"demo\">\n        <a href=\"example-responses/prepend-list.html\" target=\"prepend-example\">\n          Prepend to this list:\n        </a>\n        <ul>\n          <i-html id=\"prepend-example\" target=\"li\" insert=\"prepend\">\n            <li>Milk</li>\n            <li>Eggs</li>\n          </i-html>\n        </ul>\n      </div>\n\n      <h3 id=\"deferring-loading\">Deferring Loading</h3>\n\n      <p>\n        Just like <code>&lt;img></code> and <code>&lt;iframe></code> tags,\n        <code>&lt;i-html></code> tags have a <code>loading=</code> attribute. By\n        default they are <code>loading=eager</code>, but changing it to\n        <code>loading=lazy</code> means it will wait until it's visible in the\n        viewport until it makes the request. This also respects CSS styles, so\n        if it's inside an element with <code>display:none</code> then\n        <code>loading=lazy</code> can prevent it from loading until its\n        container is <code>display:block</code>. This is really handy for\n        components like <code>&lt;dialog></code>s.\n      </p>\n\n      <p>\n        Changing to <code>loading=none</code> means it will <em>never load</em>,\n        unless the <code>loading=</code> attribute is changed back to either\n        <code>loading=eager</code> or <code>loading=lazy</code>. This gives you\n        an opportunity to use JavaScript to determine when the loading should\n        occur, for example on click.\n      </p>\n\n      <p>\n        A <code>&lt;button command=\"--load\" commandfor=\"..\"></code> element\n        pointing to a, <code>&lt;i-html></code> element will force it to load,\n        regardless of the <code>loading=</code> value.\n      </p>\n\n      <div class=\"demo\">\n        <i-html id=\"lazy-example\" loading=\"lazy\" src=\"example-responses/lazy.html\">\n          ... lazy loading ...\n        </i-html>\n      </div>\n\n      <h3 id=\"security\">Security</h3>\n\n      <p>\n        Injecting arbitrary HTML into a page can pose some security risks, so\n        it's important that there's a <em>defence in depth</em> approach to\n        mitigating the risk surface area. <code>&lt;i-html></code> has some\n        security provisions in place but it's important to not only lean on\n        these, and also apply additional security measures:\n      </p>\n\n      <ul>\n        <li>\n          Use a <a href=\"https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP\">\n            Content Security Policy\n          </a>. A good minimum default is <code>script-src self</code>, which\n          will prevent JavaScript from executing unless it's served by the same\n          origin, and only from <code>&lt;script></code> tags, not inline\n          attributes such as <code>onclick</code>.\n        </li>\n        <li>\n          Consider how to <a href=\"https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html\">\n            protect against CSRF\n          </a>. A good measure would be HTTP-only session cookies to protect\n          sensitive resources.\n        </li>\n        <li>\n          Fine tune your <a href=\"https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS\">\n            CORS policies\n          </a> to protect against injecting pages you might not want\n          <code>&lt;i-html></code> to embed.\n        </li>\n      </ul>\n\n      <p>\n        With that out of the way, let's talk about the protections\n        <code>&lt;i-html></code> has in place. It uses <code>fetch()</code>\n        under-the-hood and so must adhere to CORS policies. By default the\n        <code>credentials</code> option is set to <code>'same-origin'</code>\n        meaning it will send cookies (and respect the <code>Set-Cookie</code>\n        response header) for same-orign requests. To lock this down further\n        setting <code>credentials=\"omit\"</code> will never send/recieve cookies,\n        and setting <code>credentials=\"include\"</code> will open it up to\n        sending/receiving cookies in cross-origin requests.\n      </p>\n\n      <p>\n        The default behaviour of <code>&lt;i-html></code> is restricted in some\n        ways, and these restrictions can be lifted by adding keywords into the\n        <code>allow=</code> attribute. This attribute takes a space-separated\n        list of well-known keywords, and each one will allow a certain type of\n        previously restricted behaviour to happen. The keywords can be combined,\n        so for example <code>allow=\"refresh media cross-origin\"</code> is a\n        valid value.\n      </p>\n\n      <h4 id=\"cross-origin\">Cross Origin (<code>allow=\"cross-origin\"</code>)</h4>\n\n      <p>\n        The default behaviour of <code>&lt;i-html></code> is to <em>only</em>\n        fetch same-origin URLs. This means\n        <code>&lt;i-html src=\"https://other-site\"></code> won't actually do\n        anything, as you'll need to allow cross-origin requests explicitly.\n        This can be done with the <code>allow=\"cross-origin\"</code> attribute.\n      </p>\n\n      <p>\n        It is important to note that <code>allow=\"cross-origin\"</code> only\n        impacts the <em>initial fetch</em>. Without\n        <code>allow=\"cross-origin\"</code> a page may still have URLs or\n        resources (such as images) to other origins, and these will be rendered\n        regardless of this setting.\n      </p>\n\n      <h4 id=\"sanitization\">Sanitization</h4>\n\n      <p>\n        <code>&lt;i-html></code> will never explicitly append certain elements\n        into the page, unless you opt in. For example if a response contains an\n        <code>&lt;iframe></code> element, this will simply be deleted before\n        the contents are injected. If you want <code>&lt;iframe></code>\n        elements to be injected, you'll need to add the\n        <code>allow=\"iframe\"</code> attribute to the element.\n        <code>&lt;iframe></code>s aren't the only elements that get sanitized.\n        In fact, there are quite a few. Anything that fetches a sub-resource\n        will be sanitized out by default, only to be opted in with an\n        <code>allow=</code> attribute.\n      </p>\n\n      <dl>\n        <dt><code>allow=\"iframe\"</code></dt>\n        <dd>\n          This allows the rendering of <code>&lt;iframe></code> elements in the\n          response body.\n        </dd>\n\n        <dt><code>allow=\"i-html\"</code></dt>\n        <dd>\n          This allows the rendering of <code>&lt;i-html></code> elements in the\n          response body.\n        </dd>\n\n        <dt><code>allow=\"script\"</code></dt>\n        <dd>\n          This allows the rendering of <code>&lt;script></code> elements in the\n          response body.\n        </dd>\n\n        <dt><code>allow=\"style\"</code></dt>\n        <dd>\n          This allows the rendering of <code>&lt;style></code> and\n          <code>&lt;link rel=stylesheet</code> elements in the response body.\n        </dd>\n\n        <dt><code>allow=\"media\"</code></dt>\n        <dd>\n          This allows the rendering of <code>&lt;img></code>,\n          <code>&lt;picture></code>, <code>&lt;video></code>,\n          <code>&lt;audio></code>, and <code>&lt;object></code>\n          elements in the response body.\n        </dd>\n      </dl>\n\n      <pre>\n        <code>\n  &lt;i-html <mark>src=\"example-responses/sanitization.html\"</mark> <mark b>allow=\"media\"</mark>>&lt;/i-html>\n        </code>\n      </pre>\n\n      <div class=\"demo\">\n        <i-html src=\"example-responses/sanitization.html\" allow=\"media\"></i-html>\n      </div>\n\n      <pre>\n        <code>\n  &lt;a target=\"theme-switcher\" href=\"example-responses/theme-pink.html\">Switch to pink theme&lt;/a>&lt;br>\n  &lt;a target=\"theme-switcher\" href=\"example-responses/theme-yellow.html\">Switch to yellow theme&lt;/a>&lt;br>\n  &lt;a target=\"theme-switcher\" href=\"example-responses/theme-gray.html\">Switch to grayscale theme&lt;/a>&lt;br>\n  &lt;a target=\"theme-switcher\" href=\"example-responses/empty.html\">Reset theme&lt;/a>&lt;br>\n  &lt;i-html id=\"theme-switcher\" allow=\"style\">&lt;/i-html>\n        </code>\n      </pre>\n\n      <div class=\"demo\">\n        <a target=\"theme-switcher\" href=\"example-responses/theme-pink.html\">Switch to pink theme</a><br>\n        <a target=\"theme-switcher\" href=\"example-responses/theme-yellow.html\">Switch to yellow theme</a><br>\n        <a target=\"theme-switcher\" href=\"example-responses/theme-gray.html\">Switch to grayscale theme</a><br>\n        <a target=\"theme-switcher\" href=\"example-responses/empty.html\">Reset theme</a><br>\n        <i-html id=\"theme-switcher\" allow=\"style\"></i-html>\n      </div>\n\n\n      <h4 id=\"turning-off-protections\">Tuning off protections</h4>\n\n      <p>\n        Of course, if you like to live dangerously, you can turn all of this off\n        by setting <code>allow=\"*\"</code>. This is a very bad idea but can be\n        useful during development.\n      </p>\n\n      <h3 id=\"dsd\">Declarative Shadow DOM</h3>\n\n      <p>\n        Support for parsing fragments of HTML which include declarative shadow DOM (DSD) templates via <code>Document.parseHTMLUnsafe</code> <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Document/parseHTMLUnsafe_static\">recently entered Baseline 2024 status</a>, so <code>&lt;i-html></code> will use that method of parsing if supported. Older browsers would need a polyfill to handle those cases, otherwise, <code>&lt;i-html></code> will fall back on <code>new DOMParser().parseFromString</code> which does not support DSD.\n      </p>\n\n      <p>\n        <strong>Note:</strong> the \"unsafe\" nomenclature of this API simply means the browser won't perform any sanitization. (Coming soon to the web platform, there will be a `parseHTML` counterpart which does.) But as explained above, <code>&lt;i-html></code> will perform its <em>own</em> sanitizing process unless you choose to opt into the various `allow` directives.\n      </p>\n\n      <div class=\"demo\">\n        <i-html id=\"dsd-example\" loading=\"lazy\" src=\"example-responses/dsd.html\">\n          ... loading dsd ...\n        </i-html>\n      </div>\n\n      <h3 id=\"styling\">Styling</h3>\n\n      <p>\n        By default this element is <code>display: contents</code> so it won't\n        effect layout, and it has <code>role=presentation</code> so the\n        container itself has no impact on the accessibility tree. All of the\n        content loaded in, however, will effect layout and the accessibility\n        tree. There are 4 pseudo classes you can use to style it during various\n        stages of its lifecycle, it will always be in one of these states, and\n        never more than one. The states are <code>loading</code>,\n        <code>loaded</code>, <code>errored</code>, and <code>waiting</code>.\n        They can be styled like so:\n      </p>\n\n      <pre>\n        <code>\n          <style style=\"display:block\">\n.foo:state(waiting) { background: grey; }\n.foo:state(loading) { background: yellow; }\n.foo:state(loaded) { background: green; }\n.foo:state(error) { background: red; }\n          </style>\n        </code>\n      </pre>\n\n      <details>\n        <summary>(For older browsers that don't support\n        <a\n          href=\"https://developer.mozilla.org/en-US/docs/Web/API/CustomStateSet\"\n          >CSS CustomStateSet</a\n        > you'll need some additional CSS.)</summary>\n\n        <p>\n          The element will fall back to using attributes like\n          <code>[state-waiting]</code> in older browsers, such as Safari 17.3\n          and below, Firefox 125 and below, and Chrome 89 and below.\n        </p>\n        <p>\n          Chrome versions 90-124 used a different syntax for <code>:state()</code>,\n          which used double dashes instead, like <code>:--waiting</code>. There's\n          a small bit of support code to handle those versions and so it will fall\n          back to using syntax like <code>:--waiting</code> if it needs to. If you need to\n          support all of these you'll need something more like:\n        </p>\n\n        <pre><code><style style=\"display:block\">\n.foo:where(:state(waiting), :--waiting, [state-waiting]) { background: grey; }\n.foo:where(:state(loading), :--loading, [state-loading]) { background: yellow; }\n.foo:where(:state(loaded), :--loaded, [state-loaded]) { background: green; }\n.foo:where(:state(error), :--error, [state-error]) { background: red; }\n            </style>\n          </code>\n        </pre>\n      </details>\n\n      <p>\n        Here are some details for what each of these states <em>mean</em>:\n      </p>\n\n      <ul>\n        <li>\n          <p>\n            <code>waiting</code> is the state used when the element is in the\n            DOM, but either does not have a <code>src=</code> or has\n            <code>loading=lazy</code> or <code>loading=none</code> and hasn't\n            yet begun to load. Once an element has left the <code>waiting</code>\n            state it'll never go back to it.\n          </p>\n        </li>\n        <li>\n          <p>\n            <code>loading</code> is the state used when the element is in making\n            a request, but the request hasn't yet completed. An individual\n            element can enter and exit this state multiple times per session.\n          </p>\n        </li>\n        <li>\n          <p>\n            <code>streaming</code> is the state used when the request was an\n            <code>text/event-stream</code> type, and the connection has been\n            opened. In this state events can stream in, and the\n            <code>loaded</code> state is removed. When the connection closes it\n            will transition to a <code>loaded</code> or <code>error</code>\n            state.\n          </p>\n        </li>\n        <li>\n          <p>\n            <code>loaded</code> is the state used when the element has\n            successfully completed and closed the request and has inserted the\n            content into the page, and has no more work to do for now. If the\n            <code>src=</code> changes, it might move back to the\n            <code>loading</code> state.\n          </p>\n        </li>\n        <li>\n          <p>\n            <code>error</code> is the state used when the element has completed\n            a request, but the request failed somehow. Unless in streaming mode,\n            the element won't have inserted any content into the page. If the\n            <code>src=</code> or <code>accept=</code> attributes change, it\n            might move back to the <code>loading</code> state.\n          </p>\n        </li>\n      </ul>\n\n      <h3 id=\"events\">Events</h3>\n\n      <p>\n        There are a wealth of events that are fired for each stage of the\n        element's lifecycle. Just like <code>&lt;img></code> and\n        <code>&lt;iframe></code> elements, <code>&lt;i-html></code> elements\n        dispatch <code>loadstart</code>, <code>load</code>,\n        <code>loadend</code>, and <code>error</code> events to announce which\n        stage of the loading process they're in. They also dispatch\n        <code>beforeinsert</code>, and <code>inserted</code> events.\n      </p>\n\n      <ul>\n        <li>\n          <p>\n            <code>loadstart</code> is dispatched right before a request is\n            started. This event also has a <code>.request</code> property that\n            is the <code>Request</code> object that will be given to\n            <code>fetch()</code>. You can re-assign <code>.request</code> or\n            mutate some of its properties, and whatever changes you make to it\n            will propagate to the <code>fetch()</code> call, so the\n            <code>loadstart</code> event is really useful if you want to\n            customise requests beyond the default capabilities, for example\n            adding new headers. You can call <code>.preventDefault()</code> on\n            this event to stop loading happening altogether, and no subsequent\n            events will fire unless the request lifecycle is restarted (for\n            example by changing <code>src=</code>).\n          </p>\n        </li>\n        <li>\n          <p>\n            <code>load</code> is dispatched as soon as the request has completed\n            successfully. When streaming, this will be dispatched upon a\n            successful (non error) close of the <code>EventSource</code>. This\n            event does not come with any additional properties. This event isn't\n            fired if the network request had an error.\n          </p>\n        </li>\n        <li>\n          <p>\n            <code>error</code> is dispatched if the network request failed for\n            some reason, or if it was unable to be created for example due to\n            bad Request data. This can happen when streaming if the connection\n            errors, even after events have been sent. This event does not come\n            with any additional properties.\n          </p>\n        </li>\n        <li>\n          <p>\n            <code>loadend</code> is dispatched at the end of a request,\n            regardless of the end state of the request. In other words this will\n            always come directly after a <code>load</code> or <code>error</code>\n            event. This event does not come with any additional properties.\n          </p>\n        </li>\n        <li>\n          <p>\n            <code>beforeinsert</code> is dispatched right <em>before</em>the\n            contents are about to be inserted into the page. In streaming mode\n            this event could be fired many times. This comes with a\n            <code>.content</code> property which is an array of all the child\n            <code>Node</code>s about to be inserted into the element. If you\n            re-assign or otherwise mutate this array, then that is what will be\n            inserted. This is useful for doing extra sanitization or simply\n            removing elements with additional scripting. If you want to do\n            something even more radical, you can call\n            <code>.preventDefault()</code> and it will <em>not</em>\n            insert the contents, leaving it up to you what to do next. Other\n            events will continue to fire, and if in streaming mode, you may get\n            new data coming in.\n          </p>\n        </li>\n        <li>\n          <p>\n            <code>inserted</code> is dispatched right <em>after</em> the\n            contents have been inserted into the page, provided that\n            <code>beforeinsert</code> was not cancelled. In streaming mode this\n            event could be fired many times. This comes with a\n            <code>.content</code> property which is an array of all the child\n            <code>Node</code>s that were inserted - which may be different to\n            the elements <code>.childNodes</code>. This event is not\n            preventable, as the action has already happened.\n          </p>\n        </li>\n      </ul>\n\n      <h2 id=\"faq\">Questions you might have</h2>\n\n      <h3 id=\"alternatives\">What about using &lt;alternative>?</h3>\n\n      <p>\n        There are many elements like this, but none that are <em>quite</em> like\n        this. I think this is a culmination of the best features of the other\n        elements.\n      </p>\n\n      <h3 id=\"more-alternatives\">What about using htmx / htmz?</h3>\n\n      <p>\n        <a href=\"https://htmx.org/\">htmx</a> and\n        <a href=\"https://leanrada.com/htmz\">htmz</a> came as small inspiration\n        for this library. htmx is a great project which demonstrates the power\n        of leveraging HTML, and htmz is a great lightweight alternative that\n        leans into the web platform even futher, but it also lacks some real\n        niceties that you'd want from a component like this. This is why\n        <code>&lt;i-html></code> exists - to take the concepts of htmz and\n        expand them into a fully-fledged element, adding the missing features.\n        If you want a fully featured and more popular library, try htmx.\n        If you're after a really tiny codebase and leveraging as much of the\n        web platform as possible, htmz looks to be a great choice. If you're\n        after something a little bit in-between, I think <code>&lt;i-html></code>\n        is the best next step.\n      </p>\n\n      <h3 id=\"include-fragment\">Why did you not extend include-fragment?</h3>\n\n      <p>\n        The\n        <a href=\"https://github.com/github/include-fragment-element\"\n          >include-fragment-element</a\n        >\n        is very similar to this, and I'm the current maintainer of both, so it\n        is within my power (and hubris) to make changes to that element to make\n        it more like this element. However, I think they are spiritually\n        different elements. This element definitely builds upon the success of\n        <code>&lt;include-fragment></code>, but it also changes some design\n        decisions that aren't worth changing in\n        <code>&lt;include-fragment></code>, given its large user base and the\n        amount of churn it would take to make those changes. In that regard you\n        might consider this element a \"rewrite\" or a \"major version bump\" of\n        <code>&lt;include-fragment</code>, but I think both have a valid use\n        case and certainly both can co-exist, maybe even on the same website.\n      </p>\n\n      <h3 id=\"include-fragment-differences\">\n        What are the differences between this and include-fragment then?\n      </h3>\n\n      <p>\n        Aside from the obvious difference being the name,\n        <code>&lt;include-fragment></code> has a smaller featureset, for example\n        it doesn't support streaming mode, it doesn't support custom CSS states,\n        it doesn't support preventing <code>loadstart</code> events. Those\n        things could be added, but then it also has some big differences that\n        are design decisions, and would be breaking changes.\n        <code>&lt;include-fragment></code> replaces <em>itself</em> on the page,\n        so a fully loaded fragment no longer exists on the page and the loaded\n        HTML stands in its place. This is different to\n        <code>&lt;i-html></code> which remains in the DOM, and can re-fetch\n        contents again and again. This design change gives the two elements very\n        different use cases, for example <code>&lt;i-html></code> respects link\n        and form <code>target=</code> attributes which would seem pointless to\n        do in <code>&lt;include-fragment></code> with this design choice in\n        mind.\n      </p>\n    </main>\n  </body>\n</html>\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"i-html-element\",\n  \"version\": \"0.6.0\",\n  \"description\": \"A drop-in tag that allows for dynamically importing html, inline. It's a bit like an <iframe>, except the html gets adopted into the page.\",\n  \"main\": \"i-html.js\",\n  \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\",\n    \"prepack\": \"npx esbuild --target=es2020 --minify --bundle i-html.js > i-html.min.js\",\n    \"prepublishOnly\": \"npm run prepack\"\n  },\n  \"files\": [\n    \"*.js\"\n  ],\n  \"author\": \"Keith Cirkel <https://keithcirkel.co.uk>\",\n  \"license\": \"MIT\"\n}\n"
  }
]