@tanstack/devtools 0.5.1 → 0.6.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 (38) hide show
  1. package/dist/esm/components/main-panel.js +8 -2
  2. package/dist/esm/components/main-panel.js.map +1 -1
  3. package/dist/esm/components/tabs.js +10 -0
  4. package/dist/esm/components/tabs.js.map +1 -1
  5. package/dist/esm/context/draw-context.d.ts +13 -0
  6. package/dist/esm/context/draw-context.js +55 -0
  7. package/dist/esm/context/draw-context.js.map +1 -0
  8. package/dist/esm/context/use-devtools-context.js +10 -1
  9. package/dist/esm/context/use-devtools-context.js.map +1 -1
  10. package/dist/esm/hooks/use-head-changes.d.ts +39 -0
  11. package/dist/esm/hooks/use-head-changes.js +65 -0
  12. package/dist/esm/hooks/use-head-changes.js.map +1 -0
  13. package/dist/esm/styles/tokens.js +4 -2
  14. package/dist/esm/styles/tokens.js.map +1 -1
  15. package/dist/esm/styles/use-styles.d.ts +19 -5
  16. package/dist/esm/styles/use-styles.js +142 -39
  17. package/dist/esm/styles/use-styles.js.map +1 -1
  18. package/dist/esm/tabs/index.d.ts +5 -0
  19. package/dist/esm/tabs/index.js +8 -2
  20. package/dist/esm/tabs/index.js.map +1 -1
  21. package/dist/esm/tabs/plugins-tab.js +31 -13
  22. package/dist/esm/tabs/plugins-tab.js.map +1 -1
  23. package/dist/esm/tabs/seo-tab.d.ts +1 -0
  24. package/dist/esm/tabs/seo-tab.js +295 -0
  25. package/dist/esm/tabs/seo-tab.js.map +1 -0
  26. package/dist/esm/tabs/settings-tab.js +261 -222
  27. package/dist/esm/tabs/settings-tab.js.map +1 -1
  28. package/package.json +2 -2
  29. package/src/components/main-panel.tsx +5 -1
  30. package/src/components/tabs.tsx +9 -0
  31. package/src/context/draw-context.tsx +67 -0
  32. package/src/context/use-devtools-context.ts +12 -2
  33. package/src/hooks/use-head-changes.ts +110 -0
  34. package/src/styles/use-styles.ts +147 -39
  35. package/src/tabs/index.tsx +25 -0
  36. package/src/tabs/plugins-tab.tsx +51 -23
  37. package/src/tabs/seo-tab.tsx +245 -0
  38. package/src/tabs/settings-tab.tsx +109 -95
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/devtools",
3
- "version": "0.5.1",
3
+ "version": "0.6.1",
4
4
  "description": "TanStack Devtools is a set of tools for building advanced devtools for your application.",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -43,7 +43,7 @@
43
43
  "goober": "^2.1.16",
44
44
  "solid-js": "^1.9.7",
45
45
  "@tanstack/devtools-event-bus": "0.3.0",
46
- "@tanstack/devtools-ui": "0.3.1"
46
+ "@tanstack/devtools-ui": "0.3.2"
47
47
  },
48
48
  "peerDependencies": {
49
49
  "solid-js": ">=1.9.7"
@@ -1,8 +1,10 @@
1
1
  import clsx from 'clsx'
2
+ import { DrawClientProvider } from '../context/draw-context'
2
3
  import { useDevtoolsSettings, useHeight } from '../context/use-devtools-context'
3
4
  import { useStyles } from '../styles/use-styles'
4
5
  import { TANSTACK_DEVTOOLS } from '../utils/storage'
5
6
  import { usePiPWindow } from '../context/pip-context'
7
+
6
8
  import type { Accessor, JSX } from 'solid-js'
7
9
 
8
10
  export const MainPanel = (props: {
@@ -30,7 +32,9 @@ export const MainPanel = (props: {
30
32
  styles().devtoolsPanelContainerResizing(props.isResizing),
31
33
  )}
32
34
  >
33
- {props.children}
35
+ <DrawClientProvider animationMs={400}>
36
+ {props.children}
37
+ </DrawClientProvider>
34
38
  </div>
35
39
  )
36
40
  }
@@ -2,6 +2,7 @@ import clsx from 'clsx'
2
2
  import { For } from 'solid-js'
3
3
  import { useStyles } from '../styles/use-styles'
4
4
  import { useDevtoolsState } from '../context/use-devtools-context'
5
+ import { useDrawContext } from '../context/draw-context'
5
6
  import { tabs } from '../tabs'
6
7
  import { usePiPWindow } from '../context/pip-context'
7
8
 
@@ -18,6 +19,8 @@ export const Tabs = (props: TabsProps) => {
18
19
  `width=${window.innerWidth},height=${state().height},top=${window.screen.height},left=${window.screenLeft}}`,
19
20
  )
20
21
  }
22
+ const { hoverUtils } = useDrawContext()
23
+
21
24
  return (
22
25
  <div class={styles().tabContainer}>
23
26
  <For each={tabs}>
@@ -26,6 +29,12 @@ export const Tabs = (props: TabsProps) => {
26
29
  type="button"
27
30
  onClick={() => setState({ activeTab: tab.id })}
28
31
  class={clsx(styles().tab, { active: state().activeTab === tab.id })}
32
+ onMouseEnter={() => {
33
+ if (tab.id === 'plugins') hoverUtils.enter()
34
+ }}
35
+ onMouseLeave={() => {
36
+ if (tab.id === 'plugins') hoverUtils.leave()
37
+ }}
29
38
  >
30
39
  {tab.icon}
31
40
  </button>
@@ -0,0 +1,67 @@
1
+ import {
2
+ createContext,
3
+ createMemo,
4
+ createSignal,
5
+ onCleanup,
6
+ useContext,
7
+ } from 'solid-js'
8
+ import type { ParentComponent } from 'solid-js'
9
+
10
+ const useDraw = (props: { animationMs: number }) => {
11
+ const [activeHover, setActiveHover] = createSignal<boolean>(false)
12
+ const [forceExpand, setForceExpand] = createSignal<boolean>(false)
13
+
14
+ const expanded = createMemo(() => activeHover() || forceExpand())
15
+
16
+ let hoverTimeout: ReturnType<typeof setTimeout> | null = null
17
+ onCleanup(() => {
18
+ if (hoverTimeout) clearTimeout(hoverTimeout)
19
+ })
20
+
21
+ const hoverUtils = {
22
+ enter: () => {
23
+ if (hoverTimeout) {
24
+ clearTimeout(hoverTimeout)
25
+ hoverTimeout = null
26
+ }
27
+ setActiveHover(true)
28
+ },
29
+
30
+ leave: () => {
31
+ hoverTimeout = setTimeout(() => {
32
+ setActiveHover(false)
33
+ }, props.animationMs)
34
+ },
35
+ }
36
+
37
+ return {
38
+ expanded,
39
+ setForceExpand,
40
+ hoverUtils,
41
+ animationMs: props.animationMs,
42
+ }
43
+ }
44
+
45
+ type ContextType = ReturnType<typeof useDraw>
46
+
47
+ const DrawContext = createContext<ContextType | undefined>(undefined)
48
+
49
+ export const DrawClientProvider: ParentComponent<{
50
+ animationMs: number
51
+ }> = (props) => {
52
+ const value = useDraw({ animationMs: props.animationMs })
53
+
54
+ return (
55
+ <DrawContext.Provider value={value}>{props.children}</DrawContext.Provider>
56
+ )
57
+ }
58
+
59
+ export function useDrawContext() {
60
+ const context = useContext(DrawContext)
61
+
62
+ if (context === undefined) {
63
+ throw new Error(`useDrawContext must be used within a DrawClientProvider`)
64
+ }
65
+
66
+ return context
67
+ }
@@ -1,6 +1,7 @@
1
- import { createMemo, useContext } from 'solid-js'
1
+ import { createEffect, createMemo, useContext } from 'solid-js'
2
2
  import { DevtoolsContext } from './devtools-context.jsx'
3
- /* import type { DevtoolsPlugin } from './devtools-context' */
3
+ import { useDrawContext } from './draw-context.jsx'
4
+
4
5
  import type { DevtoolsStore } from './devtools-store.js'
5
6
 
6
7
  /**
@@ -19,10 +20,19 @@ const useDevtoolsContext = () => {
19
20
 
20
21
  export const usePlugins = () => {
21
22
  const { store, setStore } = useDevtoolsContext()
23
+ const { setForceExpand } = useDrawContext()
22
24
 
23
25
  const plugins = createMemo(() => store.plugins)
24
26
  const activePlugin = createMemo(() => store.state.activePlugin)
25
27
 
28
+ createEffect(() => {
29
+ if (activePlugin() == null) {
30
+ setForceExpand(false)
31
+ } else {
32
+ setForceExpand(true)
33
+ }
34
+ })
35
+
26
36
  const setActivePlugin = (pluginId: string) => {
27
37
  setStore((prev) => ({
28
38
  ...prev,
@@ -0,0 +1,110 @@
1
+ import { onCleanup, onMount } from 'solid-js'
2
+
3
+ type HeadChange =
4
+ | { kind: 'added'; node: Node }
5
+ | { kind: 'removed'; node: Node }
6
+ | {
7
+ kind: 'attr'
8
+ target: Element
9
+ name: string | null
10
+ oldValue: string | null
11
+ }
12
+ | { kind: 'title'; title: string }
13
+
14
+ type UseHeadChangesOptions = {
15
+ /**
16
+ * Observe attribute changes on elements inside <head>
17
+ * Default: true
18
+ */
19
+ attributes?: boolean
20
+ /**
21
+ * Observe added/removed nodes in <head>
22
+ * Default: true
23
+ */
24
+ childList?: boolean
25
+ /**
26
+ * Observe descendants of <head>
27
+ * Default: true
28
+ */
29
+ subtree?: boolean
30
+ /**
31
+ * Also observe <title> changes explicitly
32
+ * Default: true
33
+ */
34
+ observeTitle?: boolean
35
+ }
36
+
37
+ export function useHeadChanges(
38
+ onChange: (change: HeadChange, raw?: MutationRecord) => void,
39
+ opts: UseHeadChangesOptions = {},
40
+ ) {
41
+ const {
42
+ attributes = true,
43
+ childList = true,
44
+ subtree = true,
45
+ observeTitle = true,
46
+ } = opts
47
+
48
+ onMount(() => {
49
+ const headObserver = new MutationObserver((mutations) => {
50
+ for (const m of mutations) {
51
+ if (m.type === 'childList') {
52
+ m.addedNodes.forEach((node) => onChange({ kind: 'added', node }, m))
53
+ m.removedNodes.forEach((node) =>
54
+ onChange({ kind: 'removed', node }, m),
55
+ )
56
+ } else if (m.type === 'attributes') {
57
+ const el = m.target as Element
58
+ onChange(
59
+ {
60
+ kind: 'attr',
61
+ target: el,
62
+ name: m.attributeName,
63
+ oldValue: m.oldValue ?? null,
64
+ },
65
+ m,
66
+ )
67
+ } else {
68
+ // If someone mutates a Text node inside <title>, surface it as a title change.
69
+ const isInTitle =
70
+ m.target.parentNode &&
71
+ (m.target.parentNode as Element).tagName.toLowerCase() === 'title'
72
+ if (isInTitle) onChange({ kind: 'title', title: document.title }, m)
73
+ }
74
+ }
75
+ })
76
+
77
+ headObserver.observe(document.head, {
78
+ childList,
79
+ attributes,
80
+ subtree,
81
+ attributeOldValue: attributes,
82
+ characterData: true, // helps catch <title> text node edits
83
+ characterDataOldValue: false,
84
+ })
85
+
86
+ // Extra explicit observer for <title>, since `document.title = "..."`
87
+ // may not always bubble as a head mutation in all setups.
88
+ let titleObserver: MutationObserver | undefined
89
+ if (observeTitle) {
90
+ const titleEl =
91
+ document.head.querySelector('title') ||
92
+ // create a <title> if missing so future changes are observable
93
+ document.head.appendChild(document.createElement('title'))
94
+
95
+ titleObserver = new MutationObserver(() => {
96
+ onChange({ kind: 'title', title: document.title })
97
+ })
98
+ titleObserver.observe(titleEl, {
99
+ childList: true,
100
+ characterData: true,
101
+ subtree: true,
102
+ })
103
+ }
104
+
105
+ onCleanup(() => {
106
+ headObserver.disconnect()
107
+ titleObserver?.disconnect()
108
+ })
109
+ })
110
+ }
@@ -4,12 +4,132 @@ import { tokens } from './tokens'
4
4
  import type { TanStackDevtoolsConfig } from '../context/devtools-context'
5
5
  import type { Accessor } from 'solid-js'
6
6
 
7
+ const mSecondsToCssSeconds = (mSeconds: number) =>
8
+ `${(mSeconds / 1000).toFixed(2)}s`
9
+
7
10
  const stylesFactory = () => {
8
11
  const { colors, font, size, alpha, border } = tokens
9
12
  const { fontFamily, size: fontSize } = font
10
13
  const css = goober.css
11
14
 
12
15
  return {
16
+ seoTabContainer: css`
17
+ padding: 0;
18
+ margin: 0 auto;
19
+ background: ${colors.darkGray[700]};
20
+ border-radius: 12px;
21
+ box-shadow: 0 2px 16px rgba(0, 0, 0, 0.08);
22
+ overflow-y: auto;
23
+ height: 100%;
24
+ display: flex;
25
+ flex-direction: column;
26
+ gap: 0;
27
+ width: 100%;
28
+ overflow-y: auto;
29
+ `,
30
+ seoTabTitle: css`
31
+ font-size: 1.25rem;
32
+ font-weight: 600;
33
+ color: ${colors.purple[400]};
34
+ margin: 0;
35
+ padding: 1rem 1.5rem 0.5rem 1.5rem;
36
+ text-align: left;
37
+ border-bottom: 1px solid ${colors.gray[700]};
38
+ `,
39
+ seoTabSection: css`
40
+ padding: 1.5rem;
41
+ background: ${colors.darkGray[800]};
42
+ border: 1px solid ${colors.gray[700]};
43
+ display: flex;
44
+ flex-direction: column;
45
+ gap: 0.5rem;
46
+ margin-bottom: 2rem;
47
+ border-radius: 0.75rem;
48
+ `,
49
+ seoPreviewSection: css`
50
+ display: flex;
51
+ flex-direction: row;
52
+ gap: 16px;
53
+ margin-bottom: 0;
54
+ justify-content: flex-start;
55
+ align-items: flex-start;
56
+ overflow-x: auto;
57
+ flex-wrap: wrap;
58
+ padding-bottom: 0.5rem;
59
+ `,
60
+ seoPreviewCard: css`
61
+ border: 1px solid ${colors.gray[700]};
62
+ border-radius: 8px;
63
+ padding: 12px 10px;
64
+ background: ${colors.darkGray[900]};
65
+ margin-bottom: 0;
66
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
67
+ display: flex;
68
+ flex-direction: column;
69
+ align-items: flex-start;
70
+ min-width: 200px;
71
+ max-width: 240px;
72
+ font-size: 0.95rem;
73
+ gap: 4px;
74
+ `,
75
+ seoPreviewHeader: css`
76
+ font-size: 1rem;
77
+ font-weight: 500;
78
+ margin-bottom: 6px;
79
+ color: ${colors.purple[400]};
80
+ `,
81
+ seoPreviewImage: css`
82
+ max-width: 100%;
83
+ border-radius: 6px;
84
+ margin-bottom: 6px;
85
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
86
+ height: 160px;
87
+ `,
88
+ seoPreviewTitle: css`
89
+ font-size: 1rem;
90
+ font-weight: 600;
91
+ margin-bottom: 2px;
92
+ color: ${colors.gray[100]};
93
+ `,
94
+ seoPreviewDesc: css`
95
+ color: ${colors.gray[300]};
96
+ margin-bottom: 2px;
97
+ font-size: 0.95rem;
98
+ `,
99
+ seoPreviewUrl: css`
100
+ color: ${colors.gray[500]};
101
+ font-size: 0.9rem;
102
+ margin-bottom: 2px;
103
+ word-break: break-all;
104
+ `,
105
+ seoMissingTagsSection: css`
106
+ margin-top: 4px;
107
+ font-size: 0.95rem;
108
+ color: ${colors.red[400]};
109
+ `,
110
+ seoMissingTagsList: css`
111
+ margin: 4px 0 0 0;
112
+ padding: 0;
113
+ list-style: none;
114
+ display: flex;
115
+ flex-wrap: wrap;
116
+ gap: 6px;
117
+ max-width: 240px;
118
+ `,
119
+ seoMissingTag: css`
120
+ background: ${colors.red[500]}22;
121
+ color: ${colors.red[500]};
122
+ border-radius: 4px;
123
+ padding: 1px 6px;
124
+ font-size: 0.9rem;
125
+ font-weight: 500;
126
+ `,
127
+ seoAllTagsFound: css`
128
+ color: ${colors.green[500]};
129
+ font-weight: 500;
130
+ margin-left: 6px;
131
+ font-size: 0.95rem;
132
+ `,
13
133
  devtoolsPanelContainer: (
14
134
  panelLocation: TanStackDevtoolsConfig['panelLocation'],
15
135
  isDetached: boolean,
@@ -231,13 +351,37 @@ const stylesFactory = () => {
231
351
  height: 100%;
232
352
  overflow: hidden;
233
353
  `,
234
- pluginsTabSidebar: css`
235
- width: ${size[48]};
354
+
355
+ pluginsTabDraw: css`
356
+ width: 0px;
357
+ height: 100%;
236
358
  background-color: ${colors.darkGray[800]};
237
- border-right: 1px solid ${colors.gray[700]};
238
359
  box-shadow: 0 1px 0 ${colors.gray[700]};
360
+ `,
361
+ pluginsTabDrawExpanded: css`
362
+ width: ${size[48]};
363
+ border-right: 1px solid ${colors.gray[700]};
364
+ `,
365
+ pluginsTabDrawTransition: (mSeconds: number) => {
366
+ return css`
367
+ transition: width ${mSecondsToCssSeconds(mSeconds)} ease;
368
+ `
369
+ },
370
+
371
+ pluginsTabSidebar: css`
372
+ width: ${size[48]};
239
373
  overflow-y: auto;
374
+ transform: translateX(-100%);
375
+ `,
376
+ pluginsTabSidebarExpanded: css`
377
+ transform: translateX(0);
240
378
  `,
379
+ pluginsTabSidebarTransition: (mSeconds: number) => {
380
+ return css`
381
+ transition: transform ${mSecondsToCssSeconds(mSeconds)} ease;
382
+ `
383
+ },
384
+
241
385
  pluginName: css`
242
386
  font-size: ${fontSize.xs};
243
387
  font-family: ${fontFamily.sans};
@@ -262,42 +406,6 @@ const stylesFactory = () => {
262
406
  overflow-y: auto;
263
407
  `,
264
408
 
265
- settingsContainer: css`
266
- padding: 1.5rem;
267
- height: 100%;
268
- overflow-y: auto;
269
- background-color: ${colors.darkGray[700]};
270
- `,
271
- settingsSection: css`
272
- margin-bottom: 2rem;
273
- padding: 1.5rem;
274
- background-color: ${colors.darkGray[800]};
275
- border: 1px solid ${colors.gray[700]};
276
- border-radius: 0.75rem;
277
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
278
- `,
279
- sectionTitle: css`
280
- font-size: 1.125rem;
281
- font-weight: 600;
282
- color: ${colors.gray[100]};
283
- margin: 0 0 1rem 0;
284
- padding-bottom: 0.5rem;
285
- border-bottom: 1px solid ${colors.gray[700]};
286
- display: flex;
287
- align-items: center;
288
- gap: 0.5rem;
289
- text-align: left;
290
- `,
291
- sectionIcon: css`
292
- color: ${colors.purple[400]};
293
- `,
294
- sectionDescription: css`
295
- color: ${colors.gray[400]};
296
- font-size: 0.875rem;
297
- margin: 0 0 1.5rem 0;
298
- line-height: 1.5;
299
- text-align: left;
300
- `,
301
409
  settingsGroup: css`
302
410
  display: flex;
303
411
  flex-direction: column;
@@ -1,5 +1,6 @@
1
1
  import { SettingsTab } from './settings-tab'
2
2
  import { PluginsTab } from './plugins-tab'
3
+ import { SeoTab } from './seo-tab'
3
4
 
4
5
  export const tabs = [
5
6
  {
@@ -24,6 +25,30 @@ export const tabs = [
24
25
  </svg>
25
26
  ),
26
27
  },
28
+ {
29
+ name: 'SEO',
30
+ id: 'seo',
31
+ component: () => <SeoTab />,
32
+ icon: (
33
+ <svg
34
+ xmlns="http://www.w3.org/2000/svg"
35
+ width="24"
36
+ height="24"
37
+ viewBox="0 0 24 24"
38
+ fill="none"
39
+ stroke="currentColor"
40
+ stroke-width="2"
41
+ stroke-linecap="round"
42
+ stroke-linejoin="round"
43
+ class="lucide lucide-file-search2-icon lucide-file-search-2"
44
+ >
45
+ <path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" />
46
+ <path d="M14 2v4a2 2 0 0 0 2 2h4" />
47
+ <circle cx="11.5" cy="14.5" r="2.5" />
48
+ <path d="M13.3 16.3 15 18" />
49
+ </svg>
50
+ ),
51
+ },
27
52
  {
28
53
  name: 'Settings',
29
54
  id: 'settings',
@@ -1,11 +1,13 @@
1
1
  import { For, createEffect } from 'solid-js'
2
2
  import clsx from 'clsx'
3
+ import { useDrawContext } from '../context/draw-context'
3
4
  import { usePlugins } from '../context/use-devtools-context'
4
5
  import { useStyles } from '../styles/use-styles'
5
6
  import { PLUGIN_CONTAINER_ID, PLUGIN_TITLE_CONTAINER_ID } from '../constants'
6
7
 
7
8
  export const PluginsTab = () => {
8
9
  const { plugins, activePlugin, setActivePlugin } = usePlugins()
10
+ const { expanded, hoverUtils, animationMs } = useDrawContext()
9
11
  let activePluginRef: HTMLDivElement | undefined
10
12
 
11
13
  createEffect(() => {
@@ -17,32 +19,58 @@ export const PluginsTab = () => {
17
19
  }
18
20
  })
19
21
  const styles = useStyles()
22
+
20
23
  return (
21
24
  <div class={styles().pluginsTabPanel}>
22
- <div class={styles().pluginsTabSidebar}>
23
- <For each={plugins()}>
24
- {(plugin) => {
25
- let pluginHeading: HTMLHeadingElement | undefined
26
- createEffect(() => {
27
- if (pluginHeading) {
28
- typeof plugin.name === 'string'
29
- ? (pluginHeading.textContent = plugin.name)
30
- : plugin.name(pluginHeading)
31
- }
32
- })
33
- return (
34
- <div
35
- onClick={() => setActivePlugin(plugin.id!)}
36
- class={clsx(styles().pluginName, {
37
- active: activePlugin() === plugin.id,
38
- })}
39
- >
40
- <h3 id={PLUGIN_TITLE_CONTAINER_ID} ref={pluginHeading} />
41
- </div>
42
- )
43
- }}
44
- </For>
25
+ <div
26
+ class={clsx(
27
+ styles().pluginsTabDraw,
28
+ {
29
+ [styles().pluginsTabDrawExpanded]: expanded(),
30
+ },
31
+ styles().pluginsTabDrawTransition(animationMs),
32
+ )}
33
+ onMouseEnter={() => {
34
+ hoverUtils.enter()
35
+ }}
36
+ onMouseLeave={() => {
37
+ hoverUtils.leave()
38
+ }}
39
+ >
40
+ <div
41
+ class={clsx(
42
+ styles().pluginsTabSidebar,
43
+ {
44
+ [styles().pluginsTabSidebarExpanded]: expanded(),
45
+ },
46
+ styles().pluginsTabSidebarTransition(animationMs),
47
+ )}
48
+ >
49
+ <For each={plugins()}>
50
+ {(plugin) => {
51
+ let pluginHeading: HTMLHeadingElement | undefined
52
+ createEffect(() => {
53
+ if (pluginHeading) {
54
+ typeof plugin.name === 'string'
55
+ ? (pluginHeading.textContent = plugin.name)
56
+ : plugin.name(pluginHeading)
57
+ }
58
+ })
59
+ return (
60
+ <div
61
+ onClick={() => setActivePlugin(plugin.id!)}
62
+ class={clsx(styles().pluginName, {
63
+ active: activePlugin() === plugin.id,
64
+ })}
65
+ >
66
+ <h3 id={PLUGIN_TITLE_CONTAINER_ID} ref={pluginHeading} />
67
+ </div>
68
+ )
69
+ }}
70
+ </For>
71
+ </div>
45
72
  </div>
73
+
46
74
  <div
47
75
  id={PLUGIN_CONTAINER_ID}
48
76
  ref={activePluginRef}