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

mozilla / blurts-server / #13051

pending completion
#13051

push

circleci

Vinnl
Add a bunch of initial type annotations

282 of 1619 branches covered (17.42%)

Branch coverage included in aggregate %.

34 of 34 new or added lines in 6 files covered. (100.0%)

959 of 4334 relevant lines covered (22.13%)

1.84 hits per line

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

0.0
/src/client/js/components/toast-alert.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
/**
6
 * Toast alert
7
 *
8
 * Displays a short message towards the top of user's screen,
9
 * and auto removes itself after a period of time (default ~7s)
10
 *
11
 * Client JS examples:
12
 * ```
13
 * const toast = document.createElement('toast-alert')
14
 * toast.textContent = 'Alert message here'
15
 * document.body.append(toast)
16
 * ```
17
 *
18
 * ```
19
 * const toast = document.createElement('toast-alert')
20
 * toast.textContent = 'Another alert message here'
21
 * toast.ttl = 10 // seconds before fade-out (defaults to 7)
22
 * toast.type = 'error' // 'error' (default) or 'success'
23
 * document.body.append(toast)
24
 * ```
25
 *
26
 * SSR/HTML examples:
27
 * ```
28
 * <toast-alert>Alert message here</toast-alert>
29
 * <toast-alert ttl='10' type='error'>Another alert message here</toast-alert>
30
 * ```
31
 */
32

33
const html = `
×
34
<style>
35
  :host{
36
    contain: layout style;
37
    position: fixed;
38
    top: var(--padding-md);
39
    left: 0;
40
    width: 100%;
41
    text-align: center;
42
    font-size: .875rem;
43
    color: white;
44
    transform: translateY(var(--toast-y, 0));
45
    transition: transform 0.3s;
46
    animation: fade-out 0.6s var(--ttl, 7s) forwards;
47
    z-index: 2;
48
    pointer-events: none;
49
  }
50

51
  :host(:hover){
52
    animation-play-state: paused;
53
  }
54

55
  :host([hidden]) {
56
    display: none 
57
  }
58

59
  output{
60
    position: relative;
61
    display: inline-block;
62
    padding: var(--padding-sm) var(--padding-xl);
63
    border-radius: var(--border-radius);
64
    box-shadow: 0 0 6px -3px black;
65
    animation: fly-in 0.3s forwards;
66
    pointer-events: auto;
67
  }
68

69
  :host([type="error"]) output {
70
    background-color: var(--red-70);
71
  }
72

73
  :host([type="success"]) output {
74
    background-color: var(--green-80);
75
  }
76

77
  button{
78
    position: absolute;
79
    top: 0;
80
    right: 0;
81
    height: 100%;
82
    padding: 0 var(--padding-md);
83
    border: none;
84
    cursor: pointer;
85
    font: inherit;
86
    color: inherit;
87
    background-color: transparent;
88
  }
89

90
  button:hover{
91
    box-shadow: inset 0 0 64px #fc95;
92
  }
93

94
  @keyframes fly-in{
95
    from{
96
      opacity: 0;
97
      transform: translateY(-30%);
98
    }
99
    to{
100
      opacity: 1;
101
    }
102
  }
103

104
  @keyframes fade-out{
105
    from{
106
      opacity: 1;
107
    }
108
    to{
109
      opacity: 0;
110
    }
111
  }
112
</style>
113

114
<output>
115
  <slot></slot>
116
  <button aria-label="Close">✕</button>
117
</output>
118
`
119

120
const ToastTypes = /** @type {const} */ {
×
121
  Error: 'error',
122
  Success: 'success'
123
}
124

125
customElements.define('toast-alert', class extends HTMLElement {
×
126
  constructor () {
127
    super()
×
128
    this.attachShadow({ mode: 'open' })
×
129
    // @ts-ignore this.shadowRoot exists, as per this.attachShadow above:
130
    this.shadowRoot.innerHTML = html
×
131
  }
132

133
  get ttl () {
134
    return this.getAttribute('ttl')
×
135
  }
136

137
  /** @param {string | null} value */
138
  set ttl (value) {
139
    this.setAttribute('ttl', value ?? '')
×
140
    this.style.setProperty('--ttl', `${value}s`) // seconds before fade-out starts
×
141
  }
142

143
  get type () {
144
    return this.getAttribute('type')
×
145
  }
146

147
  /** @param {string | null} value */
148
  set type (value) {
149
    const isValidType = typeof value === 'string' && Object.values(ToastTypes).includes(value)
×
150
    if (!isValidType) {
×
151
      console.warn(`Unknown toast type ${value}.`)
×
152
      return
×
153
    }
154

155
    this.setAttribute('type', value)
×
156
  }
157

158
  connectedCallback () {
159
    const toasts = /** @type {HTMLElement[]} */ (Array.from(document.querySelectorAll('toast-alert')).reverse())
×
160

161
    for (let i = 1, y = 0; i < toasts.length; i++) {
×
162
      // start at index 1 to push old toasts down with aggregated toast heights plus 10px gap
163
      y += toasts[i - 1].getBoundingClientRect().height + 10
×
164
      toasts[i].style.setProperty('--toast-y', `${y}px`)
×
165
    }
166

167
    if (this.hasAttribute('ttl')) {
×
168
      this.ttl = this.getAttribute('ttl')
×
169
    }
170

171
    if (this.hasAttribute('type')) {
×
172
      this.type = this.getAttribute('type')
×
173
    } else {
174
      this.type = ToastTypes.Error
×
175
    }
176

177
    // @ts-ignore this.shadowRoot exists, as per this.attachShadow in the constructor:
178
    this.shadowRoot.addEventListener('click', this)
×
179
    this.addEventListener('animationend', this)
×
180
  }
181

182
  /** @param {Event} e */
183
  handleEvent (e) {
184
    switch (true) {
×
185
      case e.target instanceof HTMLElement && e.target.matches('button'):
×
186
        this.remove()
×
187
        window.gtag('event', 'toast_alert', { action: 'dismiss' })
×
188
        break
×
189
      case e instanceof AnimationEvent && e.animationName === 'fade-out':
×
190
        this.remove()
×
191
        window.gtag('event', 'toast_alert', { action: 'faded' })
×
192
        break
×
193
    }
194
  }
195

196
  disconnectedCallback () {
197
    // @ts-ignore this.shadowRoot exists, as per this.attachShadow in the constructor:
198
    this.shadowRoot.removeEventListener('click', this)
×
199
    this.removeEventListener('animationend', this)
×
200
  }
201
})
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