Skip to content
Open
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
276 changes: 120 additions & 156 deletions app/assets/javascript/lexxy.js

Large diffs are not rendered by default.

Binary file modified app/assets/javascript/lexxy.js.br
Binary file not shown.
Binary file modified app/assets/javascript/lexxy.js.gz
Binary file not shown.
2 changes: 1 addition & 1 deletion app/assets/javascript/lexxy.min.js

Large diffs are not rendered by default.

Binary file modified app/assets/javascript/lexxy.min.js.br
Binary file not shown.
Binary file modified app/assets/javascript/lexxy.min.js.gz
Binary file not shown.
144 changes: 93 additions & 51 deletions app/assets/stylesheets/lexxy-editor.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@
transition: opacity 150ms;

input,
button {
button,
summary {
&:focus-visible {
outline-color: var(--lexxy-focus-ring-color);
outline: var(--lexxy-focus-ring-size) solid var(--lexxy-focus-ring-color);
outline-offset: var(--lexxy-focus-ring-offset);
}
}

Expand Down Expand Up @@ -179,49 +181,64 @@
}
}

/* Dialog
/* Dropdowns
/* -------------------------------------------------------------------------- */

:where(.lexxy-dialog) {
display: contents;
:where(.lexxy-editor__toolbar-dropdown) {
position: relative;
user-select: none;
-webkit-user-select: none;

.lexxy-editor__toolbar-dropdown-content {
--dropdown-padding: 1ch;
--dropdown-gap: calc(var(--dropdown-padding) / 2);

dialog {
background-color: var(--lexxy-color-canvas);
border: 2px solid var(--lexxy-color-selected-hover);
border-radius: var(--lexxy-radius);
border-start-start-radius: 0;
box-sizing: border-box;
color: var(--lexxy-color-ink);
display: flex;
gap: var(--dropdown-gap);
inset-block-start: 2lh;
inline-size: 100%;
max-inline-size: 38ch;
inset-inline-start: 0;
max-inline-size: 40ch;
margin: 0;
padding: 1ch;
padding: var(--dropdown-padding);
position: absolute;
z-index: 3;
}

&:has(dialog[open]) + .lexxy-editor__toolbar-button {
&:is([open]) .lexxy-editor__toolbar-button {
background-color: var(--lexxy-color-selected-hover);
border-end-end-radius: 0;
border-end-start-radius: 0;

&:hover {
background-color: var(--lexxy-color-selected-hover);
}
}
}

[overflowing] & {
position: static;

:where(lexxy-toolbar[overflowing]) .lexxy-dialog dialog {
--dialog-padding: 0.5ch;
inset-inline-end: var(--dialog-padding);
inset-inline-start: var(--dialog-padding) !important;
inline-size: calc(100% - 2 * var(--dialog-padding));
margin: auto;
.lexxy-editor__toolbar-dropdown-content {
--dropdown-padding: 0.5ch;
inset-inline-end: var(--dropdown-padding);
inset-inline-start: var(--dropdown-padding);
}
}
}

/* Link dialog
/* Link dropdown
/* -------------------------------------------------------------------------- */

:where(.lexxy-link-dialog) {
.lexxy-dialog-actions {
:where(lexxy-link-dropdown) {

> * { flex: 1; }

.lexxy-editor__toolbar-dropdown-actions {
display: flex;
font-size: var(--lexxy-text-small);
flex: 1 1 0%;
Expand All @@ -242,6 +259,11 @@
color: var(--lexxy-color-text);
box-sizing: border-box;
inline-size: 100%;
min-inline-size: 40ch;

[overflowing] & {
min-inline-size: 0;
}
}

button {
Expand All @@ -254,31 +276,31 @@
button[type="submit"] {
background-color: var(--lexxy-color-accent-dark);
color: var(--lexxy-color-ink-inverted);

&:hover {
background-color: var(--lexxy-color-accent-medium);
}
}
}

/* Color dialog
/* Color dropdown
/* -------------------------------------------------------------------------- */

:where(.lexxy-highlight-dialog) {
.lexxy-highlight-dialog-content {
:where(lexxy-highlight-dropdown) {
display: flex;
flex-direction: column;

[data-button-group] {
display: flex;
flex-direction: column;
gap: 0.75ch;
justify-items: flex-start;
flex-direction: row;
gap: var(--dropdown-gap);

[data-button-group] {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 0.5ch;
}

button {
block-size: 3.5ch;
color: var(--lexxy-color-text);
inline-size: 3.5ch;
position: relative;
outline: none;
aspect-ratio: 1 / 1;
inline-size: var(--button-size);
min-inline-size: var(--button-size);
max-inline-size: var(--button-size);

&:after {
align-self: center;
Expand All @@ -291,32 +313,52 @@
inset-inline-end: 0;
inset-inline-start: 0;
}
}
}

&:hover {
opacity: 0.8;
}
button {
--button-size: 2lh;

&[aria-pressed="true"] {
box-shadow: 0 0 0 1px var(--lexxy-color-canvas), 0 0 0 3px var(--lexxy-color-accent-dark);
color: var(--lexxy-color-text);
flex: 1;
min-block-size: var(--button-size);
position: relative;

&:after {
content: "✓";
}
}
&:hover {
opacity: 0.8;
}

.lexxy-highlight-dialog-reset {
inline-size: 100%;
&[aria-pressed="true"] {
box-shadow: 0 0 0 2px currentColor inset;

&[disabled] {
display: none;
&:after {
content: "✓";
}
}
}

&:after { display: none; }
.lexxy-editor__toolbar-dropdown-reset[disabled] {
display: none;
}

[overflowing] & {
inline-size: fit-content;

[data-button-group] {
flex-wrap: wrap;

button {
--button-size: 1.6lh;

&:after {
font-size: 0.9em;
}
}
}
}
}


/* Language picker
/* -------------------------------------------------------------------------- */

Expand Down
2 changes: 1 addition & 1 deletion app/assets/stylesheets/lexxy-variables.css
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@

/* Focus ring */
--lexxy-focus-ring-color: var(--lexxy-color-accent-dark);
--lexxy-focus-ring-offset: 2px;
--lexxy-focus-ring-offset: 0;
--lexxy-focus-ring-size: 2px;

/* Misc */
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { $getSelection, $isRangeSelection } from "lexical"
import { $getSelectionStyleValueForProperty } from "@lexical/selection"
import { ToolbarDialog } from "./toolbar_dialog"
import { ToolbarDropdown } from "../toolbar_dropdown"

const APPLY_HIGHLIGHT_SELECTOR = "button.lexxy-highlight-button"
const REMOVE_HIGHLIGHT_SELECTOR = "[data-command='removeHighlight']"
Expand All @@ -10,21 +10,18 @@ const REMOVE_HIGHLIGHT_SELECTOR = "[data-command='removeHighlight']"
// see https://github.com/facebook/lexical/issues/8013
const NO_STYLE = Symbol("no_style")

export class HighlightDialog extends ToolbarDialog {
export class HighlightDropdown extends ToolbarDropdown {
connectedCallback() {
super.connectedCallback()

this.#setUpButtons()
this.#registerHandlers()
}

updateStateCallback() {
this.#updateColorButtonStates($getSelection())
}

#registerHandlers() {
this.querySelector(REMOVE_HIGHLIGHT_SELECTOR).addEventListener("click", this.#handleRemoveHighlightClick.bind(this))
this.container.addEventListener("toggle", this.#handleToggle.bind(this))
this.#colorButtons.forEach(button => button.addEventListener("click", this.#handleColorButtonClick.bind(this)))
this.querySelector(REMOVE_HIGHLIGHT_SELECTOR).addEventListener("click", this.#handleRemoveHighlightClick.bind(this))
}

#setUpButtons() {
Expand All @@ -51,6 +48,14 @@ export class HighlightDialog extends ToolbarDialog {
return button
}

#handleToggle({ newState }) {
if (newState === "open") {
this.editor.getEditorState().read(() => {
this.#updateColorButtonStates($getSelection())
})
}
Comment on lines +52 to +56
Copy link
Contributor

@samuelpecher samuelpecher Dec 3, 2025

Choose a reason for hiding this comment

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

Just a general thought: I removed the callbacks yet I don't love a toolbar summary reaching into the editor like this. It's the trade-off for not scaffolding a whole series of callbacks.

}

#handleColorButtonClick(event) {
event.preventDefault()

Expand Down Expand Up @@ -96,6 +101,4 @@ export class HighlightDialog extends ToolbarDialog {
}
}

// We should extend the native dialog and avoid the intermediary <dialog> but not
// supported by Safari yet: customElements.define("lexxy-hightlight-dialog", HighlightDialog, { extends: "dialog" })
customElements.define("lexxy-highlight-dialog", HighlightDialog)
customElements.define("lexxy-highlight-dropdown", HighlightDropdown)
20 changes: 8 additions & 12 deletions src/elements/link_dialog.js → src/elements/dropdown/link.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,30 @@
import { $getSelection, $isRangeSelection } from "lexical"
import { $isLinkNode } from "@lexical/link"
import { ToolbarDialog } from "./toolbar_dialog"
import { ToolbarDropdown } from "../toolbar_dropdown"

export class LinkDialog extends ToolbarDialog {
export class LinkDropdown extends ToolbarDropdown {
connectedCallback() {
super.connectedCallback()
this.input = this.querySelector("input")

this.#registerHandlers()
}

updateStateCallback() {
this.input.value = this.#selectedLinkUrl
}

#registerHandlers() {
this.dialog.addEventListener("beforetoggle", this.#handleBeforeToggle.bind(this))
this.dialog.addEventListener("submit", this.#handleSubmit.bind(this))
this.container.addEventListener("toggle", this.#handleToggle.bind(this))
this.addEventListener("submit", this.#handleSubmit.bind(this))
this.querySelector("[value='unlink']").addEventListener("click", this.#handleUnlink.bind(this))
}

#handleBeforeToggle({ newState }) {
#handleToggle({ newState }) {
this.input.value = this.#selectedLinkUrl
this.input.required = newState === "open"
}

#handleSubmit(event) {
const command = event.submitter?.value
this.editor.dispatchCommand(command, this.input.value)
this.close()
}

#handleUnlink() {
Expand Down Expand Up @@ -55,6 +53,4 @@ export class LinkDialog extends ToolbarDialog {
}
}

// We should extend the native dialog and avoid the intermediary <dialog> but not
// supported by Safari yet: customElements.define("lexxy-link-dialog", LinkDialog, { extends: "dialog" })
customElements.define("lexxy-link-dialog", LinkDialog)
customElements.define("lexxy-link-dropdown", LinkDropdown)
4 changes: 4 additions & 0 deletions src/elements/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ export default class LexicalEditorElement extends HTMLElement {
return this.getAttribute("attachments") !== "false"
}

get contentTabIndex() {
return parseInt(this.editorContentElement?.getAttribute("tabindex") ?? "0")
}

focus() {
this.editor.focus()
}
Expand Down
Loading