@jsenv/dom 0.17.26 → 0.17.28

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.
Files changed (2) hide show
  1. package/dist/jsenv_dom.js +78 -28
  2. package/package.json +1 -1
package/dist/jsenv_dom.js CHANGED
@@ -2702,6 +2702,12 @@ const normalizeStyle = (
2702
2702
 
2703
2703
  if (colorPropertySet.has(propertyName)) {
2704
2704
  if (typeof value === "string") {
2705
+ if (context === "css") {
2706
+ // A string is already what the DOM takes, and it takes more of them
2707
+ // (system colors, relative color syntax) than this parser knows;
2708
+ // parsing it to rgba and back would only cost.
2709
+ return value;
2710
+ }
2705
2711
  if (isCSSKeyword(value)) {
2706
2712
  return value;
2707
2713
  }
@@ -9477,7 +9483,8 @@ const css$4 = /* css */`
9477
9483
  not to the gesture, and two fingers are never a drag. */
9478
9484
  touch-action: pinch-zoom;
9479
9485
  }
9480
- [data-drag-ignore] {
9486
+ [data-drag-ignore],
9487
+ [data-own-target] {
9481
9488
  -webkit-touch-callout: default;
9482
9489
  touch-action: auto;
9483
9490
  }
@@ -11931,7 +11938,8 @@ const css$1 = /* css */`
11931
11938
  [data-drag-source] {
11932
11939
  cursor: default;
11933
11940
  }
11934
- [data-drag-ignore] {
11941
+ [data-drag-ignore],
11942
+ [data-own-target] {
11935
11943
  cursor: auto;
11936
11944
  }
11937
11945
 
@@ -12026,6 +12034,14 @@ const css$1 = /* css */`
12026
12034
  // can start a drag, and they have to be true BEFORE anyone drags anything.
12027
12035
  import.meta.css = [css$1, "@jsenv/dom/src/interaction/drag/drag_to.js"];
12028
12036
 
12037
+ // What a press must not be read from at all. `data-drag-ignore` is said by
12038
+ // something whose press is its own business — a text one wants to select, a
12039
+ // control that reads the pointer itself. `data-own-target` is the same fact said
12040
+ // once for every gesture there is: an element declaring that a press landing on
12041
+ // it is aimed AT it, whatever it happens to sit inside (see also
12042
+ // DRAG_EXCLUDED_SELECTOR in drag_to_travel.js).
12043
+ const DRAG_IGNORED_SELECTOR = "[data-drag-ignore],[data-own-target]";
12044
+
12029
12045
  /**
12030
12046
  * Starts a drag-to-reorder interaction on a list item.
12031
12047
  *
@@ -12525,7 +12541,7 @@ const startDragTo = (event, effects, {
12525
12541
  } = {}) => {
12526
12542
  // An area that opted out of dragging (a text one wants to select, a control that
12527
12543
  // owns the gesture): the press there is none of our business.
12528
- if (event.target.closest && event.target.closest("[data-drag-ignore]")) {
12544
+ if (event.target.closest && event.target.closest(DRAG_IGNORED_SELECTOR)) {
12529
12545
  return undefined;
12530
12546
  }
12531
12547
  // A secondary button (right click and friends) is a context menu, not a grab.
@@ -12707,7 +12723,7 @@ const startDragToCarryCopy = (event, {
12707
12723
  }) => {
12708
12724
  // An area that opted out of dragging (a text one wants to select, a control
12709
12725
  // that owns the gesture): the press there is none of our business.
12710
- if (event.target.closest && event.target.closest("[data-drag-ignore]")) {
12726
+ if (event.target.closest && event.target.closest(DRAG_IGNORED_SELECTOR)) {
12711
12727
  return undefined;
12712
12728
  }
12713
12729
  // A secondary button (right click and friends) is a context menu, not a grab.
@@ -13452,8 +13468,11 @@ const DRAG_RESISTANCE = 0.3;
13452
13468
  // from one travels, and the click it would have made is swallowed on the way
13453
13469
  // out. A drag SOURCE is not either: it says which way it goes and only takes
13454
13470
  // that (see DRAG_SOURCE_AXES_ATTRIBUTE) — but a dedicated handle is, being a
13455
- // place whose only purpose is to be taken hold of, from the first pixel.
13456
- const DRAG_EXCLUDED_SELECTOR = ["input", "textarea", "select", '[contenteditable=""]', '[contenteditable="true"]', "[data-drag-handle]", "[data-no-drag-travel]"].join(",");
13471
+ // place whose only purpose is to be taken hold of, from the first pixel. And so
13472
+ // is an OWN TARGET: an element saying a press landing on it is aimed at IT,
13473
+ // which is the same sentence said to every gesture at once rather than to this
13474
+ // one (see DRAG_IGNORED_SELECTOR in drag_to.js).
13475
+ const DRAG_EXCLUDED_SELECTOR = ["input", "textarea", "select", '[contenteditable=""]', '[contenteditable="true"]', "[data-drag-handle]", "[data-no-drag-travel]", "[data-own-target]"].join(",");
13457
13476
 
13458
13477
  // Which axes a box travels on, one attribute per gesture, said in the DOM by
13459
13478
  // whoever owns the box: it is what a box ABOVE another reads to know the
@@ -19707,14 +19726,22 @@ const getMaxWidth = (
19707
19726
  * Summing their `width` values therefore over-counts the true line width.
19708
19727
  *
19709
19728
  * Instead we compute the bounding extent per line: track the minimum `left`
19710
- * and maximum `right` across all rects sharing the same rounded `top`, then
19711
- * use `right - left` as the line width. This is correct regardless of nesting
19729
+ * and maximum `right` across all rects of the same line, then use
19730
+ * `right - left` as the line width. This is correct regardless of nesting
19712
19731
  * depth and works well for regular inline text content.
19713
19732
  *
19714
- * Limitation: rects are grouped by `Math.round(r.top)`, so elements on the
19715
- * same visual line but with slightly different baselines (e.g. an icon taller
19716
- * than surrounding text) could be counted as separate lines. This is unlikely
19717
- * to matter in practice for normal text rendering.
19733
+ * ## Implementation note lines are found by vertical overlap
19734
+ *
19735
+ * Boxes sharing a line do not share a `top`: an inline icon sized to 1em and
19736
+ * sitting on the baseline, a superscript, an inline-block, all start a pixel
19737
+ * or a fraction of one away from the text beside them (and that fraction
19738
+ * moves with a sub-pixel scroll offset). Grouping rects by their rounded
19739
+ * `top` would count such a box as a line of its own, and the "longest line"
19740
+ * would come out as the text without it — short enough to make it wrap once
19741
+ * applied as a width. A rect therefore joins the line it overlaps vertically
19742
+ * by more than half the smaller of the two heights: boxes on one line share
19743
+ * most of their height, while the text rects of two consecutive lines overlap
19744
+ * by a sliver at most (a line-height below the font's content height).
19718
19745
  *
19719
19746
  * Limitation: `range.getClientRects()` returns rects for text nodes and inline
19720
19747
  * boxes as laid out in the flow, ignoring any `overflow: hidden` or `max-width`
@@ -19732,31 +19759,41 @@ const measureLongestVisualLineWidth = (el) => {
19732
19759
  const range = document.createRange();
19733
19760
  range.selectNodeContents(el);
19734
19761
 
19735
- const lineBoundsByTop = new Map();
19736
- for (const r of range.getClientRects()) {
19737
- if (r.width === 0) {
19762
+ const lines = [];
19763
+ for (const rect of range.getClientRects()) {
19764
+ if (rect.width === 0) {
19738
19765
  continue;
19739
19766
  }
19740
- const top = Math.round(r.top);
19741
- const existing = lineBoundsByTop.get(top);
19742
- if (existing === undefined) {
19743
- lineBoundsByTop.set(top, { left: r.left, right: r.right });
19744
- } else {
19745
- if (r.left < existing.left) {
19746
- existing.left = r.left;
19747
- }
19748
- if (r.right > existing.right) {
19749
- existing.right = r.right;
19750
- }
19767
+ const line = lines.find((candidate) => sharesLine(candidate, rect));
19768
+ if (line === undefined) {
19769
+ lines.push({
19770
+ top: rect.top,
19771
+ bottom: rect.bottom,
19772
+ left: rect.left,
19773
+ right: rect.right,
19774
+ });
19775
+ continue;
19776
+ }
19777
+ if (rect.top < line.top) {
19778
+ line.top = rect.top;
19779
+ }
19780
+ if (rect.bottom > line.bottom) {
19781
+ line.bottom = rect.bottom;
19782
+ }
19783
+ if (rect.left < line.left) {
19784
+ line.left = rect.left;
19785
+ }
19786
+ if (rect.right > line.right) {
19787
+ line.right = rect.right;
19751
19788
  }
19752
19789
  }
19753
19790
 
19754
- if (lineBoundsByTop.size <= 1) {
19791
+ if (lines.length <= 1) {
19755
19792
  return null;
19756
19793
  }
19757
19794
 
19758
19795
  let longestLineWidth = 0;
19759
- for (const { left, right } of lineBoundsByTop.values()) {
19796
+ for (const { left, right } of lines) {
19760
19797
  const w = right - left;
19761
19798
  if (w > longestLineWidth) {
19762
19799
  longestLineWidth = w;
@@ -19765,6 +19802,19 @@ const measureLongestVisualLineWidth = (el) => {
19765
19802
  return longestLineWidth;
19766
19803
  };
19767
19804
 
19805
+ const sharesLine = (line, rect) => {
19806
+ const overlapTop = rect.top > line.top ? rect.top : line.top;
19807
+ const overlapBottom = rect.bottom < line.bottom ? rect.bottom : line.bottom;
19808
+ const overlap = overlapBottom - overlapTop;
19809
+ if (overlap <= 0) {
19810
+ return false;
19811
+ }
19812
+ const rectHeight = rect.bottom - rect.top;
19813
+ const lineHeight = line.bottom - line.top;
19814
+ const smallerHeight = rectHeight < lineHeight ? rectHeight : lineHeight;
19815
+ return overlap > smallerHeight / 2;
19816
+ };
19817
+
19768
19818
  // Measures the width of the widest row of direct children.
19769
19819
  // Uses children's bounding rects (which respect overflow:hidden / max-width)
19770
19820
  // rather than Range.getClientRects() which sees through clipping boundaries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/dom",
3
- "version": "0.17.26",
3
+ "version": "0.17.28",
4
4
  "type": "module",
5
5
  "description": "DOM utilities for writing frontend code",
6
6
  "repository": {