@jsenv/dom 0.17.32 → 0.17.33

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 +61 -21
  2. package/package.json +1 -1
package/dist/jsenv_dom.js CHANGED
@@ -15686,7 +15686,7 @@ const visibleRectEffect = (
15686
15686
  onNaviPositionChange,
15687
15687
  );
15688
15688
  // Dispatched by applyNewPosition's own notifyPositionTransition
15689
- // around this ancestor's own left/top animation (distinct from
15689
+ // around this ancestor's own placement animation (distinct from
15690
15690
  // navi_position_change, fired once with the final target, not per
15691
15691
  // frame). The anchor this element is positioned against may live
15692
15692
  // inside that ancestor and be moving right now — rather than hiding
@@ -16008,7 +16008,10 @@ const toContainerAlignedPosition = (value) => {
16008
16008
  * axis never reads the attribute back itself (`positionAreaFixed` always wins).
16009
16009
  *
16010
16010
  * @param {HTMLElement} element - The element to position (position: absolute or
16011
- * fixed — detected from its own computed style, see the scroll offset comment below)
16011
+ * fixed — detected from its own computed style, see the scroll offset comment below),
16012
+ * laid out at its containing block's own origin: the returned `left`/`top` are meant
16013
+ * to be applied as a translate from there, see `applyNewPosition` for the whole
16014
+ * contract and for why the placement may not go through `left`/`top` themselves
16012
16015
  * @param {HTMLElement} [anchor] - The anchor element to position against. Omit (or pass
16013
16016
  * `null`/`undefined`) when there's no real anchor to dock `element` against a *container*
16014
16017
  * instead — see `container` below; in that mode, "top"/"bottom"/"left"/"right" are
@@ -16206,12 +16209,12 @@ const pickPositionRelativeTo = (
16206
16209
  const clampLeftBound = availableLeft;
16207
16210
  const clampRightBound = availableRight;
16208
16211
  // offsetWidth/offsetHeight (layout box), not getBoundingClientRect() (the
16209
- // painted/transformed box): the element being positioned may have an
16210
- // active CSS `scale`/`translate` transform mid-animation (e.g. a popover
16211
- // using animation="scale"/"grow", still at its @starting-style value the
16212
- // instant it's first shown) — getBoundingClientRect() would then report
16213
- // its *shrunk* transformed size, throwing off any math that centers/fits
16214
- // against the element's own dimensions.
16212
+ // painted/transformed box): the element being positioned is moved by
16213
+ // transforms its own placement translate (see applyNewPosition), plus
16214
+ // whatever `scale` an entrance animation happens to be playing at that
16215
+ // instant (a popover using animation="scaling"/"expand-*") — so
16216
+ // getBoundingClientRect() answers where it is painted, at its *shrunk*
16217
+ // mid-animation size, instead of the box being measured here.
16215
16218
  const elementWidth = element.offsetWidth;
16216
16219
  const elementHeight = element.offsetHeight;
16217
16220
  const anchorWidth = anchorRight - anchorLeft;
@@ -16720,11 +16723,32 @@ const notifyPositionTransition = (element, animation) => {
16720
16723
  };
16721
16724
 
16722
16725
  /**
16723
- * Applies a `pickPositionRelativeTo` result to `element`. `left`/`top` are
16724
- * set instantly (a scroll-triggered reposition should never lag its
16725
- * target); when `shouldTransition` is set (a resize-triggered reposition),
16726
- * the visual move is played out via `element.animate()` instead — kept
16727
- * independent of Popover/Dialog/Callout's own opacity/scale/display CSS
16726
+ * Applies a `pickPositionRelativeTo` result to `element`, as a `translate`.
16727
+ *
16728
+ * The two halves of that contract, which a caller must honor:
16729
+ *
16730
+ * 1. `element` stays laid out at its containing block's own origin — its CSS
16731
+ * must say `left: 0; top: 0` (and leave right/bottom `auto`). The computed
16732
+ * `left`/`top` are then applied as a translate from there, which lands the
16733
+ * box in exactly the same place `left: Npx` would have. The reason to go
16734
+ * through a transform at all is the *measuring*: an out-of-flow box with a
16735
+ * shrink-to-fit width can never be wider than "containing block width -
16736
+ * left", so a box placed with `left` reports a width that depends on where
16737
+ * it currently stands — and `pickPositionRelativeTo` decides which side to
16738
+ * place it on from that very width. Feeding a placement back into the
16739
+ * decision that produced it makes the element alternate sides forever, one
16740
+ * flip per reposition. Laid out at the origin, the measured width only ever
16741
+ * depends on the content and on the size caps below.
16742
+ * 2. The `translate` property belongs to this function alone. An animation on
16743
+ * the same element uses `scale` (which composes: individual transform
16744
+ * properties apply translate, then rotate, then scale, then `transform`, so
16745
+ * a scale never rescales the placement) or `transform` — see popup_css.js,
16746
+ * whose slide entrances translate through `transform` for that reason.
16747
+ *
16748
+ * The translate is set instantly (a scroll-triggered reposition should never
16749
+ * lag its target); when `shouldTransition` is set (a resize-triggered
16750
+ * reposition), the visual move is played out via `element.animate()` instead —
16751
+ * kept independent of Popover/Dialog/Callout's own opacity/scale/display CSS
16728
16752
  * transition on the same element, so neither can clobber the other (see
16729
16753
  * notifyPositionTransition's own doc for why a dedicated Animation over a
16730
16754
  * CSS one). Duration comes from `--popup-position-transition-duration`
@@ -16796,18 +16820,19 @@ const applyNewPosition = (
16796
16820
  // A single implicit keyframe turned out not to work here: the WAAPI
16797
16821
  // "neutral" start keyframe isn't frozen at `animate()` call time, it's
16798
16822
  // resolved from the underlying value when the animation is first
16799
- // *sampled* (the next frame) — by then `element.style.left`/`top` below
16823
+ // *sampled* (the next frame) — by then `element.style.translate` below
16800
16824
  // has already been overwritten with the new target, so start === end and
16801
16825
  // nothing visibly moves (observed as the dialog just jumping). Reading
16802
16826
  // the previous value ourselves, before overwriting it, and passing both
16803
16827
  // keyframes explicitly sidesteps that entirely.
16804
- const previousLeft = parseFloat(element.style.left) || left;
16805
- const previousTop = parseFloat(element.style.top) || top;
16828
+ const previousTranslate = parseTranslate(element.style.translate);
16829
+ const previousLeft = previousTranslate ? previousTranslate.x : left;
16830
+ const previousTop = previousTranslate ? previousTranslate.y : top;
16806
16831
  if (shouldTransition) {
16807
16832
  const animation = element.animate(
16808
16833
  [
16809
- { left: `${previousLeft}px`, top: `${previousTop}px` },
16810
- { left: `${left}px`, top: `${top}px` },
16834
+ { translate: `${previousLeft}px ${previousTop}px` },
16835
+ { translate: `${left}px ${top}px` },
16811
16836
  ],
16812
16837
  {
16813
16838
  duration: parseTransitionDurationMs(
@@ -16820,16 +16845,31 @@ const applyNewPosition = (
16820
16845
  );
16821
16846
  notifyPositionTransition(element, animation);
16822
16847
  }
16823
- // The specified `left`/`top` are set to their final target right away,
16848
+ // The specified translate is set to its final target right away,
16824
16849
  // regardless of `shouldTransition` — the animation above only plays the
16825
16850
  // visual move from the old position, it never becomes the actual
16826
16851
  // specified style (see notifyPositionTransition's own commitStyles for
16827
16852
  // why that matters once it ends).
16828
- element.style.left = `${left}px`;
16829
- element.style.top = `${top}px`;
16853
+ element.style.translate = `${left}px ${top}px`;
16830
16854
  dispatchCustomEvent(element, "navi_position_change");
16831
16855
  };
16832
16856
 
16857
+ // "42px 100px" as { x, y }. Anything else — unset, "none", a single-value
16858
+ // shorthand — reads as "never placed yet" (null), so a first placement has no
16859
+ // stale point to be animated from.
16860
+ const parseTranslate = (translate) => {
16861
+ if (!translate) {
16862
+ return null;
16863
+ }
16864
+ const [x, y] = translate.split(" ");
16865
+ const xNumber = parseFloat(x);
16866
+ const yNumber = parseFloat(y);
16867
+ if (Number.isNaN(xNumber) || Number.isNaN(yNumber)) {
16868
+ return null;
16869
+ }
16870
+ return { x: xNumber, y: yNumber };
16871
+ };
16872
+
16833
16873
  const [publishDebugger, subscribeDebugger] = createPubSub();
16834
16874
 
16835
16875
  const notifyDebuggerStart = () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/dom",
3
- "version": "0.17.32",
3
+ "version": "0.17.33",
4
4
  "type": "module",
5
5
  "description": "DOM utilities for writing frontend code",
6
6
  "repository": {