Skip to content
Draft
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 44 additions & 13 deletions app/assets/stylesheets/lexxy-editor.css
Original file line number Diff line number Diff line change
Expand Up @@ -441,14 +441,6 @@
display: none;
}

&[data-upload="file"] button[name="image"] {
display: none;
}

&[data-upload="image"] button[name="file"] {
display: none;
}

.lexxy-editor__toolbar-button {
aspect-ratio: 1;
block-size: var(--lexxy-toolbar-button-size);
Expand Down Expand Up @@ -572,7 +564,7 @@
.lexxy-editor__toolbar-dropdown-list {
border-start-start-radius: 0;
flex-direction: column;
gap: 0.1ch;
gap: var(--lexxy-toolbar-gap);
padding: 0.1ch;

button {
Expand All @@ -581,6 +573,7 @@
flex-direction: row;
gap: 1ch;
padding: 1ch;
position: relative;

&[aria-pressed="true"] {
background-color: var(--lexxy-color-selected);
Expand All @@ -600,10 +593,48 @@
}
}

.separator {
background: var(--lexxy-color-ink-lighter);
block-size: 1px;
inline-size: 100%;
.lexxy-editor__toolbar-group-end {
margin-block-end: var(--lexxy-toolbar-gap);

&:after {
background: var(--lexxy-color-ink-lighter);
block-size: 1px;
content: "";
display: block;
inset-inline: 0.5ch;
inset-block-end: calc(var(--lexxy-toolbar-gap) * -2);
pointer-events: none;
position: absolute;
}
Comment on lines 593 to +608
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change removes styling for the existing .separator element used in src/elements/toolbar.js (e.g. the <div class="separator" role="separator"> inside the format dropdown). With the current toolbar markup, dropdown separators will stop rendering. Either update the toolbar template/buildTemplate to stop emitting .separator and instead apply lexxy-editor__toolbar-group-end, or keep backward-compatible .separator styles here.

Copilot uses AI. Check for mistakes.

& + * {
margin: 0;
margin-block-start: calc(var(--lexxy-toolbar-gap) + 1px);
}
}

[overflowing] & {
flex-direction: row;

button span { display: none; }

.lexxy-editor__toolbar-group-end {
margin: 0;
margin-inline-end: var(--lexxy-toolbar-gap);

&:after {
block-size: auto;
inline-size: 1px;
inset-block: 0.5ch;
inset-inline-start: auto;
inset-inline-end: calc(var(--lexxy-toolbar-gap) * -2);
}

& + * {
margin: 0;
margin-inline-start: calc(var(--lexxy-toolbar-gap) + 1px);
}
}
}
}

Expand Down
4 changes: 2 additions & 2 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ Lexxy.configure({

Editors support the following options, configurable using presets and element attributes:

- `toolbar`: Pass `false` to disable the toolbar entirely, pass the ID of a `<lexxy-toolbar>` element to use as an external toolbar, or pass an object to configure individual toolbar buttons. By default, the toolbar is bootstrapped and displayed above the editor.
- `toolbar.upload`: Control which upload button(s) appear in the toolbar. Accepts `"file"`, `"image"`, or `"both"` (default). The image button restricts the file picker to images and videos (`accept="image/*,video/*"`), which triggers the native photo/video picker on iOS and Android. The file button opens an unrestricted file picker.
- `toolbar`: Pass `false` to disable the toolbar entirely, pass the ID of a `<lexxy-toolbar>` element to use as an external toolbar, or pass an object to configure the toolbar layout. By default, the toolbar is bootstrapped and displayed above the editor. See [Toolbar Customization](toolbar.md) for full details.
- `toolbar.items`: An array that controls which items appear in the toolbar and in what order. See [Toolbar Customization](toolbar.md).
- `attachments`: Pass `false` to disable attachments completely. By default, attachments are supported, including paste and drag & drop support.
- `markdown`: Pass `false` to disable Markdown support.
- `multiLine`: Pass `false` to force single line editing.
Expand Down
173 changes: 173 additions & 0 deletions docs/toolbar.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
---
title: Toolbar
layout: default
parent: Configuration
nav_order: 2
---

# Toolbar Customization

The toolbar layout is fully configurable through the `toolbar.items` array. Each entry defines a button, dropdown group, separator, or spacer.

## Item types

| Entry | Description |
|-------|-------------|
| `"bold"` | A button name from the built-in registry (see below) |
| `{ name, icon, label, items }` | A dropdown group containing child items |
| `"|"` | A visual group separator — adds a border after the preceding item |
| `"~"` | A flexible spacer — pushes subsequent items to the right edge |

## Default items

This is the default toolbar configuration:

```js
toolbar: {
items: [
"image",
"file",
"|",
"bold",
"italic",
{ name: "format", icon: "heading", label: "Text formatting", items: [
"paragraph",
"heading-large",
"heading-medium",
"heading-small",
"|",
"strikethrough",
"underline",
]
},
{ name: "lists", icon: "ul", label: "Lists", items: [
"unordered-list",
"ordered-list",
]
},
"highlight",
"link",
"quote",
"code",
"|",
"table",
"divider",
"~",
"undo",
"redo",
]
}
```

## Available items

| Name | Icon | Description |
|------|------|-------------|
| `image` | image | Upload images |
| `file` | attachment | Upload files |
| `bold` | bold | Bold formatting |
| `italic` | italic | Italic formatting |
| `paragraph` | paragraph | Normal text (typically inside a format dropdown) |
| `heading-large` | h2 | Large heading |
| `heading-medium` | h3 | Medium heading |
| `heading-small` | h4 | Small heading |
| `strikethrough` | strikethrough | Strikethrough text |
| `underline` | underline | Underline text |
| `unordered-list` | ul | Bullet list |
| `ordered-list` | ol | Numbered list |
| `highlight` | highlight | Color highlight picker |
| `link` | link | Insert/edit link |
| `quote` | quote | Block quote |
| `code` | code | Code block |
| `table` | table | Insert table |
| `divider` | hr | Horizontal divider |
| `undo` | undo | Undo |
| `redo` | redo | Redo |

## Examples

### Minimal toolbar

```js
Lexxy.configure({
compact: {
toolbar: {
items: ["bold", "italic", "link", "|", "undo", "redo"]
}
}
})
```

```html
<lexxy-editor preset="compact"></lexxy-editor>
```

### Without attachments

Omit `image` and `file` to remove upload buttons:

```js
toolbar: {
items: [
"bold",
"italic",
{ name: "format", icon: "heading", label: "Text formatting", items: [
"paragraph", "heading-large", "heading-medium", "heading-small",
"|", "strikethrough", "underline",
]
},
"link",
"quote",
"code",
"~",
"undo",
"redo",
]
}
```

### Custom dropdown groups

Create your own dropdown groupings:

```js
toolbar: {
items: [
"bold",
"italic",
{ name: "insert", icon: "table", label: "Insert", items: [
"table",
"divider",
"code",
"quote",
]
},
"link",
"~",
"undo",
"redo",
]
}
```

### Per-editor override

Override the toolbar for a specific editor using the HTML attribute:

```html
<lexxy-editor toolbar='{"items":["bold","italic","link"]}'></lexxy-editor>
```

### Disabling the toolbar

Pass `false` to hide the toolbar entirely:

```html
<lexxy-editor toolbar="false"></lexxy-editor>
```

## Separators and spacers

Use `"|"` between items to create visual groups. The separator renders as a thin vertical line (top-level) or a horizontal line (inside dropdowns).

Use `"~"` to insert a flexible spacer that pushes all following items to the right edge of the toolbar. This is typically used before undo/redo.
33 changes: 32 additions & 1 deletion src/config/lexxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,38 @@ const presets = new Configuration({
multiLine: true,
richText: true,
toolbar: {
upload: "both"
items: [
"image",
"file",
"|",
"bold",
"italic",
{ name: "format", icon: "heading", label: "Text formatting", items: [
"paragraph",
"heading-large",
"heading-medium",
"heading-small",
"|",
"strikethrough",
"underline",
]
},
{ name: "lists", icon: "ul", label: "Lists", items: [
"unordered-list",
"ordered-list",
]
},
"highlight",
"link",
"quote",
"code",
"|",
"table",
"divider",
"~",
"undo",
"redo",
]
},
highlight: {
buttons: {
Expand Down
27 changes: 27 additions & 0 deletions src/elements/dropdown/highlight.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { $getSelection, $isRangeSelection } from "lexical"
import { $getSelectionStyleValueForProperty } from "@lexical/selection"
import { createElement } from "../../helpers/html_helper"
import ToolbarIcons from "../toolbar_icons"
import { ToolbarDropdown } from "../toolbar_dropdown"

const APPLY_HIGHLIGHT_SELECTOR = "button.lexxy-highlight-button"
Expand All @@ -11,6 +13,31 @@ const REMOVE_HIGHLIGHT_SELECTOR = "[data-command='removeHighlight']"
const NO_STYLE = Symbol("no_style")

export class HighlightDropdown extends ToolbarDropdown {
static buildToolbarElement(name, entry) {
const details = createElement("details", {
className: "lexxy-editor__toolbar-dropdown lexxy-editor__toolbar-dropdown--chevron",
name: "lexxy-dropdown"
})

details.appendChild(createElement("summary", {
className: "lexxy-editor__toolbar-button",
name,
title: entry.title
}, ToolbarIcons[entry.icon]))

const dropdown = createElement("lexxy-highlight-dropdown", {
className: "lexxy-editor__toolbar-dropdown-content"
})
dropdown.appendChild(createElement("div", { className: "lexxy-highlight-colors" }))
dropdown.appendChild(createElement("button", {
className: "lexxy-editor__toolbar-button lexxy-editor__toolbar-dropdown-reset",
"data-command": "removeHighlight"
}, "Remove all coloring"))

details.appendChild(dropdown)
return details
}

connectedCallback() {
super.connectedCallback()
this.#registerToggleHandler()
Expand Down
Loading
Loading