copy_tuner_client 2.0.0 → 2.1.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.
@@ -0,0 +1,125 @@
1
+ import { OVERLAY_STYLES } from './styles'
2
+ import { computeBoundingBox } from './util'
3
+
4
+ type OpenCallback = (key: string) => void
5
+
6
+ type Blurb = {
7
+ keys: string[]
8
+ element: Element
9
+ }
10
+
11
+ const findBlurbs = (): Blurb[] =>
12
+ Array.from(document.querySelectorAll('[data-copyray-key]')).map((element) => ({
13
+ // 1 要素に複数キーがカンマ区切りで入りうる(同一テキストノードに複数訳文が連結された場合)
14
+ keys: (element.getAttribute('data-copyray-key') ?? '').split(',').filter(Boolean),
15
+ element,
16
+ }))
17
+
18
+ // オーバーレイ背景・翻訳要素のハイライト枠(specimen)・トグルボタンをまとめて Shadow DOM 内に描画する。
19
+ export class CopyrayOverlay extends HTMLElement {
20
+ #onOpen: OpenCallback = () => {}
21
+ #onToggle: () => void = () => {}
22
+ #backdrop: HTMLDivElement
23
+ #specimens: HTMLDivElement
24
+ #toggleButton: HTMLAnchorElement
25
+
26
+ constructor() {
27
+ super()
28
+ const shadow = this.attachShadow({ mode: 'open' })
29
+
30
+ const style = document.createElement('style')
31
+ style.textContent = OVERLAY_STYLES
32
+ shadow.append(style)
33
+
34
+ this.#backdrop = document.createElement('div')
35
+ this.#backdrop.classList.add('backdrop')
36
+ this.#backdrop.addEventListener('click', () => this.hide())
37
+
38
+ // specimen をページ座標基準で absolute 配置するコンテナ
39
+ this.#specimens = document.createElement('div')
40
+ this.#specimens.classList.add('specimens')
41
+
42
+ this.#toggleButton = document.createElement('a')
43
+ this.#toggleButton.classList.add('toggle-button')
44
+ this.#toggleButton.textContent = 'Open CopyTuner'
45
+ // 旧実装ではトグルボタンが overlay と bar の両方を表示していた。show() ではなく onToggle 経由で表示する。
46
+ this.#toggleButton.addEventListener('click', () => this.#onToggle())
47
+
48
+ shadow.append(this.#backdrop, this.#specimens, this.#toggleButton)
49
+
50
+ // 初期は非表示(背景と specimen を隠す)。トグルボタンは常時表示のため :host([hidden]) は使わず個別制御する。
51
+ this.hide()
52
+ }
53
+
54
+ set onOpen(callback: OpenCallback) {
55
+ this.#onOpen = callback
56
+ }
57
+
58
+ set onToggle(callback: () => void) {
59
+ this.#onToggle = callback
60
+ }
61
+
62
+ get isShowing(): boolean {
63
+ return !this.#backdrop.hidden
64
+ }
65
+
66
+ show() {
67
+ this.reset()
68
+ this.#backdrop.hidden = false
69
+
70
+ for (const { element, keys } of findBlurbs()) {
71
+ const box = this.makeBox(element, keys)
72
+ if (box) {
73
+ this.#specimens.append(box)
74
+ }
75
+ }
76
+ }
77
+
78
+ hide() {
79
+ this.reset()
80
+ this.#backdrop.hidden = true
81
+ }
82
+
83
+ reset() {
84
+ this.#specimens.replaceChildren()
85
+ }
86
+
87
+ private makeBox(element: Element, keys: string[]): HTMLDivElement | null {
88
+ const bounds = computeBoundingBox(element)
89
+ if (bounds === null) return null
90
+
91
+ const box = document.createElement('div')
92
+ box.classList.add('specimen')
93
+ box.style.left = `${bounds.left}px`
94
+ box.style.top = `${bounds.top}px`
95
+ box.style.width = `${bounds.width}px`
96
+ box.style.height = `${bounds.height}px`
97
+
98
+ const { position, top, left } = getComputedStyle(element)
99
+ if (position === 'fixed') {
100
+ box.style.position = 'fixed'
101
+ box.style.top = top
102
+ box.style.left = left
103
+ }
104
+
105
+ // box 全体のクリックは先頭キーを開く(広いクリック領域を維持)。複数キー時は各ラベルから個別に開ける
106
+ box.addEventListener('click', () => this.#onOpen(keys[0]))
107
+
108
+ for (const key of keys) {
109
+ box.append(this.makeLabel(key))
110
+ }
111
+ return box
112
+ }
113
+
114
+ private makeLabel(key: string): HTMLDivElement {
115
+ const label = document.createElement('div')
116
+ label.classList.add('specimen-handle')
117
+ label.textContent = key
118
+ // ラベルのクリックはそのキーを開く。box への伝播を止めて先頭キーとの二重発火を防ぐ
119
+ label.addEventListener('click', (event) => {
120
+ event.stopPropagation()
121
+ this.#onOpen(key)
122
+ })
123
+ return label
124
+ }
125
+ }
@@ -0,0 +1,153 @@
1
+ import { BAR_STYLES } from './styles'
2
+ import { debounce } from './util'
3
+
4
+ type OpenCallback = (key: string) => void
5
+
6
+ type InitOptions = {
7
+ url: string
8
+ data: Record<string, string>
9
+ keysSkipped: boolean
10
+ onOpen: OpenCallback
11
+ }
12
+
13
+ // 画面下部のツールバー。CopyTuner / Sync ボタン、ページ内翻訳の検索・ログメニューを Shadow DOM 内に描画する。
14
+ export class CopytunerBar extends HTMLElement {
15
+ #onOpen: OpenCallback = () => {}
16
+ #searchBox!: HTMLInputElement
17
+ #logMenu!: HTMLDivElement
18
+
19
+ constructor() {
20
+ super()
21
+ this.attachShadow({ mode: 'open' })
22
+ }
23
+
24
+ // custom element の constructor 内では属性・プロパティを変更できない(createElement が弾く)ため、
25
+ // hidden の初期化は DOM 挿入後に呼ばれる connectedCallback で行う。
26
+ connectedCallback() {
27
+ this.hidden = true
28
+ }
29
+
30
+ // url/data/keysSkipped/onOpen はオブジェクトや関数を含むため属性ではなくメソッドで受け渡す。
31
+ init({ url, data, keysSkipped, onOpen }: InitOptions) {
32
+ this.#onOpen = onOpen
33
+ const shadow = this.shadowRoot as ShadowRoot
34
+
35
+ const style = document.createElement('style')
36
+ style.textContent = BAR_STYLES
37
+ shadow.append(style)
38
+
39
+ // 元々 Rails から出力されていたマークアップに合わせたボタン群。
40
+ // url は設定値だが innerHTML に直接埋めず setAttribute で渡す(XSS 面で安全側に倒す)。
41
+ const copyTunerButton = this.makeButton('CopyTuner', url, '_blank')
42
+ const syncButton = this.makeButton('Sync', '/copytuner', '_blank')
43
+ const openLogButton = this.makeButton('Translations in this page', 'javascript:void(0)')
44
+
45
+ this.#searchBox = document.createElement('input')
46
+ this.#searchBox.type = 'text'
47
+ this.#searchBox.classList.add('search')
48
+ this.#searchBox.placeholder = 'search'
49
+
50
+ shadow.append(copyTunerButton, syncButton, openLogButton, this.#searchBox)
51
+
52
+ this.#logMenu = this.makeLogMenu(data)
53
+ shadow.append(this.#logMenu)
54
+
55
+ // 巨大DOM/Nokogiri例外でキー付与がスキップされた場合は、オーバーレイが使えないので
56
+ // ツールバー(Translations in this page)から編集する旨を案内する。
57
+ if (keysSkipped) {
58
+ this.appendSkippedNotice()
59
+ }
60
+
61
+ openLogButton.addEventListener('click', (event) => {
62
+ event.preventDefault()
63
+ this.toggleLogMenu()
64
+ })
65
+ this.#searchBox.addEventListener('input', debounce(this.onSearch.bind(this), 250))
66
+ }
67
+
68
+ show() {
69
+ this.hidden = false
70
+ this.#searchBox.focus()
71
+ }
72
+
73
+ hide() {
74
+ this.hidden = true
75
+ }
76
+
77
+ private makeButton(label: string, href: string, target?: string): HTMLAnchorElement {
78
+ const button = document.createElement('a')
79
+ button.classList.add('button')
80
+ button.textContent = label
81
+ button.href = href
82
+ if (target) {
83
+ button.target = target
84
+ }
85
+ return button
86
+ }
87
+
88
+ private appendSkippedNotice() {
89
+ const notice = document.createElement('span')
90
+ notice.classList.add('notice')
91
+ notice.textContent = '⚠ This page is too large for the overlay. Use "Translations in this page" to edit.'
92
+ ;(this.shadowRoot as ShadowRoot).append(notice)
93
+ }
94
+
95
+ private showLogMenu() {
96
+ this.#logMenu.hidden = false
97
+ }
98
+
99
+ private toggleLogMenu() {
100
+ this.#logMenu.hidden = !this.#logMenu.hidden
101
+ }
102
+
103
+ private makeLogMenu(data: Record<string, string>): HTMLDivElement {
104
+ const div = document.createElement('div')
105
+ div.classList.add('log-menu')
106
+ div.hidden = true
107
+
108
+ const table = document.createElement('table')
109
+ const tbody = document.createElement('tbody')
110
+
111
+ for (const key of Object.keys(data).sort()) {
112
+ const value = data[key]
113
+ if (value === '') {
114
+ continue
115
+ }
116
+
117
+ const td1 = document.createElement('td')
118
+ td1.textContent = key
119
+ const td2 = document.createElement('td')
120
+ td2.textContent = value
121
+ const tr = document.createElement('tr')
122
+ tr.dataset.key = key
123
+
124
+ tr.addEventListener('click', ({ currentTarget }) => {
125
+ const row = currentTarget as HTMLTableRowElement
126
+ if (row.dataset.key) {
127
+ this.#onOpen(row.dataset.key)
128
+ }
129
+ })
130
+
131
+ tr.append(td1, td2)
132
+ tbody.append(tr)
133
+ }
134
+
135
+ table.append(tbody)
136
+ div.append(table)
137
+
138
+ return div
139
+ }
140
+
141
+ private onSearch() {
142
+ // debounce 経由で遅延実行されると Event.target は null 化されるため、検索ボックスを直接参照する
143
+ const keyword = this.#searchBox.value.trim()
144
+ this.showLogMenu()
145
+
146
+ const rows = [...this.#logMenu.querySelectorAll('tr')]
147
+ for (const row of rows) {
148
+ const isShow =
149
+ keyword === '' || [...row.querySelectorAll('td')].some((td) => (td.textContent ?? '').includes(keyword))
150
+ row.hidden = !isShow
151
+ }
152
+ }
153
+ }
data/src/main.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  /* eslint-disable no-console */
2
- import Copyray from './copyray'
2
+ import { CopyrayOverlay } from './copyray-overlay'
3
+ import { CopytunerBar } from './copytuner-bar'
3
4
  import { isMac } from './util'
4
5
 
5
6
  declare global {
@@ -7,8 +8,7 @@ declare global {
7
8
  CopyTuner: {
8
9
  url: string
9
10
  toggle?: () => void
10
- // TODO: type
11
- data: object
11
+ data: Record<string, string>
12
12
  // 巨大DOM/Nokogiri例外で data-copyray-key 付与をスキップしたか。
13
13
  // true のときオーバーレイは使えないのでツールバーから編集する旨を案内する。
14
14
  keysSkipped?: boolean
@@ -16,38 +16,42 @@ declare global {
16
16
  }
17
17
  }
18
18
 
19
- import './copyray.css'
20
-
21
- // NOTE: 元々railsから出力されいてたマークアップに合わせてひとまず、、
22
- const appendCopyTunerBar = (url: string) => {
23
- const bar = document.createElement('div')
24
- bar.id = 'copy-tuner-bar'
25
- bar.classList.add('copy-tuner-hidden')
26
- bar.innerHTML = `
27
- <a class="copy-tuner-bar-button" target="_blank" href="${url}">CopyTuner</a>
28
- <a href="/copytuner" target="_blank" class="copy-tuner-bar-button">Sync</a>
29
- <a href="javascript:void(0)" class="copy-tuner-bar-open-log copy-tuner-bar-button js-copy-tuner-bar-open-log">Translations in this page</a>
30
- <input type="text" class="copy-tuner-bar__search js-copy-tuner-bar-search" placeholder="search">
31
- `
32
- document.body.append(bar)
33
- }
19
+ customElements.define('copytuner-bar', CopytunerBar)
20
+ customElements.define('copyray-overlay', CopyrayOverlay)
34
21
 
35
22
  const start = () => {
36
23
  const { url, data, keysSkipped } = window.CopyTuner
24
+ const onOpen = (key: string) => window.open(`${url}/blurbs/${key}/edit`)
25
+
26
+ const bar = document.createElement('copytuner-bar') as CopytunerBar
27
+ document.body.append(bar)
28
+ bar.init({ url, data, keysSkipped: Boolean(keysSkipped), onOpen })
29
+
30
+ const overlay = document.createElement('copyray-overlay') as CopyrayOverlay
31
+ overlay.onOpen = onOpen
32
+ document.body.append(overlay)
33
+
34
+ const show = () => {
35
+ overlay.show()
36
+ bar.show()
37
+ }
38
+ const hide = () => {
39
+ overlay.hide()
40
+ bar.hide()
41
+ }
42
+ const toggle = () => (overlay.isShowing ? hide() : show())
37
43
 
38
- appendCopyTunerBar(url)
39
- const copyray = new Copyray(url, data, Boolean(keysSkipped))
40
- window.CopyTuner.toggle = () => copyray.toggle()
44
+ overlay.onToggle = toggle
45
+ window.CopyTuner.toggle = toggle
41
46
 
42
47
  document.addEventListener('keydown', (event) => {
43
- // @ts-expect-error TS2339
44
- if (copyray.isShowing && ['Escape', 'Esc'].includes(event.key)) {
45
- copyray.hide()
48
+ if (overlay.isShowing && ['Escape', 'Esc'].includes(event.key)) {
49
+ hide()
46
50
  return
47
51
  }
48
52
 
49
53
  if (((isMac && event.metaKey) || (!isMac && event.ctrlKey)) && event.shiftKey && event.key.toLowerCase() === 'k') {
50
- copyray.toggle()
54
+ toggle()
51
55
  }
52
56
  })
53
57
 
@@ -1,118 +1,9 @@
1
- @charset "UTF-8";
2
-
3
- /* selector for element and children */
4
- #copyray-overlay,
5
- #copyray-overlay *,
6
- #copyray-overlay a:hover,
7
- #copyray-overlay a:visited,
8
- #copyray-overlay a:active {
9
- background: none;
10
- border: none;
11
- bottom: auto;
12
- clear: none;
13
- cursor: default;
14
- float: none;
15
- font-family: Arial, Helvetica, sans-serif;
16
- font-size: medium;
17
- font-style: normal;
18
- font-weight: normal;
19
- height: auto;
20
- left: auto;
21
- letter-spacing: normal;
22
- line-height: normal;
23
- max-height: none;
24
- max-width: none;
25
- min-height: 0;
26
- min-width: 0;
27
- overflow: visible;
28
- position: static;
29
- right: auto;
30
- text-align: left;
31
- text-decoration: none;
32
- text-indent: 0;
33
- text-transform: none;
34
- top: auto;
35
- visibility: visible;
36
- white-space: normal;
37
- width: auto;
38
- z-index: auto;
39
- }
40
-
41
- #copyray-overlay {
42
- position: fixed;
43
- left: 0;
44
- top: 0;
45
- bottom: 0;
46
- right: 0;
47
- background-image: radial-gradient(
48
- ellipse farthest-corner at center,
49
- rgba(0, 0, 0, 0.4) 10%,
50
- rgba(0, 0, 0, 0.8) 100%
51
- );
52
- z-index: 9000;
53
- }
54
-
55
- .copyray-specimen {
56
- position: absolute;
57
- background: rgba(255, 255, 255, 0.15);
58
- outline: 1px solid rgba(255, 255, 255, 0.8);
59
- outline-offset: -1px;
60
- color: #666;
61
- font-family: 'Helvetica Neue', sans-serif;
62
- font-size: 13px;
63
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);
64
- }
65
-
66
- .copyray-specimen:hover {
67
- cursor: pointer;
68
- background: rgba(255, 255, 255, 0.4);
69
- }
70
-
71
- .copyray-specimen.Specimen {
72
- outline: 1px solid rgba(255, 50, 50, 0.8);
73
- background: rgba(255, 50, 50, 0.1);
74
- }
1
+ // 各 custom element の Shadow root に <style> として注入する CSS。
2
+ // Shadow DOM のスタイル隔離が効くため、旧 copyray.css にあった #copyray-overlay * のグローバルリセットは不要。
75
3
 
76
- .copyray-specimen.Specimen:hover {
77
- background: rgba(255, 50, 50, 0.4);
78
- }
79
-
80
- .copyray-specimen-handle {
81
- float: left;
82
- margin: 0 2px 2px 0;
83
- background: #fff;
84
- padding: 0 3px;
85
- color: #333;
86
- font-size: 10px;
87
- cursor: pointer;
88
- }
89
-
90
- .copyray-specimen-handle.Specimen {
91
- background: rgba(255, 50, 50, 0.8);
92
- color: #fff;
93
- }
94
-
95
- a.copyray-toggle-button {
96
- display: block;
97
- position: fixed;
98
- left: 0;
99
- bottom: 0;
100
- color: white;
101
- background: black;
102
- padding: 12px 16px;
103
- border-radius: 0 10px 0 0;
104
- opacity: 0;
105
- transition: opacity 0.6s ease-in-out;
106
- z-index: 10000;
107
- font-size: 12px;
108
- cursor: pointer;
109
- }
110
-
111
- a.copyray-toggle-button:hover {
112
- opacity: 1;
113
- }
114
-
115
- #copy-tuner-bar {
4
+ // ツールバー(<copytuner-bar>)のスタイル。:host にバー本体のレイアウトを定義する。
5
+ export const BAR_STYLES = `
6
+ :host {
116
7
  position: fixed;
117
8
  left: 0;
118
9
  right: 0;
@@ -126,44 +17,45 @@ a.copyray-toggle-button:hover {
126
17
  z-index: 2147483647;
127
18
  box-shadow: 0 -1px 0 rgba(255, 255, 255, 0.1), inset 0 2px 6px rgba(0, 0, 0, 0.8);
128
19
  background-image: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.3));
20
+ box-sizing: border-box;
21
+ }
22
+
23
+ :host([hidden]) {
24
+ display: none;
129
25
  }
130
26
 
131
- #copy-tuner-bar-log-menu {
27
+ .log-menu {
132
28
  position: fixed;
133
29
  left: 0;
134
30
  right: 0;
135
31
  bottom: 40px;
136
32
  max-height: calc(100vh - 40px);
137
33
  background: #222;
138
- font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
139
34
  color: #fff;
140
- z-index: 2147483647;
141
35
  overflow-y: auto;
142
36
  }
143
37
 
144
- #copy-tuner-bar-log-menu tbody td {
38
+ .log-menu[hidden] {
39
+ display: none;
40
+ }
41
+
42
+ .log-menu tbody td {
145
43
  padding: 2px 8px;
146
44
  }
147
45
 
148
- #copy-tuner-bar-log-menu tbody tr {
46
+ .log-menu tbody tr {
149
47
  cursor: pointer;
150
48
  }
151
49
 
152
- #copy-tuner-bar-log-menu tbody tr:hover {
50
+ .log-menu tbody tr:hover {
153
51
  background: #444;
154
52
  }
155
53
 
156
- #copy-tuner-bar-log-menu tbody a {
157
- color: #fff;
158
- }
159
-
160
- #copy-tuner-bar-log-menu tbody a:hover,
161
- #copy-tuner-bar-log-menu tbody a:focus {
162
- color: #fff;
163
- text-decoration: underline;
54
+ .log-menu tbody tr[hidden] {
55
+ display: none;
164
56
  }
165
57
 
166
- .copy-tuner-bar-button {
58
+ .button {
167
59
  position: relative;
168
60
  display: inline-block;
169
61
  color: #fff;
@@ -180,16 +72,17 @@ a.copyray-toggle-button:hover {
180
72
  box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.2),
181
73
  inset 0 0 2px rgba(255, 255, 255, 0.2);
182
74
  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.4);
75
+ text-decoration: none;
183
76
  }
184
77
 
185
- .copy-tuner-bar-button:hover,
186
- .copy-tuner-bar-button:focus {
78
+ .button:hover,
79
+ .button:focus {
187
80
  color: #fff;
188
81
  text-decoration: none;
189
82
  background-color: #555;
190
83
  }
191
84
 
192
- .copy-tuner-bar__notice {
85
+ .notice {
193
86
  display: inline-block;
194
87
  margin: 8px;
195
88
  font-size: 13px;
@@ -198,9 +91,7 @@ a.copyray-toggle-button:hover {
198
91
  color: #ffd24d;
199
92
  }
200
93
 
201
- input[type='text'].copy-tuner-bar__search {
202
- -webkit-appearance: none;
203
- -moz-appearance: none;
94
+ .search {
204
95
  appearance: none;
205
96
  border: none;
206
97
  border-radius: 2px;
@@ -215,13 +106,87 @@ input[type='text'].copy-tuner-bar__search {
215
106
  height: auto;
216
107
  font-size: 14px;
217
108
  }
109
+ `
110
+
111
+ // オーバーレイ(<copyray-overlay>)のスタイル。
112
+ // :host はドキュメント原点基準(position: absolute; top/left: 0)にして、
113
+ // 子の specimen を computeBoundingBox のページ座標で absolute 配置できるようにする。
114
+ // 背景の暗転(.backdrop)だけは viewport 固定(fixed)にする。
115
+ export const OVERLAY_STYLES = `
116
+ :host {
117
+ position: absolute;
118
+ top: 0;
119
+ left: 0;
120
+ width: 0;
121
+ height: 0;
122
+ }
123
+
124
+ :host([hidden]) {
125
+ display: none;
126
+ }
127
+
128
+ .backdrop {
129
+ position: fixed;
130
+ inset: 0;
131
+ background-image: radial-gradient(
132
+ ellipse farthest-corner at center,
133
+ rgba(0, 0, 0, 0.4) 10%,
134
+ rgba(0, 0, 0, 0.8) 100%
135
+ );
136
+ z-index: 9000;
137
+ }
138
+
139
+ .specimen {
140
+ position: absolute;
141
+ background: rgba(255, 50, 50, 0.1);
142
+ outline: 1px solid rgba(255, 50, 50, 0.8);
143
+ outline-offset: -1px;
144
+ color: #666;
145
+ font-family: 'Helvetica Neue', sans-serif;
146
+ font-size: 13px;
147
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);
148
+ z-index: 2000000000;
149
+ }
218
150
 
219
- .copy-tuner-hidden {
220
- display: none !important;
151
+ .specimen:hover {
152
+ cursor: pointer;
153
+ background: rgba(255, 50, 50, 0.4);
154
+ }
155
+
156
+ .specimen-handle {
157
+ float: left;
158
+ margin: 0 2px 2px 0;
159
+ background: rgba(255, 50, 50, 0.8);
160
+ padding: 0 3px;
161
+ color: #fff;
162
+ font-size: 10px;
163
+ cursor: pointer;
164
+ }
165
+
166
+ .toggle-button {
167
+ display: block;
168
+ position: fixed;
169
+ left: 0;
170
+ bottom: 0;
171
+ color: white;
172
+ background: black;
173
+ padding: 12px 16px;
174
+ border-radius: 0 10px 0 0;
175
+ opacity: 0;
176
+ transition: opacity 0.6s ease-in-out;
177
+ z-index: 10000;
178
+ font-size: 12px;
179
+ cursor: pointer;
180
+ text-decoration: none;
181
+ }
182
+
183
+ .toggle-button:hover {
184
+ opacity: 1;
221
185
  }
222
186
 
223
187
  @media screen and (max-width: 480px) {
224
- .hidden-on-mobile {
225
- display: none !important;
188
+ .toggle-button {
189
+ display: none;
226
190
  }
227
191
  }
192
+ `
data/src/util.ts CHANGED
@@ -35,4 +35,12 @@ const computeBoundingBox = (element) => {
35
35
  }
36
36
  }
37
37
 
38
- export { isMac, isVisible, getOffset, computeBoundingBox }
38
+ const debounce = <A extends unknown[]>(fn: (...args: A) => void, wait: number) => {
39
+ let timer: ReturnType<typeof setTimeout> | undefined
40
+ return (...args: A) => {
41
+ clearTimeout(timer)
42
+ timer = setTimeout(() => fn(...args), wait)
43
+ }
44
+ }
45
+
46
+ export { computeBoundingBox, debounce, getOffset, isMac, isVisible }
data/tsconfig.json CHANGED
@@ -3,11 +3,9 @@
3
3
  "target": "ESNext",
4
4
  "useDefineForClassFields": true,
5
5
  "module": "ESNext",
6
- "lib": [
7
- "ESNext",
8
- "DOM"
9
- ],
10
- "moduleResolution": "Node",
6
+ "lib": ["ESNext", "DOM"],
7
+ "moduleResolution": "bundler",
8
+ "verbatimModuleSyntax": true,
11
9
  "strict": true,
12
10
  "sourceMap": true,
13
11
  "resolveJsonModule": true,
@@ -17,10 +15,7 @@
17
15
  "noUnusedLocals": true,
18
16
  "noUnusedParameters": true,
19
17
  "noImplicitReturns": true,
20
- "skipLibCheck": true,
18
+ "skipLibCheck": true
21
19
  },
22
- "include": [
23
- "client",
24
- "src",
25
- ]
20
+ "include": ["src"]
26
21
  }