@tanstack/devtools-utils
Advanced tools
| export * from './panel.js'; | ||
| export * from './plugin.js'; |
| import { createSveltePanel } from "./panel.js"; | ||
| import { createSveltePlugin } from "./plugin.js"; | ||
| export { createSveltePanel, createSveltePlugin }; |
| import { Component } from 'svelte'; | ||
| export interface DevtoolsPanelProps { | ||
| theme?: 'dark' | 'light' | 'system'; | ||
| } | ||
| export declare function createSveltePanel<TComponentProps extends DevtoolsPanelProps, TCoreDevtoolsClass extends { | ||
| mount: (el: HTMLElement, theme?: DevtoolsPanelProps['theme']) => void; | ||
| unmount: () => void; | ||
| }>(CoreClass: new (props: TComponentProps) => TCoreDevtoolsClass): [Component<any>, Component<any>]; |
| //#region src/svelte/panel.ts | ||
| function createSveltePanel(CoreClass) { | ||
| const Panel = ((anchor, props) => { | ||
| const el = document.createElement("div"); | ||
| el.style.height = "100%"; | ||
| anchor.before(el); | ||
| const instance = new CoreClass(props?.devtoolsProps); | ||
| instance.mount(el, props?.theme); | ||
| return { destroy() { | ||
| instance.unmount(); | ||
| el.remove(); | ||
| } }; | ||
| }); | ||
| const NoOpPanel = (() => ({})); | ||
| return [Panel, NoOpPanel]; | ||
| } | ||
| //#endregion | ||
| export { createSveltePanel }; | ||
| //# sourceMappingURL=panel.js.map |
| {"version":3,"file":"panel.js","names":[],"sources":["../../../src/svelte/panel.ts"],"sourcesContent":["import type { Component } from 'svelte'\n\nexport interface DevtoolsPanelProps {\n theme?: 'dark' | 'light' | 'system'\n}\n\nexport function createSveltePanel<\n TComponentProps extends DevtoolsPanelProps,\n TCoreDevtoolsClass extends {\n mount: (el: HTMLElement, theme?: DevtoolsPanelProps['theme']) => void\n unmount: () => void\n },\n>(\n CoreClass: new (props: TComponentProps) => TCoreDevtoolsClass,\n): [Component<any>, Component<any>] {\n const Panel: Component<any> = ((anchor: any, props: any) => {\n const el = document.createElement('div')\n el.style.height = '100%'\n anchor.before(el)\n\n const instance = new CoreClass(props?.devtoolsProps as TComponentProps)\n instance.mount(el, props?.theme)\n\n return {\n destroy() {\n instance.unmount()\n el.remove()\n },\n }\n }) as any\n\n const NoOpPanel: Component<any> = (() => ({})) as any\n\n return [Panel, NoOpPanel]\n}\n"],"mappings":";AAMA,SAAgB,kBAOd,WACkC;CAClC,MAAM,UAA0B,QAAa,UAAe;EAC1D,MAAM,KAAK,SAAS,cAAc,MAAM;EACxC,GAAG,MAAM,SAAS;EAClB,OAAO,OAAO,GAAG;EAEjB,MAAM,WAAW,IAAI,UAAU,OAAO,cAAiC;EACvE,SAAS,MAAM,IAAI,OAAO,MAAM;EAEhC,OAAO,EACL,UAAU;GACR,SAAS,SAAS;GAClB,GAAG,QAAQ;KAEd;;CAGH,MAAM,oBAAoC,EAAE;CAE5C,OAAO,CAAC,OAAO,UAAU"} |
| export {}; |
| import { Component } from 'svelte'; | ||
| export declare function createSveltePlugin<TComponentProps extends Record<string, any>>(name: string, component: Component<TComponentProps>): readonly [(props?: TComponentProps) => { | ||
| name: string; | ||
| component: Component<TComponentProps, {}, string>; | ||
| props: TComponentProps | undefined; | ||
| }, (props?: TComponentProps) => { | ||
| name: string; | ||
| component: Component<any>; | ||
| props: TComponentProps | undefined; | ||
| }]; |
| //#region src/svelte/plugin.ts | ||
| function createSveltePlugin(name, component) { | ||
| function Plugin(props) { | ||
| return { | ||
| name, | ||
| component, | ||
| props | ||
| }; | ||
| } | ||
| function NoOpPlugin(props) { | ||
| return { | ||
| name, | ||
| component: (() => {}), | ||
| props | ||
| }; | ||
| } | ||
| return [Plugin, NoOpPlugin]; | ||
| } | ||
| //#endregion | ||
| export { createSveltePlugin }; | ||
| //# sourceMappingURL=plugin.js.map |
| {"version":3,"file":"plugin.js","names":[],"sources":["../../../src/svelte/plugin.ts"],"sourcesContent":["import type { Component } from 'svelte'\n\nexport function createSveltePlugin<TComponentProps extends Record<string, any>>(\n name: string,\n component: Component<TComponentProps>,\n) {\n function Plugin(props?: TComponentProps) {\n return {\n name,\n component,\n props,\n }\n }\n\n function NoOpPlugin(props?: TComponentProps) {\n return {\n name,\n component: (() => {}) as unknown as Component<any>,\n props,\n }\n }\n\n return [Plugin, NoOpPlugin] as const\n}\n"],"mappings":";AAEA,SAAgB,mBACd,MACA,WACA;CACA,SAAS,OAAO,OAAyB;EACvC,OAAO;GACL;GACA;GACA;GACD;;CAGH,SAAS,WAAW,OAAyB;EAC3C,OAAO;GACL;GACA,kBAAkB;GAClB;GACD;;CAGH,OAAO,CAAC,QAAQ,WAAW"} |
| export {}; |
| export * from './panel' | ||
| export * from './plugin' |
| import { describe, expect, it, vi } from 'vitest' | ||
| import { createSveltePanel } from './panel' | ||
| // Minimal stand-in for a class-based devtools core. | ||
| function makeCoreClass() { | ||
| const mount = vi.fn() | ||
| const unmount = vi.fn() | ||
| let lastProps: unknown | ||
| class Core { | ||
| mount = mount | ||
| unmount = unmount | ||
| constructor(props: unknown) { | ||
| lastProps = props | ||
| } | ||
| } | ||
| return { Core, mount, unmount, getLastProps: () => lastProps } | ||
| } | ||
| describe('createSveltePanel', () => { | ||
| it('returns a [Panel, NoOpPanel] tuple of component functions', () => { | ||
| const { Core } = makeCoreClass() | ||
| const [Panel, NoOpPanel] = createSveltePanel(Core as any) | ||
| expect(typeof Panel).toBe('function') | ||
| expect(typeof NoOpPanel).toBe('function') | ||
| }) | ||
| it('Panel constructs the core with devtoolsProps, mounts it into a host element, and tears it down on destroy', () => { | ||
| const { Core, mount, unmount, getLastProps } = makeCoreClass() | ||
| const [Panel] = createSveltePanel(Core as any) | ||
| const anchor = document.createElement('span') | ||
| document.body.appendChild(anchor) | ||
| // Svelte 5 invokes a component with (anchor, props); the panel inserts its | ||
| // own host element before the anchor and mounts the core into it. | ||
| const instance = (Panel as any)(anchor, { | ||
| theme: 'dark', | ||
| devtoolsProps: { foo: 'bar' }, | ||
| }) | ||
| expect(getLastProps()).toEqual({ foo: 'bar' }) | ||
| expect(mount).toHaveBeenCalledTimes(1) | ||
| const call = mount.mock.calls[0]! | ||
| const mountedEl = call[0] as HTMLElement | ||
| const theme = call[1] | ||
| expect(mountedEl).toBeInstanceOf(HTMLElement) | ||
| expect(mountedEl.parentElement).toBe(document.body) | ||
| expect(theme).toBe('dark') | ||
| instance.destroy() | ||
| expect(unmount).toHaveBeenCalledTimes(1) | ||
| expect(mountedEl.parentElement).toBeNull() | ||
| anchor.remove() | ||
| }) | ||
| it('NoOpPanel never constructs or mounts the core class', () => { | ||
| const { Core, mount } = makeCoreClass() | ||
| const [, NoOpPanel] = createSveltePanel(Core as any) | ||
| const anchor = document.createElement('span') | ||
| ;(NoOpPanel as any)(anchor, { theme: 'dark' }) | ||
| expect(mount).not.toHaveBeenCalled() | ||
| }) | ||
| }) |
| import type { Component } from 'svelte' | ||
| export interface DevtoolsPanelProps { | ||
| theme?: 'dark' | 'light' | 'system' | ||
| } | ||
| export function createSveltePanel< | ||
| TComponentProps extends DevtoolsPanelProps, | ||
| TCoreDevtoolsClass extends { | ||
| mount: (el: HTMLElement, theme?: DevtoolsPanelProps['theme']) => void | ||
| unmount: () => void | ||
| }, | ||
| >( | ||
| CoreClass: new (props: TComponentProps) => TCoreDevtoolsClass, | ||
| ): [Component<any>, Component<any>] { | ||
| const Panel: Component<any> = ((anchor: any, props: any) => { | ||
| const el = document.createElement('div') | ||
| el.style.height = '100%' | ||
| anchor.before(el) | ||
| const instance = new CoreClass(props?.devtoolsProps as TComponentProps) | ||
| instance.mount(el, props?.theme) | ||
| return { | ||
| destroy() { | ||
| instance.unmount() | ||
| el.remove() | ||
| }, | ||
| } | ||
| }) as any | ||
| const NoOpPanel: Component<any> = (() => ({})) as any | ||
| return [Panel, NoOpPanel] | ||
| } |
| import { describe, expect, it, vi } from 'vitest' | ||
| import { createSveltePlugin } from './plugin' | ||
| // A stand-in for a real Svelte component — createSveltePlugin only stores the | ||
| // reference, it never mounts it, so an opaque function is sufficient here. | ||
| const FakeComponent = vi.fn() as any | ||
| describe('createSveltePlugin', () => { | ||
| it('returns a [Plugin, NoOpPlugin] tuple of factory functions', () => { | ||
| const result = createSveltePlugin('My Plugin', FakeComponent) | ||
| expect(result).toHaveLength(2) | ||
| const [Plugin, NoOpPlugin] = result | ||
| expect(typeof Plugin).toBe('function') | ||
| expect(typeof NoOpPlugin).toBe('function') | ||
| }) | ||
| it('Plugin() returns the name, the real component, and the forwarded props', () => { | ||
| const [Plugin] = createSveltePlugin('My Plugin', FakeComponent) | ||
| const props = { foo: 'bar' } | ||
| expect(Plugin(props)).toEqual({ | ||
| name: 'My Plugin', | ||
| component: FakeComponent, | ||
| props, | ||
| }) | ||
| }) | ||
| it('Plugin() works when no props are supplied', () => { | ||
| const [Plugin] = createSveltePlugin('My Plugin', FakeComponent) | ||
| expect(Plugin()).toEqual({ | ||
| name: 'My Plugin', | ||
| component: FakeComponent, | ||
| props: undefined, | ||
| }) | ||
| }) | ||
| it('NoOpPlugin() keeps the name but swaps in a no-op component (not the real one)', () => { | ||
| const [, NoOpPlugin] = createSveltePlugin('My Plugin', FakeComponent) | ||
| const noop = NoOpPlugin({ foo: 'bar' }) | ||
| expect(noop.name).toBe('My Plugin') | ||
| expect(noop.component).not.toBe(FakeComponent) | ||
| expect(typeof noop.component).toBe('function') | ||
| expect(noop.props).toEqual({ foo: 'bar' }) | ||
| }) | ||
| }) |
| import type { Component } from 'svelte' | ||
| export function createSveltePlugin<TComponentProps extends Record<string, any>>( | ||
| name: string, | ||
| component: Component<TComponentProps>, | ||
| ) { | ||
| function Plugin(props?: TComponentProps) { | ||
| return { | ||
| name, | ||
| component, | ||
| props, | ||
| } | ||
| } | ||
| function NoOpPlugin(props?: TComponentProps) { | ||
| return { | ||
| name, | ||
| component: (() => {}) as unknown as Component<any>, | ||
| props, | ||
| } | ||
| } | ||
| return [Plugin, NoOpPlugin] as const | ||
| } |
+17
-3
| { | ||
| "name": "@tanstack/devtools-utils", | ||
| "version": "0.5.1", | ||
| "version": "0.6.0", | ||
| "description": "TanStack Devtools utilities for creating your own devtools.", | ||
@@ -13,2 +13,5 @@ "author": "Tanner Linsley", | ||
| "homepage": "https://tanstack.com/devtools", | ||
| "bugs": { | ||
| "url": "https://github.com/TanStack/devtools/issues" | ||
| }, | ||
| "funding": { | ||
@@ -67,2 +70,8 @@ "type": "github", | ||
| }, | ||
| "./svelte": { | ||
| "import": { | ||
| "types": "./dist/svelte/esm/index.d.ts", | ||
| "default": "./dist/svelte/esm/index.js" | ||
| } | ||
| }, | ||
| "./angular": { | ||
@@ -84,6 +93,7 @@ "import": { | ||
| "devDependencies": { | ||
| "svelte": "^5.0.0", | ||
| "tsup": "^8.5.0", | ||
| "tsup-preset-solid": "^2.2.0", | ||
| "vite-plugin-solid": "^2.11.11", | ||
| "@tanstack/devtools": "^0.12.3" | ||
| "@tanstack/devtools": "^0.12.4" | ||
| }, | ||
@@ -96,2 +106,3 @@ "peerDependencies": { | ||
| "solid-js": ">=1.9.7", | ||
| "svelte": ">=5.0.0", | ||
| "vue": ">=3.2.0" | ||
@@ -115,2 +126,5 @@ }, | ||
| }, | ||
| "svelte": { | ||
| "optional": true | ||
| }, | ||
| "vue": { | ||
@@ -134,4 +148,4 @@ "optional": true | ||
| "test:build": "publint --strict", | ||
| "build": "vite build && vite build --config vite.config.preact.ts && vite build --config vite.config.vue.ts && vite build --config vite.config.solid-class.ts && vite build --config vite.config.angular.ts && tsup" | ||
| "build": "vite build && vite build --config vite.config.preact.ts && vite build --config vite.config.vue.ts && vite build --config vite.config.solid-class.ts && vite build --config vite.config.angular.ts && vite build --config vite.config.svelte.ts && tsup" | ||
| } | ||
| } |
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
126437
8.32%92
19.48%1792
13.2%0
-100%7
16.67%5
25%