From a943c8ac54b7fc5b8316404de74e0e5c2e95ef2e Mon Sep 17 00:00:00 2001 From: John McLear Date: Tue, 2 Jun 2026 18:03:08 +0100 Subject: [PATCH 1/5] docs: add README and a live example page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a README documenting all 15 components, theming, the API, and the dev/test/build workflow. Adds examples/index.html — a single-page live demo wiring up every component (buttons, inputs, cards, dropdown, color picker/wheel, chat, modal, toasts, notifications, themed editor) with an event log. Run with `pnpm dev` and open /examples/. examples/verify.mjs drives the page in headless Chromium to confirm every element upgrades and the interactions work (used to validate the demo). Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 142 +++++++++++++++++++++++ examples/index.html | 267 ++++++++++++++++++++++++++++++++++++++++++++ examples/verify.mjs | 62 ++++++++++ 3 files changed, 471 insertions(+) create mode 100644 README.md create mode 100644 examples/index.html create mode 100644 examples/verify.mjs diff --git a/README.md b/README.md new file mode 100644 index 0000000..6189625 --- /dev/null +++ b/README.md @@ -0,0 +1,142 @@ +# etherpad-webcomponents + +Reusable [Lit](https://lit.dev) web components for the [Etherpad](https://etherpad.org) project — framework-agnostic custom elements that ship Etherpad's UI building blocks (buttons, inputs, dropdowns, modals, toasts, a color wheel, …) plus a **standalone rich-text editor** (``) powered by Etherpad's Ace editor engine and changeset model. + +Because they are standard custom elements, the components work in any page or framework (React, Vue, Angular, Svelte, or plain HTML) without a wrapper. + +- **Built with:** Lit 3 + TypeScript +- **Documented & tested with:** Storybook 10 + Vitest browser mode (Playwright/Chromium) +- **License:** MIT © The Etherpad Foundation + +--- + +## Install + +```bash +pnpm add etherpad-webcomponents +# or: npm install etherpad-webcomponents +``` + +## Usage + +Import the whole library, or just the components you need (each is individually exported for tree-shaking): + +```js +// everything +import 'etherpad-webcomponents'; + +// or a single component +import 'etherpad-webcomponents/EpButton.js'; +``` + +```html + + Save changes + + + + + +``` + +The named exports (classes) are also available for programmatic use: + +```js +import { EpButton, EpTheme, themes } from 'etherpad-webcomponents'; +``` + +--- + +## Components + +| Tag | Class | Key attributes | Custom event | +| --- | --- | --- | --- | +| `` | `EpButton` | `variant` (`default`/`primary`/`ghost`/`icon`), `size`, `uppercase`, `disabled`, `type` | — | +| `` | `EpCard` | `card-title`, `subtitle`, `bordered`, `compact` | — | +| `` | `EpChatMessage` | `author`, `author-color`, `time`, `own` | — | +| `` | `EpCheckbox` | `checked`, `variant`, `disabled`, `label` | `ep-change` | +| `` | `EpColorPicker` | `colors[]`, `value` | `ep-color-select` | +| `` | `EpColorWheel` | `value` (canvas HSL hue/sat/lum wheel) | `ep-color-change` | +| `` / `` | `EpDropdown` / `EpDropdownItem` | `trigger` (`click`/`hover`), `align`, `open` | `ep-dropdown-select` | +| `` | `EpInput` | `label`, `value`, `placeholder`, `hint`, `error-text`, `error`, `type` (incl. `textarea`) | `ep-input` | +| `` | `EpModal` | dialog with backdrop & slots | `ep-modal-close` | +| `` | `EpNotification` | `type`, `position`, `duration` | — | +| `` / `` | `EpToastContainer` / `EpToastItem` | `position`, `type`, `message`, `duration` | — | +| `` | `EpToolbarSelect` | `options[]`, `value`, `icon-class`, `label`, `placeholder` | `ep-toolbar-select:change` | +| `` | `EpUserBadge` | `name`, `color`, `size`, `online` | — | +| `` | `EpTheme` | `name` — applies a token set to its subtree | — | +| `` | `EpEditor` | `content`, `readonly`, `wrap`, `author-id` | `ready`, `content-changed`, `selection-changed` | + +### Theming + +Components read CSS custom properties (`--text-color`, `--primary-color`, `--bg-soft-color`, …). Wrap a subtree in `` to apply a token set. Built-in themes: **`colibris`** (default, matches Etherpad's skin), **`colibris-dark`**, **`high-contrast`**, and **`warm`**. Register your own at runtime: + +```js +import { EpTheme } from 'etherpad-webcomponents'; +EpTheme.registerTheme('brand', { '--primary-color': '#ff5722', /* …all ThemeTokens */ }); +``` + +### The editor (``) + +`` embeds Etherpad's Ace editor engine (under `src/editor/`: `Changeset`, `AttributePool`, `changesettracker`, content collector, op assemblers, etc.) as a self-contained element. It supports bold/italic/underline/strikethrough, ordered & unordered lists, indentation, undo/redo, and changeset-based collaboration. + +Selected methods on the element (see `src/EpEditor.ts` for the full API): + +```js +const ed = document.querySelector('ep-editor'); +ed.getText(); // current document text +ed.setText('...'); // replace contents +ed.toggleFormat('bold'); // toggle a formatting attribute on the selection +ed.insertUnorderedList(); // list / indent helpers +ed.undo(); ed.redo(); + +// collaboration +ed.setBaseAttributedText(atext, pool); +ed.applyChangeset(cs, author, pool); +const pending = ed.prepareUserChangeset(); // { changeset, apool } to send to a server +``` + +The lower-level engine pieces are also exported for advanced use: + +```js +import { AceEditor, AttributePool, makeChangesetTracker } from 'etherpad-webcomponents'; +``` + +--- + +## Development + +Requires Node.js and [pnpm](https://pnpm.io). + +```bash +pnpm install # install dependencies +pnpm exec playwright install chromium # one-time: browser for the test runner + +pnpm storybook # interactive component explorer at http://localhost:6006 +pnpm dev # vite dev server +pnpm typecheck # tsc --noEmit +pnpm build # emit dist/ (.js + .d.ts) via tsc +pnpm test # run the Storybook test suite (see below) +``` + +### Testing + +Each component has a Storybook story file under `stories/`. The stories double as the test suite: their Storybook **`play` functions** assert behavior (rendering, attribute reflection, click handling, disabled state, event dispatch, …), and `pnpm test` runs every story's play function in a real **Chromium** browser via Vitest browser mode (`@storybook/addon-vitest` + `@vitest/browser-playwright`, configured in `vitest.config.ts`). + +```bash +pnpm test --run +``` + +To add coverage, add a story with a `play` function — it will automatically be picked up as a test. + +## Project layout + +``` +src/ + Ep*.ts # the Lit components (one per custom element) + index.ts # public entry point — re-exports every component + editor/ # Etherpad Ace editor engine + changeset model (backs ) +stories/ # Storybook stories (also the test suite, via play functions) +.storybook/ # Storybook config +vitest.config.ts # browser-mode test runner config +``` diff --git a/examples/index.html b/examples/index.html new file mode 100644 index 0000000..1331290 --- /dev/null +++ b/examples/index.html @@ -0,0 +1,267 @@ + + + + + + etherpad-webcomponents — demo + + + + + + + + +
+

🪶 etherpad-webcomponents live demo

+ +
+ +
+ +
+

Buttons

+
+ Default + Primary + Ghost + Uppercase + Small + Large + Disabled +
+
+ + +
+

Inputs

+
+ + + +
+
+ + + +
+
+ + +
+

Cards

+
+ +
Cards group related content and accept slotted children.
+
+ +
A compact card variant.
+
+
+
+ + +
+

Dropdown & toolbar select

+
+ + Open menu ▾ + Cut + Copy + Paste (disabled) + + + +
+
+ + +
+

Color picker & wheel

+
+ + + +
+
+ + +
+

Chat messages

+ + Shall we collaborate on this pad? + + + Absolutely — joining now. + +
+ + +
+

Modal, toasts & notifications

+
+ Open modal + Show toast + Show notification +
+
+ + +
+

Rich-text editor (<ep-editor>)

+
+ B + I + U + S + • List + 1. List + ↶ Undo + ↷ Redo +
+ +
+ + +
+

Event log

+
+
+
+ + + + + + + Example modal +

This is an <ep-modal>. Click the backdrop, press Escape, or the close button to dismiss.

+
+ Got it +
+
+
+ + + + diff --git a/examples/verify.mjs b/examples/verify.mjs new file mode 100644 index 0000000..da14e47 --- /dev/null +++ b/examples/verify.mjs @@ -0,0 +1,62 @@ +import { chromium } from 'playwright'; + +const errors = []; +const browser = await chromium.launch(); +const page = await browser.newPage(); +page.on('console', (m) => { if (m.type() === 'error') errors.push(m.text()); }); +page.on('pageerror', (e) => errors.push('pageerror: ' + e.message)); + +await page.goto('http://localhost:5199/examples/', { waitUntil: 'networkidle' }); +await page.waitForTimeout(800); + +const check = async (label, fn) => { + try { const v = await fn(); console.log(`${v ? '✓' : '✗'} ${label}${v && v !== true ? ' → ' + v : ''}`); return !!v; } + catch (e) { console.log(`✗ ${label} → ${e.message}`); return false; } +}; + +// every custom element upgraded (has a shadowRoot or is defined) +const tags = ['ep-theme','ep-button','ep-input','ep-checkbox','ep-card','ep-dropdown', + 'ep-toolbar-select','ep-color-picker','ep-color-wheel','ep-user-badge','ep-chat-message', + 'ep-modal','ep-toast-container','ep-editor']; +for (const t of tags) { + await check(`<${t}> upgraded`, () => page.evaluate((tag) => { + const el = document.querySelector(tag); + return !!(el && (el.shadowRoot || customElements.get(tag))); + }, t)); +} + +// button renders a real