@jsenv/navi 0.12.7 → 0.12.9

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.
@@ -1,10 +1,10 @@
1
1
  import { installImportMetaCss } from "./jsenv_navi_side_effects.js";
2
- import { createIterableWeakSet, createPubSub, createValueEffect, createStyleController, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, allowWheelThrough, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, activeElementSignal, canInterceptKeys, initUITransition, getElementSignature, normalizeStyle, mergeTwoStyles, appendStyles, normalizeStyles, resolveCSSSize, findBefore, findAfter, initFocusGroup, elementIsFocusable, pickLightOrDark, resolveColorLuminance, dragAfterThreshold, getScrollContainer, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement } from "@jsenv/dom";
2
+ import { createIterableWeakSet, createPubSub, createValueEffect, createStyleController, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, allowWheelThrough, resolveCSSColor, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, activeElementSignal, canInterceptKeys, createGroupTransitionController, getElementSignature, getBorderRadius, getBackground, preventIntermediateScrollbar, createOpacityTransition, stringifyStyle, mergeOneStyle, mergeTwoStyles, appendStyles, normalizeStyles, resolveCSSSize, findBefore, findAfter, initFocusGroup, elementIsFocusable, pickLightOrDark, resolveColorLuminance, dragAfterThreshold, getScrollContainer, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement } from "@jsenv/dom";
3
3
  import { prefixFirstAndIndentRemainingLines } from "@jsenv/humanize";
4
4
  import { effect, signal, computed, batch, useSignal } from "@preact/signals";
5
5
  import { useEffect, useRef, useCallback, useContext, useState, useLayoutEffect, useMemo, useErrorBoundary, useImperativeHandle, useId } from "preact/hooks";
6
6
  import { createContext, toChildArray, createRef, cloneElement } from "preact";
7
- import { jsx, jsxs, Fragment } from "preact/jsx-runtime";
7
+ import { jsxs, jsx, Fragment } from "preact/jsx-runtime";
8
8
  import { createPortal, forwardRef } from "preact/compat";
9
9
 
10
10
  const actionPrivatePropertiesWeakMap = new WeakMap();
@@ -2089,10 +2089,11 @@ const openCallout = (
2089
2089
  });
2090
2090
 
2091
2091
  addLevelEffect(() => {
2092
- anchorElement.style.setProperty(
2093
- "--callout-color",
2094
- `var(--navi-${level}-color)`,
2092
+ const levelColor = resolveCSSColor(
2093
+ `var(--${level}-color)`,
2094
+ calloutElement,
2095
2095
  );
2096
+ anchorElement.style.setProperty("--callout-color", levelColor);
2096
2097
  return () => {
2097
2098
  anchorElement.style.removeProperty("--callout-color");
2098
2099
  };
@@ -2200,7 +2201,13 @@ const ARROW_SPACING = 8;
2200
2201
  import.meta.css = /* css */ `
2201
2202
  @layer navi {
2202
2203
  .navi_callout {
2204
+ --success-color: #4caf50;
2205
+ --info-color: #2196f3;
2206
+ --warning-color: #ff9800;
2207
+ --error-color: #f44336;
2208
+
2203
2209
  --background-color: white;
2210
+ --icon-color: black;
2204
2211
  --padding: 8px;
2205
2212
  }
2206
2213
  }
@@ -2218,8 +2225,8 @@ import.meta.css = /* css */ `
2218
2225
  overflow: visible;
2219
2226
 
2220
2227
  --x-border-color: var(--x-level-color);
2228
+ --x-background-color: var(--x-level-color);
2221
2229
  --x-icon-color: var(--x-level-color);
2222
- --x-background-color: var(--background-color);
2223
2230
  }
2224
2231
 
2225
2232
  .navi_callout_frame {
@@ -2230,16 +2237,6 @@ import.meta.css = /* css */ `
2230
2237
  .navi_callout .navi_callout_border {
2231
2238
  fill: var(--x-border-color);
2232
2239
  }
2233
- .navi_callout[data-level="info"] {
2234
- --x-level-color: var(--navi-info-color);
2235
- }
2236
- .navi_callout[data-level="warning"] {
2237
- --x-level-color: var(--navi-warning-color);
2238
- }
2239
- .navi_callout[data-level="error"] {
2240
- --x-level-color: var(--navi-error-color);
2241
- }
2242
-
2243
2240
  .navi_callout_frame svg {
2244
2241
  position: absolute;
2245
2242
  inset: 0;
@@ -2248,7 +2245,6 @@ import.meta.css = /* css */ `
2248
2245
  .navi_callout_background {
2249
2246
  fill: var(--x-background-color);
2250
2247
  }
2251
-
2252
2248
  .navi_callout_box {
2253
2249
  position: relative;
2254
2250
  border-style: solid;
@@ -2316,6 +2312,19 @@ import.meta.css = /* css */ `
2316
2312
  max-height: 200px;
2317
2313
  overflow: auto;
2318
2314
  }
2315
+
2316
+ .navi_callout[data-level="success"] {
2317
+ --x-level-color: var(--success-color);
2318
+ }
2319
+ .navi_callout[data-level="info"] {
2320
+ --x-level-color: var(--info-color);
2321
+ }
2322
+ .navi_callout[data-level="warning"] {
2323
+ --x-level-color: var(--warning-color);
2324
+ }
2325
+ .navi_callout[data-level="error"] {
2326
+ --x-level-color: var(--error-color);
2327
+ }
2319
2328
  `;
2320
2329
 
2321
2330
  // HTML template for the callout
@@ -5203,11 +5212,11 @@ const useExternalValueSync = (
5203
5212
  }
5204
5213
  };
5205
5214
 
5206
- const UNSET = {};
5215
+ const UNSET$1 = {};
5207
5216
  const useInitialValue = (compute) => {
5208
- const initialValueRef = useRef(UNSET);
5217
+ const initialValueRef = useRef(UNSET$1);
5209
5218
  let initialValue = initialValueRef.current;
5210
- if (initialValue !== UNSET) {
5219
+ if (initialValue !== UNSET$1) {
5211
5220
  return initialValue;
5212
5221
  }
5213
5222
 
@@ -8576,6 +8585,909 @@ const useUrlSearchParam = (paramName) => {
8576
8585
  return [value, setSearchParamValue];
8577
8586
  };
8578
8587
 
8588
+ /**
8589
+ * Fix alignment behavior for flex/grid containers that use `height: 100%`.
8590
+ *
8591
+ * Context:
8592
+ * - When a flex/grid container has `height: 100%`, it is "height-locked".
8593
+ * - If its content becomes taller than the container, alignment rules like
8594
+ * `align-items: center` will cause content to be partially clipped.
8595
+ *
8596
+ * Goal:
8597
+ * - Center content only when it fits.
8598
+ * - Align content at start when it overflows.
8599
+ *
8600
+ * How:
8601
+ * - Temporarily remove height-constraint (`height:auto`) to measure natural height.
8602
+ * - Compare natural height to container height.
8603
+ * - Add/remove an attribute so CSS can adapt alignment.
8604
+ *
8605
+ * Usage:
8606
+ * monitorItemsOverflow(containerElement);
8607
+ *
8608
+ * CSS example:
8609
+ * .container { align-items: center; }
8610
+ * .container[data-items-height-overflow] { align-items: flex-start; }
8611
+ */
8612
+
8613
+
8614
+ const WIDTH_ATTRIBUTE_NAME = "data-items-width-overflow";
8615
+ const HEIGHT_ATTRIBUTE_NAME = "data-items-height-overflow";
8616
+ const monitorItemsOverflow = (container) => {
8617
+ let widthIsOverflowing;
8618
+ let heightIsOverflowing;
8619
+ const onItemsWidthOverflowChange = () => {
8620
+ if (widthIsOverflowing) {
8621
+ container.setAttribute(WIDTH_ATTRIBUTE_NAME, "");
8622
+ } else {
8623
+ container.removeAttribute(WIDTH_ATTRIBUTE_NAME);
8624
+ }
8625
+ };
8626
+ const onItemsHeightOverflowChange = () => {
8627
+ if (heightIsOverflowing) {
8628
+ container.setAttribute(HEIGHT_ATTRIBUTE_NAME, "");
8629
+ } else {
8630
+ container.removeAttribute(HEIGHT_ATTRIBUTE_NAME);
8631
+ }
8632
+ };
8633
+
8634
+ const update = () => {
8635
+ // Save current manual height constraint
8636
+ const prevWidth = container.style.width;
8637
+ const prevHeight = container.style.height;
8638
+ // Remove constraint → get true content dimension
8639
+ container.style.width = "auto";
8640
+ container.style.height = "auto";
8641
+ const naturalWidth = container.scrollWidth;
8642
+ const naturalHeight = container.scrollHeight;
8643
+ if (prevWidth) {
8644
+ container.style.width = prevWidth;
8645
+ } else {
8646
+ container.style.removeProperty("width");
8647
+ }
8648
+ if (prevHeight) {
8649
+ container.style.height = prevHeight;
8650
+ } else {
8651
+ container.style.removeProperty("height");
8652
+ }
8653
+
8654
+ const lockedWidth = container.clientWidth;
8655
+ const lockedHeight = container.clientHeight;
8656
+ const currentWidthIsOverflowing = naturalWidth > lockedWidth;
8657
+ const currentHeightIsOverflowing = naturalHeight > lockedHeight;
8658
+ if (currentWidthIsOverflowing !== widthIsOverflowing) {
8659
+ widthIsOverflowing = currentWidthIsOverflowing;
8660
+ onItemsWidthOverflowChange();
8661
+ }
8662
+ if (currentHeightIsOverflowing !== heightIsOverflowing) {
8663
+ heightIsOverflowing = currentHeightIsOverflowing;
8664
+ onItemsHeightOverflowChange();
8665
+ }
8666
+ };
8667
+
8668
+ const [teardown, addTeardown] = createPubSub();
8669
+
8670
+ update();
8671
+
8672
+ // mutation observer
8673
+ const mutationObserver = new MutationObserver(() => {
8674
+ update();
8675
+ });
8676
+ mutationObserver.observe(container, {
8677
+ childList: true,
8678
+ characterData: true,
8679
+ });
8680
+ addTeardown(() => {
8681
+ mutationObserver.disconnect();
8682
+ });
8683
+
8684
+ // resize observer
8685
+ const resizeObserver = new ResizeObserver(update);
8686
+ resizeObserver.observe(container);
8687
+ addTeardown(() => {
8688
+ resizeObserver.disconnect();
8689
+ });
8690
+
8691
+ const destroy = () => {
8692
+ teardown();
8693
+ container.removeAttribute(WIDTH_ATTRIBUTE_NAME);
8694
+ container.removeAttribute(HEIGHT_ATTRIBUTE_NAME);
8695
+ };
8696
+ return destroy;
8697
+ };
8698
+
8699
+ installImportMetaCss(import.meta);
8700
+ import.meta.css = /* css */ `
8701
+ * {
8702
+ box-sizing: border-box;
8703
+ }
8704
+
8705
+ .ui_transition {
8706
+ --transition-duration: 300ms;
8707
+ --justify-content: center;
8708
+ --align-items: center;
8709
+
8710
+ --x-transition-duration: var(--transition-duration);
8711
+ --x-justify-content: var(--justify-content);
8712
+ --x-align-items: var(--align-items);
8713
+
8714
+ position: relative;
8715
+ }
8716
+ /* Alignment controls */
8717
+ .ui_transition[data-align-x="start"] {
8718
+ --x-justify-content: flex-start;
8719
+ }
8720
+ .ui_transition[data-align-x="center"] {
8721
+ --x-justify-content: center;
8722
+ }
8723
+ .ui_transition[data-align-x="end"] {
8724
+ --x-justify-content: flex-end;
8725
+ }
8726
+ .ui_transition[data-align-y="start"] {
8727
+ --x-align-items: flex-start;
8728
+ }
8729
+ .ui_transition[data-align-y="center"] {
8730
+ --x-align-items: center;
8731
+ }
8732
+ .ui_transition[data-align-y="end"] {
8733
+ --x-align-items: flex-end;
8734
+ }
8735
+
8736
+ .ui_transition,
8737
+ .active_group,
8738
+ .previous_group,
8739
+ .target_slot,
8740
+ .previous_target_slot,
8741
+ .outgoing_slot,
8742
+ .previous_outgoing_slot {
8743
+ width: 100%;
8744
+ height: 100%;
8745
+ }
8746
+
8747
+ .target_slot,
8748
+ .outgoing_slot,
8749
+ .previous_target_slot,
8750
+ .previous_outgoing_slot {
8751
+ display: flex;
8752
+ align-items: var(--x-align-items);
8753
+ justify-content: var(--x-justify-content);
8754
+ }
8755
+ .target_slot[data-items-width-overflow],
8756
+ .previous_target_slot[data-items-width-overflow],
8757
+ .previous_target_slot[data-items-width-overflow],
8758
+ .previous_outgoing_slot[data-items-width-overflow] {
8759
+ --x-justify-content: flex-start;
8760
+ }
8761
+ .target_slot[data-items-height-overflow],
8762
+ .previous_slot[data-items-height-overflow],
8763
+ .previous_target_slot[data-items-height-overflow],
8764
+ .previous_outgoing_slot[data-items-height-overflow] {
8765
+ --x-align-items: flex-start;
8766
+ }
8767
+
8768
+ .active_group {
8769
+ position: relative;
8770
+ }
8771
+ .target_slot {
8772
+ position: relative;
8773
+ }
8774
+ .outgoing_slot,
8775
+ .previous_outgoing_slot {
8776
+ position: absolute;
8777
+ top: 0;
8778
+ left: 0;
8779
+ }
8780
+ .previous_group {
8781
+ position: absolute;
8782
+ inset: 0;
8783
+ }
8784
+ .ui_transition[data-only-previous-group] .previous_group {
8785
+ position: relative;
8786
+ }
8787
+
8788
+ .target_slot_background {
8789
+ position: absolute;
8790
+ top: 0;
8791
+ left: 0;
8792
+ z-index: -1;
8793
+ display: none;
8794
+ width: var(--target-slot-width, 100%);
8795
+ height: var(--target-slot-height, 100%);
8796
+ background: var(--target-slot-background, transparent);
8797
+ pointer-events: none;
8798
+ }
8799
+ .ui_transition[data-transitioning] .target_slot_background {
8800
+ display: block;
8801
+ }
8802
+ `;
8803
+
8804
+ const CONTENT_ID_ATTRIBUTE = "data-content-id";
8805
+ const CONTENT_PHASE_ATTRIBUTE = "data-content-phase";
8806
+ const UNSET = {
8807
+ domNodes: [],
8808
+ domNodesClone: [],
8809
+ isEmpty: true,
8810
+
8811
+ type: "unset",
8812
+ contentId: "unset",
8813
+ contentPhase: undefined,
8814
+ isContentPhase: false,
8815
+ isContent: false,
8816
+ toString: () => "unset",
8817
+ };
8818
+
8819
+ const isSameConfiguration = (configA, configB) => {
8820
+ return configA.toString() === configB.toString();
8821
+ };
8822
+
8823
+ const createUITransitionController = (
8824
+ root,
8825
+ {
8826
+ duration = 300,
8827
+ alignX = "center",
8828
+ alignY = "center",
8829
+ onStateChange = () => {},
8830
+ pauseBreakpoints = [],
8831
+ } = {},
8832
+ ) => {
8833
+ const debugConfig = {
8834
+ detection: root.hasAttribute("data-debug-detection"),
8835
+ size: root.hasAttribute("data-debug-size"),
8836
+ };
8837
+ const hasDebugLogs = debugConfig.size;
8838
+ const debugDetection = (message) => {
8839
+ if (!debugConfig.detection) return;
8840
+ console.debug(`[detection]`, message);
8841
+ };
8842
+ const debugSize = (message) => {
8843
+ if (!debugConfig.size) return;
8844
+ console.debug(`[size]`, message);
8845
+ };
8846
+
8847
+ const activeGroup = root.querySelector(".active_group");
8848
+ const targetSlot = root.querySelector(".target_slot");
8849
+ const outgoingSlot = root.querySelector(".outgoing_slot");
8850
+ const previousGroup = root.querySelector(".previous_group");
8851
+ const previousTargetSlot = previousGroup?.querySelector(
8852
+ ".previous_target_slot",
8853
+ );
8854
+ const previousOutgoingSlot = previousGroup?.querySelector(
8855
+ ".previous_outgoing_slot",
8856
+ );
8857
+
8858
+ if (
8859
+ !root ||
8860
+ !activeGroup ||
8861
+ !targetSlot ||
8862
+ !outgoingSlot ||
8863
+ !previousGroup ||
8864
+ !previousTargetSlot ||
8865
+ !previousOutgoingSlot
8866
+ ) {
8867
+ throw new Error(
8868
+ "createUITransitionController requires element with .active_group, .target_slot, .outgoing_slot, .previous_group, .previous_target_slot, and .previous_outgoing_slot elements",
8869
+ );
8870
+ }
8871
+
8872
+ // we maintain a background copy behind target slot to avoid showing
8873
+ // the body flashing during the fade-in
8874
+ const targetSlotBackground = document.createElement("div");
8875
+ targetSlotBackground.className = "target_slot_background";
8876
+ activeGroup.insertBefore(targetSlotBackground, targetSlot);
8877
+
8878
+ root.style.setProperty("--x-transition-duration", `${duration}ms`);
8879
+ outgoingSlot.setAttribute("inert", "");
8880
+ previousGroup.setAttribute("inert", "");
8881
+
8882
+ const detectConfiguration = (slot, { contentId, contentPhase } = {}) => {
8883
+ const domNodes = Array.from(slot.childNodes);
8884
+ if (!domNodes) {
8885
+ return UNSET;
8886
+ }
8887
+
8888
+ const isEmpty = domNodes.length === 0;
8889
+ let textNodeCount = 0;
8890
+ let elementNodeCount = 0;
8891
+ let firstElementNode;
8892
+ const domNodesClone = [];
8893
+ if (isEmpty) {
8894
+ if (contentPhase === undefined) {
8895
+ contentPhase = "empty";
8896
+ }
8897
+ } else {
8898
+ const contentIdSlotAttr = slot.getAttribute(CONTENT_ID_ATTRIBUTE);
8899
+ let contentIdChildAttr;
8900
+ for (const domNode of domNodes) {
8901
+ if (domNode.nodeType === Node.TEXT_NODE) {
8902
+ textNodeCount++;
8903
+ } else {
8904
+ if (!firstElementNode) {
8905
+ firstElementNode = domNode;
8906
+ }
8907
+ elementNodeCount++;
8908
+
8909
+ if (domNode.hasAttribute("data-content-phase")) {
8910
+ const contentPhaseAttr = domNode.getAttribute("data-content-phase");
8911
+ contentPhase = contentPhaseAttr || "attr";
8912
+ }
8913
+ if (domNode.hasAttribute("data-content-key")) {
8914
+ contentIdChildAttr = domNode.getAttribute("data-content-key");
8915
+ }
8916
+ }
8917
+ const domNodeClone = domNode.cloneNode(true);
8918
+ domNodesClone.push(domNodeClone);
8919
+ }
8920
+
8921
+ if (contentIdSlotAttr && contentIdChildAttr) {
8922
+ console.warn(
8923
+ `Slot and slot child both have a [${CONTENT_ID_ATTRIBUTE}]. Slot is ${contentIdSlotAttr} and child is ${contentIdChildAttr}, using the child.`,
8924
+ );
8925
+ }
8926
+ if (contentId === undefined) {
8927
+ contentId = contentIdChildAttr || contentIdSlotAttr || undefined;
8928
+ }
8929
+ }
8930
+ const isOnlyTextNodes = elementNodeCount === 0 && textNodeCount > 1;
8931
+ const singleElementNode = elementNodeCount === 1 ? firstElementNode : null;
8932
+
8933
+ contentId = contentId || getElementSignature(domNodes[0]);
8934
+ if (!contentPhase && isEmpty) {
8935
+ // Imagine code rendering null while switching to a new content
8936
+ // or even while staying on the same content.
8937
+ // In the UI we want to consider this as an "empty" phase.
8938
+ // meaning the ui will keep the same size until something else happens
8939
+ // This prevent layout shifts of code not properly handling
8940
+ // intermediate states.
8941
+ contentPhase = "empty";
8942
+ }
8943
+
8944
+ let width;
8945
+ let height;
8946
+ let borderRadius;
8947
+ let border;
8948
+ let background;
8949
+
8950
+ if (isEmpty) {
8951
+ debugSize(`measureSlot(".${slot.className}") -> it is empty`);
8952
+ } else if (singleElementNode) {
8953
+ const rect = singleElementNode.getBoundingClientRect();
8954
+ width = rect.width;
8955
+ height = rect.height;
8956
+ debugSize(`measureSlot(".${slot.className}") -> [${width}x${height}]`);
8957
+ borderRadius = getBorderRadius(singleElementNode);
8958
+ border = getComputedStyle(singleElementNode).border;
8959
+ background = getBackground(singleElementNode);
8960
+ } else {
8961
+ // text, multiple elements
8962
+ const rect = slot.getBoundingClientRect();
8963
+ width = rect.width;
8964
+ height = rect.height;
8965
+ debugSize(`measureSlot(".${slot.className}") -> [${width}x${height}]`);
8966
+ }
8967
+
8968
+ const commonProperties = {
8969
+ domNodes,
8970
+ domNodesClone,
8971
+ isEmpty,
8972
+ isOnlyTextNodes,
8973
+ singleElementNode,
8974
+
8975
+ width,
8976
+ height,
8977
+ borderRadius,
8978
+ border,
8979
+ background,
8980
+
8981
+ contentId,
8982
+ };
8983
+
8984
+ if (contentPhase) {
8985
+ return {
8986
+ ...commonProperties,
8987
+ type: "content_phase",
8988
+ contentPhase,
8989
+ isContentPhase: true,
8990
+ isContent: false,
8991
+ toString: () => `content(${contentId}).phase(${contentPhase})`,
8992
+ };
8993
+ }
8994
+ return {
8995
+ ...commonProperties,
8996
+ type: "content",
8997
+ contentPhase: undefined,
8998
+ isContentPhase: false,
8999
+ isContent: true,
9000
+ toString: () => `content(${contentId})`,
9001
+ };
9002
+ };
9003
+
9004
+ const targetSlotInitialConfiguration = detectConfiguration(targetSlot);
9005
+ const outgoingSlotInitialConfiguration = detectConfiguration(outgoingSlot, {
9006
+ contentPhase: "true",
9007
+ });
9008
+ let targetSlotConfiguration = targetSlotInitialConfiguration;
9009
+ let outgoingSlotConfiguration = outgoingSlotInitialConfiguration;
9010
+ let previousTargetSlotConfiguration = UNSET;
9011
+
9012
+ const updateSlotAttributes = () => {
9013
+ if (targetSlotConfiguration.isEmpty && outgoingSlotConfiguration.isEmpty) {
9014
+ root.setAttribute("data-only-previous-group", "");
9015
+ } else {
9016
+ root.removeAttribute("data-only-previous-group");
9017
+ }
9018
+ };
9019
+ const updateAlignment = () => {
9020
+ // Set data attributes for CSS-based alignment
9021
+ root.setAttribute("data-align-x", alignX);
9022
+ root.setAttribute("data-align-y", alignY);
9023
+ };
9024
+
9025
+ const moveConfigurationIntoSlot = (configuration, slot) => {
9026
+ slot.innerHTML = "";
9027
+ for (const domNode of configuration.domNodesClone) {
9028
+ slot.appendChild(domNode);
9029
+ }
9030
+ // in case border or stuff like that have changed we re-detect the config
9031
+ const updatedConfig = detectConfiguration(slot);
9032
+ if (slot === targetSlot) {
9033
+ targetSlotConfiguration = updatedConfig;
9034
+ } else if (slot === outgoingSlot) {
9035
+ outgoingSlotConfiguration = updatedConfig;
9036
+ } else if (slot === previousTargetSlot) {
9037
+ previousTargetSlotConfiguration = updatedConfig;
9038
+ } else if (slot === previousOutgoingSlot) ; else {
9039
+ throw new Error("Unknown slot for applyConfiguration");
9040
+ }
9041
+ };
9042
+
9043
+ updateAlignment();
9044
+
9045
+ let transitionType = "none";
9046
+ const groupTransitionOptions = {
9047
+ // debugBreakpoints: [0.25],
9048
+ pauseBreakpoints,
9049
+ lifecycle: {
9050
+ setup: () => {
9051
+ updateSlotAttributes();
9052
+ root.setAttribute("data-transitioning", "");
9053
+ onStateChange({ isTransitioning: true });
9054
+ return {
9055
+ teardown: () => {
9056
+ root.removeAttribute("data-transitioning");
9057
+ updateSlotAttributes(); // Update positioning after transition
9058
+ onStateChange({ isTransitioning: false });
9059
+ },
9060
+ };
9061
+ },
9062
+ },
9063
+ };
9064
+ const transitionController = createGroupTransitionController(
9065
+ groupTransitionOptions,
9066
+ );
9067
+
9068
+ const elementToClip = root;
9069
+ const morphContainerIntoTarget = () => {
9070
+ const morphTransitions = [];
9071
+ {
9072
+ // TODO: ideally when scrollContainer is document AND we transition
9073
+ // from a layout with scrollbar to a layout without
9074
+ // we have clip path detecting we go from a given width/height to a new width/height
9075
+ // that might just be the result of scrollbar appearing/disappearing
9076
+ // we should detect when this happens to avoid clipping what correspond to the scrollbar presence toggling
9077
+ const fromWidth = previousTargetSlotConfiguration.width || 0;
9078
+ const fromHeight = previousTargetSlotConfiguration.height || 0;
9079
+ const toWidth = targetSlotConfiguration.width || 0;
9080
+ const toHeight = targetSlotConfiguration.height || 0;
9081
+ debugSize(
9082
+ `transition from [${fromWidth}x${fromHeight}] to [${toWidth}x${toHeight}]`,
9083
+ );
9084
+ const restoreOverflow = preventIntermediateScrollbar(root, {
9085
+ fromWidth,
9086
+ fromHeight,
9087
+ toWidth,
9088
+ toHeight,
9089
+ onPrevent: ({ x, y, scrollContainer }) => {
9090
+ if (x) {
9091
+ debugSize(
9092
+ `Temporarily hiding horizontal overflow during transition on ${getElementSignature(scrollContainer)}`,
9093
+ );
9094
+ }
9095
+ if (y) {
9096
+ debugSize(
9097
+ `Temporarily hiding vertical overflow during transition on ${getElementSignature(scrollContainer)}`,
9098
+ );
9099
+ }
9100
+ },
9101
+ onRestore: () => {
9102
+ debugSize(`Restored overflow after transition`);
9103
+ },
9104
+ });
9105
+
9106
+ const onSizeTransitionFinished = () => {
9107
+ // Restore overflow when transition is complete
9108
+ restoreOverflow();
9109
+ };
9110
+
9111
+ // https://emilkowal.ski/ui/the-magic-of-clip-path
9112
+ const elementToClipRect = elementToClip.getBoundingClientRect();
9113
+ const elementToClipWidth = elementToClipRect.width;
9114
+ const elementToClipHeight = elementToClipRect.height;
9115
+ // Calculate where content is positioned within the large container
9116
+ const getAlignedPosition = (containerSize, contentSize, align) => {
9117
+ switch (align) {
9118
+ case "start":
9119
+ return 0;
9120
+ case "end":
9121
+ return containerSize - contentSize;
9122
+ case "center":
9123
+ default:
9124
+ return (containerSize - contentSize) / 2;
9125
+ }
9126
+ };
9127
+ // Position of "from" content within large container
9128
+ const fromLeft = getAlignedPosition(
9129
+ elementToClipWidth,
9130
+ fromWidth,
9131
+ alignX,
9132
+ );
9133
+ const fromTop = getAlignedPosition(
9134
+ elementToClipHeight,
9135
+ fromHeight,
9136
+ alignY,
9137
+ );
9138
+ // Position of target content within large container
9139
+ const targetLeft = getAlignedPosition(
9140
+ elementToClipWidth,
9141
+ toWidth,
9142
+ alignX,
9143
+ );
9144
+ const targetTop = getAlignedPosition(
9145
+ elementToClipHeight,
9146
+ toHeight,
9147
+ alignY,
9148
+ );
9149
+ debugSize(
9150
+ `Positions in container: from [${fromLeft},${fromTop}] ${fromWidth}x${fromHeight} to [${targetLeft},${targetTop}] ${toWidth}x${toHeight}`,
9151
+ );
9152
+ // Get border-radius values
9153
+ const fromBorderRadius =
9154
+ previousTargetSlotConfiguration.borderRadius || 0;
9155
+ const toBorderRadius = targetSlotConfiguration.borderRadius || 0;
9156
+ const startInsetTop = fromTop;
9157
+ const startInsetRight = elementToClipWidth - (fromLeft + fromWidth);
9158
+ const startInsetBottom = elementToClipHeight - (fromTop + fromHeight);
9159
+ const startInsetLeft = fromLeft;
9160
+
9161
+ const endInsetTop = targetTop;
9162
+ const endInsetRight = elementToClipWidth - (targetLeft + toWidth);
9163
+ const endInsetBottom = elementToClipHeight - (targetTop + toHeight);
9164
+ const endInsetLeft = targetLeft;
9165
+
9166
+ const startClipPath = `inset(${startInsetTop}px ${startInsetRight}px ${startInsetBottom}px ${startInsetLeft}px round ${fromBorderRadius}px)`;
9167
+ const endClipPath = `inset(${endInsetTop}px ${endInsetRight}px ${endInsetBottom}px ${endInsetLeft}px round ${toBorderRadius}px)`;
9168
+ // Create clip-path animation using Web Animations API
9169
+ const clipAnimation = elementToClip.animate(
9170
+ [{ clipPath: startClipPath }, { clipPath: endClipPath }],
9171
+ {
9172
+ duration,
9173
+ easing: "ease",
9174
+ fill: "forwards",
9175
+ },
9176
+ );
9177
+
9178
+ // Handle finish
9179
+ clipAnimation.finished
9180
+ .then(() => {
9181
+ // Clear clip-path to restore normal behavior
9182
+ elementToClip.style.clipPath = "";
9183
+ clipAnimation.cancel();
9184
+ onSizeTransitionFinished();
9185
+ })
9186
+ .catch(() => {
9187
+ // Animation was cancelled
9188
+ });
9189
+ clipAnimation.play();
9190
+ }
9191
+
9192
+ return morphTransitions;
9193
+ };
9194
+ const fadeInTargetSlot = () => {
9195
+ targetSlotBackground.style.setProperty(
9196
+ "--target-slot-background",
9197
+ stringifyStyle(targetSlotConfiguration.background, "background"),
9198
+ );
9199
+ targetSlotBackground.style.setProperty(
9200
+ "--target-slot-width",
9201
+ `${targetSlotConfiguration.width || 0}px`,
9202
+ );
9203
+ targetSlotBackground.style.setProperty(
9204
+ "--target-slot-height",
9205
+ `${targetSlotConfiguration.height || 0}px`,
9206
+ );
9207
+ return createOpacityTransition(targetSlot, 1, {
9208
+ from: 0,
9209
+ duration,
9210
+ styleSynchronizer: "inline_style",
9211
+ onFinish: (targetSlotOpacityTransition) => {
9212
+ targetSlotOpacityTransition.cancel();
9213
+ },
9214
+ });
9215
+ };
9216
+ const fadeOutPreviousGroup = () => {
9217
+ return createOpacityTransition(previousGroup, 0, {
9218
+ from: 1,
9219
+ duration,
9220
+ styleSynchronizer: "inline_style",
9221
+ onFinish: (previousGroupOpacityTransition) => {
9222
+ previousGroupOpacityTransition.cancel();
9223
+ previousGroup.style.opacity = "0"; // keep previous group visually hidden
9224
+ },
9225
+ });
9226
+ };
9227
+ const fadeOutOutgoingSlot = () => {
9228
+ return createOpacityTransition(outgoingSlot, 0, {
9229
+ duration,
9230
+ from: 1,
9231
+ styleSynchronizer: "inline_style",
9232
+ onFinish: (outgoingSlotOpacityTransition) => {
9233
+ outgoingSlotOpacityTransition.cancel();
9234
+ outgoingSlot.style.opacity = "0"; // keep outgoing slot visually hidden
9235
+ },
9236
+ });
9237
+ };
9238
+
9239
+ // content_to_content transition (uses previous_group)
9240
+ const applyContentToContentTransition = (toConfiguration) => {
9241
+ // 1. move target slot to previous
9242
+ moveConfigurationIntoSlot(targetSlotConfiguration, previousTargetSlot);
9243
+ targetSlotConfiguration = toConfiguration;
9244
+ // 2. move outgoing slot to previous
9245
+ moveConfigurationIntoSlot(outgoingSlotConfiguration, previousOutgoingSlot);
9246
+ moveConfigurationIntoSlot(UNSET, outgoingSlot);
9247
+
9248
+ const transitions = [
9249
+ ...morphContainerIntoTarget(),
9250
+ fadeInTargetSlot(),
9251
+ fadeOutPreviousGroup(),
9252
+ ];
9253
+ const transition = transitionController.update(transitions, {
9254
+ onFinish: () => {
9255
+ moveConfigurationIntoSlot(UNSET, previousTargetSlot);
9256
+ moveConfigurationIntoSlot(UNSET, previousOutgoingSlot);
9257
+ if (hasDebugLogs) {
9258
+ console.groupEnd();
9259
+ }
9260
+ },
9261
+ });
9262
+ transition.play();
9263
+ };
9264
+ // content_phase_to_content_phase transition (uses outgoing_slot)
9265
+ const applyContentPhaseToContentPhaseTransition = (toConfiguration) => {
9266
+ // 1. Move target slot to outgoing
9267
+ moveConfigurationIntoSlot(targetSlotConfiguration, outgoingSlot);
9268
+ targetSlotConfiguration = toConfiguration;
9269
+
9270
+ const transitions = [
9271
+ ...morphContainerIntoTarget(),
9272
+ fadeInTargetSlot(),
9273
+ fadeOutOutgoingSlot(),
9274
+ ];
9275
+ const transition = transitionController.update(transitions, {
9276
+ onFinish: () => {
9277
+ moveConfigurationIntoSlot(UNSET, outgoingSlot);
9278
+
9279
+ if (hasDebugLogs) {
9280
+ console.groupEnd();
9281
+ }
9282
+ },
9283
+ });
9284
+ transition.play();
9285
+ };
9286
+ // any_to_empty transition
9287
+ const applyToEmptyTransition = () => {
9288
+ // 1. move target slot to previous
9289
+ moveConfigurationIntoSlot(targetSlotConfiguration, previousTargetSlot);
9290
+ targetSlotConfiguration = UNSET;
9291
+ // 2. move outgoing slot to previous
9292
+ moveConfigurationIntoSlot(outgoingSlotConfiguration, previousOutgoingSlot);
9293
+ outgoingSlotConfiguration = UNSET;
9294
+
9295
+ const transitions = [...morphContainerIntoTarget(), fadeOutPreviousGroup()];
9296
+ const transition = transitionController.update(transitions, {
9297
+ onFinish: () => {
9298
+ moveConfigurationIntoSlot(UNSET, previousTargetSlot);
9299
+ moveConfigurationIntoSlot(UNSET, previousOutgoingSlot);
9300
+ if (hasDebugLogs) {
9301
+ console.groupEnd();
9302
+ }
9303
+ },
9304
+ });
9305
+ transition.play();
9306
+ };
9307
+ // Main transition method
9308
+ const transitionTo = (
9309
+ newContentElement,
9310
+ { contentPhase, contentId } = {},
9311
+ ) => {
9312
+ if (contentId) {
9313
+ targetSlot.setAttribute(CONTENT_ID_ATTRIBUTE, contentId);
9314
+ } else {
9315
+ targetSlot.removeAttribute(CONTENT_ID_ATTRIBUTE);
9316
+ }
9317
+ if (contentPhase) {
9318
+ targetSlot.setAttribute(CONTENT_PHASE_ATTRIBUTE, contentPhase);
9319
+ } else {
9320
+ targetSlot.removeAttribute(CONTENT_PHASE_ATTRIBUTE);
9321
+ }
9322
+ if (newContentElement) {
9323
+ targetSlot.innerHTML = "";
9324
+ targetSlot.appendChild(newContentElement);
9325
+ } else {
9326
+ targetSlot.innerHTML = "";
9327
+ }
9328
+ };
9329
+ // Reset to initial content
9330
+ const resetContent = () => {
9331
+ transitionController.cancel();
9332
+ moveConfigurationIntoSlot(targetSlotInitialConfiguration, targetSlot);
9333
+ moveConfigurationIntoSlot(outgoingSlotInitialConfiguration, outgoingSlot);
9334
+ moveConfigurationIntoSlot(UNSET, previousTargetSlot);
9335
+ moveConfigurationIntoSlot(UNSET, previousOutgoingSlot);
9336
+ };
9337
+
9338
+ const targetSlotEffect = (reasons) => {
9339
+ const fromConfiguration = targetSlotConfiguration;
9340
+ const toConfiguration = detectConfiguration(targetSlot);
9341
+ if (hasDebugLogs) {
9342
+ console.group(`targetSlotEffect()`);
9343
+ console.debug(`reasons:`);
9344
+ console.debug(`- ${reasons.join("\n- ")}`);
9345
+ }
9346
+ if (isSameConfiguration(fromConfiguration, toConfiguration)) {
9347
+ debugDetection(
9348
+ `already in desired state (${toConfiguration}) -> early return`,
9349
+ );
9350
+ if (hasDebugLogs) {
9351
+ console.groupEnd();
9352
+ }
9353
+ return;
9354
+ }
9355
+ const fromConfigType = fromConfiguration.type;
9356
+ const toConfigType = toConfiguration.type;
9357
+ transitionType = `${fromConfigType}_to_${toConfigType}`;
9358
+ debugDetection(
9359
+ `Prepare "${transitionType}" transition (${fromConfiguration} -> ${toConfiguration})`,
9360
+ );
9361
+ // content_to_empty / content_phase_to_empty
9362
+ if (toConfiguration.isEmpty) {
9363
+ applyToEmptyTransition();
9364
+ return;
9365
+ }
9366
+ // content_phase_to_content_phase
9367
+ if (fromConfiguration.isContentPhase && toConfiguration.isContentPhase) {
9368
+ applyContentPhaseToContentPhaseTransition(toConfiguration);
9369
+ return;
9370
+ }
9371
+ // content_phase_to_content
9372
+ if (fromConfiguration.isContentPhase && toConfiguration.isContent) {
9373
+ applyContentPhaseToContentPhaseTransition(toConfiguration);
9374
+ return;
9375
+ }
9376
+ // content_to_content_phase
9377
+ if (fromConfiguration.isContent && toConfiguration.isContentPhase) {
9378
+ applyContentPhaseToContentPhaseTransition(toConfiguration);
9379
+ return;
9380
+ }
9381
+ // content_to_content (default case)
9382
+ applyContentToContentTransition(toConfiguration);
9383
+ };
9384
+
9385
+ const [teardown, addTeardown] = createPubSub();
9386
+ {
9387
+ const mutationObserver = new MutationObserver((mutations) => {
9388
+ const reasonParts = [];
9389
+ for (const mutation of mutations) {
9390
+ if (mutation.type === "childList") {
9391
+ const added = mutation.addedNodes.length;
9392
+ const removed = mutation.removedNodes.length;
9393
+ if (added && removed) {
9394
+ reasonParts.push(`addedNodes(${added}) removedNodes(${removed})`);
9395
+ } else if (added) {
9396
+ reasonParts.push(`addedNodes(${added})`);
9397
+ } else {
9398
+ reasonParts.push(`removedNodes(${removed})`);
9399
+ }
9400
+ continue;
9401
+ }
9402
+ if (mutation.type === "attributes") {
9403
+ const { attributeName } = mutation;
9404
+ if (
9405
+ attributeName === CONTENT_ID_ATTRIBUTE ||
9406
+ attributeName === CONTENT_PHASE_ATTRIBUTE
9407
+ ) {
9408
+ const { oldValue } = mutation;
9409
+ if (oldValue === null) {
9410
+ const value = targetSlot.getAttribute(attributeName);
9411
+ reasonParts.push(
9412
+ value
9413
+ ? `added [${attributeName}=${value}]`
9414
+ : `added [${attributeName}]`,
9415
+ );
9416
+ } else if (targetSlot.hasAttribute(attributeName)) {
9417
+ const value = targetSlot.getAttribute(attributeName);
9418
+ reasonParts.push(`[${attributeName}] ${oldValue} -> ${value}`);
9419
+ } else {
9420
+ reasonParts.push(
9421
+ oldValue
9422
+ ? `removed [${attributeName}=${oldValue}]`
9423
+ : `removed [${attributeName}]`,
9424
+ );
9425
+ }
9426
+ }
9427
+ }
9428
+ }
9429
+
9430
+ if (reasonParts.length === 0) {
9431
+ return;
9432
+ }
9433
+ targetSlotEffect(reasonParts);
9434
+ });
9435
+ mutationObserver.observe(targetSlot, {
9436
+ childList: true,
9437
+ attributes: true,
9438
+ attributeFilter: [CONTENT_ID_ATTRIBUTE, CONTENT_PHASE_ATTRIBUTE],
9439
+ characterData: false,
9440
+ });
9441
+ addTeardown(() => {
9442
+ mutationObserver.disconnect();
9443
+ });
9444
+ }
9445
+ {
9446
+ const slots = [
9447
+ targetSlot,
9448
+ outgoingSlot,
9449
+ previousTargetSlot,
9450
+ previousOutgoingSlot,
9451
+ ];
9452
+ for (const slot of slots) {
9453
+ addTeardown(monitorItemsOverflow(slot));
9454
+ }
9455
+ }
9456
+
9457
+ const setDuration = (newDuration) => {
9458
+ duration = newDuration;
9459
+ // Update CSS variable immediately
9460
+ root.style.setProperty("--x-transition-duration", `${duration}ms`);
9461
+ };
9462
+ const setAlignment = (newAlignX, newAlignY) => {
9463
+ alignX = newAlignX;
9464
+ alignY = newAlignY;
9465
+ updateAlignment();
9466
+ };
9467
+
9468
+ return {
9469
+ updateContentId: (value) => {
9470
+ if (value) {
9471
+ targetSlot.setAttribute(CONTENT_ID_ATTRIBUTE, value);
9472
+ } else {
9473
+ targetSlot.removeAttribute(CONTENT_ID_ATTRIBUTE);
9474
+ }
9475
+ },
9476
+
9477
+ transitionTo,
9478
+ resetContent,
9479
+ setDuration,
9480
+ setAlignment,
9481
+ updateAlignment,
9482
+ setPauseBreakpoints: (value) => {
9483
+ groupTransitionOptions.pauseBreakpoints = value;
9484
+ },
9485
+ cleanup: () => {
9486
+ teardown();
9487
+ },
9488
+ };
9489
+ };
9490
+
8579
9491
  /**
8580
9492
  * UITransition
8581
9493
  *
@@ -8591,74 +9503,79 @@ const useUrlSearchParam = (paramName) => {
8591
9503
  *
8592
9504
  * Usage:
8593
9505
  * - Wrap dynamic content in <UITransition> to animate between states
8594
- * - Set a unique `data-content-key` on your rendered content to identify each content variant
9506
+ * - Set a unique `data-content-id` on your rendered content to identify each content variant
8595
9507
  * - Use `data-content-phase` to mark loading/error states for phase transitions
8596
9508
  * - Configure transition types and durations for both content and phase changes
8597
9509
  *
8598
9510
  * Example:
8599
9511
  *
8600
- * <UITransition
8601
- * transitionType="slide-left"
8602
- * transitionDuration={400}
8603
- * phaseTransitionType="cross-fade"
8604
- * phaseTransitionDuration={300}
8605
- * >
9512
+ * <UITransition>
8606
9513
  * {isLoading
8607
9514
  * ? <Spinner data-content-key={userId} data-content-phase />
8608
9515
  * : <UserProfile user={user} data-content-key={userId} />}
8609
9516
  * </UITransition>
8610
9517
  *
8611
- * When `data-content-key` changes, UITransition animates content transitions.
9518
+ * When `data-content-id` changes, UITransition animates content transitions.
8612
9519
  * When `data-content-phase` changes for the same key, it animates phase transitions.
8613
9520
  */
8614
9521
 
8615
- const ContentKeyContext = createContext();
9522
+ const UITransitionContentIdContext = createContext();
8616
9523
  const UITransition = ({
8617
9524
  children,
8618
- contentKey,
8619
- sizeTransition,
8620
- sizeTransitionDuration,
8621
- transitionType,
8622
- transitionDuration,
8623
- phaseTransitionType,
8624
- phaseTransitionDuration,
8625
- debugTransition,
9525
+ contentId,
9526
+ type,
9527
+ duration,
9528
+ debugDetection,
9529
+ debugContent,
9530
+ debugSize,
9531
+ disabled,
9532
+ uiTransitionRef,
8626
9533
  ...props
8627
9534
  }) => {
8628
- const [contentKeyFromContext, setContentKeyFromContext] = useState();
8629
- const contentKeyContextValue = useMemo(() => {
8630
- const keySet = new Set();
8631
- const onKeySetChange = () => {
8632
- setContentKeyFromContext(Array.from(keySet).join("|"));
9535
+ const contentIdRef = useRef(contentId);
9536
+ const updateContentId = () => {
9537
+ const uiTransition = uiTransitionRef.current;
9538
+ if (!uiTransition) {
9539
+ return;
9540
+ }
9541
+ const value = contentIdRef.current;
9542
+ uiTransition.updateContentId(value);
9543
+ };
9544
+ const uiTransitionContentIdContextValue = useMemo(() => {
9545
+ const set = new Set();
9546
+ const onSetChange = () => {
9547
+ const value = Array.from(set).join("|");
9548
+ contentIdRef.current = value;
9549
+ updateContentId();
8633
9550
  };
8634
- const update = (key, newKey) => {
8635
- if (!keySet.has(key)) {
8636
- console.warn(`UITransition: trying to update a key that does not exist: ${key}`);
9551
+ const update = (part, newPart) => {
9552
+ if (!set.has(part)) {
9553
+ console.warn(`UITransition: trying to update an id that does not exist: ${part}`);
8637
9554
  return;
8638
9555
  }
8639
- keySet.delete(key);
8640
- keySet.add(newKey);
8641
- onKeySetChange();
9556
+ set.delete(part);
9557
+ set.add(newPart);
9558
+ onSetChange();
8642
9559
  };
8643
- const add = key => {
8644
- if (!key) {
9560
+ const add = part => {
9561
+ if (!part) {
8645
9562
  return;
8646
9563
  }
8647
- if (keySet.has(key)) {
9564
+ if (set.has(part)) {
8648
9565
  return;
8649
9566
  }
8650
- keySet.add(key);
8651
- onKeySetChange();
9567
+ set.add(part);
9568
+ onSetChange();
8652
9569
  };
8653
- const remove = key => {
8654
- if (!key) {
9570
+ const remove = part => {
9571
+ if (!part) {
8655
9572
  return;
8656
9573
  }
8657
- if (!keySet.has(key)) {
9574
+ if (!set.has(part)) {
8658
9575
  return;
8659
9576
  }
8660
- keySet.delete(key);
8661
- onKeySetChange();
9577
+ set.delete(part);
9578
+ onSetChange();
8662
9579
  };
8663
9580
  return {
8664
9581
  add,
@@ -8666,43 +9583,51 @@ const UITransition = ({
8666
9583
  remove
8667
9584
  };
8668
9585
  }, []);
8669
- const effectiveContentKey = contentKey || contentKeyFromContext;
8670
9586
  const ref = useRef();
9587
+ const uiTransitionRefDefault = useRef();
9588
+ uiTransitionRef = uiTransitionRef || uiTransitionRefDefault;
8671
9589
  useLayoutEffect(() => {
8672
- const uiTransition = initUITransition(ref.current);
9590
+ if (disabled) {
9591
+ return null;
9592
+ }
9593
+ const uiTransition = createUITransitionController(ref.current);
9594
+ uiTransitionRef.current = uiTransition;
8673
9595
  return () => {
8674
9596
  uiTransition.cleanup();
8675
9597
  };
8676
- }, []);
8677
- return jsx(ContentKeyContext.Provider, {
8678
- value: contentKeyContextValue,
8679
- children: jsxs("div", {
8680
- ref: ref,
8681
- ...props,
8682
- className: "ui_transition_container",
8683
- "data-size-transition": sizeTransition ? "" : undefined,
8684
- "data-size-transition-duration": sizeTransitionDuration ? sizeTransitionDuration : undefined,
8685
- "data-content-transition": transitionType ? transitionType : undefined,
8686
- "data-content-transition-duration": transitionDuration ? transitionDuration : undefined,
8687
- "data-phase-transition": phaseTransitionType ? phaseTransitionType : undefined,
8688
- "data-phase-transition-duration": phaseTransitionDuration ? phaseTransitionDuration : undefined,
8689
- "data-debug-transition": debugTransition ? "" : undefined,
9598
+ }, [disabled]);
9599
+ if (disabled) {
9600
+ return children;
9601
+ }
9602
+ return jsxs("div", {
9603
+ ref: ref,
9604
+ ...props,
9605
+ className: "ui_transition",
9606
+ "data-transition-type": type,
9607
+ "data-transition-duration": duration,
9608
+ "data-debug-detection": debugDetection ? "" : undefined,
9609
+ "data-debug-size": debugSize ? "" : undefined,
9610
+ "data-debug-content": debugContent ? "" : undefined,
9611
+ children: [jsxs("div", {
9612
+ className: "active_group",
8690
9613
  children: [jsx("div", {
8691
- className: "ui_transition_outer_wrapper",
8692
- children: jsxs("div", {
8693
- className: "ui_transition_measure_wrapper",
8694
- children: [jsx("div", {
8695
- className: "ui_transition_slot",
8696
- "data-content-key": effectiveContentKey ? effectiveContentKey : undefined,
8697
- children: children
8698
- }), jsx("div", {
8699
- className: "ui_transition_phase_overlay"
8700
- })]
9614
+ className: "target_slot",
9615
+ "data-content-id": contentIdRef.current ? contentIdRef.current : undefined,
9616
+ children: jsx(UITransitionContentIdContext.Provider, {
9617
+ value: uiTransitionContentIdContextValue,
9618
+ children: children
8701
9619
  })
8702
9620
  }), jsx("div", {
8703
- className: "ui_transition_content_overlay"
9621
+ className: "outgoing_slot"
8704
9622
  })]
8705
- })
9623
+ }), jsxs("div", {
9624
+ className: "previous_group",
9625
+ children: [jsx("div", {
9626
+ className: "previous_target_slot"
9627
+ }), jsx("div", {
9628
+ className: "previous_outgoing_slot"
9629
+ })]
9630
+ })]
8706
9631
  });
8707
9632
  };
8708
9633
 
@@ -8714,28 +9639,28 @@ const UITransition = ({
8714
9639
  * as changed even if the component is still the same
8715
9640
  *
8716
9641
  * This is used by <Route> to set the content key to the route path
8717
- * When the route becomes inactive it will call useContentKey(undefined)
8718
- * And if a sibling route becones active it will call useContentKey with its own path
9642
+ * When the route becomes inactive it will call useUITransitionContentId(undefined)
9643
+ * And if a sibling route becones active it will call useUITransitionContentId with its own path
8719
9644
  *
8720
9645
  */
8721
- const useContentKey = key => {
8722
- const contentKey = useContext(ContentKeyContext);
8723
- const keyRef = useRef();
8724
- if (keyRef.current !== key && contentKey) {
8725
- const previousKey = keyRef.current;
8726
- keyRef.current = key;
8727
- if (previousKey) {
8728
- contentKey.update(previousKey, key);
9646
+ const useUITransitionContentId = value => {
9647
+ const contentId = useContext(UITransitionContentIdContext);
9648
+ const valueRef = useRef();
9649
+ if (contentId !== undefined && valueRef.current !== value) {
9650
+ const previousValue = valueRef.current;
9651
+ valueRef.current = value;
9652
+ if (previousValue === undefined) {
9653
+ contentId.add(value);
8729
9654
  } else {
8730
- contentKey.add(key);
9655
+ contentId.update(previousValue, value);
8731
9656
  }
8732
9657
  }
8733
9658
  useLayoutEffect(() => {
8734
- if (!contentKey) {
9659
+ if (contentId === undefined) {
8735
9660
  return null;
8736
9661
  }
8737
9662
  return () => {
8738
- contentKey.remove(keyRef.current);
9663
+ contentId.remove(valueRef.current);
8739
9664
  };
8740
9665
  }, []);
8741
9666
  };
@@ -8791,10 +9716,13 @@ const Routes = ({
8791
9716
  });
8792
9717
  };
8793
9718
  const SlotContext = createContext(null);
9719
+ const RouteInfoContext = createContext(null);
9720
+ const useActiveRouteInfo = () => useContext(RouteInfoContext);
8794
9721
  const Route = ({
8795
9722
  element,
8796
9723
  route,
8797
9724
  fallback,
9725
+ meta,
8798
9726
  children
8799
9727
  }) => {
8800
9728
  const forceRender = useForceRender();
@@ -8805,6 +9733,7 @@ const Route = ({
8805
9733
  element: element,
8806
9734
  route: route,
8807
9735
  fallback: fallback,
9736
+ meta: meta,
8808
9737
  onActiveInfoChange: activeInfo => {
8809
9738
  hasDiscoveredRef.current = true;
8810
9739
  activeInfoRef.current = activeInfo;
@@ -8831,18 +9760,23 @@ const ActiveRouteManager = ({
8831
9760
  element,
8832
9761
  route,
8833
9762
  fallback,
9763
+ meta,
8834
9764
  onActiveInfoChange,
8835
9765
  children
8836
9766
  }) => {
9767
+ if (route && fallback) {
9768
+ throw new Error("Route cannot have both route and fallback props");
9769
+ }
8837
9770
  const registerChildRouteFromContext = useContext(RegisterChildRouteContext);
8838
9771
  getElementSignature(element);
8839
9772
  const candidateSet = new Set();
8840
- const registerChildRoute = (ChildActiveElement, childRoute, childFallback) => {
9773
+ const registerChildRoute = (ChildActiveElement, childRoute, childFallback, childMeta) => {
8841
9774
  getElementSignature(ChildActiveElement);
8842
9775
  candidateSet.add({
8843
9776
  ActiveElement: ChildActiveElement,
8844
9777
  route: childRoute,
8845
- fallback: childFallback
9778
+ fallback: childFallback,
9779
+ meta: childMeta
8846
9780
  });
8847
9781
  };
8848
9782
  useLayoutEffect(() => {
@@ -8850,6 +9784,7 @@ const ActiveRouteManager = ({
8850
9784
  element,
8851
9785
  route,
8852
9786
  fallback,
9787
+ meta,
8853
9788
  candidateSet,
8854
9789
  onActiveInfoChange,
8855
9790
  registerChildRouteFromContext
@@ -8864,6 +9799,7 @@ const initRouteObserver = ({
8864
9799
  element,
8865
9800
  route,
8866
9801
  fallback,
9802
+ meta,
8867
9803
  candidateSet,
8868
9804
  onActiveInfoChange,
8869
9805
  registerChildRouteFromContext
@@ -8884,19 +9820,13 @@ const initRouteObserver = ({
8884
9820
  let fallbackInfo = null;
8885
9821
  for (const candidate of candidateSet) {
8886
9822
  if (candidate.route?.active) {
8887
- return {
8888
- ChildActiveElement: candidate.ActiveElement,
8889
- route: candidate.route
8890
- };
9823
+ return candidate;
8891
9824
  }
8892
9825
  // fallback without route can match when no other route matches.
8893
9826
  // This is useful solely for "catch all" fallback used on the <Routes>
8894
9827
  // otherwise a fallback would always match and make the parent route always active
8895
9828
  if (candidate.fallback && !candidate.route.routeFromProps) {
8896
- fallbackInfo = {
8897
- ChildActiveElement: candidate.ActiveElement,
8898
- route: candidate.route
8899
- };
9829
+ fallbackInfo = candidate;
8900
9830
  }
8901
9831
  }
8902
9832
  return fallbackInfo;
@@ -8913,8 +9843,9 @@ const initRouteObserver = ({
8913
9843
  return activeChildInfo;
8914
9844
  }
8915
9845
  return {
8916
- ChildActiveElement: null,
8917
- route
9846
+ ActiveElement: null,
9847
+ route,
9848
+ meta
8918
9849
  };
8919
9850
  } : () => {
8920
9851
  // we don't have a route, do we have an active child?
@@ -8924,22 +9855,22 @@ const initRouteObserver = ({
8924
9855
  }
8925
9856
  return null;
8926
9857
  };
8927
- const activeRouteSignal = signal();
9858
+ const activeRouteInfoSignal = signal();
8928
9859
  const SlotActiveElementSignal = signal();
8929
9860
  const ActiveElement = () => {
8930
- const activeRoute = activeRouteSignal.value;
8931
- useContentKey(activeRoute?.urlPattern);
9861
+ const activeRouteInfo = activeRouteInfoSignal.value;
9862
+ useUITransitionContentId(activeRouteInfo ? activeRouteInfo.route.urlPattern : fallback ? "fallback" : undefined);
8932
9863
  const SlotActiveElement = SlotActiveElementSignal.value;
8933
9864
  if (typeof element === "function") {
8934
9865
  const Element = element;
8935
- return jsx(SlotContext.Provider, {
8936
- value: SlotActiveElement,
8937
- children: jsx(Element, {})
8938
- });
9866
+ element = jsx(Element, {});
8939
9867
  }
8940
- return jsx(SlotContext.Provider, {
8941
- value: SlotActiveElement,
8942
- children: element
9868
+ return jsx(RouteInfoContext.Provider, {
9869
+ value: activeRouteInfo,
9870
+ children: jsx(SlotContext.Provider, {
9871
+ value: SlotActiveElement,
9872
+ children: element
9873
+ })
8943
9874
  });
8944
9875
  };
8945
9876
  ActiveElement.underlyingElementId = candidateSet.size === 0 ? `${getElementSignature(element)} without slot` : `[${getElementSignature(element)} with slot one of ${candidateElementIds}]`;
@@ -8947,20 +9878,17 @@ const initRouteObserver = ({
8947
9878
  const newActiveInfo = getActiveInfo();
8948
9879
  if (newActiveInfo) {
8949
9880
  compositeRoute.active = true;
8950
- const {
8951
- route,
8952
- ChildActiveElement
8953
- } = newActiveInfo;
8954
- activeRouteSignal.value = route;
8955
- SlotActiveElementSignal.value = ChildActiveElement;
9881
+ activeRouteInfoSignal.value = newActiveInfo;
9882
+ SlotActiveElementSignal.value = newActiveInfo.ActiveElement;
8956
9883
  onActiveInfoChange({
8957
- route: newActiveInfo.route,
8958
9884
  ActiveElement,
8959
- SlotActiveElement: ChildActiveElement
9885
+ SlotActiveElement: newActiveInfo.ActiveElement,
9886
+ route: newActiveInfo.route,
9887
+ meta: newActiveInfo.meta
8960
9888
  });
8961
9889
  } else {
8962
9890
  compositeRoute.active = false;
8963
- activeRouteSignal.value = null;
9891
+ activeRouteInfoSignal.value = null;
8964
9892
  SlotActiveElementSignal.value = null;
8965
9893
  onActiveInfoChange(null);
8966
9894
  }
@@ -8976,7 +9904,7 @@ const initRouteObserver = ({
8976
9904
  candidate.route.subscribeStatus(onChange);
8977
9905
  }
8978
9906
  if (registerChildRouteFromContext) {
8979
- registerChildRouteFromContext(ActiveElement, compositeRoute, fallback);
9907
+ registerChildRouteFromContext(ActiveElement, compositeRoute, fallback, meta);
8980
9908
  }
8981
9909
  updateActiveInfo();
8982
9910
  };
@@ -9178,14 +10106,11 @@ const renderActionableComponent = (props, {
9178
10106
 
9179
10107
  const normalizeSpacingStyle = (value, property = "padding") => {
9180
10108
  const cssSize = sizeSpacingScale[value];
9181
- return cssSize || normalizeStyle(value, property, "css");
10109
+ return cssSize || stringifyStyle(value, property);
9182
10110
  };
9183
10111
  const normalizeTypoStyle = (value, property = "fontSize") => {
9184
10112
  const cssSize = sizeTypoScale[value];
9185
- return cssSize || normalizeStyle(value, property, "css");
9186
- };
9187
- const normalizeCssStyle = (value, property) => {
9188
- return normalizeStyle(value, property, "css");
10113
+ return cssSize || stringifyStyle(value, property);
9189
10114
  };
9190
10115
 
9191
10116
  const PASS_THROUGH = { name: "pass_through" };
@@ -9236,6 +10161,13 @@ const applyOnTwoProps = (propA, propB) => {
9236
10161
  };
9237
10162
  };
9238
10163
 
10164
+ const LAYOUT_PROPS = {
10165
+ // all are handled by data-attributes
10166
+ inline: () => {},
10167
+ box: () => {},
10168
+ row: () => {},
10169
+ column: () => {},
10170
+ };
9239
10171
  const OUTER_SPACING_PROPS = {
9240
10172
  margin: PASS_THROUGH,
9241
10173
  marginLeft: PASS_THROUGH,
@@ -9272,21 +10204,21 @@ const DIMENSION_PROPS = {
9272
10204
  return { flexGrow: 1 }; // Grow horizontally in row
9273
10205
  }
9274
10206
  if (parentLayout === "row") {
9275
- return { minWidth: "100%" }; // Take full width in column
10207
+ return { minWidth: "100%", width: "auto" }; // Take full width in column
9276
10208
  }
9277
- return { minWidth: "100%" }; // Take full width outside flex
10209
+ return { minWidth: "100%", width: "auto" }; // Take full width outside flex
9278
10210
  },
9279
10211
  expandY: (value, { parentLayout }) => {
9280
10212
  if (!value) {
9281
10213
  return null;
9282
10214
  }
9283
10215
  if (parentLayout === "column") {
9284
- return { minHeight: "100%" }; // Make column full height
10216
+ return { minHeight: "100%", height: "auto" }; // Make column full height
9285
10217
  }
9286
10218
  if (parentLayout === "row" || parentLayout === "inline-row") {
9287
10219
  return { flexGrow: 1 }; // Make row full height
9288
10220
  }
9289
- return { minHeight: "100%" }; // Take full height outside flex
10221
+ return { minHeight: "100%", height: "auto" }; // Take full height outside flex
9290
10222
  },
9291
10223
  shrinkX: (value, { parentLayout }) => {
9292
10224
  if (!value) {
@@ -9306,6 +10238,23 @@ const DIMENSION_PROPS = {
9306
10238
  }
9307
10239
  return { maxHeight: "100%" };
9308
10240
  },
10241
+
10242
+ scaleX: (value) => {
10243
+ return { transform: `scaleX(${stringifyStyle(value, "scaleX")})` };
10244
+ },
10245
+ scaleY: (value) => {
10246
+ return { transform: `scaleY(${value})` };
10247
+ },
10248
+ scale: (value) => {
10249
+ if (Array.isArray(value)) {
10250
+ const [x, y] = value;
10251
+ return { transform: `scale(${x}, ${y})` };
10252
+ }
10253
+ return { transform: `scale(${value})` };
10254
+ },
10255
+ scaleZ: (value) => {
10256
+ return { transform: `scaleZ(${value})` };
10257
+ },
9309
10258
  };
9310
10259
  const POSITION_PROPS = {
9311
10260
  // For row, alignX uses auto margins for positioning
@@ -9346,7 +10295,7 @@ const POSITION_PROPS = {
9346
10295
 
9347
10296
  if (value === "start") {
9348
10297
  if (inlineColumnLayout) {
9349
- return undefined; // this is the default
10298
+ return { alignSelf: "start" };
9350
10299
  }
9351
10300
  return { marginBottom: "auto" };
9352
10301
  }
@@ -9366,8 +10315,49 @@ const POSITION_PROPS = {
9366
10315
  },
9367
10316
  left: PASS_THROUGH,
9368
10317
  top: PASS_THROUGH,
10318
+
10319
+ translateX: (value) => {
10320
+ return { transform: `translateX(${value})` };
10321
+ },
10322
+ translateY: (value) => {
10323
+ return { transform: `translateY(${value})` };
10324
+ },
10325
+ translate: (value) => {
10326
+ if (Array.isArray(value)) {
10327
+ const [x, y] = value;
10328
+ return { transform: `translate(${x}, ${y})` };
10329
+ }
10330
+ return { transform: `translate(${stringifyStyle(value, "translateX")})` };
10331
+ },
10332
+ rotateX: (value) => {
10333
+ return { transform: `rotateX(${value})` };
10334
+ },
10335
+ rotateY: (value) => {
10336
+ return { transform: `rotateY(${value})` };
10337
+ },
10338
+ rotateZ: (value) => {
10339
+ return { transform: `rotateZ(${value})` };
10340
+ },
10341
+ rotate: (value) => {
10342
+ return { transform: `rotate(${value})` };
10343
+ },
10344
+ skewX: (value) => {
10345
+ return { transform: `skewX(${value})` };
10346
+ },
10347
+ skewY: (value) => {
10348
+ return { transform: `skewY(${value})` };
10349
+ },
10350
+ skew: (value) => {
10351
+ if (Array.isArray(value)) {
10352
+ const [x, y] = value;
10353
+ return { transform: `skew(${x}, ${y})` };
10354
+ }
10355
+ return { transform: `skew(${value})` };
10356
+ },
9369
10357
  };
9370
10358
  const TYPO_PROPS = {
10359
+ font: PASS_THROUGH,
10360
+ fontFamily: PASS_THROUGH,
9371
10361
  size: applyOnCSSProp("fontSize"),
9372
10362
  fontSize: PASS_THROUGH,
9373
10363
  bold: applyToCssPropWhenTruthy("fontWeight", "bold", "normal"),
@@ -9384,6 +10374,11 @@ const TYPO_PROPS = {
9384
10374
  preWrap: applyToCssPropWhenTruthy("whiteSpace", "pre-wrap", "normal"),
9385
10375
  };
9386
10376
  const VISUAL_PROPS = {
10377
+ outline: PASS_THROUGH,
10378
+ outlineStyle: PASS_THROUGH,
10379
+ outlineColor: PASS_THROUGH,
10380
+ outlineWidth: PASS_THROUGH,
10381
+ boxDecorationBreak: PASS_THROUGH,
9387
10382
  boxShadow: PASS_THROUGH,
9388
10383
  background: PASS_THROUGH,
9389
10384
  backgroundColor: PASS_THROUGH,
@@ -9463,6 +10458,7 @@ const CONTENT_PROPS = {
9463
10458
  },
9464
10459
  };
9465
10460
  const All_PROPS = {
10461
+ ...LAYOUT_PROPS,
9466
10462
  ...OUTER_SPACING_PROPS,
9467
10463
  ...INNER_SPACING_PROPS,
9468
10464
  ...DIMENSION_PROPS,
@@ -9471,6 +10467,7 @@ const All_PROPS = {
9471
10467
  ...VISUAL_PROPS,
9472
10468
  ...CONTENT_PROPS,
9473
10469
  };
10470
+ const LAYOUT_PROP_NAME_SET = new Set(Object.keys(LAYOUT_PROPS));
9474
10471
  const OUTER_SPACING_PROP_NAME_SET = new Set(Object.keys(OUTER_SPACING_PROPS));
9475
10472
  const INNER_SPACING_PROP_NAME_SET = new Set(Object.keys(INNER_SPACING_PROPS));
9476
10473
  const DIMENSION_PROP_NAME_SET = new Set(Object.keys(DIMENSION_PROPS));
@@ -9486,6 +10483,7 @@ const HANDLED_BY_VISUAL_CHILD_PROP_SET = new Set([
9486
10483
  ...CONTENT_PROP_NAME_SET,
9487
10484
  ]);
9488
10485
  const COPIED_ON_VISUAL_CHILD_PROP_SET = new Set([
10486
+ ...LAYOUT_PROP_NAME_SET,
9489
10487
  "expand",
9490
10488
  "shrink",
9491
10489
  "expandX",
@@ -9497,6 +10495,9 @@ const COPIED_ON_VISUAL_CHILD_PROP_SET = new Set([
9497
10495
  const isStyleProp = (name) => STYLE_PROP_NAME_SET.has(name);
9498
10496
 
9499
10497
  const getStylePropGroup = (name) => {
10498
+ if (LAYOUT_PROP_NAME_SET.has(name)) {
10499
+ return "layout";
10500
+ }
9500
10501
  if (OUTER_SPACING_PROP_NAME_SET.has(name)) {
9501
10502
  return "margin";
9502
10503
  }
@@ -9528,7 +10529,7 @@ const getNormalizer = (key) => {
9528
10529
  if (group === "typo") {
9529
10530
  return normalizeTypoStyle;
9530
10531
  }
9531
- return normalizeCssStyle;
10532
+ return stringifyStyle;
9532
10533
  };
9533
10534
 
9534
10535
  const assignStyle = (
@@ -9536,7 +10537,7 @@ const assignStyle = (
9536
10537
  propValue,
9537
10538
  propName,
9538
10539
  styleContext,
9539
- normalizer = getNormalizer(propName),
10540
+ context = "js",
9540
10541
  ) => {
9541
10542
  if (propValue === undefined) {
9542
10543
  return;
@@ -9545,6 +10546,7 @@ const assignStyle = (
9545
10546
  if (!managedByCSSVars) {
9546
10547
  throw new Error("managedByCSSVars is required in styleContext");
9547
10548
  }
10549
+ const normalizer = getNormalizer(propName);
9548
10550
  const getStyle = All_PROPS[propName];
9549
10551
  if (
9550
10552
  getStyle === PASS_THROUGH ||
@@ -9553,10 +10555,16 @@ const assignStyle = (
9553
10555
  ) {
9554
10556
  const cssValue = normalizer(propValue, propName);
9555
10557
  const cssVar = managedByCSSVars[propName];
10558
+ const mergedValue = mergeOneStyle(
10559
+ styleObject[propName],
10560
+ cssValue,
10561
+ propName,
10562
+ context,
10563
+ );
9556
10564
  if (cssVar) {
9557
- styleObject[cssVar] = cssValue;
10565
+ styleObject[cssVar] = mergedValue;
9558
10566
  } else {
9559
- styleObject[propName] = cssValue;
10567
+ styleObject[propName] = mergedValue;
9560
10568
  }
9561
10569
  return;
9562
10570
  }
@@ -9568,10 +10576,11 @@ const assignStyle = (
9568
10576
  const value = values[key];
9569
10577
  const cssValue = normalizer(value, key);
9570
10578
  const cssVar = managedByCSSVars[key];
10579
+ const mergedValue = mergeOneStyle(styleObject[key], cssValue, key, context);
9571
10580
  if (cssVar) {
9572
- styleObject[cssVar] = cssValue;
10581
+ styleObject[cssVar] = mergedValue;
9573
10582
  } else {
9574
- styleObject[key] = cssValue;
10583
+ styleObject[key] = mergedValue;
9575
10584
  }
9576
10585
  }
9577
10586
  };
@@ -9588,12 +10597,8 @@ const sizeSpacingScale = {
9588
10597
  xl: "2em", // 2 = 32px at 16px base
9589
10598
  xxl: "3em", // 3 = 48px at 16px base
9590
10599
  };
9591
- const resolveSpacingSize = (
9592
- size,
9593
- property = "padding",
9594
- context = "css",
9595
- ) => {
9596
- return normalizeStyle(sizeSpacingScale[size] || size, property, context);
10600
+ const resolveSpacingSize = (size, property = "padding") => {
10601
+ return stringifyStyle(sizeSpacingScale[size] || size, property);
9597
10602
  };
9598
10603
 
9599
10604
  const sizeTypoScale = {
@@ -9971,7 +10976,7 @@ const initPseudoStyles = (
9971
10976
  }
9972
10977
  currentState[pseudoClass] = currentValue;
9973
10978
  const oldValue = state ? state[pseudoClass] : undefined;
9974
- if (oldValue !== currentValue) {
10979
+ if (oldValue !== currentValue || !state) {
9975
10980
  someChange = true;
9976
10981
  const { attribute, add, remove } = pseudoClassDefinition;
9977
10982
  if (currentValue) {
@@ -10026,8 +11031,15 @@ const applyStyle = (element, style, pseudoState, pseudoNamedStyles) => {
10026
11031
  updateStyle(element, getStyleToApply(style, pseudoState, pseudoNamedStyles));
10027
11032
  };
10028
11033
 
11034
+ const PSEUDO_STATE_DEFAULT = {};
11035
+ const PSEUDO_NAMED_STYLES_DEFAULT = {};
10029
11036
  const getStyleToApply = (styles, pseudoState, pseudoNamedStyles) => {
10030
- if (!pseudoState || !pseudoNamedStyles) {
11037
+ if (
11038
+ !pseudoState ||
11039
+ pseudoState === PSEUDO_STATE_DEFAULT ||
11040
+ !pseudoNamedStyles ||
11041
+ pseudoNamedStyles === PSEUDO_NAMED_STYLES_DEFAULT
11042
+ ) {
10031
11043
  return styles;
10032
11044
  }
10033
11045
 
@@ -10094,7 +11106,13 @@ const updateStyle = (element, style) => {
10094
11106
  }
10095
11107
  }
10096
11108
  for (const toDeleteKey of toDeleteKeySet) {
10097
- element.style.removeProperty(toDeleteKey);
11109
+ if (toDeleteKey.startsWith("--")) {
11110
+ element.style.removeProperty(toDeleteKey);
11111
+ } else {
11112
+ // we can't use removeProperty because "toDeleteKey" is in camelCase
11113
+ // e.g., backgroundColor (and it's safer to just let the browser do the conversion)
11114
+ element.style[toDeleteKey] = "";
11115
+ }
10098
11116
  }
10099
11117
  styleKeySetWeakMap.set(element, styleKeySet);
10100
11118
  return;
@@ -10153,8 +11171,10 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
10153
11171
  flex-direction: row;
10154
11172
  }
10155
11173
 
10156
- [data-layout-row] > *,
10157
- [data-layout-column] > * {
11174
+ [data-layout-row] > [data-layout-row],
11175
+ [data-layout-row] > [data-layout-column],
11176
+ [data-layout-column] > [data-layout-column],
11177
+ [data-layout-column] > [data-layout-row] {
10158
11178
  flex-shrink: 0;
10159
11179
  }
10160
11180
 
@@ -10169,9 +11189,6 @@ const MANAGED_BY_CSS_VARS_DEFAULT = {};
10169
11189
  const Box = props => {
10170
11190
  const {
10171
11191
  as = "div",
10172
- layoutRow,
10173
- layoutColumn,
10174
- layoutInline,
10175
11192
  baseClassName,
10176
11193
  className,
10177
11194
  baseStyle,
@@ -10201,30 +11218,78 @@ const Box = props => {
10201
11218
  const defaultRef = useRef();
10202
11219
  const ref = props.ref || defaultRef;
10203
11220
  const TagName = as;
11221
+ const {
11222
+ box,
11223
+ inline = box,
11224
+ row,
11225
+ column = box
11226
+ } = rest;
10204
11227
  let layout;
10205
- if (layoutInline) {
10206
- if (layoutRow) {
11228
+ if (inline) {
11229
+ if (row) {
10207
11230
  layout = "inline-row";
10208
- } else if (layoutColumn) {
11231
+ } else if (column) {
10209
11232
  layout = "inline-column";
10210
11233
  } else {
10211
11234
  layout = "inline";
10212
11235
  }
10213
- } else if (layoutRow) {
11236
+ } else if (row) {
10214
11237
  layout = "row";
10215
- } else if (layoutColumn) {
11238
+ } else if (column) {
10216
11239
  layout = "column";
10217
11240
  } else {
10218
11241
  layout = getDefaultDisplay(TagName);
10219
11242
  }
10220
11243
  const innerClassName = withPropsClassName(baseClassName, className);
10221
11244
  const remainingProps = {};
11245
+ const propsToForward = {};
11246
+ const shouldForwardAllToChild = visualSelector && pseudoStateSelector;
10222
11247
  {
10223
11248
  const parentLayout = useContext(BoxLayoutContext);
10224
- const innerPseudoState = basePseudoState && pseudoState ? {
10225
- ...basePseudoState,
10226
- ...pseudoState
10227
- } : basePseudoState;
11249
+ const styleDeps = [
11250
+ // Layout and alignment props
11251
+ parentLayout, layout,
11252
+ // Style context dependencies
11253
+ managedByCSSVars, pseudoClasses, pseudoElements,
11254
+ // Selectors
11255
+ visualSelector, pseudoStateSelector];
11256
+ let innerPseudoState;
11257
+ if (basePseudoState && pseudoState) {
11258
+ innerPseudoState = {};
11259
+ const baseStateKeys = Object.keys(basePseudoState);
11260
+ const pseudoStateKeySet = new Set(Object.keys(pseudoState));
11261
+ for (const key of baseStateKeys) {
11262
+ if (pseudoStateKeySet.has(key)) {
11263
+ pseudoStateKeySet.delete(key);
11264
+ const value = pseudoState[key];
11265
+ styleDeps.push(value);
11266
+ innerPseudoState[key] = value;
11267
+ } else {
11268
+ const value = basePseudoState[key];
11269
+ styleDeps.push(value);
11270
+ innerPseudoState[key] = value;
11271
+ }
11272
+ }
11273
+ for (const key of pseudoStateKeySet) {
11274
+ const value = pseudoState[key];
11275
+ styleDeps.push(value);
11276
+ innerPseudoState[key] = value;
11277
+ }
11278
+ } else if (basePseudoState) {
11279
+ innerPseudoState = basePseudoState;
11280
+ for (const key of Object.keys(basePseudoState)) {
11281
+ const value = basePseudoState[key];
11282
+ styleDeps.push(value);
11283
+ }
11284
+ } else if (pseudoState) {
11285
+ innerPseudoState = pseudoState;
11286
+ for (const key of Object.keys(pseudoState)) {
11287
+ const value = pseudoState[key];
11288
+ styleDeps.push(value);
11289
+ }
11290
+ } else {
11291
+ innerPseudoState = PSEUDO_STATE_DEFAULT;
11292
+ }
10228
11293
  const styleContext = {
10229
11294
  parentLayout,
10230
11295
  layout,
@@ -10233,13 +11298,6 @@ const Box = props => {
10233
11298
  pseudoClasses,
10234
11299
  pseudoElements
10235
11300
  };
10236
- const styleDeps = [
10237
- // Layout and alignment props
10238
- parentLayout, layout,
10239
- // Style context dependencies
10240
- managedByCSSVars, pseudoClasses, pseudoElements,
10241
- // Selectors
10242
- visualSelector, pseudoStateSelector];
10243
11301
  const boxStyles = {};
10244
11302
  if (baseStyle) {
10245
11303
  for (const key of baseStyle) {
@@ -10249,22 +11307,6 @@ const Box = props => {
10249
11307
  }
10250
11308
  }
10251
11309
  const stylingKeyCandidateArray = Object.keys(rest);
10252
- const assignStyleFromProp = (propValue, propName, stylesTarget, context) => {
10253
- const propEffect = getPropEffect(propName);
10254
- if (propEffect === "ignore") {
10255
- return;
10256
- }
10257
- if (propEffect === "forward" || propEffect === "style_and_forward") {
10258
- if (stylesTarget === boxStyles) {
10259
- remainingProps[propName] = propValue;
10260
- }
10261
- if (propEffect === "forward") {
10262
- return;
10263
- }
10264
- }
10265
- styleDeps.push(propValue);
10266
- assignStyle(stylesTarget, propValue, propName, context);
10267
- };
10268
11310
  const getPropEffect = propName => {
10269
11311
  if (visualSelector) {
10270
11312
  if (HANDLED_BY_VISUAL_CHILD_PROP_SET.has(propName)) {
@@ -10274,7 +11316,34 @@ const Box = props => {
10274
11316
  return "style_and_forward";
10275
11317
  }
10276
11318
  }
10277
- return isStyleProp(propName) ? "style" : "forward";
11319
+ if (isStyleProp(propName)) {
11320
+ return "style";
11321
+ }
11322
+ if (propName.startsWith("data-")) {
11323
+ return "use";
11324
+ }
11325
+ return "forward";
11326
+ };
11327
+ const assignStyleFromProp = (propValue, propName, stylesTarget, styleContext) => {
11328
+ const propEffect = getPropEffect(propName);
11329
+ if (propEffect === "ignore") {
11330
+ return;
11331
+ }
11332
+ const useToStyle = propEffect === "style" || propEffect === "style_and_forward";
11333
+ const shouldForward = propEffect === "forward" || propEffect === "style_and_forward";
11334
+ if (useToStyle) {
11335
+ styleDeps.push(propValue);
11336
+ assignStyle(stylesTarget, propValue, propName, styleContext, "css");
11337
+ }
11338
+ if (stylesTarget === boxStyles) {
11339
+ if (!shouldForwardAllToChild && !useToStyle) {
11340
+ // we'll put these props on ourselves
11341
+ remainingProps[propName] = propValue;
11342
+ }
11343
+ if (shouldForward) {
11344
+ propsToForward[propName] = propValue;
11345
+ }
11346
+ }
10278
11347
  };
10279
11348
  for (const key of stylingKeyCandidateArray) {
10280
11349
  if (key === "ref") {
@@ -10285,46 +11354,49 @@ const Box = props => {
10285
11354
  const value = rest[key];
10286
11355
  assignStyleFromProp(value, key, boxStyles, styleContext);
10287
11356
  }
10288
- const pseudoNamedStyles = {};
11357
+ let pseudoNamedStyles = PSEUDO_NAMED_STYLES_DEFAULT;
10289
11358
  if (pseudoStyle) {
10290
- for (const key of Object.keys(pseudoStyle)) {
10291
- const pseudoStyleContext = {
10292
- ...styleContext,
10293
- managedByCSSVars: {
10294
- ...managedByCSSVars,
10295
- ...managedByCSSVars[key]
10296
- },
10297
- pseudoName: key
10298
- };
11359
+ const pseudoStyleKeys = Object.keys(pseudoStyle);
11360
+ if (pseudoStyleKeys.length) {
11361
+ pseudoNamedStyles = {};
11362
+ for (const key of pseudoStyleKeys) {
11363
+ const pseudoStyleContext = {
11364
+ ...styleContext,
11365
+ managedByCSSVars: {
11366
+ ...managedByCSSVars,
11367
+ ...managedByCSSVars[key]
11368
+ },
11369
+ pseudoName: key
11370
+ };
10299
11371
 
10300
- // pseudo class
10301
- if (key.startsWith(":")) {
10302
- styleDeps.push(key);
10303
- const pseudoClassStyles = {};
10304
- const pseudoClassStyle = pseudoStyle[key];
10305
- for (const pseudoClassStyleKey of Object.keys(pseudoClassStyle)) {
10306
- const pseudoClassStyleValue = pseudoClassStyle[pseudoClassStyleKey];
10307
- assignStyleFromProp(pseudoClassStyleValue, pseudoClassStyleKey, pseudoClassStyles, pseudoStyleContext);
11372
+ // pseudo class
11373
+ if (key.startsWith(":")) {
11374
+ styleDeps.push(key);
11375
+ const pseudoClassStyles = {};
11376
+ const pseudoClassStyle = pseudoStyle[key];
11377
+ for (const pseudoClassStyleKey of Object.keys(pseudoClassStyle)) {
11378
+ const pseudoClassStyleValue = pseudoClassStyle[pseudoClassStyleKey];
11379
+ assignStyleFromProp(pseudoClassStyleValue, pseudoClassStyleKey, pseudoClassStyles, pseudoStyleContext);
11380
+ }
11381
+ pseudoNamedStyles[key] = pseudoClassStyles;
11382
+ continue;
10308
11383
  }
10309
- pseudoNamedStyles[key] = pseudoClassStyles;
10310
- continue;
10311
- }
10312
- // pseudo element
10313
- if (key.startsWith("::")) {
10314
- styleDeps.push(key);
10315
- const pseudoElementStyles = {};
10316
- const pseudoElementStyle = pseudoStyle[key];
10317
- for (const pseudoElementStyleKey of Object.keys(pseudoElementStyle)) {
10318
- const pseudoElementStyleValue = pseudoElementStyle[pseudoElementStyleKey];
10319
- assignStyleFromProp(pseudoElementStyleValue, pseudoElementStyleKey, pseudoElementStyles, pseudoStyleContext);
11384
+ // pseudo element
11385
+ if (key.startsWith("::")) {
11386
+ styleDeps.push(key);
11387
+ const pseudoElementStyles = {};
11388
+ const pseudoElementStyle = pseudoStyle[key];
11389
+ for (const pseudoElementStyleKey of Object.keys(pseudoElementStyle)) {
11390
+ const pseudoElementStyleValue = pseudoElementStyle[pseudoElementStyleKey];
11391
+ assignStyleFromProp(pseudoElementStyleValue, pseudoElementStyleKey, pseudoElementStyles, pseudoStyleContext);
11392
+ }
11393
+ pseudoNamedStyles[key] = pseudoElementStyles;
11394
+ continue;
10320
11395
  }
10321
- pseudoNamedStyles[key] = pseudoElementStyles;
10322
- continue;
11396
+ console.warn(`unsupported pseudo style key "${key}"`);
10323
11397
  }
10324
- console.warn(`unsupported pseudo style key "${key}"`);
10325
11398
  }
10326
11399
  remainingProps.pseudoStyle = pseudoStyle;
10327
- // TODO: we should also pass pseudoState right?
10328
11400
  }
10329
11401
  if (typeof style === "string") {
10330
11402
  appendStyles(boxStyles, normalizeStyles(style, "css"), "css");
@@ -10332,7 +11404,7 @@ const Box = props => {
10332
11404
  } else if (style && typeof style === "object") {
10333
11405
  for (const key of Object.keys(style)) {
10334
11406
  const stylePropValue = style[key];
10335
- assignStyle(boxStyles, stylePropValue, key, styleContext);
11407
+ assignStyle(boxStyles, stylePropValue, key, styleContext, "css");
10336
11408
  styleDeps.push(stylePropValue); // impact box style -> add to deps
10337
11409
  }
10338
11410
  }
@@ -10384,23 +11456,22 @@ const Box = props => {
10384
11456
  let innerChildren;
10385
11457
  if (hasChildFunction) {
10386
11458
  if (Array.isArray(children)) {
10387
- innerChildren = children.map(child => typeof child === "function" ? child(remainingProps) : child);
11459
+ innerChildren = children.map(child => typeof child === "function" ? child(propsToForward) : child);
10388
11460
  } else if (typeof children === "function") {
10389
- innerChildren = children(remainingProps);
11461
+ innerChildren = children(propsToForward);
10390
11462
  } else {
10391
11463
  innerChildren = children;
10392
11464
  }
10393
11465
  } else {
10394
11466
  innerChildren = children;
10395
11467
  }
10396
- const shouldForwardAllToChild = visualSelector && pseudoStateSelector;
10397
11468
  return jsx(TagName, {
10398
11469
  ref: ref,
10399
11470
  className: innerClassName,
10400
- "data-layout-inline": layoutInline ? "" : undefined,
10401
- "data-layout-row": layoutRow ? "" : undefined,
10402
- "data-layout-column": layoutColumn ? "" : undefined,
10403
- ...(shouldForwardAllToChild ? undefined : remainingProps),
11471
+ "data-layout-inline": inline ? "" : undefined,
11472
+ "data-layout-row": row ? "" : undefined,
11473
+ "data-layout-column": column ? "" : undefined,
11474
+ ...remainingProps,
10404
11475
  children: jsx(BoxLayoutContext.Provider, {
10405
11476
  value: layout,
10406
11477
  children: innerChildren
@@ -10408,25 +11479,8 @@ const Box = props => {
10408
11479
  });
10409
11480
  };
10410
11481
  const Layout = props => {
10411
- const {
10412
- row,
10413
- column,
10414
- ...rest
10415
- } = props;
10416
- if (row) {
10417
- return jsx(Box, {
10418
- layoutRow: true,
10419
- ...rest
10420
- });
10421
- }
10422
- if (column) {
10423
- return jsx(Box, {
10424
- layoutColumn: true,
10425
- ...rest
10426
- });
10427
- }
10428
11482
  return jsx(Box, {
10429
- ...rest
11483
+ ...props
10430
11484
  });
10431
11485
  };
10432
11486
 
@@ -12357,39 +13411,14 @@ const useAutoFocus = (
12357
13411
  if (autoFocus) {
12358
13412
  const focusableElement = focusableElementRef.current;
12359
13413
  focusableElement.scrollIntoView({ inline: "nearest", block: "nearest" });
12360
- }
12361
- }, []);
12362
- };
12363
-
12364
- installImportMetaCss(import.meta);import.meta.css = /* css */`
12365
- .navi_text {
12366
- position: relative;
12367
- color: inherit;
12368
- }
12369
-
12370
- .navi_char_slot_invisible {
12371
- opacity: 0;
12372
- }
12373
-
12374
- .navi_icon {
12375
- display: flex;
12376
- aspect-ratio: 1 / 1;
12377
- height: 100%;
12378
- max-height: 1em;
12379
- align-items: center;
12380
- justify-content: center;
12381
- }
12382
-
12383
- .navi_text[data-has-foreground] {
12384
- display: inline-block;
12385
- }
13414
+ }
13415
+ }, []);
13416
+ };
12386
13417
 
12387
- .navi_text_foreground {
12388
- position: absolute;
12389
- inset: 0;
12390
- display: inline-flex;
12391
- align-items: center;
12392
- justify-content: center;
13418
+ installImportMetaCss(import.meta);import.meta.css = /* css */`
13419
+ .navi_text {
13420
+ position: relative;
13421
+ color: inherit;
12393
13422
  }
12394
13423
 
12395
13424
  .navi_text_overflow_wrapper {
@@ -12476,91 +13505,18 @@ const TextOverflowPinned = ({
12476
13505
  };
12477
13506
  const TextBasic = ({
12478
13507
  as = "span",
12479
- foregroundColor,
12480
- foregroundElement,
12481
13508
  contentSpacing = " ",
12482
- box,
12483
13509
  children,
12484
13510
  ...rest
12485
13511
  }) => {
12486
- const hasForeground = Boolean(foregroundElement || foregroundColor);
12487
- const text = jsxs(Box, {
13512
+ const text = jsx(Box, {
12488
13513
  ...rest,
12489
13514
  baseClassName: "navi_text",
12490
13515
  as: as,
12491
- layoutInline: true,
12492
- layoutColumn: box ? true : undefined,
12493
- "data-has-foreground": hasForeground ? "" : undefined,
12494
- children: [applyContentSpacingOnTextChildren(children, contentSpacing), hasForeground && jsx("span", {
12495
- className: "navi_text_foreground",
12496
- style: {
12497
- backgroundColor: foregroundColor
12498
- },
12499
- children: foregroundElement
12500
- })]
13516
+ children: applyContentSpacingOnTextChildren(children, contentSpacing)
12501
13517
  });
12502
13518
  return text;
12503
13519
  };
12504
- const CharSlot = ({
12505
- charWidth = 1,
12506
- // 0 (zéro) is the real char width
12507
- // but 2 zéros gives too big icons
12508
- // while 1 "W" gives a nice result
12509
- baseChar = "W",
12510
- "aria-label": ariaLabel,
12511
- role,
12512
- decorative = false,
12513
- children,
12514
- ...rest
12515
- }) => {
12516
- const invisibleText = baseChar.repeat(charWidth);
12517
- const ariaProps = decorative ? {
12518
- "aria-hidden": "true"
12519
- } : {
12520
- role,
12521
- "aria-label": ariaLabel
12522
- };
12523
- return jsx(Text, {
12524
- ...rest,
12525
- ...ariaProps,
12526
- foregroundElement: children,
12527
- children: jsx("span", {
12528
- className: "navi_char_slot_invisible",
12529
- "aria-hidden": "true",
12530
- children: invisibleText
12531
- })
12532
- });
12533
- };
12534
- const Icon = ({
12535
- box,
12536
- href,
12537
- children,
12538
- ...props
12539
- }) => {
12540
- const innerChildren = href ? jsx("svg", {
12541
- width: "100%",
12542
- height: "100%",
12543
- children: jsx("use", {
12544
- href: href
12545
- })
12546
- }) : children;
12547
- if (box) {
12548
- return jsx(Box, {
12549
- layoutInline: true,
12550
- layoutColumn: true,
12551
- ...props,
12552
- children: innerChildren
12553
- });
12554
- }
12555
- return jsx(CharSlot, {
12556
- decorative: true,
12557
- ...props,
12558
- children: jsx("span", {
12559
- className: "navi_icon",
12560
- children: innerChildren
12561
- })
12562
- });
12563
- };
12564
13520
  const Paragraph = ({
12565
13521
  contentSpacing = " ",
12566
13522
  marginTop = "md",
@@ -12623,32 +13579,142 @@ const applyContentSpacingOnTextChildren = (children, contentSpacing) => {
12623
13579
  return childrenWithGap;
12624
13580
  };
12625
13581
 
13582
+ installImportMetaCss(import.meta);import.meta.css = /* css */`
13583
+ .navi_icon {
13584
+ display: inline-block;
13585
+ box-sizing: border-box;
13586
+ }
13587
+
13588
+ .navi_icon_char_slot {
13589
+ opacity: 0;
13590
+ }
13591
+ .navi_icon_foreground {
13592
+ position: absolute;
13593
+ inset: 0;
13594
+ display: inline-flex;
13595
+ box-sizing: border-box;
13596
+ align-items: center;
13597
+ justify-content: start;
13598
+ }
13599
+ .navi_icon_foreground > .navi_text {
13600
+ display: flex;
13601
+ aspect-ratio: 1 / 1;
13602
+ height: 100%;
13603
+ max-height: 1em;
13604
+ align-items: center;
13605
+ justify-content: center;
13606
+ }
13607
+
13608
+ .navi_icon > svg,
13609
+ .navi_icon > img {
13610
+ width: 100%;
13611
+ height: 100%;
13612
+ }
13613
+ .navi_icon[data-width] > svg,
13614
+ .navi_icon[data-width] > img {
13615
+ width: 100%;
13616
+ height: auto;
13617
+ }
13618
+ .navi_icon[data-height] > svg,
13619
+ .navi_icon[data-height] > img {
13620
+ width: auto;
13621
+ height: 100%;
13622
+ }
13623
+ `;
13624
+ const Icon = ({
13625
+ href,
13626
+ children,
13627
+ className,
13628
+ charWidth = 1,
13629
+ // 0 (zéro) is the real char width
13630
+ // but 2 zéros gives too big icons
13631
+ // while 1 "W" gives a nice result
13632
+ baseChar = "W",
13633
+ "aria-label": ariaLabel,
13634
+ role,
13635
+ decorative = false,
13636
+ ...props
13637
+ }) => {
13638
+ const innerChildren = href ? jsx("svg", {
13639
+ width: "100%",
13640
+ height: "100%",
13641
+ children: jsx("use", {
13642
+ href: href
13643
+ })
13644
+ }) : children;
13645
+ let {
13646
+ box,
13647
+ width,
13648
+ height
13649
+ } = props;
13650
+ if (width !== undefined || height !== undefined) {
13651
+ box = true;
13652
+ }
13653
+ if (box) {
13654
+ return jsx(Box, {
13655
+ ...props,
13656
+ baseClassName: "navi_icon",
13657
+ "data-width": width,
13658
+ "data-height": height,
13659
+ children: innerChildren
13660
+ });
13661
+ }
13662
+ const invisibleText = baseChar.repeat(charWidth);
13663
+ const ariaProps = decorative ? {
13664
+ "aria-hidden": "true"
13665
+ } : {
13666
+ role,
13667
+ "aria-label": ariaLabel
13668
+ };
13669
+ return jsxs(Text, {
13670
+ ...props,
13671
+ ...ariaProps,
13672
+ box: box,
13673
+ className: withPropsClassName("navi_icon", className),
13674
+ "data-icon-char": "",
13675
+ "data-width": width,
13676
+ "data-height": height,
13677
+ children: [jsx("span", {
13678
+ className: "navi_icon_char_slot",
13679
+ "aria-hidden": "true",
13680
+ children: invisibleText
13681
+ }), jsx("span", {
13682
+ className: "navi_icon_foreground",
13683
+ children: jsx(Text, {
13684
+ children: innerChildren
13685
+ })
13686
+ })]
13687
+ });
13688
+ };
13689
+
12626
13690
  installImportMetaCss(import.meta);import.meta.css = /* css */`
12627
13691
  @layer navi {
12628
13692
  .navi_link {
13693
+ --border-radius: 2px;
13694
+ --outline-color: var(--navi-focus-outline-color);
12629
13695
  --color: rgb(0, 0, 238);
12630
13696
  --color-visited: light-dark(#6a1b9a, #ab47bc);
12631
13697
  --color-active: red;
12632
13698
  --text-decoration: underline;
12633
- --text-decoration-hover: underline;
13699
+ --text-decoration-hover: var(--text-decoration);
12634
13700
  --cursor: pointer;
12635
13701
  }
12636
13702
  }
12637
13703
 
12638
13704
  .navi_link {
12639
- position: relative;
12640
- border-radius: 2px;
12641
-
12642
13705
  --x-color: var(--color);
12643
13706
  --x-color-hover: var(--color-hover, var(--color));
12644
13707
  --x-color-visited: var(--color-visited);
12645
13708
  --x-color-active: var(--color-active);
12646
13709
  --x-text-decoration: var(--text-decoration);
12647
- --x-text-decoration-hover: var(--text-decoration-hover,);
13710
+ --x-text-decoration-hover: var(--text-decoration-hover);
12648
13711
  --x-cursor: var(--cursor);
12649
13712
 
13713
+ position: relative;
12650
13714
  color: var(--x-color);
12651
13715
  text-decoration: var(--x-text-decoration);
13716
+ border-radius: var(--border-radius);
13717
+ outline-color: var(--outline-color);
12652
13718
  cursor: var(--x-cursor);
12653
13719
  }
12654
13720
  /* Hover */
@@ -12661,6 +13727,10 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
12661
13727
  position: relative;
12662
13728
  z-index: 1; /* Ensure focus outline is above other elements */
12663
13729
  }
13730
+ .navi_link[data-focus-visible] {
13731
+ outline-width: 2px;
13732
+ outline-style: solid;
13733
+ }
12664
13734
  /* Visited */
12665
13735
  .navi_link[data-visited] {
12666
13736
  --x-color: var(--x-color-visited);
@@ -12694,9 +13764,18 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
12694
13764
  }
12695
13765
  `;
12696
13766
  const LinkManagedByCSSVars = {
12697
- color: "--color",
12698
- cursor: "--cursor",
12699
- textDecoration: "--text-decoration"
13767
+ "outlineColor": "--outline-color",
13768
+ "borderRadius": "--border-radius",
13769
+ "color": "--color",
13770
+ "cursor": "--cursor",
13771
+ "textDecoration": "--text-decoration",
13772
+ ":hover": {
13773
+ color: "--color-hover",
13774
+ textDecoration: "--text-decoration-hover"
13775
+ },
13776
+ ":active": {
13777
+ color: "--color-active"
13778
+ }
12700
13779
  };
12701
13780
  const LinkPseudoClasses = [":hover", ":active", ":focus", ":focus-visible", ":read-only", ":disabled", ":visited", ":-navi-loading", ":-navi-internal-link", ":-navi-external-link", ":-navi-anchor-link", ":-navi-current-link"];
12702
13781
  const LinkPseudoElements = ["::-navi-loader"];
@@ -12746,7 +13825,6 @@ const LinkPlain = props => {
12746
13825
  rel,
12747
13826
  preventDefault,
12748
13827
  // visual
12749
- box,
12750
13828
  blankTargetIcon,
12751
13829
  anchorIcon,
12752
13830
  icon,
@@ -12806,8 +13884,6 @@ const LinkPlain = props => {
12806
13884
  // Visual
12807
13885
  ,
12808
13886
  baseClassName: "navi_link",
12809
- layoutInline: true,
12810
- layoutColumn: box ? true : undefined,
12811
13887
  managedByCSSVars: LinkManagedByCSSVars,
12812
13888
  pseudoClasses: LinkPseudoClasses,
12813
13889
  pseudoElements: LinkPseudoElements,
@@ -12853,8 +13929,6 @@ const LinkPlain = props => {
12853
13929
  const BlankTargetLinkSvg = () => {
12854
13930
  return jsx("svg", {
12855
13931
  viewBox: "0 0 24 24",
12856
- width: "100%",
12857
- height: "100%",
12858
13932
  xmlns: "http://www.w3.org/2000/svg",
12859
13933
  children: jsx("path", {
12860
13934
  d: "M10.0002 5H8.2002C7.08009 5 6.51962 5 6.0918 5.21799C5.71547 5.40973 5.40973 5.71547 5.21799 6.0918C5 6.51962 5 7.08009 5 8.2002V15.8002C5 16.9203 5 17.4801 5.21799 17.9079C5.40973 18.2842 5.71547 18.5905 6.0918 18.7822C6.5192 19 7.07899 19 8.19691 19H15.8031C16.921 19 17.48 19 17.9074 18.7822C18.2837 18.5905 18.5905 18.2839 18.7822 17.9076C19 17.4802 19 16.921 19 15.8031V14M20 9V4M20 4H15M20 4L13 11",
@@ -12869,8 +13943,6 @@ const BlankTargetLinkSvg = () => {
12869
13943
  const AnchorLinkSvg = () => {
12870
13944
  return jsxs("svg", {
12871
13945
  viewBox: "0 0 24 24",
12872
- width: "100%",
12873
- height: "100%",
12874
13946
  xmlns: "http://www.w3.org/2000/svg",
12875
13947
  children: [jsx("path", {
12876
13948
  d: "M13.2218 3.32234C15.3697 1.17445 18.8521 1.17445 21 3.32234C23.1479 5.47022 23.1479 8.95263 21 11.1005L17.4645 14.636C15.3166 16.7839 11.8342 16.7839 9.6863 14.636C9.48752 14.4373 9.30713 14.2271 9.14514 14.0075C8.90318 13.6796 8.97098 13.2301 9.25914 12.9419C9.73221 12.4688 10.5662 12.6561 11.0245 13.1435C11.0494 13.1699 11.0747 13.196 11.1005 13.2218C12.4673 14.5887 14.6834 14.5887 16.0503 13.2218L19.5858 9.6863C20.9526 8.31947 20.9526 6.10339 19.5858 4.73655C18.219 3.36972 16.0029 3.36972 14.636 4.73655L13.5754 5.79721C13.1849 6.18774 12.5517 6.18774 12.1612 5.79721C11.7706 5.40669 11.7706 4.77352 12.1612 4.383L13.2218 3.32234Z",
@@ -12884,8 +13956,6 @@ const AnchorLinkSvg = () => {
12884
13956
  const PhoneSvg = () => {
12885
13957
  return jsx("svg", {
12886
13958
  viewBox: "0 0 24 24",
12887
- width: "100%",
12888
- height: "100%",
12889
13959
  xmlns: "http://www.w3.org/2000/svg",
12890
13960
  children: jsx("path", {
12891
13961
  d: "M6.62 10.79c1.44 2.83 3.76 5.14 6.59 6.59l2.2-2.2c.27-.27.67-.36 1.02-.24 1.12.37 2.33.57 3.57.57.55 0 1 .45 1 1V20c0 .55-.45 1-1 1-9.39 0-17-7.61-17-17 0-.55.45-1 1-1h3.5c.55 0 1 .45 1 1 0 1.25.2 2.45.57 3.57.11.35.03.74-.25 1.02l-2.2 2.2z",
@@ -12896,8 +13966,6 @@ const PhoneSvg = () => {
12896
13966
  const SmsSvg = () => {
12897
13967
  return jsx("svg", {
12898
13968
  viewBox: "0 0 24 24",
12899
- width: "100%",
12900
- height: "100%",
12901
13969
  xmlns: "http://www.w3.org/2000/svg",
12902
13970
  children: jsx("path", {
12903
13971
  d: "M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zM18 14H6v-2h12v2zm0-3H6V9h12v2zm0-3H6V6h12v2z",
@@ -12908,8 +13976,6 @@ const SmsSvg = () => {
12908
13976
  const EmailSvg = () => {
12909
13977
  return jsxs("svg", {
12910
13978
  viewBox: "0 0 24 24",
12911
- width: "100%",
12912
- height: "100%",
12913
13979
  xmlns: "http://www.w3.org/2000/svg",
12914
13980
  children: [jsx("path", {
12915
13981
  d: "M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z",
@@ -12929,8 +13995,6 @@ const EmailSvg = () => {
12929
13995
  const GithubSvg = () => {
12930
13996
  return jsx("svg", {
12931
13997
  viewBox: "0 0 24 24",
12932
- width: "100%",
12933
- height: "100%",
12934
13998
  xmlns: "http://www.w3.org/2000/svg",
12935
13999
  children: jsx("path", {
12936
14000
  d: "M12 2C6.48 2 2 6.48 2 12c0 4.42 2.87 8.17 6.84 9.5.5.08.66-.23.66-.5v-1.69c-2.77.6-3.36-1.34-3.36-1.34-.46-1.16-1.11-1.47-1.11-1.47-.91-.62.07-.6.07-.6 1 .07 1.53 1.03 1.53 1.03.87 1.52 2.34 1.07 2.91.83.09-.65.35-1.09.63-1.34-2.22-.25-4.55-1.11-4.55-4.92 0-1.11.38-2 1.03-2.71-.1-.25-.45-1.29.1-2.64 0 0 .84-.27 2.75 1.02.79-.22 1.65-.33 2.5-.33.85 0 1.71.11 2.5.33 1.91-1.29 2.75-1.02 2.75-1.02.55 1.35.2 2.39.1 2.64.65.71 1.03 1.6 1.03 2.71 0 3.82-2.34 4.66-4.57 4.91.36.31.69.92.69 1.85V21c0 .27.16.59.67.5C19.14 20.16 22 16.42 22 12A10 10 0 0012 2z",
@@ -14075,7 +15139,8 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
14075
15139
  --width: 13px;
14076
15140
  --height: 13px;
14077
15141
 
14078
- --outline-color: light-dark(#4476ff, #3b82f6);
15142
+ --outline-color: var(--navi-focus-outline-color);
15143
+ --loader-color: var(--navi-loader-color);
14079
15144
  --border-color: light-dark(#767676, #8e8e93);
14080
15145
  --background-color: white;
14081
15146
  --color: light-dark(#4476ff, #3b82f6);
@@ -14373,7 +15438,7 @@ const InputCheckboxBasic = props => {
14373
15438
  children: [jsx(LoaderBackground, {
14374
15439
  loading: innerLoading,
14375
15440
  inset: -1,
14376
- color: "var(--navi-loader-color)"
15441
+ color: "var(--loader-color)"
14377
15442
  }), renderCheckboxMemoized, jsx("div", {
14378
15443
  className: "navi_checkbox_field",
14379
15444
  children: jsx("svg", {
@@ -14464,7 +15529,8 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
14464
15529
  --width: 13px;
14465
15530
  --height: 13px;
14466
15531
 
14467
- --outline-color: light-dark(#4476ff, #3b82f6);
15532
+ --outline-color: var(--navi-focus-outline-color);
15533
+ --loader-color: var(--navi-loader-color);
14468
15534
  --border-color: light-dark(#767676, #8e8e93);
14469
15535
  --background-color: white;
14470
15536
  --color: light-dark(#4476ff, #3b82f6);
@@ -14806,7 +15872,7 @@ const InputRadioBasic = props => {
14806
15872
  loading: innerLoading,
14807
15873
  inset: -1,
14808
15874
  targetSelector: ".navi_radio_field",
14809
- color: "var(--navi-loader-color)"
15875
+ color: "var(--loader-color)"
14810
15876
  }), renderRadioMemoized, jsx("span", {
14811
15877
  className: "navi_radio_field",
14812
15878
  children: jsxs("svg", {
@@ -14851,7 +15917,8 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
14851
15917
  --outer-width: calc(var(--border-width) + var(--outline-width));
14852
15918
 
14853
15919
  /* Default */
14854
- --outline-color: light-dark(#4476ff, #3b82f6);
15920
+ --outline-color: var(--navi-focus-outline-color);
15921
+ --loader-color: var(--navi-loader-color);
14855
15922
  --border-color: light-dark(#767676, #8e8e93);
14856
15923
  --background-color: white;
14857
15924
  --color: currentColor;
@@ -15093,7 +16160,7 @@ const InputTextualBasic = props => {
15093
16160
  ...rest,
15094
16161
  children: [jsx(LoaderBackground, {
15095
16162
  loading: innerLoading,
15096
- color: "var(--navi-loader-color)",
16163
+ color: "var(--loader-color)",
15097
16164
  inset: -1
15098
16165
  }), renderInputMemoized]
15099
16166
  });
@@ -15454,7 +16521,8 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
15454
16521
  --border-width: 1px;
15455
16522
  --border-radius: 2px;
15456
16523
  /* default */
15457
- --outline-color: light-dark(#4476ff, #3b82f6);
16524
+ --outline-color: var(--navi-focus-outline-color);
16525
+ --loader-color: var(--navi-loader-color);
15458
16526
  --border-color: light-dark(#767676, #8e8e93);
15459
16527
  --background-color: light-dark(#f3f4f6, #2d3748);
15460
16528
  --color: currentColor;
@@ -15497,13 +16565,10 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
15497
16565
  --x-color: var(--color);
15498
16566
 
15499
16567
  position: relative;
16568
+ display: inline-flex;
15500
16569
  box-sizing: border-box;
15501
- width: fit-content;
15502
- height: fit-content;
15503
16570
  padding: 0;
15504
16571
  flex-direction: inherit;
15505
- align-items: inherit;
15506
- justify-content: inherit;
15507
16572
  background: none;
15508
16573
  border: none;
15509
16574
  border-radius: inherit;
@@ -15512,6 +16577,7 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
15512
16577
  }
15513
16578
  .navi_button_content {
15514
16579
  position: relative;
16580
+ box-sizing: border-box;
15515
16581
  padding-top: var(--padding-top, var(--padding-y, var(--padding, 1px)));
15516
16582
  padding-right: var(--padding-right, var(--padding-x, var(--padding, 6px)));
15517
16583
  padding-bottom: var(
@@ -15682,7 +16748,6 @@ const ButtonBasic = props => {
15682
16748
  ...buttonProps,
15683
16749
  as: "span",
15684
16750
  baseClassName: "navi_button_content",
15685
- layoutInline: true,
15686
16751
  children: [innerChildren, jsx("span", {
15687
16752
  className: "navi_button_shadow"
15688
16753
  })]
@@ -15700,8 +16765,6 @@ const ButtonBasic = props => {
15700
16765
  // style management
15701
16766
  ,
15702
16767
  baseClassName: "navi_button",
15703
- layoutInline: true,
15704
- layoutColumn: true,
15705
16768
  managedByCSSVars: ButtonManagedByCSSVars,
15706
16769
  pseudoClasses: ButtonPseudoClasses,
15707
16770
  pseudoElements: ButtonPseudoElements,
@@ -15715,7 +16778,7 @@ const ButtonBasic = props => {
15715
16778
  children: [jsx(LoaderBackground, {
15716
16779
  loading: innerLoading,
15717
16780
  inset: -1,
15718
- color: "var(--navi-loader-color)"
16781
+ color: "var(--loader-color)"
15719
16782
  }), renderButtonContentMemoized]
15720
16783
  });
15721
16784
  };
@@ -20182,53 +21245,9 @@ const useSignalSync = (value, initialValue = value) => {
20182
21245
  return signal;
20183
21246
  };
20184
21247
 
20185
- installImportMetaCss(import.meta);import.meta.css = /* css */`
20186
- .navi_font_sized_svg {
20187
- display: flex;
20188
- width: 1em;
20189
- height: 1em;
20190
- flex-shrink: 0;
20191
- align-items: center;
20192
- justify-self: center;
20193
- line-height: 1em;
20194
- }
20195
- `;
20196
- const FontSizedSvg = ({
20197
- children,
20198
- ...rest
20199
- }) => {
20200
- return jsx(Box, {
20201
- ...rest,
20202
- as: "span",
20203
- baseClassName: "navi_font_sized_svg",
20204
- children: children
20205
- });
20206
- };
21248
+ const FontSizedSvg = () => {};
20207
21249
 
20208
- const IconAndText = ({
20209
- icon,
20210
- children,
20211
- ...rest
20212
- }) => {
20213
- if (typeof icon === "function") icon = icon({});
20214
- return jsxs("span", {
20215
- className: "icon_and_text",
20216
- ...rest,
20217
- style: {
20218
- display: "flex",
20219
- alignItems: "center",
20220
- gap: "0.1em",
20221
- ...rest.style
20222
- },
20223
- children: [jsx(FontSizedSvg, {
20224
- className: "icon",
20225
- children: icon
20226
- }), jsx("span", {
20227
- className: "text",
20228
- children: children
20229
- })]
20230
- });
20231
- };
21250
+ const IconAndText = () => {};
20232
21251
 
20233
21252
  installImportMetaCss(import.meta);import.meta.css = /* css */`
20234
21253
  .svg_mask_content * {
@@ -20296,29 +21315,97 @@ const SVGMaskOverlay = ({
20296
21315
  });
20297
21316
  };
20298
21317
 
21318
+ const CSS_VAR_NAME = "--color-contrasting";
21319
+
21320
+ const useContrastingColor = (ref) => {
21321
+ useLayoutEffect(() => {
21322
+ const el = ref.current;
21323
+ if (!el) {
21324
+ return;
21325
+ }
21326
+ const lightColor = "var(--navi-color-light)";
21327
+ const darkColor = "var(--navi-color-dark)";
21328
+ const backgroundColor = getComputedStyle(el).backgroundColor;
21329
+ if (!backgroundColor) {
21330
+ el.style.removeProperty(CSS_VAR_NAME);
21331
+ return;
21332
+ }
21333
+ const colorPicked = pickLightOrDark(
21334
+ backgroundColor,
21335
+ lightColor,
21336
+ darkColor,
21337
+ el,
21338
+ );
21339
+ el.style.setProperty(CSS_VAR_NAME, colorPicked);
21340
+ }, []);
21341
+ };
21342
+
20299
21343
  installImportMetaCss(import.meta);import.meta.css = /* css */`
20300
- .navi_count {
20301
- position: relative;
20302
- top: -1px;
20303
- color: rgba(28, 43, 52, 0.4);
21344
+ @layer navi {
21345
+ .navi_badge {
21346
+ --border-radius: 1em;
21347
+ }
21348
+ }
21349
+ .navi_badge {
21350
+ display: inline-block;
21351
+ box-sizing: border-box;
21352
+ min-width: 1.5em;
21353
+ height: 1.5em;
21354
+ max-height: 1.5em;
21355
+ padding-right: var(
21356
+ --padding-right,
21357
+ var(--padding-x, var(--padding, 0.4em))
21358
+ );
21359
+ padding-left: var(--padding-left, var(--padding-x, var(--padding, 0.4em)));
21360
+ color: var(--color, var(--color-contrasting));
21361
+ text-align: center;
21362
+ line-height: 1.5em;
21363
+ vertical-align: middle;
21364
+ border-radius: var(--border-radius, 1em);
20304
21365
  }
20305
21366
  `;
20306
- const Count = ({
21367
+ const BadgeManagedByCSSVars = {
21368
+ borderWidth: "--border-width",
21369
+ borderRadius: "--border-radius",
21370
+ paddingRight: "--padding-right",
21371
+ paddingLeft: "--padding-left",
21372
+ backgroundColor: "--background-color",
21373
+ borderColor: "--border-color",
21374
+ color: "--color"
21375
+ };
21376
+ const BadgeCount = ({
20307
21377
  children,
20308
- ...rest
21378
+ bold = true,
21379
+ max,
21380
+ ...props
20309
21381
  }) => {
20310
- return jsxs(Box, {
20311
- as: "span",
20312
- baseClassName: ".navi_count",
20313
- ...rest,
20314
- children: ["(", children, ")"]
20315
- });
20316
- };
21382
+ const defaultRef = useRef();
21383
+ const ref = props.ref || defaultRef;
20317
21384
 
20318
- const Image = props => {
20319
- return jsx(Box, {
21385
+ // Calculer la valeur à afficher en fonction du paramètre max
21386
+ const getDisplayValue = () => {
21387
+ if (max === undefined) {
21388
+ return children;
21389
+ }
21390
+ const numericValue = typeof children === "string" ? parseInt(children, 10) : children;
21391
+ const numericMax = typeof max === "string" ? parseInt(max, 10) : max;
21392
+ if (isNaN(numericValue) || isNaN(numericMax)) {
21393
+ return children;
21394
+ }
21395
+ if (numericValue > numericMax) {
21396
+ return `${numericMax}+`;
21397
+ }
21398
+ return children;
21399
+ };
21400
+ const displayValue = getDisplayValue();
21401
+ useContrastingColor(ref);
21402
+ return jsx(Text, {
20320
21403
  ...props,
20321
- as: "img"
21404
+ ref: ref,
21405
+ className: "navi_badge",
21406
+ bold: bold,
21407
+ managedByCSSVars: BadgeManagedByCSSVars,
21408
+ children: displayValue
20322
21409
  });
20323
21410
  };
20324
21411
 
@@ -20334,6 +21421,13 @@ const Code = ({
20334
21421
  });
20335
21422
  };
20336
21423
 
21424
+ const Image = props => {
21425
+ return jsx(Box, {
21426
+ ...props,
21427
+ as: "img"
21428
+ });
21429
+ };
21430
+
20337
21431
  const LinkWithIcon = () => {};
20338
21432
 
20339
21433
  installImportMetaCss(import.meta);import.meta.css = /* css */`
@@ -20571,5 +21665,5 @@ const useDependenciesDiff = (inputs) => {
20571
21665
  return diffRef.current;
20572
21666
  };
20573
21667
 
20574
- export { ActionRenderer, ActiveKeyboardShortcuts, Box, Button, CharSlot, Checkbox, CheckboxList, Code, Col, Colgroup, Count, Details, Editable, ErrorBoundaryContext, FontSizedSvg, Form, Icon, IconAndText, Image, Input, Label, Layout, Link, LinkWithIcon, MessageBox, Paragraph, Radio, RadioList, Route, RouteLink, Routes, RowNumberCol, RowNumberTableCell, SINGLE_SPACE_CONSTRAINT, SVGMaskOverlay, Select, SelectionContext, SummaryMarker, Svg, Tab, TabList, Table, TableCell, Tbody, Text, Thead, Title, Tr, UITransition, actionIntegratedVia, addCustomMessage, createAction, createSelectionKeyboardShortcuts, createUniqueValueConstraint, enableDebugActions, enableDebugOnDocumentLoading, forwardActionRequested, goBack, goForward, goTo, installCustomConstraintValidation, isCellSelected, isColumnSelected, isRowSelected, openCallout, rawUrlPart, reload, removeCustomMessage, rerunActions, resource, setBaseUrl, setupRoutes, stopLoad, stringifyTableSelectionValue, updateActions, useActionData, useActionStatus, useCellsAndColumns, useDependenciesDiff, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useKeyboardShortcuts, useNavState, useRouteStatus, useRunOnMount, useSelectableElement, useSelectionController, useSignalSync, useStateArray, useUrlSearchParam, valueInLocalStorage };
21668
+ export { ActionRenderer, ActiveKeyboardShortcuts, BadgeCount, Box, Button, Checkbox, CheckboxList, Code, Col, Colgroup, Details, Editable, ErrorBoundaryContext, FontSizedSvg, Form, Icon, IconAndText, Image, Input, Label, Layout, Link, LinkWithIcon, MessageBox, Paragraph, Radio, RadioList, Route, RouteLink, Routes, RowNumberCol, RowNumberTableCell, SINGLE_SPACE_CONSTRAINT, SVGMaskOverlay, Select, SelectionContext, SummaryMarker, Svg, Tab, TabList, Table, TableCell, Tbody, Text, Thead, Title, Tr, UITransition, actionIntegratedVia, addCustomMessage, createAction, createSelectionKeyboardShortcuts, createUniqueValueConstraint, enableDebugActions, enableDebugOnDocumentLoading, forwardActionRequested, goBack, goForward, goTo, installCustomConstraintValidation, isCellSelected, isColumnSelected, isRowSelected, openCallout, rawUrlPart, reload, removeCustomMessage, rerunActions, resource, setBaseUrl, setupRoutes, stopLoad, stringifyTableSelectionValue, updateActions, useActionData, useActionStatus, useActiveRouteInfo, useCellsAndColumns, useDependenciesDiff, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useKeyboardShortcuts, useNavState, useRouteStatus, useRunOnMount, useSelectableElement, useSelectionController, useSignalSync, useStateArray, useUrlSearchParam, valueInLocalStorage };
20575
21669
  //# sourceMappingURL=jsenv_navi.js.map