@tnotesjs/ui 0.1.5 → 0.2.1

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.
Files changed (65) hide show
  1. package/README.md +30 -97
  2. package/dist/code/highlight.js +6 -15
  3. package/dist/components/Footprints/parse.js +1 -3
  4. package/dist/components/Mindmap/markdown.d.ts +1 -7
  5. package/dist/components/Mindmap/markdown.js +1 -15
  6. package/package.json +26 -9
  7. package/src/browser/clipboard.ts +16 -19
  8. package/src/browser/theme.ts +10 -15
  9. package/src/code/highlight.ts +89 -105
  10. package/src/components/Badge/Badge.vue +4 -4
  11. package/src/components/CodeBlock/CodeBlock.vue +43 -53
  12. package/src/components/CodeGroup/CodeGroup.vue +32 -35
  13. package/src/components/Footprints/Footprints.vue +2 -14
  14. package/src/components/Footprints/parse.ts +1 -3
  15. package/src/components/ImagePreview/ImagePreview.vue +101 -126
  16. package/src/components/Mermaid/Mermaid.vue +9 -12
  17. package/src/components/Mindmap/FocusBreadcrumbs.vue +31 -28
  18. package/src/components/Mindmap/InlineRuns.ts +25 -16
  19. package/src/components/Mindmap/Mindmap.vue +147 -81
  20. package/src/components/Mindmap/MindmapOutlineNode.vue +4 -1
  21. package/src/components/Mindmap/editor/AppIcon.vue +32 -11
  22. package/src/components/Mindmap/editor/CanvasContextMenu.vue +126 -49
  23. package/src/components/Mindmap/editor/LinkPopover.vue +101 -23
  24. package/src/components/Mindmap/editor/MarkdownView.vue +7 -4
  25. package/src/components/Mindmap/editor/MindmapView.vue +11 -3
  26. package/src/components/Mindmap/editor/OutlineView.vue +144 -58
  27. package/src/components/Mindmap/editor/RichInlineEditor.vue +93 -44
  28. package/src/components/Mindmap/editor/SelectionToolbar.vue +133 -41
  29. package/src/components/Mindmap/editor/canvasClipboard.ts +1 -5
  30. package/src/components/Mindmap/editor/imagePaste.ts +1 -1
  31. package/src/components/Mindmap/editor/mindmapClipboard.ts +17 -5
  32. package/src/components/Mindmap/editor/outlineDrag.ts +1 -1
  33. package/src/components/Mindmap/markdown.ts +10 -29
  34. package/src/components/WordList/RightClickMenu.vue +10 -35
  35. package/src/components/WordList/WordList.vue +19 -42
  36. package/src/entries/bilibili-video.ts +1 -1
  37. package/src/entries/excalidraw-editor.ts +20 -0
  38. package/src/entries/excalidraw-view.ts +15 -0
  39. package/src/entries/footprints.ts +1 -1
  40. package/src/entries/mermaid.ts +1 -1
  41. package/src/entries/mindmap.ts +1 -1
  42. package/src/entries/notes-table.ts +2 -2
  43. package/src/entries/word-list.ts +1 -1
  44. package/src/excalidraw/ExcalidrawSvg.vue +125 -0
  45. package/src/excalidraw/editorHost.ts +110 -0
  46. package/src/excalidraw/editorSession.ts +224 -0
  47. package/src/excalidraw/exporter.ts +50 -0
  48. package/src/excalidraw/focus.ts +33 -0
  49. package/src/excalidraw/fonts.ts +92 -0
  50. package/src/excalidraw/renderer.ts +105 -0
  51. package/src/excalidraw/scene.ts +93 -0
  52. package/src/index.ts +25 -33
  53. package/src/markdown/image.ts +4 -13
  54. package/src/styles/code.css +5 -5
  55. package/src/styles/prose.css +1 -1
  56. package/src/styles/swiper.css +1 -1
  57. package/src/styles/tokens.css +5 -6
  58. package/src/swiper/hydrate.ts +91 -102
  59. package/src/code/highlight.test.ts +0 -55
  60. package/src/components/Mermaid/Mermaid.test.ts +0 -54
  61. package/src/components/Mermaid/adoptLiveSvg.test.ts +0 -16
  62. package/src/components/Mindmap/FocusBreadcrumbs.test.ts +0 -265
  63. package/src/components/sharedMedia.test.ts +0 -139
  64. package/src/markdown/image.test.ts +0 -88
  65. package/src/swiper/hydrate.test.ts +0 -65
@@ -76,13 +76,9 @@ export function normalizeImageWidth(raw: string): string {
76
76
  }
77
77
 
78
78
  /** Logical CSS px for a pasted bitmap, folding device pixel ratio (4K/retina). */
79
- export function pasteDisplayWidthPx(
80
- naturalWidth: number,
81
- devicePixelRatio = 1
82
- ): string {
79
+ export function pasteDisplayWidthPx(naturalWidth: number, devicePixelRatio = 1): string {
83
80
  if (!Number.isFinite(naturalWidth) || naturalWidth <= 0) return ''
84
- const dpr =
85
- Number.isFinite(devicePixelRatio) && devicePixelRatio > 0 ? devicePixelRatio : 1
81
+ const dpr = Number.isFinite(devicePixelRatio) && devicePixelRatio > 0 ? devicePixelRatio : 1
86
82
  return normalizeImageWidth(`${Math.round(naturalWidth / dpr)}px`)
87
83
  }
88
84
 
@@ -90,10 +86,7 @@ export function formatImageWidthAttr(width: string): string {
90
86
  return formatImageAttrs({ width, align: 'left' })
91
87
  }
92
88
 
93
- export function formatImageAttrs(attrs: {
94
- width?: string
95
- align?: ImageAlign | string
96
- }): string {
89
+ export function formatImageAttrs(attrs: { width?: string; align?: ImageAlign | string }): string {
97
90
  const width = normalizeImageWidth(attrs.width ?? '')
98
91
  const align = normalizeImageAlign(attrs.align ?? '')
99
92
  const parts: string[] = []
@@ -104,9 +97,7 @@ export function formatImageAttrs(attrs: {
104
97
 
105
98
  export function serializeImageMarkdown(image: ImageMarkdown): string {
106
99
  const alt = image.alt.replaceAll('[', '').replaceAll(']', '')
107
- const title = image.title?.trim()
108
- ? ` "${image.title.replaceAll('"', '')}"`
109
- : ''
100
+ const title = image.title?.trim() ? ` "${image.title.replaceAll('"', '')}"` : ''
110
101
  const suffix = formatImageAttrs({
111
102
  width: image.width ?? '',
112
103
  align: image.align ?? 'left'
@@ -105,13 +105,13 @@
105
105
  color: var(--shiki-light);
106
106
  background-color: transparent;
107
107
  }
108
- html.dark:not([data-theme="light"]) .tn-code-highlight,
109
- html[data-theme="dark"] .tn-code-highlight {
108
+ html.dark:not([data-theme='light']) .tn-code-highlight,
109
+ html[data-theme='dark'] .tn-code-highlight {
110
110
  color: var(--shiki-dark);
111
111
  background-color: var(--tn-c-bg-soft);
112
112
  }
113
- html.dark:not([data-theme="light"]) .tn-code-highlight span:not(.line),
114
- html[data-theme="dark"] .tn-code-highlight span:not(.line) {
113
+ html.dark:not([data-theme='light']) .tn-code-highlight span:not(.line),
114
+ html[data-theme='dark'] .tn-code-highlight span:not(.line) {
115
115
  color: var(--shiki-dark);
116
116
  background-color: transparent;
117
117
  }
@@ -151,7 +151,7 @@ html[data-theme="dark"] .tn-code-highlight span:not(.line) {
151
151
  border-bottom: 2px solid transparent;
152
152
  padding: 4px 10px;
153
153
  }
154
- .tn-code-group__tabs button[aria-selected="true"] {
154
+ .tn-code-group__tabs button[aria-selected='true'] {
155
155
  color: var(--tn-c-brand);
156
156
  border-bottom-color: var(--tn-c-brand);
157
157
  }
@@ -198,7 +198,7 @@
198
198
  display: none;
199
199
  }
200
200
  :is(.tn-prose, .vp-doc) :where(details.tn-custom-block > summary::before) {
201
- content: "";
201
+ content: '';
202
202
  flex: none;
203
203
  box-sizing: border-box;
204
204
  width: 0.42em;
@@ -101,7 +101,7 @@
101
101
  z-index: 1;
102
102
  height: 2px;
103
103
  border-radius: 2px;
104
- content: "";
104
+ content: '';
105
105
  background-color: transparent;
106
106
  transition: background-color 0.2s ease;
107
107
  }
@@ -24,10 +24,9 @@
24
24
  --tn-c-default-soft: #e8e8e9;
25
25
  --tn-shadow-2: 0 4px 12px rgba(0, 0, 0, 0.12);
26
26
  --tn-font-sans:
27
- "Punctuation SC", Inter, ui-sans-serif, system-ui, sans-serif,
28
- "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
29
- --tn-font-mono:
30
- "SFMono-Regular", "Cascadia Code", "Roboto Mono", Consolas, monospace;
27
+ 'Punctuation SC', Inter, ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji',
28
+ 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
29
+ --tn-font-mono: 'SFMono-Regular', 'Cascadia Code', 'Roboto Mono', Consolas, monospace;
31
30
  --tn-prose-size: 16px;
32
31
  --tn-prose-line-height: 28px;
33
32
  --tn-heading-weight: 600;
@@ -46,8 +45,8 @@
46
45
  color-scheme: light;
47
46
  }
48
47
 
49
- :root.dark:not([data-theme="light"]),
50
- :root[data-theme="dark"] {
48
+ :root.dark:not([data-theme='light']),
49
+ :root[data-theme='dark'] {
51
50
  --tn-c-brand-strong: #a8b1ff;
52
51
  --tn-c-text: #dfdfd6;
53
52
  --tn-c-text-2: #98989f;
@@ -5,56 +5,49 @@
5
5
  */
6
6
 
7
7
  export function applySwiperTabsPadding(tabs: HTMLElement, hasNav: boolean): void {
8
- tabs.style.padding = hasNav ? "0 0.8rem 0 3rem" : "0 0.8rem";
8
+ tabs.style.padding = hasNav ? '0 0.8rem 0 3rem' : '0 0.8rem'
9
9
  }
10
10
 
11
- export function createSwiperTabNav(handlers: {
12
- onPrev: () => void;
13
- onNext: () => void;
14
- }): {
15
- prev: HTMLButtonElement;
16
- line: HTMLSpanElement;
17
- next: HTMLButtonElement;
11
+ export function createSwiperTabNav(handlers: { onPrev: () => void; onNext: () => void }): {
12
+ prev: HTMLButtonElement
13
+ line: HTMLSpanElement
14
+ next: HTMLButtonElement
18
15
  } {
19
- const prev = document.createElement("button");
20
- prev.type = "button";
21
- prev.className = "tn-tab-nav tn-tab-prev";
22
- prev.textContent = "<";
23
- prev.title = "上一页";
24
- prev.addEventListener("click", (event) => {
25
- event.preventDefault();
26
- event.stopPropagation();
27
- handlers.onPrev();
28
- });
29
-
30
- const line = document.createElement("span");
31
- line.className = "tn-tab-nav tab-tab-line";
32
- line.textContent = "/";
33
-
34
- const next = document.createElement("button");
35
- next.type = "button";
36
- next.className = "tn-tab-nav tn-tab-next";
37
- next.textContent = ">";
38
- next.title = "下一页";
39
- next.addEventListener("click", (event) => {
40
- event.preventDefault();
41
- event.stopPropagation();
42
- handlers.onNext();
43
- });
44
-
45
- return { prev, line, next };
16
+ const prev = document.createElement('button')
17
+ prev.type = 'button'
18
+ prev.className = 'tn-tab-nav tn-tab-prev'
19
+ prev.textContent = '<'
20
+ prev.title = '上一页'
21
+ prev.addEventListener('click', (event) => {
22
+ event.preventDefault()
23
+ event.stopPropagation()
24
+ handlers.onPrev()
25
+ })
26
+
27
+ const line = document.createElement('span')
28
+ line.className = 'tn-tab-nav tab-tab-line'
29
+ line.textContent = '/'
30
+
31
+ const next = document.createElement('button')
32
+ next.type = 'button'
33
+ next.className = 'tn-tab-nav tn-tab-next'
34
+ next.textContent = '>'
35
+ next.title = '下一页'
36
+ next.addEventListener('click', (event) => {
37
+ event.preventDefault()
38
+ event.stopPropagation()
39
+ handlers.onNext()
40
+ })
41
+
42
+ return { prev, line, next }
46
43
  }
47
44
 
48
- export function wrapSlideIndex(
49
- index: number,
50
- length: number,
51
- delta: -1 | 1,
52
- ): number {
53
- if (length <= 0) return 0;
54
- return (index + delta + length) % length;
45
+ export function wrapSlideIndex(index: number, length: number, delta: -1 | 1): number {
46
+ if (length <= 0) return 0
47
+ return (index + delta + length) % length
55
48
  }
56
49
 
57
- const READY = "tnSwiperReady";
50
+ const READY = 'tnSwiperReady'
58
51
 
59
52
  /**
60
53
  * Hydrate every `.tn-swiper` under `root` (or `document`). Idempotent:
@@ -62,103 +55,99 @@ const READY = "tnSwiperReady";
62
55
  * When `root` itself is a `.tn-swiper`, it is included.
63
56
  */
64
57
  export function hydrateTnSwipers(root: ParentNode = document): number {
65
- const hosts: HTMLElement[] = [];
66
- if (root instanceof HTMLElement && root.classList.contains("tn-swiper")) {
67
- hosts.push(root);
58
+ const hosts: HTMLElement[] = []
59
+ if (root instanceof HTMLElement && root.classList.contains('tn-swiper')) {
60
+ hosts.push(root)
68
61
  }
69
- hosts.push(...root.querySelectorAll<HTMLElement>(".tn-swiper"));
62
+ hosts.push(...root.querySelectorAll<HTMLElement>('.tn-swiper'))
70
63
 
71
- let count = 0;
72
- const seen = new Set<HTMLElement>();
64
+ let count = 0
65
+ const seen = new Set<HTMLElement>()
73
66
  for (const host of hosts) {
74
- if (seen.has(host)) continue;
75
- seen.add(host);
76
- if (host.dataset[READY] === "true") continue;
67
+ if (seen.has(host)) continue
68
+ seen.add(host)
69
+ if (host.dataset[READY] === 'true') continue
77
70
  if (hydrateOne(host)) {
78
- host.dataset[READY] = "true";
79
- count += 1;
71
+ host.dataset[READY] = 'true'
72
+ count += 1
80
73
  }
81
74
  }
82
- return count;
75
+ return count
83
76
  }
84
77
 
85
78
  function hydrateOne(host: HTMLElement): boolean {
86
- const tabs = host.querySelector<HTMLElement>(".tn-swiper-tabs");
87
- const wrapper = host.querySelector<HTMLElement>(".swiper-wrapper");
88
- if (!wrapper) return false;
79
+ const tabs = host.querySelector<HTMLElement>('.tn-swiper-tabs')
80
+ const wrapper = host.querySelector<HTMLElement>('.swiper-wrapper')
81
+ if (!wrapper) return false
89
82
 
90
- const slides = [
91
- ...wrapper.querySelectorAll<HTMLElement>(":scope > .swiper-slide"),
92
- ];
83
+ const slides = [...wrapper.querySelectorAll<HTMLElement>(':scope > .swiper-slide')]
93
84
  if (slides.length === 0) {
94
85
  // Empty shell — still mark ready so we don't loop.
95
- return true;
86
+ return true
96
87
  }
97
88
 
98
89
  // Ensure tabs host exists (site always emits it; Desk may omit for 1 slide).
99
- let tabsEl = tabs;
90
+ let tabsEl = tabs
100
91
  if (!tabsEl) {
101
- tabsEl = document.createElement("div");
102
- tabsEl.className = "tn-swiper-tabs";
103
- host.prepend(tabsEl);
92
+ tabsEl = document.createElement('div')
93
+ tabsEl.className = 'tn-swiper-tabs'
94
+ host.prepend(tabsEl)
104
95
  }
105
96
 
106
- const useTabs = slides.length > 1;
107
- tabsEl.replaceChildren();
108
- applySwiperTabsPadding(tabsEl, useTabs);
97
+ const useTabs = slides.length > 1
98
+ tabsEl.replaceChildren()
99
+ applySwiperTabsPadding(tabsEl, useTabs)
109
100
 
110
- const tabButtons: HTMLButtonElement[] = [];
101
+ const tabButtons: HTMLButtonElement[] = []
111
102
 
112
103
  const activate = (index: number): void => {
113
104
  tabButtons.forEach((button, buttonIndex) =>
114
- button.classList.toggle("active", buttonIndex === index),
115
- );
105
+ button.classList.toggle('active', buttonIndex === index)
106
+ )
116
107
  slides.forEach((slide, slideIndex) => {
117
- const on = slideIndex === index;
118
- slide.classList.toggle("is-active", on);
119
- slide.hidden = !on;
120
- });
121
- };
108
+ const on = slideIndex === index
109
+ slide.classList.toggle('is-active', on)
110
+ slide.hidden = !on
111
+ })
112
+ }
122
113
 
123
114
  const activeIndex = (): number => {
124
- const found = tabButtons.findIndex((button) =>
125
- button.classList.contains("active"),
126
- );
127
- return found >= 0 ? found : 0;
128
- };
115
+ const found = tabButtons.findIndex((button) => button.classList.contains('active'))
116
+ return found >= 0 ? found : 0
117
+ }
129
118
 
130
119
  if (useTabs) {
131
120
  const nav = createSwiperTabNav({
132
121
  onPrev: () => activate(wrapSlideIndex(activeIndex(), slides.length, -1)),
133
- onNext: () => activate(wrapSlideIndex(activeIndex(), slides.length, 1)),
134
- });
135
- tabsEl.append(nav.prev, nav.line);
122
+ onNext: () => activate(wrapSlideIndex(activeIndex(), slides.length, 1))
123
+ })
124
+ tabsEl.append(nav.prev, nav.line)
136
125
 
137
126
  slides.forEach((slide, index) => {
138
127
  const title =
139
128
  slide.dataset.title?.trim() ||
140
- slide.querySelector("img")?.getAttribute("alt")?.trim() ||
141
- "img";
142
- slide.dataset.title = title;
143
- const tab = document.createElement("button");
144
- tab.type = "button";
145
- tab.className = "tn-tab";
146
- tab.textContent = title;
147
- tab.addEventListener("click", () => activate(index));
148
- tabButtons.push(tab);
149
- tabsEl!.append(tab);
150
- });
151
-
152
- tabsEl.append(nav.next);
129
+ slide.querySelector('img')?.getAttribute('alt')?.trim() ||
130
+ 'img'
131
+ slide.dataset.title = title
132
+ const tab = document.createElement('button')
133
+ tab.type = 'button'
134
+ tab.className = 'tn-tab'
135
+ tab.textContent = title
136
+ tab.addEventListener('click', () => activate(index))
137
+ tabButtons.push(tab)
138
+ tabsEl!.append(tab)
139
+ })
140
+
141
+ tabsEl.append(nav.next)
153
142
  // Multi-slide: tabs stay above the container.
154
143
  if (tabsEl.parentElement !== host || host.firstElementChild !== tabsEl) {
155
- host.prepend(tabsEl);
144
+ host.prepend(tabsEl)
156
145
  }
157
146
  } else {
158
147
  // Single slide: drop empty tab bar (Desk parity).
159
- tabsEl.remove();
148
+ tabsEl.remove()
160
149
  }
161
150
 
162
- activate(0);
163
- return true;
151
+ activate(0)
152
+ return true
164
153
  }
@@ -1,55 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
- import { highlightCode, highlightCodeSync, parseCodeMeta, prepareCodeHighlighter } from './highlight'
3
-
4
- describe('shared code rendering', () => {
5
- it('parses titles, line offsets, highlight ranges and per-block overrides', () => {
6
- expect(parseCodeMeta('ts:line-numbers=12 {1,3-5} [demo.ts]')).toEqual({
7
- language: 'ts', title: 'demo.ts', startLine: 12, lineNumbers: true,
8
- highlightedLines: [1, 3, 4, 5]
9
- })
10
- expect(parseCodeMeta('js {1-2} [setup.js]')).toEqual({
11
- language: 'js', title: 'setup.js', startLine: 1, lineNumbers: true,
12
- highlightedLines: [1, 2]
13
- })
14
- expect(parseCodeMeta('txt {3}')).toEqual({
15
- language: 'txt', title: '', startLine: 1, lineNumbers: true,
16
- highlightedLines: [3]
17
- })
18
- expect(parseCodeMeta('text:no-line-numbers').lineNumbers).toBe(false)
19
- expect(parseCodeMeta('text {0,-1,8-3,1-9999999}').highlightedLines).toEqual([])
20
- })
21
- it('shares one lazy engine and matches build-time/browser output byte for byte', async () => {
22
- const [a, b] = await Promise.all([prepareCodeHighlighter(['js']), prepareCodeHighlighter(['typescript'])])
23
- expect(a).toBe(b)
24
- const source = 'const answer = 42\nconsole.log(answer)\n'
25
- const runtime = await highlightCode(source, 'js {2}')
26
- expect(runtime).toBe(highlightCodeSync(source, 'js {2}'))
27
- expect(runtime).toContain('--shiki-light:')
28
- expect(runtime).toContain('--shiki-dark:')
29
- expect(runtime).toContain('class="line highlighted"')
30
- expect(runtime).toContain('data-line="2"')
31
- expect(a.getLoadedLanguages()).not.toContain('cobol')
32
- })
33
- it('escapes unknown-language code instead of executing HTML', async () => {
34
- const html = await highlightCode('<script>alert(1)</script> {{ injected }}', 'unknown-label')
35
- expect(html).not.toContain('<script>')
36
- const host = document.createElement('div')
37
- host.innerHTML = html
38
- expect(host.querySelector('script')).toBeNull()
39
- expect(host.textContent).toBe('<script>alert(1)</script> {{ injected }}')
40
- expect(html).toContain('{{ injected }}')
41
- })
42
- it('applies fence line highlights and offsets without Shiki notation', async () => {
43
- const withNotation = await highlightCode(
44
- 'const x = 1 // [!code ++]\nconst y = 2 // [!code focus]',
45
- 'js:line-numbers=5 {2}',
46
- )
47
- // Magic comments stay as source text; Desk has no annotation write path.
48
- expect(withNotation).toContain('[!code ++]')
49
- expect(withNotation).not.toContain('diff add')
50
- expect(withNotation).not.toContain('focused')
51
- expect(withNotation).toContain('data-line="5"')
52
- expect(withNotation).toContain('data-line="6"')
53
- expect(withNotation).toContain('class="line highlighted"')
54
- })
55
- })
@@ -1,54 +0,0 @@
1
- // @vitest-environment happy-dom
2
-
3
- import { createApp, defineComponent, h, nextTick, type Component } from 'vue'
4
- import { afterEach, describe, expect, it } from 'vitest'
5
-
6
- import Mermaid from './Mermaid.vue'
7
-
8
- const cleanups: Array<() => void> = []
9
-
10
- function mount(component: Component, props: Record<string, unknown> = {}): HTMLElement {
11
- const host = document.createElement('div')
12
- document.body.append(host)
13
- const app = createApp(defineComponent({ render: () => h(component, props) }))
14
- app.mount(host)
15
- cleanups.push(() => {
16
- app.unmount()
17
- host.remove()
18
- })
19
- return host
20
- }
21
-
22
- afterEach(() => {
23
- cleanups
24
- .splice(0)
25
- .reverse()
26
- .forEach((cleanup) => cleanup())
27
- document.body.innerHTML = ''
28
- })
29
-
30
- describe('Mermaid preview host', () => {
31
- it('keeps the diagram host laid out while loading (note 0020 diagrams)', async () => {
32
- const sources = [
33
- `flowchart LR\n Desk --> UI`,
34
- `sequenceDiagram\n participant Desk\n participant Site\n Desk->>Site: hi`,
35
- `graph TD\n A[Start] --> B{ok}`
36
- ]
37
- for (const source of sources) {
38
- const host = mount(Mermaid, { source })
39
- await nextTick()
40
- const diagram = host.querySelector('.tn-mermaid__diagram')
41
- expect(diagram).not.toBeNull()
42
- // Mermaid 11 needs layout during mermaid.render(); do not visibility:hidden the host.
43
- expect(diagram!.classList.contains('is-obscured')).toBe(false)
44
- }
45
- })
46
-
47
- it('only obscures the host for empty or error states', async () => {
48
- const emptyHost = mount(Mermaid, { source: '' })
49
- await nextTick()
50
- expect(emptyHost.querySelector('.tn-mermaid__diagram')!.classList.contains('is-obscured')).toBe(
51
- true
52
- )
53
- })
54
- })
@@ -1,16 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
-
3
- import { adoptLiveSvg } from './adoptLiveSvg'
4
-
5
- describe('adoptLiveSvg', () => {
6
- it('renames the root svg id and scoped theme selectors', () => {
7
- const svg = `<svg id="tn-mermaid-1" xmlns="http://www.w3.org/2000/svg"><style>#tn-mermaid-1{fill:#ccc;}#tn-mermaid-1 .node{stroke:#fff;}#tn-mermaid-1_flowchart-v2-pointEnd{fill:red;}</style><path id="tn-mermaid-1_flowchart-v2-pointEnd"/></svg>`
8
- const out = adoptLiveSvg(svg, 'tn-mermaid-1')
9
- expect(out).toContain('id="tn-mermaid-1-live"')
10
- expect(out).toContain('#tn-mermaid-1-live{fill:#ccc;}')
11
- expect(out).toContain('#tn-mermaid-1-live .node{stroke:#fff;}')
12
- expect(out).toContain('id="tn-mermaid-1_flowchart-v2-pointEnd"')
13
- expect(out).toContain('#tn-mermaid-1_flowchart-v2-pointEnd{fill:red;}')
14
- expect(out).not.toMatch(/id="tn-mermaid-1"/)
15
- })
16
- })