@jsenv/navi 0.26.32 → 0.26.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.
@@ -3,8 +3,8 @@ import { isValidElement, createContext, h, toChildArray, render, cloneElement }
3
3
  import { useErrorBoundary, useLayoutEffect, useEffect, useContext, useMemo, useRef, useState, useCallback, useImperativeHandle, useId } from "preact/hooks";
4
4
  import { jsxs, jsx, Fragment } from "preact/jsx-runtime";
5
5
  import { signal, effect, computed, batch, useSignal } from "@preact/signals";
6
- import { createIterableWeakSet, getElementSignature, mergeOneStyle, normalizeStyle, createPubSub, mergeTwoStyles, normalizeStyles, createGroupTransitionController, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, findBefore, findAfter, createValueEffect, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, allowWheelThrough, resolveCSSColor, createStyleController, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, resolveCSSSize, canInterceptKeys, activeElementSignal, hasCSSSizeUnit, resolveOklchLightness, contrastColor, initFocusGroup, elementIsFocusable, scrollIntoViewScoped, findFocusable, trapScrollInside, trapFocusInside, snapToPixel, dragAfterThreshold, getScrollContainer, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement } from "@jsenv/dom";
7
- export { contrastColor } from "@jsenv/dom";
6
+ import { createIterableWeakSet, getElementSignature, mergeOneStyle, normalizeStyle, createPubSub, dispatchInternalCustomEvent, mergeTwoStyles, normalizeStyles, createGroupTransitionController, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, findBefore, findAfter, createValueEffect, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, allowWheelThrough, dispatchPublicCustomEvent, resolveCSSColor, createStyleController, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, resolveCSSSize, canInterceptKeys, activeElementSignal, hasCSSSizeUnit, resolveOklchLightness, contrastColor, initFocusGroup, elementIsFocusable, dispatchCustomEvent, scrollIntoViewScoped, findFocusable, trapScrollInside, trapFocusInside, snapToPixel, dragAfterThreshold, getScrollContainer, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement } from "@jsenv/dom";
7
+ export { contrastColor, startDragToReorder } from "@jsenv/dom";
8
8
  import { prefixFirstAndIndentRemainingLines } from "@jsenv/humanize";
9
9
  import { createValidity } from "@jsenv/validity";
10
10
  import { Suspense, createPortal, forwardRef } from "preact/compat";
@@ -6347,6 +6347,7 @@ const FLOW_PROPS = {
6347
6347
 
6348
6348
  // not really related to flow but should be on the container element if any
6349
6349
  pointerEvents: PASS_THROUGH,
6350
+ viewTransitionName: PASS_THROUGH,
6350
6351
  };
6351
6352
  const OUTER_SPACING_PROPS = {
6352
6353
  margin: PASS_THROUGH,
@@ -7293,81 +7294,6 @@ const listenInputValueChange = (input, callback) => {
7293
7294
  return teardown;
7294
7295
  };
7295
7296
 
7296
- /**
7297
- * Navi uses three categories of custom events:
7298
- *
7299
- * 1. **Internal events** (`dispatchInternalCustomEvent`) — a component communicates
7300
- * with other navi components internally. Not meant to be observed from outside.
7301
- * They do not bubble so they stay contained within the subtree that handles them.
7302
- * Names often reflect their internal nature (e.g. `navi_pseudo_state_request_check`).
7303
- *
7304
- * 2. **Public events** (`dispatchPublicCustomEvent`) — a component exposes information
7305
- * about something that happened (e.g. `navi_list_select`). They bubble so any
7306
- * ancestor can observe them. These are part of the public API and should be documented.
7307
- *
7308
- * 3. **Request events** (`dispatchCustomEvent`) — code *outside* a component asks it
7309
- * to perform an action (e.g. `navi_list_request_open`). They are cancelable so the
7310
- * component can signal whether it handled the request. Names are prefixed
7311
- * with `request_` by convention.
7312
- */
7313
-
7314
- /**
7315
- * Dispatches an internal event on `el`.
7316
- * Does not bubble — stays within the local subtree.
7317
- */
7318
- const dispatchInternalCustomEvent = (
7319
- el,
7320
- customEventName,
7321
- customEventDetail,
7322
- ) => {
7323
- const customEvent = new CustomEvent(customEventName, {
7324
- detail: customEventDetail,
7325
- });
7326
- return el.dispatchEvent(customEvent);
7327
- };
7328
-
7329
- /**
7330
- * Dispatches a public event from `el`, announcing something that happened.
7331
- * Bubbles so any ancestor can observe it.
7332
- */
7333
- const dispatchPublicCustomEvent = (
7334
- el,
7335
- customEventName,
7336
- customEventDetail,
7337
- ) => {
7338
- const customEvent = new CustomEvent(customEventName, {
7339
- detail: resolveEventDetail(customEventDetail),
7340
- bubbles: true,
7341
- });
7342
- return el.dispatchEvent(customEvent);
7343
- };
7344
-
7345
- /**
7346
- * Dispatches a request event *at* `el`, asking it to perform an action.
7347
- * Cancelable — returns `false` if the component called `preventDefault()`,
7348
- * indicating it did not (or could not) handle the request.
7349
- * Names are conventionally prefixed with `request_` (e.g. `navi_list_request_open`).
7350
- */
7351
- const dispatchCustomEvent = (el, customEventName, customEventDetail) => {
7352
- const customEvent = new CustomEvent(customEventName, {
7353
- detail: resolveEventDetail(customEventDetail),
7354
- cancelable: true,
7355
- });
7356
- const result = el.dispatchEvent(customEvent);
7357
- return result;
7358
- };
7359
-
7360
- const resolveEventDetail = (customEventDetail) => {
7361
- const { event, ...rest } = customEventDetail ?? {};
7362
- let resolvedEvent;
7363
- if (event?.detail?.event !== undefined) {
7364
- resolvedEvent = event.detail.event;
7365
- } else if (event !== undefined) {
7366
- resolvedEvent = event;
7367
- }
7368
- return { ...rest, event: resolvedEvent };
7369
- };
7370
-
7371
7297
  const requestPseudoStateCheck = (element, detail) => {
7372
7298
  dispatchInternalCustomEvent(
7373
7299
  element,
@@ -7845,6 +7771,45 @@ definePseudoClass(":-navi-has-value", {
7845
7771
  });
7846
7772
  }
7847
7773
 
7774
+ {
7775
+ definePseudoClass(":-navi-drag-grabbed", {
7776
+ attribute: "navi-drag-grabbed",
7777
+ setup: (el, callback) => {
7778
+ const onGrab = () => {
7779
+ callback();
7780
+ const onRelease = () => {
7781
+ el.removeEventListener("navi_drag_release", onRelease);
7782
+ callback();
7783
+ };
7784
+ el.addEventListener("navi_drag_release", onRelease);
7785
+ };
7786
+ el.addEventListener("navi_drag_grab", onGrab);
7787
+ return () => {
7788
+ el.removeEventListener("navi_drag_grab", onGrab);
7789
+ };
7790
+ },
7791
+ test: (el) => el.hasAttribute("data-drag-grabbed"),
7792
+ });
7793
+ definePseudoClass(":-navi-dragging", {
7794
+ attribute: "navi-dragging",
7795
+ setup: (el, callback) => {
7796
+ const onStart = () => {
7797
+ callback();
7798
+ const onRelease = () => {
7799
+ el.removeEventListener("navi_drag_release", onRelease);
7800
+ callback();
7801
+ };
7802
+ el.addEventListener("navi_drag_release", onRelease);
7803
+ };
7804
+ el.addEventListener("navi_drag_start", onStart);
7805
+ return () => {
7806
+ el.removeEventListener("navi_drag_start", onStart);
7807
+ };
7808
+ },
7809
+ test: (el) => el.hasAttribute("data-dragging"),
7810
+ });
7811
+ }
7812
+
7848
7813
  const EMPTY_STATE = {};
7849
7814
  const initPseudoStyles = (
7850
7815
  element,
@@ -8150,8 +8115,11 @@ const useElementRefEffect = (externalRef, syncElement, deps) => {
8150
8115
  }
8151
8116
 
8152
8117
  if (!refCallbackRef.current) {
8153
- refCallbackRef.current = (el) => {
8118
+ const refCallback = (el) => {
8154
8119
  elRef.current = el;
8120
+ // Keep .current in sync immediately so useEffect callbacks that read
8121
+ // ref.current (e.g. usePartiallyHidden) see the element, not null.
8122
+ refCallback.current = el;
8155
8123
  if (externalRef) {
8156
8124
  if (typeof externalRef === "function") {
8157
8125
  externalRef(el);
@@ -8169,6 +8137,7 @@ const useElementRefEffect = (externalRef, syncElement, deps) => {
8169
8137
  prevDepsRef.current = undefined;
8170
8138
  }
8171
8139
  };
8140
+ refCallbackRef.current = refCallback;
8172
8141
  }
8173
8142
 
8174
8143
  const refCallback = refCallbackRef.current;
@@ -8176,6 +8145,50 @@ const useElementRefEffect = (externalRef, syncElement, deps) => {
8176
8145
  return refCallback;
8177
8146
  };
8178
8147
 
8148
+ /**
8149
+ * Tracks whether an element is fully visible in its scroll container and sets
8150
+ * the `navi-partially-hidden` attribute when any part of it is clipped.
8151
+ *
8152
+ * This is used to suppress `view-transition-name` on elements that are partially
8153
+ * outside the viewport or a scrollable container. Without this, a partially clipped
8154
+ * element would still participate in view transitions, producing ghost animations or
8155
+ * incorrect cross-fade effects.
8156
+ *
8157
+ * CSS usage:
8158
+ * ```css
8159
+ * [navi-partially-hidden] {
8160
+ * view-transition-name: none !important;
8161
+ * }
8162
+ * ```
8163
+ *
8164
+ * `Box` enables this hook automatically when a `viewTransitionName` prop is provided.
8165
+ *
8166
+ * @param {import("preact").RefObject} ref - Ref to the element to observe.
8167
+ * @param {boolean} enabled - Only observe when true (typically when view-transition-name is set).
8168
+ */
8169
+ const usePartiallyHidden = (ref, enabled) => {
8170
+ useEffect(() => {
8171
+ const el = ref.current;
8172
+ if (!el || !enabled) {
8173
+ return undefined;
8174
+ }
8175
+ const observer = new IntersectionObserver(
8176
+ ([entry]) => {
8177
+ if (entry.intersectionRatio >= 0.99) {
8178
+ el.removeAttribute("navi-partially-hidden");
8179
+ } else {
8180
+ el.setAttribute("navi-partially-hidden", "");
8181
+ }
8182
+ },
8183
+ { threshold: 0.99 },
8184
+ );
8185
+ observer.observe(el);
8186
+ return () => {
8187
+ observer.disconnect();
8188
+ };
8189
+ }, [enabled]);
8190
+ };
8191
+
8179
8192
  installImportMetaCssBuild(import.meta);/**
8180
8193
  * Box - A Swiss Army Knife for Layout
8181
8194
  *
@@ -8308,6 +8321,12 @@ import.meta.css = [/* css */`
8308
8321
  [hidden] {
8309
8322
  display: none !important;
8310
8323
  }
8324
+
8325
+ /* Partially hidden (or fully hidden) element should not participate in view transition no matter what */
8326
+ /* Otherwise they appear immedatly and fully visible from a fully/partially hidden state */
8327
+ [navi-partially-hidden] {
8328
+ view-transition-name: none !important;
8329
+ }
8311
8330
  `, "@jsenv/navi/src/box/box.jsx"];
8312
8331
  const PSEUDO_CLASSES_DEFAULT = [];
8313
8332
  const PSEUDO_ELEMENTS_DEFAULT = [];
@@ -8727,6 +8746,7 @@ const Box = props => {
8727
8746
  elementListeningPseudoState: visualEl === pseudoStateEl ? null : visualEl
8728
8747
  });
8729
8748
  }, styleDeps);
8749
+ usePartiallyHidden(ref, Boolean(rest.viewTransitionName));
8730
8750
  }
8731
8751
 
8732
8752
  // When hasChildFunction is used it means
@@ -8800,6 +8820,19 @@ const shouldInjectSeparatorBetween = (left, right) => {
8800
8820
  return true;
8801
8821
  };
8802
8822
 
8823
+ const ensureDocumentStartViewTransition = () => {
8824
+ if (document.startViewTransition) {
8825
+ return;
8826
+ }
8827
+ document.startViewTransition = (updateCallback) => {
8828
+ updateCallback();
8829
+ return {
8830
+ ready: Promise.resolve(),
8831
+ finished: Promise.resolve(),
8832
+ };
8833
+ };
8834
+ };
8835
+
8803
8836
  /**
8804
8837
  * Fix alignment behavior for flex/grid containers that use `height: 100%`.
8805
8838
  *
@@ -9074,7 +9107,7 @@ import.meta.css = [/* css */`
9074
9107
  .ui_transition[data-transitioning] .ui_transition_target_slot_background {
9075
9108
  display: block;
9076
9109
  }
9077
- `, "@jsenv/navi/src/ui_transition/ui_transition.js"];
9110
+ `, "@jsenv/navi/src/transition/ui_transition.js"];
9078
9111
  const CONTENT_ID_ATTRIBUTE = "data-content-id";
9079
9112
  const CONTENT_PHASE_ATTRIBUTE = "data-content-phase";
9080
9113
  const UNSET = {
@@ -20243,11 +20276,6 @@ const useExecuteAction = (
20243
20276
  method,
20244
20277
  };
20245
20278
 
20246
- const dispatchCustomEvent = (type, options) => {
20247
- const element = elementRef.current;
20248
- const customEvent = new CustomEvent(type, options);
20249
- element.dispatchEvent(customEvent);
20250
- };
20251
20279
  if (resetErrorBoundary) {
20252
20280
  resetErrorBoundary();
20253
20281
  }
@@ -20257,9 +20285,7 @@ const useExecuteAction = (
20257
20285
  const validationMessageTarget = requester || elementRef.current;
20258
20286
  validationMessageTargetRef.current = validationMessageTarget;
20259
20287
 
20260
- dispatchCustomEvent("navi_action_start", {
20261
- detail: sharedActionEventDetail,
20262
- });
20288
+ dispatchPublicCustomEvent("navi_action_start", sharedActionEventDetail);
20263
20289
 
20264
20290
  return action[method]({
20265
20291
  reason: `"${event.type}" event on ${(() => {
@@ -20284,11 +20310,9 @@ const useExecuteAction = (
20284
20310
  // but other side effects might do this
20285
20311
  elementRef.current
20286
20312
  ) {
20287
- dispatchCustomEvent("navi_action_abort", {
20288
- detail: {
20289
- ...sharedActionEventDetail,
20290
- reason,
20291
- },
20313
+ dispatchPublicCustomEvent("navi_action_abort", {
20314
+ ...sharedActionEventDetail,
20315
+ reason,
20292
20316
  });
20293
20317
  }
20294
20318
  },
@@ -20299,11 +20323,9 @@ const useExecuteAction = (
20299
20323
  // but other side effects might do this
20300
20324
  elementRef.current
20301
20325
  ) {
20302
- dispatchCustomEvent("navi_action_error", {
20303
- detail: {
20304
- ...sharedActionEventDetail,
20305
- error,
20306
- },
20326
+ dispatchPublicCustomEvent("navi_action_error", {
20327
+ ...sharedActionEventDetail,
20328
+ error,
20307
20329
  });
20308
20330
  }
20309
20331
  if (errorEffect === "show_validation_message") {
@@ -20319,11 +20341,9 @@ const useExecuteAction = (
20319
20341
  // but other side effects might do this
20320
20342
  elementRef.current
20321
20343
  ) {
20322
- dispatchCustomEvent("navi_action_end", {
20323
- detail: {
20324
- ...sharedActionEventDetail,
20325
- data,
20326
- },
20344
+ dispatchPublicCustomEvent("navi_action_end", {
20345
+ ...sharedActionEventDetail,
20346
+ data,
20327
20347
  });
20328
20348
  }
20329
20349
  },
@@ -32830,7 +32850,7 @@ const initDragTableColumnViaPointer = (pointerdownEvent, {
32830
32850
  onGrab,
32831
32851
  onDrag: gestureInfo => {
32832
32852
  },
32833
- resetPositionAfterRelease: true,
32853
+ releasePositionEffect: "commit",
32834
32854
  onRelease: gestureInfo => {
32835
32855
  {
32836
32856
  teardown();
@@ -33402,7 +33422,7 @@ const initResizeViaPointer = (pointerdownEvent, {
33402
33422
  onGrab?.();
33403
33423
  },
33404
33424
  onDrag,
33405
- resetPositionAfterRelease: true,
33425
+ releasePositionEffect: "cancel",
33406
33426
  onRelease: gestureInfo => {
33407
33427
  const sizeChange = axis === "x" ? gestureInfo.layout.xDelta : gestureInfo.layout.yDelta;
33408
33428
  const newSize = currentSize + sizeChange;
@@ -38015,5 +38035,5 @@ const UserSvg = () => jsx("svg", {
38015
38035
  })
38016
38036
  });
38017
38037
 
38018
- export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, Box, Button, ButtonCopyToClipboard, Caption, CheckSvg, Checkbox, CheckboxList, CloseSvg, Code, Col, Colgroup, ConstructionSvg, Details, Dialog, DialogLayout, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, Input, Interpolate, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, List, ListItem, ListItemFooter, ListItemGroup, ListItemHeader, Loading, LoadingDotsSvg, LoadingIndicator, LoadingIndicatorFluid, MessageBox, Meter, Nav, NaviDebug, Paragraph, Popover, Quantity, QuantityIntl, Radio, RadioList, Route, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, Select, SelectionContext, Separator, SettingsSvg, SidePanel, StarSvg, SummaryMarker, Svg, Table, TableCell, Tbody, Text, Thead, Title, Tr, UITransition, UserSvg, ViewportLayout, actionIntegratedVia, actionRunEffect, addCustomMessage, anyMatchingRouteSignal, applySearch, arraySignalMembership, compareTwoJsValues, createAction, createAvailableConstraint, createIntl, createRequestCanceller, createSearch, createSelectionKeyboardShortcuts, enableDebugActions, enableDebugOnDocumentLoading, filterTableSelection, forwardActionRequested, installCustomConstraintValidation, isCellSelected, isColumnSelected, isRowSelected, localStorageSignal, moveArrayItemByIndex, navBack, navForward, navTo, openCallout, rawUrlPart, reload, removeCustomMessage, requestAction, requestListClose, requestListOpen, rerunActions, resource, route, routeAction, setBaseUrl, setupRoutes, stateSignal, stopLoad, stringifyTableSelectionValue, swapArrayItemByIndex, syncOwnedResourceToSignals, syncResourceToSignals, updateActions, useActionStatus, useArraySignalMembership, useAsyncData, useCalloutClose, useCancelPrevious, useCellGridFromRows, useConstraintValidityState, useDependenciesDiff, useDisplayedLayoutEffect, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useKeyboardShortcuts, useNavState, useOrderedColumns, useRouteStatus, useRunOnMount, useSearchText, useSelectRequestClose, useSelectableElement, useSelectionController, useSidePanelClose, useSignalSync, useStateArray, useTitleLevel, useUrlSearchParam, valueInLocalStorage, windowWidthSignal };
38038
+ export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, Box, Button, ButtonCopyToClipboard, Caption, CheckSvg, Checkbox, CheckboxList, CloseSvg, Code, Col, Colgroup, ConstructionSvg, Details, Dialog, DialogLayout, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, Input, Interpolate, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, List, ListItem, ListItemFooter, ListItemGroup, ListItemHeader, Loading, LoadingDotsSvg, LoadingIndicator, LoadingIndicatorFluid, MessageBox, Meter, Nav, NaviDebug, Paragraph, Popover, Quantity, QuantityIntl, Radio, RadioList, Route, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, Select, SelectionContext, Separator, SettingsSvg, SidePanel, StarSvg, SummaryMarker, Svg, Table, TableCell, Tbody, Text, Thead, Title, Tr, UITransition, UserSvg, ViewportLayout, actionIntegratedVia, actionRunEffect, addCustomMessage, anyMatchingRouteSignal, applySearch, arraySignalMembership, compareTwoJsValues, createAction, createAvailableConstraint, createIntl, createRequestCanceller, createSearch, createSelectionKeyboardShortcuts, enableDebugActions, enableDebugOnDocumentLoading, ensureDocumentStartViewTransition, filterTableSelection, forwardActionRequested, installCustomConstraintValidation, isCellSelected, isColumnSelected, isRowSelected, localStorageSignal, moveArrayItemByIndex, navBack, navForward, navTo, openCallout, rawUrlPart, reload, removeCustomMessage, requestAction, requestListClose, requestListOpen, rerunActions, resource, route, routeAction, setBaseUrl, setupRoutes, stateSignal, stopLoad, stringifyTableSelectionValue, swapArrayItemByIndex, syncOwnedResourceToSignals, syncResourceToSignals, updateActions, useActionStatus, useArraySignalMembership, useAsyncData, useCalloutClose, useCancelPrevious, useCellGridFromRows, useConstraintValidityState, useDependenciesDiff, useDisplayedLayoutEffect, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useKeyboardShortcuts, useNavState, useOrderedColumns, useRouteStatus, useRunOnMount, useSearchText, useSelectRequestClose, useSelectableElement, useSelectionController, useSidePanelClose, useSignalSync, useStateArray, useTitleLevel, useUrlSearchParam, valueInLocalStorage, windowWidthSignal };
38019
38039
  //# sourceMappingURL=jsenv_navi.js.map