Skip to content

Latest commit

 

History

History
91 lines (68 loc) · 9.76 KB

AGENTS.md

File metadata and controls

91 lines (68 loc) · 9.76 KB

AGENTS.md — FlexLayout

flexlayout-react: a multi-tab docking layout manager for React 18/19 (only peer dependency). MIT licensed, ESM-only. You render <Layout model={model} factory={factory} /> driven by a JSON model tree; the model is the single source of truth and the UI is derived from it. CSS themes ship as flexlayout-react/style/<theme>.css (alpha_light, alpha_dark, alpha_rounded, light, dark, underline, gray, rounded, aria, combined).

Definition of done

Before claiming a task complete, run and pass:

  • pnpm lint (eslint + generated-interface check)
  • pnpm typecheck
  • pnpm test (vitest, once)
  • pnpm playwright / pnpm playwright:ui for changes touching src/view/ or drag/drop behavior
  • If you changed JSON model interfaces in src/model/IJsonModel.ts: pnpm generate:interfaces (they are generated by scripts/generate-json-interfaces.ts; pnpm lint fails if they're stale)

The rules (violating these = bug)

  1. Never mutate model nodes directly. All changes go through model.doAction(Actions.*) — no setting attributes, adding/removing children, etc. GUI edits also flow through actions (intercept via the onAction prop).
  2. Only symbols exported from src/index.ts are public API. Treat everything else as internal.
  3. The factory must return a React node, dispatched on node.getComponent(). Handle per-window effects via node.getDocument() / node.getWindow() (see demo/monaco.tsx for the document-change remount pattern).
  4. Popout windows render into a different document — libraries bound to the main window/document may need remounting (see README "Popout Windows"). Component state is otherwise preserved when tabs move.
  5. TypeScript is strict; Prettier and ESLint are enforced. Don't add stray comments. Follow existing code style; make minimal changes.

Commands

  • pnpm dev — demo app dev server (vite with HMR; the demo imports src/ directly).
  • pnpm test — vitest single run (terminates). pnpm test:coverage for coverage.
  • pnpm playwright — Playwright e2e (headless chromium). pnpm playwright:ui for the UI runner.
  • pnpm lint — check:interfaces + eslint. pnpm format / pnpm format:check — prettier.
  • pnpm typecheck — tsc -p tsconfig.json.
  • pnpm build — full build: clean, generate interfaces, typecheck, demo, examples, css, lib, types, typedoc.
  • pnpm dev:examples / pnpm build:examples — run/build the examples/ app (multi-page vite, root ./examples/, one URL per example). examples/ is covered by lint/typecheck/format.

Mental model (60 seconds)

  • Model tree = serialized JSON via Model.fromJson(json) / model.toJson().
  • Node types in JSON: row (children: rows + tabsets; orientation alternates per nesting level), tabset (children: tabs), tab (holds the component), border (only under the top-level borders array: top/bottom/left/right).
  • Node classes: RowNode, TabSetNode, TabNode, BorderNode, all extending Node (id, attributes, listeners).
  • Attribute inheritance: node attributes default from global.* prefixed by node type — tabXxx, tabSetXxx, borderXxx (e.g. global.tabEnableClose vs per-tab enableClose). See src/model/Attributes.ts.
  • Tabsets are created/destroyed dynamically as tabs move (unless enableDeleteWhenEmpty: false).
  • <Layout> fills its parent container, which must be position: absolute or relative.

API patterns

Full signatures are in src/model/Actions.ts; commonly needed ones:

  • Add a tab: Actions.addTab(jsonTabNode, toTabsetId, DockLocation.CENTER, index, select?) — index -1 = append. Actions.addNode has the same signature.
  • Move/remove/rename: Actions.moveNode, deleteTab, deleteTabset, renameTab.
  • Attributes at runtime: Actions.updateNodeAttributes(nodeId, attrs); model-level Actions.updateModelAttributes(attrs).
  • Tabsets/borders: Actions.maximizeToggle(tabsetId, layoutId?), Actions.setBorderType(borderId, "split" | "overlay"), Actions.adjustWeights, Actions.adjustBorderSplit.
  • Popouts/floating: Actions.popoutTab(id, type) / popoutTabset(id, type) with type: "window" | "float"; createPopout, createSubLayout, moveFloat, closePopout, movePopoutToFront.
  • Undo/redo: useUndo(initialModelOrFactory) hook (src/view/useUndo.ts) — owns model state, snapshots before mutations (an entire drag = one step), swaps models via Model.fromJson to preserve mounted content.
  • Layout ref API (ILayoutApi via useRef<Layout>): addTabToTabSet(id, jsonTabNode), redraw(), etc.
  • Node events (node.setEventListener): resize, close, visibility, save (persist node.getConfig() before serialization).

Where things live (feature → file)

  • Public <Layout> component + ILayoutProps/ILayoutApi → src/view/Layout.tsx; callback/option types (ITabRenderValues, ITabSetRenderValues, IIcons, IKeyMap, defaultKeyMap) → src/view/layout/LayoutTypes.ts.
  • Rendering engine → src/view/layout/LayoutController.tsx (LayoutController) + src/view/layout/LayoutInternal.tsx (view wrapper, edge drop rects).
  • Docking / drag-and-drop / resizing → src/view/layout/DragDropManager.tsx, src/view/Splitter.tsx, src/model/DropInfo.ts, IDraggable.ts, IDropTarget.ts.
  • Popout windows → src/view/PopoutWindow.tsx (React portals; requires a popout.html host page); floating panels → src/view/FloatWindow.tsx, src/view/layout/FloatingWindowContainer.tsx.
  • Border tabsets → src/view/BorderTabSet.tsx, BorderTab.tsx, BorderButton.tsx, src/view/layout/BorderContainer.tsx; overlay behavior → src/view/Overlay.tsx.
  • Tabs/tabstrip → src/view/Tab.tsx, TabButton.tsx, TabLayout.tsx, TabContentRenderer.tsx, TabOverflowHook.tsx, DragTabButton.tsx, TabButtonStamp.tsx.
  • Customization hooks → onRenderTab / onRenderTabSet Layout props; custom icons via icons prop and src/view/Icons.tsx; class remapping via classNameMapper and the CLASSES enum in src/CSSClassNames.ts.
  • Context/overflow menus → src/view/PopupMenu.tsx, src/view/ContextMenuBuilder.tsx (showPopupMenu).
  • Accessibility / keyboard → keyMap prop + defaultKeyMap; ARIA roles in view components and themes.
  • i18n → i18nTranslator prop on <Layout>; default English text in I18nLabelDefaults; keys in src/view/I18nLabel.ts.
  • Data model (no React) → src/model/: Model.ts (serialization), Node.ts + subclasses, Actions.ts, IJsonModel.ts, Attributes.ts, BorderSet.ts, ModelLayout.ts (popout/float sub-layout), Rect.ts, Orientation.ts, DockLocation.ts.

Examples

The examples/ directory is a multi-page vite app (pnpm dev:examples), one self-contained example per feature at its own URL (see the landing page examples/index.html for the full list). Each example is a single .tsx importing only the public API and the combined stylesheet — they are the canonical reference for common features (borders, tab/tabset rendering, context menus, min sizes, pinned tabs, sticky buttons, tab wrapping, external drag, many tabs, undo/redo, localstorage persistence, empty-tabset placeholders, popouts, theming). To add one: create examples/<name>/{index.html, Name.tsx}, register it in examples/vite.config.examples.ts and link it from the landing page. All examples are covered by lint/typecheck/format.

Theming

  • All themable values are CSS custom properties defined in style/_themes.scss (--fl-color-*, --fl-font-*, --fl-splitter-size, --fl-tab-button-radius, etc.), each as var(--flexlayout-<name>, <theme default>). Override the global --flexlayout-<name> variables (on :root, a container, or the layout root) to restyle every theme/layout — including floats, sublayouts and popouts — without an scss rebuild.
  • Full themes: import one theme css, or combined.css for runtime switching via a flexlayout__theme_<name> class on the container.
  • Scss sources/mixins: style/_themes.scss, style/_base.scss; compiled css is committed alongside.

Testing

  • tests/ — vitest unit tests (model, actions, serialization, useUndo, view components under tests/view/).
  • tests-playwright/ — Playwright e2e; tests-playwright/helpers.ts has ready-made helpers. Layout fixtures live in demo/public/layouts/*.layout and are loaded by the demo app the e2e tests drive.
  • e2e selectors: every rendered element carries data-layout-path, e.g. /r1/ts0 (row 1, tabset 0), /border/left, /ts0/tb1 (tab button 1), /ts0/g0 (group pill), /ts0/t0 (tab panel 0), /s0 (splitter), /button/max, /popup-menu. Paths reflect the current tree, so indices shift after moves.

Docs & reference

  • README.md — authoritative usage guide: model JSON examples, attribute inheritance, themes, popout windows (incl. CSP/secure-env caveats), accessibility tables, data-layout-path table.
  • typedoc/ — generated API docs (pnpm doc).
  • CHANGELOG.md — release history.
  • demo/ — reference app and working patterns: App.tsx, plus showcases monaco.tsx (popout remounting), aggrid.tsx, MUIDataGrid.tsx, chart.tsx, openlayer.tsx, terminal.tsx, SimpleForm.tsx, ModelExplorer.tsx, JsonView.tsx, AttributeEditor.tsx, ThemePanel.tsx, ActionLog.tsx.
  • examples/ — runnable self-contained example apps (one per feature), each a multi-page vite entry at examples/<name>/{index.html, X.tsx} (e.g. basic, borders, context-menu, undo-redo, pinned-tabs). They import only the public API (../../src/index) and the theme styles (../../style/combined.scss), share examples/Header.tsx (title + GitHub source link), and are registered in examples/vite.config.examples.ts. These are the canonical "how do I use feature X" references; add new examples there rather than demo/.