Create a new framework adapter library for @toolbox-web/grid, with a matching demo app. Covers library scaffolding, adapter implementation, demo setup, workspace wiring, and e2e parity testing.
インストール
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
Create a new framework adapter library for @toolbox-web/grid, with a matching demo app. Covers library scaffolding, adapter implementation, demo setup, workspace wiring, and e2e parity testing.
argument-hint
<framework-name>
Create a New Framework Adapter
This skill guides you through creating a complete framework adapter for @toolbox-web/grid — a library (@toolbox-web/grid-{framework}), a demo app (demo-{framework}), and all workspace wiring needed to integrate with the monorepo.
Prerequisite: Read the new-adapter-feature skill to understand how features work within existing adapters. This skill covers creating an adapter from scratch.
Parity Mandate (non-negotiable)
A new adapter is not "another adapter" — it is the same product wearing a fourth (or fifth) façade. It MUST ship at complete parity with the existing adapters from day one:
Identical public API. Same exported names, same config types (same canonical names — no framework prefix), same feature props, same events, same programmatic-access surface (useGrid/injectGrid equivalent). Every capability the other adapters expose, this one exposes too. A partial adapter is not shippable.
As close to the same DX as the framework allows. Match the usage patterns of the existing adapters; deviate ONLY where the framework's idioms force it (slots vs render props vs structural directives, <DataGrid> vs <TbwGrid>, emits vs outputs). Idiomatic differences are fine; capability or API-name gaps are not.
Adapters facilitate, they do not add capabilities. The adapter's only job is bridging framework components into the HTMLElement slots the grid already exposes, plus idiomatic event/config ergonomics. It MUST respect the core ↔ feature boundary: the adapter core never runtime-references feature behaviour; feature wiring lives in features/<name> secondary entries via registered bridges/hooks. Keep DX-only extras minimal and symmetric with the other adapters.
Parity is enforced by tests, not honour. The mandatory demo app (Step 3) is wired into the cross-framework e2e suite (Step 4), which compares the new demo against the vanilla/React/Angular/Vue baselines for visual, structural, renderer, editor, master-detail, responsive, and functional parity. The demo and its e2e wiring are NOT optional — they are how parity is proven. A new adapter without a passing parity demo is incomplete.
How to Use This Skill
The skill is long because an adapter has many moving parts. To keep cognitive load manageable, work through the six numbered steps below in order. Do not jump ahead — each step depends on artifacts produced by the previous one.
Step
Section
Mandatory?
Output
1
Scaffold the Library
Mandatory
libs/grid-{framework}/ skeleton + config files
2
Implement the Adapter
Mandatory
Working FrameworkAdapter + wrapper component
3
Scaffold the Demo App
Mandatory
demos/employee-management/{framework}/
4
Workspace Wiring
Mandatory
tsconfig paths, resolve aliases, e2e DEMOS map
5
Testing
Mandatory
Library unit tests + e2e parity tests passing
6
Documentation
Optional (recommended)
READMEs, TypeDoc, llms.txt updates
Use the Checklist at the end of the file as the single source of truth for completion. The reference sections below the checklist (Architecture Reference, etc.) are background material — read them once before starting Step 1, then refer back as needed.
What You're Building
An adapter bridges the gap between the vanilla <tbw-grid> web component and a specific framework's component model. It provides:
A wrapper component — idiomatic API surface (props, events, slots/children) for the framework
Feature modules — tree-shakeable plugin registration via side-effect imports
A demo app — full employee management demo matching all other framework demos visually and functionally
Guiding principles (same as the new-adapter-feature skill): An adapter only facilitates framework rendering niceties — it lets users pass framework components where the grid expects an HTMLElement, plus idiomatic event/config ergonomics. It does NOT add grid capabilities (those belong in core or a plugin), and it MUST respect the core ↔ feature boundary: the adapter core never runtime-references feature behaviour, feature wiring lives in features/<name> secondary entries. Aim for full parity with the existing adapters in both API and usage — differences must be idiomatic (slots vs render props vs directives), never capability or API-name gaps.
Architecture Reference
The FrameworkAdapter Interface
Every adapter must implement this interface (from @toolbox-web/grid):
exportinterfaceFrameworkAdapter {
/** Can this adapter handle the given light DOM element? */canHandle(element: HTMLElement): boolean;
/** Create a cell renderer from a light DOM column element */
createRenderer<TRow, TValue>(element: HTMLElement): ColumnViewRenderer<TRow, TValue>;
/** Create a cell editor from a light DOM column element (undefined = use grid's built-in editor) */
createEditor<TRow, TValue>(element: HTMLElement): ColumnEditorSpec<TRow, TValue> | undefined;
/** Create a tool panel renderer from a light DOM element (optional) */
createToolPanelRenderer?(element: HTMLElement): ((container: HTMLElement) =>void | (() =>void)) | undefined;
/** Get type-level defaults from an app-level registry (optional) */
getTypeDefault?<TRow>(type: string): TypeDefault<TRow> | undefined;
/** Properly destroy cached views/components for a cell being released (optional) */
releaseCell?(cellEl: HTMLElement): void;
/** Unmount a framework container and free its resources (optional) */
unmount?(container: HTMLElement): void;
}
How Adapters Register
The grid has a static method for adapter registration:
import { GridElement } from'@toolbox-web/grid';
// Register once at app bootstrapGridElement.registerAdapter(newMyFrameworkAdapter(/* framework deps */));
Existing adapters auto-register when their wrapper component/directive initializes, so users don't call this manually.
Existing Adapter Patterns
Framework
Wrapper Component
Rendering
Column Registry
Component Mount/Destroy
Angular
Grid directive
TemplateRef + ViewContainerRef
Structural directives register templates
createComponent() / viewRef.destroy()
React
<DataGrid> component
ReactDOM.createRoot() per cell
WeakMap<HTMLElement, ColumnRegistry> + field fallback
root.render() / root.unmount()
Vue
<TbwGrid> SFC
createApp() + createVNode()
WeakMap<HTMLElement, ColumnRegistry> + field fallback
app.mount() / app.unmount()
The React and Vue patterns are nearly identical — use them as your primary reference for a new adapter. Angular is unique due to its DI system and ng-packagr build.
Feature Registry Pattern (Tree-Shakeable Plugins)
All adapters share a common pattern for tree-shakeable plugin loading:
feature-registry.ts — A Map<string, PluginFactory> where features self-register
Feature modules (features/selection.ts, etc.) — Import the grid plugin, call registerFeature('name', factoryFn), and export a composable/hook
// features/selection.ts (side-effect module)
import { SelectionPlugin } from '@toolbox-web/grid/plugins/selection';
import { registerFeature } from '../lib/feature-registry';
registerFeature('selection', (config) => {
if (config === 'cell' || config === 'row' || config === 'range') {
return new SelectionPlugin({ mode: config });
}
return new SelectionPlugin(typeof config === 'object' ? config : {});
});
// Also export a composable/hook for programmatic access
export function useGridSelection() { /* ... */ }
Users then import features as side effects:
import'@toolbox-web/grid-{framework}/features/selection';
// The 'selection' prop now works on the wrapper component
There are 26 feature modules to implement (matching React/Vue — run ls libs/grid-react/src/features/*.ts | grep -v spec | grep -v index to confirm the current set):
clipboard, column-virtualization, context-menu, editing, export, filtering, grouping-columns, grouping-rows, master-detail, multi-sort, pinned-columns, pinned-rows, pivot, print, reorder-columns, reorder-rows, responsive, row-drag-drop, selection, server-side, shell, sticky-rows, tooltip, tree, undo-redo, visibility, plus a barrel index.ts that re-exports all.
Step 1: Scaffold the Library
Use the Nx MCP server tools to create the library project, or create it manually:
{"name":"grid-{framework}","$schema":"../../node_modules/nx/schemas/project-schema.json","sourceRoot":"libs/grid-{framework}/src","projectType":"library","tags":["type:lib","scope:grid"],"targets":{"build":{"executor":"@nx/vite:build","outputs":["{options.outputPath}"],"defaultConfiguration":"production","dependsOn":[{"projects":["grid"],"target":"build"}],"options":{"outputPath":"dist/libs/grid-{framework}","emptyOutDir":true,},"configurations":{"development":{"mode":"development"},"production":{"mode":"production"},},},"lint":{"executor":"@nx/eslint:lint","outputs":["{options.outputFile}"],"options":{"lintFilePatterns":["libs/grid-{framework}/**/*.{ts,tsx,vue,svelte}"],// ↑ Adjust extensions for your framework},},"typedoc":{"executor":"nx:run-commands","options":{"command":"npx typedoc --options libs/grid-{framework}/typedoc.json",},},},}
package.json
{"name":"@toolbox-web/grid-{framework}","version":"0.1.0","description":"{Framework} adapter for @toolbox-web/grid data grid component","type":"module","main":"./index.js","module":"./index.js","types":"./index.d.ts","typesVersions":{"*":{"features":["features/index.d.ts"],"features/*":["features/*.d.ts"],},},"exports":{"./package.json":"./package.json",".":{"types":"./index.d.ts","import":"./index.js","default":"./index.js",},"./features":{"types":"./features/index.d.ts","import":"./features/index.js","default":"./features/index.js",},"./features/*":{"types":"./features/*.d.ts","import":"./features/*.js","default":"./features/*.js",},},"peerDependencies":{"{framework-package}":">=X.Y.Z","@toolbox-web/grid":">=1.0.0",},"sideEffects":["./features/*.js"],}
vite.config.mts
Copy libs/grid-react/vite.config.mts (or libs/grid-vue/vite.config.mts) and adapt the framework-specific bits. The required structural elements you must keep are listed below; everything else (plugin choice, framework externals, JSX/SFC handling) is framework-specific.
Plugins: a framework-specific Vite plugin (e.g. @vitejs/plugin-react, @vitejs/plugin-vue) plus vite-plugin-dts configured with pathsToAliases: false.
Library entry map: one entry per public surface — index, features/index, and one entry per feature module (26 total).
Rollup externals: the framework runtime package(s) and every @toolbox-web/grid subpath.
Test config: self-referencing aliases for @toolbox-web/grid-{framework}/features/* and grid dist aliases for @toolbox-web/grid / @toolbox-web/grid/all / @toolbox-web/grid/plugins/*.
{"extends":"./tsconfig.json","compilerOptions":{"outDir":"../../dist/libs/grid-{framework}","types":["vite/client"],// Add framework-specific compiler options as needed// e.g. "jsx": "react-jsx" for React, or "jsx": "preserve" for Vue},"include":["src/**/*.ts","src/**/*.{ext}"],// ↑ Add framework extensions: .tsx, .vue, .svelte, etc."exclude":["src/**/*.spec.ts","src/**/*.test.ts"],"references":[{"path":"../grid/tsconfig.lib.json"}],}
Step 2: Implement the Adapter
Core Adapter ({framework}-grid-adapter.ts)
Start from the React or Vue adapter as a template. The key responsibilities:
Column Registry — WeakMap<HTMLElement, ColumnRegistry> keyed on column elements, with a Map<string, ColumnRegistry> field-name fallback (handles framework component re-creation where DOM refs change)
canHandle() — Return true when a column element has framework-specific markers. For React/Vue this always returns true when the adapter is registered (they check for registered renderers). For Angular, it checks for template refs.
createRenderer() — Look up the renderer function from the column registry, return a ColumnViewRenderer that:
Mounts the framework component into the cell element
createEditor() — Similar to renderer but wraps the framework component with commit/cancel callbacks from ColumnEditorContext
releaseCell() — Properly destroy framework component instances attached to a cell. This is critical for memory leak prevention.
unmount() — Destroy a framework container (used by MasterDetailPlugin, tool panels, etc.)
Wrapper Component (DataGrid.{ext})
The main wrapper component should:
Accept the same props as <tbw-grid> (rows, gridConfig, columns, fitMode)
Accept feature props (e.g., selection, editing, filtering) — look them up in the feature registry to create plugin instances
Accept event props (e.g., onCellClick, onSelectionChange) — forward to grid.on() on the grid element (returns unsubscribe functions for cleanup)
Create the underlying <tbw-grid> element and register the adapter on it
Provide a context/injection mechanism so child components and composables can access the grid element (React: Context, Vue: provide/inject, Svelte: setContext/getContext, etc.)
Process GridColumn child components that register renderers/editors in the column registry
Composables / Hooks
Implement at minimum:
useGrid() — Returns the grid element ref for programmatic API access
useGridEvent(eventName, handler) — Type-safe event subscription with automatic cleanup on component destroy
Port assignment: Existing ports are vanilla=4000, vue=4100, angular=4200, react=4300, docs=4400, docs-e2e=4450. Pick the next available (e.g., 4500 for Svelte, 4600 for Solid).
The demo must look and behave identically to the vanilla, React, Angular, and Vue demos. Specifically:
Same shared data — Import Employee type and generateEmployees() from @demo/shared
Same shared styles — Import @demo/shared/demo-styles.css
Same shell layout — Header with title "Employee Management System ({Framework})", matching CSS classes (.tbw-shell-header, .tbw-shell-title)
Same column configuration — Same columns, same types, same widths, same order
Same custom renderers — StatusBadge (colored badge), RatingDisplay (stars), TopPerformerBadge (star icon). Use the same CSS classes as other demos (.status-badge, .rating-display, .top-performer-star) so e2e selectors work
Same custom editors — Status dropdown, star rating, bonus slider, date picker
Same plugins enabled — Selection, filtering, sorting, editing, master-detail, responsive, etc.
Same master-detail panel — Employee details with projects and reviews (import shared data structure)
Same tool panel — Column stats sidebar
Same responsive card layout — Mobile card view with the same class (.responsive-employee-card)
Step 4: Workspace Wiring
4.1: Update tsconfig.base.json
Add path mappings for the new adapter:
// In "compilerOptions.paths":"@toolbox-web/grid-{framework}":["dist/libs/grid-{framework}/index.d.ts"],"@toolbox-web/grid-{framework}/features/*":["dist/libs/grid-{framework}/features/*.d.ts"]
4.2: Update resolve-aliases.ts
Add a new option to getResolveAliases() in demos/employee-management/shared/resolve-aliases.ts:
Then add both dist-mode and source-mode aliases following the exact same pattern as the React/Vue blocks. Source mode should point to libs/grid-{framework}/src/..., dist mode to dist/libs/grid-{framework}/....
4.3: Update E2E Tests
Add the new demo to the DEMOS map in e2e/tests/utils.ts:
libs/grid-{framework}/README.md — Installation, quick start, API reference
demos/employee-management/{framework}/README.md — How to run the demo
TypeDoc config — libs/grid-{framework}/typedoc.json following existing pattern
Update llms.txt and llms-full.txt — Add the new adapter to the library listing
Docs site (optional) — The Astro docs site already covers the grid; framework-specific demo pages can be added to apps/docs/
Checklist
Parity (gate — the adapter is incomplete until all are true)
Public API matches the existing adapters exactly: same exported names, same config types (same canonical names, no framework prefix), same feature props, same events, same programmatic-access surface
DX mirrors the existing adapters as closely as the framework allows; any deviation is idiomatic (forced by the framework), not a capability or API-name gap
No grid behaviour added in the adapter; core ↔ feature boundary respected (adapter core never runtime-references feature behaviour)
Parity demo built and wired into the cross-framework e2e suite, passing all parity categories
Library
libs/grid-{framework}/ created with all config files