@hanology/cham-browser 0.2.2 → 0.3.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,174 @@
1
+ import { ref, computed, watch } from 'vue'
2
+
3
+ export type Locale = 'zh-Hant' | 'zh-Hans' | 'en'
4
+
5
+ export const LOCALE_LABELS: Record<Locale, string> = {
6
+ 'zh-Hant': '繁',
7
+ 'zh-Hans': '简',
8
+ 'en': 'EN',
9
+ }
10
+
11
+ const messages: Record<Locale, Record<string, string>> = {
12
+ 'zh-Hant': {
13
+ 'site.title': '古典詩文圖書館',
14
+ 'site.subtitle': 'Classical Chinese Text Library',
15
+ 'nav.back': '返回',
16
+ 'nav.home': '首頁',
17
+ 'settings.layout': '版面',
18
+ 'settings.vertical': '直排',
19
+ 'settings.horizontal': '橫排',
20
+ 'settings.theme': '主題',
21
+ 'settings.language': '語言',
22
+ 'settings.mainFontSize': '正文字號',
23
+ 'settings.bodyFontSize': '內文字號',
24
+ 'settings.reading': '閱讀設定',
25
+ 'settings.close': '關閉設定',
26
+ 'piece.stanzas': '段',
27
+ 'piece.notes': '注',
28
+ 'piece.noNotes': '無注',
29
+ 'piece.previous': '上一篇',
30
+ 'piece.next': '下一篇',
31
+ 'piece.enterLibrary': '進 入 文 庫',
32
+ 'piece.loading': '載入中…',
33
+ 'catalog.title': '篇 章 目 錄',
34
+ 'catalog.total': '共 {count} 篇',
35
+ 'catalog.search': '搜索詩題、作者…',
36
+ 'author.biography': '作者簡介',
37
+ 'author.collectedWorks': '收錄作品',
38
+ 'author.worksCount': '{count} 篇收錄作品',
39
+ 'author.unknownDynasty': '未知朝代',
40
+ 'annotation.pronunciation': '音',
41
+ 'annotation.semantic': '義',
42
+ 'annotation.notes': '注釋',
43
+ 'genre.poetry': '詩歌',
44
+ 'genre.prose': '散文',
45
+ 'genre.mixed': '綜合',
46
+ 'genre.drama': '戲曲',
47
+ 'genre.classicalText': '古典文本',
48
+ 'genre.fourTreasuries': '四庫全書',
49
+ 'genre.textbooks': '教材',
50
+ 'stat.books': '部',
51
+ 'stat.pieces': '篇',
52
+ 'stat.pieceCount': '{count} 篇',
53
+ 'stat.authorCount': '{count} 位作者',
54
+ 'stat.piecePoems': '篇詩文',
55
+ },
56
+ 'zh-Hans': {
57
+ 'site.title': '古典诗文图书馆',
58
+ 'site.subtitle': 'Classical Chinese Text Library',
59
+ 'nav.back': '返回',
60
+ 'nav.home': '首页',
61
+ 'settings.layout': '版面',
62
+ 'settings.vertical': '直排',
63
+ 'settings.horizontal': '横排',
64
+ 'settings.theme': '主题',
65
+ 'settings.language': '语言',
66
+ 'settings.mainFontSize': '正文字号',
67
+ 'settings.bodyFontSize': '内文字号',
68
+ 'settings.reading': '阅读设定',
69
+ 'settings.close': '关闭设定',
70
+ 'piece.stanzas': '段',
71
+ 'piece.notes': '注',
72
+ 'piece.noNotes': '无注',
73
+ 'piece.previous': '上一篇',
74
+ 'piece.next': '下一篇',
75
+ 'piece.enterLibrary': '进 入 文 库',
76
+ 'piece.loading': '载入中…',
77
+ 'catalog.title': '篇 章 目 录',
78
+ 'catalog.total': '共 {count} 篇',
79
+ 'catalog.search': '搜索诗题、作者…',
80
+ 'author.biography': '作者简介',
81
+ 'author.collectedWorks': '收录作品',
82
+ 'author.worksCount': '{count} 篇收录作品',
83
+ 'author.unknownDynasty': '未知朝代',
84
+ 'annotation.pronunciation': '音',
85
+ 'annotation.semantic': '义',
86
+ 'annotation.notes': '注释',
87
+ 'genre.poetry': '诗歌',
88
+ 'genre.prose': '散文',
89
+ 'genre.mixed': '综合',
90
+ 'genre.drama': '戏曲',
91
+ 'genre.classicalText': '古典文本',
92
+ 'genre.fourTreasuries': '四库全书',
93
+ 'genre.textbooks': '教材',
94
+ 'stat.books': '部',
95
+ 'stat.pieces': '篇',
96
+ 'stat.pieceCount': '{count} 篇',
97
+ 'stat.authorCount': '{count} 位作者',
98
+ 'stat.piecePoems': '篇诗文',
99
+ },
100
+ 'en': {
101
+ 'site.title': 'Classical Chinese Text Library',
102
+ 'site.subtitle': 'Classical Chinese Text Library',
103
+ 'nav.back': 'Back',
104
+ 'nav.home': 'Home',
105
+ 'settings.layout': 'Layout',
106
+ 'settings.vertical': 'Vertical',
107
+ 'settings.horizontal': 'Horizontal',
108
+ 'settings.theme': 'Theme',
109
+ 'settings.language': 'Language',
110
+ 'settings.mainFontSize': 'Main Size',
111
+ 'settings.bodyFontSize': 'Body Size',
112
+ 'settings.reading': 'Reading Settings',
113
+ 'settings.close': 'Close Settings',
114
+ 'piece.stanzas': 'stanzas',
115
+ 'piece.notes': 'notes',
116
+ 'piece.noNotes': 'No notes',
117
+ 'piece.previous': 'Previous',
118
+ 'piece.next': 'Next',
119
+ 'piece.enterLibrary': 'Enter Library',
120
+ 'piece.loading': 'Loading…',
121
+ 'catalog.title': 'Catalogue',
122
+ 'catalog.total': '{count} works',
123
+ 'catalog.search': 'Search titles, authors…',
124
+ 'author.biography': 'Biography',
125
+ 'author.collectedWorks': 'Collected Works',
126
+ 'author.worksCount': '{count} collected works',
127
+ 'author.unknownDynasty': 'Unknown dynasty',
128
+ 'annotation.pronunciation': 'Pron',
129
+ 'annotation.semantic': 'Def',
130
+ 'annotation.notes': 'Notes',
131
+ 'genre.poetry': 'Poetry',
132
+ 'genre.prose': 'Prose',
133
+ 'genre.mixed': 'Mixed',
134
+ 'genre.drama': 'Drama',
135
+ 'genre.classicalText': 'Classical Texts',
136
+ 'genre.fourTreasuries': 'Four Treasuries',
137
+ 'genre.textbooks': 'Textbooks',
138
+ 'stat.books': 'books',
139
+ 'stat.pieces': 'works',
140
+ 'stat.pieceCount': '{count} works',
141
+ 'stat.authorCount': '{count} authors',
142
+ 'stat.piecePoems': 'works',
143
+ },
144
+ }
145
+
146
+ const AVAILABLE_LOCALES: Locale[] = ['zh-Hant', 'zh-Hans', 'en']
147
+
148
+ const locale = ref<Locale>('zh-Hant')
149
+
150
+ if (!import.meta.env.SSR) {
151
+ const saved = localStorage.getItem('cham-locale') as Locale | null
152
+ if (saved && AVAILABLE_LOCALES.includes(saved)) locale.value = saved
153
+
154
+ watch(locale, l => {
155
+ localStorage.setItem('cham-locale', l)
156
+ document.documentElement.setAttribute('lang', l === 'zh-Hans' ? 'zh-Hans' : l === 'en' ? 'en' : 'zh-Hant')
157
+ }, { immediate: true })
158
+ }
159
+
160
+ export function useI18n() {
161
+ function t(key: string, params?: Record<string, string | number>): string {
162
+ let msg = messages[locale.value]?.[key] || messages['zh-Hant']?.[key] || key
163
+ if (params) {
164
+ for (const [k, v] of Object.entries(params)) {
165
+ msg = msg.replace(`{${k}}`, String(v))
166
+ }
167
+ }
168
+ return msg
169
+ }
170
+
171
+ function setLocale(l: Locale) { locale.value = l }
172
+
173
+ return { locale, t, setLocale, availableLocales: AVAILABLE_LOCALES, localeLabels: LOCALE_LABELS }
174
+ }
@@ -0,0 +1,173 @@
1
+ <script setup lang="ts">
2
+ import { useI18n } from '../composables/useI18n'
3
+ import { useTitle } from '../composables/useTitle'
4
+ import { useReadingMode } from '../composables/useReadingMode'
5
+ import { useHorizontalScroll } from '../composables/useHorizontalScroll'
6
+ import SideNav from '../components/SideNav.vue'
7
+ import ReadingToolbar from '../components/ReadingToolbar.vue'
8
+ import { ref, computed } from 'vue'
9
+ import { useRouter } from 'vue-router'
10
+
11
+ const { t, locale } = useI18n()
12
+ const { layout } = useReadingMode()
13
+ const isVertical = computed(() => layout.value === 'vertical')
14
+ const vPageRef = ref<HTMLElement | null>(null)
15
+ const vScroll = useHorizontalScroll(vPageRef)
16
+ const router = useRouter()
17
+
18
+ useTitle(locale.value === 'en' ? 'About — Hanology' : '關於 — 漢流')
19
+
20
+ function goBack() { router.push('/') }
21
+ function goHome() { router.push('/') }
22
+ </script>
23
+
24
+ <template>
25
+ <div v-if="isVertical" class="v-root">
26
+ <SideNav @back="goBack" @home="goHome" />
27
+ <div ref="vPageRef" class="v-page">
28
+ <section class="v-about">
29
+ <h1 class="v-about-title">關 於 漢 流</h1>
30
+ <div class="v-divider"></div>
31
+ <div class="v-about-body">
32
+ <p><strong>漢流</strong>,粵音 Han-Lou,普音 Han-Liu,意為「漢學之流」。</p>
33
+ <p>經典如水,源遠流長,世代浸潤其中,前人開源,後人受益。每一代人都能在經典的長河中,找到屬於自己的領悟。</p>
34
+ <p><strong>Hanology</strong>,English portmanteau of <em>Han</em> + <em>anthology</em> + <em>-logy</em> (the study of). The sound "lou/liu" echoes the first syllable of "-logy" — a perfect phonetic and semantic fit.</p>
35
+ <p>Hanology is a digital library for classical Chinese texts, designed to make the wisdom of the ages accessible in the modern world.</p>
36
+ </div>
37
+ </section>
38
+ </div>
39
+ </div>
40
+
41
+ <div v-else class="h-root">
42
+ <header class="h-header">
43
+ <button class="h-back" @click="goBack">← {{ t('nav.back') }}</button>
44
+ <h1 class="h-page-title">關於漢流 / About Hanology</h1>
45
+ </header>
46
+ <div class="h-content">
47
+ <div class="h-seal">漢流</div>
48
+ <div class="h-about-block">
49
+ <h2>漢流 · Hanology</h2>
50
+ <p><strong>漢流</strong>,粵音 Han-Lou,普音 Han-Liu,意為「漢學之流」。</p>
51
+ <p>經典如水,源遠流長,世代浸潤其中,前人開源,後人受益。每一代人都能在經典的長河中,找到屬於自己的領悟。</p>
52
+ </div>
53
+ <div class="h-about-block">
54
+ <h2>Hanology</h2>
55
+ <p>English portmanteau of <em>Han</em> + <em>anthology</em> + <em>-logy</em> (the study of).</p>
56
+ <p>The sound "lou/liu" echoes the first syllable of "-logy" — a perfect phonetic and semantic fit. Just as the classics flow through generations, Hanology aims to carry that stream into the digital age.</p>
57
+ </div>
58
+ <div class="h-about-block">
59
+ <h2>Our Mission</h2>
60
+ <p>Hanology is a digital library for classical Chinese texts. We believe that the wisdom of antiquity should not be locked behind impenetrable editions or forgotten in dusty shelves. By combining rigorous scholarship with thoughtful design, we make the classics accessible, beautiful, and alive for every reader.</p>
61
+ </div>
62
+ </div>
63
+ <ReadingToolbar />
64
+ </div>
65
+ </template>
66
+
67
+ <style scoped>
68
+ /* ═══════ 直排模式 ═══════ */
69
+ .v-page {
70
+ height: 100vh;
71
+ display: flex;
72
+ flex-direction: row-reverse;
73
+ overflow-x: auto;
74
+ overflow-y: hidden;
75
+ margin-right: var(--nav-width, 56px);
76
+ padding: 0 32px;
77
+ background: var(--paper);
78
+ scrollbar-width: thin;
79
+ scrollbar-color: var(--gold) transparent;
80
+ }
81
+ .v-page::-webkit-scrollbar { height: 4px; }
82
+ .v-page::-webkit-scrollbar-thumb { background: var(--gold); border-radius: 2px; }
83
+
84
+ .v-about {
85
+ writing-mode: vertical-rl;
86
+ text-orientation: mixed;
87
+ flex-shrink: 0;
88
+ height: 100vh;
89
+ display: flex;
90
+ flex-direction: column;
91
+ align-items: flex-start;
92
+ justify-content: center;
93
+ padding: 40px 24px;
94
+ }
95
+ .v-about-title {
96
+ font-size: 48px; font-weight: 900;
97
+ letter-spacing: 16px; color: var(--ink);
98
+ margin-left: 20px; padding-left: 20px;
99
+ border-left: 4px solid var(--vermillion);
100
+ line-height: 1.6;
101
+ }
102
+ .v-divider {
103
+ width: 2px; height: 80px;
104
+ background: linear-gradient(180deg, transparent, var(--gold), transparent);
105
+ margin-left: 20px;
106
+ }
107
+ .v-about-body {
108
+ font-size: 16px; line-height: 2.4;
109
+ color: var(--ink-mid);
110
+ max-height: 80vh;
111
+ overflow-x: auto;
112
+ }
113
+ .v-about-body p {
114
+ margin-left: 16px;
115
+ text-indent: 0;
116
+ }
117
+
118
+ /* ═══════ 橫排模式 ═══════ */
119
+ .h-root { max-width: 960px; margin: 0 auto; padding: 40px 24px 120px; }
120
+ .h-header {
121
+ display: flex; align-items: center; gap: 16px;
122
+ margin-bottom: 40px; padding-bottom: 16px;
123
+ border-bottom: 1px solid var(--border);
124
+ }
125
+ .h-back {
126
+ padding: 6px 16px; border: 1px solid var(--border);
127
+ border-radius: 2px; background: none;
128
+ font-family: var(--sans); font-size: 13px;
129
+ color: var(--ink-mid); cursor: pointer;
130
+ transition: all 0.2s;
131
+ }
132
+ .h-back:hover { background: var(--ink); color: var(--paper); border-color: var(--ink); }
133
+ .h-page-title { font-size: 20px; font-weight: 700; letter-spacing: 2px; }
134
+
135
+ .h-content { max-width: 680px; margin: 0 auto; }
136
+ .h-seal {
137
+ writing-mode: vertical-rl;
138
+ text-orientation: upright;
139
+ display: inline-flex;
140
+ align-items: center; justify-content: center;
141
+ width: 56px; height: 72px;
142
+ border: 2px solid var(--vermillion);
143
+ color: var(--vermillion);
144
+ font-size: 24px; font-family: var(--serif);
145
+ font-weight: 900; letter-spacing: 2px;
146
+ margin: 0 auto 40px; border-radius: 4px;
147
+ line-height: 1;
148
+ }
149
+ .h-about-block {
150
+ margin-bottom: 40px; padding: 32px;
151
+ background: var(--surface);
152
+ border: 1px solid var(--border-light);
153
+ border-radius: 8px;
154
+ }
155
+ .h-about-block:last-child { margin-bottom: 0; }
156
+ .h-about-block h2 {
157
+ font-size: 20px; font-weight: 700;
158
+ letter-spacing: 3px; color: var(--ink);
159
+ margin-bottom: 16px; padding-bottom: 12px;
160
+ border-bottom: 1px solid var(--border);
161
+ }
162
+ .h-about-block p {
163
+ font-size: 16px; line-height: 2.2;
164
+ color: var(--ink-mid); text-align: justify;
165
+ text-indent: 2em; margin-bottom: 12px;
166
+ }
167
+ .h-about-block p:last-child { margin-bottom: 0; }
168
+
169
+ @media (max-width: 768px) {
170
+ .h-root { padding: 24px 16px 80px; }
171
+ .h-about-block { padding: 20px; }
172
+ }
173
+ </style>
@@ -5,13 +5,13 @@ import { useBook } from '../composables/useBook'
5
5
  import { useTitle } from '../composables/useTitle'
6
6
  import { useReadingMode } from '../composables/useReadingMode'
7
7
  import { useHorizontalScroll } from '../composables/useHorizontalScroll'
8
- import { useAnnotationTooltip } from '../composables/useAnnotationRenderer'
8
+ import { useAnnotationInteraction } from '../composables/useAnnotationInteraction'
9
9
  import { useData } from '../composables/useData'
10
10
  import VerticalScroll from '../components/VerticalScroll.vue'
11
11
  import HorizontalDisplay from '../components/HorizontalDisplay.vue'
12
12
  import SectionBlock from '../components/SectionBlock.vue'
13
13
  import AnnotationTooltip from '../components/AnnotationTooltip.vue'
14
- import AnnotationLayerSelector from '../components/AnnotationLayerSelector.vue'
14
+ import AnnotationControlBar from '../components/AnnotationControlBar.vue'
15
15
  import SideNav from '../components/SideNav.vue'
16
16
  import type { Piece, Annotation, AnnotationLayer } from '../types'
17
17
 
@@ -26,7 +26,7 @@ const vScroll = useHorizontalScroll(vPageRef)
26
26
 
27
27
  const authorPaneOpen = ref(false)
28
28
  const selectedAuthorId = ref('')
29
- const tooltip = reactive(useAnnotationTooltip())
29
+ const interaction = useAnnotationInteraction()
30
30
  const titleCollapsed = ref(false)
31
31
  const vTitleRef = ref<HTMLElement | null>(null)
32
32
 
@@ -61,6 +61,7 @@ const isVertical = computed(() => layout.value === 'vertical')
61
61
  const annotationLayers = computed<AnnotationLayer[]>(() => piece.value?.annotationLayers || [])
62
62
  const hasLayers = computed(() => annotationLayers.value.length > 1)
63
63
  const activeLayerIds = ref<string[]>([])
64
+ const annotationsVisible = ref(true)
64
65
 
65
66
  function initLayers() {
66
67
  if (hasLayers.value && activeLayerIds.value.length === 0) {
@@ -94,7 +95,7 @@ const layerLabels = computed(() => {
94
95
  })
95
96
 
96
97
  const layerAnnotationBlocks = computed(() => {
97
- if (!hasLayers.value) return []
98
+ if (!hasLayers.value || !annotationsVisible.value) return []
98
99
  const result: { label: string; text: string }[] = []
99
100
  const activeLayers = annotationLayers.value.filter(l => activeLayerIds.value.includes(l.id) && l.id !== 'default')
100
101
  for (const layer of activeLayers) {
@@ -152,16 +153,7 @@ const proseSections = computed(() => {
152
153
  return result
153
154
  })
154
155
 
155
- function handleAnnotationHover(event: MouseEvent, annotations: Annotation[]) {
156
- tooltip.show(event, annotations)
157
- }
158
- function handleAnnotationLeave() {
159
- if (window.innerWidth >= 768) tooltip.hide()
160
- }
161
- function handleAnnotationTap(event: MouseEvent, annotations: Annotation[]) {
162
- tooltip.toggle(event, annotations)
163
- }
164
- function dismissTooltip() { tooltip.hide() }
156
+
165
157
  const { getAuthor, loadShared } = useData()
166
158
  await loadShared()
167
159
 
@@ -252,41 +244,54 @@ function tcy(n: number): string {
252
244
  :verses="piece.verses"
253
245
  :author-initial="piece.author?.charAt(0) || '詩'"
254
246
  :annotations="mergedAnnotations"
255
- @annotation-hover="handleAnnotationHover"
256
- @annotation-leave="handleAnnotationLeave"
257
- @annotation-tap="handleAnnotationTap"
247
+ @annotation-hover="interaction.onHover"
248
+ @annotation-leave="interaction.onLeave"
249
+ @annotation-tap="interaction.onTap"
258
250
  @open-author="openAuthorPane"
259
251
  />
260
252
  </section>
261
253
 
262
- <div class="v-section">
254
+ <SectionBlock
255
+ v-if="annotationsVisible && piece.sections.annotations"
256
+ num=""
257
+ label="注釋"
258
+ :special="false"
259
+ :text="piece.sections.annotations"
260
+ :is-annotations="true"
261
+ :vertical="true"
262
+ class="v-section"
263
+ />
264
+ <template v-if="hasLayers">
265
+ <div class="v-layers-inline v-section">
266
+ <AnnotationControlBar
267
+ :layers="annotationLayers"
268
+ :has-annotations="piece.annotations.length > 0"
269
+ v-model:active-ids="activeLayerIds"
270
+ v-model:annotations-visible="annotationsVisible"
271
+ />
272
+ </div>
263
273
  <SectionBlock
274
+ v-for="block in visibleLayerBlocks"
275
+ :key="block.label"
264
276
  num=""
265
- label="注釋"
277
+ :label="block.label"
266
278
  :special="false"
267
- :text="piece.sections.annotations || ''"
279
+ :text="block.text"
268
280
  :is-annotations="true"
269
281
  :vertical="true"
282
+ class="v-section"
270
283
  />
271
- <template v-if="hasLayers">
272
- <div class="v-layers-inline">
273
- <AnnotationLayerSelector
274
- :layers="annotationLayers"
275
- v-model:activeIds="activeLayerIds"
276
- />
277
- </div>
278
- <SectionBlock
279
- v-for="block in layerAnnotationBlocks"
280
- :key="block.label"
281
- num=""
282
- :label="block.label"
283
- :special="false"
284
- :text="block.text"
285
- :is-annotations="true"
286
- :vertical="true"
287
- />
288
- </template>
289
- </div>
284
+ </template>
285
+ <SectionBlock
286
+ v-else-if="piece.annotations.length > 0"
287
+ num=""
288
+ label="注釋"
289
+ :special="false"
290
+ :text="piece.sections.annotations || ''"
291
+ :is-annotations="true"
292
+ :vertical="true"
293
+ class="v-section"
294
+ />
290
295
 
291
296
  <SectionBlock
292
297
  v-for="(sec, idx) in proseSections"
@@ -316,11 +321,13 @@ function tcy(n: number): string {
316
321
  </div>
317
322
 
318
323
  <AnnotationTooltip
319
- :visible="tooltip.visible"
320
- :annotations="tooltip.items"
324
+ :visible="interaction.visible"
325
+ :annotations="interaction.items"
321
326
  :layer-labels="layerLabels"
322
- :style="tooltip.style"
323
- @close="dismissTooltip"
327
+ :style="interaction.style"
328
+ @close="interaction.dismiss"
329
+ @tooltip-enter="interaction.onTooltipEnter"
330
+ @tooltip-leave="interaction.onTooltipLeave"
324
331
  />
325
332
 
326
333
  <Teleport to="body">
@@ -376,16 +383,23 @@ function tcy(n: number): string {
376
383
  :author="piece.author"
377
384
  :verses="piece.verses"
378
385
  :annotations="mergedAnnotations"
379
- @annotation-hover="handleAnnotationHover"
380
- @annotation-leave="handleAnnotationLeave"
381
- @annotation-tap="handleAnnotationTap"
386
+ @annotation-hover="interaction.onHover"
387
+ @annotation-leave="interaction.onLeave"
388
+ @annotation-tap="interaction.onTap"
382
389
  />
383
390
  </div>
384
391
 
385
392
  <div class="h-sections">
386
- <div v-if="piece.sections.annotations || hasLayers" class="h-ann-section">
393
+ <div v-if="(piece.sections.annotations && annotationsVisible) || hasLayers" class="h-ann-section">
394
+ <AnnotationControlBar
395
+ :layers="annotationLayers"
396
+ :has-annotations="piece.annotations.length > 0"
397
+ v-model:active-ids="activeLayerIds"
398
+ v-model:annotations-visible="annotationsVisible"
399
+ style="margin-bottom: 16px"
400
+ />
387
401
  <SectionBlock
388
- v-if="piece.sections.annotations"
402
+ v-if="annotationsVisible && piece.sections.annotations"
389
403
  num=""
390
404
  label="注釋"
391
405
  :special="false"
@@ -393,12 +407,6 @@ function tcy(n: number): string {
393
407
  :is-annotations="true"
394
408
  />
395
409
  <template v-if="hasLayers">
396
- <div class="h-layers-inline">
397
- <AnnotationLayerSelector
398
- :layers="annotationLayers"
399
- v-model:activeIds="activeLayerIds"
400
- />
401
- </div>
402
410
  <SectionBlock
403
411
  v-for="block in layerAnnotationBlocks"
404
412
  :key="block.label"
@@ -438,11 +446,13 @@ function tcy(n: number): string {
438
446
  </div>
439
447
 
440
448
  <AnnotationTooltip
441
- :visible="tooltip.visible"
442
- :annotations="tooltip.items"
449
+ :visible="interaction.visible"
450
+ :annotations="interaction.items"
443
451
  :layer-labels="layerLabels"
444
- :style="tooltip.style"
445
- @close="dismissTooltip"
452
+ :style="interaction.style"
453
+ @close="interaction.dismiss"
454
+ @tooltip-enter="interaction.onTooltipEnter"
455
+ @tooltip-leave="interaction.onTooltipLeave"
446
456
  />
447
457
 
448
458
  <Teleport to="body">