@jsenv/navi 0.26.32 → 0.26.34

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,23 +20276,21 @@ 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
  }
20254
20282
  removeErrorMessage();
20255
20283
  setError(null);
20256
20284
 
20257
- const validationMessageTarget = requester || elementRef.current;
20285
+ const element = elementRef.current;
20286
+ const validationMessageTarget = requester || element;
20258
20287
  validationMessageTargetRef.current = validationMessageTarget;
20259
20288
 
20260
- dispatchCustomEvent("navi_action_start", {
20261
- detail: sharedActionEventDetail,
20262
- });
20289
+ dispatchPublicCustomEvent(
20290
+ element,
20291
+ "navi_action_start",
20292
+ sharedActionEventDetail,
20293
+ );
20263
20294
 
20264
20295
  return action[method]({
20265
20296
  reason: `"${event.type}" event on ${(() => {
@@ -20278,32 +20309,30 @@ const useExecuteAction = (
20278
20309
  return `<${tagName}>`;
20279
20310
  })()}`,
20280
20311
  onAbort: (reason) => {
20312
+ const element = elementRef.current;
20281
20313
  if (
20282
20314
  // at this stage the action side effect might have removed the <element> from the DOM
20283
20315
  // (in theory no because action side effect are batched to happen after)
20284
20316
  // but other side effects might do this
20285
- elementRef.current
20317
+ element
20286
20318
  ) {
20287
- dispatchCustomEvent("navi_action_abort", {
20288
- detail: {
20289
- ...sharedActionEventDetail,
20290
- reason,
20291
- },
20319
+ dispatchPublicCustomEvent(element, "navi_action_abort", {
20320
+ ...sharedActionEventDetail,
20321
+ reason,
20292
20322
  });
20293
20323
  }
20294
20324
  },
20295
20325
  onError: (error) => {
20326
+ const element = elementRef.current;
20296
20327
  if (
20297
20328
  // at this stage the action side effect might have removed the <element> from the DOM
20298
20329
  // (in theory no because action side effect are batched to happen after)
20299
20330
  // but other side effects might do this
20300
- elementRef.current
20331
+ element
20301
20332
  ) {
20302
- dispatchCustomEvent("navi_action_error", {
20303
- detail: {
20304
- ...sharedActionEventDetail,
20305
- error,
20306
- },
20333
+ dispatchPublicCustomEvent(element, "navi_action_error", {
20334
+ ...sharedActionEventDetail,
20335
+ error,
20307
20336
  });
20308
20337
  }
20309
20338
  if (errorEffect === "show_validation_message") {
@@ -20313,17 +20342,16 @@ const useExecuteAction = (
20313
20342
  }
20314
20343
  },
20315
20344
  onComplete: (data) => {
20345
+ const element = elementRef.current;
20316
20346
  if (
20317
20347
  // at this stage the action side effect might have removed the <element> from the DOM
20318
20348
  // (in theory no because action side effect are batched to happen after)
20319
20349
  // but other side effects might do this
20320
- elementRef.current
20350
+ element
20321
20351
  ) {
20322
- dispatchCustomEvent("navi_action_end", {
20323
- detail: {
20324
- ...sharedActionEventDetail,
20325
- data,
20326
- },
20352
+ dispatchPublicCustomEvent(element, "navi_action_end", {
20353
+ ...sharedActionEventDetail,
20354
+ data,
20327
20355
  });
20328
20356
  }
20329
20357
  },
@@ -32830,7 +32858,7 @@ const initDragTableColumnViaPointer = (pointerdownEvent, {
32830
32858
  onGrab,
32831
32859
  onDrag: gestureInfo => {
32832
32860
  },
32833
- resetPositionAfterRelease: true,
32861
+ releasePositionEffect: "commit",
32834
32862
  onRelease: gestureInfo => {
32835
32863
  {
32836
32864
  teardown();
@@ -33402,7 +33430,7 @@ const initResizeViaPointer = (pointerdownEvent, {
33402
33430
  onGrab?.();
33403
33431
  },
33404
33432
  onDrag,
33405
- resetPositionAfterRelease: true,
33433
+ releasePositionEffect: "cancel",
33406
33434
  onRelease: gestureInfo => {
33407
33435
  const sizeChange = axis === "x" ? gestureInfo.layout.xDelta : gestureInfo.layout.yDelta;
33408
33436
  const newSize = currentSize + sizeChange;
@@ -38015,5 +38043,5 @@ const UserSvg = () => jsx("svg", {
38015
38043
  })
38016
38044
  });
38017
38045
 
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 };
38046
+ 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
38047
  //# sourceMappingURL=jsenv_navi.js.map