makethisbetter 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +62 -0
- package/README.md +113 -0
- package/dist/annotate/draw.d.ts +21 -0
- package/dist/annotate/draw.d.ts.map +1 -0
- package/dist/annotate/draw.test.d.ts +2 -0
- package/dist/annotate/draw.test.d.ts.map +1 -0
- package/dist/annotate/highlighter.d.ts +11 -0
- package/dist/annotate/highlighter.d.ts.map +1 -0
- package/dist/annotate/highlighter.test.d.ts +2 -0
- package/dist/annotate/highlighter.test.d.ts.map +1 -0
- package/dist/annotate/overlay.d.ts +17 -0
- package/dist/annotate/overlay.d.ts.map +1 -0
- package/dist/annotate/overlay.test.d.ts +2 -0
- package/dist/annotate/overlay.test.d.ts.map +1 -0
- package/dist/annotate/pin.d.ts +10 -0
- package/dist/annotate/pin.d.ts.map +1 -0
- package/dist/annotate/pin.test.d.ts +2 -0
- package/dist/annotate/pin.test.d.ts.map +1 -0
- package/dist/annotate/session.d.ts +20 -0
- package/dist/annotate/session.d.ts.map +1 -0
- package/dist/annotate/session.test.d.ts +2 -0
- package/dist/annotate/session.test.d.ts.map +1 -0
- package/dist/annotate/toolbar.d.ts +17 -0
- package/dist/annotate/toolbar.d.ts.map +1 -0
- package/dist/annotate/toolbar.test.d.ts +2 -0
- package/dist/annotate/toolbar.test.d.ts.map +1 -0
- package/dist/api/client.d.ts +10 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.test.d.ts +2 -0
- package/dist/api/client.test.d.ts.map +1 -0
- package/dist/context/collector.d.ts +10 -0
- package/dist/context/collector.d.ts.map +1 -0
- package/dist/context/collector.test.d.ts +2 -0
- package/dist/context/collector.test.d.ts.map +1 -0
- package/dist/context/console.d.ts +10 -0
- package/dist/context/console.d.ts.map +1 -0
- package/dist/context/console.test.d.ts +2 -0
- package/dist/context/console.test.d.ts.map +1 -0
- package/dist/context/error-source.d.ts +21 -0
- package/dist/context/error-source.d.ts.map +1 -0
- package/dist/context/error-source.test.d.ts +2 -0
- package/dist/context/error-source.test.d.ts.map +1 -0
- package/dist/context/frustration.d.ts +27 -0
- package/dist/context/frustration.d.ts.map +1 -0
- package/dist/context/frustration.test.d.ts +2 -0
- package/dist/context/frustration.test.d.ts.map +1 -0
- package/dist/global.d.ts +3 -0
- package/dist/global.d.ts.map +1 -0
- package/dist/i18n/en.d.ts +47 -0
- package/dist/i18n/en.d.ts.map +1 -0
- package/dist/i18n/index.d.ts +4 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.test.d.ts +2 -0
- package/dist/i18n/index.test.d.ts.map +1 -0
- package/dist/i18n/zh-CN.d.ts +47 -0
- package/dist/i18n/zh-CN.d.ts.map +1 -0
- package/dist/index.d.mts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/makethisbetter.cjs.js +84 -0
- package/dist/makethisbetter.cjs.js.map +1 -0
- package/dist/makethisbetter.esm.js +1433 -0
- package/dist/makethisbetter.esm.js.map +1 -0
- package/dist/makethisbetter.js +84 -0
- package/dist/makethisbetter.js.map +1 -0
- package/dist/popup/comment.d.ts +31 -0
- package/dist/popup/comment.d.ts.map +1 -0
- package/dist/popup/comment.test.d.ts +2 -0
- package/dist/popup/comment.test.d.ts.map +1 -0
- package/dist/popup/success.d.ts +8 -0
- package/dist/popup/success.d.ts.map +1 -0
- package/dist/popup/success.test.d.ts +2 -0
- package/dist/popup/success.test.d.ts.map +1 -0
- package/dist/record/control-bar.d.ts +12 -0
- package/dist/record/control-bar.d.ts.map +1 -0
- package/dist/record/control-bar.test.d.ts +2 -0
- package/dist/record/control-bar.test.d.ts.map +1 -0
- package/dist/record/session.d.ts +26 -0
- package/dist/record/session.d.ts.map +1 -0
- package/dist/record/session.test.d.ts +2 -0
- package/dist/record/session.test.d.ts.map +1 -0
- package/dist/screenshot/capture.d.ts +3 -0
- package/dist/screenshot/capture.d.ts.map +1 -0
- package/dist/screenshot/capture.test.d.ts +2 -0
- package/dist/screenshot/capture.test.d.ts.map +1 -0
- package/dist/styles.d.ts +9 -0
- package/dist/styles.d.ts.map +1 -0
- package/dist/types.d.ts +45 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/widget/controller.d.ts +37 -0
- package/dist/widget/controller.d.ts.map +1 -0
- package/dist/widget/controller.test.d.ts +2 -0
- package/dist/widget/controller.test.d.ts.map +1 -0
- package/dist/widget/frustration-prompt.d.ts +11 -0
- package/dist/widget/frustration-prompt.d.ts.map +1 -0
- package/dist/widget/frustration-prompt.test.d.ts +2 -0
- package/dist/widget/frustration-prompt.test.d.ts.map +1 -0
- package/dist/widget/payload.d.ts +5 -0
- package/dist/widget/payload.d.ts.map +1 -0
- package/dist/widget/payload.test.d.ts +2 -0
- package/dist/widget/payload.test.d.ts.map +1 -0
- package/dist/widget/pet.d.ts +12 -0
- package/dist/widget/pet.d.ts.map +1 -0
- package/dist/widget/pet.test.d.ts +2 -0
- package/dist/widget/pet.test.d.ts.map +1 -0
- package/dist/widget/shadow.d.ts +11 -0
- package/dist/widget/shadow.d.ts.map +1 -0
- package/dist/widget/shadow.test.d.ts +2 -0
- package/dist/widget/shadow.test.d.ts.map +1 -0
- package/dist/widget/tab.d.ts +10 -0
- package/dist/widget/tab.d.ts.map +1 -0
- package/dist/widget/tab.test.d.ts +2 -0
- package/dist/widget/tab.test.d.ts.map +1 -0
- package/package.json +64 -0
- package/src/annotate/draw.test.ts +168 -0
- package/src/annotate/draw.ts +108 -0
- package/src/annotate/highlighter.test.ts +47 -0
- package/src/annotate/highlighter.ts +84 -0
- package/src/annotate/overlay.test.ts +91 -0
- package/src/annotate/overlay.ts +44 -0
- package/src/annotate/pin.test.ts +87 -0
- package/src/annotate/pin.ts +28 -0
- package/src/annotate/session.test.ts +219 -0
- package/src/annotate/session.ts +104 -0
- package/src/annotate/toolbar.test.ts +132 -0
- package/src/annotate/toolbar.ts +81 -0
- package/src/api/client.test.ts +75 -0
- package/src/api/client.ts +77 -0
- package/src/context/collector.test.ts +14 -0
- package/src/context/collector.ts +41 -0
- package/src/context/console.test.ts +44 -0
- package/src/context/console.ts +34 -0
- package/src/context/error-source.test.ts +115 -0
- package/src/context/error-source.ts +62 -0
- package/src/context/frustration.test.ts +134 -0
- package/src/context/frustration.ts +145 -0
- package/src/global.ts +3 -0
- package/src/i18n/en.ts +47 -0
- package/src/i18n/index.test.ts +24 -0
- package/src/i18n/index.ts +16 -0
- package/src/i18n/zh-CN.ts +47 -0
- package/src/index.ts +32 -0
- package/src/popup/comment.test.ts +73 -0
- package/src/popup/comment.ts +177 -0
- package/src/popup/success.test.ts +75 -0
- package/src/popup/success.ts +33 -0
- package/src/record/control-bar.test.ts +110 -0
- package/src/record/control-bar.ts +46 -0
- package/src/record/session.test.ts +153 -0
- package/src/record/session.ts +139 -0
- package/src/screenshot/capture.test.ts +68 -0
- package/src/screenshot/capture.ts +96 -0
- package/src/styles/widget.css +847 -0
- package/src/styles.ts +9 -0
- package/src/types.ts +46 -0
- package/src/vite-env.d.ts +6 -0
- package/src/widget/controller.test.ts +62 -0
- package/src/widget/controller.ts +249 -0
- package/src/widget/frustration-prompt.test.ts +86 -0
- package/src/widget/frustration-prompt.ts +34 -0
- package/src/widget/payload.test.ts +167 -0
- package/src/widget/payload.ts +31 -0
- package/src/widget/pet.test.ts +63 -0
- package/src/widget/pet.ts +67 -0
- package/src/widget/shadow.test.ts +25 -0
- package/src/widget/shadow.ts +41 -0
- package/src/widget/tab.test.ts +92 -0
- package/src/widget/tab.ts +31 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { MakeThisBetter } from '../index'
|
|
3
|
+
|
|
4
|
+
vi.mock('html-to-image', () => ({
|
|
5
|
+
toPng: vi.fn(async () => 'data:image/png;base64,AA=='),
|
|
6
|
+
}))
|
|
7
|
+
|
|
8
|
+
describe('Pet mode', () => {
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
MakeThisBetter.destroy()
|
|
11
|
+
vi.restoreAllMocks()
|
|
12
|
+
document.body.innerHTML = ''
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('renders pet entry instead of tab when entryMode is pet', () => {
|
|
16
|
+
MakeThisBetter.init({
|
|
17
|
+
projectKey: 'proj_test',
|
|
18
|
+
entryMode: 'pet',
|
|
19
|
+
apiUrl: 'https://api.example.com/api/v1',
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const shadow = document.getElementById('mtb-widget-host')!.shadowRoot!
|
|
23
|
+
expect(shadow.querySelector('.mtb-pet')).toBeTruthy()
|
|
24
|
+
expect(shadow.querySelector('.mtb-tab')).toBeNull()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('shows speech bubble on the pet', () => {
|
|
28
|
+
MakeThisBetter.init({
|
|
29
|
+
projectKey: 'proj_test',
|
|
30
|
+
entryMode: 'pet',
|
|
31
|
+
apiUrl: 'https://api.example.com/api/v1',
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const shadow = document.getElementById('mtb-widget-host')!.shadowRoot!
|
|
35
|
+
const bubble = shadow.querySelector('.mtb-pet-bubble')
|
|
36
|
+
expect(bubble).toBeTruthy()
|
|
37
|
+
expect(bubble!.textContent).toBe('Need help?')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('opens annotation mode on pet click', () => {
|
|
41
|
+
MakeThisBetter.init({
|
|
42
|
+
projectKey: 'proj_test',
|
|
43
|
+
entryMode: 'pet',
|
|
44
|
+
apiUrl: 'https://api.example.com/api/v1',
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const shadow = document.getElementById('mtb-widget-host')!.shadowRoot!
|
|
48
|
+
shadow.querySelector<HTMLDivElement>('.mtb-pet')!.click()
|
|
49
|
+
expect(shadow.querySelector('.mtb-overlay')).toBeTruthy()
|
|
50
|
+
expect(shadow.querySelector('.mtb-toolbar')).toBeTruthy()
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('renders tab by default (no entryMode)', () => {
|
|
54
|
+
MakeThisBetter.init({
|
|
55
|
+
projectKey: 'proj_test',
|
|
56
|
+
apiUrl: 'https://api.example.com/api/v1',
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const shadow = document.getElementById('mtb-widget-host')!.shadowRoot!
|
|
60
|
+
expect(shadow.querySelector('.mtb-tab')).toBeTruthy()
|
|
61
|
+
expect(shadow.querySelector('.mtb-pet')).toBeNull()
|
|
62
|
+
})
|
|
63
|
+
})
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { ShadowContainer } from './shadow'
|
|
2
|
+
import type { I18nMessages } from '../i18n'
|
|
3
|
+
import { BRAND_COLOR } from '../styles'
|
|
4
|
+
|
|
5
|
+
export class PetEntry {
|
|
6
|
+
private el: HTMLDivElement
|
|
7
|
+
private bubble: HTMLDivElement
|
|
8
|
+
private onClick: () => void
|
|
9
|
+
|
|
10
|
+
constructor(
|
|
11
|
+
shadow: ShadowContainer,
|
|
12
|
+
messages: I18nMessages,
|
|
13
|
+
onClick: () => void,
|
|
14
|
+
) {
|
|
15
|
+
this.onClick = onClick
|
|
16
|
+
|
|
17
|
+
this.el = shadow.el<HTMLDivElement>('div', 'mtb-pet')
|
|
18
|
+
this.el.innerHTML = this.buildSVG()
|
|
19
|
+
this.el.setAttribute('role', 'button')
|
|
20
|
+
this.el.setAttribute('aria-label', messages.tab)
|
|
21
|
+
this.el.tabIndex = 0
|
|
22
|
+
|
|
23
|
+
this.bubble = shadow.el<HTMLDivElement>('div', 'mtb-pet-bubble')
|
|
24
|
+
this.bubble.textContent = messages.pet.bubble
|
|
25
|
+
this.el.appendChild(this.bubble)
|
|
26
|
+
|
|
27
|
+
this.el.addEventListener('click', this.onClick)
|
|
28
|
+
this.el.addEventListener('keydown', (e: KeyboardEvent) => {
|
|
29
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
30
|
+
e.preventDefault()
|
|
31
|
+
this.onClick()
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
shadow.append(this.el)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private buildSVG(): string {
|
|
39
|
+
return `<svg class="mtb-pet-svg" width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
40
|
+
<circle cx="24" cy="28" r="16" fill="${BRAND_COLOR}"/>
|
|
41
|
+
<circle cx="24" cy="28" r="14" fill="#10b981"/>
|
|
42
|
+
<circle cx="19" cy="25" r="3" fill="#fff"/>
|
|
43
|
+
<circle cx="29" cy="25" r="3" fill="#fff"/>
|
|
44
|
+
<circle cx="20" cy="24.5" r="1.5" fill="#064e3b"/>
|
|
45
|
+
<circle cx="30" cy="24.5" r="1.5" fill="#064e3b"/>
|
|
46
|
+
<ellipse cx="19.5" cy="23.5" rx="0.8" ry="0.6" fill="#fff" opacity="0.7"/>
|
|
47
|
+
<ellipse cx="29.5" cy="23.5" rx="0.8" ry="0.6" fill="#fff" opacity="0.7"/>
|
|
48
|
+
<path d="M20 31 Q24 34 28 31" stroke="#064e3b" stroke-width="1.5" stroke-linecap="round" fill="none"/>
|
|
49
|
+
<circle cx="15" cy="30" r="2.5" fill="${BRAND_COLOR}" opacity="0.4"/>
|
|
50
|
+
<circle cx="33" cy="30" r="2.5" fill="${BRAND_COLOR}" opacity="0.4"/>
|
|
51
|
+
<path d="M14 17 Q16 12 20 15" stroke="#10b981" stroke-width="2" stroke-linecap="round" fill="none"/>
|
|
52
|
+
<path d="M34 17 Q32 12 28 15" stroke="#10b981" stroke-width="2" stroke-linecap="round" fill="none"/>
|
|
53
|
+
</svg>`
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
setActive(active: boolean): void {
|
|
57
|
+
this.el.classList.toggle('active', active)
|
|
58
|
+
if (active) {
|
|
59
|
+
this.bubble.classList.add('hidden')
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
destroy(): void {
|
|
64
|
+
this.el.removeEventListener('click', this.onClick)
|
|
65
|
+
this.el.remove()
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
2
|
+
import { ShadowContainer } from './shadow'
|
|
3
|
+
|
|
4
|
+
describe('ShadowContainer', () => {
|
|
5
|
+
afterEach(() => {
|
|
6
|
+
document.body.innerHTML = ''
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
it('mounts widget styles inside a shadow root', () => {
|
|
10
|
+
const shadow = new ShadowContainer()
|
|
11
|
+
|
|
12
|
+
expect(document.getElementById('mtb-widget-host')?.shadowRoot).toBe(shadow.root)
|
|
13
|
+
expect(shadow.root.querySelector('style')).toBeInstanceOf(HTMLStyleElement)
|
|
14
|
+
|
|
15
|
+
shadow.destroy()
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('stores configured theme on the host', () => {
|
|
19
|
+
const shadow = new ShadowContainer('dark')
|
|
20
|
+
|
|
21
|
+
expect(document.getElementById('mtb-widget-host')?.getAttribute('data-mtb-theme')).toBe('dark')
|
|
22
|
+
|
|
23
|
+
shadow.destroy()
|
|
24
|
+
})
|
|
25
|
+
})
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import cssText from '../styles/widget.css?inline'
|
|
2
|
+
import type { MakeThisBetterConfig } from '../types'
|
|
3
|
+
|
|
4
|
+
export class ShadowContainer {
|
|
5
|
+
private host: HTMLElement
|
|
6
|
+
readonly root: ShadowRoot
|
|
7
|
+
|
|
8
|
+
constructor(theme: MakeThisBetterConfig['theme'] = 'auto') {
|
|
9
|
+
this.host = document.createElement('div')
|
|
10
|
+
this.host.id = 'mtb-widget-host'
|
|
11
|
+
this.host.style.cssText = 'position:fixed;top:0;left:0;width:0;height:0;overflow:visible;z-index:2147483646;'
|
|
12
|
+
this.host.setAttribute('data-mtb-theme', theme)
|
|
13
|
+
document.body.appendChild(this.host)
|
|
14
|
+
|
|
15
|
+
this.root = this.host.attachShadow({ mode: 'open' })
|
|
16
|
+
|
|
17
|
+
const style = document.createElement('style')
|
|
18
|
+
style.textContent = cssText
|
|
19
|
+
this.root.appendChild(style)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
el<T extends HTMLElement = HTMLElement>(tag: string, className?: string): T {
|
|
23
|
+
const el = document.createElement(tag) as T
|
|
24
|
+
if (className) el.className = className
|
|
25
|
+
return el
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
append(...nodes: Node[]): void {
|
|
29
|
+
for (const n of nodes) this.root.appendChild(n)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
remove(...nodes: Node[]): void {
|
|
33
|
+
for (const n of nodes) {
|
|
34
|
+
if (n.parentNode === this.root) this.root.removeChild(n)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
destroy(): void {
|
|
39
|
+
document.body.removeChild(this.host)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { FeedbackTab } from './tab'
|
|
3
|
+
import { ShadowContainer } from './shadow'
|
|
4
|
+
import { getMessages } from '../i18n'
|
|
5
|
+
|
|
6
|
+
function setupTab(position: 'left' | 'right' = 'right') {
|
|
7
|
+
const shadow = new ShadowContainer()
|
|
8
|
+
const onClick = vi.fn()
|
|
9
|
+
const messages = getMessages('en')
|
|
10
|
+
const tab = new FeedbackTab(shadow, messages, position, onClick)
|
|
11
|
+
|
|
12
|
+
return { shadow, tab, onClick, messages }
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe('FeedbackTab', () => {
|
|
16
|
+
it('renders button with feedback text', () => {
|
|
17
|
+
const { shadow, tab, messages } = setupTab()
|
|
18
|
+
|
|
19
|
+
const btn = shadow.root.querySelector<HTMLButtonElement>('.mtb-tab')
|
|
20
|
+
expect(btn).toBeTruthy()
|
|
21
|
+
expect(btn!.textContent).toBe(messages.tab)
|
|
22
|
+
expect(btn!.getAttribute('aria-label')).toBe(messages.tab)
|
|
23
|
+
|
|
24
|
+
tab.destroy()
|
|
25
|
+
shadow.destroy()
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('positions on right by default (no left class)', () => {
|
|
29
|
+
const { shadow, tab } = setupTab('right')
|
|
30
|
+
|
|
31
|
+
const btn = shadow.root.querySelector<HTMLButtonElement>('.mtb-tab')!
|
|
32
|
+
expect(btn.classList.contains('left')).toBe(false)
|
|
33
|
+
|
|
34
|
+
tab.destroy()
|
|
35
|
+
shadow.destroy()
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('adds left class when position is left', () => {
|
|
39
|
+
const { shadow, tab } = setupTab('left')
|
|
40
|
+
|
|
41
|
+
const btn = shadow.root.querySelector<HTMLButtonElement>('.mtb-tab')!
|
|
42
|
+
expect(btn.classList.contains('left')).toBe(true)
|
|
43
|
+
|
|
44
|
+
tab.destroy()
|
|
45
|
+
shadow.destroy()
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('calls onClick when button is clicked', () => {
|
|
49
|
+
const { shadow, tab, onClick } = setupTab()
|
|
50
|
+
|
|
51
|
+
shadow.root.querySelector<HTMLButtonElement>('.mtb-tab')!.click()
|
|
52
|
+
expect(onClick).toHaveBeenCalledOnce()
|
|
53
|
+
|
|
54
|
+
tab.destroy()
|
|
55
|
+
shadow.destroy()
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('setActive toggles active class', () => {
|
|
59
|
+
const { shadow, tab } = setupTab()
|
|
60
|
+
const btn = shadow.root.querySelector<HTMLButtonElement>('.mtb-tab')!
|
|
61
|
+
|
|
62
|
+
tab.setActive(true)
|
|
63
|
+
expect(btn.classList.contains('active')).toBe(true)
|
|
64
|
+
|
|
65
|
+
tab.setActive(false)
|
|
66
|
+
expect(btn.classList.contains('active')).toBe(false)
|
|
67
|
+
|
|
68
|
+
tab.destroy()
|
|
69
|
+
shadow.destroy()
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('destroy removes element from shadow root', () => {
|
|
73
|
+
const { shadow, tab } = setupTab()
|
|
74
|
+
|
|
75
|
+
expect(shadow.root.querySelector('.mtb-tab')).toBeTruthy()
|
|
76
|
+
tab.destroy()
|
|
77
|
+
expect(shadow.root.querySelector('.mtb-tab')).toBeNull()
|
|
78
|
+
|
|
79
|
+
shadow.destroy()
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('destroy removes click listener', () => {
|
|
83
|
+
const { shadow, tab, onClick } = setupTab()
|
|
84
|
+
|
|
85
|
+
tab.destroy()
|
|
86
|
+
const btn = shadow.root.querySelector('.mtb-tab')
|
|
87
|
+
expect(btn).toBeNull()
|
|
88
|
+
expect(onClick).not.toHaveBeenCalled()
|
|
89
|
+
|
|
90
|
+
shadow.destroy()
|
|
91
|
+
})
|
|
92
|
+
})
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ShadowContainer } from './shadow'
|
|
2
|
+
import type { I18nMessages } from '../i18n'
|
|
3
|
+
|
|
4
|
+
export class FeedbackTab {
|
|
5
|
+
private el: HTMLButtonElement
|
|
6
|
+
private onClick: () => void
|
|
7
|
+
|
|
8
|
+
constructor(
|
|
9
|
+
shadow: ShadowContainer,
|
|
10
|
+
messages: I18nMessages,
|
|
11
|
+
position: 'left' | 'right',
|
|
12
|
+
onClick: () => void,
|
|
13
|
+
) {
|
|
14
|
+
this.onClick = onClick
|
|
15
|
+
this.el = shadow.el<HTMLButtonElement>('button', 'mtb-tab')
|
|
16
|
+
if (position === 'left') this.el.classList.add('left')
|
|
17
|
+
this.el.textContent = messages.tab
|
|
18
|
+
this.el.setAttribute('aria-label', messages.tab)
|
|
19
|
+
this.el.addEventListener('click', onClick)
|
|
20
|
+
shadow.append(this.el)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
setActive(active: boolean): void {
|
|
24
|
+
this.el.classList.toggle('active', active)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
destroy(): void {
|
|
28
|
+
this.el.removeEventListener('click', this.onClick)
|
|
29
|
+
this.el.remove()
|
|
30
|
+
}
|
|
31
|
+
}
|