@37signals/lexxy 0.9.25 → 0.9.26

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 CHANGED
@@ -21,6 +21,7 @@ A modern rich text editor for Rails.
21
21
 
22
22
  - Bug reports and pull requests are welcome on [GitHub Issues](https://github.com/basecamp/lexxy/issues). Help is especially welcome with [those tagged as "Help Wanted"](https://github.com/basecamp/lexxy/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22help%20wanted%22).
23
23
  - For questions and general Lexxy discussion, please use the [Discussions section](https://github.com/basecamp/lexxy/discussions)
24
+ - To build with a development version of Lexical, use `bin/link-to-local-lexical`.
24
25
 
25
26
  ## License
26
27
 
package/dist/lexxy.esm.js CHANGED
@@ -5923,6 +5923,7 @@ class PastedContentFormatter {
5923
5923
  this.#stripStyleElements();
5924
5924
  this.#unwrapPlaceholderAnchors();
5925
5925
  this.#stripTableCellColorStyles();
5926
+ this.#unwrapWrappedListChildren();
5926
5927
  this.#nestStrayListChildren();
5927
5928
  this.#stripStrayListChildren();
5928
5929
  return this.doc
@@ -5964,6 +5965,29 @@ class PastedContentFormatter {
5964
5965
  }
5965
5966
  }
5966
5967
 
5968
+ // Some sources wrap runs of <li>s in a stray element (e.g. a <div> directly
5969
+ // inside the list). Dissolve such wrappers so their items become direct
5970
+ // list children and any nested list they hide becomes visible to
5971
+ // #nestStrayListChildren below.
5972
+ #unwrapWrappedListChildren() {
5973
+ for (const list of this.doc.querySelectorAll("ol, ul")) {
5974
+ let wrapper = this.#wrappedListChild(list);
5975
+ while (wrapper) {
5976
+ wrapper.replaceWith(...wrapper.childNodes);
5977
+ wrapper = this.#wrappedListChild(list);
5978
+ }
5979
+ }
5980
+ }
5981
+
5982
+ #wrappedListChild(list) {
5983
+ for (const child of list.children) {
5984
+ if (child.tagName !== "LI" && !this.#isNestedList(child) && this.#containsListItems(child)) {
5985
+ return child
5986
+ }
5987
+ }
5988
+ return null
5989
+ }
5990
+
5967
5991
  // Some sources (e.g. Gmail) nest a sublist as a direct child of the parent
5968
5992
  // <ol>/<ul> instead of inside a <li>. Move each nested list into its
5969
5993
  // preceding <li> so the import preserves the nesting instead of dropping it.
@@ -5980,15 +6004,40 @@ class PastedContentFormatter {
5980
6004
  }
5981
6005
  }
5982
6006
 
5983
- // Only <li> is a valid child of a list; drop stray <br>/whitespace so the
5984
- // import doesn't wrap them into an empty leading item.
6007
+ // Only <li> is a valid child of a list. Unwrap remaining stray children
6008
+ // that still hold list items (a nested list with no preceding <li> to nest
6009
+ // under) so the items survive, and drop stray <br>/whitespace so the import
6010
+ // doesn't wrap them into an empty leading item.
5985
6011
  #stripStrayListChildren() {
5986
6012
  for (const list of this.doc.querySelectorAll("ol, ul")) {
5987
- for (const child of Array.from(list.childNodes)) {
5988
- if (child.nodeType === Node.ELEMENT_NODE && child.tagName === "LI") continue
5989
- list.removeChild(child);
6013
+ let stray = this.#firstStrayListChild(list);
6014
+ while (stray) {
6015
+ if (this.#containsListItems(stray)) {
6016
+ stray.replaceWith(...stray.childNodes);
6017
+ } else {
6018
+ stray.remove();
6019
+ }
6020
+
6021
+ stray = this.#firstStrayListChild(list);
6022
+ }
6023
+ }
6024
+ }
6025
+
6026
+ #firstStrayListChild(list) {
6027
+ for (const child of list.childNodes) {
6028
+ if (child.nodeType !== Node.ELEMENT_NODE || child.tagName !== "LI") {
6029
+ return child
5990
6030
  }
5991
6031
  }
6032
+ return null
6033
+ }
6034
+
6035
+ #isNestedList(node) {
6036
+ return node.nodeType === Node.ELEMENT_NODE && (node.tagName === "OL" || node.tagName === "UL")
6037
+ }
6038
+
6039
+ #containsListItems(node) {
6040
+ return node.nodeType === Node.ELEMENT_NODE && node.querySelector("li") !== null
5992
6041
  }
5993
6042
  }
5994
6043
 
@@ -9,6 +9,9 @@
9
9
  }
10
10
  --lexxy-toolbar-gap: 2px;
11
11
  --lexxy-toolbar-spacing: 0.5ch;
12
+ --lexxy-toolbar-padding: 2px;
13
+ --lexxy-toolbar-border-size: 1px;
14
+ --lexxy-toolbar-height: calc(var(--lexxy-toolbar-button-size) + 2 * var(--lexxy-toolbar-padding) + var(--lexxy-toolbar-border-size));
12
15
 
13
16
  border: 1px solid var(--lexxy-color-ink-lighter);
14
17
  border-radius: calc(var(--lexxy-radius) + var(--lexxy-toolbar-gap));
@@ -408,6 +411,11 @@
408
411
  }
409
412
  }
410
413
 
414
+ /* Reserve the mounted height before upgrade so it doesn't shift layout on mount. */
415
+ :where(lexxy-editor:not(:defined)) {
416
+ min-block-size: calc(var(--lexxy-toolbar-height) + var(--lexxy-editor-rows) + 2 * var(--lexxy-editor-padding));
417
+ }
418
+
411
419
  /* Placeholder */
412
420
  :where(.lexxy-editor--empty) {
413
421
  .lexxy-editor__content:not(:has(h1, h2, h3, h4, h5, h6, table, ul, ol, figure, .attachment-gallery))::before {
@@ -461,13 +469,13 @@
461
469
  :where(lexxy-toolbar) {
462
470
  --lexxy-toolbar-icon-size: 1.125em;
463
471
 
464
- border-block-end: 1px solid var(--lexxy-color-ink-lighter);
472
+ border-block-end: var(--lexxy-toolbar-border-size) solid var(--lexxy-color-ink-lighter);
465
473
  color: currentColor;
466
474
  display: flex;
467
475
  font-size: inherit;
468
476
  gap: var(--lexxy-toolbar-gap);
469
477
  max-inline-size: 100%;
470
- padding: 2px;
478
+ padding: var(--lexxy-toolbar-padding);
471
479
  position: relative;
472
480
 
473
481
  @container (width < 300px) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@37signals/lexxy",
3
- "version": "0.9.25",
3
+ "version": "0.9.26",
4
4
  "description": "Lexxy - A modern rich text editor for Rails.",
5
5
  "module": "dist/lexxy.esm.js",
6
6
  "type": "module",