@37signals/lexxy 0.9.33 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/lexxy.esm.js +309 -43
- package/dist/stylesheets/lexxy-editor.css +137 -2
- package/dist/stylesheets/lexxy-variables.css +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,6 +8,7 @@ A modern rich text editor for Rails.
|
|
|
8
8
|
|
|
9
9
|
- Built on top of [Lexical](https://lexical.dev), the powerful text editor framework from Meta.
|
|
10
10
|
- Good HTML semantics. Paragraphs are real `<p>` tags, as they should be.
|
|
11
|
+
- Designed with [accessibility](https://lexxy.dev/docs/accessibility.html) in mind.
|
|
11
12
|
- Markdown support: shortcuts, auto-formatting on paste.
|
|
12
13
|
- Real-time code syntax highlighting.
|
|
13
14
|
- Create links by pasting URLs on selected text.
|
package/dist/lexxy.esm.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { highlightCode, highlightElement } from './lexxy_helpers.esm.js';
|
|
2
2
|
import createDOMPurify from 'dompurify';
|
|
3
3
|
import { getStyleObjectFromCSS, getCSSFromStyleObject, $getSelectionStyleValueForProperty, $ensureForwardRangeSelection, $isAtNodeEnd, $patchStyleText, $setBlocksType, $forEachSelectedTextNode } from '@lexical/selection';
|
|
4
|
-
import { SKIP_DOM_SELECTION_TAG, CAN_UNDO_COMMAND, COMMAND_PRIORITY_LOW, CAN_REDO_COMMAND, $getSelection, $isRangeSelection, $isTextNode, DecoratorNode, $createTextNode, defineExtension, $createNodeSelection, $getRoot, $caretFromPoint, $setSelectionFromCaretRange, $getCaretRange, $normalizeCaret, $getChildCaret, $getCaretInDirection, $isParagraphNode, $isLineBreakNode, $createParagraphNode, $isElementNode, $isRootOrShadowRoot, $isRootNode, $isDecoratorNode, $getSiblingCaret, $rewindSiblingCaret, $splitAtPointCaretNext, $normalizeSelection__EXPERIMENTAL, $isNodeSelection, $isChildCaret, $isTextPointCaret, $isExtendableTextPointCaret, $isSiblingCaret, $getCommonAncestor, $findMatchingParent, TextNode, createCommand, COMMAND_PRIORITY_EDITOR, $getEditor, $getNodeByKey, HISTORY_MERGE_TAG, SKIP_SCROLL_INTO_VIEW_TAG, $cloneWithProperties, $getNearestRootOrShadowRoot, $createRangeSelection, $setSelection, ElementNode, $splitNode, createState, COMMAND_PRIORITY_NORMAL, $getState, $setState, $hasUpdateTag, PASTE_TAG, FORMAT_TEXT_COMMAND, UNDO_COMMAND, REDO_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_TAB_COMMAND, INSERT_LINE_BREAK_COMMAND, COMMAND_PRIORITY_HIGH, INSERT_PARAGRAPH_COMMAND, OUTDENT_CONTENT_COMMAND, INDENT_CONTENT_COMMAND, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ARROW_DOWN_COMMAND, DELETE_CHARACTER_COMMAND, SELECTION_CHANGE_COMMAND, COMMAND_PRIORITY_CRITICAL, $addUpdateTag, CLICK_COMMAND, isDOMNode, $getNearestNodeFromDOMNode, $getChildCaretAtIndex, $createLineBreakNode, SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, PASTE_COMMAND, $onUpdate, ParagraphNode, RootNode, DRAGSTART_COMMAND, DROP_COMMAND, KEY_DOWN_COMMAND, flipDirection, mergeRegister as mergeRegister$1, $createRangeSelectionFromDom, CLEAR_HISTORY_COMMAND, KEY_ENTER_COMMAND, KEY_SPACE_COMMAND, INPUT_COMMAND, KEY_BACKSPACE_COMMAND } from 'lexical';
|
|
4
|
+
import { SKIP_DOM_SELECTION_TAG, CAN_UNDO_COMMAND, COMMAND_PRIORITY_LOW, CAN_REDO_COMMAND, $getSelection, $isRangeSelection, $isTextNode, DecoratorNode, $createTextNode, defineExtension, $createNodeSelection, $getRoot, $caretFromPoint, $setSelectionFromCaretRange, $getCaretRange, $normalizeCaret, $getChildCaret, $getCaretInDirection, $isParagraphNode, $isLineBreakNode, $createParagraphNode, $isElementNode, $isRootOrShadowRoot, $isRootNode, $isDecoratorNode, $getSiblingCaret, $rewindSiblingCaret, $splitAtPointCaretNext, $normalizeSelection__EXPERIMENTAL, $isNodeSelection, $isChildCaret, $isTextPointCaret, $isExtendableTextPointCaret, $isSiblingCaret, $getCommonAncestor, $findMatchingParent, TextNode, createCommand, COMMAND_PRIORITY_EDITOR, $getEditor, $getNodeByKey, HISTORY_MERGE_TAG, SKIP_SCROLL_INTO_VIEW_TAG, $cloneWithProperties, $getNearestRootOrShadowRoot, $createRangeSelection, $setSelection, ElementNode, $splitNode, createState, COMMAND_PRIORITY_NORMAL, $getState, $setState, $hasUpdateTag, PASTE_TAG, FORMAT_TEXT_COMMAND, UNDO_COMMAND, REDO_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_TAB_COMMAND, INSERT_LINE_BREAK_COMMAND, COMMAND_PRIORITY_HIGH, INSERT_PARAGRAPH_COMMAND, OUTDENT_CONTENT_COMMAND, INDENT_CONTENT_COMMAND, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ARROW_DOWN_COMMAND, DELETE_CHARACTER_COMMAND, SELECTION_CHANGE_COMMAND, COMMAND_PRIORITY_CRITICAL, $addUpdateTag, CLICK_COMMAND, isDOMNode, $getNearestNodeFromDOMNode, $getChildCaretAtIndex, $createLineBreakNode, SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, PASTE_COMMAND, $onUpdate, ParagraphNode, RootNode, DRAGSTART_COMMAND, DROP_COMMAND, KEY_DOWN_COMMAND, flipDirection, mergeRegister as mergeRegister$1, $createRangeSelectionFromDom, CLEAR_HISTORY_COMMAND, KEY_ENTER_COMMAND, KEY_SPACE_COMMAND, INPUT_COMMAND, HISTORY_PUSH_TAG, KEY_BACKSPACE_COMMAND } from 'lexical';
|
|
5
5
|
import * as lexical from 'lexical';
|
|
6
6
|
export { lexical as Lexical };
|
|
7
7
|
import { LinkNode, $createAutoLinkNode, $toggleLink, $createLinkNode, $isLinkNode, AutoLinkNode } from '@lexical/link';
|
|
@@ -589,6 +589,35 @@ function isKeyboardActivation(event) {
|
|
|
589
589
|
return event instanceof PointerEvent && event.pointerId === -1
|
|
590
590
|
}
|
|
591
591
|
|
|
592
|
+
function trapFocusAtTabBoundary(container, event) {
|
|
593
|
+
if (event.key !== "Tab") return
|
|
594
|
+
|
|
595
|
+
const tabStops = Array.from(container.querySelectorAll("a[href], area[href], button, input:not([type='hidden']), select, textarea, iframe, object, embed, [contenteditable]:not([contenteditable='false']), [tabindex], summary, audio[controls], video[controls]"))
|
|
596
|
+
.filter(element => element.tabIndex >= 0 && !element.matches(":disabled") && !element.closest("[inert]") && isActiveAndVisible(element))
|
|
597
|
+
.sort((first, second) => {
|
|
598
|
+
if (first.tabIndex > 0 && second.tabIndex > 0) {
|
|
599
|
+
return first.tabIndex - second.tabIndex
|
|
600
|
+
} else if (first.tabIndex > 0) {
|
|
601
|
+
return -1
|
|
602
|
+
} else if (second.tabIndex > 0) {
|
|
603
|
+
return 1
|
|
604
|
+
} else {
|
|
605
|
+
return 0
|
|
606
|
+
}
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
const firstTabStop = tabStops[0];
|
|
610
|
+
const lastTabStop = tabStops.at(-1);
|
|
611
|
+
|
|
612
|
+
if (event.shiftKey && document.activeElement === firstTabStop) {
|
|
613
|
+
event.preventDefault();
|
|
614
|
+
lastTabStop.focus();
|
|
615
|
+
} else if (!event.shiftKey && document.activeElement === lastTabStop) {
|
|
616
|
+
event.preventDefault();
|
|
617
|
+
firstTabStop.focus();
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
592
621
|
function handleRollingTabIndex(elements, event, { orientation = "horizontal", wrap = false } = {}) {
|
|
593
622
|
const previousActiveElement = document.activeElement;
|
|
594
623
|
|
|
@@ -1443,10 +1472,12 @@ class ToolbarDropdown extends HTMLElement {
|
|
|
1443
1472
|
}
|
|
1444
1473
|
|
|
1445
1474
|
#handleKeyDown = (event) => {
|
|
1446
|
-
if (event.key === "Escape") {
|
|
1475
|
+
if (event.key === "Escape" && this.isOpen) {
|
|
1447
1476
|
event.stopPropagation();
|
|
1448
1477
|
this.close({ focusEditor: !this.#shouldReturnFocusToTrigger });
|
|
1449
1478
|
if (this.#shouldReturnFocusToTrigger) this.trigger?.focus();
|
|
1479
|
+
} else if (this.isOpen && this.panel.role === "dialog") {
|
|
1480
|
+
trapFocusAtTabBoundary(this.panel, event);
|
|
1450
1481
|
} else if (this.#isNavigatingMenu(event)) {
|
|
1451
1482
|
event.stopPropagation();
|
|
1452
1483
|
handleRollingTabIndex(this.#buttons, event, { orientation: "both", wrap: true });
|
|
@@ -1461,7 +1492,7 @@ class ToolbarDropdown extends HTMLElement {
|
|
|
1461
1492
|
// Ask for the ring explicitly: opening the menu with the mouse otherwise leaves the
|
|
1462
1493
|
// first item focused without a focus ring, since focus() inherits the mouse modality.
|
|
1463
1494
|
this.#interactiveElements[0]?.focus({ focusVisible: true });
|
|
1464
|
-
await this.#resetTabIndexValues();
|
|
1495
|
+
if (this.panel.role === "menu") await this.#resetTabIndexValues();
|
|
1465
1496
|
}
|
|
1466
1497
|
|
|
1467
1498
|
async #resetTabIndexValues() {
|
|
@@ -2233,12 +2264,33 @@ class CustomActionTextAttachmentNode extends DecoratorNode {
|
|
|
2233
2264
|
const trimmedLabel = this.label.trim();
|
|
2234
2265
|
const matches = [ ...figure.querySelectorAll("span") ].filter((span) => span.textContent.trim() === trimmedLabel);
|
|
2235
2266
|
const deepest = matches.filter((span) => !matches.some((other) => other !== span && span.contains(other)));
|
|
2267
|
+
const labelImage = figure.querySelector("[data-lexxy-label-image]");
|
|
2268
|
+
|
|
2236
2269
|
for (const span of deepest) {
|
|
2237
2270
|
if (span.getAttribute("aria-hidden") !== "true") {
|
|
2238
|
-
span.
|
|
2271
|
+
if (span.contains(labelImage)) {
|
|
2272
|
+
this.#wrapLabelTextNodesIn(span);
|
|
2273
|
+
} else {
|
|
2274
|
+
span.setAttribute("data-lexxy-label-mirror", "");
|
|
2275
|
+
}
|
|
2239
2276
|
}
|
|
2240
2277
|
}
|
|
2241
2278
|
}
|
|
2279
|
+
|
|
2280
|
+
#wrapLabelTextNodesIn(element) {
|
|
2281
|
+
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT);
|
|
2282
|
+
const textNodes = [];
|
|
2283
|
+
|
|
2284
|
+
while (walker.nextNode()) {
|
|
2285
|
+
if (walker.currentNode.textContent.trim()) textNodes.push(walker.currentNode);
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
for (const textNode of textNodes) {
|
|
2289
|
+
const mirror = createElement("span", { "data-lexxy-label-mirror": "" });
|
|
2290
|
+
textNode.replaceWith(mirror);
|
|
2291
|
+
mirror.append(textNode);
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2242
2294
|
}
|
|
2243
2295
|
|
|
2244
2296
|
function $isCustomActionTextAttachmentNode(node) {
|
|
@@ -2975,7 +3027,7 @@ class ActionTextAttachmentNode extends DecoratorNode {
|
|
|
2975
3027
|
node: new ActionTextAttachmentNode({
|
|
2976
3028
|
src: img.getAttribute("src"),
|
|
2977
3029
|
fileName: fileName,
|
|
2978
|
-
|
|
3030
|
+
altText: img.getAttribute("alt"),
|
|
2979
3031
|
contentType: "image/*",
|
|
2980
3032
|
width: img.getAttribute("width"),
|
|
2981
3033
|
height: img.getAttribute("height")
|
|
@@ -3300,8 +3352,9 @@ class ActionTextAttachmentNode extends DecoratorNode {
|
|
|
3300
3352
|
}
|
|
3301
3353
|
|
|
3302
3354
|
#swapToPreviewDOM(figure, previewSrc) {
|
|
3355
|
+
const altText = this.editor.read(() => this.getLatest().altText);
|
|
3303
3356
|
this.#swapFigureContent(figure, "attachment--file", "attachment--preview", () => {
|
|
3304
|
-
const img = createElement("img", { src: previewSrc, draggable: false, alt:
|
|
3357
|
+
const img = createElement("img", { src: previewSrc, draggable: false, alt: altText });
|
|
3305
3358
|
img.onerror = () => this.#swapPreviewToFileDOM(img);
|
|
3306
3359
|
const container = createElement("div", { className: "attachment__container" });
|
|
3307
3360
|
container.appendChild(img);
|
|
@@ -6184,6 +6237,11 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
|
|
|
6184
6237
|
updateDOM(prevNode, dom) {
|
|
6185
6238
|
if (this.uploadError !== prevNode.uploadError) return true
|
|
6186
6239
|
|
|
6240
|
+
const image = dom.querySelector("img");
|
|
6241
|
+
if (image && prevNode.altText !== this.altText) {
|
|
6242
|
+
image.alt = this.altText;
|
|
6243
|
+
}
|
|
6244
|
+
|
|
6187
6245
|
if (prevNode.progress !== this.progress) {
|
|
6188
6246
|
const progress = dom.querySelector("progress");
|
|
6189
6247
|
progress.value = this.progress ?? 0;
|
|
@@ -6217,7 +6275,7 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
|
|
|
6217
6275
|
}
|
|
6218
6276
|
|
|
6219
6277
|
#createDOMForImage() {
|
|
6220
|
-
return createElement("img")
|
|
6278
|
+
return createElement("img", { alt: this.altText })
|
|
6221
6279
|
}
|
|
6222
6280
|
|
|
6223
6281
|
#createDOMForFile() {
|
|
@@ -6382,7 +6440,6 @@ class AttachmentNodeConversion {
|
|
|
6382
6440
|
const { blob } = this;
|
|
6383
6441
|
return {
|
|
6384
6442
|
sgid: blob.attachable_sgid,
|
|
6385
|
-
altText: blob.filename,
|
|
6386
6443
|
contentType: blob.content_type,
|
|
6387
6444
|
fileName: blob.filename,
|
|
6388
6445
|
fileSize: blob.byte_size,
|
|
@@ -7260,7 +7317,7 @@ class Contents {
|
|
|
7260
7317
|
this.#performTextReplacement(anchorNode, lastIndex, stringToReplace, replacementNodes);
|
|
7261
7318
|
}
|
|
7262
7319
|
|
|
7263
|
-
uploadFiles(files, { selectLast } = {}) {
|
|
7320
|
+
uploadFiles(files, { selectLast, altText } = {}) {
|
|
7264
7321
|
if (!this.editorElement) return // Disposed (e.g. on turbo:before-cache); a late drop can still land here
|
|
7265
7322
|
|
|
7266
7323
|
if (!this.editorElement.supportsAttachments) {
|
|
@@ -7273,6 +7330,10 @@ class Contents {
|
|
|
7273
7330
|
const uploader = Uploader.for(this.editorElement, validFiles);
|
|
7274
7331
|
uploader.$uploadFiles();
|
|
7275
7332
|
|
|
7333
|
+
if (altText && files.length === 1 && uploader.nodes.length === 1) {
|
|
7334
|
+
uploader.nodes[0].getWritable().altText = altText;
|
|
7335
|
+
}
|
|
7336
|
+
|
|
7276
7337
|
if (selectLast && uploader.nodes?.length) {
|
|
7277
7338
|
const lastNode = uploader.nodes.at(-1);
|
|
7278
7339
|
lastNode.selectEnd();
|
|
@@ -7985,46 +8046,51 @@ class Clipboard {
|
|
|
7985
8046
|
}
|
|
7986
8047
|
|
|
7987
8048
|
#handlePastedFiles(clipboardData) {
|
|
7988
|
-
if (
|
|
7989
|
-
|
|
7990
|
-
|
|
7991
|
-
|
|
7992
|
-
|
|
7993
|
-
if (files.length && this.#isCopiedImageHTML(html)) {
|
|
7994
|
-
this.#uploadFilesPreservingScroll(files);
|
|
7995
|
-
return true
|
|
7996
|
-
}
|
|
7997
|
-
|
|
7998
|
-
if (html && !this.#isLexicalClipboardData(clipboardData)) {
|
|
7999
|
-
this.contents.insertHtml(html, { tag: PASTE_TAG });
|
|
8000
|
-
return true
|
|
8001
|
-
}
|
|
8049
|
+
if (this.editorElement.supportsAttachments) {
|
|
8050
|
+
const html = clipboardData.getData("text/html");
|
|
8051
|
+
const files = clipboardData.files;
|
|
8052
|
+
const copiedImage = files.length && this.#copiedImage(html);
|
|
8002
8053
|
|
|
8003
|
-
|
|
8004
|
-
|
|
8005
|
-
|
|
8054
|
+
if (copiedImage) {
|
|
8055
|
+
this.#uploadFilesPreservingScroll(files, copiedImage.getAttribute("alt"));
|
|
8056
|
+
return true
|
|
8057
|
+
} else if (html && !this.#isLexicalClipboardData(clipboardData)) {
|
|
8058
|
+
this.contents.insertHtml(html, { tag: PASTE_TAG });
|
|
8059
|
+
return true
|
|
8060
|
+
} else if (files.length) {
|
|
8061
|
+
this.#uploadFilesPreservingScroll(files);
|
|
8062
|
+
return true
|
|
8063
|
+
} else {
|
|
8064
|
+
return false
|
|
8065
|
+
}
|
|
8066
|
+
} else {
|
|
8067
|
+
return false
|
|
8006
8068
|
}
|
|
8007
|
-
|
|
8008
|
-
return false
|
|
8009
8069
|
}
|
|
8010
8070
|
|
|
8011
8071
|
#isLexicalClipboardData(clipboardData) {
|
|
8012
8072
|
return Array.from(clipboardData.types).includes("application/x-lexical-editor")
|
|
8013
8073
|
}
|
|
8014
8074
|
|
|
8015
|
-
#
|
|
8016
|
-
if (
|
|
8017
|
-
|
|
8018
|
-
|
|
8019
|
-
const elementChildren = Array.from(doc.body.children);
|
|
8075
|
+
#copiedImage(html) {
|
|
8076
|
+
if (html) {
|
|
8077
|
+
const doc = parseHtml(html);
|
|
8078
|
+
const elementChildren = Array.from(doc.body.children);
|
|
8020
8079
|
|
|
8021
|
-
|
|
8080
|
+
if (elementChildren.length === 1 && elementChildren[0].tagName === "IMG") {
|
|
8081
|
+
return elementChildren[0]
|
|
8082
|
+
} else {
|
|
8083
|
+
return null
|
|
8084
|
+
}
|
|
8085
|
+
} else {
|
|
8086
|
+
return null
|
|
8087
|
+
}
|
|
8022
8088
|
}
|
|
8023
8089
|
|
|
8024
|
-
#uploadFilesPreservingScroll(files) {
|
|
8090
|
+
#uploadFilesPreservingScroll(files, altText) {
|
|
8025
8091
|
this.#preservingScrollPosition(() => {
|
|
8026
8092
|
if (files.length) {
|
|
8027
|
-
this.contents.uploadFiles(files, { selectLast: true });
|
|
8093
|
+
this.contents.uploadFiles(files, { selectLast: true, altText });
|
|
8028
8094
|
}
|
|
8029
8095
|
});
|
|
8030
8096
|
}
|
|
@@ -10324,6 +10390,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
10324
10390
|
#historyState = { undo: false, redo: false }
|
|
10325
10391
|
|
|
10326
10392
|
#validity = new Map()
|
|
10393
|
+
#validationAttempted = false
|
|
10327
10394
|
#validationTextArea = document.createElement("textarea")
|
|
10328
10395
|
#uploadRequests
|
|
10329
10396
|
#liveRegion
|
|
@@ -10408,10 +10475,13 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
10408
10475
|
}
|
|
10409
10476
|
|
|
10410
10477
|
requiredChangedCallback() {
|
|
10478
|
+
if (this.editorContentElement) this.#synchronizeAriaAttribute("aria-required");
|
|
10411
10479
|
if (this.isConnected) this.#requestValidityRefresh();
|
|
10412
10480
|
}
|
|
10413
10481
|
|
|
10414
10482
|
formResetCallback() {
|
|
10483
|
+
this.#validationAttempted = false;
|
|
10484
|
+
this.#synchronizeAriaAttribute("aria-invalid");
|
|
10415
10485
|
this.value = this.#initialValue;
|
|
10416
10486
|
this.editor.dispatchCommand(CLEAR_HISTORY_COMMAND, undefined);
|
|
10417
10487
|
}
|
|
@@ -10438,6 +10508,15 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
10438
10508
|
return this.internals.validity
|
|
10439
10509
|
}
|
|
10440
10510
|
|
|
10511
|
+
get validationMessage() {
|
|
10512
|
+
return this.internals.validationMessage
|
|
10513
|
+
}
|
|
10514
|
+
|
|
10515
|
+
setCustomValidity(message) {
|
|
10516
|
+
this.#validationTextArea.setCustomValidity(message);
|
|
10517
|
+
this.#refreshValidity();
|
|
10518
|
+
}
|
|
10519
|
+
|
|
10441
10520
|
checkValidity() {
|
|
10442
10521
|
return this.internals.checkValidity()
|
|
10443
10522
|
}
|
|
@@ -10675,6 +10754,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
10675
10754
|
this.#attachDebugHooks();
|
|
10676
10755
|
this.#attachToolbar();
|
|
10677
10756
|
this.#resetBeforeTurboCaches();
|
|
10757
|
+
this.#observeAriaAttributes();
|
|
10678
10758
|
|
|
10679
10759
|
this.#setInternalFormValue(this.value, { suppressEvent: true });
|
|
10680
10760
|
this.#synchronizeWithChanges();
|
|
@@ -10747,7 +10827,9 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
10747
10827
|
classList: "lexxy-editor__content",
|
|
10748
10828
|
contenteditable: true,
|
|
10749
10829
|
role: "textbox",
|
|
10750
|
-
"aria-multiline":
|
|
10830
|
+
"aria-multiline": this.supportsMultiLine,
|
|
10831
|
+
"aria-required": this.required,
|
|
10832
|
+
"aria-invalid": false,
|
|
10751
10833
|
"aria-label": this.#labelText,
|
|
10752
10834
|
placeholder: this.getAttribute("placeholder")
|
|
10753
10835
|
});
|
|
@@ -10780,6 +10862,46 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
10780
10862
|
return Array.from(this.attributes).filter(attribute => attribute.name.startsWith("aria-"))
|
|
10781
10863
|
}
|
|
10782
10864
|
|
|
10865
|
+
#observeAriaAttributes() {
|
|
10866
|
+
const observer = new MutationObserver(mutations => {
|
|
10867
|
+
for (const { attributeName } of mutations) {
|
|
10868
|
+
if (attributeName.startsWith("aria-")) {
|
|
10869
|
+
this.#synchronizeAriaAttribute(attributeName);
|
|
10870
|
+
} else if (attributeName === "single-line") {
|
|
10871
|
+
this.#synchronizeAriaAttribute("aria-multiline");
|
|
10872
|
+
}
|
|
10873
|
+
}
|
|
10874
|
+
});
|
|
10875
|
+
observer.observe(this, { attributes: true });
|
|
10876
|
+
|
|
10877
|
+
this.#listeners.track(
|
|
10878
|
+
() => observer.disconnect(),
|
|
10879
|
+
registerEventListener(this, "invalid", () => {
|
|
10880
|
+
this.#validationAttempted = true;
|
|
10881
|
+
this.#synchronizeAriaAttribute("aria-invalid");
|
|
10882
|
+
})
|
|
10883
|
+
);
|
|
10884
|
+
}
|
|
10885
|
+
|
|
10886
|
+
#synchronizeAriaAttribute(name) {
|
|
10887
|
+
const value = this.getAttribute(name) ?? this.#defaultAriaValue(name);
|
|
10888
|
+
if (value === null) {
|
|
10889
|
+
this.editorContentElement.removeAttribute(name);
|
|
10890
|
+
} else if (this.editorContentElement.getAttribute(name) !== String(value)) {
|
|
10891
|
+
this.editorContentElement.setAttribute(name, value);
|
|
10892
|
+
}
|
|
10893
|
+
}
|
|
10894
|
+
|
|
10895
|
+
#defaultAriaValue(name) {
|
|
10896
|
+
switch (name) {
|
|
10897
|
+
case "aria-label": return this.#labelText
|
|
10898
|
+
case "aria-required": return this.required
|
|
10899
|
+
case "aria-multiline": return this.supportsMultiLine
|
|
10900
|
+
case "aria-invalid": return this.#validationAttempted && !this.validity.valid
|
|
10901
|
+
default: return null
|
|
10902
|
+
}
|
|
10903
|
+
}
|
|
10904
|
+
|
|
10783
10905
|
#setInternalFormValue(html, { suppressEvent = false } = {}) {
|
|
10784
10906
|
const changed = html !== this.#previousInternalFormValue;
|
|
10785
10907
|
|
|
@@ -10836,11 +10958,16 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
10836
10958
|
#refreshValidity() {
|
|
10837
10959
|
this.#refreshInternalValidity();
|
|
10838
10960
|
const { validity, message } = this.#calculateValidity();
|
|
10839
|
-
this.internals.setValidity(validity, message, this.editorContentElement);
|
|
10961
|
+
this.internals.setValidity(validity, message, this.editorContentElement ?? undefined);
|
|
10962
|
+
if (this.editorContentElement) {
|
|
10963
|
+
this.#synchronizeAriaAttribute("aria-invalid");
|
|
10964
|
+
}
|
|
10840
10965
|
}
|
|
10841
10966
|
|
|
10842
10967
|
#refreshInternalValidity() {
|
|
10843
|
-
|
|
10968
|
+
if (this.editorContentElement) {
|
|
10969
|
+
this.#validationTextArea.required = this.required && this.isBlank;
|
|
10970
|
+
}
|
|
10844
10971
|
const flags = this.#validationTextArea.validity;
|
|
10845
10972
|
const message = this.#validationTextArea.validationMessage;
|
|
10846
10973
|
|
|
@@ -10855,12 +10982,14 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
10855
10982
|
// internal TextArea's ValidityState can contain `valid: true`
|
|
10856
10983
|
if (flags.valid === true) continue
|
|
10857
10984
|
|
|
10985
|
+
let hasError = false;
|
|
10858
10986
|
for (const flag in flags) {
|
|
10859
10987
|
if (flags[flag]) {
|
|
10860
10988
|
validity[flag] = true;
|
|
10861
|
-
|
|
10989
|
+
hasError = true;
|
|
10862
10990
|
}
|
|
10863
10991
|
}
|
|
10992
|
+
if (hasError) messages.push(message);
|
|
10864
10993
|
}
|
|
10865
10994
|
|
|
10866
10995
|
return { validity, message: messages.join("\n") }
|
|
@@ -11178,6 +11307,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
11178
11307
|
|
|
11179
11308
|
#resetValidity() {
|
|
11180
11309
|
this.#validity = new Map();
|
|
11310
|
+
this.#validationAttempted = false;
|
|
11181
11311
|
}
|
|
11182
11312
|
}
|
|
11183
11313
|
|
|
@@ -12123,6 +12253,111 @@ class CodeLanguagePicker extends HTMLElement {
|
|
|
12123
12253
|
}
|
|
12124
12254
|
}
|
|
12125
12255
|
|
|
12256
|
+
class AlternativeTextDialog {
|
|
12257
|
+
#editor
|
|
12258
|
+
#editorElement
|
|
12259
|
+
#dialog
|
|
12260
|
+
#input
|
|
12261
|
+
#nodeKey
|
|
12262
|
+
#elementToRefocus
|
|
12263
|
+
#listeners = new ListenerBin()
|
|
12264
|
+
|
|
12265
|
+
constructor(editorElement) {
|
|
12266
|
+
this.#editor = editorElement.editor;
|
|
12267
|
+
this.#editorElement = editorElement;
|
|
12268
|
+
this.#dialog = createElement("dialog", { className: "lexxy-alternative-text-dialog", ariaLabel: "Alternative text" });
|
|
12269
|
+
this.#input = createElement("textarea", { rows: 1, autofocus: true, ariaLabel: "Description", placeholder: "Keep it short, but enough to convey the context…" });
|
|
12270
|
+
|
|
12271
|
+
const title = createElement("h2", { textContent: "Describe the image for people who can't see it:" });
|
|
12272
|
+
|
|
12273
|
+
const actions = createElement("div", { className: "lexxy-alternative-text-dialog__actions" });
|
|
12274
|
+
const cancel = createElement("button", { type: "button", textContent: "Cancel" });
|
|
12275
|
+
const save = createElement("button", { type: "button", className: "lexxy-alternative-text-dialog__save", textContent: "Save" });
|
|
12276
|
+
actions.append(cancel, save);
|
|
12277
|
+
|
|
12278
|
+
const controls = createElement("div", { className: "lexxy-alternative-text-dialog__controls" });
|
|
12279
|
+
controls.append(this.#input, actions);
|
|
12280
|
+
this.#dialog.append(title, controls);
|
|
12281
|
+
editorElement.appendChild(this.#dialog);
|
|
12282
|
+
|
|
12283
|
+
this.#listeners.track(
|
|
12284
|
+
registerEventListener(cancel, "click", () => this.#close()),
|
|
12285
|
+
registerEventListener(save, "click", () => this.#save()),
|
|
12286
|
+
registerEventListener(this.#dialog, "keydown", this.#handleKeyDown),
|
|
12287
|
+
registerEventListener(document, "pointerdown", this.#closeOnClickOutside)
|
|
12288
|
+
);
|
|
12289
|
+
}
|
|
12290
|
+
|
|
12291
|
+
dispose() {
|
|
12292
|
+
this.#dialog.remove();
|
|
12293
|
+
this.#listeners.dispose();
|
|
12294
|
+
}
|
|
12295
|
+
|
|
12296
|
+
open(nodeKey) {
|
|
12297
|
+
const description = this.#editor.read(() => {
|
|
12298
|
+
const node = $getNodeByKey(nodeKey);
|
|
12299
|
+
if ($isActionTextAttachmentNode(node)) {
|
|
12300
|
+
return node.altText
|
|
12301
|
+
}
|
|
12302
|
+
});
|
|
12303
|
+
|
|
12304
|
+
if (description !== undefined) {
|
|
12305
|
+
this.#nodeKey = nodeKey;
|
|
12306
|
+
this.#input.value = description;
|
|
12307
|
+
this.#anchorBelowAttachment(nodeKey);
|
|
12308
|
+
this.#elementToRefocus = document.activeElement;
|
|
12309
|
+
this.#editorElement.querySelector("lexxy-toolbar")?.closeDropdowns();
|
|
12310
|
+
this.#dialog.show();
|
|
12311
|
+
this.#input.focus();
|
|
12312
|
+
}
|
|
12313
|
+
}
|
|
12314
|
+
|
|
12315
|
+
#anchorBelowAttachment(nodeKey) {
|
|
12316
|
+
const figureElement = this.#editor.getElementByKey(nodeKey);
|
|
12317
|
+
if (figureElement) {
|
|
12318
|
+
const rect = figureElement.getBoundingClientRect();
|
|
12319
|
+
const editorRect = this.#editorElement.getBoundingClientRect();
|
|
12320
|
+
const bottom = rect.bottom - editorRect.top - this.#editorElement.clientTop + this.#editorElement.scrollTop;
|
|
12321
|
+
this.#dialog.style.setProperty("--lexxy-anchor-bottom", `${bottom}px`);
|
|
12322
|
+
}
|
|
12323
|
+
}
|
|
12324
|
+
|
|
12325
|
+
#save() {
|
|
12326
|
+
const description = this.#input.value.trim();
|
|
12327
|
+
this.#editor.update(() => {
|
|
12328
|
+
const node = $getNodeByKey(this.#nodeKey);
|
|
12329
|
+
if ($isActionTextAttachmentNode(node) && node.altText !== description) {
|
|
12330
|
+
node.getWritable().altText = description;
|
|
12331
|
+
}
|
|
12332
|
+
}, { tag: [ HISTORY_PUSH_TAG, SKIP_DOM_SELECTION_TAG ] });
|
|
12333
|
+
this.#close();
|
|
12334
|
+
}
|
|
12335
|
+
|
|
12336
|
+
#handleKeyDown = (event) => {
|
|
12337
|
+
event.stopPropagation();
|
|
12338
|
+
if (event.key === "Escape") {
|
|
12339
|
+
this.#close();
|
|
12340
|
+
} else {
|
|
12341
|
+
trapFocusAtTabBoundary(this.#dialog, event);
|
|
12342
|
+
}
|
|
12343
|
+
}
|
|
12344
|
+
|
|
12345
|
+
// Dismissing by clicking elsewhere must leave focus where the click landed.
|
|
12346
|
+
#closeOnClickOutside = (event) => {
|
|
12347
|
+
if (this.#dialog.open && !this.#dialog.contains(event.target)) {
|
|
12348
|
+
this.#close({ refocus: false });
|
|
12349
|
+
}
|
|
12350
|
+
}
|
|
12351
|
+
|
|
12352
|
+
#close({ refocus = true } = {}) {
|
|
12353
|
+
this.#dialog.close();
|
|
12354
|
+
if (refocus && this.#elementToRefocus?.isConnected) {
|
|
12355
|
+
this.#elementToRefocus.focus();
|
|
12356
|
+
}
|
|
12357
|
+
this.#elementToRefocus = null;
|
|
12358
|
+
}
|
|
12359
|
+
}
|
|
12360
|
+
|
|
12126
12361
|
const DELETE_ICON = `<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
|
12127
12362
|
<path d="M11.2041 1.01074C12.2128 1.113 13 1.96435 13 3V4H15L15.1025 4.00488C15.6067 4.05621 16 4.48232 16 5C16 5.55228 15.5523 6 15 6H14.8457L14.1416 15.1533C14.0614 16.1953 13.1925 17 12.1475 17H5.85254L5.6582 16.9902C4.76514 16.9041 4.03607 16.2296 3.88184 15.3457L3.8584 15.1533L3.1543 6H3C2.44772 6 2 5.55228 2 5C2 4.44772 2.44772 4 3 4H5V3C5 1.89543 5.89543 1 7 1H11L11.2041 1.01074ZM5.85254 15H12.1475L12.8398 6H5.16016L5.85254 15ZM7 4H11V3H7V4Z"/>
|
|
12128
12363
|
</svg>`;
|
|
@@ -12130,6 +12365,8 @@ const DELETE_ICON = `<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"
|
|
|
12130
12365
|
class AttachmentToolbar extends HTMLElement {
|
|
12131
12366
|
#listeners = new ListenerBin()
|
|
12132
12367
|
#buttons = []
|
|
12368
|
+
#alternativeTextButton
|
|
12369
|
+
#alternativeTextDialog
|
|
12133
12370
|
#currentNodeKey = null
|
|
12134
12371
|
#reflowObserver = new ResizeObserver(() => this.#updatePosition())
|
|
12135
12372
|
#presentationObserver = new MutationObserver(() => this.#refresh())
|
|
@@ -12156,6 +12393,7 @@ class AttachmentToolbar extends HTMLElement {
|
|
|
12156
12393
|
this.#listeners.dispose();
|
|
12157
12394
|
this.#reflowObserver.disconnect();
|
|
12158
12395
|
this.#presentationObserver.disconnect();
|
|
12396
|
+
this.#alternativeTextDialog?.dispose();
|
|
12159
12397
|
}
|
|
12160
12398
|
|
|
12161
12399
|
get #editor() {
|
|
@@ -12170,7 +12408,9 @@ class AttachmentToolbar extends HTMLElement {
|
|
|
12170
12408
|
this.innerHTML = "";
|
|
12171
12409
|
|
|
12172
12410
|
const container = createElement("div", { className: "lexxy-floating-controls__group" });
|
|
12411
|
+
container.appendChild(this.#createAlternativeTextButton());
|
|
12173
12412
|
container.appendChild(this.#createRemoveButton());
|
|
12413
|
+
this.#alternativeTextDialog = new AlternativeTextDialog(this.#editorElement);
|
|
12174
12414
|
this.appendChild(container);
|
|
12175
12415
|
|
|
12176
12416
|
this.#buttons = Array.from(this.querySelectorAll("button"));
|
|
@@ -12203,6 +12443,22 @@ class AttachmentToolbar extends HTMLElement {
|
|
|
12203
12443
|
return this.#currentNodeKey !== null
|
|
12204
12444
|
}
|
|
12205
12445
|
|
|
12446
|
+
#createAlternativeTextButton() {
|
|
12447
|
+
this.#alternativeTextButton = createElement("button", {
|
|
12448
|
+
type: "button",
|
|
12449
|
+
className: "lexxy-attachment-toolbar__alternative-text",
|
|
12450
|
+
ariaLabel: "Alternative text",
|
|
12451
|
+
title: "Alternative text",
|
|
12452
|
+
ariaHasPopup: "dialog",
|
|
12453
|
+
tabIndex: -1
|
|
12454
|
+
});
|
|
12455
|
+
this.#alternativeTextButton.appendChild(createElement("span", null, "ALT"));
|
|
12456
|
+
this.#listeners.track(registerEventListener(this.#alternativeTextButton, "click", () => {
|
|
12457
|
+
this.#alternativeTextDialog.open(this.#currentNodeKey);
|
|
12458
|
+
}));
|
|
12459
|
+
return this.#alternativeTextButton
|
|
12460
|
+
}
|
|
12461
|
+
|
|
12206
12462
|
#navigateOrExit = (event) => {
|
|
12207
12463
|
if (event.key === "Escape") {
|
|
12208
12464
|
event.preventDefault();
|
|
@@ -12275,6 +12531,7 @@ class AttachmentToolbar extends HTMLElement {
|
|
|
12275
12531
|
|
|
12276
12532
|
this.#editor.getEditorState().read(() => {
|
|
12277
12533
|
const node = $getNodeByKey(nodeKey);
|
|
12534
|
+
this.#alternativeTextButton.hidden = !this.#canEditAlternativeText(node);
|
|
12278
12535
|
this.dataset.nodeType = node.getType();
|
|
12279
12536
|
this.dataset.presentation = this.#presentationOf(node, element);
|
|
12280
12537
|
|
|
@@ -12286,6 +12543,12 @@ class AttachmentToolbar extends HTMLElement {
|
|
|
12286
12543
|
});
|
|
12287
12544
|
}
|
|
12288
12545
|
|
|
12546
|
+
#canEditAlternativeText(node) {
|
|
12547
|
+
return this.#editorElement.dataset.actionTextSupportsAlt !== "false"
|
|
12548
|
+
&& node.getType() === "action_text_attachment"
|
|
12549
|
+
&& node.isPreviewableAttachment
|
|
12550
|
+
}
|
|
12551
|
+
|
|
12289
12552
|
#presentationOf(node, element) {
|
|
12290
12553
|
if ($isImageGalleryNode(node.getParent())) {
|
|
12291
12554
|
return "gallery"
|
|
@@ -12307,8 +12570,11 @@ class AttachmentToolbar extends HTMLElement {
|
|
|
12307
12570
|
if (figureElement) {
|
|
12308
12571
|
const rect = figureElement.getBoundingClientRect();
|
|
12309
12572
|
const editorRect = this.#editorElement.getBoundingClientRect();
|
|
12310
|
-
|
|
12311
|
-
|
|
12573
|
+
// We are positioned against the editor's padding box, but the rect measures its border box.
|
|
12574
|
+
const borderTop = this.#editorElement.clientTop;
|
|
12575
|
+
const borderLeft = this.#editorElement.clientLeft;
|
|
12576
|
+
this.style.setProperty("--lexxy-anchor-top", `${rect.top - editorRect.top - borderTop}px`);
|
|
12577
|
+
this.style.setProperty("--lexxy-anchor-left", `${rect.left - editorRect.left - borderLeft}px`);
|
|
12312
12578
|
this.style.setProperty("--lexxy-anchor-width", `${rect.width}px`);
|
|
12313
12579
|
this.style.setProperty("--lexxy-anchor-height", `${rect.height}px`);
|
|
12314
12580
|
this.#flipWhenOverflowing(editorRect);
|
|
@@ -12348,7 +12614,7 @@ class AttachmentToolbar extends HTMLElement {
|
|
|
12348
12614
|
#focusToolbarOnAltF10 = (event) => {
|
|
12349
12615
|
if (this.#hasSelectedNode && event.altKey && event.key === "F10") {
|
|
12350
12616
|
event.preventDefault();
|
|
12351
|
-
this.#buttons
|
|
12617
|
+
this.#buttons.find(isActiveAndVisible)?.focus();
|
|
12352
12618
|
return true
|
|
12353
12619
|
} else {
|
|
12354
12620
|
return false
|
|
@@ -855,7 +855,7 @@
|
|
|
855
855
|
|
|
856
856
|
.lexxy-table-control__more-menu-trigger {
|
|
857
857
|
aspect-ratio: auto;
|
|
858
|
-
box-shadow: 0 0 0 1px inset
|
|
858
|
+
box-shadow: 0 0 0 1px inset var(--lexxy-color-floating-controls-separator);
|
|
859
859
|
font-weight: normal;
|
|
860
860
|
padding: 0 1.25ch;
|
|
861
861
|
}
|
|
@@ -981,7 +981,7 @@
|
|
|
981
981
|
/* The anchor is measured in px from the editor's top-left, so these stay physical. */
|
|
982
982
|
left: calc(var(--lexxy-anchor-left) + var(--lexxy-anchor-width));
|
|
983
983
|
top: var(--lexxy-anchor-top);
|
|
984
|
-
transform: translate(-100
|
|
984
|
+
transform: translate(calc(-100% - 1.5ch), 1.5ch);
|
|
985
985
|
|
|
986
986
|
&[hidden] {
|
|
987
987
|
display: none;
|
|
@@ -1006,6 +1006,39 @@
|
|
|
1006
1006
|
}
|
|
1007
1007
|
}
|
|
1008
1008
|
|
|
1009
|
+
.lexxy-floating-controls__group {
|
|
1010
|
+
display: flex;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
button[hidden] {
|
|
1014
|
+
display: none;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
.lexxy-attachment-toolbar__alternative-text {
|
|
1018
|
+
aspect-ratio: auto;
|
|
1019
|
+
margin-inline-end: calc(var(--lexxy-toolbar-spacing) + 1px);
|
|
1020
|
+
padding-inline: 1ch;
|
|
1021
|
+
position: relative;
|
|
1022
|
+
|
|
1023
|
+
span {
|
|
1024
|
+
display: inline-block;
|
|
1025
|
+
font-size: 0.9em;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
&:after {
|
|
1029
|
+
background-color: var(--lexxy-color-floating-controls-separator);
|
|
1030
|
+
block-size: 1.125em;
|
|
1031
|
+
content: "";
|
|
1032
|
+
display: block;
|
|
1033
|
+
inline-size: 1px;
|
|
1034
|
+
inset-block: 0;
|
|
1035
|
+
inset-inline-end: calc(-1 * var(--lexxy-toolbar-spacing));
|
|
1036
|
+
margin: auto;
|
|
1037
|
+
pointer-events: none;
|
|
1038
|
+
position: absolute;
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1009
1042
|
.lexxy-attachment-toolbar__remove:hover,
|
|
1010
1043
|
.lexxy-attachment-toolbar__remove:focus-visible {
|
|
1011
1044
|
background-color: var(--lexxy-color-red);
|
|
@@ -1181,3 +1214,105 @@ action-text-attachment[content-type^="application/vnd.actiontext"] {
|
|
|
1181
1214
|
display: none;
|
|
1182
1215
|
}
|
|
1183
1216
|
}
|
|
1217
|
+
|
|
1218
|
+
.lexxy-alternative-text-dialog {
|
|
1219
|
+
background: var(--lexxy-color-canvas);
|
|
1220
|
+
border: 2px solid var(--lexxy-color-selected-hover);
|
|
1221
|
+
border-radius: calc(var(--lexxy-radius) + var(--lexxy-toolbar-gap));
|
|
1222
|
+
box-shadow: var(--lexxy-shadow);
|
|
1223
|
+
box-sizing: border-box;
|
|
1224
|
+
color: var(--lexxy-color-ink);
|
|
1225
|
+
font: inherit;
|
|
1226
|
+
inline-size: 60%;
|
|
1227
|
+
inset-block: calc(var(--lexxy-anchor-bottom) + var(--lexxy-toolbar-spacing)) auto;
|
|
1228
|
+
inset-inline: 0;
|
|
1229
|
+
margin-block: 0;
|
|
1230
|
+
margin-inline: auto;
|
|
1231
|
+
min-inline-size: min(20rem, 100%);
|
|
1232
|
+
overflow: auto;
|
|
1233
|
+
padding: var(--lexxy-toolbar-spacing);
|
|
1234
|
+
z-index: 3;
|
|
1235
|
+
|
|
1236
|
+
h2 {
|
|
1237
|
+
font-size: inherit;
|
|
1238
|
+
font-weight: bold;
|
|
1239
|
+
line-height: 1.5;
|
|
1240
|
+
margin: 0;
|
|
1241
|
+
padding-block-start: calc(0.25 * var(--lexxy-text-small));
|
|
1242
|
+
padding-inline: calc(0.5 * var(--lexxy-text-small));
|
|
1243
|
+
text-align: center;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
.lexxy-alternative-text-dialog__controls {
|
|
1247
|
+
align-items: stretch;
|
|
1248
|
+
display: flex;
|
|
1249
|
+
flex-direction: column;
|
|
1250
|
+
gap: 0.5em;
|
|
1251
|
+
margin-block-start: 0.5em;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
textarea {
|
|
1255
|
+
background: var(--lexxy-color-canvas);
|
|
1256
|
+
border: 1px solid var(--lexxy-color-ink-lighter);
|
|
1257
|
+
border-radius: var(--lexxy-radius);
|
|
1258
|
+
box-sizing: border-box;
|
|
1259
|
+
color: inherit;
|
|
1260
|
+
display: block;
|
|
1261
|
+
font: inherit;
|
|
1262
|
+
font-size: var(--lexxy-text-small);
|
|
1263
|
+
font-weight: normal;
|
|
1264
|
+
min-block-size: calc(2lh + 1em + 2px);
|
|
1265
|
+
min-inline-size: 0;
|
|
1266
|
+
padding: 0.5em;
|
|
1267
|
+
|
|
1268
|
+
@supports (field-sizing: content) {
|
|
1269
|
+
field-sizing: content;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
@supports (resize: none) {
|
|
1273
|
+
resize: none;
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
button {
|
|
1278
|
+
background-color: var(--lexxy-color-ink-lightest);
|
|
1279
|
+
block-size: var(--lexxy-toolbar-button-size);
|
|
1280
|
+
border: 0;
|
|
1281
|
+
border-radius: var(--lexxy-radius);
|
|
1282
|
+
color: currentColor;
|
|
1283
|
+
cursor: pointer;
|
|
1284
|
+
display: grid;
|
|
1285
|
+
font-size: var(--lexxy-text-small);
|
|
1286
|
+
inline-size: 100%;
|
|
1287
|
+
line-height: inherit;
|
|
1288
|
+
padding-inline: 2ch;
|
|
1289
|
+
place-items: center;
|
|
1290
|
+
|
|
1291
|
+
&:hover {
|
|
1292
|
+
opacity: 0.8;
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
.lexxy-alternative-text-dialog__save {
|
|
1297
|
+
background-color: var(--lexxy-color-accent-dark);
|
|
1298
|
+
color: var(--lexxy-color-ink-inverted);
|
|
1299
|
+
|
|
1300
|
+
&:hover {
|
|
1301
|
+
background-color: var(--lexxy-color-accent-medium);
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
textarea:focus-visible {
|
|
1306
|
+
outline: var(--lexxy-focus-ring-size) solid var(--lexxy-focus-ring-color);
|
|
1307
|
+
outline-offset: var(--lexxy-focus-ring-offset);
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
.lexxy-alternative-text-dialog__actions {
|
|
1311
|
+
display: flex;
|
|
1312
|
+
gap: 0.5em;
|
|
1313
|
+
|
|
1314
|
+
button {
|
|
1315
|
+
flex: 1;
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
--lexxy-color-selected-dark: var(--lexxy-color-blue);
|
|
36
36
|
--lexxy-color-selected-50: color-mix(in oklch, var(--lexxy-color-selected-dark) 50%, transparent);
|
|
37
37
|
--lexxy-color-code-bg: var(--lexxy-color-ink-lightest);
|
|
38
|
+
--lexxy-color-floating-controls-separator: rgba(128, 128, 128, 0.3);
|
|
38
39
|
|
|
39
40
|
/* Text color highlights */
|
|
40
41
|
--highlight-1: rgb(136, 118, 38);
|