@jsenv/navi 0.29.473 → 0.29.475
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/dist/dev/jsenv_navi.js +150 -45
- package/dist/dev/jsenv_navi.js.map +8 -8
- package/dist/jsenv_navi.js +134 -45
- package/dist/jsenv_navi.js.map +8 -8
- package/docs/control_value.md +3 -2
- package/docs/drag_interactions.md +21 -4
- package/docs/popup_open.md +10 -9
- package/package.json +2 -2
package/dist/jsenv_navi.js
CHANGED
|
@@ -18307,6 +18307,20 @@ const openCallout = (
|
|
|
18307
18307
|
// deduped away on the grounds that the anchor did not move.
|
|
18308
18308
|
callout.updatePosition = () =>
|
|
18309
18309
|
positioner.update(new CustomEvent(ELEMENT_SIZE_CHANGE));
|
|
18310
|
+
// Content that sizes itself once on screen (a shrink-wrapped BadgeList) does
|
|
18311
|
+
// so after the callout measured it. The size observer positionCallout puts
|
|
18312
|
+
// on the message sees the change, but answers a frame later (see observeSize
|
|
18313
|
+
// in @jsenv/dom for why); placed again on the spot, the first painted frame
|
|
18314
|
+
// is the right one. Stopped here, since a popup further up holds the callout
|
|
18315
|
+
// out of flow and does not resize with it.
|
|
18316
|
+
const onContentSizeChange = (sizeChangeEvent) => {
|
|
18317
|
+
sizeChangeEvent.stopPropagation();
|
|
18318
|
+
callout.updatePosition();
|
|
18319
|
+
};
|
|
18320
|
+
calloutElement.addEventListener("navi_size_change", onContentSizeChange);
|
|
18321
|
+
addTeardown(() => {
|
|
18322
|
+
calloutElement.removeEventListener("navi_size_change", onContentSizeChange);
|
|
18323
|
+
});
|
|
18310
18324
|
|
|
18311
18325
|
return callout;
|
|
18312
18326
|
};
|
|
@@ -22584,6 +22598,10 @@ const DROP_CONTAINER_ATTRIBUTE = "data-drop-container";
|
|
|
22584
22598
|
// every outcome — a list runs one way, and something being put somewhere goes
|
|
22585
22599
|
// wherever it is put.
|
|
22586
22600
|
const AXIS_ATTRIBUTE = "data-drag-axis";
|
|
22601
|
+
// How the items of a reordered list are laid out: "column", "row", or "wrap" for
|
|
22602
|
+
// side by side over several lines. Said nothing, it is read from where the
|
|
22603
|
+
// items stand.
|
|
22604
|
+
const FLOW_ATTRIBUTE = "data-reorder-flow";
|
|
22587
22605
|
const DELAY_ATTRIBUTE = "data-drag-delay";
|
|
22588
22606
|
const SLOP_ATTRIBUTE = "data-drag-slop";
|
|
22589
22607
|
const THRESHOLD_ATTRIBUTE$1 = "data-drag-threshold";
|
|
@@ -22636,14 +22654,24 @@ defineInteractionDetector({
|
|
|
22636
22654
|
const tellsWhenRefused = types.includes(REFUSE);
|
|
22637
22655
|
// Read at setup rather than at the press: a container can say it, and it is
|
|
22638
22656
|
// what the gesture is about rather than something it discovers.
|
|
22657
|
+
const itemFlow =
|
|
22658
|
+
element.closest(`[${FLOW_ATTRIBUTE}]`)?.getAttribute(FLOW_ATTRIBUTE) ||
|
|
22659
|
+
undefined;
|
|
22639
22660
|
const axisHolder = element.closest(`[${AXIS_ATTRIBUTE}]`);
|
|
22640
22661
|
const axes =
|
|
22641
22662
|
axisHolder?.getAttribute(AXIS_ATTRIBUTE) ||
|
|
22642
|
-
// A list runs one way, and reordering walks it
|
|
22643
|
-
//
|
|
22644
|
-
//
|
|
22645
|
-
//
|
|
22646
|
-
|
|
22663
|
+
// A list runs one way, and reordering walks it: down a column, along a
|
|
22664
|
+
// row, and both for a row that wraps — its copy changes line. Anything
|
|
22665
|
+
// else goes wherever the hand takes it: a board has places all around, a
|
|
22666
|
+
// thing put somewhere has two axes to be put along, a throw goes where it
|
|
22667
|
+
// was thrown and a thing that leaves goes out by whichever edge.
|
|
22668
|
+
(canReorder && !canLand && !canToss && !canLeave
|
|
22669
|
+
? itemFlow === "row"
|
|
22670
|
+
? "x"
|
|
22671
|
+
: itemFlow === "wrap"
|
|
22672
|
+
? "xy"
|
|
22673
|
+
: "y"
|
|
22674
|
+
: "xy");
|
|
22647
22675
|
// Where the places are, read at setup for the same reason as the axes: they
|
|
22648
22676
|
// are what the gesture is about, and it is what holds the two sides of it
|
|
22649
22677
|
// that says them.
|
|
@@ -22702,6 +22730,7 @@ defineInteractionDetector({
|
|
|
22702
22730
|
: undefined,
|
|
22703
22731
|
containerElement: dropContainer || undefined,
|
|
22704
22732
|
outsideOf,
|
|
22733
|
+
itemFlow,
|
|
22705
22734
|
getItemId: (itemElement) => itemElement.id,
|
|
22706
22735
|
direction: { x: axes.includes("x"), y: axes.includes("y") },
|
|
22707
22736
|
// Where it may go, said in the DOM. A thing that is put somewhere stays
|
|
@@ -46870,8 +46899,8 @@ const useControlgroupProps = (props, {
|
|
|
46870
46899
|
const [boundAction] = useActionBoundToOneParam(action, uiGroupStateController.uiStateSignal);
|
|
46871
46900
|
// Mirror single-input behaviour: a controlled value with nobody listening
|
|
46872
46901
|
// makes the group read-only so children don't appear interactive when they
|
|
46873
|
-
// can't change. A form or a group
|
|
46874
|
-
// what will send the value and hand a new one back.
|
|
46902
|
+
// can't change. A form or a group collecting it IS someone listening — that
|
|
46903
|
+
// is what will send the value and hand a new one back.
|
|
46875
46904
|
const listenedTo = useIsControlListenedTo(props, uiGroupStateController);
|
|
46876
46905
|
useRef(false);
|
|
46877
46906
|
const implicitReadOnly = uiGroupStateController.hasValueProp && !listenedTo;
|
|
@@ -70210,6 +70239,15 @@ const useDialogProps = props => {
|
|
|
70210
70239
|
freezeSize(dialogEl);
|
|
70211
70240
|
}
|
|
70212
70241
|
rectEffect.observeSize(dialogEl);
|
|
70242
|
+
// Content that sizes itself once on screen (a shrink-wrapped BadgeList)
|
|
70243
|
+
// says so, after the placement read it: placed again on the spot, where
|
|
70244
|
+
// observeSize answers a frame late. Stopped here, since a popup further up
|
|
70245
|
+
// holds this one out of flow and does not resize with it.
|
|
70246
|
+
const onContentSizeChange = sizeChangeEvent => {
|
|
70247
|
+
sizeChangeEvent.stopPropagation();
|
|
70248
|
+
rectEffect.check(new CustomEvent(ELEMENT_SIZE_CHANGE));
|
|
70249
|
+
};
|
|
70250
|
+
dialogEl.addEventListener("navi_size_change", onContentSizeChange);
|
|
70213
70251
|
// Exposed for the placement-props effect below, which needs to re-place an
|
|
70214
70252
|
// already-open dialog.
|
|
70215
70253
|
repositionRef.current = repositionEvent => {
|
|
@@ -70223,6 +70261,7 @@ const useDialogProps = props => {
|
|
|
70223
70261
|
};
|
|
70224
70262
|
addCleanup(() => {
|
|
70225
70263
|
repositionRef.current = null;
|
|
70264
|
+
dialogEl.removeEventListener("navi_size_change", onContentSizeChange);
|
|
70226
70265
|
rectEffect.disconnect();
|
|
70227
70266
|
});
|
|
70228
70267
|
// A descendant anchored to something inside this dialog (a Callout, a
|
|
@@ -72015,6 +72054,18 @@ const usePopoverProps = props => {
|
|
|
72015
72054
|
// while open (e.g. an expand/collapse toggle inside it) — not just when
|
|
72016
72055
|
// the anchor itself moves/resizes/re-anchors.
|
|
72017
72056
|
rectEffect.observeSize(popoverEl);
|
|
72057
|
+
// Content that sizes itself once on screen (a shrink-wrapped BadgeList)
|
|
72058
|
+
// says so, after the placement read it: placed again on the spot, where
|
|
72059
|
+
// observeSize answers a frame late. Stopped here, since a popup further up
|
|
72060
|
+
// holds this one out of flow and does not resize with it.
|
|
72061
|
+
const onContentSizeChange = sizeChangeEvent => {
|
|
72062
|
+
sizeChangeEvent.stopPropagation();
|
|
72063
|
+
rectEffect.check(new CustomEvent(ELEMENT_SIZE_CHANGE));
|
|
72064
|
+
};
|
|
72065
|
+
popoverEl.addEventListener("navi_size_change", onContentSizeChange);
|
|
72066
|
+
addCleanup(() => {
|
|
72067
|
+
popoverEl.removeEventListener("navi_size_change", onContentSizeChange);
|
|
72068
|
+
});
|
|
72018
72069
|
// Exposed for the placement-props effect above, which needs to re-place an
|
|
72019
72070
|
// already-open popover.
|
|
72020
72071
|
repositionRef.current = repositionEvent => {
|
|
@@ -77485,6 +77536,7 @@ const useListScrollSync = ({
|
|
|
77485
77536
|
useDuplicateHeaderWarning();
|
|
77486
77537
|
useStuckWindowWarning({
|
|
77487
77538
|
ref,
|
|
77539
|
+
getListEl,
|
|
77488
77540
|
scrollerElResolved,
|
|
77489
77541
|
renderWindowRef,
|
|
77490
77542
|
windowLeavesItemsOut,
|
|
@@ -79009,6 +79061,7 @@ const readItemsPerLine = listEl => {
|
|
|
79009
79061
|
// instead of scrolling — and there is no event missing to notice it by.
|
|
79010
79062
|
const useStuckWindowWarning = ({
|
|
79011
79063
|
ref,
|
|
79064
|
+
getListEl,
|
|
79012
79065
|
scrollerElResolved,
|
|
79013
79066
|
renderWindowRef,
|
|
79014
79067
|
windowLeavesItemsOut,
|
|
@@ -79554,28 +79607,40 @@ const findItemElementAt = (listEl, index) => {
|
|
|
79554
79607
|
}
|
|
79555
79608
|
return null;
|
|
79556
79609
|
};
|
|
79557
|
-
//
|
|
79558
|
-
//
|
|
79559
|
-
//
|
|
79560
|
-
//
|
|
79561
|
-
//
|
|
79562
|
-
//
|
|
79563
|
-
|
|
79564
|
-
// a screen they make grow. A list off screen is met at its edge nearest to the
|
|
79565
|
-
// screen, where it comes in.
|
|
79566
|
-
const readWindowGeometry = (scrollerEl, listEl, horizontal) => {
|
|
79610
|
+
// The screen the render window is framed on, along the scroll axis and in
|
|
79611
|
+
// viewport coordinates: the viewport of the box that scrolls the list — what
|
|
79612
|
+
// "150%" is a percentage of. A box that does not scroll — a list given no
|
|
79613
|
+
// height, as tall as its items — shows no more of them than the browser does:
|
|
79614
|
+
// its viewport is cut by the browser's, or a budget in % would draw items to
|
|
79615
|
+
// fill a screen they make grow.
|
|
79616
|
+
const readWindowScreen = (scrollerEl, horizontal) => {
|
|
79567
79617
|
const scrollerRect = getScrollerViewportRect(scrollerEl);
|
|
79568
|
-
let
|
|
79569
|
-
let
|
|
79618
|
+
let from = horizontal ? scrollerRect.left : scrollerRect.top;
|
|
79619
|
+
let to = horizontal ? scrollerRect.right : scrollerRect.bottom;
|
|
79570
79620
|
if (!canScrollerScroll(scrollerEl, horizontal ? "x" : "y")) {
|
|
79571
79621
|
const browserSize = horizontal ? document.documentElement.clientWidth : document.documentElement.clientHeight;
|
|
79572
|
-
if (
|
|
79573
|
-
|
|
79622
|
+
if (from < 0) {
|
|
79623
|
+
from = 0;
|
|
79574
79624
|
}
|
|
79575
|
-
if (
|
|
79576
|
-
|
|
79625
|
+
if (to > browserSize) {
|
|
79626
|
+
to = browserSize;
|
|
79577
79627
|
}
|
|
79578
79628
|
}
|
|
79629
|
+
return {
|
|
79630
|
+
from,
|
|
79631
|
+
to
|
|
79632
|
+
};
|
|
79633
|
+
};
|
|
79634
|
+
// What the render window is placed and sized on (see evaluateWindow), along
|
|
79635
|
+
// the scroll axis and in viewport coordinates: the size of the screen (see
|
|
79636
|
+
// readWindowScreen), the part of the list on it (the band), and the items
|
|
79637
|
+
// drawn, in the order they stand. A list off screen is met at its edge nearest
|
|
79638
|
+
// to the screen, where it comes in.
|
|
79639
|
+
const readWindowGeometry = (scrollerEl, listEl, horizontal) => {
|
|
79640
|
+
const {
|
|
79641
|
+
from: screenFrom,
|
|
79642
|
+
to: screenTo
|
|
79643
|
+
} = readWindowScreen(scrollerEl, horizontal);
|
|
79579
79644
|
const screenSize = screenTo - screenFrom;
|
|
79580
79645
|
if (screenSize < 1) {
|
|
79581
79646
|
return null;
|
|
@@ -83478,39 +83543,29 @@ const BadgeListShrinkWrap = ({
|
|
|
83478
83543
|
const registry = useBadgeRegistry(children, max !== undefined || fallback !== undefined);
|
|
83479
83544
|
const measureRef = useRef();
|
|
83480
83545
|
const visibleRef = useRef();
|
|
83546
|
+
|
|
83547
|
+
// In a closed popup the rows have no size to read: the list is measured when
|
|
83548
|
+
// it comes on screen, before the frame is painted.
|
|
83549
|
+
useDisplayedLayoutEffect(visibleRef, () => {
|
|
83550
|
+
shrinkWrapToWidestRow(visibleRef.current, measureRef.current);
|
|
83551
|
+
}, [children]);
|
|
83481
83552
|
useLayoutEffect(() => {
|
|
83482
83553
|
const measureEl = measureRef.current;
|
|
83483
83554
|
const visibleEl = visibleRef.current;
|
|
83484
83555
|
if (!measureEl || !visibleEl) {
|
|
83485
83556
|
return undefined;
|
|
83486
83557
|
}
|
|
83487
|
-
|
|
83488
|
-
let rafId;
|
|
83489
|
-
const measure = () => {
|
|
83490
|
-
visibleEl.style.width = "";
|
|
83491
|
-
// Clone the already-rendered DOM nodes instead of letting React/Preact
|
|
83492
|
-
// render the children a second time into the ghost: re-rendering would
|
|
83493
|
-
// instantiate Badge/Badge.Button a second time, double-registering their
|
|
83494
|
-
// controllers (and any other mount side effect) under the same id.
|
|
83495
|
-
measureEl.replaceChildren(...Array.from(visibleEl.children, child => child.cloneNode(true)));
|
|
83496
|
-
const optimalWidth = measureWidestChildRow(measureEl);
|
|
83497
|
-
if (optimalWidth !== null) {
|
|
83498
|
-
visibleEl.style.width = `${Math.ceil(optimalWidth)}px`;
|
|
83499
|
-
}
|
|
83500
|
-
};
|
|
83501
|
-
|
|
83502
|
-
// A single badge is a single row, and a single row is already the widest
|
|
83503
|
-
// one: there is nothing to narrow, and no width the list could be given
|
|
83504
|
-
// that would change that.
|
|
83558
|
+
// A single row whatever room it is given: nothing a resize could narrow.
|
|
83505
83559
|
if (visibleEl.children.length < 2) {
|
|
83506
|
-
visibleEl.style.width = "";
|
|
83507
|
-
measureEl.replaceChildren();
|
|
83508
83560
|
return undefined;
|
|
83509
83561
|
}
|
|
83510
|
-
|
|
83562
|
+
let observer;
|
|
83563
|
+
let rafId;
|
|
83511
83564
|
const onResize = () => {
|
|
83512
83565
|
cancelAnimationFrame(rafId);
|
|
83513
|
-
rafId = requestAnimationFrame(
|
|
83566
|
+
rafId = requestAnimationFrame(() => {
|
|
83567
|
+
shrinkWrapToWidestRow(visibleEl, measureEl);
|
|
83568
|
+
});
|
|
83514
83569
|
};
|
|
83515
83570
|
const outerParent = measureEl.parentElement?.parentElement;
|
|
83516
83571
|
if (outerParent) {
|
|
@@ -83556,6 +83611,40 @@ const BadgeListShrinkWrap = ({
|
|
|
83556
83611
|
})
|
|
83557
83612
|
);
|
|
83558
83613
|
};
|
|
83614
|
+
|
|
83615
|
+
// Narrows the list to its widest row, read from clones of its badges in the
|
|
83616
|
+
// ghost.
|
|
83617
|
+
//
|
|
83618
|
+
// What holds the list may have sized itself around the width it had before —
|
|
83619
|
+
// a callout pins its body to the width it measured — so a change is announced
|
|
83620
|
+
// with a bubbling navi_size_change for it to measure again, in the same frame.
|
|
83621
|
+
const shrinkWrapToWidestRow = (visibleEl, measureEl) => {
|
|
83622
|
+
// Not drawn (back in a closed popup): no row to read. The width is kept for
|
|
83623
|
+
// the next opening, which measures again before painting.
|
|
83624
|
+
if (visibleEl.getClientRects().length === 0) {
|
|
83625
|
+
return;
|
|
83626
|
+
}
|
|
83627
|
+
const widthBefore = visibleEl.style.width;
|
|
83628
|
+
visibleEl.style.width = "";
|
|
83629
|
+
if (visibleEl.children.length < 2) {
|
|
83630
|
+
// A single badge is a single row, and a single row is already the widest
|
|
83631
|
+
// one: there is nothing to narrow.
|
|
83632
|
+
measureEl.replaceChildren();
|
|
83633
|
+
} else {
|
|
83634
|
+
// Clone the already-rendered DOM nodes instead of letting React/Preact
|
|
83635
|
+
// render the children a second time into the ghost: re-rendering would
|
|
83636
|
+
// instantiate Badge/Badge.Button a second time, double-registering their
|
|
83637
|
+
// controllers (and any other mount side effect) under the same id.
|
|
83638
|
+
measureEl.replaceChildren(...Array.from(visibleEl.children, child => child.cloneNode(true)));
|
|
83639
|
+
const optimalWidth = measureWidestChildRow(measureEl);
|
|
83640
|
+
if (optimalWidth !== null) {
|
|
83641
|
+
visibleEl.style.width = `${Math.ceil(optimalWidth)}px`;
|
|
83642
|
+
}
|
|
83643
|
+
}
|
|
83644
|
+
if (visibleEl.style.width !== widthBefore) {
|
|
83645
|
+
dispatchPublicCustomEvent(visibleEl, "navi_size_change");
|
|
83646
|
+
}
|
|
83647
|
+
};
|
|
83559
83648
|
const BadgeListMaxLines = ({
|
|
83560
83649
|
fallback,
|
|
83561
83650
|
children,
|