diff --git a/demo/propagator-toolbar.html b/demo/propagator-toolbar.html
deleted file mode 100644
index 41e7da0..0000000
--- a/demo/propagator-toolbar.html
+++ /dev/null
@@ -1,46 +0,0 @@
-
-
-
-
-
- Toolbar Demo
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/demo/toolset.html b/demo/toolset.html
new file mode 100644
index 0000000..c0df420
--- /dev/null
+++ b/demo/toolset.html
@@ -0,0 +1,52 @@
+
+
+
+
+
+ Toolbar Demo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/folk-toolbar.ts b/src/folk-toolbar.ts
deleted file mode 100644
index f964a03..0000000
--- a/src/folk-toolbar.ts
+++ /dev/null
@@ -1,126 +0,0 @@
-import { css } from './common/tags.ts';
-
-const styles = css`
- :host {
- position: fixed;
- bottom: 16px;
- left: 50%;
- transform: translateX(-50%);
- background: white;
- padding: 8px;
- border-radius: 8px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
- display: flex;
- gap: 8px;
- }
-
- button {
- padding: 8px 16px;
- border-radius: 4px;
- border: 1px solid #ccc;
- background: white;
- cursor: pointer;
- }
-
- button.active {
- background: #eee;
- }
-`;
-
-export class FolkToolbar extends HTMLElement {
- static tagName = 'folk-toolbar';
-
- static define() {
- if (customElements.get(this.tagName)) return;
- customElements.define(this.tagName, this);
- }
-
- #mode: 'idle' | 'connecting' = 'idle';
- #sourceElement: Element | null = null;
- #connectBtn: HTMLButtonElement;
-
- constructor() {
- super();
-
- const shadow = this.attachShadow({ mode: 'open' });
- shadow.adoptedStyleSheets = [styles];
-
- const connectBtn = document.createElement('button');
- connectBtn.textContent = 'Connect Elements';
- connectBtn.addEventListener('click', () => this.toggleConnectionMode());
- this.#connectBtn = connectBtn;
- shadow.appendChild(connectBtn);
-
- document.addEventListener('click', this.handleDocumentClick.bind(this));
- }
-
- toggleConnectionMode() {
- if (this.#mode === 'idle') {
- this.#mode = 'connecting';
- this.#sourceElement = null;
- document.body.style.cursor = 'crosshair';
- this.#connectBtn.classList.add('active');
- this.#connectBtn.textContent = 'Select Source Element...';
- } else {
- this.#mode = 'idle';
- this.#sourceElement = null;
- document.body.style.cursor = '';
- this.#connectBtn.classList.remove('active');
- this.#connectBtn.textContent = 'Connect Elements';
- }
- }
-
- handleDocumentClick(event: MouseEvent) {
- if (this.#mode !== 'connecting') return;
-
- // Prevent clicking toolbar itself
- if (event.composedPath().includes(this)) return;
-
- event.preventDefault();
-
- const target = event.target as Element;
-
- if (!this.#sourceElement) {
- // First click - select source
- this.#sourceElement = target;
- this.#connectBtn.textContent = 'Select Target Element...';
- } else {
- // Second click - create connection
- this.createConnection(this.#sourceElement, target);
- this.#sourceElement = null;
- this.#mode = 'idle';
- document.body.style.cursor = '';
- this.#connectBtn.classList.remove('active');
- this.#connectBtn.textContent = 'Connect Elements';
- }
- }
-
- createConnection(source: Element, target: Element) {
- const sourceId = source.id || this.ensureElementId(source);
- const targetId = target.id || this.ensureElementId(target);
-
- // hack because we gotta sort out usage of constructor vs connectedCallback
- const propagator = new DOMParser().parseFromString(
- `
-
- `,
- 'text/html'
- ).body.firstElementChild;
-
- if (propagator) {
- document.body.appendChild(propagator);
- }
- }
-
- ensureElementId(element: Element): string {
- if (!element.id) {
- element.id = `folk-element-${Math.random().toString(36).slice(2)}`;
- }
- return element.id;
- }
-}
diff --git a/src/folk-toolset.ts b/src/folk-toolset.ts
new file mode 100644
index 0000000..4d2d7bf
--- /dev/null
+++ b/src/folk-toolset.ts
@@ -0,0 +1,157 @@
+import { FolkShape } from './folk-shape';
+
+export abstract class FolkInteractionHandler extends HTMLElement {
+ abstract readonly events: string[];
+ abstract handleEvent(event: Event): void;
+
+ static toolbar: FolkToolset | null = null;
+
+ protected button: HTMLButtonElement;
+
+ constructor() {
+ super();
+ this.attachShadow({ mode: 'open' });
+
+ const style = document.createElement('style');
+ style.textContent = `
+ button {
+ padding: 8px 16px;
+ border: 2px solid transparent;
+ cursor: pointer;
+ }
+ :host(.active) button {
+ background-color: #00aaff;
+ outline: none;
+ }
+ `;
+
+ this.button = document.createElement('button');
+ this.shadowRoot!.appendChild(style);
+ this.shadowRoot!.appendChild(this.button);
+ this.button.addEventListener('click', () => this.activate());
+ }
+
+ activate() {
+ console.log('activate', this);
+ FolkToolset.setActiveTool(this);
+ }
+}
+
+export class FolkShapeTool extends FolkInteractionHandler {
+ static tagName = 'folk-shape-tool';
+ readonly events = ['pointerdown'];
+
+ constructor() {
+ super();
+ this.button.textContent = 'Create Shape';
+ }
+
+ handleEvent(event: Event): void {
+ if (!(event instanceof PointerEvent)) return;
+ const target = event.target as HTMLElement;
+ if (!target || target instanceof FolkShape) return;
+
+ event.stopImmediatePropagation();
+
+ const shape = new FolkShape();
+ const rect = target.getBoundingClientRect();
+ const width = 100;
+ const height = 100;
+ shape.x = event.clientX - rect.left - width / 2;
+ shape.y = event.clientY - rect.top - height / 2;
+ shape.width = width;
+ shape.height = height;
+
+ target.appendChild(shape);
+ shape.focus();
+ }
+
+ static define() {
+ if (!customElements.get(this.tagName)) {
+ customElements.define(this.tagName, this);
+ }
+ }
+}
+
+export class FolkDeleteTool extends FolkInteractionHandler {
+ static tagName = 'folk-delete-tool';
+ readonly events = ['pointerdown'];
+
+ constructor() {
+ super();
+ this.button.textContent = 'Delete';
+ }
+
+ handleEvent(event: Event): void {
+ if (!(event instanceof PointerEvent)) return;
+ const target = event.target as HTMLElement;
+ if (!target || !(target instanceof FolkShape)) return;
+ event.stopImmediatePropagation();
+ target.remove();
+ }
+
+ static define() {
+ if (!customElements.get(this.tagName)) {
+ customElements.define(this.tagName, this);
+ }
+ }
+}
+
+export class FolkToolset extends HTMLElement {
+ static tagName = 'folk-toolset';
+ private static instance: FolkToolset | null = null;
+ private currentHandler: ((event: Event) => void) | null = null;
+ private activeTool: FolkInteractionHandler | null = null;
+
+ static setActiveTool(tool: FolkInteractionHandler) {
+ if (this.instance) {
+ this.instance.activateTool(tool);
+ }
+ }
+
+ constructor() {
+ super();
+ FolkToolset.instance = this;
+ }
+
+ private activateTool(tool: FolkInteractionHandler) {
+ // Remove active class from previous tool
+ if (this.activeTool) {
+ this.activeTool.classList.remove('active');
+ }
+
+ // Deactivate current handler
+ if (this.currentHandler) {
+ tool.events.forEach((event) => {
+ this.removeEventListener(event, this.currentHandler!, true);
+ });
+ }
+
+ // If clicking same tool, just deactivate
+ if (this.currentHandler === tool.handleEvent.bind(tool)) {
+ this.currentHandler = null;
+ this.activeTool = null;
+ return;
+ }
+
+ // Activate new handler
+ this.currentHandler = tool.handleEvent.bind(tool);
+ tool.events.forEach((event) => {
+ this.addEventListener(event, this.currentHandler!, true);
+ });
+
+ // Add active class to new tool
+ tool.classList.add('active');
+ this.activeTool = tool;
+ }
+
+ static define() {
+ if (!customElements.get(this.tagName)) {
+ customElements.define(this.tagName, this);
+ }
+ }
+}
+
+FolkShapeTool.define();
+FolkDeleteTool.define();
+FolkToolset.define();
diff --git a/src/standalone/folk-toolbar.ts b/src/standalone/folk-toolbar.ts
deleted file mode 100644
index 65efce5..0000000
--- a/src/standalone/folk-toolbar.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import './folk-event-propagator';
-import { FolkToolbar } from '../folk-toolbar';
-
-FolkToolbar.define();
-
-export { FolkToolbar };
diff --git a/src/standalone/folk-toolset.ts b/src/standalone/folk-toolset.ts
new file mode 100644
index 0000000..36e2f4c
--- /dev/null
+++ b/src/standalone/folk-toolset.ts
@@ -0,0 +1,6 @@
+import './folk-event-propagator';
+import { FolkToolset } from '../folk-toolset';
+
+FolkToolset.define();
+
+export { FolkToolset as FolkToolbar };