Skip to content
This repository was archived by the owner on Sep 12, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
136 changes: 136 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# AGENTS.md

## What this repo is

Source for **fabricjs.com** — the marketing site, docs, demo gallery, and
API reference for [Fabric.js](https://github.com/fabricjs/fabric.js) (an
HTML5 canvas library). Built with [Astro](https://astro.build) using the
[Starlight](https://starlight.astro.build) docs theme, plus React islands
for interactive bits. Deployed to GitHub Pages.

This is a documentation/marketing site, **not** the Fabric.js library
itself — the library source lives in the `fabric.js` git submodule and is
only used here to generate the API reference via TypeDoc.

## Repo structure

```
/
├── fabric.js/ git submodule → fabricjs/fabric.js (library source)
│ used only as TypeDoc input for the API reference;
│ not built/bundled into the site otherwise
├── src/
│ ├── pages/ Astro routes: index, demos, resources, team, 404
│ │ └── demos/[...slug].astro renders each entry from the `demo` collection
│ ├── content/
│ │ ├── config.ts defines the `demo` and `docs` content collections
│ │ ├── demo/ one folder per interactive demo:
│ │ │ index.mdx (frontmatter + <CodeEditor>), code.js, thumbnail
│ │ └── docs/ Starlight-schema docs (guides + API reference)
│ │ ├── docs/ hand-written guides (getting started, cropping-images, etc.)
│ │ └── api/ TypeDoc-generated API reference (auto-generated, do not hand-edit)
│ ├── components/ Astro/React components (CodeEditor, Header, Card, TeamMember, ...)
│ ├── layouts/ Layout.astro + global CSS
│ ├── css/ additional stylesheets
│ └── utils/
├── public/ static assets (favicon, images, etc.)
├── astro.config.mjs Starlight sidebar, TypeDoc integration, redirects
└── .github/workflows/ build.yml (PR check), deploy.yml (push to main → GitHub Pages)
```

## Docs vs demos — which one to write

The two sections have different jobs, and content written for one does not
belong in the other.

**Demos** (`src/content/demo/`) show *what the library can do*. They are a
showcase: the visual result is the point, the code is there so a reader can
see it is achievable and poke at it. Keep the snippet as short as the effect
allows — no scaffolding, no hand-rolled reimplementations of something the
library already exports. Prose is minimal, just enough to say what to try
(what to drag, what to double-click) and what each thing on the canvas is.

**Docs** (`src/content/docs/docs/`) explain *how it works, and why*. They are
allowed — encouraged — to go deep and get technical: quote the library's own
source when it teaches something, walk through a helper line by line, name the
internal functions involved, explain which pieces are exported and how to
recombine them, and be explicit about limitations and caveats. A guide's
interactive example should stay barebone, precisely so the surrounding prose
carries the explanation rather than the sample code.

Practical consequences when adding content:

- A long code listing is fine in a guide if it is *explanatory* (the
implementation of a helper being taught). It is not fine as example
scaffolding in either place.
- Explaining an extension means saying it exists, what it packages, how to
assign it, and that it is a base to build on — extensions export their
individual handlers precisely so readers can recompose them.
- Don't duplicate: a guide can link to a demo for the visual showcase, and a
demo can link to a guide for the depth.

## Key mechanics

- **Content collections** (`src/content/config.ts`): `demo` (interactive
code demos, schema: title/tags/thumbnail/description) and `docs`
(Starlight's schema, covers both guides and API reference pages).
- **API reference generation**: `astro.config.mjs` runs
`starlight-typedoc` against `./fabric.js/fabric.ts`, but only if the
`fabric.js` submodule is checked out locally (`fs.existsSync` guard) —
this lets `npm run dev` work without the submodule, skipping API docs.
- **Demos**: each demo is a folder under `src/content/demo/<name>/` with
an `index.mdx` that imports `code.js` as raw text and renders it inside
`<CodeEditor>` (a React island using CodeMirror) next to a live
`<canvas>`. Demo routing is handled by `src/pages/demos/[...slug].astro`.
A `thumbnail` image must exist in the demo folder — the gallery resolves
it via `import.meta.glob`, so a missing file fails the build.
- **Demo code scope**: `code.js` is `eval`'d, so it cannot use `import`.
`CodeEditor` injects a preamble exposing `fabric`, `extensions`
(helpers from `fabric/extensions`), and `canvasEl`. The snippet must end
up with a `canvas` variable — the editor stores it on `window.canvasesId`
for disposal between runs.
Note `CodeEditor` deliberately imports `fabric/extensions` with **named
imports** rather than `import * as`: a namespace object assigned to
`window` keeps every export alive and drags the westures gesture
integration into the client bundle. Add new helpers to both the import
list and the `window.fabricExtensions` object.
- **Sidebar**: configured manually in `astro.config.mjs` with
`autogenerate` pointing at `src/content/docs/docs` and
`src/content/docs/api`.

## Working with the submodule

The `fabric.js` submodule must be checked out for the API reference to
build. If it's missing/out of date:

```bash
git submodule update --init --recursive
```

Without it, `npm run dev`/`build` still work but the `Api` sidebar
section won't be generated.

## Commands

| Command | Action |
| ------------------ | -------------------------------------------------------- |
| `npm install` | install dependencies |
| `npm run dev` | start local dev server at `localhost:4321` |
| `npm run build` | `astro check` (typecheck) then `astro build` → `./dist/` |
| `npm run preview` | preview the production build locally |
| `npm run astro ...` | run Astro CLI commands (e.g. `astro add`) |

CI (`.github/workflows/build.yml`, `deploy.yml`) checks out the
submodule, runs the same Astro build on PRs, and deploys `main` to
GitHub Pages.

## Conventions

- Formatting via Prettier (`.prettierrc.json`: semicolons, single
quotes, 2-space tabs). `.prettierignore` excludes `fabric.js` and a
few content dirs.
- `src/content/docs/api/**` is generated output from TypeDoc — treat it
as build output, not hand-authored content (regenerated whenever the
submodule/docs build runs).
- Guides live under `src/content/docs/docs/**` as `.md`/`.mdx` with
Starlight frontmatter (title, description, etc.).
1 change: 1 addition & 0 deletions CLAUDE.md
2 changes: 1 addition & 1 deletion fabric.js
Submodule fabric.js updated 192 files
18 changes: 13 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"astro": "^5.17.3",
"canvas": "^3.2.0",
"codemirror": "^6.0.1",
"fabric": "^7.0.0-rc1",
"fabric": "^7.4.0",
"jsdom": "^26.1.0",
"prettier": "^3.4.2",
"react": "19.2",
Expand All @@ -40,6 +40,7 @@
"packageManager": "npm@10.8.1+sha512.0e9d42e92bd2318408ed81eaff2da5f78baf23ee7d12a6eed44a6e2901d0f29d7ab715d1b918ade601f72e769a824d9a5c322383f22bbbda5dd396e79de2a077",
"devDependencies": {
"typedoc": "^0.28.11",
"typedoc-plugin-markdown": "^4.7.0"
"typedoc-plugin-markdown": "^4.7.0",
"westures": "^1.1.1"
}
}
42 changes: 42 additions & 0 deletions src/components/CodeEditor/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,28 @@ import {
import { StateField, EditorState } from '@codemirror/state';
import { EditorView, basicSetup } from 'codemirror';
import * as fabric from 'fabric';
// Named imports, not `import * as`: a namespace object assigned to `window`
// keeps every export alive, which drags the westures gesture integration
// (and its `westures` dependency) into the client bundle. Listing the
// helpers explicitly lets rollup drop the ones no demo uses.
import {
AligningGuidelines,
createImageCroppingControls,
createImageResizeControlsWithScaleToCover,
createLinearGradientControls,
enterCropMode,
changeCropX,
changeCropY,
changeCropWidth,
changeCropHeight,
changeWidthAndScaleToCover,
changeHeightAndScaleToCover,
withFlip,
installGradientUpdater,
installOriginWrapperUpdater,
gradientUpdaterWrapper,
originUpdaterWrapper,
} from 'fabric/extensions';
import { debounce } from '../../utils/debounce';
import { useCallback, useEffect, useRef, useState } from 'react';
import { espresso } from 'thememirror';
Expand All @@ -23,6 +45,7 @@ export const CodeEditor = ({ code: codeProp, children, canvasId, autoRun = true,
}
const preamble = [
`const fabric = window.fabric;`,
`const extensions = window.fabricExtensions;`,
`const canvasEl = document.getElementById('${canvasId}');`,
];
// when click 'run me' button, makes code string change to trigger 'setCode' function.
Expand All @@ -43,6 +66,25 @@ export const CodeEditor = ({ code: codeProp, children, canvasId, autoRun = true,
// and add to window canvases for cleanup/restart
// seems hacky, maybe it is, but it allow us to use the imported version
window.fabric = fabric;
// exposed to demo code as `extensions` (see the eval preamble below)
window.fabricExtensions = {
AligningGuidelines,
createImageCroppingControls,
createImageResizeControlsWithScaleToCover,
createLinearGradientControls,
enterCropMode,
changeCropX,
changeCropY,
changeCropWidth,
changeCropHeight,
changeWidthAndScaleToCover,
changeHeightAndScaleToCover,
withFlip,
installGradientUpdater,
installOriginWrapperUpdater,
gradientUpdaterWrapper,
originUpdaterWrapper,
};
window.canvasesId = window.canvasesId || {};

// https://github.com/codemirror/dev/issues/44#issuecomment-789093799
Expand Down
10 changes: 0 additions & 10 deletions src/components/Companies/Companies.astro
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,6 @@ const logos = [
description: 'Zaparoo',
logo: '/logos/zaparoo.webp',
},
{
link: 'https://go.warp.dev/fabric',
description: 'Warp, built for coding with multiple AI agents',
logo: '/logos/warp.webp',
},
{
link: 'https://netrouting.com/',
description: 'Netrouting has created an interactive web application using Fabric.js for the canvas editable objects and full-featured interface. The application is hosted on the reliable hosting infrastructure.',
logo: '/logos/netrouting-square.webp',
},
{
link: 'https://automatio.ai',
description: 'Automatio AI',
Expand Down
2 changes: 1 addition & 1 deletion src/components/LatestRelease/LatestRelease.astro
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export interface Props {
subtitle?: string;
}
const {
version = '7.0.0',
version = '7.4.0',
url = 'https://github.com/fabricjs/fabric.js/releases',
subtitle = 'Download latest release from GitHub',
} = Astro.props as Props;
Expand Down
84 changes: 84 additions & 0 deletions src/content/demo/cropping-controls/code.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
const canvas = new fabric.Canvas(canvasEl, {
backgroundColor: '#f5f5f5',
});

// Both factories come from `fabric/extensions`:
//
// import {
// createImageCroppingControls,
// createImageResizeControlsWithScaleToCover,
// } from 'fabric/extensions';
//
// In this editor they are pre-imported for you as `extensions`.
const { createImageCroppingControls, createImageResizeControlsWithScaleToCover } =
extensions;

const shared = {
scaleX: 0.45,
scaleY: 0.45,
cropX: 80,
cropY: 80,
width: 400,
height: 400,
cornerColor: '#ffffff',
borderScaleFactor: 2,
transparentCorners: false,
padding: 0,
};

const label = (text, left, top, fill) =>
new fabric.FabricText(text, {
left,
top,
fontSize: 14,
fontFamily: 'sans-serif',
fill,
originX: 'center',
selectable: false,
evented: false,
});

const imageUrl = '/assets/dragon.jpg';

Promise.all([
fabric.FabricImage.fromURL(imageUrl),
fabric.FabricImage.fromURL(imageUrl),
]).then(([cropped, resized]) => {
// Replace the whole control set: every handle now moves the crop window
// instead of scaling the object.
cropped.controls = createImageCroppingControls();
cropped.set({
...shared,
left: 210,
top: 250,
cornerStyle: 'circle',
cornerStrokeColor: '#7c3aed',
borderColor: '#7c3aed',
});

// Keep the default controls and swap in only the four side handles.
const edge = createImageResizeControlsWithScaleToCover();
Object.assign(resized.controls, {
ml: edge.mle,
mr: edge.mre,
mt: edge.mte,
mb: edge.mbe,
});
resized.set({
...shared,
left: 590,
top: 250,
cornerStrokeColor: '#0284c7',
borderColor: '#0284c7',
});

canvas.add(
cropped,
resized,
label('createImageCroppingControls()', 210, 380, '#7c3aed'),
label('drag any handle to move the crop window', 210, 404, '#64748b'),
label('createImageResizeControlsWithScaleToCover()', 590, 380, '#0284c7'),
label('drag a side handle to resize, corners still scale', 590, 404, '#64748b'),
);
canvas.setActiveObject(cropped);
});
Loading
Loading