• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mozilla / blurts-server / #13117

pending completion
#13117

push

circleci

Vinnl
Pretend HTMLElement.shadowRoot is always set

We generally only tend to access it after having called
this.attachShadow({ mode: 'open' }), and the error message about it
potentially being undefined at every location we access
this.shadowRoot is more noisy than helpful.

See also
https://github.com/mozilla/blurts-server/pull/2959#discussion_r1154023113
and
https://github.com/mozilla/blurts-server/pull/2959#discussion_r1154960095

282 of 1629 branches covered (17.31%)

Branch coverage included in aggregate %.

1 of 1 new or added line in 1 file covered. (100.0%)

959 of 4374 relevant lines covered (21.93%)

1.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/client/js/components/custom-select.js
1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
 * License, v. 2.0. If a copy of the MPL was not distributed with this
3
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4

5
const html = `
×
6
<style>
7
  :host{
8
    contain: style paint;
9
    position: relative;
10
    display: inline-block;
11
    width: min(100%, var(--option-w) + 20px);
12
    color: var(--purple-70);
13
  }
14

15
  :host([hidden]) {
16
    display: none 
17
  }
18

19
  select{
20
    appearance: none;
21
    background: none;
22
    border: none;
23
    outline: none;
24
    width: 100%;
25
    margin: 0;
26
    padding: 0 20px 0 0;
27
    overflow: hidden;
28
    text-overflow: ellipsis;
29
    font: inherit;
30
    color: inherit;
31
 }
32

33
  select.hidden{
34
    position: absolute;
35
    visibility: hidden;
36
    width: auto;
37
    padding: 0;
38
    pointer-events: none;
39
 }
40

41
  svg {
42
    position: absolute;
43
    top: 0;
44
    right: 0;
45
    width: 16px;
46
    height: 100%;
47
    color: inherit;
48
    pointer-events: none;
49
  }
50
</style>
51

52
<select></select>
53
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="currentColor">
54
  <path d="M233.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 338.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z"/>
55
</svg>
56
`
57

58
customElements.define('custom-select', class extends HTMLElement {
×
59
  /** @type {HTMLSelectElement} */
60
  select
61

62
  constructor () {
63
    super()
×
64
    this.attachShadow({ mode: 'open' })
×
65
    this.shadowRoot.innerHTML = html
×
66
    // @ts-ignore: We know that this will not return null
67
    this.select = this.shadowRoot.querySelector('select')
×
68
    this.options = this.querySelectorAll('option')
×
69

70
    // move <option> elements into <select> (<slot> not permitted as <select> child)
71
    this.select.append(...this.options)
×
72
    this.setAttribute('value', this.select.value)
×
73
    this.setAttribute('selected-index', this.select.selectedIndex.toString())
×
74
  }
75

76
  get value () {
77
    return this.getAttribute('value')
×
78
  }
79

80
  get selectedIndex () {
81
    return this.getAttribute('selected-index')
×
82
  }
83

84
  connectedCallback () {
85
    this.matchOptionWidth()
×
86
    this.select?.addEventListener('change', this)
×
87
  }
88

89
  /**
90
   * @param {InputEvent & { target: HTMLSelectElement }} e
91
   */
92
  handleEvent (e) {
93
    switch (e.type) {
×
94
      case 'change':
95
        this.matchOptionWidth()
×
96
        this.setAttribute('value', e.target.value)
×
97
        this.setAttribute('selected-index', e.target.selectedIndex.toString())
×
98
        this.dispatchEvent(new Event('change'))
×
99
        break
×
100
    }
101
  }
102

103
  matchOptionWidth () {
104
    // update <select> width based on selected <option> (override fixed width based on largest <option>)
105
    /** @type {HTMLSelectElement & { w?: number }} */
106
    const temp = document.createElement('select')
×
107
    const selectedOption = this.options[this.select.selectedIndex]
×
108

109
    temp.className = 'hidden'
×
110
    temp.append(selectedOption.cloneNode(true))
×
111
    this.shadowRoot.append(temp)
×
112

113
    // let’s wait for the next tick to make sure that the dimensions of temp are available
114
    window.requestAnimationFrame(() => {
×
115
      temp.w = Math.ceil(temp.getBoundingClientRect().width) + 5 // adds 5px safety for font load delay or other quirks
×
116
      this.style.setProperty('--option-w', `${temp.w}px`)
×
117
      temp.remove()
×
118
    })
119
  }
120

121
  disconnectedCallback () {
122
    this.select.removeEventListener('change', this)
×
123
  }
124
})
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc