@jsenv/navi 0.21.9 → 0.22.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/jsenv_navi.js +2022 -1870
- package/dist/jsenv_navi.js.map +131 -94
- package/package.json +1 -1
package/dist/jsenv_navi.js
CHANGED
|
@@ -3,7 +3,7 @@ import { isValidElement, h, createContext, toChildArray, render, createRef, clon
|
|
|
3
3
|
import { useErrorBoundary, useLayoutEffect, useEffect, useMemo, useRef, useState, useCallback, useContext, useImperativeHandle, useId } from "preact/hooks";
|
|
4
4
|
import { jsxs, jsx, Fragment } from "preact/jsx-runtime";
|
|
5
5
|
import { signal, effect, computed, batch, useSignal } from "@preact/signals";
|
|
6
|
-
import { createIterableWeakSet, mergeOneStyle, stringifyStyle, createPubSub, mergeTwoStyles, normalizeStyles, createGroupTransitionController, getElementSignature, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, findBefore, findAfter, createValueEffect, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, allowWheelThrough, resolveCSSColor, createStyleController, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes,
|
|
6
|
+
import { createIterableWeakSet, mergeOneStyle, stringifyStyle, createPubSub, mergeTwoStyles, normalizeStyles, createGroupTransitionController, getElementSignature, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, findBefore, findAfter, createValueEffect, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, allowWheelThrough, resolveCSSColor, createStyleController, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, resolveCSSSize, activeElementSignal, canInterceptKeys, hasCSSSizeUnit, contrastColor, initFocusGroup, elementIsFocusable, resolveColorLuminance, dragAfterThreshold, getScrollContainer, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement } from "@jsenv/dom";
|
|
7
7
|
export { contrastColor } from "@jsenv/dom";
|
|
8
8
|
import { prefixFirstAndIndentRemainingLines } from "@jsenv/humanize";
|
|
9
9
|
import { createValidity } from "@jsenv/validity";
|
|
@@ -18734,2036 +18734,2178 @@ const useConstraints = (elementRef, props, { targetSelector } = {}) => {
|
|
|
18734
18734
|
return remainingProps;
|
|
18735
18735
|
};
|
|
18736
18736
|
|
|
18737
|
-
const
|
|
18738
|
-
|
|
18739
|
-
|
|
18740
|
-
|
|
18741
|
-
|
|
18742
|
-
|
|
18743
|
-
|
|
18737
|
+
const EmailSvg = () => {
|
|
18738
|
+
return jsxs("svg", {
|
|
18739
|
+
viewBox: "0 0 24 24",
|
|
18740
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
18741
|
+
children: [jsx("path", {
|
|
18742
|
+
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",
|
|
18743
|
+
fill: "none",
|
|
18744
|
+
stroke: "currentColor",
|
|
18745
|
+
"stroke-width": "2"
|
|
18746
|
+
}), jsx("path", {
|
|
18747
|
+
d: "m2 6 8 5 2 1.5 2-1.5 8-5",
|
|
18748
|
+
fill: "none",
|
|
18749
|
+
stroke: "currentColor",
|
|
18750
|
+
"stroke-width": "2",
|
|
18751
|
+
"stroke-linecap": "round",
|
|
18752
|
+
"stroke-linejoin": "round"
|
|
18753
|
+
})]
|
|
18754
|
+
});
|
|
18755
|
+
};
|
|
18756
|
+
|
|
18757
|
+
const LinkBlankTargetSvg = () => {
|
|
18758
|
+
return jsx("svg", {
|
|
18759
|
+
viewBox: "0 0 24 24",
|
|
18760
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
18761
|
+
children: jsx("path", {
|
|
18762
|
+
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",
|
|
18763
|
+
stroke: "currentColor",
|
|
18764
|
+
fill: "none",
|
|
18765
|
+
"stroke-width": "2",
|
|
18766
|
+
"stroke-linecap": "round",
|
|
18767
|
+
"stroke-linejoin": "round"
|
|
18768
|
+
})
|
|
18769
|
+
});
|
|
18770
|
+
};
|
|
18771
|
+
const LinkAnchorSvg = () => {
|
|
18772
|
+
return jsx("svg", {
|
|
18773
|
+
viewBox: "0 0 24 24",
|
|
18774
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
18775
|
+
children: jsxs("g", {
|
|
18776
|
+
children: [jsx("path", {
|
|
18777
|
+
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",
|
|
18778
|
+
fill: "currentColor"
|
|
18779
|
+
}), jsx("path", {
|
|
18780
|
+
d: "M6.85787 9.6863C8.90184 7.64233 12.2261 7.60094 14.3494 9.42268C14.7319 9.75083 14.7008 10.3287 14.3444 10.685C13.9253 11.1041 13.2317 11.0404 12.7416 10.707C11.398 9.79292 9.48593 9.88667 8.27209 11.1005L4.73655 14.636C3.36972 16.0029 3.36972 18.219 4.73655 19.5858C6.10339 20.9526 8.31947 20.9526 9.6863 19.5858L10.747 18.5251C11.1375 18.1346 11.7706 18.1346 12.1612 18.5251C12.5517 18.9157 12.5517 19.5488 12.1612 19.9394L11.1005 21C8.95263 23.1479 5.47022 23.1479 3.32234 21C1.17445 18.8521 1.17445 15.3697 3.32234 13.2218L6.85787 9.6863Z",
|
|
18781
|
+
fill: "currentColor"
|
|
18782
|
+
})]
|
|
18783
|
+
})
|
|
18784
|
+
});
|
|
18785
|
+
};
|
|
18786
|
+
const LinkSmsSvg = () => {
|
|
18787
|
+
return jsx("svg", {
|
|
18788
|
+
viewBox: "0 0 24 24",
|
|
18789
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
18790
|
+
children: jsx("path", {
|
|
18791
|
+
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",
|
|
18792
|
+
fill: "currentColor"
|
|
18793
|
+
})
|
|
18794
|
+
});
|
|
18795
|
+
};
|
|
18796
|
+
const LinkGithubSvg = () => {
|
|
18797
|
+
return jsx("svg", {
|
|
18798
|
+
viewBox: "0 0 24 24",
|
|
18799
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
18800
|
+
children: jsx("path", {
|
|
18801
|
+
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",
|
|
18802
|
+
fill: "currentColor"
|
|
18803
|
+
})
|
|
18804
|
+
});
|
|
18805
|
+
};
|
|
18806
|
+
const LinkCurrentSvg = () => {
|
|
18807
|
+
return jsx("svg", {
|
|
18808
|
+
viewBox: "0 0 16 16",
|
|
18809
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
18810
|
+
children: jsx("path", {
|
|
18811
|
+
d: "m 8 0 c -3.3125 0 -6 2.6875 -6 6 c 0.007812 0.710938 0.136719 1.414062 0.386719 2.078125 l -0.015625 -0.003906 c 0.636718 1.988281 3.78125 5.082031 5.625 6.929687 h 0.003906 v -0.003906 c 1.507812 -1.507812 3.878906 -3.925781 5.046875 -5.753906 c 0.261719 -0.414063 0.46875 -0.808594 0.585937 -1.171875 l -0.019531 0.003906 c 0.25 -0.664063 0.382813 -1.367187 0.386719 -2.078125 c 0 -3.3125 -2.683594 -6 -6 -6 z m 0 3.691406 c 1.273438 0 2.308594 1.035156 2.308594 2.308594 s -1.035156 2.308594 -2.308594 2.308594 c -1.273438 -0.003906 -2.304688 -1.035156 -2.304688 -2.308594 c -0.003906 -1.273438 1.03125 -2.304688 2.304688 -2.308594 z m 0 0",
|
|
18812
|
+
fill: "currentColor"
|
|
18813
|
+
})
|
|
18814
|
+
});
|
|
18815
|
+
};
|
|
18816
|
+
|
|
18817
|
+
const PhoneSvg = () => {
|
|
18818
|
+
return jsx("svg", {
|
|
18819
|
+
viewBox: "0 0 24 24",
|
|
18820
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
18821
|
+
children: jsx("path", {
|
|
18822
|
+
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",
|
|
18823
|
+
fill: "currentColor"
|
|
18824
|
+
})
|
|
18825
|
+
});
|
|
18826
|
+
};
|
|
18827
|
+
|
|
18828
|
+
const useDebounceTrue = (value, delay = 300) => {
|
|
18829
|
+
const [debouncedTrue, setDebouncedTrue] = useState(false);
|
|
18830
|
+
const timerRef = useRef(null);
|
|
18831
|
+
|
|
18744
18832
|
useLayoutEffect(() => {
|
|
18745
|
-
|
|
18746
|
-
if (
|
|
18747
|
-
|
|
18748
|
-
|
|
18749
|
-
|
|
18750
|
-
|
|
18751
|
-
|
|
18752
|
-
if (
|
|
18753
|
-
|
|
18754
|
-
|
|
18755
|
-
// Format: [0, 10] - character indices
|
|
18756
|
-
selectByCharacterIndices(el, range, start, end);
|
|
18757
|
-
} else if (typeof start === "string" && typeof end === "string") {
|
|
18758
|
-
// Format: ["Click on the", "button to return"] - text strings
|
|
18759
|
-
selectByTextStrings(el, range, start, end);
|
|
18760
|
-
}
|
|
18833
|
+
// If value is true or becomes true, start a timer
|
|
18834
|
+
if (value) {
|
|
18835
|
+
timerRef.current = setTimeout(() => {
|
|
18836
|
+
setDebouncedTrue(true);
|
|
18837
|
+
}, delay);
|
|
18838
|
+
} else {
|
|
18839
|
+
// If value becomes false, clear any pending timer and immediately set to false
|
|
18840
|
+
if (timerRef.current) {
|
|
18841
|
+
clearTimeout(timerRef.current);
|
|
18842
|
+
timerRef.current = null;
|
|
18761
18843
|
}
|
|
18762
|
-
|
|
18763
|
-
// Format: "some text" - select the entire string occurrence
|
|
18764
|
-
selectSingleTextString(el, range, textSelection);
|
|
18844
|
+
setDebouncedTrue(false);
|
|
18765
18845
|
}
|
|
18766
|
-
selection.removeAllRanges();
|
|
18767
|
-
selection.addRange(range);
|
|
18768
|
-
}, deps);
|
|
18769
|
-
};
|
|
18770
|
-
const selectByCharacterIndices = (element, range, startIndex, endIndex) => {
|
|
18771
|
-
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false);
|
|
18772
|
-
let currentIndex = 0;
|
|
18773
|
-
let startNode = null;
|
|
18774
|
-
let startOffset = 0;
|
|
18775
|
-
let endNode = null;
|
|
18776
|
-
let endOffset = 0;
|
|
18777
|
-
while (walker.nextNode()) {
|
|
18778
|
-
const textContent = walker.currentNode.textContent;
|
|
18779
|
-
const nodeLength = textContent.length;
|
|
18780
18846
|
|
|
18781
|
-
//
|
|
18782
|
-
|
|
18783
|
-
|
|
18784
|
-
|
|
18785
|
-
|
|
18847
|
+
// Cleanup function
|
|
18848
|
+
return () => {
|
|
18849
|
+
if (timerRef.current) {
|
|
18850
|
+
clearTimeout(timerRef.current);
|
|
18851
|
+
}
|
|
18852
|
+
};
|
|
18853
|
+
}, [value, delay]);
|
|
18786
18854
|
|
|
18787
|
-
|
|
18788
|
-
if (currentIndex + nodeLength >= endIndex) {
|
|
18789
|
-
endNode = walker.currentNode;
|
|
18790
|
-
endOffset = endIndex - currentIndex;
|
|
18791
|
-
break;
|
|
18792
|
-
}
|
|
18793
|
-
currentIndex += nodeLength;
|
|
18794
|
-
}
|
|
18795
|
-
if (startNode && endNode) {
|
|
18796
|
-
range.setStart(startNode, startOffset);
|
|
18797
|
-
range.setEnd(endNode, endOffset);
|
|
18798
|
-
}
|
|
18855
|
+
return debouncedTrue;
|
|
18799
18856
|
};
|
|
18800
|
-
|
|
18801
|
-
|
|
18802
|
-
|
|
18803
|
-
const textContent = walker.currentNode.textContent;
|
|
18804
|
-
const index = textContent.indexOf(text);
|
|
18805
|
-
if (index !== -1) {
|
|
18806
|
-
range.setStart(walker.currentNode, index);
|
|
18807
|
-
range.setEnd(walker.currentNode, index + text.length);
|
|
18808
|
-
return;
|
|
18809
|
-
}
|
|
18810
|
-
}
|
|
18857
|
+
|
|
18858
|
+
const useNetworkSpeed = () => {
|
|
18859
|
+
return networkSpeedSignal.value;
|
|
18811
18860
|
};
|
|
18812
|
-
|
|
18813
|
-
|
|
18814
|
-
|
|
18815
|
-
|
|
18816
|
-
|
|
18817
|
-
|
|
18818
|
-
|
|
18819
|
-
|
|
18820
|
-
|
|
18821
|
-
|
|
18861
|
+
|
|
18862
|
+
const connection =
|
|
18863
|
+
window.navigator.connection ||
|
|
18864
|
+
window.navigator.mozConnection ||
|
|
18865
|
+
window.navigator.webkitConnection;
|
|
18866
|
+
|
|
18867
|
+
const getNetworkSpeed = () => {
|
|
18868
|
+
// ✅ Network Information API (support moderne)
|
|
18869
|
+
if (!connection) {
|
|
18870
|
+
return "3g";
|
|
18871
|
+
}
|
|
18872
|
+
if (connection) {
|
|
18873
|
+
const effectiveType = connection.effectiveType;
|
|
18874
|
+
if (effectiveType) {
|
|
18875
|
+
return effectiveType; // "slow-2g", "2g", "3g", "4g", "5g"
|
|
18822
18876
|
}
|
|
18823
|
-
|
|
18824
|
-
|
|
18825
|
-
|
|
18877
|
+
const downlink = connection.downlink;
|
|
18878
|
+
if (downlink) {
|
|
18879
|
+
// downlink is in Mbps
|
|
18880
|
+
if (downlink < 1) return "slow-2g"; // < 1 Mbps
|
|
18881
|
+
if (downlink < 2.5) return "2g"; // 1-2.5 Mbps
|
|
18882
|
+
if (downlink < 10) return "3g"; // 2.5-10 Mbps
|
|
18883
|
+
return "4g"; // > 10 Mbps
|
|
18826
18884
|
}
|
|
18827
18885
|
}
|
|
18828
|
-
|
|
18829
|
-
const startOffset = startNode.textContent.indexOf(startText);
|
|
18830
|
-
const endOffset = endNode.textContent.indexOf(endText) + endText.length;
|
|
18831
|
-
range.setStart(startNode, startOffset);
|
|
18832
|
-
range.setEnd(endNode, endOffset);
|
|
18833
|
-
}
|
|
18886
|
+
return "3g";
|
|
18834
18887
|
};
|
|
18835
18888
|
|
|
18836
|
-
|
|
18837
|
-
|
|
18838
|
-
|
|
18839
|
-
.navi_text {
|
|
18840
|
-
&[data-skeleton] {
|
|
18841
|
-
border-radius: 0.2em;
|
|
18842
|
-
}
|
|
18843
|
-
}
|
|
18844
|
-
}
|
|
18889
|
+
const updateNetworkSpeed = () => {
|
|
18890
|
+
networkSpeedSignal.value = getNetworkSpeed();
|
|
18891
|
+
};
|
|
18845
18892
|
|
|
18846
|
-
|
|
18847
|
-
|
|
18848
|
-
|
|
18893
|
+
const networkSpeedSignal = signal(getNetworkSpeed());
|
|
18894
|
+
|
|
18895
|
+
const setupNetworkMonitoring = () => {
|
|
18896
|
+
const cleanupFunctions = [];
|
|
18897
|
+
|
|
18898
|
+
// ✅ 1. Écouter les changements natifs
|
|
18899
|
+
|
|
18900
|
+
if (connection) {
|
|
18901
|
+
connection.addEventListener("change", updateNetworkSpeed);
|
|
18902
|
+
cleanupFunctions.push(() => {
|
|
18903
|
+
connection.removeEventListener("change", updateNetworkSpeed);
|
|
18904
|
+
});
|
|
18849
18905
|
}
|
|
18850
18906
|
|
|
18851
|
-
.
|
|
18852
|
-
|
|
18907
|
+
// ✅ 2. Polling de backup (toutes les 60 secondes)
|
|
18908
|
+
const pollInterval = setInterval(updateNetworkSpeed, 60000);
|
|
18909
|
+
cleanupFunctions.push(() => clearInterval(pollInterval));
|
|
18853
18910
|
|
|
18854
|
-
|
|
18855
|
-
|
|
18856
|
-
|
|
18857
|
-
|
|
18858
|
-
text-transform: uppercase;
|
|
18859
|
-
}
|
|
18860
|
-
.navi_text_bold_clone::first-letter {
|
|
18861
|
-
text-transform: uppercase;
|
|
18862
|
-
}
|
|
18863
|
-
.navi_text_bold_foreground::first-letter {
|
|
18864
|
-
text-transform: uppercase;
|
|
18865
|
-
}
|
|
18866
|
-
}
|
|
18867
|
-
|
|
18868
|
-
.navi_text_bold_wrapper,
|
|
18869
|
-
.navi_text_bold_clone,
|
|
18870
|
-
.navi_text_bold_foreground {
|
|
18871
|
-
display: inherit;
|
|
18872
|
-
width: inherit;
|
|
18873
|
-
min-width: inherit;
|
|
18874
|
-
height: inherit;
|
|
18875
|
-
min-height: inherit;
|
|
18876
|
-
flex-grow: inherit;
|
|
18877
|
-
align-items: inherit;
|
|
18878
|
-
justify-content: inherit;
|
|
18879
|
-
gap: inherit;
|
|
18880
|
-
text-align: inherit;
|
|
18881
|
-
border-radius: inherit;
|
|
18882
|
-
}
|
|
18883
|
-
|
|
18884
|
-
&[data-text-overflow] {
|
|
18885
|
-
min-width: 0;
|
|
18886
|
-
flex-wrap: wrap;
|
|
18887
|
-
text-overflow: ellipsis;
|
|
18888
|
-
overflow: hidden;
|
|
18889
|
-
|
|
18890
|
-
.navi_text_overflow_wrapper {
|
|
18891
|
-
display: flex;
|
|
18892
|
-
width: 100%;
|
|
18893
|
-
flex-grow: 1;
|
|
18894
|
-
gap: 0.3em;
|
|
18895
|
-
|
|
18896
|
-
.navi_text_overflow_text {
|
|
18897
|
-
max-width: 100%;
|
|
18898
|
-
text-overflow: ellipsis;
|
|
18899
|
-
overflow: hidden;
|
|
18900
|
-
}
|
|
18901
|
-
}
|
|
18911
|
+
// ✅ 3. Vérifier lors de la reprise d'activité
|
|
18912
|
+
const handleVisibilityChange = () => {
|
|
18913
|
+
if (!document.hidden) {
|
|
18914
|
+
updateNetworkSpeed();
|
|
18902
18915
|
}
|
|
18916
|
+
};
|
|
18903
18917
|
|
|
18904
|
-
|
|
18905
|
-
|
|
18906
|
-
|
|
18907
|
-
|
|
18908
|
-
|
|
18909
|
-
/* When there are no children a placeholder "W" is injected (see JSX).
|
|
18910
|
-
It must stretch to the full available width so the skeleton
|
|
18911
|
-
fills the container rather than collapsing to a single character. */
|
|
18912
|
-
.navi_text_skeleton_children_placeholder {
|
|
18913
|
-
display: inline-flex;
|
|
18914
|
-
width: 100%;
|
|
18915
|
-
}
|
|
18916
|
-
|
|
18917
|
-
/* Three-level structure to respect padding AND border-radius:
|
|
18918
|
-
|
|
18919
|
-
1. navi_text_skeleton_container — absolutely fills the border box
|
|
18920
|
-
(inset:0), then applies padding:inherit so its content box equals
|
|
18921
|
-
the parent's content box. line-height:normal prevents the container
|
|
18922
|
-
from inheriting a large line-height that would make it taller than
|
|
18923
|
-
the border box. border-radius:inherit passes the radius down.
|
|
18924
|
-
visibility:visible overrides the parent's visibility:hidden.
|
|
18925
|
-
|
|
18926
|
-
2. navi_text_skeleton_inset — a relative block that fills 100% of the
|
|
18927
|
-
container's content box (= parent's content box). It is the
|
|
18928
|
-
positioned ancestor for the absolutely placed skeleton bar.
|
|
18929
|
-
border-radius:inherit chains the radius further down.
|
|
18930
|
-
|
|
18931
|
-
3. navi_text_skeleton — the visible gradient bar. position:absolute
|
|
18932
|
-
inset:0 fills the inset box precisely. border-radius:inherit
|
|
18933
|
-
finally applies the radius at this level, which is now correctly
|
|
18934
|
-
sized to the content area. */
|
|
18935
|
-
.navi_text_skeleton_container {
|
|
18936
|
-
position: absolute;
|
|
18937
|
-
inset: 0;
|
|
18938
|
-
padding: inherit;
|
|
18939
|
-
line-height: normal;
|
|
18940
|
-
border-radius: inherit;
|
|
18941
|
-
visibility: visible;
|
|
18942
|
-
}
|
|
18943
|
-
|
|
18944
|
-
.navi_text_skeleton_inset {
|
|
18945
|
-
position: relative;
|
|
18946
|
-
display: inline-flex;
|
|
18947
|
-
width: 100%;
|
|
18948
|
-
height: 100%;
|
|
18949
|
-
border-radius: inherit;
|
|
18950
|
-
}
|
|
18918
|
+
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
18919
|
+
cleanupFunctions.push(() => {
|
|
18920
|
+
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
18921
|
+
});
|
|
18951
18922
|
|
|
18952
|
-
|
|
18953
|
-
|
|
18954
|
-
|
|
18955
|
-
|
|
18956
|
-
90deg,
|
|
18957
|
-
#e0e0e0 25%,
|
|
18958
|
-
#f0f0f0 50%,
|
|
18959
|
-
#e0e0e0 75%
|
|
18960
|
-
);
|
|
18961
|
-
background-size: 200% 100%;
|
|
18962
|
-
border-radius: inherit;
|
|
18963
|
-
}
|
|
18923
|
+
// ✅ 4. Vérifier lors de la reprise de connexion
|
|
18924
|
+
const handleOnline = () => {
|
|
18925
|
+
updateNetworkSpeed();
|
|
18926
|
+
};
|
|
18964
18927
|
|
|
18965
|
-
|
|
18966
|
-
|
|
18967
|
-
|
|
18968
|
-
|
|
18969
|
-
}
|
|
18970
|
-
}
|
|
18971
|
-
}
|
|
18928
|
+
window.addEventListener("online", handleOnline);
|
|
18929
|
+
cleanupFunctions.push(() => {
|
|
18930
|
+
window.removeEventListener("online", handleOnline);
|
|
18931
|
+
});
|
|
18972
18932
|
|
|
18973
|
-
|
|
18974
|
-
|
|
18975
|
-
|
|
18976
|
-
|
|
18977
|
-
|
|
18978
|
-
|
|
18979
|
-
}
|
|
18980
|
-
}
|
|
18933
|
+
// Cleanup global
|
|
18934
|
+
return () => {
|
|
18935
|
+
cleanupFunctions.forEach((cleanup) => cleanup());
|
|
18936
|
+
};
|
|
18937
|
+
};
|
|
18938
|
+
setupNetworkMonitoring();
|
|
18981
18939
|
|
|
18982
|
-
|
|
18940
|
+
installImportMetaCssBuild(import.meta);/**
|
|
18941
|
+
* RectangleLoading Component
|
|
18942
|
+
*
|
|
18943
|
+
* A responsive loading indicator that dynamically adjusts to fit its container.
|
|
18944
|
+
* Displays an animated outline with a traveling dot that follows the container's shape.
|
|
18945
|
+
*
|
|
18946
|
+
* Features:
|
|
18947
|
+
* - Adapts to any container dimensions using ResizeObserver
|
|
18948
|
+
* - Scales stroke width, margins and corner radius proportionally
|
|
18949
|
+
* - Animates using native SVG animations for smooth performance
|
|
18950
|
+
* - High-quality SVG rendering with proper path calculations
|
|
18951
|
+
*
|
|
18952
|
+
* @param {Object} props - Component props
|
|
18953
|
+
* @param {string} [props.color="#383a36"] - Color of the loading indicator
|
|
18954
|
+
* @param {number} [props.radius=0] - Corner radius of the rectangle (px)
|
|
18955
|
+
*/
|
|
18956
|
+
import.meta.css = [/* css */`
|
|
18957
|
+
.navi_rectangle_loading {
|
|
18983
18958
|
position: relative;
|
|
18984
|
-
display:
|
|
18985
|
-
|
|
18986
|
-
|
|
18987
|
-
font-weight: bold;
|
|
18988
|
-
opacity: 0;
|
|
18989
|
-
}
|
|
18990
|
-
.navi_text_bold_foreground {
|
|
18991
|
-
position: absolute;
|
|
18992
|
-
inset: 0;
|
|
18993
|
-
}
|
|
18994
|
-
}
|
|
18995
|
-
|
|
18996
|
-
.navi_text_bold_background {
|
|
18997
|
-
position: absolute;
|
|
18998
|
-
top: 0;
|
|
18999
|
-
left: 0;
|
|
19000
|
-
color: currentColor;
|
|
19001
|
-
font-weight: normal;
|
|
19002
|
-
background: currentColor;
|
|
19003
|
-
background-clip: text;
|
|
19004
|
-
-webkit-background-clip: text;
|
|
19005
|
-
transform-origin: center;
|
|
19006
|
-
-webkit-text-fill-color: transparent;
|
|
18959
|
+
display: flex;
|
|
18960
|
+
width: 100%;
|
|
18961
|
+
height: 100%;
|
|
19007
18962
|
opacity: 0;
|
|
19008
18963
|
}
|
|
19009
18964
|
|
|
19010
|
-
.
|
|
19011
|
-
|
|
19012
|
-
opacity: 1;
|
|
19013
|
-
}
|
|
19014
|
-
}
|
|
19015
|
-
|
|
19016
|
-
.navi_text[data-bold-transition] {
|
|
19017
|
-
.navi_text_bold_foreground {
|
|
19018
|
-
transition-property: font-weight;
|
|
19019
|
-
transition-duration: 0.3s;
|
|
19020
|
-
transition-timing-function: ease;
|
|
19021
|
-
}
|
|
19022
|
-
|
|
19023
|
-
.navi_text_bold_background {
|
|
19024
|
-
transition-property: opacity;
|
|
19025
|
-
transition-duration: 0.3s;
|
|
19026
|
-
transition-timing-function: ease;
|
|
19027
|
-
}
|
|
18965
|
+
.navi_rectangle_loading[data-visible] {
|
|
18966
|
+
opacity: 1;
|
|
19028
18967
|
}
|
|
19029
|
-
|
|
19030
|
-
|
|
19031
|
-
|
|
19032
|
-
|
|
19033
|
-
|
|
19034
|
-
|
|
19035
|
-
"data-navi-space": "",
|
|
19036
|
-
children: "\u200B"
|
|
19037
|
-
});
|
|
19038
|
-
const CustomWidthSpace = ({
|
|
19039
|
-
value
|
|
18968
|
+
`, "@jsenv/navi/src/graphic/loader/rectangle_loading.jsx"];
|
|
18969
|
+
const RectangleLoading = ({
|
|
18970
|
+
shouldShowSpinner,
|
|
18971
|
+
color = "currentColor",
|
|
18972
|
+
radius = 0,
|
|
18973
|
+
size = 2
|
|
19040
18974
|
}) => {
|
|
19041
|
-
|
|
19042
|
-
|
|
19043
|
-
|
|
19044
|
-
|
|
19045
|
-
|
|
19046
|
-
|
|
19047
|
-
|
|
19048
|
-
if (spacing === "pre" || spacing === "0" || spacing === 0) {
|
|
19049
|
-
return children;
|
|
19050
|
-
}
|
|
19051
|
-
if (!children) {
|
|
19052
|
-
return children;
|
|
19053
|
-
}
|
|
19054
|
-
const childArray = toChildArray(children);
|
|
19055
|
-
const childCount = childArray.length;
|
|
19056
|
-
if (childCount <= 1) {
|
|
19057
|
-
return children;
|
|
19058
|
-
}
|
|
19059
|
-
let separator;
|
|
19060
|
-
if (spacing === undefined) {
|
|
19061
|
-
spacing = REGULAR_SPACE;
|
|
19062
|
-
} else if (typeof spacing === "string") {
|
|
19063
|
-
if (isSizeSpacingScaleKey(spacing)) {
|
|
19064
|
-
separator = jsx(CustomWidthSpace, {
|
|
19065
|
-
value: resolveSpacingSize(spacing)
|
|
19066
|
-
});
|
|
19067
|
-
} else if (hasCSSSizeUnit(spacing)) {
|
|
19068
|
-
separator = jsx(CustomWidthSpace, {
|
|
19069
|
-
value: resolveSpacingSize(spacing)
|
|
19070
|
-
});
|
|
19071
|
-
} else {
|
|
19072
|
-
separator = spacing;
|
|
18975
|
+
const containerRef = useRef(null);
|
|
18976
|
+
const [containerWidth, setContainerWidth] = useState(0);
|
|
18977
|
+
const [containerHeight, setContainerHeight] = useState(0);
|
|
18978
|
+
useLayoutEffect(() => {
|
|
18979
|
+
const container = containerRef.current;
|
|
18980
|
+
if (!container) {
|
|
18981
|
+
return null;
|
|
19073
18982
|
}
|
|
19074
|
-
|
|
19075
|
-
|
|
19076
|
-
|
|
19077
|
-
});
|
|
19078
|
-
|
|
19079
|
-
|
|
19080
|
-
|
|
19081
|
-
|
|
19082
|
-
|
|
19083
|
-
|
|
19084
|
-
|
|
19085
|
-
|
|
19086
|
-
|
|
19087
|
-
|
|
19088
|
-
|
|
19089
|
-
|
|
19090
|
-
|
|
19091
|
-
|
|
19092
|
-
|
|
19093
|
-
|
|
19094
|
-
|
|
19095
|
-
|
|
19096
|
-
}
|
|
19097
|
-
return childrenWithGap;
|
|
19098
|
-
};
|
|
19099
|
-
const outsideTextFlowSet = new Set();
|
|
19100
|
-
const markAsOutsideTextFlow = jsxElement => {
|
|
19101
|
-
outsideTextFlowSet.add(jsxElement);
|
|
19102
|
-
};
|
|
19103
|
-
const isMarkedAsOutsideTextFlow = jsxElement => {
|
|
19104
|
-
return outsideTextFlowSet.has(jsxElement.type);
|
|
19105
|
-
};
|
|
19106
|
-
const isPreactNode = jsxChild => {
|
|
19107
|
-
return jsxChild !== null && typeof jsxChild === "object" && jsxChild.type !== undefined;
|
|
19108
|
-
};
|
|
19109
|
-
const shouldInjectSpacingBetween = (left, right) => {
|
|
19110
|
-
const leftIsNode = isPreactNode(left);
|
|
19111
|
-
const rightIsNode = isPreactNode(right);
|
|
19112
|
-
// only inject spacing when at least one side is a preact node
|
|
19113
|
-
if (!leftIsNode && !rightIsNode) {
|
|
19114
|
-
return false;
|
|
19115
|
-
}
|
|
19116
|
-
if (leftIsNode && isMarkedAsOutsideTextFlow(left)) {
|
|
19117
|
-
return false;
|
|
19118
|
-
}
|
|
19119
|
-
if (rightIsNode && isMarkedAsOutsideTextFlow(right)) {
|
|
19120
|
-
return false;
|
|
19121
|
-
}
|
|
19122
|
-
if (rightIsNode && right.props && right.props.overflowPinned) {
|
|
19123
|
-
return false;
|
|
19124
|
-
}
|
|
19125
|
-
if (typeof left === "string" && /\s$/.test(left)) {
|
|
19126
|
-
return false;
|
|
19127
|
-
}
|
|
19128
|
-
if (typeof right === "string" && /^\s/.test(right)) {
|
|
19129
|
-
return false;
|
|
19130
|
-
}
|
|
19131
|
-
return true;
|
|
19132
|
-
};
|
|
19133
|
-
const OverflowPinnedElementContext = createContext(null);
|
|
19134
|
-
const Text = props => {
|
|
19135
|
-
import.meta.css = [css$4, "@jsenv/navi/src/text/text.jsx"];
|
|
19136
|
-
if (props.loading || props.skeleton) {
|
|
19137
|
-
return jsx(TextSkeleton, {
|
|
19138
|
-
...props
|
|
19139
|
-
});
|
|
19140
|
-
}
|
|
19141
|
-
if (props.overflowEllipsis) {
|
|
19142
|
-
return jsx(TextOverflow, {
|
|
19143
|
-
...props
|
|
19144
|
-
});
|
|
19145
|
-
}
|
|
19146
|
-
if (props.overflowPinned) {
|
|
19147
|
-
return jsx(TextOverflowPinned, {
|
|
19148
|
-
...props
|
|
19149
|
-
});
|
|
19150
|
-
}
|
|
19151
|
-
if (props.selectRange) {
|
|
19152
|
-
return jsx(TextWithSelectRange, {
|
|
19153
|
-
...props
|
|
18983
|
+
const {
|
|
18984
|
+
width,
|
|
18985
|
+
height
|
|
18986
|
+
} = container.getBoundingClientRect();
|
|
18987
|
+
setContainerWidth(width);
|
|
18988
|
+
setContainerHeight(height);
|
|
18989
|
+
let animationFrameId = null;
|
|
18990
|
+
// Create a resize observer to detect changes in the container's dimensions
|
|
18991
|
+
const resizeObserver = new ResizeObserver(entries => {
|
|
18992
|
+
// Use requestAnimationFrame to debounce updates
|
|
18993
|
+
if (animationFrameId) {
|
|
18994
|
+
cancelAnimationFrame(animationFrameId);
|
|
18995
|
+
}
|
|
18996
|
+
animationFrameId = requestAnimationFrame(() => {
|
|
18997
|
+
const [containerEntry] = entries;
|
|
18998
|
+
const {
|
|
18999
|
+
width,
|
|
19000
|
+
height
|
|
19001
|
+
} = containerEntry.contentRect;
|
|
19002
|
+
setContainerWidth(width);
|
|
19003
|
+
setContainerHeight(height);
|
|
19004
|
+
});
|
|
19154
19005
|
});
|
|
19155
|
-
|
|
19156
|
-
|
|
19157
|
-
|
|
19158
|
-
|
|
19159
|
-
}
|
|
19160
|
-
|
|
19161
|
-
|
|
19162
|
-
|
|
19163
|
-
|
|
19164
|
-
|
|
19165
|
-
|
|
19166
|
-
|
|
19167
|
-
|
|
19168
|
-
|
|
19169
|
-
|
|
19170
|
-
|
|
19171
|
-
|
|
19172
|
-
|
|
19173
|
-
})
|
|
19006
|
+
resizeObserver.observe(container);
|
|
19007
|
+
return () => {
|
|
19008
|
+
if (animationFrameId) {
|
|
19009
|
+
cancelAnimationFrame(animationFrameId);
|
|
19010
|
+
}
|
|
19011
|
+
resizeObserver.disconnect();
|
|
19012
|
+
};
|
|
19013
|
+
}, []);
|
|
19014
|
+
return jsx("span", {
|
|
19015
|
+
ref: containerRef,
|
|
19016
|
+
className: "navi_rectangle_loading",
|
|
19017
|
+
"data-visible": shouldShowSpinner ? "" : undefined,
|
|
19018
|
+
children: containerWidth > 0 && containerHeight > 0 && jsx(RectangleLoadingSvg, {
|
|
19019
|
+
radius: radius,
|
|
19020
|
+
color: color,
|
|
19021
|
+
width: containerWidth,
|
|
19022
|
+
height: containerHeight,
|
|
19023
|
+
strokeWidth: size
|
|
19174
19024
|
})
|
|
19175
19025
|
});
|
|
19176
|
-
// When there are no children, inject a full-width placeholder so the element
|
|
19177
|
-
// has measurable height driven by the current font-size/line-height, and the
|
|
19178
|
-
// skeleton fills the available width instead of shrinking to a single char.
|
|
19179
|
-
const hasChildren = children !== null && children !== undefined && children !== false;
|
|
19180
|
-
const innerChildren = hasChildren ? children : jsx("span", {
|
|
19181
|
-
className: "navi_text_skeleton_children_placeholder",
|
|
19182
|
-
"aria-hidden": "true",
|
|
19183
|
-
children: "W"
|
|
19184
|
-
});
|
|
19185
|
-
return jsx(Text, {
|
|
19186
|
-
"data-skeleton": "",
|
|
19187
|
-
"data-loading": loading ? "" : undefined,
|
|
19188
|
-
...props,
|
|
19189
|
-
skeleton: undefined,
|
|
19190
|
-
childrenOutsideFlow: skeletonOverlay,
|
|
19191
|
-
children: innerChildren
|
|
19192
|
-
});
|
|
19193
19026
|
};
|
|
19194
|
-
const
|
|
19195
|
-
|
|
19196
|
-
|
|
19197
|
-
|
|
19198
|
-
|
|
19027
|
+
const RectangleLoadingSvg = ({
|
|
19028
|
+
width,
|
|
19029
|
+
height,
|
|
19030
|
+
color,
|
|
19031
|
+
radius,
|
|
19032
|
+
trailColor = "transparent",
|
|
19033
|
+
strokeWidth
|
|
19199
19034
|
}) => {
|
|
19200
|
-
const
|
|
19201
|
-
return jsx(Text, {
|
|
19202
|
-
flex: true,
|
|
19203
|
-
block: true,
|
|
19204
|
-
as: "div",
|
|
19205
|
-
nowWrap: noWrap,
|
|
19206
|
-
pre: !noWrap
|
|
19207
|
-
// For paragraph we prefer to keep lines and only hide unbreakable long sections
|
|
19208
|
-
,
|
|
19035
|
+
const margin = Math.max(2, Math.min(width, height) * 0.03);
|
|
19209
19036
|
|
|
19210
|
-
|
|
19211
|
-
|
|
19212
|
-
|
|
19213
|
-
"data-text-overflow": "",
|
|
19214
|
-
spacing: "pre",
|
|
19215
|
-
children: jsxs("span", {
|
|
19216
|
-
className: "navi_text_overflow_wrapper",
|
|
19217
|
-
children: [jsx(OverflowPinnedElementContext.Provider, {
|
|
19218
|
-
value: setOverflowPinnedElement,
|
|
19219
|
-
children: jsx(Text, {
|
|
19220
|
-
className: "navi_text_overflow_text",
|
|
19221
|
-
spacing: spacing,
|
|
19222
|
-
children: children
|
|
19223
|
-
})
|
|
19224
|
-
}), OverflowPinnedElement]
|
|
19225
|
-
})
|
|
19226
|
-
});
|
|
19227
|
-
};
|
|
19228
|
-
const TextOverflowPinned = ({
|
|
19229
|
-
overflowPinned,
|
|
19230
|
-
...props
|
|
19231
|
-
}) => {
|
|
19232
|
-
const setOverflowPinnedElement = useContext(OverflowPinnedElementContext);
|
|
19233
|
-
const text = jsx(Text, {
|
|
19234
|
-
...props,
|
|
19235
|
-
"data-overflow-pinned": ""
|
|
19236
|
-
});
|
|
19237
|
-
if (!setOverflowPinnedElement) {
|
|
19238
|
-
console.warn("<Text overflowPinned> declared outside a <Text overflowEllipsis>");
|
|
19239
|
-
return text;
|
|
19240
|
-
}
|
|
19241
|
-
if (overflowPinned) {
|
|
19242
|
-
setOverflowPinnedElement(text);
|
|
19243
|
-
return null;
|
|
19244
|
-
}
|
|
19245
|
-
setOverflowPinnedElement(null);
|
|
19246
|
-
return text;
|
|
19247
|
-
};
|
|
19248
|
-
const TextWithSelectRange = ({
|
|
19249
|
-
selectRange,
|
|
19250
|
-
...props
|
|
19251
|
-
}) => {
|
|
19252
|
-
const defaultRef = useRef();
|
|
19253
|
-
const ref = props.ref || defaultRef;
|
|
19254
|
-
useInitialTextSelection(ref, selectRange);
|
|
19255
|
-
return jsx(Text, {
|
|
19256
|
-
ref: ref,
|
|
19257
|
-
...props
|
|
19258
|
-
});
|
|
19259
|
-
};
|
|
19260
|
-
const TextBasic = ({
|
|
19261
|
-
spacing = REGULAR_SPACE,
|
|
19262
|
-
boldTransition,
|
|
19263
|
-
boldStable,
|
|
19264
|
-
preventBoldLayoutShift = boldTransition,
|
|
19265
|
-
capitalize,
|
|
19266
|
-
children,
|
|
19267
|
-
childrenOutsideFlow,
|
|
19268
|
-
...rest
|
|
19269
|
-
}) => {
|
|
19270
|
-
const boxProps = {
|
|
19271
|
-
"as": "span",
|
|
19272
|
-
"data-bold-transition": boldTransition ? "" : undefined,
|
|
19273
|
-
"data-capitalize": capitalize ? "" : undefined,
|
|
19274
|
-
...rest,
|
|
19275
|
-
"baseClassName": withPropsClassName("navi_text", rest.baseClassName)
|
|
19276
|
-
};
|
|
19277
|
-
const shouldPreserveSpacing = rest.as === "pre" || rest.flex || rest.grid;
|
|
19278
|
-
if (shouldPreserveSpacing) {
|
|
19279
|
-
boxProps.spacing = spacing;
|
|
19280
|
-
} else {
|
|
19281
|
-
children = applySpacingOnTextChildren(children, spacing);
|
|
19282
|
-
}
|
|
19283
|
-
if (boldStable) {
|
|
19284
|
-
const {
|
|
19285
|
-
bold
|
|
19286
|
-
} = boxProps;
|
|
19287
|
-
return jsxs(Box, {
|
|
19288
|
-
...boxProps,
|
|
19289
|
-
bold: undefined,
|
|
19290
|
-
"data-bold": bold ? "" : undefined,
|
|
19291
|
-
children: [jsx("span", {
|
|
19292
|
-
className: "navi_text_bold_background",
|
|
19293
|
-
"aria-hidden": "true",
|
|
19294
|
-
children: children
|
|
19295
|
-
}), children, childrenOutsideFlow]
|
|
19296
|
-
});
|
|
19297
|
-
}
|
|
19298
|
-
if (preventBoldLayoutShift) {
|
|
19299
|
-
const alignX = rest.alignX || rest.align || "start";
|
|
19037
|
+
// Calculate the drawable area
|
|
19038
|
+
const drawableWidth = width - margin * 2;
|
|
19039
|
+
const drawableHeight = height - margin * 2;
|
|
19300
19040
|
|
|
19301
|
-
|
|
19302
|
-
|
|
19303
|
-
|
|
19304
|
-
|
|
19305
|
-
// on pourrait auto-active cela sur une prop genre boldCanChange
|
|
19306
|
-
return jsxs(Box, {
|
|
19307
|
-
...boxProps,
|
|
19308
|
-
children: [jsxs("span", {
|
|
19309
|
-
className: "navi_text_bold_wrapper",
|
|
19310
|
-
children: [jsx("span", {
|
|
19311
|
-
className: "navi_text_bold_clone",
|
|
19312
|
-
"aria-hidden": "true",
|
|
19313
|
-
children: children
|
|
19314
|
-
}), jsx("span", {
|
|
19315
|
-
className: "navi_text_bold_foreground",
|
|
19316
|
-
"data-align": alignX,
|
|
19317
|
-
children: children
|
|
19318
|
-
})]
|
|
19319
|
-
}), childrenOutsideFlow]
|
|
19320
|
-
});
|
|
19321
|
-
}
|
|
19322
|
-
return jsxs(Box, {
|
|
19323
|
-
...boxProps,
|
|
19324
|
-
children: [children, childrenOutsideFlow]
|
|
19041
|
+
// ✅ Check if this should be a circle - only if width and height are nearly equal
|
|
19042
|
+
const maxPossibleRadius = Math.min(drawableWidth, drawableHeight) / 2;
|
|
19043
|
+
radius = resolveCSSSize(radius, {
|
|
19044
|
+
availableSize: Math.min(width, height)
|
|
19325
19045
|
});
|
|
19326
|
-
|
|
19327
|
-
|
|
19328
|
-
|
|
19329
|
-
|
|
19330
|
-
|
|
19331
|
-
|
|
19332
|
-
|
|
19333
|
-
|
|
19334
|
-
|
|
19335
|
-
|
|
19336
|
-
}
|
|
19337
|
-
}
|
|
19338
|
-
|
|
19339
|
-
.navi_icon {
|
|
19340
|
-
&[data-flow-inline] {
|
|
19341
|
-
width: 1em;
|
|
19342
|
-
height: 1em;
|
|
19343
|
-
}
|
|
19344
|
-
&[data-icon-char] {
|
|
19345
|
-
flex-grow: 0 !important;
|
|
19046
|
+
const actualRadius = Math.min(radius || Math.min(drawableWidth, drawableHeight) * 0.05, maxPossibleRadius // ✅ Limité au radius maximum possible
|
|
19047
|
+
);
|
|
19048
|
+
const aspectRatio = Math.max(drawableWidth, drawableHeight) / Math.min(drawableWidth, drawableHeight);
|
|
19049
|
+
const isNearlySquare = aspectRatio <= 1.2; // Allow some tolerance for nearly square shapes
|
|
19050
|
+
const isCircle = isNearlySquare && actualRadius >= maxPossibleRadius * 0.95;
|
|
19051
|
+
let pathLength;
|
|
19052
|
+
let rectPath;
|
|
19053
|
+
if (isCircle) {
|
|
19054
|
+
// ✅ Circle: perimeter = 2πr
|
|
19055
|
+
pathLength = 2 * Math.PI * actualRadius;
|
|
19346
19056
|
|
|
19347
|
-
|
|
19348
|
-
|
|
19349
|
-
|
|
19350
|
-
|
|
19351
|
-
}
|
|
19352
|
-
|
|
19353
|
-
|
|
19354
|
-
|
|
19355
|
-
|
|
19356
|
-
|
|
19357
|
-
|
|
19358
|
-
|
|
19057
|
+
// ✅ Circle path centered in the drawable area
|
|
19058
|
+
const centerX = margin + drawableWidth / 2;
|
|
19059
|
+
const centerY = margin + drawableHeight / 2;
|
|
19060
|
+
rectPath = `
|
|
19061
|
+
M ${centerX + actualRadius},${centerY}
|
|
19062
|
+
A ${actualRadius},${actualRadius} 0 1 1 ${centerX - actualRadius},${centerY}
|
|
19063
|
+
A ${actualRadius},${actualRadius} 0 1 1 ${centerX + actualRadius},${centerY}
|
|
19064
|
+
`;
|
|
19065
|
+
} else {
|
|
19066
|
+
// ✅ Rectangle: calculate perimeter properly
|
|
19067
|
+
const straightEdges = 2 * (drawableWidth - 2 * actualRadius) + 2 * (drawableHeight - 2 * actualRadius);
|
|
19068
|
+
const cornerArcs = actualRadius > 0 ? 2 * Math.PI * actualRadius : 0;
|
|
19069
|
+
pathLength = straightEdges + cornerArcs;
|
|
19070
|
+
rectPath = `
|
|
19071
|
+
M ${margin + actualRadius},${margin}
|
|
19072
|
+
L ${margin + drawableWidth - actualRadius},${margin}
|
|
19073
|
+
A ${actualRadius},${actualRadius} 0 0 1 ${margin + drawableWidth},${margin + actualRadius}
|
|
19074
|
+
L ${margin + drawableWidth},${margin + drawableHeight - actualRadius}
|
|
19075
|
+
A ${actualRadius},${actualRadius} 0 0 1 ${margin + drawableWidth - actualRadius},${margin + drawableHeight}
|
|
19076
|
+
L ${margin + actualRadius},${margin + drawableHeight}
|
|
19077
|
+
A ${actualRadius},${actualRadius} 0 0 1 ${margin},${margin + drawableHeight - actualRadius}
|
|
19078
|
+
L ${margin},${margin + actualRadius}
|
|
19079
|
+
A ${actualRadius},${actualRadius} 0 0 1 ${margin + actualRadius},${margin}
|
|
19080
|
+
`;
|
|
19359
19081
|
}
|
|
19360
19082
|
|
|
19361
|
-
|
|
19362
|
-
|
|
19363
|
-
|
|
19364
|
-
|
|
19365
|
-
}
|
|
19366
|
-
.navi_text.navi_icon_foreground {
|
|
19367
|
-
position: absolute;
|
|
19368
|
-
inset: 0;
|
|
19369
|
-
display: inline-flex;
|
|
19083
|
+
// Fixed segment size in pixels
|
|
19084
|
+
const maxSegmentSize = 40;
|
|
19085
|
+
const segmentLength = Math.min(maxSegmentSize, pathLength * 0.25);
|
|
19086
|
+
const gapLength = pathLength - segmentLength;
|
|
19370
19087
|
|
|
19371
|
-
|
|
19372
|
-
|
|
19373
|
-
|
|
19374
|
-
|
|
19375
|
-
|
|
19376
|
-
|
|
19377
|
-
|
|
19378
|
-
|
|
19379
|
-
|
|
19380
|
-
}
|
|
19088
|
+
// Vitesse constante en pixels par seconde
|
|
19089
|
+
const networkSpeed = useNetworkSpeed();
|
|
19090
|
+
const pixelsPerSecond = {
|
|
19091
|
+
"slow-2g": 40,
|
|
19092
|
+
"2g": 60,
|
|
19093
|
+
"3g": 80,
|
|
19094
|
+
"4g": 120
|
|
19095
|
+
}[networkSpeed] || 80;
|
|
19096
|
+
const animationDuration = Math.max(1.5, pathLength / pixelsPerSecond);
|
|
19381
19097
|
|
|
19382
|
-
|
|
19383
|
-
|
|
19384
|
-
|
|
19385
|
-
|
|
19386
|
-
backface-visibility: hidden;
|
|
19387
|
-
}
|
|
19388
|
-
.navi_icon[data-width-fixed] > svg,
|
|
19389
|
-
.navi_icon[data-width-fixed] > img {
|
|
19390
|
-
width: 100%;
|
|
19391
|
-
height: auto;
|
|
19392
|
-
}
|
|
19393
|
-
.navi_icon[data-height-fixed] > svg,
|
|
19394
|
-
.navi_icon[data-height-fixed] > img {
|
|
19395
|
-
width: auto;
|
|
19396
|
-
height: 100%;
|
|
19397
|
-
}
|
|
19398
|
-
.navi_icon[data-width-fixed][data-height-fixed] > svg,
|
|
19399
|
-
.navi_icon[data-width-fixed][data-height-fixed] > img {
|
|
19400
|
-
width: 100%;
|
|
19401
|
-
height: 100%;
|
|
19402
|
-
}
|
|
19403
|
-
`;
|
|
19404
|
-
const Icon = ({
|
|
19405
|
-
href,
|
|
19406
|
-
children,
|
|
19407
|
-
charWidth = 1,
|
|
19408
|
-
// 0 (zéro) is the real char width
|
|
19409
|
-
// but 2 zéros gives too big icons
|
|
19410
|
-
// while 1 "W" gives a nice result
|
|
19411
|
-
baseChar = "W",
|
|
19412
|
-
decorative,
|
|
19413
|
-
onClick,
|
|
19414
|
-
...props
|
|
19415
|
-
}) => {
|
|
19416
|
-
import.meta.css = [css$3, "@jsenv/navi/src/graphic/icon.jsx"];
|
|
19417
|
-
const innerChildren = href ? jsx("svg", {
|
|
19098
|
+
// ✅ Calculate correct offset based on actual segment size
|
|
19099
|
+
const segmentRatio = segmentLength / pathLength;
|
|
19100
|
+
const circleOffset = -animationDuration * segmentRatio;
|
|
19101
|
+
return jsxs("svg", {
|
|
19418
19102
|
width: "100%",
|
|
19419
19103
|
height: "100%",
|
|
19420
|
-
|
|
19421
|
-
|
|
19422
|
-
|
|
19423
|
-
}) : children;
|
|
19424
|
-
let {
|
|
19425
|
-
flex,
|
|
19426
|
-
grid,
|
|
19427
|
-
width,
|
|
19428
|
-
height
|
|
19429
|
-
} = props;
|
|
19430
|
-
if (width === "auto") {
|
|
19431
|
-
width = undefined;
|
|
19432
|
-
}
|
|
19433
|
-
if (height === "auto") {
|
|
19434
|
-
height = undefined;
|
|
19435
|
-
}
|
|
19436
|
-
const hasExplicitWidth = width !== undefined;
|
|
19437
|
-
const hasExplicitHeight = height !== undefined;
|
|
19438
|
-
const widthFixed = hasExplicitWidth || hasExplicitHeight && (props.square || props.circle || props.aspectRatio);
|
|
19439
|
-
const heightFixed = hasExplicitHeight || hasExplicitWidth && (props.square || props.circle || props.aspectRatio);
|
|
19440
|
-
if (widthFixed || heightFixed) {
|
|
19441
|
-
if (flex === undefined) {
|
|
19442
|
-
flex = "x";
|
|
19443
|
-
}
|
|
19444
|
-
} else if (decorative === undefined && !onClick) {
|
|
19445
|
-
decorative = true;
|
|
19446
|
-
}
|
|
19447
|
-
const ariaProps = decorative ? {
|
|
19448
|
-
"aria-hidden": "true"
|
|
19449
|
-
} : {};
|
|
19450
|
-
if (typeof children === "string") {
|
|
19451
|
-
return jsx(Text, {
|
|
19452
|
-
...props,
|
|
19453
|
-
...ariaProps,
|
|
19454
|
-
"data-icon-text": "",
|
|
19455
|
-
children: children
|
|
19456
|
-
});
|
|
19457
|
-
}
|
|
19458
|
-
if (flex || grid) {
|
|
19459
|
-
return jsx(Box, {
|
|
19460
|
-
square: true,
|
|
19461
|
-
...props,
|
|
19462
|
-
...ariaProps,
|
|
19463
|
-
flex: flex,
|
|
19464
|
-
baseClassName: "navi_icon",
|
|
19465
|
-
"data-width-fixed": widthFixed ? "" : undefined,
|
|
19466
|
-
"data-height-fixed": heightFixed ? "" : undefined,
|
|
19467
|
-
"data-interactive": onClick ? "" : undefined,
|
|
19468
|
-
onClick: onClick,
|
|
19469
|
-
children: innerChildren
|
|
19470
|
-
});
|
|
19471
|
-
}
|
|
19472
|
-
const invisibleText = baseChar.repeat(charWidth);
|
|
19473
|
-
return jsxs(Text, {
|
|
19474
|
-
...props,
|
|
19475
|
-
...ariaProps,
|
|
19476
|
-
className: withPropsClassName("navi_icon", props.className),
|
|
19477
|
-
spacing: "pre",
|
|
19478
|
-
"data-icon-char": "",
|
|
19479
|
-
"data-width-fixed": widthFixed ? "" : undefined,
|
|
19480
|
-
"data-height-fixed": heightFixed ? "" : undefined,
|
|
19481
|
-
"data-interactive": onClick ? "" : undefined,
|
|
19482
|
-
onClick: onClick,
|
|
19483
|
-
children: [jsx("span", {
|
|
19484
|
-
className: "navi_icon_char_slot",
|
|
19485
|
-
"aria-hidden": "true",
|
|
19486
|
-
children: invisibleText
|
|
19487
|
-
}), jsx(Text, {
|
|
19488
|
-
className: "navi_icon_foreground",
|
|
19489
|
-
spacing: "pre",
|
|
19490
|
-
children: innerChildren
|
|
19491
|
-
})]
|
|
19492
|
-
});
|
|
19493
|
-
};
|
|
19494
|
-
|
|
19495
|
-
const EmailSvg = () => {
|
|
19496
|
-
return jsxs("svg", {
|
|
19497
|
-
viewBox: "0 0 24 24",
|
|
19104
|
+
viewBox: `0 0 ${width} ${height}`,
|
|
19105
|
+
preserveAspectRatio: "none",
|
|
19106
|
+
style: "overflow: visible",
|
|
19498
19107
|
xmlns: "http://www.w3.org/2000/svg",
|
|
19499
|
-
|
|
19500
|
-
|
|
19108
|
+
"shape-rendering": "geometricPrecision",
|
|
19109
|
+
children: [isCircle ? jsx("circle", {
|
|
19110
|
+
cx: margin + drawableWidth / 2,
|
|
19111
|
+
cy: margin + drawableHeight / 2,
|
|
19112
|
+
r: actualRadius,
|
|
19501
19113
|
fill: "none",
|
|
19502
|
-
stroke:
|
|
19503
|
-
|
|
19114
|
+
stroke: trailColor,
|
|
19115
|
+
strokeWidth: strokeWidth
|
|
19116
|
+
}) : jsx("rect", {
|
|
19117
|
+
x: margin,
|
|
19118
|
+
y: margin,
|
|
19119
|
+
width: drawableWidth,
|
|
19120
|
+
height: drawableHeight,
|
|
19121
|
+
fill: "none",
|
|
19122
|
+
stroke: trailColor,
|
|
19123
|
+
strokeWidth: strokeWidth,
|
|
19124
|
+
rx: actualRadius
|
|
19504
19125
|
}), jsx("path", {
|
|
19505
|
-
d:
|
|
19126
|
+
d: rectPath,
|
|
19506
19127
|
fill: "none",
|
|
19507
|
-
stroke:
|
|
19508
|
-
|
|
19509
|
-
|
|
19510
|
-
|
|
19128
|
+
stroke: color,
|
|
19129
|
+
strokeWidth: strokeWidth,
|
|
19130
|
+
strokeLinecap: "round",
|
|
19131
|
+
strokeDasharray: `${segmentLength} ${gapLength}`,
|
|
19132
|
+
pathLength: pathLength,
|
|
19133
|
+
children: jsx("animate", {
|
|
19134
|
+
attributeName: "stroke-dashoffset",
|
|
19135
|
+
from: pathLength,
|
|
19136
|
+
to: "0",
|
|
19137
|
+
dur: `${animationDuration}s`,
|
|
19138
|
+
repeatCount: "indefinite",
|
|
19139
|
+
begin: "0s"
|
|
19140
|
+
})
|
|
19141
|
+
}), jsx("circle", {
|
|
19142
|
+
r: strokeWidth,
|
|
19143
|
+
fill: color,
|
|
19144
|
+
children: jsx("animateMotion", {
|
|
19145
|
+
path: rectPath,
|
|
19146
|
+
dur: `${animationDuration}s`,
|
|
19147
|
+
repeatCount: "indefinite",
|
|
19148
|
+
rotate: "auto",
|
|
19149
|
+
begin: `${circleOffset}s`
|
|
19150
|
+
})
|
|
19511
19151
|
})]
|
|
19512
19152
|
});
|
|
19513
19153
|
};
|
|
19514
|
-
|
|
19515
|
-
|
|
19516
|
-
|
|
19517
|
-
|
|
19518
|
-
|
|
19519
|
-
|
|
19520
|
-
|
|
19521
|
-
|
|
19522
|
-
|
|
19523
|
-
|
|
19524
|
-
|
|
19525
|
-
|
|
19526
|
-
|
|
19527
|
-
|
|
19528
|
-
}
|
|
19529
|
-
|
|
19530
|
-
|
|
19531
|
-
|
|
19532
|
-
|
|
19533
|
-
|
|
19534
|
-
|
|
19535
|
-
|
|
19536
|
-
|
|
19537
|
-
|
|
19538
|
-
|
|
19539
|
-
|
|
19540
|
-
|
|
19541
|
-
|
|
19542
|
-
|
|
19543
|
-
}
|
|
19544
|
-
|
|
19545
|
-
|
|
19546
|
-
|
|
19547
|
-
|
|
19548
|
-
|
|
19549
|
-
|
|
19550
|
-
|
|
19551
|
-
|
|
19552
|
-
|
|
19553
|
-
|
|
19554
|
-
|
|
19555
|
-
|
|
19556
|
-
|
|
19557
|
-
|
|
19558
|
-
|
|
19559
|
-
|
|
19560
|
-
|
|
19561
|
-
|
|
19154
|
+
|
|
19155
|
+
installImportMetaCssBuild(import.meta);import.meta.css = [/* css */`
|
|
19156
|
+
.navi_loading_rectangle_wrapper {
|
|
19157
|
+
position: absolute;
|
|
19158
|
+
top: var(--rectangle-top, 0);
|
|
19159
|
+
right: var(--rectangle-right, 0);
|
|
19160
|
+
bottom: var(--rectangle-bottom, 0);
|
|
19161
|
+
left: var(--rectangle-left, 0);
|
|
19162
|
+
z-index: 1;
|
|
19163
|
+
opacity: 0;
|
|
19164
|
+
pointer-events: none;
|
|
19165
|
+
|
|
19166
|
+
&[data-visible] {
|
|
19167
|
+
opacity: 1;
|
|
19168
|
+
}
|
|
19169
|
+
}
|
|
19170
|
+
`, "@jsenv/navi/src/graphic/loader/loader_background.jsx"];
|
|
19171
|
+
const LoaderBackground = ({
|
|
19172
|
+
loading,
|
|
19173
|
+
containerRef,
|
|
19174
|
+
targetSelector,
|
|
19175
|
+
color,
|
|
19176
|
+
inset = 0,
|
|
19177
|
+
borderRadius = 0,
|
|
19178
|
+
spacingTop = 0,
|
|
19179
|
+
spacingLeft = 0,
|
|
19180
|
+
spacingBottom = 0,
|
|
19181
|
+
spacingRight = 0,
|
|
19182
|
+
children
|
|
19183
|
+
}) => {
|
|
19184
|
+
if (containerRef) {
|
|
19185
|
+
const container = containerRef.current;
|
|
19186
|
+
if (!container) {
|
|
19187
|
+
return children;
|
|
19188
|
+
}
|
|
19189
|
+
return createPortal(jsx(LoaderBackgroundWithPortal, {
|
|
19190
|
+
container: container,
|
|
19191
|
+
loading: loading,
|
|
19192
|
+
color: color,
|
|
19193
|
+
inset: inset,
|
|
19194
|
+
spacingTop: spacingTop,
|
|
19195
|
+
spacingLeft: spacingLeft,
|
|
19196
|
+
spacingBottom: spacingBottom,
|
|
19197
|
+
spacingRight: spacingRight,
|
|
19198
|
+
children: children
|
|
19199
|
+
}), container);
|
|
19200
|
+
}
|
|
19201
|
+
return jsx(LoaderBackgroundBasic, {
|
|
19202
|
+
targetSelector: targetSelector,
|
|
19203
|
+
loading: loading,
|
|
19204
|
+
color: color,
|
|
19205
|
+
inset: inset,
|
|
19206
|
+
borderRadius: borderRadius,
|
|
19207
|
+
spacingTop: spacingTop,
|
|
19208
|
+
spacingLeft: spacingLeft,
|
|
19209
|
+
spacingBottom: spacingBottom,
|
|
19210
|
+
spacingRight: spacingRight,
|
|
19211
|
+
children: children
|
|
19562
19212
|
});
|
|
19563
19213
|
};
|
|
19564
|
-
const
|
|
19565
|
-
|
|
19566
|
-
|
|
19567
|
-
|
|
19568
|
-
|
|
19569
|
-
|
|
19570
|
-
|
|
19571
|
-
|
|
19214
|
+
const LoaderBackgroundWithPortal = ({
|
|
19215
|
+
container,
|
|
19216
|
+
loading,
|
|
19217
|
+
color,
|
|
19218
|
+
inset,
|
|
19219
|
+
borderRadius,
|
|
19220
|
+
spacingTop,
|
|
19221
|
+
spacingLeft,
|
|
19222
|
+
spacingBottom,
|
|
19223
|
+
spacingRight,
|
|
19224
|
+
children
|
|
19225
|
+
}) => {
|
|
19226
|
+
const shouldShowSpinner = useDebounceTrue(loading, 300);
|
|
19227
|
+
if (!shouldShowSpinner) {
|
|
19228
|
+
return children;
|
|
19229
|
+
}
|
|
19230
|
+
container.style.position = "relative";
|
|
19231
|
+
let paddingTop = 0;
|
|
19232
|
+
if (container.nodeName === "DETAILS") {
|
|
19233
|
+
paddingTop = container.querySelector("summary").offsetHeight;
|
|
19234
|
+
}
|
|
19235
|
+
return jsxs(Fragment, {
|
|
19236
|
+
children: [jsx("div", {
|
|
19237
|
+
style: {
|
|
19238
|
+
position: "absolute",
|
|
19239
|
+
top: `${inset + paddingTop + spacingTop}px`,
|
|
19240
|
+
bottom: `${inset + spacingBottom}px`,
|
|
19241
|
+
left: `${inset + spacingLeft}px`,
|
|
19242
|
+
right: `${inset + spacingRight}px`
|
|
19243
|
+
},
|
|
19244
|
+
children: shouldShowSpinner && jsx(RectangleLoading, {
|
|
19245
|
+
color: color,
|
|
19246
|
+
radius: borderRadius
|
|
19247
|
+
})
|
|
19248
|
+
}), children]
|
|
19572
19249
|
});
|
|
19573
19250
|
};
|
|
19574
|
-
|
|
19575
|
-
|
|
19576
|
-
|
|
19577
|
-
|
|
19578
|
-
|
|
19579
|
-
|
|
19580
|
-
|
|
19581
|
-
|
|
19582
|
-
|
|
19251
|
+
const LoaderBackgroundBasic = ({
|
|
19252
|
+
loading,
|
|
19253
|
+
targetSelector,
|
|
19254
|
+
color,
|
|
19255
|
+
borderWidth = 0,
|
|
19256
|
+
borderRadius = 0,
|
|
19257
|
+
spacingTop,
|
|
19258
|
+
spacingLeft,
|
|
19259
|
+
spacingBottom,
|
|
19260
|
+
spacingRight,
|
|
19261
|
+
marginTop = 0,
|
|
19262
|
+
marginLeft = 0,
|
|
19263
|
+
marginBottom = 0,
|
|
19264
|
+
marginRight = 0,
|
|
19265
|
+
paddingTop = 0,
|
|
19266
|
+
paddingLeft = 0,
|
|
19267
|
+
paddingBottom = 0,
|
|
19268
|
+
paddingRight = 0,
|
|
19269
|
+
inset,
|
|
19270
|
+
children
|
|
19271
|
+
}) => {
|
|
19272
|
+
const shouldShowSpinner = useDebounceTrue(loading, 300);
|
|
19273
|
+
const rectangleRef = useRef(null);
|
|
19274
|
+
spacingTop += inset;
|
|
19275
|
+
// spacingTop += outlineOffset;
|
|
19276
|
+
// spacingTop -= borderTopWidth;
|
|
19277
|
+
spacingTop += marginTop;
|
|
19278
|
+
spacingLeft += inset;
|
|
19279
|
+
// spacingLeft += outlineOffset;
|
|
19280
|
+
// spacingLeft -= borderLeftWidth;
|
|
19281
|
+
spacingLeft += marginLeft;
|
|
19282
|
+
spacingRight += inset;
|
|
19283
|
+
// spacingRight += outlineOffset;
|
|
19284
|
+
// spacingRight -= borderRightWidth;
|
|
19285
|
+
spacingRight += marginRight;
|
|
19286
|
+
spacingBottom += inset;
|
|
19287
|
+
// spacingBottom += outlineOffset;
|
|
19288
|
+
// spacingBottom -= borderBottomWidth;
|
|
19289
|
+
spacingBottom += marginBottom;
|
|
19290
|
+
if (targetSelector) {
|
|
19291
|
+
// oversimplification that actually works
|
|
19292
|
+
// (simplified because it assumes the targeted element is a direct child of the contained element which may have padding)
|
|
19293
|
+
spacingTop += paddingTop;
|
|
19294
|
+
spacingLeft += paddingLeft;
|
|
19295
|
+
spacingRight += paddingRight;
|
|
19296
|
+
spacingBottom += paddingBottom;
|
|
19297
|
+
}
|
|
19298
|
+
const maxBorderWidth = Math.max(borderWidth);
|
|
19299
|
+
const halfMaxBorderSize = maxBorderWidth / 2;
|
|
19300
|
+
const size = halfMaxBorderSize < 2 ? 2 : halfMaxBorderSize;
|
|
19301
|
+
const lineHalfSize = size / 2;
|
|
19302
|
+
spacingTop -= lineHalfSize;
|
|
19303
|
+
spacingLeft -= lineHalfSize;
|
|
19304
|
+
spacingRight -= lineHalfSize;
|
|
19305
|
+
spacingBottom -= lineHalfSize;
|
|
19306
|
+
return jsxs(Fragment, {
|
|
19307
|
+
children: [jsx("span", {
|
|
19308
|
+
ref: rectangleRef,
|
|
19309
|
+
className: "navi_loading_rectangle_wrapper",
|
|
19310
|
+
"data-visible": shouldShowSpinner ? "" : undefined,
|
|
19311
|
+
style: {
|
|
19312
|
+
"--rectangle-top": `${spacingTop}px`,
|
|
19313
|
+
"--rectangle-left": `${spacingLeft}px`,
|
|
19314
|
+
"--rectangle-bottom": `${spacingBottom}px`,
|
|
19315
|
+
"--rectangle-right": `${spacingRight}px`
|
|
19316
|
+
},
|
|
19317
|
+
children: loading && jsx(RectangleLoading, {
|
|
19318
|
+
shouldShowSpinner: shouldShowSpinner,
|
|
19319
|
+
color: color,
|
|
19320
|
+
radius: borderRadius,
|
|
19321
|
+
size: size
|
|
19322
|
+
})
|
|
19323
|
+
}), children]
|
|
19583
19324
|
});
|
|
19584
19325
|
};
|
|
19585
19326
|
|
|
19586
|
-
|
|
19587
|
-
|
|
19588
|
-
|
|
19327
|
+
// used by form elements such as <input>, <select>, <textarea> to have their own action bound to a single parameter
|
|
19328
|
+
// when inside a <form> the form params are updated when the form element single param is updated
|
|
19329
|
+
const useActionBoundToOneParam = (action, externalValue) => {
|
|
19330
|
+
const actionFirstArgSignal = useSignal(externalValue);
|
|
19331
|
+
const boundAction = useBoundAction(action, actionFirstArgSignal);
|
|
19332
|
+
const getValue = useCallback(() => actionFirstArgSignal.value, []);
|
|
19333
|
+
const setValue = useCallback((value) => {
|
|
19334
|
+
actionFirstArgSignal.value = value;
|
|
19335
|
+
}, []);
|
|
19336
|
+
const externalValueRef = useRef(externalValue);
|
|
19337
|
+
if (externalValue !== externalValueRef.current) {
|
|
19338
|
+
externalValueRef.current = externalValue;
|
|
19339
|
+
setValue(externalValue);
|
|
19340
|
+
}
|
|
19589
19341
|
|
|
19590
|
-
|
|
19591
|
-
|
|
19592
|
-
|
|
19593
|
-
|
|
19594
|
-
|
|
19595
|
-
|
|
19596
|
-
|
|
19597
|
-
|
|
19598
|
-
|
|
19599
|
-
|
|
19600
|
-
|
|
19601
|
-
|
|
19602
|
-
|
|
19603
|
-
}
|
|
19342
|
+
const value = getValue();
|
|
19343
|
+
return [boundAction, value, setValue];
|
|
19344
|
+
};
|
|
19345
|
+
const useActionBoundToOneArrayParam = (
|
|
19346
|
+
action,
|
|
19347
|
+
name,
|
|
19348
|
+
externalValue,
|
|
19349
|
+
fallbackValue,
|
|
19350
|
+
defaultValue,
|
|
19351
|
+
) => {
|
|
19352
|
+
const [boundAction, value, setValue] = useActionBoundToOneParam(
|
|
19353
|
+
action,
|
|
19354
|
+
name);
|
|
19604
19355
|
|
|
19605
|
-
|
|
19606
|
-
|
|
19607
|
-
|
|
19608
|
-
clearTimeout(timerRef.current);
|
|
19609
|
-
}
|
|
19610
|
-
};
|
|
19611
|
-
}, [value, delay]);
|
|
19356
|
+
const add = (valueToAdd, valueArray = value) => {
|
|
19357
|
+
setValue(addIntoArray(valueArray, valueToAdd));
|
|
19358
|
+
};
|
|
19612
19359
|
|
|
19613
|
-
|
|
19614
|
-
|
|
19360
|
+
const remove = (valueToRemove, valueArray = value) => {
|
|
19361
|
+
setValue(removeFromArray(valueArray, valueToRemove));
|
|
19362
|
+
};
|
|
19615
19363
|
|
|
19616
|
-
const
|
|
19617
|
-
|
|
19364
|
+
const result = [boundAction, value, setValue];
|
|
19365
|
+
result.add = add;
|
|
19366
|
+
result.remove = remove;
|
|
19367
|
+
return result;
|
|
19368
|
+
};
|
|
19369
|
+
// used by <details> to just call their action
|
|
19370
|
+
const useAction = (action, paramsSignal) => {
|
|
19371
|
+
return useBoundAction(action, paramsSignal);
|
|
19618
19372
|
};
|
|
19619
19373
|
|
|
19620
|
-
const
|
|
19621
|
-
|
|
19622
|
-
|
|
19623
|
-
|
|
19624
|
-
|
|
19625
|
-
|
|
19626
|
-
// ✅ Network Information API (support moderne)
|
|
19627
|
-
if (!connection) {
|
|
19628
|
-
return "3g";
|
|
19374
|
+
const useBoundAction = (action, actionParamsSignal) => {
|
|
19375
|
+
const actionRef = useRef();
|
|
19376
|
+
const actionCallbackRef = useRef();
|
|
19377
|
+
|
|
19378
|
+
if (!action) {
|
|
19379
|
+
return null;
|
|
19629
19380
|
}
|
|
19630
|
-
if (
|
|
19631
|
-
|
|
19632
|
-
|
|
19633
|
-
|
|
19381
|
+
if (isFunctionButNotAnActionFunction(action)) {
|
|
19382
|
+
actionCallbackRef.current = action;
|
|
19383
|
+
const existingAction = actionRef.current;
|
|
19384
|
+
if (existingAction) {
|
|
19385
|
+
return existingAction;
|
|
19634
19386
|
}
|
|
19635
|
-
const
|
|
19636
|
-
|
|
19637
|
-
|
|
19638
|
-
|
|
19639
|
-
|
|
19640
|
-
|
|
19641
|
-
|
|
19387
|
+
const actionFromFunction = createAction(
|
|
19388
|
+
(...args) => {
|
|
19389
|
+
return actionCallbackRef.current?.(...args);
|
|
19390
|
+
},
|
|
19391
|
+
{
|
|
19392
|
+
name: action.name,
|
|
19393
|
+
// We don't want to give empty params by default
|
|
19394
|
+
// we want to give undefined for regular functions
|
|
19395
|
+
params: undefined,
|
|
19396
|
+
},
|
|
19397
|
+
);
|
|
19398
|
+
if (!actionParamsSignal) {
|
|
19399
|
+
actionRef.current = actionFromFunction;
|
|
19400
|
+
return actionFromFunction;
|
|
19642
19401
|
}
|
|
19402
|
+
const actionBoundToParams =
|
|
19403
|
+
actionFromFunction.bindParams(actionParamsSignal);
|
|
19404
|
+
actionRef.current = actionBoundToParams;
|
|
19405
|
+
return actionBoundToParams;
|
|
19643
19406
|
}
|
|
19644
|
-
|
|
19407
|
+
if (actionParamsSignal) {
|
|
19408
|
+
return action.bindParams(actionParamsSignal);
|
|
19409
|
+
}
|
|
19410
|
+
return action;
|
|
19645
19411
|
};
|
|
19646
19412
|
|
|
19647
|
-
const
|
|
19648
|
-
|
|
19413
|
+
const isFunctionButNotAnActionFunction = (action) => {
|
|
19414
|
+
return typeof action === "function" && !action.isAction;
|
|
19649
19415
|
};
|
|
19650
19416
|
|
|
19651
|
-
const
|
|
19417
|
+
const ErrorBoundaryContext = createContext(null);
|
|
19652
19418
|
|
|
19653
|
-
const
|
|
19654
|
-
const
|
|
19419
|
+
const useResetErrorBoundary = () => {
|
|
19420
|
+
const resetErrorBoundary = useContext(ErrorBoundaryContext);
|
|
19421
|
+
return resetErrorBoundary;
|
|
19422
|
+
};
|
|
19655
19423
|
|
|
19656
|
-
|
|
19424
|
+
const addCustomMessage = (element, key, message, options) => {
|
|
19425
|
+
const customConstraintValidation =
|
|
19426
|
+
element.__validationInterface__ ||
|
|
19427
|
+
(element.__validationInterface__ =
|
|
19428
|
+
installCustomConstraintValidation(element));
|
|
19657
19429
|
|
|
19658
|
-
|
|
19659
|
-
|
|
19660
|
-
cleanupFunctions.push(() => {
|
|
19661
|
-
connection.removeEventListener("change", updateNetworkSpeed);
|
|
19662
|
-
});
|
|
19663
|
-
}
|
|
19430
|
+
return customConstraintValidation.addCustomMessage(key, message, options);
|
|
19431
|
+
};
|
|
19664
19432
|
|
|
19665
|
-
|
|
19666
|
-
const
|
|
19667
|
-
|
|
19433
|
+
const removeCustomMessage = (element, key) => {
|
|
19434
|
+
const customConstraintValidation = element.__validationInterface__;
|
|
19435
|
+
if (!customConstraintValidation) {
|
|
19436
|
+
return;
|
|
19437
|
+
}
|
|
19438
|
+
customConstraintValidation.removeCustomMessage(key);
|
|
19439
|
+
};
|
|
19668
19440
|
|
|
19669
|
-
|
|
19670
|
-
|
|
19671
|
-
|
|
19672
|
-
|
|
19441
|
+
const useExecuteAction = (
|
|
19442
|
+
elementRef,
|
|
19443
|
+
{
|
|
19444
|
+
errorEffect = "show_validation_message", // "show_validation_message" or "throw"
|
|
19445
|
+
errorMapping,
|
|
19446
|
+
} = {},
|
|
19447
|
+
) => {
|
|
19448
|
+
// see https://medium.com/trabe/catching-asynchronous-errors-in-react-using-error-boundaries-5e8a5fd7b971
|
|
19449
|
+
// and https://codepen.io/dmail/pen/XJJqeGp?editors=0010
|
|
19450
|
+
// To change if https://github.com/preactjs/preact/issues/4754 lands
|
|
19451
|
+
const [error, setError] = useState(null);
|
|
19452
|
+
const resetErrorBoundary = useResetErrorBoundary();
|
|
19453
|
+
useLayoutEffect(() => {
|
|
19454
|
+
if (error) {
|
|
19455
|
+
throw error;
|
|
19673
19456
|
}
|
|
19674
|
-
};
|
|
19675
|
-
|
|
19676
|
-
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
19677
|
-
cleanupFunctions.push(() => {
|
|
19678
|
-
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
19679
|
-
});
|
|
19457
|
+
}, [error]);
|
|
19680
19458
|
|
|
19681
|
-
|
|
19682
|
-
const
|
|
19683
|
-
|
|
19459
|
+
const validationMessageTargetRef = useRef(null);
|
|
19460
|
+
const addErrorMessage = (error) => {
|
|
19461
|
+
let calloutAnchor = validationMessageTargetRef.current;
|
|
19462
|
+
let message;
|
|
19463
|
+
if (errorMapping) {
|
|
19464
|
+
const errorMappingResult = errorMapping(error);
|
|
19465
|
+
if (typeof errorMappingResult === "string") {
|
|
19466
|
+
message = errorMappingResult;
|
|
19467
|
+
} else if (Error.isError(errorMappingResult)) {
|
|
19468
|
+
message = errorMappingResult;
|
|
19469
|
+
} else if (isValidElement(errorMappingResult)) {
|
|
19470
|
+
message = errorMappingResult;
|
|
19471
|
+
} else if (
|
|
19472
|
+
typeof errorMappingResult === "object" &&
|
|
19473
|
+
errorMappingResult !== null
|
|
19474
|
+
) {
|
|
19475
|
+
message = errorMappingResult.message || error.message;
|
|
19476
|
+
calloutAnchor = errorMappingResult.target || calloutAnchor;
|
|
19477
|
+
}
|
|
19478
|
+
} else {
|
|
19479
|
+
message = error;
|
|
19480
|
+
}
|
|
19481
|
+
addCustomMessage(calloutAnchor, "action_error", message, {
|
|
19482
|
+
status: "error",
|
|
19483
|
+
// This error should not prevent <form> submission
|
|
19484
|
+
// so whenever user tries to submit the form the error is cleared
|
|
19485
|
+
// (Hitting enter key, clicking on submit button, etc. would allow to re-submit the form in error state)
|
|
19486
|
+
removeOnRequestAction: true,
|
|
19487
|
+
});
|
|
19684
19488
|
};
|
|
19685
|
-
|
|
19686
|
-
|
|
19687
|
-
|
|
19688
|
-
|
|
19689
|
-
|
|
19690
|
-
|
|
19691
|
-
// Cleanup global
|
|
19692
|
-
return () => {
|
|
19693
|
-
cleanupFunctions.forEach((cleanup) => cleanup());
|
|
19489
|
+
const removeErrorMessage = () => {
|
|
19490
|
+
const validationMessageTarget = validationMessageTargetRef.current;
|
|
19491
|
+
if (validationMessageTarget) {
|
|
19492
|
+
removeCustomMessage(validationMessageTarget, "action_error");
|
|
19493
|
+
}
|
|
19694
19494
|
};
|
|
19695
|
-
};
|
|
19696
|
-
setupNetworkMonitoring();
|
|
19697
19495
|
|
|
19698
|
-
installImportMetaCssBuild(import.meta);/**
|
|
19699
|
-
* RectangleLoading Component
|
|
19700
|
-
*
|
|
19701
|
-
* A responsive loading indicator that dynamically adjusts to fit its container.
|
|
19702
|
-
* Displays an animated outline with a traveling dot that follows the container's shape.
|
|
19703
|
-
*
|
|
19704
|
-
* Features:
|
|
19705
|
-
* - Adapts to any container dimensions using ResizeObserver
|
|
19706
|
-
* - Scales stroke width, margins and corner radius proportionally
|
|
19707
|
-
* - Animates using native SVG animations for smooth performance
|
|
19708
|
-
* - High-quality SVG rendering with proper path calculations
|
|
19709
|
-
*
|
|
19710
|
-
* @param {Object} props - Component props
|
|
19711
|
-
* @param {string} [props.color="#383a36"] - Color of the loading indicator
|
|
19712
|
-
* @param {number} [props.radius=0] - Corner radius of the rectangle (px)
|
|
19713
|
-
*/
|
|
19714
|
-
import.meta.css = [/* css */`
|
|
19715
|
-
.navi_rectangle_loading {
|
|
19716
|
-
position: relative;
|
|
19717
|
-
display: flex;
|
|
19718
|
-
width: 100%;
|
|
19719
|
-
height: 100%;
|
|
19720
|
-
opacity: 0;
|
|
19721
|
-
}
|
|
19722
|
-
|
|
19723
|
-
.navi_rectangle_loading[data-visible] {
|
|
19724
|
-
opacity: 1;
|
|
19725
|
-
}
|
|
19726
|
-
`, "@jsenv/navi/src/graphic/loader/rectangle_loading.jsx"];
|
|
19727
|
-
const RectangleLoading = ({
|
|
19728
|
-
shouldShowSpinner,
|
|
19729
|
-
color = "currentColor",
|
|
19730
|
-
radius = 0,
|
|
19731
|
-
size = 2
|
|
19732
|
-
}) => {
|
|
19733
|
-
const containerRef = useRef(null);
|
|
19734
|
-
const [containerWidth, setContainerWidth] = useState(0);
|
|
19735
|
-
const [containerHeight, setContainerHeight] = useState(0);
|
|
19736
19496
|
useLayoutEffect(() => {
|
|
19737
|
-
const
|
|
19738
|
-
if (!
|
|
19497
|
+
const element = elementRef.current;
|
|
19498
|
+
if (!element) {
|
|
19739
19499
|
return null;
|
|
19740
19500
|
}
|
|
19741
|
-
const
|
|
19742
|
-
|
|
19743
|
-
|
|
19744
|
-
}
|
|
19745
|
-
|
|
19746
|
-
|
|
19747
|
-
|
|
19748
|
-
|
|
19749
|
-
const resizeObserver = new ResizeObserver(entries => {
|
|
19750
|
-
// Use requestAnimationFrame to debounce updates
|
|
19751
|
-
if (animationFrameId) {
|
|
19752
|
-
cancelAnimationFrame(animationFrameId);
|
|
19753
|
-
}
|
|
19754
|
-
animationFrameId = requestAnimationFrame(() => {
|
|
19755
|
-
const [containerEntry] = entries;
|
|
19756
|
-
const {
|
|
19757
|
-
width,
|
|
19758
|
-
height
|
|
19759
|
-
} = containerEntry.contentRect;
|
|
19760
|
-
setContainerWidth(width);
|
|
19761
|
-
setContainerHeight(height);
|
|
19762
|
-
});
|
|
19763
|
-
});
|
|
19764
|
-
resizeObserver.observe(container);
|
|
19501
|
+
const form = element.tagName === "FORM" ? element : element.form;
|
|
19502
|
+
if (!form) {
|
|
19503
|
+
return null;
|
|
19504
|
+
}
|
|
19505
|
+
const onReset = () => {
|
|
19506
|
+
removeErrorMessage();
|
|
19507
|
+
};
|
|
19508
|
+
form.addEventListener("reset", onReset);
|
|
19765
19509
|
return () => {
|
|
19766
|
-
|
|
19767
|
-
|
|
19510
|
+
form.removeEventListener("reset", onReset);
|
|
19511
|
+
};
|
|
19512
|
+
});
|
|
19513
|
+
|
|
19514
|
+
// const errorEffectRef = useRef();
|
|
19515
|
+
// errorEffectRef.current = errorEffect;
|
|
19516
|
+
const executeAction = useCallback(
|
|
19517
|
+
(actionEvent) => {
|
|
19518
|
+
const { action, actionOrigin, requester, event, method } =
|
|
19519
|
+
actionEvent.detail;
|
|
19520
|
+
const sharedActionEventDetail = {
|
|
19521
|
+
action,
|
|
19522
|
+
actionOrigin,
|
|
19523
|
+
requester,
|
|
19524
|
+
event,
|
|
19525
|
+
method,
|
|
19526
|
+
};
|
|
19527
|
+
|
|
19528
|
+
const dispatchCustomEvent = (type, options) => {
|
|
19529
|
+
const element = elementRef.current;
|
|
19530
|
+
const customEvent = new CustomEvent(type, options);
|
|
19531
|
+
element.dispatchEvent(customEvent);
|
|
19532
|
+
};
|
|
19533
|
+
if (resetErrorBoundary) {
|
|
19534
|
+
resetErrorBoundary();
|
|
19768
19535
|
}
|
|
19769
|
-
|
|
19770
|
-
|
|
19771
|
-
}, []);
|
|
19772
|
-
return jsx("span", {
|
|
19773
|
-
ref: containerRef,
|
|
19774
|
-
className: "navi_rectangle_loading",
|
|
19775
|
-
"data-visible": shouldShowSpinner ? "" : undefined,
|
|
19776
|
-
children: containerWidth > 0 && containerHeight > 0 && jsx(RectangleLoadingSvg, {
|
|
19777
|
-
radius: radius,
|
|
19778
|
-
color: color,
|
|
19779
|
-
width: containerWidth,
|
|
19780
|
-
height: containerHeight,
|
|
19781
|
-
strokeWidth: size
|
|
19782
|
-
})
|
|
19783
|
-
});
|
|
19784
|
-
};
|
|
19785
|
-
const RectangleLoadingSvg = ({
|
|
19786
|
-
width,
|
|
19787
|
-
height,
|
|
19788
|
-
color,
|
|
19789
|
-
radius,
|
|
19790
|
-
trailColor = "transparent",
|
|
19791
|
-
strokeWidth
|
|
19792
|
-
}) => {
|
|
19793
|
-
const margin = Math.max(2, Math.min(width, height) * 0.03);
|
|
19536
|
+
removeErrorMessage();
|
|
19537
|
+
setError(null);
|
|
19794
19538
|
|
|
19795
|
-
|
|
19796
|
-
|
|
19797
|
-
const drawableHeight = height - margin * 2;
|
|
19539
|
+
const validationMessageTarget = requester || elementRef.current;
|
|
19540
|
+
validationMessageTargetRef.current = validationMessageTarget;
|
|
19798
19541
|
|
|
19799
|
-
|
|
19800
|
-
|
|
19801
|
-
|
|
19802
|
-
availableSize: Math.min(width, height)
|
|
19803
|
-
});
|
|
19804
|
-
const actualRadius = Math.min(radius || Math.min(drawableWidth, drawableHeight) * 0.05, maxPossibleRadius // ✅ Limité au radius maximum possible
|
|
19805
|
-
);
|
|
19806
|
-
const aspectRatio = Math.max(drawableWidth, drawableHeight) / Math.min(drawableWidth, drawableHeight);
|
|
19807
|
-
const isNearlySquare = aspectRatio <= 1.2; // Allow some tolerance for nearly square shapes
|
|
19808
|
-
const isCircle = isNearlySquare && actualRadius >= maxPossibleRadius * 0.95;
|
|
19809
|
-
let pathLength;
|
|
19810
|
-
let rectPath;
|
|
19811
|
-
if (isCircle) {
|
|
19812
|
-
// ✅ Circle: perimeter = 2πr
|
|
19813
|
-
pathLength = 2 * Math.PI * actualRadius;
|
|
19542
|
+
dispatchCustomEvent("actionstart", {
|
|
19543
|
+
detail: sharedActionEventDetail,
|
|
19544
|
+
});
|
|
19814
19545
|
|
|
19815
|
-
|
|
19816
|
-
|
|
19817
|
-
|
|
19818
|
-
|
|
19819
|
-
M ${centerX + actualRadius},${centerY}
|
|
19820
|
-
A ${actualRadius},${actualRadius} 0 1 1 ${centerX - actualRadius},${centerY}
|
|
19821
|
-
A ${actualRadius},${actualRadius} 0 1 1 ${centerX + actualRadius},${centerY}
|
|
19822
|
-
`;
|
|
19823
|
-
} else {
|
|
19824
|
-
// ✅ Rectangle: calculate perimeter properly
|
|
19825
|
-
const straightEdges = 2 * (drawableWidth - 2 * actualRadius) + 2 * (drawableHeight - 2 * actualRadius);
|
|
19826
|
-
const cornerArcs = actualRadius > 0 ? 2 * Math.PI * actualRadius : 0;
|
|
19827
|
-
pathLength = straightEdges + cornerArcs;
|
|
19828
|
-
rectPath = `
|
|
19829
|
-
M ${margin + actualRadius},${margin}
|
|
19830
|
-
L ${margin + drawableWidth - actualRadius},${margin}
|
|
19831
|
-
A ${actualRadius},${actualRadius} 0 0 1 ${margin + drawableWidth},${margin + actualRadius}
|
|
19832
|
-
L ${margin + drawableWidth},${margin + drawableHeight - actualRadius}
|
|
19833
|
-
A ${actualRadius},${actualRadius} 0 0 1 ${margin + drawableWidth - actualRadius},${margin + drawableHeight}
|
|
19834
|
-
L ${margin + actualRadius},${margin + drawableHeight}
|
|
19835
|
-
A ${actualRadius},${actualRadius} 0 0 1 ${margin},${margin + drawableHeight - actualRadius}
|
|
19836
|
-
L ${margin},${margin + actualRadius}
|
|
19837
|
-
A ${actualRadius},${actualRadius} 0 0 1 ${margin + actualRadius},${margin}
|
|
19838
|
-
`;
|
|
19839
|
-
}
|
|
19546
|
+
return action[method]({
|
|
19547
|
+
reason: `"${event.type}" event on ${(() => {
|
|
19548
|
+
const target = event.target;
|
|
19549
|
+
const tagName = target.tagName.toLowerCase();
|
|
19840
19550
|
|
|
19841
|
-
|
|
19842
|
-
|
|
19843
|
-
|
|
19844
|
-
const gapLength = pathLength - segmentLength;
|
|
19551
|
+
if (target.id) {
|
|
19552
|
+
return `${tagName}#${target.id}`;
|
|
19553
|
+
}
|
|
19845
19554
|
|
|
19846
|
-
|
|
19847
|
-
|
|
19848
|
-
|
|
19849
|
-
|
|
19850
|
-
"2g": 60,
|
|
19851
|
-
"3g": 80,
|
|
19852
|
-
"4g": 120
|
|
19853
|
-
}[networkSpeed] || 80;
|
|
19854
|
-
const animationDuration = Math.max(1.5, pathLength / pixelsPerSecond);
|
|
19555
|
+
const uiName = target.getAttribute("data-ui-name");
|
|
19556
|
+
if (uiName) {
|
|
19557
|
+
return `${tagName}[data-ui-name="${uiName}"]`;
|
|
19558
|
+
}
|
|
19855
19559
|
|
|
19856
|
-
|
|
19857
|
-
|
|
19858
|
-
|
|
19859
|
-
|
|
19860
|
-
|
|
19861
|
-
|
|
19862
|
-
|
|
19863
|
-
|
|
19864
|
-
|
|
19865
|
-
|
|
19866
|
-
|
|
19867
|
-
|
|
19868
|
-
|
|
19869
|
-
|
|
19870
|
-
|
|
19871
|
-
|
|
19872
|
-
|
|
19873
|
-
|
|
19874
|
-
|
|
19875
|
-
|
|
19876
|
-
|
|
19877
|
-
|
|
19878
|
-
|
|
19879
|
-
|
|
19880
|
-
|
|
19881
|
-
|
|
19882
|
-
|
|
19883
|
-
|
|
19884
|
-
|
|
19885
|
-
|
|
19886
|
-
|
|
19887
|
-
|
|
19888
|
-
|
|
19889
|
-
|
|
19890
|
-
|
|
19891
|
-
|
|
19892
|
-
|
|
19893
|
-
|
|
19894
|
-
|
|
19895
|
-
|
|
19896
|
-
|
|
19897
|
-
|
|
19898
|
-
|
|
19899
|
-
|
|
19900
|
-
|
|
19901
|
-
|
|
19902
|
-
|
|
19903
|
-
|
|
19904
|
-
|
|
19905
|
-
|
|
19906
|
-
|
|
19907
|
-
|
|
19908
|
-
})
|
|
19909
|
-
}
|
|
19910
|
-
|
|
19560
|
+
return `<${tagName}>`;
|
|
19561
|
+
})()}`,
|
|
19562
|
+
onAbort: (reason) => {
|
|
19563
|
+
if (
|
|
19564
|
+
// at this stage the action side effect might have removed the <element> from the DOM
|
|
19565
|
+
// (in theory no because action side effect are batched to happen after)
|
|
19566
|
+
// but other side effects might do this
|
|
19567
|
+
elementRef.current
|
|
19568
|
+
) {
|
|
19569
|
+
dispatchCustomEvent("actionabort", {
|
|
19570
|
+
detail: {
|
|
19571
|
+
...sharedActionEventDetail,
|
|
19572
|
+
reason,
|
|
19573
|
+
},
|
|
19574
|
+
});
|
|
19575
|
+
}
|
|
19576
|
+
},
|
|
19577
|
+
onError: (error) => {
|
|
19578
|
+
if (
|
|
19579
|
+
// at this stage the action side effect might have removed the <element> from the DOM
|
|
19580
|
+
// (in theory no because action side effect are batched to happen after)
|
|
19581
|
+
// but other side effects might do this
|
|
19582
|
+
elementRef.current
|
|
19583
|
+
) {
|
|
19584
|
+
dispatchCustomEvent("actionerror", {
|
|
19585
|
+
detail: {
|
|
19586
|
+
...sharedActionEventDetail,
|
|
19587
|
+
error,
|
|
19588
|
+
},
|
|
19589
|
+
});
|
|
19590
|
+
}
|
|
19591
|
+
if (errorEffect === "show_validation_message") {
|
|
19592
|
+
addErrorMessage(error);
|
|
19593
|
+
} else if (errorEffect === "throw") {
|
|
19594
|
+
setError(error);
|
|
19595
|
+
}
|
|
19596
|
+
},
|
|
19597
|
+
onComplete: (data) => {
|
|
19598
|
+
if (
|
|
19599
|
+
// at this stage the action side effect might have removed the <element> from the DOM
|
|
19600
|
+
// (in theory no because action side effect are batched to happen after)
|
|
19601
|
+
// but other side effects might do this
|
|
19602
|
+
elementRef.current
|
|
19603
|
+
) {
|
|
19604
|
+
dispatchCustomEvent("actionend", {
|
|
19605
|
+
detail: {
|
|
19606
|
+
...sharedActionEventDetail,
|
|
19607
|
+
data,
|
|
19608
|
+
},
|
|
19609
|
+
});
|
|
19610
|
+
}
|
|
19611
|
+
},
|
|
19612
|
+
});
|
|
19613
|
+
},
|
|
19614
|
+
[errorEffect],
|
|
19615
|
+
);
|
|
19616
|
+
|
|
19617
|
+
return executeAction;
|
|
19911
19618
|
};
|
|
19912
19619
|
|
|
19913
|
-
|
|
19914
|
-
|
|
19915
|
-
|
|
19916
|
-
|
|
19917
|
-
|
|
19918
|
-
|
|
19919
|
-
|
|
19920
|
-
|
|
19921
|
-
|
|
19922
|
-
|
|
19620
|
+
const detectMac = () => {
|
|
19621
|
+
// Modern way using User-Agent Client Hints API
|
|
19622
|
+
if (window.navigator.userAgentData) {
|
|
19623
|
+
return window.navigator.userAgentData.platform === "macOS";
|
|
19624
|
+
}
|
|
19625
|
+
// Fallback to userAgent string parsing
|
|
19626
|
+
return /Mac|iPhone|iPad|iPod/.test(window.navigator.userAgent);
|
|
19627
|
+
};
|
|
19628
|
+
const isMac = detectMac();
|
|
19629
|
+
|
|
19630
|
+
// Maps canonical browser key names to their user-friendly aliases.
|
|
19631
|
+
// Used for both event matching and ARIA normalization.
|
|
19632
|
+
const keyMapping = {
|
|
19633
|
+
" ": { alias: ["space"] },
|
|
19634
|
+
"escape": { alias: ["esc"] },
|
|
19635
|
+
"arrowup": { alias: ["up"] },
|
|
19636
|
+
"arrowdown": { alias: ["down"] },
|
|
19637
|
+
"arrowleft": { alias: ["left"] },
|
|
19638
|
+
"arrowright": { alias: ["right"] },
|
|
19639
|
+
"delete": { alias: ["del"] },
|
|
19640
|
+
// Platform-specific mappings
|
|
19641
|
+
...(isMac
|
|
19642
|
+
? { delete: { alias: ["backspace"] } }
|
|
19643
|
+
: { backspace: { alias: ["delete"] } }),
|
|
19644
|
+
};
|
|
19645
|
+
|
|
19646
|
+
const activeShortcutsSignal = signal([]);
|
|
19647
|
+
const shortcutsMap = new Map();
|
|
19648
|
+
|
|
19649
|
+
const areShortcutsEqual = (shortcutA, shortcutB) => {
|
|
19650
|
+
return (
|
|
19651
|
+
shortcutA.key === shortcutB.key &&
|
|
19652
|
+
shortcutA.description === shortcutB.description &&
|
|
19653
|
+
shortcutA.enabled === shortcutB.enabled
|
|
19654
|
+
);
|
|
19655
|
+
};
|
|
19923
19656
|
|
|
19924
|
-
|
|
19925
|
-
|
|
19926
|
-
|
|
19657
|
+
const areShortcutArraysEqual = (arrayA, arrayB) => {
|
|
19658
|
+
if (arrayA.length !== arrayB.length) {
|
|
19659
|
+
return false;
|
|
19927
19660
|
}
|
|
19928
|
-
|
|
19929
|
-
|
|
19930
|
-
|
|
19931
|
-
|
|
19932
|
-
targetSelector,
|
|
19933
|
-
color,
|
|
19934
|
-
inset = 0,
|
|
19935
|
-
borderRadius = 0,
|
|
19936
|
-
spacingTop = 0,
|
|
19937
|
-
spacingLeft = 0,
|
|
19938
|
-
spacingBottom = 0,
|
|
19939
|
-
spacingRight = 0,
|
|
19940
|
-
children
|
|
19941
|
-
}) => {
|
|
19942
|
-
if (containerRef) {
|
|
19943
|
-
const container = containerRef.current;
|
|
19944
|
-
if (!container) {
|
|
19945
|
-
return children;
|
|
19661
|
+
|
|
19662
|
+
for (let i = 0; i < arrayA.length; i++) {
|
|
19663
|
+
if (!areShortcutsEqual(arrayA[i], arrayB[i])) {
|
|
19664
|
+
return false;
|
|
19946
19665
|
}
|
|
19947
|
-
return createPortal(jsx(LoaderBackgroundWithPortal, {
|
|
19948
|
-
container: container,
|
|
19949
|
-
loading: loading,
|
|
19950
|
-
color: color,
|
|
19951
|
-
inset: inset,
|
|
19952
|
-
spacingTop: spacingTop,
|
|
19953
|
-
spacingLeft: spacingLeft,
|
|
19954
|
-
spacingBottom: spacingBottom,
|
|
19955
|
-
spacingRight: spacingRight,
|
|
19956
|
-
children: children
|
|
19957
|
-
}), container);
|
|
19958
19666
|
}
|
|
19959
|
-
|
|
19960
|
-
|
|
19961
|
-
loading: loading,
|
|
19962
|
-
color: color,
|
|
19963
|
-
inset: inset,
|
|
19964
|
-
borderRadius: borderRadius,
|
|
19965
|
-
spacingTop: spacingTop,
|
|
19966
|
-
spacingLeft: spacingLeft,
|
|
19967
|
-
spacingBottom: spacingBottom,
|
|
19968
|
-
spacingRight: spacingRight,
|
|
19969
|
-
children: children
|
|
19970
|
-
});
|
|
19667
|
+
|
|
19668
|
+
return true;
|
|
19971
19669
|
};
|
|
19972
|
-
|
|
19973
|
-
|
|
19974
|
-
|
|
19975
|
-
|
|
19976
|
-
|
|
19977
|
-
|
|
19978
|
-
|
|
19979
|
-
|
|
19980
|
-
|
|
19981
|
-
spacingRight,
|
|
19982
|
-
children
|
|
19983
|
-
}) => {
|
|
19984
|
-
const shouldShowSpinner = useDebounceTrue(loading, 300);
|
|
19985
|
-
if (!shouldShowSpinner) {
|
|
19986
|
-
return children;
|
|
19670
|
+
|
|
19671
|
+
const updateActiveShortcuts = () => {
|
|
19672
|
+
const activeElement = activeElementSignal.peek();
|
|
19673
|
+
const currentActiveShortcuts = activeShortcutsSignal.peek();
|
|
19674
|
+
const activeShortcuts = [];
|
|
19675
|
+
for (const [element, { shortcuts }] of shortcutsMap) {
|
|
19676
|
+
if (element === activeElement || element.contains(activeElement)) {
|
|
19677
|
+
activeShortcuts.push(...shortcuts);
|
|
19678
|
+
}
|
|
19987
19679
|
}
|
|
19988
|
-
|
|
19989
|
-
|
|
19990
|
-
if (
|
|
19991
|
-
|
|
19680
|
+
|
|
19681
|
+
// Only update if shortcuts have actually changed
|
|
19682
|
+
if (!areShortcutArraysEqual(currentActiveShortcuts, activeShortcuts)) {
|
|
19683
|
+
activeShortcutsSignal.value = activeShortcuts;
|
|
19992
19684
|
}
|
|
19993
|
-
return jsxs(Fragment, {
|
|
19994
|
-
children: [jsx("div", {
|
|
19995
|
-
style: {
|
|
19996
|
-
position: "absolute",
|
|
19997
|
-
top: `${inset + paddingTop + spacingTop}px`,
|
|
19998
|
-
bottom: `${inset + spacingBottom}px`,
|
|
19999
|
-
left: `${inset + spacingLeft}px`,
|
|
20000
|
-
right: `${inset + spacingRight}px`
|
|
20001
|
-
},
|
|
20002
|
-
children: shouldShowSpinner && jsx(RectangleLoading, {
|
|
20003
|
-
color: color,
|
|
20004
|
-
radius: borderRadius
|
|
20005
|
-
})
|
|
20006
|
-
}), children]
|
|
20007
|
-
});
|
|
20008
19685
|
};
|
|
20009
|
-
|
|
20010
|
-
|
|
20011
|
-
|
|
20012
|
-
|
|
20013
|
-
|
|
20014
|
-
|
|
20015
|
-
|
|
20016
|
-
|
|
20017
|
-
|
|
20018
|
-
|
|
20019
|
-
|
|
20020
|
-
|
|
20021
|
-
|
|
20022
|
-
|
|
20023
|
-
|
|
20024
|
-
|
|
20025
|
-
|
|
20026
|
-
|
|
20027
|
-
|
|
20028
|
-
|
|
20029
|
-
|
|
20030
|
-
|
|
20031
|
-
|
|
20032
|
-
|
|
20033
|
-
|
|
20034
|
-
|
|
20035
|
-
|
|
20036
|
-
|
|
20037
|
-
|
|
20038
|
-
|
|
20039
|
-
spacingLeft += marginLeft;
|
|
20040
|
-
spacingRight += inset;
|
|
20041
|
-
// spacingRight += outlineOffset;
|
|
20042
|
-
// spacingRight -= borderRightWidth;
|
|
20043
|
-
spacingRight += marginRight;
|
|
20044
|
-
spacingBottom += inset;
|
|
20045
|
-
// spacingBottom += outlineOffset;
|
|
20046
|
-
// spacingBottom -= borderBottomWidth;
|
|
20047
|
-
spacingBottom += marginBottom;
|
|
20048
|
-
if (targetSelector) {
|
|
20049
|
-
// oversimplification that actually works
|
|
20050
|
-
// (simplified because it assumes the targeted element is a direct child of the contained element which may have padding)
|
|
20051
|
-
spacingTop += paddingTop;
|
|
20052
|
-
spacingLeft += paddingLeft;
|
|
20053
|
-
spacingRight += paddingRight;
|
|
20054
|
-
spacingBottom += paddingBottom;
|
|
19686
|
+
effect(() => {
|
|
19687
|
+
// eslint-disable-next-line no-unused-expressions
|
|
19688
|
+
activeElementSignal.value;
|
|
19689
|
+
updateActiveShortcuts();
|
|
19690
|
+
});
|
|
19691
|
+
const addShortcuts = (element, shortcuts) => {
|
|
19692
|
+
shortcutsMap.set(element, { shortcuts });
|
|
19693
|
+
updateActiveShortcuts();
|
|
19694
|
+
};
|
|
19695
|
+
const removeShortcuts = (element) => {
|
|
19696
|
+
shortcutsMap.delete(element);
|
|
19697
|
+
updateActiveShortcuts();
|
|
19698
|
+
};
|
|
19699
|
+
|
|
19700
|
+
const useKeyboardShortcuts = (
|
|
19701
|
+
elementRef,
|
|
19702
|
+
shortcuts,
|
|
19703
|
+
{
|
|
19704
|
+
onActionPrevented,
|
|
19705
|
+
onActionStart,
|
|
19706
|
+
onActionAbort,
|
|
19707
|
+
onActionError,
|
|
19708
|
+
onActionEnd,
|
|
19709
|
+
allowConcurrentActions,
|
|
19710
|
+
} = {},
|
|
19711
|
+
) => {
|
|
19712
|
+
if (!elementRef) {
|
|
19713
|
+
throw new Error(
|
|
19714
|
+
"useKeyboardShortcuts requires an elementRef to attach shortcuts to.",
|
|
19715
|
+
);
|
|
20055
19716
|
}
|
|
20056
|
-
|
|
20057
|
-
const
|
|
20058
|
-
const
|
|
20059
|
-
|
|
20060
|
-
|
|
20061
|
-
|
|
20062
|
-
|
|
20063
|
-
|
|
20064
|
-
|
|
20065
|
-
|
|
20066
|
-
|
|
20067
|
-
|
|
20068
|
-
|
|
20069
|
-
|
|
20070
|
-
|
|
20071
|
-
|
|
20072
|
-
|
|
20073
|
-
|
|
20074
|
-
|
|
20075
|
-
|
|
20076
|
-
|
|
20077
|
-
|
|
20078
|
-
|
|
20079
|
-
|
|
20080
|
-
|
|
20081
|
-
|
|
19717
|
+
|
|
19718
|
+
const executeAction = useExecuteAction(elementRef);
|
|
19719
|
+
const shortcutActionIsBusyRef = useRef(false);
|
|
19720
|
+
useActionEvents(elementRef, {
|
|
19721
|
+
actionOrigin: "keyboard_shortcut",
|
|
19722
|
+
onPrevented: onActionPrevented,
|
|
19723
|
+
onAction: (actionEvent) => {
|
|
19724
|
+
const { shortcut } = actionEvent.detail.meta || {};
|
|
19725
|
+
if (!shortcut) {
|
|
19726
|
+
// not a shortcut (an other interaction triggered the action, don't request it again)
|
|
19727
|
+
return;
|
|
19728
|
+
}
|
|
19729
|
+
// action can be a function or an action object, whem a function we must "wrap" it in a function returning that function
|
|
19730
|
+
// otherwise setState would call that action immediately
|
|
19731
|
+
// setAction(() => actionEvent.detail.action);
|
|
19732
|
+
executeAction(actionEvent, {
|
|
19733
|
+
requester: document.activeElement,
|
|
19734
|
+
});
|
|
19735
|
+
},
|
|
19736
|
+
onStart: (e) => {
|
|
19737
|
+
const { shortcut } = e.detail.meta || {};
|
|
19738
|
+
if (!shortcut) {
|
|
19739
|
+
return;
|
|
19740
|
+
}
|
|
19741
|
+
if (!allowConcurrentActions) {
|
|
19742
|
+
shortcutActionIsBusyRef.current = true;
|
|
19743
|
+
}
|
|
19744
|
+
shortcut.onStart?.(e);
|
|
19745
|
+
onActionStart?.(e);
|
|
19746
|
+
},
|
|
19747
|
+
onAbort: (e) => {
|
|
19748
|
+
const { shortcut } = e.detail.meta || {};
|
|
19749
|
+
if (!shortcut) {
|
|
19750
|
+
return;
|
|
19751
|
+
}
|
|
19752
|
+
shortcutActionIsBusyRef.current = false;
|
|
19753
|
+
shortcut.onAbort?.(e);
|
|
19754
|
+
onActionAbort?.(e);
|
|
19755
|
+
},
|
|
19756
|
+
onError: (error, e) => {
|
|
19757
|
+
const { shortcut } = e.detail.meta || {};
|
|
19758
|
+
if (!shortcut) {
|
|
19759
|
+
return;
|
|
19760
|
+
}
|
|
19761
|
+
shortcutActionIsBusyRef.current = false;
|
|
19762
|
+
shortcut.onError?.(error, e);
|
|
19763
|
+
onActionError?.(error, e);
|
|
19764
|
+
},
|
|
19765
|
+
onEnd: (e) => {
|
|
19766
|
+
const { shortcut } = e.detail.meta || {};
|
|
19767
|
+
if (!shortcut) {
|
|
19768
|
+
return;
|
|
19769
|
+
}
|
|
19770
|
+
shortcutActionIsBusyRef.current = false;
|
|
19771
|
+
shortcut.onEnd?.(e);
|
|
19772
|
+
onActionEnd?.(e);
|
|
19773
|
+
},
|
|
20082
19774
|
});
|
|
20083
|
-
};
|
|
20084
19775
|
|
|
20085
|
-
|
|
20086
|
-
|
|
20087
|
-
|
|
20088
|
-
|
|
20089
|
-
|
|
20090
|
-
|
|
20091
|
-
|
|
20092
|
-
|
|
20093
|
-
|
|
20094
|
-
const externalValueRef = useRef(externalValue);
|
|
20095
|
-
if (externalValue !== externalValueRef.current) {
|
|
20096
|
-
externalValueRef.current = externalValue;
|
|
20097
|
-
setValue(externalValue);
|
|
19776
|
+
const shortcutDeps = [];
|
|
19777
|
+
for (const shortcut of shortcuts) {
|
|
19778
|
+
shortcutDeps.push(
|
|
19779
|
+
shortcut.key,
|
|
19780
|
+
shortcut.description,
|
|
19781
|
+
shortcut.enabled,
|
|
19782
|
+
shortcut.confirmMessage,
|
|
19783
|
+
);
|
|
19784
|
+
shortcut.action = useAction(shortcut.action);
|
|
20098
19785
|
}
|
|
20099
19786
|
|
|
20100
|
-
|
|
20101
|
-
|
|
20102
|
-
|
|
20103
|
-
|
|
20104
|
-
|
|
20105
|
-
|
|
20106
|
-
|
|
20107
|
-
|
|
20108
|
-
|
|
20109
|
-
) => {
|
|
20110
|
-
|
|
20111
|
-
|
|
20112
|
-
|
|
20113
|
-
|
|
20114
|
-
|
|
20115
|
-
|
|
20116
|
-
|
|
19787
|
+
useEffect(() => {
|
|
19788
|
+
const element = elementRef.current;
|
|
19789
|
+
if (!element) {
|
|
19790
|
+
return null;
|
|
19791
|
+
}
|
|
19792
|
+
const shortcutsCopy = [];
|
|
19793
|
+
for (const shortcutCandidate of shortcuts) {
|
|
19794
|
+
shortcutsCopy.push({
|
|
19795
|
+
...shortcutCandidate,
|
|
19796
|
+
handler: (keyboardEvent) => {
|
|
19797
|
+
if (shortcutCandidate.handler) {
|
|
19798
|
+
return shortcutCandidate.handler(keyboardEvent);
|
|
19799
|
+
}
|
|
19800
|
+
if (shortcutActionIsBusyRef.current) {
|
|
19801
|
+
return false;
|
|
19802
|
+
}
|
|
19803
|
+
const { action } = shortcutCandidate;
|
|
19804
|
+
const actionWithEvent = action.bindParams(keyboardEvent);
|
|
19805
|
+
return requestAction(element, actionWithEvent, {
|
|
19806
|
+
actionOrigin: "keyboard_shortcut",
|
|
19807
|
+
event: keyboardEvent,
|
|
19808
|
+
requester: document.activeElement,
|
|
19809
|
+
confirmMessage: shortcutCandidate.confirmMessage,
|
|
19810
|
+
meta: {
|
|
19811
|
+
shortcut: shortcutCandidate,
|
|
19812
|
+
},
|
|
19813
|
+
});
|
|
19814
|
+
},
|
|
19815
|
+
});
|
|
19816
|
+
}
|
|
20117
19817
|
|
|
20118
|
-
|
|
20119
|
-
setValue(removeFromArray(valueArray, valueToRemove));
|
|
20120
|
-
};
|
|
19818
|
+
addShortcuts(element, shortcuts);
|
|
20121
19819
|
|
|
20122
|
-
|
|
20123
|
-
|
|
20124
|
-
|
|
20125
|
-
|
|
20126
|
-
|
|
20127
|
-
|
|
20128
|
-
|
|
20129
|
-
|
|
19820
|
+
const onKeydown = (event) => {
|
|
19821
|
+
applyKeyboardShortcuts(shortcutsCopy, event);
|
|
19822
|
+
};
|
|
19823
|
+
element.addEventListener("keydown", onKeydown);
|
|
19824
|
+
return () => {
|
|
19825
|
+
element.removeEventListener("keydown", onKeydown);
|
|
19826
|
+
removeShortcuts(element);
|
|
19827
|
+
};
|
|
19828
|
+
}, [shortcutDeps]);
|
|
20130
19829
|
};
|
|
20131
19830
|
|
|
20132
|
-
const
|
|
20133
|
-
|
|
20134
|
-
const actionCallbackRef = useRef();
|
|
20135
|
-
|
|
20136
|
-
if (!action) {
|
|
19831
|
+
const applyKeyboardShortcuts = (shortcuts, keyboardEvent) => {
|
|
19832
|
+
if (!canInterceptKeys(keyboardEvent)) {
|
|
20137
19833
|
return null;
|
|
20138
19834
|
}
|
|
20139
|
-
|
|
20140
|
-
|
|
20141
|
-
|
|
20142
|
-
|
|
20143
|
-
return existingAction;
|
|
19835
|
+
for (const shortcutCandidate of shortcuts) {
|
|
19836
|
+
let { enabled = true, key } = shortcutCandidate;
|
|
19837
|
+
if (!enabled) {
|
|
19838
|
+
continue;
|
|
20144
19839
|
}
|
|
20145
|
-
|
|
20146
|
-
|
|
20147
|
-
|
|
20148
|
-
|
|
20149
|
-
|
|
20150
|
-
|
|
20151
|
-
|
|
20152
|
-
|
|
20153
|
-
|
|
20154
|
-
|
|
20155
|
-
|
|
20156
|
-
if (!actionParamsSignal) {
|
|
20157
|
-
actionRef.current = actionFromFunction;
|
|
20158
|
-
return actionFromFunction;
|
|
19840
|
+
|
|
19841
|
+
if (typeof key === "function") {
|
|
19842
|
+
const keyReturnValue = key(keyboardEvent);
|
|
19843
|
+
if (!keyReturnValue) {
|
|
19844
|
+
continue;
|
|
19845
|
+
}
|
|
19846
|
+
key = keyReturnValue;
|
|
19847
|
+
}
|
|
19848
|
+
if (!key) {
|
|
19849
|
+
console.error(shortcutCandidate);
|
|
19850
|
+
throw new TypeError(`key is required in keyboard shortcut, got ${key}`);
|
|
20159
19851
|
}
|
|
20160
|
-
const actionBoundToParams =
|
|
20161
|
-
actionFromFunction.bindParams(actionParamsSignal);
|
|
20162
|
-
actionRef.current = actionBoundToParams;
|
|
20163
|
-
return actionBoundToParams;
|
|
20164
|
-
}
|
|
20165
|
-
if (actionParamsSignal) {
|
|
20166
|
-
return action.bindParams(actionParamsSignal);
|
|
20167
|
-
}
|
|
20168
|
-
return action;
|
|
20169
|
-
};
|
|
20170
19852
|
|
|
20171
|
-
|
|
20172
|
-
|
|
20173
|
-
|
|
19853
|
+
// Handle platform-specific combination objects
|
|
19854
|
+
let actualCombination;
|
|
19855
|
+
let crossPlatformCombination;
|
|
19856
|
+
if (typeof key === "object" && key !== null) {
|
|
19857
|
+
actualCombination = isMac ? key.mac : key.other;
|
|
19858
|
+
} else {
|
|
19859
|
+
actualCombination = key;
|
|
19860
|
+
if (containsPlatformSpecificKeys(key)) {
|
|
19861
|
+
crossPlatformCombination = generateCrossPlatformCombination(key);
|
|
19862
|
+
}
|
|
19863
|
+
}
|
|
20174
19864
|
|
|
20175
|
-
|
|
19865
|
+
// Check both the actual combination and cross-platform combination
|
|
19866
|
+
const matchesActual =
|
|
19867
|
+
actualCombination &&
|
|
19868
|
+
keyboardEventIsMatchingKeyCombination(keyboardEvent, actualCombination);
|
|
19869
|
+
const matchesCrossPlatform =
|
|
19870
|
+
crossPlatformCombination &&
|
|
19871
|
+
crossPlatformCombination !== actualCombination &&
|
|
19872
|
+
keyboardEventIsMatchingKeyCombination(
|
|
19873
|
+
keyboardEvent,
|
|
19874
|
+
crossPlatformCombination,
|
|
19875
|
+
);
|
|
20176
19876
|
|
|
20177
|
-
|
|
20178
|
-
|
|
20179
|
-
|
|
19877
|
+
if (!matchesActual && !matchesCrossPlatform) {
|
|
19878
|
+
continue;
|
|
19879
|
+
}
|
|
19880
|
+
if (typeof enabled === "function" && !enabled(keyboardEvent)) {
|
|
19881
|
+
continue;
|
|
19882
|
+
}
|
|
19883
|
+
const returnValue = shortcutCandidate.handler(keyboardEvent);
|
|
19884
|
+
if (returnValue) {
|
|
19885
|
+
keyboardEvent.preventDefault();
|
|
19886
|
+
}
|
|
19887
|
+
return shortcutCandidate;
|
|
19888
|
+
}
|
|
19889
|
+
return null;
|
|
20180
19890
|
};
|
|
19891
|
+
const containsPlatformSpecificKeys = (combination) => {
|
|
19892
|
+
const lowerCombination = combination.toLowerCase();
|
|
19893
|
+
const macSpecificKeys = ["command", "cmd"];
|
|
20181
19894
|
|
|
20182
|
-
|
|
20183
|
-
const customConstraintValidation =
|
|
20184
|
-
element.__validationInterface__ ||
|
|
20185
|
-
(element.__validationInterface__ =
|
|
20186
|
-
installCustomConstraintValidation(element));
|
|
20187
|
-
|
|
20188
|
-
return customConstraintValidation.addCustomMessage(key, message, options);
|
|
19895
|
+
return macSpecificKeys.some((key) => lowerCombination.includes(key));
|
|
20189
19896
|
};
|
|
19897
|
+
const generateCrossPlatformCombination = (combination) => {
|
|
19898
|
+
let crossPlatform = combination;
|
|
20190
19899
|
|
|
20191
|
-
|
|
20192
|
-
|
|
20193
|
-
|
|
20194
|
-
return;
|
|
19900
|
+
if (isMac) {
|
|
19901
|
+
// No need to convert anything TO Windows/Linux-specific format since we're on Mac
|
|
19902
|
+
return null;
|
|
20195
19903
|
}
|
|
20196
|
-
|
|
20197
|
-
|
|
20198
|
-
|
|
20199
|
-
|
|
20200
|
-
|
|
20201
|
-
|
|
20202
|
-
|
|
20203
|
-
|
|
20204
|
-
|
|
20205
|
-
)
|
|
20206
|
-
|
|
20207
|
-
|
|
20208
|
-
|
|
20209
|
-
|
|
20210
|
-
|
|
20211
|
-
|
|
20212
|
-
|
|
20213
|
-
|
|
20214
|
-
|
|
20215
|
-
}, [error]);
|
|
20216
|
-
|
|
20217
|
-
const validationMessageTargetRef = useRef(null);
|
|
20218
|
-
const addErrorMessage = (error) => {
|
|
20219
|
-
let calloutAnchor = validationMessageTargetRef.current;
|
|
20220
|
-
let message;
|
|
20221
|
-
if (errorMapping) {
|
|
20222
|
-
const errorMappingResult = errorMapping(error);
|
|
20223
|
-
if (typeof errorMappingResult === "string") {
|
|
20224
|
-
message = errorMappingResult;
|
|
20225
|
-
} else if (Error.isError(errorMappingResult)) {
|
|
20226
|
-
message = errorMappingResult;
|
|
20227
|
-
} else if (isValidElement(errorMappingResult)) {
|
|
20228
|
-
message = errorMappingResult;
|
|
20229
|
-
} else if (
|
|
20230
|
-
typeof errorMappingResult === "object" &&
|
|
20231
|
-
errorMappingResult !== null
|
|
20232
|
-
) {
|
|
20233
|
-
message = errorMappingResult.message || error.message;
|
|
20234
|
-
calloutAnchor = errorMappingResult.target || calloutAnchor;
|
|
19904
|
+
// If not on Mac but combination contains Mac-specific keys, generate Windows equivalent
|
|
19905
|
+
crossPlatform = crossPlatform.replace(/\bcommand\b/gi, "control");
|
|
19906
|
+
crossPlatform = crossPlatform.replace(/\bcmd\b/gi, "control");
|
|
19907
|
+
|
|
19908
|
+
return crossPlatform;
|
|
19909
|
+
};
|
|
19910
|
+
const keyboardEventIsMatchingKeyCombination = (event, keyCombination) => {
|
|
19911
|
+
const keys = keyCombination.toLowerCase().split("+");
|
|
19912
|
+
|
|
19913
|
+
for (const key of keys) {
|
|
19914
|
+
let modifierFound = false;
|
|
19915
|
+
|
|
19916
|
+
// Check if this key is a modifier
|
|
19917
|
+
for (const [eventProperty, config] of Object.entries(modifierKeyMapping)) {
|
|
19918
|
+
const allNames = [...config.names];
|
|
19919
|
+
|
|
19920
|
+
// Add Mac-specific names only if we're on Mac and they exist
|
|
19921
|
+
if (isMac && config.macNames) {
|
|
19922
|
+
allNames.push(...config.macNames);
|
|
20235
19923
|
}
|
|
20236
|
-
} else {
|
|
20237
|
-
message = error;
|
|
20238
|
-
}
|
|
20239
|
-
addCustomMessage(calloutAnchor, "action_error", message, {
|
|
20240
|
-
status: "error",
|
|
20241
|
-
// This error should not prevent <form> submission
|
|
20242
|
-
// so whenever user tries to submit the form the error is cleared
|
|
20243
|
-
// (Hitting enter key, clicking on submit button, etc. would allow to re-submit the form in error state)
|
|
20244
|
-
removeOnRequestAction: true,
|
|
20245
|
-
});
|
|
20246
|
-
};
|
|
20247
|
-
const removeErrorMessage = () => {
|
|
20248
|
-
const validationMessageTarget = validationMessageTargetRef.current;
|
|
20249
|
-
if (validationMessageTarget) {
|
|
20250
|
-
removeCustomMessage(validationMessageTarget, "action_error");
|
|
20251
|
-
}
|
|
20252
|
-
};
|
|
20253
19924
|
|
|
20254
|
-
|
|
20255
|
-
|
|
20256
|
-
|
|
20257
|
-
|
|
19925
|
+
if (allNames.includes(key)) {
|
|
19926
|
+
// Check if the corresponding event property is pressed
|
|
19927
|
+
if (!event[eventProperty]) {
|
|
19928
|
+
return false;
|
|
19929
|
+
}
|
|
19930
|
+
modifierFound = true;
|
|
19931
|
+
break;
|
|
19932
|
+
}
|
|
20258
19933
|
}
|
|
20259
|
-
|
|
20260
|
-
|
|
20261
|
-
return null;
|
|
19934
|
+
if (modifierFound) {
|
|
19935
|
+
continue;
|
|
20262
19936
|
}
|
|
20263
|
-
const onReset = () => {
|
|
20264
|
-
removeErrorMessage();
|
|
20265
|
-
};
|
|
20266
|
-
form.addEventListener("reset", onReset);
|
|
20267
|
-
return () => {
|
|
20268
|
-
form.removeEventListener("reset", onReset);
|
|
20269
|
-
};
|
|
20270
|
-
});
|
|
20271
19937
|
|
|
20272
|
-
|
|
20273
|
-
|
|
20274
|
-
|
|
20275
|
-
|
|
20276
|
-
|
|
20277
|
-
|
|
20278
|
-
|
|
20279
|
-
|
|
20280
|
-
|
|
20281
|
-
requester,
|
|
20282
|
-
event,
|
|
20283
|
-
method,
|
|
20284
|
-
};
|
|
19938
|
+
// Check if it's a range pattern like "a-z" or "0-9"
|
|
19939
|
+
if (key.includes("-") && key.length === 3) {
|
|
19940
|
+
const [startChar, dash, endChar] = key;
|
|
19941
|
+
if (dash === "-") {
|
|
19942
|
+
// Only check ranges for single alphanumeric characters
|
|
19943
|
+
const eventKey = event.key.toLowerCase();
|
|
19944
|
+
if (eventKey.length !== 1) {
|
|
19945
|
+
return false; // Not a single character key
|
|
19946
|
+
}
|
|
20285
19947
|
|
|
20286
|
-
|
|
20287
|
-
const
|
|
20288
|
-
|
|
20289
|
-
|
|
20290
|
-
};
|
|
20291
|
-
if (resetErrorBoundary) {
|
|
20292
|
-
resetErrorBoundary();
|
|
20293
|
-
}
|
|
20294
|
-
removeErrorMessage();
|
|
20295
|
-
setError(null);
|
|
19948
|
+
// Only allow a-z and 0-9 ranges
|
|
19949
|
+
const isValidRange =
|
|
19950
|
+
(startChar >= "a" && endChar <= "z") ||
|
|
19951
|
+
(startChar >= "0" && endChar <= "9");
|
|
20296
19952
|
|
|
20297
|
-
|
|
20298
|
-
|
|
19953
|
+
if (!isValidRange) {
|
|
19954
|
+
return false; // Invalid range pattern
|
|
19955
|
+
}
|
|
20299
19956
|
|
|
20300
|
-
|
|
20301
|
-
|
|
20302
|
-
|
|
19957
|
+
const eventKeyCode = eventKey.charCodeAt(0);
|
|
19958
|
+
const startCode = startChar.charCodeAt(0);
|
|
19959
|
+
const endCode = endChar.charCodeAt(0);
|
|
20303
19960
|
|
|
20304
|
-
|
|
20305
|
-
|
|
20306
|
-
|
|
20307
|
-
|
|
19961
|
+
if (eventKeyCode >= startCode && eventKeyCode <= endCode) {
|
|
19962
|
+
continue; // Range matched
|
|
19963
|
+
}
|
|
19964
|
+
return false; // Range not matched
|
|
19965
|
+
}
|
|
19966
|
+
}
|
|
20308
19967
|
|
|
20309
|
-
|
|
20310
|
-
|
|
20311
|
-
|
|
19968
|
+
// If it's not a modifier or range, check if it matches the actual key
|
|
19969
|
+
if (!isSameKey(event.key, key)) {
|
|
19970
|
+
return false;
|
|
19971
|
+
}
|
|
19972
|
+
}
|
|
19973
|
+
return true;
|
|
19974
|
+
};
|
|
19975
|
+
// Configuration for mapping shortcut key names to browser event properties
|
|
19976
|
+
const modifierKeyMapping = {
|
|
19977
|
+
metaKey: {
|
|
19978
|
+
names: ["meta"],
|
|
19979
|
+
macNames: ["command", "cmd"],
|
|
19980
|
+
},
|
|
19981
|
+
ctrlKey: {
|
|
19982
|
+
names: ["control", "ctrl"],
|
|
19983
|
+
},
|
|
19984
|
+
shiftKey: {
|
|
19985
|
+
names: ["shift"],
|
|
19986
|
+
},
|
|
19987
|
+
altKey: {
|
|
19988
|
+
names: ["alt"],
|
|
19989
|
+
macNames: ["option"],
|
|
19990
|
+
},
|
|
19991
|
+
};
|
|
19992
|
+
const isSameKey = (browserEventKey, key) => {
|
|
19993
|
+
browserEventKey = browserEventKey.toLowerCase();
|
|
19994
|
+
key = key.toLowerCase();
|
|
20312
19995
|
|
|
20313
|
-
|
|
20314
|
-
|
|
20315
|
-
|
|
20316
|
-
}
|
|
19996
|
+
if (browserEventKey === key) {
|
|
19997
|
+
return true;
|
|
19998
|
+
}
|
|
20317
19999
|
|
|
20318
|
-
|
|
20319
|
-
|
|
20320
|
-
|
|
20321
|
-
|
|
20322
|
-
|
|
20323
|
-
|
|
20324
|
-
|
|
20325
|
-
elementRef.current
|
|
20326
|
-
) {
|
|
20327
|
-
dispatchCustomEvent("actionabort", {
|
|
20328
|
-
detail: {
|
|
20329
|
-
...sharedActionEventDetail,
|
|
20330
|
-
reason,
|
|
20331
|
-
},
|
|
20332
|
-
});
|
|
20333
|
-
}
|
|
20334
|
-
},
|
|
20335
|
-
onError: (error) => {
|
|
20336
|
-
if (
|
|
20337
|
-
// at this stage the action side effect might have removed the <element> from the DOM
|
|
20338
|
-
// (in theory no because action side effect are batched to happen after)
|
|
20339
|
-
// but other side effects might do this
|
|
20340
|
-
elementRef.current
|
|
20341
|
-
) {
|
|
20342
|
-
dispatchCustomEvent("actionerror", {
|
|
20343
|
-
detail: {
|
|
20344
|
-
...sharedActionEventDetail,
|
|
20345
|
-
error,
|
|
20346
|
-
},
|
|
20347
|
-
});
|
|
20348
|
-
}
|
|
20349
|
-
if (errorEffect === "show_validation_message") {
|
|
20350
|
-
addErrorMessage(error);
|
|
20351
|
-
} else if (errorEffect === "throw") {
|
|
20352
|
-
setError(error);
|
|
20353
|
-
}
|
|
20354
|
-
},
|
|
20355
|
-
onComplete: (data) => {
|
|
20356
|
-
if (
|
|
20357
|
-
// at this stage the action side effect might have removed the <element> from the DOM
|
|
20358
|
-
// (in theory no because action side effect are batched to happen after)
|
|
20359
|
-
// but other side effects might do this
|
|
20360
|
-
elementRef.current
|
|
20361
|
-
) {
|
|
20362
|
-
dispatchCustomEvent("actionend", {
|
|
20363
|
-
detail: {
|
|
20364
|
-
...sharedActionEventDetail,
|
|
20365
|
-
data,
|
|
20366
|
-
},
|
|
20367
|
-
});
|
|
20368
|
-
}
|
|
20369
|
-
},
|
|
20370
|
-
});
|
|
20371
|
-
},
|
|
20372
|
-
[errorEffect],
|
|
20373
|
-
);
|
|
20000
|
+
// Check if either key is an alias for the other
|
|
20001
|
+
for (const [canonicalKey, config] of Object.entries(keyMapping)) {
|
|
20002
|
+
const allKeys = [canonicalKey, ...config.alias];
|
|
20003
|
+
if (allKeys.includes(browserEventKey) && allKeys.includes(key)) {
|
|
20004
|
+
return true;
|
|
20005
|
+
}
|
|
20006
|
+
}
|
|
20374
20007
|
|
|
20375
|
-
return
|
|
20008
|
+
return false;
|
|
20376
20009
|
};
|
|
20377
20010
|
|
|
20378
|
-
const
|
|
20379
|
-
|
|
20380
|
-
|
|
20381
|
-
|
|
20011
|
+
installImportMetaCssBuild(import.meta);const css$7 = /* css */`
|
|
20012
|
+
.navi_text_aligner_anchor {
|
|
20013
|
+
vertical-align: baseline;
|
|
20014
|
+
user-select: none;
|
|
20015
|
+
overflow: hidden;
|
|
20382
20016
|
}
|
|
20383
|
-
|
|
20384
|
-
return /Mac|iPhone|iPad|iPod/.test(window.navigator.userAgent);
|
|
20385
|
-
};
|
|
20386
|
-
const isMac = detectMac();
|
|
20017
|
+
`;
|
|
20387
20018
|
|
|
20388
|
-
|
|
20389
|
-
|
|
20390
|
-
|
|
20391
|
-
|
|
20392
|
-
|
|
20393
|
-
|
|
20394
|
-
|
|
20395
|
-
|
|
20396
|
-
|
|
20397
|
-
|
|
20398
|
-
|
|
20399
|
-
|
|
20400
|
-
|
|
20401
|
-
|
|
20019
|
+
/**
|
|
20020
|
+
* Positions children vertically relative to the surrounding text, correcting for font-size differences.
|
|
20021
|
+
*
|
|
20022
|
+
* Place this component around any inline element whose font-size differs from the surrounding text.
|
|
20023
|
+
* It renders an invisible anchor that inherits the surrounding text's font metrics, then shifts
|
|
20024
|
+
* the child so that its visual position matches the requested `align` value — regardless of
|
|
20025
|
+
* font-size, display type (inline, inline-block, inline-flex…), or the active `vertical-align`.
|
|
20026
|
+
*
|
|
20027
|
+
* @param {"center"|"baseline"|"start"|"end"} [align="baseline"]
|
|
20028
|
+
* - `"center"` — child is vertically centered on the surrounding text's ink bounds
|
|
20029
|
+
* - `"baseline"` — no correction applied; child sits wherever the browser places it (default)
|
|
20030
|
+
* - `"start"` — child top aligns with the surrounding text's ink top
|
|
20031
|
+
* - `"end"` — child bottom aligns with the surrounding text's ink bottom
|
|
20032
|
+
* @param {import("ignore:preact").RefObject} childRef — ref on the child element to reposition
|
|
20033
|
+
*/
|
|
20034
|
+
const SurroundingTextAligner = ({
|
|
20035
|
+
children,
|
|
20036
|
+
align = "baseline",
|
|
20037
|
+
childRef,
|
|
20038
|
+
textSize,
|
|
20039
|
+
size
|
|
20040
|
+
}) => {
|
|
20041
|
+
import.meta.css = [css$7, "@jsenv/navi/src/text/surrounding_text_aligner.jsx"];
|
|
20042
|
+
const anchorRef = useRef();
|
|
20043
|
+
useLayoutEffect(() => {
|
|
20044
|
+
const anchorEl = anchorRef.current;
|
|
20045
|
+
const childEl = childRef.current;
|
|
20046
|
+
if (!anchorEl || !childEl) {
|
|
20047
|
+
return;
|
|
20048
|
+
}
|
|
20049
|
+
const topOffset = computeTopOffset({
|
|
20050
|
+
anchorEl,
|
|
20051
|
+
childEl,
|
|
20052
|
+
align
|
|
20053
|
+
});
|
|
20054
|
+
if (topOffset) {
|
|
20055
|
+
childEl.style.position = "relative";
|
|
20056
|
+
childEl.style.top = `${topOffset}px`;
|
|
20057
|
+
} else {
|
|
20058
|
+
childEl.style.position = "";
|
|
20059
|
+
childEl.style.top = "";
|
|
20060
|
+
}
|
|
20061
|
+
}, [size, textSize]);
|
|
20062
|
+
return jsxs(Fragment, {
|
|
20063
|
+
children: [children, jsx("span", {
|
|
20064
|
+
ref: anchorRef,
|
|
20065
|
+
className: "navi_text_aligner_anchor",
|
|
20066
|
+
children: "\u200B"
|
|
20067
|
+
})]
|
|
20068
|
+
});
|
|
20402
20069
|
};
|
|
20403
|
-
|
|
20404
|
-
|
|
20405
|
-
|
|
20406
|
-
|
|
20407
|
-
|
|
20408
|
-
|
|
20409
|
-
|
|
20410
|
-
|
|
20411
|
-
|
|
20412
|
-
|
|
20070
|
+
const computeTopOffset = ({
|
|
20071
|
+
anchorEl,
|
|
20072
|
+
childEl,
|
|
20073
|
+
align
|
|
20074
|
+
}) => {
|
|
20075
|
+
if (align === "baseline") {
|
|
20076
|
+
return 0;
|
|
20077
|
+
}
|
|
20078
|
+
// Only correct when the anchor lives in an inline formatting context.
|
|
20079
|
+
// If the parent is a flex/grid container, inline layout rules don't apply
|
|
20080
|
+
// and our font-metrics model is invalid.
|
|
20081
|
+
const parentDisplay = getComputedStyle(anchorEl.parentElement).display;
|
|
20082
|
+
if (parentDisplay !== "inline" && parentDisplay !== "inline-block" && parentDisplay !== "block") {
|
|
20083
|
+
return 0;
|
|
20084
|
+
}
|
|
20085
|
+
const anchorStyle = getComputedStyle(anchorEl);
|
|
20086
|
+
const anchorMetrics = measureFontAscDesc("M", anchorStyle);
|
|
20087
|
+
const [anchorABA, anchorABD] = anchorMetrics.actual;
|
|
20088
|
+
const anchorActH = anchorABA + anchorABD;
|
|
20089
|
+
const [, anchorFBBD] = anchorMetrics.font;
|
|
20090
|
+
|
|
20091
|
+
// Estimate the baseline Y from the anchor's bounding rect.
|
|
20092
|
+
// For an inline span, the font cell bottom is always at the element's bottom edge
|
|
20093
|
+
// (regardless of vertical-align), so baseline = rect.bottom - fontBoundingBoxDescent.
|
|
20094
|
+
const anchorRect = anchorEl.getBoundingClientRect();
|
|
20095
|
+
const baselineY = anchorRect.bottom - anchorFBBD;
|
|
20096
|
+
const anchorInkTopY = baselineY - anchorABA;
|
|
20097
|
+
|
|
20098
|
+
// Measure the child's current rect, then subtract any previously applied top correction
|
|
20099
|
+
// to recover its natural position — avoiding a style reset + reflow.
|
|
20100
|
+
const childRect = childEl.getBoundingClientRect();
|
|
20101
|
+
const childH = childRect.height;
|
|
20102
|
+
const previousTop = parseFloat(childEl.style.top) || 0;
|
|
20103
|
+
const childNaturalTop = childRect.top - previousTop;
|
|
20104
|
+
|
|
20105
|
+
// Compute desired child top Y based on align intention.
|
|
20106
|
+
let desiredChildTopY = 0;
|
|
20107
|
+
if (align === "center") {
|
|
20108
|
+
const anchorInkCenterY = anchorInkTopY + anchorActH / 2;
|
|
20109
|
+
desiredChildTopY = anchorInkCenterY - childH / 2;
|
|
20110
|
+
} else if (align === "start") {
|
|
20111
|
+
desiredChildTopY = anchorInkTopY;
|
|
20112
|
+
} else if (align === "end") {
|
|
20113
|
+
desiredChildTopY = anchorInkTopY + anchorActH - childH;
|
|
20114
|
+
}
|
|
20115
|
+
return desiredChildTopY - childNaturalTop;
|
|
20116
|
+
};
|
|
20117
|
+
const canvas = document.createElement("canvas");
|
|
20118
|
+
const measureFontAscDesc = (text, computedStyle) => {
|
|
20119
|
+
const ctx = canvas.getContext("2d");
|
|
20120
|
+
ctx.font = `${computedStyle.fontWeight} ${computedStyle.fontSize} ${computedStyle.fontFamily}`;
|
|
20121
|
+
const metrics = ctx.measureText(text);
|
|
20122
|
+
return {
|
|
20123
|
+
actual: [metrics.actualBoundingBoxAscent, metrics.actualBoundingBoxDescent],
|
|
20124
|
+
font: [metrics.fontBoundingBoxAscent, metrics.fontBoundingBoxDescent]
|
|
20125
|
+
};
|
|
20413
20126
|
};
|
|
20414
20127
|
|
|
20415
|
-
const
|
|
20416
|
-
|
|
20417
|
-
|
|
20128
|
+
const useInitialTextSelection = (ref, textSelection) => {
|
|
20129
|
+
const deps = [];
|
|
20130
|
+
if (Array.isArray(textSelection)) {
|
|
20131
|
+
deps.push(...textSelection);
|
|
20132
|
+
} else {
|
|
20133
|
+
deps.push(textSelection);
|
|
20418
20134
|
}
|
|
20419
|
-
|
|
20420
|
-
|
|
20421
|
-
if (!
|
|
20422
|
-
return
|
|
20135
|
+
useLayoutEffect(() => {
|
|
20136
|
+
const el = ref.current;
|
|
20137
|
+
if (!el || !textSelection) {
|
|
20138
|
+
return;
|
|
20423
20139
|
}
|
|
20424
|
-
|
|
20425
|
-
|
|
20426
|
-
|
|
20140
|
+
const range = document.createRange();
|
|
20141
|
+
const selection = window.getSelection();
|
|
20142
|
+
if (Array.isArray(textSelection)) {
|
|
20143
|
+
if (textSelection.length === 2) {
|
|
20144
|
+
const [start, end] = textSelection;
|
|
20145
|
+
if (typeof start === "number" && typeof end === "number") {
|
|
20146
|
+
// Format: [0, 10] - character indices
|
|
20147
|
+
selectByCharacterIndices(el, range, start, end);
|
|
20148
|
+
} else if (typeof start === "string" && typeof end === "string") {
|
|
20149
|
+
// Format: ["Click on the", "button to return"] - text strings
|
|
20150
|
+
selectByTextStrings(el, range, start, end);
|
|
20151
|
+
}
|
|
20152
|
+
}
|
|
20153
|
+
} else if (typeof textSelection === "string") {
|
|
20154
|
+
// Format: "some text" - select the entire string occurrence
|
|
20155
|
+
selectSingleTextString(el, range, textSelection);
|
|
20156
|
+
}
|
|
20157
|
+
selection.removeAllRanges();
|
|
20158
|
+
selection.addRange(range);
|
|
20159
|
+
}, deps);
|
|
20427
20160
|
};
|
|
20161
|
+
const selectByCharacterIndices = (element, range, startIndex, endIndex) => {
|
|
20162
|
+
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false);
|
|
20163
|
+
let currentIndex = 0;
|
|
20164
|
+
let startNode = null;
|
|
20165
|
+
let startOffset = 0;
|
|
20166
|
+
let endNode = null;
|
|
20167
|
+
let endOffset = 0;
|
|
20168
|
+
while (walker.nextNode()) {
|
|
20169
|
+
const textContent = walker.currentNode.textContent;
|
|
20170
|
+
const nodeLength = textContent.length;
|
|
20428
20171
|
|
|
20429
|
-
|
|
20430
|
-
|
|
20431
|
-
|
|
20432
|
-
|
|
20433
|
-
for (const [element, { shortcuts }] of shortcutsMap) {
|
|
20434
|
-
if (element === activeElement || element.contains(activeElement)) {
|
|
20435
|
-
activeShortcuts.push(...shortcuts);
|
|
20172
|
+
// Check if start position is in this text node
|
|
20173
|
+
if (!startNode && currentIndex + nodeLength > startIndex) {
|
|
20174
|
+
startNode = walker.currentNode;
|
|
20175
|
+
startOffset = startIndex - currentIndex;
|
|
20436
20176
|
}
|
|
20437
|
-
}
|
|
20438
20177
|
|
|
20439
|
-
|
|
20440
|
-
|
|
20441
|
-
|
|
20178
|
+
// Check if end position is in this text node
|
|
20179
|
+
if (currentIndex + nodeLength >= endIndex) {
|
|
20180
|
+
endNode = walker.currentNode;
|
|
20181
|
+
endOffset = endIndex - currentIndex;
|
|
20182
|
+
break;
|
|
20183
|
+
}
|
|
20184
|
+
currentIndex += nodeLength;
|
|
20185
|
+
}
|
|
20186
|
+
if (startNode && endNode) {
|
|
20187
|
+
range.setStart(startNode, startOffset);
|
|
20188
|
+
range.setEnd(endNode, endOffset);
|
|
20442
20189
|
}
|
|
20443
20190
|
};
|
|
20444
|
-
|
|
20445
|
-
|
|
20446
|
-
|
|
20447
|
-
|
|
20448
|
-
|
|
20449
|
-
|
|
20450
|
-
|
|
20451
|
-
|
|
20191
|
+
const selectSingleTextString = (element, range, text) => {
|
|
20192
|
+
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false);
|
|
20193
|
+
while (walker.nextNode()) {
|
|
20194
|
+
const textContent = walker.currentNode.textContent;
|
|
20195
|
+
const index = textContent.indexOf(text);
|
|
20196
|
+
if (index !== -1) {
|
|
20197
|
+
range.setStart(walker.currentNode, index);
|
|
20198
|
+
range.setEnd(walker.currentNode, index + text.length);
|
|
20199
|
+
return;
|
|
20200
|
+
}
|
|
20201
|
+
}
|
|
20452
20202
|
};
|
|
20453
|
-
const
|
|
20454
|
-
|
|
20455
|
-
|
|
20203
|
+
const selectByTextStrings = (element, range, startText, endText) => {
|
|
20204
|
+
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false);
|
|
20205
|
+
let startNode = null;
|
|
20206
|
+
let endNode = null;
|
|
20207
|
+
let foundStart = false;
|
|
20208
|
+
while (walker.nextNode()) {
|
|
20209
|
+
const textContent = walker.currentNode.textContent;
|
|
20210
|
+
if (!foundStart && textContent.includes(startText)) {
|
|
20211
|
+
startNode = walker.currentNode;
|
|
20212
|
+
foundStart = true;
|
|
20213
|
+
}
|
|
20214
|
+
if (foundStart && textContent.includes(endText)) {
|
|
20215
|
+
endNode = walker.currentNode;
|
|
20216
|
+
break;
|
|
20217
|
+
}
|
|
20218
|
+
}
|
|
20219
|
+
if (startNode && endNode) {
|
|
20220
|
+
const startOffset = startNode.textContent.indexOf(startText);
|
|
20221
|
+
const endOffset = endNode.textContent.indexOf(endText) + endText.length;
|
|
20222
|
+
range.setStart(startNode, startOffset);
|
|
20223
|
+
range.setEnd(endNode, endOffset);
|
|
20224
|
+
}
|
|
20456
20225
|
};
|
|
20457
20226
|
|
|
20458
|
-
|
|
20459
|
-
|
|
20460
|
-
|
|
20461
|
-
|
|
20462
|
-
|
|
20463
|
-
|
|
20464
|
-
|
|
20465
|
-
|
|
20466
|
-
onActionEnd,
|
|
20467
|
-
allowConcurrentActions,
|
|
20468
|
-
} = {},
|
|
20469
|
-
) => {
|
|
20470
|
-
if (!elementRef) {
|
|
20471
|
-
throw new Error(
|
|
20472
|
-
"useKeyboardShortcuts requires an elementRef to attach shortcuts to.",
|
|
20473
|
-
);
|
|
20227
|
+
installImportMetaCssBuild(import.meta);/* eslint-disable jsenv/no-unknown-params */
|
|
20228
|
+
const css$6 = /* css */`
|
|
20229
|
+
@layer navi {
|
|
20230
|
+
.navi_text {
|
|
20231
|
+
&[data-skeleton] {
|
|
20232
|
+
border-radius: 0.2em;
|
|
20233
|
+
}
|
|
20234
|
+
}
|
|
20474
20235
|
}
|
|
20475
20236
|
|
|
20476
|
-
|
|
20477
|
-
|
|
20478
|
-
|
|
20479
|
-
|
|
20480
|
-
|
|
20481
|
-
|
|
20482
|
-
|
|
20483
|
-
|
|
20484
|
-
|
|
20485
|
-
|
|
20237
|
+
*[data-navi-space] {
|
|
20238
|
+
}
|
|
20239
|
+
|
|
20240
|
+
.navi_text {
|
|
20241
|
+
position: relative;
|
|
20242
|
+
|
|
20243
|
+
/* There is a chrome specific bug that prevents text-transform: capitalize to be applied in nested DOM structure */
|
|
20244
|
+
/* The CSS below ensure capitalize is propagated to the bold clones */
|
|
20245
|
+
&[data-capitalize] {
|
|
20246
|
+
&::first-letter {
|
|
20247
|
+
text-transform: uppercase;
|
|
20486
20248
|
}
|
|
20487
|
-
|
|
20488
|
-
|
|
20489
|
-
// setAction(() => actionEvent.detail.action);
|
|
20490
|
-
executeAction(actionEvent, {
|
|
20491
|
-
requester: document.activeElement,
|
|
20492
|
-
});
|
|
20493
|
-
},
|
|
20494
|
-
onStart: (e) => {
|
|
20495
|
-
const { shortcut } = e.detail.meta || {};
|
|
20496
|
-
if (!shortcut) {
|
|
20497
|
-
return;
|
|
20249
|
+
.navi_text_bold_clone::first-letter {
|
|
20250
|
+
text-transform: uppercase;
|
|
20498
20251
|
}
|
|
20499
|
-
|
|
20500
|
-
|
|
20252
|
+
.navi_text_bold_foreground::first-letter {
|
|
20253
|
+
text-transform: uppercase;
|
|
20501
20254
|
}
|
|
20502
|
-
|
|
20503
|
-
|
|
20504
|
-
|
|
20505
|
-
|
|
20506
|
-
|
|
20507
|
-
|
|
20508
|
-
|
|
20255
|
+
}
|
|
20256
|
+
|
|
20257
|
+
.navi_text_bold_wrapper,
|
|
20258
|
+
.navi_text_bold_clone,
|
|
20259
|
+
.navi_text_bold_foreground {
|
|
20260
|
+
display: inherit;
|
|
20261
|
+
width: inherit;
|
|
20262
|
+
min-width: inherit;
|
|
20263
|
+
height: inherit;
|
|
20264
|
+
min-height: inherit;
|
|
20265
|
+
flex-grow: inherit;
|
|
20266
|
+
align-items: inherit;
|
|
20267
|
+
justify-content: inherit;
|
|
20268
|
+
gap: inherit;
|
|
20269
|
+
text-align: inherit;
|
|
20270
|
+
border-radius: inherit;
|
|
20271
|
+
}
|
|
20272
|
+
|
|
20273
|
+
&[data-text-overflow] {
|
|
20274
|
+
min-width: 0;
|
|
20275
|
+
flex-wrap: wrap;
|
|
20276
|
+
text-overflow: ellipsis;
|
|
20277
|
+
overflow: hidden;
|
|
20278
|
+
|
|
20279
|
+
.navi_text_overflow_wrapper {
|
|
20280
|
+
display: flex;
|
|
20281
|
+
width: 100%;
|
|
20282
|
+
flex-grow: 1;
|
|
20283
|
+
gap: 0.3em;
|
|
20284
|
+
|
|
20285
|
+
.navi_text_overflow_text {
|
|
20286
|
+
max-width: 100%;
|
|
20287
|
+
text-overflow: ellipsis;
|
|
20288
|
+
overflow: hidden;
|
|
20289
|
+
}
|
|
20290
|
+
}
|
|
20291
|
+
}
|
|
20292
|
+
|
|
20293
|
+
&[data-skeleton] {
|
|
20294
|
+
/* Children stay in the DOM to preserve natural layout dimensions,
|
|
20295
|
+
but are hidden so only the skeleton is visible. */
|
|
20296
|
+
visibility: hidden;
|
|
20297
|
+
|
|
20298
|
+
/* When there are no children a placeholder "W" is injected (see JSX).
|
|
20299
|
+
It must stretch to the full available width so the skeleton
|
|
20300
|
+
fills the container rather than collapsing to a single character. */
|
|
20301
|
+
.navi_text_skeleton_children_placeholder {
|
|
20302
|
+
display: inline-flex;
|
|
20303
|
+
width: 100%;
|
|
20304
|
+
}
|
|
20305
|
+
|
|
20306
|
+
/* Three-level structure to respect padding AND border-radius:
|
|
20307
|
+
|
|
20308
|
+
1. navi_text_skeleton_container — absolutely fills the border box
|
|
20309
|
+
(inset:0), then applies padding:inherit so its content box equals
|
|
20310
|
+
the parent's content box. line-height:normal prevents the container
|
|
20311
|
+
from inheriting a large line-height that would make it taller than
|
|
20312
|
+
the border box. border-radius:inherit passes the radius down.
|
|
20313
|
+
visibility:visible overrides the parent's visibility:hidden.
|
|
20314
|
+
|
|
20315
|
+
2. navi_text_skeleton_inset — a relative block that fills 100% of the
|
|
20316
|
+
container's content box (= parent's content box). It is the
|
|
20317
|
+
positioned ancestor for the absolutely placed skeleton bar.
|
|
20318
|
+
border-radius:inherit chains the radius further down.
|
|
20319
|
+
|
|
20320
|
+
3. navi_text_skeleton — the visible gradient bar. position:absolute
|
|
20321
|
+
inset:0 fills the inset box precisely. border-radius:inherit
|
|
20322
|
+
finally applies the radius at this level, which is now correctly
|
|
20323
|
+
sized to the content area. */
|
|
20324
|
+
.navi_text_skeleton_container {
|
|
20325
|
+
position: absolute;
|
|
20326
|
+
inset: 0;
|
|
20327
|
+
padding: inherit;
|
|
20328
|
+
line-height: normal;
|
|
20329
|
+
border-radius: inherit;
|
|
20330
|
+
visibility: visible;
|
|
20509
20331
|
}
|
|
20510
|
-
|
|
20511
|
-
|
|
20512
|
-
|
|
20513
|
-
|
|
20514
|
-
|
|
20515
|
-
|
|
20516
|
-
|
|
20517
|
-
return;
|
|
20332
|
+
|
|
20333
|
+
.navi_text_skeleton_inset {
|
|
20334
|
+
position: relative;
|
|
20335
|
+
display: inline-flex;
|
|
20336
|
+
width: 100%;
|
|
20337
|
+
height: 100%;
|
|
20338
|
+
border-radius: inherit;
|
|
20518
20339
|
}
|
|
20519
|
-
|
|
20520
|
-
|
|
20521
|
-
|
|
20522
|
-
|
|
20523
|
-
|
|
20524
|
-
|
|
20525
|
-
|
|
20526
|
-
|
|
20340
|
+
|
|
20341
|
+
.navi_text_skeleton {
|
|
20342
|
+
position: absolute;
|
|
20343
|
+
inset: 0;
|
|
20344
|
+
background: linear-gradient(
|
|
20345
|
+
90deg,
|
|
20346
|
+
#e0e0e0 25%,
|
|
20347
|
+
#f0f0f0 50%,
|
|
20348
|
+
#e0e0e0 75%
|
|
20349
|
+
);
|
|
20350
|
+
background-size: 200% 100%;
|
|
20351
|
+
border-radius: inherit;
|
|
20527
20352
|
}
|
|
20528
|
-
shortcutActionIsBusyRef.current = false;
|
|
20529
|
-
shortcut.onEnd?.(e);
|
|
20530
|
-
onActionEnd?.(e);
|
|
20531
|
-
},
|
|
20532
|
-
});
|
|
20533
20353
|
|
|
20534
|
-
|
|
20535
|
-
|
|
20536
|
-
|
|
20537
|
-
|
|
20538
|
-
|
|
20539
|
-
|
|
20540
|
-
shortcut.confirmMessage,
|
|
20541
|
-
);
|
|
20542
|
-
shortcut.action = useAction(shortcut.action);
|
|
20354
|
+
&[data-loading] {
|
|
20355
|
+
.navi_text_skeleton {
|
|
20356
|
+
animation: navi_text_skeleton_shimmer 1.5s infinite;
|
|
20357
|
+
}
|
|
20358
|
+
}
|
|
20359
|
+
}
|
|
20543
20360
|
}
|
|
20544
20361
|
|
|
20545
|
-
|
|
20546
|
-
|
|
20547
|
-
|
|
20548
|
-
return null;
|
|
20362
|
+
@keyframes navi_text_skeleton_shimmer {
|
|
20363
|
+
0% {
|
|
20364
|
+
background-position: 200% 0;
|
|
20549
20365
|
}
|
|
20550
|
-
|
|
20551
|
-
|
|
20552
|
-
shortcutsCopy.push({
|
|
20553
|
-
...shortcutCandidate,
|
|
20554
|
-
handler: (keyboardEvent) => {
|
|
20555
|
-
if (shortcutCandidate.handler) {
|
|
20556
|
-
return shortcutCandidate.handler(keyboardEvent);
|
|
20557
|
-
}
|
|
20558
|
-
if (shortcutActionIsBusyRef.current) {
|
|
20559
|
-
return false;
|
|
20560
|
-
}
|
|
20561
|
-
const { action } = shortcutCandidate;
|
|
20562
|
-
const actionWithEvent = action.bindParams(keyboardEvent);
|
|
20563
|
-
return requestAction(element, actionWithEvent, {
|
|
20564
|
-
actionOrigin: "keyboard_shortcut",
|
|
20565
|
-
event: keyboardEvent,
|
|
20566
|
-
requester: document.activeElement,
|
|
20567
|
-
confirmMessage: shortcutCandidate.confirmMessage,
|
|
20568
|
-
meta: {
|
|
20569
|
-
shortcut: shortcutCandidate,
|
|
20570
|
-
},
|
|
20571
|
-
});
|
|
20572
|
-
},
|
|
20573
|
-
});
|
|
20366
|
+
100% {
|
|
20367
|
+
background-position: -200% 0;
|
|
20574
20368
|
}
|
|
20369
|
+
}
|
|
20575
20370
|
|
|
20576
|
-
|
|
20371
|
+
.navi_text_bold_wrapper {
|
|
20372
|
+
position: relative;
|
|
20373
|
+
display: inline-block;
|
|
20577
20374
|
|
|
20578
|
-
|
|
20579
|
-
|
|
20580
|
-
|
|
20581
|
-
|
|
20582
|
-
|
|
20583
|
-
|
|
20584
|
-
|
|
20585
|
-
}
|
|
20586
|
-
}
|
|
20587
|
-
};
|
|
20375
|
+
.navi_text_bold_clone {
|
|
20376
|
+
font-weight: bold;
|
|
20377
|
+
opacity: 0;
|
|
20378
|
+
}
|
|
20379
|
+
.navi_text_bold_foreground {
|
|
20380
|
+
position: absolute;
|
|
20381
|
+
inset: 0;
|
|
20382
|
+
}
|
|
20383
|
+
}
|
|
20588
20384
|
|
|
20589
|
-
|
|
20590
|
-
|
|
20591
|
-
|
|
20385
|
+
.navi_text_bold_background {
|
|
20386
|
+
position: absolute;
|
|
20387
|
+
top: 0;
|
|
20388
|
+
left: 0;
|
|
20389
|
+
color: currentColor;
|
|
20390
|
+
font-weight: normal;
|
|
20391
|
+
background: currentColor;
|
|
20392
|
+
background-clip: text;
|
|
20393
|
+
-webkit-background-clip: text;
|
|
20394
|
+
transform-origin: center;
|
|
20395
|
+
-webkit-text-fill-color: transparent;
|
|
20396
|
+
opacity: 0;
|
|
20592
20397
|
}
|
|
20593
|
-
for (const shortcutCandidate of shortcuts) {
|
|
20594
|
-
let { enabled = true, key } = shortcutCandidate;
|
|
20595
|
-
if (!enabled) {
|
|
20596
|
-
continue;
|
|
20597
|
-
}
|
|
20598
20398
|
|
|
20599
|
-
|
|
20600
|
-
|
|
20601
|
-
|
|
20602
|
-
continue;
|
|
20603
|
-
}
|
|
20604
|
-
key = keyReturnValue;
|
|
20399
|
+
.navi_text[data-bold] {
|
|
20400
|
+
.navi_text_bold_background {
|
|
20401
|
+
opacity: 1;
|
|
20605
20402
|
}
|
|
20606
|
-
|
|
20607
|
-
|
|
20608
|
-
|
|
20403
|
+
}
|
|
20404
|
+
|
|
20405
|
+
.navi_text[data-bold-transition] {
|
|
20406
|
+
.navi_text_bold_foreground {
|
|
20407
|
+
transition-property: font-weight;
|
|
20408
|
+
transition-duration: 0.3s;
|
|
20409
|
+
transition-timing-function: ease;
|
|
20609
20410
|
}
|
|
20610
20411
|
|
|
20611
|
-
|
|
20612
|
-
|
|
20613
|
-
|
|
20614
|
-
|
|
20615
|
-
|
|
20412
|
+
.navi_text_bold_background {
|
|
20413
|
+
transition-property: opacity;
|
|
20414
|
+
transition-duration: 0.3s;
|
|
20415
|
+
transition-timing-function: ease;
|
|
20416
|
+
}
|
|
20417
|
+
}
|
|
20418
|
+
`;
|
|
20419
|
+
const REGULAR_SPACE = jsx("span", {
|
|
20420
|
+
"data-navi-space": "",
|
|
20421
|
+
children: " "
|
|
20422
|
+
});
|
|
20423
|
+
// A space that uses padding-left instead of a real space character.
|
|
20424
|
+
// This avoids the underline that browsers draw under spaces inside links.
|
|
20425
|
+
const FAKE_SPACE = jsx("span", {
|
|
20426
|
+
"data-navi-space": "",
|
|
20427
|
+
style: "padding-left: 0.25em",
|
|
20428
|
+
children: "\u200B"
|
|
20429
|
+
});
|
|
20430
|
+
const CustomWidthSpace = ({
|
|
20431
|
+
value,
|
|
20432
|
+
useRealSpaceChar
|
|
20433
|
+
}) => {
|
|
20434
|
+
if (useRealSpaceChar) {
|
|
20435
|
+
return jsxs("span", {
|
|
20436
|
+
children: [jsx("span", {
|
|
20437
|
+
style: "font-size: 0",
|
|
20438
|
+
children: " "
|
|
20439
|
+
}), jsx("span", {
|
|
20440
|
+
style: `padding-left: ${value}`,
|
|
20441
|
+
children: "\u200B"
|
|
20442
|
+
})]
|
|
20443
|
+
});
|
|
20444
|
+
}
|
|
20445
|
+
return jsx("span", {
|
|
20446
|
+
style: `padding-left: ${value}`,
|
|
20447
|
+
children: "\u200B"
|
|
20448
|
+
});
|
|
20449
|
+
};
|
|
20450
|
+
const applySpacingOnTextChildren = (children, spacing, defaultSpace) => {
|
|
20451
|
+
if (spacing === "pre" || spacing === "0" || spacing === 0) {
|
|
20452
|
+
return children;
|
|
20453
|
+
}
|
|
20454
|
+
if (!children) {
|
|
20455
|
+
return children;
|
|
20456
|
+
}
|
|
20457
|
+
const childArray = toChildArray(children);
|
|
20458
|
+
const childCount = childArray.length;
|
|
20459
|
+
if (childCount <= 1) {
|
|
20460
|
+
return children;
|
|
20461
|
+
}
|
|
20462
|
+
const useRealSpaceChar = defaultSpace !== FAKE_SPACE;
|
|
20463
|
+
let separator;
|
|
20464
|
+
if (spacing === REGULAR_SPACE || spacing === FAKE_SPACE) {
|
|
20465
|
+
separator = defaultSpace;
|
|
20466
|
+
} else if (typeof spacing === "string") {
|
|
20467
|
+
if (isSizeSpacingScaleKey(spacing) || hasCSSSizeUnit(spacing)) {
|
|
20468
|
+
separator = jsx(CustomWidthSpace, {
|
|
20469
|
+
value: resolveSpacingSize(spacing),
|
|
20470
|
+
useRealSpaceChar: useRealSpaceChar
|
|
20471
|
+
});
|
|
20616
20472
|
} else {
|
|
20617
|
-
|
|
20618
|
-
if (containsPlatformSpecificKeys(key)) {
|
|
20619
|
-
crossPlatformCombination = generateCrossPlatformCombination(key);
|
|
20620
|
-
}
|
|
20473
|
+
separator = spacing;
|
|
20621
20474
|
}
|
|
20622
|
-
|
|
20623
|
-
|
|
20624
|
-
|
|
20625
|
-
|
|
20626
|
-
|
|
20627
|
-
|
|
20628
|
-
|
|
20629
|
-
|
|
20630
|
-
|
|
20631
|
-
|
|
20632
|
-
|
|
20633
|
-
|
|
20634
|
-
|
|
20635
|
-
|
|
20636
|
-
|
|
20475
|
+
} else if (typeof spacing === "number") {
|
|
20476
|
+
separator = jsx(CustomWidthSpace, {
|
|
20477
|
+
value: `${spacing}px`,
|
|
20478
|
+
useRealSpaceChar: useRealSpaceChar
|
|
20479
|
+
});
|
|
20480
|
+
} else {
|
|
20481
|
+
separator = spacing;
|
|
20482
|
+
}
|
|
20483
|
+
const childrenWithGap = [];
|
|
20484
|
+
let i = 0;
|
|
20485
|
+
while (true) {
|
|
20486
|
+
const child = childArray[i];
|
|
20487
|
+
childrenWithGap.push(child);
|
|
20488
|
+
i++;
|
|
20489
|
+
if (i === childCount) {
|
|
20490
|
+
break;
|
|
20637
20491
|
}
|
|
20638
|
-
|
|
20492
|
+
const currentChild = childArray[i - 1];
|
|
20493
|
+
const nextChild = childArray[i];
|
|
20494
|
+
if (!shouldInjectSpacingBetween(currentChild, nextChild)) {
|
|
20639
20495
|
continue;
|
|
20640
20496
|
}
|
|
20641
|
-
|
|
20642
|
-
if (returnValue) {
|
|
20643
|
-
keyboardEvent.preventDefault();
|
|
20644
|
-
}
|
|
20645
|
-
return shortcutCandidate;
|
|
20497
|
+
childrenWithGap.push(separator);
|
|
20646
20498
|
}
|
|
20647
|
-
return
|
|
20499
|
+
return childrenWithGap;
|
|
20648
20500
|
};
|
|
20649
|
-
const
|
|
20650
|
-
|
|
20651
|
-
|
|
20652
|
-
|
|
20653
|
-
return macSpecificKeys.some((key) => lowerCombination.includes(key));
|
|
20501
|
+
const outsideTextFlowSet = new Set();
|
|
20502
|
+
const markAsOutsideTextFlow = jsxElement => {
|
|
20503
|
+
outsideTextFlowSet.add(jsxElement);
|
|
20654
20504
|
};
|
|
20655
|
-
const
|
|
20656
|
-
|
|
20505
|
+
const isMarkedAsOutsideTextFlow = jsxElement => {
|
|
20506
|
+
return outsideTextFlowSet.has(jsxElement.type);
|
|
20507
|
+
};
|
|
20508
|
+
const isPreactNode = jsxChild => {
|
|
20509
|
+
return jsxChild !== null && typeof jsxChild === "object" && jsxChild.type !== undefined;
|
|
20510
|
+
};
|
|
20511
|
+
const shouldInjectSpacingBetween = (left, right) => {
|
|
20512
|
+
const leftIsNode = isPreactNode(left);
|
|
20513
|
+
const rightIsNode = isPreactNode(right);
|
|
20514
|
+
// only inject spacing when at least one side is a preact node
|
|
20515
|
+
if (!leftIsNode && !rightIsNode) {
|
|
20516
|
+
return false;
|
|
20517
|
+
}
|
|
20518
|
+
if (leftIsNode && isMarkedAsOutsideTextFlow(left)) {
|
|
20519
|
+
return false;
|
|
20520
|
+
}
|
|
20521
|
+
if (rightIsNode && isMarkedAsOutsideTextFlow(right)) {
|
|
20522
|
+
return false;
|
|
20523
|
+
}
|
|
20524
|
+
if (rightIsNode && right.props && right.props.overflowPinned) {
|
|
20525
|
+
return false;
|
|
20526
|
+
}
|
|
20527
|
+
if (typeof left === "string" && /\s$/.test(left)) {
|
|
20528
|
+
return false;
|
|
20529
|
+
}
|
|
20530
|
+
if (typeof right === "string" && /^\s/.test(right)) {
|
|
20531
|
+
return false;
|
|
20532
|
+
}
|
|
20533
|
+
return true;
|
|
20534
|
+
};
|
|
20535
|
+
const OverflowPinnedElementContext = createContext(null);
|
|
20536
|
+
const Text = props => {
|
|
20537
|
+
import.meta.css = [css$6, "@jsenv/navi/src/text/text.jsx"];
|
|
20538
|
+
if (props.loading || props.skeleton) {
|
|
20539
|
+
return jsx(TextSkeleton, {
|
|
20540
|
+
...props
|
|
20541
|
+
});
|
|
20542
|
+
}
|
|
20543
|
+
if (props.overflowEllipsis) {
|
|
20544
|
+
return jsx(TextOverflow, {
|
|
20545
|
+
...props
|
|
20546
|
+
});
|
|
20547
|
+
}
|
|
20548
|
+
if (props.overflowPinned) {
|
|
20549
|
+
return jsx(TextOverflowPinned, {
|
|
20550
|
+
...props
|
|
20551
|
+
});
|
|
20552
|
+
}
|
|
20553
|
+
if (props.selectRange) {
|
|
20554
|
+
return jsx(TextWithSelectRange, {
|
|
20555
|
+
...props
|
|
20556
|
+
});
|
|
20557
|
+
}
|
|
20558
|
+
return jsx(TextBasic, {
|
|
20559
|
+
...props
|
|
20560
|
+
});
|
|
20561
|
+
};
|
|
20562
|
+
const TextSkeleton = ({
|
|
20563
|
+
loading,
|
|
20564
|
+
children,
|
|
20565
|
+
...props
|
|
20566
|
+
}) => {
|
|
20567
|
+
// Three-level structure — see CSS comment on [data-skeleton] for details.
|
|
20568
|
+
const skeletonOverlay = jsx("span", {
|
|
20569
|
+
className: "navi_text_skeleton_container",
|
|
20570
|
+
"aria-hidden": "true",
|
|
20571
|
+
children: jsx("span", {
|
|
20572
|
+
className: "navi_text_skeleton_inset",
|
|
20573
|
+
children: jsx("span", {
|
|
20574
|
+
className: "navi_text_skeleton"
|
|
20575
|
+
})
|
|
20576
|
+
})
|
|
20577
|
+
});
|
|
20578
|
+
// When there are no children, inject a full-width placeholder so the element
|
|
20579
|
+
// has measurable height driven by the current font-size/line-height, and the
|
|
20580
|
+
// skeleton fills the available width instead of shrinking to a single char.
|
|
20581
|
+
const hasChildren = children !== null && children !== undefined && children !== false;
|
|
20582
|
+
const innerChildren = hasChildren ? children : jsx("span", {
|
|
20583
|
+
className: "navi_text_skeleton_children_placeholder",
|
|
20584
|
+
"aria-hidden": "true",
|
|
20585
|
+
children: "W"
|
|
20586
|
+
});
|
|
20587
|
+
return jsx(Text, {
|
|
20588
|
+
"data-skeleton": "",
|
|
20589
|
+
"data-loading": loading ? "" : undefined,
|
|
20590
|
+
...props,
|
|
20591
|
+
skeleton: undefined,
|
|
20592
|
+
childrenOutsideFlow: skeletonOverlay,
|
|
20593
|
+
children: innerChildren
|
|
20594
|
+
});
|
|
20595
|
+
};
|
|
20596
|
+
const TextOverflow = ({
|
|
20597
|
+
noWrap,
|
|
20598
|
+
spacing,
|
|
20599
|
+
children,
|
|
20600
|
+
...rest
|
|
20601
|
+
}) => {
|
|
20602
|
+
const [OverflowPinnedElement, setOverflowPinnedElement] = useState(null);
|
|
20603
|
+
return jsx(Text, {
|
|
20604
|
+
flex: true,
|
|
20605
|
+
block: true,
|
|
20606
|
+
as: "div",
|
|
20607
|
+
nowWrap: noWrap,
|
|
20608
|
+
pre: !noWrap
|
|
20609
|
+
// For paragraph we prefer to keep lines and only hide unbreakable long sections
|
|
20610
|
+
,
|
|
20657
20611
|
|
|
20658
|
-
|
|
20659
|
-
|
|
20612
|
+
preLine: rest.as === "p",
|
|
20613
|
+
...rest,
|
|
20614
|
+
overflowEllipsis: undefined,
|
|
20615
|
+
"data-text-overflow": "",
|
|
20616
|
+
spacing: "pre",
|
|
20617
|
+
children: jsxs("span", {
|
|
20618
|
+
className: "navi_text_overflow_wrapper",
|
|
20619
|
+
children: [jsx(OverflowPinnedElementContext.Provider, {
|
|
20620
|
+
value: setOverflowPinnedElement,
|
|
20621
|
+
children: jsx(Text, {
|
|
20622
|
+
className: "navi_text_overflow_text",
|
|
20623
|
+
spacing: spacing,
|
|
20624
|
+
children: children
|
|
20625
|
+
})
|
|
20626
|
+
}), OverflowPinnedElement]
|
|
20627
|
+
})
|
|
20628
|
+
});
|
|
20629
|
+
};
|
|
20630
|
+
const TextOverflowPinned = ({
|
|
20631
|
+
overflowPinned,
|
|
20632
|
+
...props
|
|
20633
|
+
}) => {
|
|
20634
|
+
const setOverflowPinnedElement = useContext(OverflowPinnedElementContext);
|
|
20635
|
+
const text = jsx(Text, {
|
|
20636
|
+
...props,
|
|
20637
|
+
"data-overflow-pinned": ""
|
|
20638
|
+
});
|
|
20639
|
+
if (!setOverflowPinnedElement) {
|
|
20640
|
+
console.warn("<Text overflowPinned> declared outside a <Text overflowEllipsis>");
|
|
20641
|
+
return text;
|
|
20642
|
+
}
|
|
20643
|
+
if (overflowPinned) {
|
|
20644
|
+
setOverflowPinnedElement(text);
|
|
20660
20645
|
return null;
|
|
20661
20646
|
}
|
|
20662
|
-
|
|
20663
|
-
|
|
20664
|
-
crossPlatform = crossPlatform.replace(/\bcmd\b/gi, "control");
|
|
20665
|
-
|
|
20666
|
-
return crossPlatform;
|
|
20647
|
+
setOverflowPinnedElement(null);
|
|
20648
|
+
return text;
|
|
20667
20649
|
};
|
|
20668
|
-
const
|
|
20669
|
-
|
|
20670
|
-
|
|
20671
|
-
|
|
20672
|
-
|
|
20673
|
-
|
|
20674
|
-
|
|
20675
|
-
|
|
20676
|
-
|
|
20650
|
+
const TextWithSelectRange = ({
|
|
20651
|
+
selectRange,
|
|
20652
|
+
...props
|
|
20653
|
+
}) => {
|
|
20654
|
+
const defaultRef = useRef();
|
|
20655
|
+
const ref = props.ref || defaultRef;
|
|
20656
|
+
useInitialTextSelection(ref, selectRange);
|
|
20657
|
+
return jsx(Text, {
|
|
20658
|
+
ref: ref,
|
|
20659
|
+
...props
|
|
20660
|
+
});
|
|
20661
|
+
};
|
|
20662
|
+
const TextBasic = ({
|
|
20663
|
+
spacing,
|
|
20664
|
+
preventSpaceUnderlines = false,
|
|
20665
|
+
boldTransition,
|
|
20666
|
+
boldStable,
|
|
20667
|
+
preventBoldLayoutShift = boldTransition,
|
|
20668
|
+
capitalize,
|
|
20669
|
+
children,
|
|
20670
|
+
childrenOutsideFlow,
|
|
20671
|
+
...rest
|
|
20672
|
+
}) => {
|
|
20673
|
+
const defaultSpace = preventSpaceUnderlines ? FAKE_SPACE : REGULAR_SPACE;
|
|
20674
|
+
const resolvedSpacing = spacing ?? defaultSpace;
|
|
20675
|
+
const boxProps = {
|
|
20676
|
+
"as": "span",
|
|
20677
|
+
"data-bold-transition": boldTransition ? "" : undefined,
|
|
20678
|
+
"data-capitalize": capitalize ? "" : undefined,
|
|
20679
|
+
...rest,
|
|
20680
|
+
"baseClassName": withPropsClassName("navi_text", rest.baseClassName)
|
|
20681
|
+
};
|
|
20682
|
+
const shouldPreserveSpacing = rest.as === "pre" || rest.flex || rest.grid;
|
|
20683
|
+
if (shouldPreserveSpacing) {
|
|
20684
|
+
boxProps.spacing = resolvedSpacing;
|
|
20685
|
+
} else {
|
|
20686
|
+
children = applySpacingOnTextChildren(children, resolvedSpacing, defaultSpace);
|
|
20687
|
+
}
|
|
20688
|
+
if (boldStable) {
|
|
20689
|
+
const {
|
|
20690
|
+
bold
|
|
20691
|
+
} = boxProps;
|
|
20692
|
+
return jsxs(Box, {
|
|
20693
|
+
...boxProps,
|
|
20694
|
+
bold: undefined,
|
|
20695
|
+
"data-bold": bold ? "" : undefined,
|
|
20696
|
+
children: [jsx("span", {
|
|
20697
|
+
className: "navi_text_bold_background",
|
|
20698
|
+
"aria-hidden": "true",
|
|
20699
|
+
children: children
|
|
20700
|
+
}), children, childrenOutsideFlow]
|
|
20701
|
+
});
|
|
20702
|
+
}
|
|
20703
|
+
if (preventBoldLayoutShift) {
|
|
20704
|
+
const alignX = rest.alignX || rest.align || "start";
|
|
20677
20705
|
|
|
20678
|
-
|
|
20679
|
-
|
|
20680
|
-
|
|
20681
|
-
|
|
20706
|
+
// La technique consiste a avoid un double gras qui force une taille
|
|
20707
|
+
// et la version light par dessus en position absolute
|
|
20708
|
+
// on la centre aussi pour donner l'impression que le gras s'applique depuis le centre
|
|
20709
|
+
// ne fonctionne que sur une seule ligne de texte (donc lorsque noWrap est actif)
|
|
20710
|
+
// on pourrait auto-active cela sur une prop genre boldCanChange
|
|
20711
|
+
return jsxs(Box, {
|
|
20712
|
+
...boxProps,
|
|
20713
|
+
children: [jsxs("span", {
|
|
20714
|
+
className: "navi_text_bold_wrapper",
|
|
20715
|
+
children: [jsx("span", {
|
|
20716
|
+
className: "navi_text_bold_clone",
|
|
20717
|
+
"aria-hidden": "true",
|
|
20718
|
+
children: children
|
|
20719
|
+
}), jsx("span", {
|
|
20720
|
+
className: "navi_text_bold_foreground",
|
|
20721
|
+
"data-align": alignX,
|
|
20722
|
+
children: children
|
|
20723
|
+
})]
|
|
20724
|
+
}), childrenOutsideFlow]
|
|
20725
|
+
});
|
|
20726
|
+
}
|
|
20727
|
+
return jsxs(Box, {
|
|
20728
|
+
...boxProps,
|
|
20729
|
+
children: [children, childrenOutsideFlow]
|
|
20730
|
+
});
|
|
20731
|
+
};
|
|
20682
20732
|
|
|
20683
|
-
|
|
20684
|
-
|
|
20685
|
-
|
|
20686
|
-
|
|
20687
|
-
|
|
20688
|
-
|
|
20689
|
-
|
|
20690
|
-
|
|
20691
|
-
}
|
|
20692
|
-
if (modifierFound) {
|
|
20693
|
-
continue;
|
|
20733
|
+
installImportMetaCssBuild(import.meta);const css$5 = /* css */`
|
|
20734
|
+
@layer navi {
|
|
20735
|
+
/* Ensure data attributes from box.jsx can win to update display */
|
|
20736
|
+
.navi_icon {
|
|
20737
|
+
display: inline-block;
|
|
20738
|
+
box-sizing: border-box;
|
|
20739
|
+
max-width: 100%;
|
|
20740
|
+
max-height: 100%;
|
|
20694
20741
|
}
|
|
20742
|
+
}
|
|
20695
20743
|
|
|
20696
|
-
|
|
20697
|
-
|
|
20698
|
-
|
|
20699
|
-
if (dash === "-") {
|
|
20700
|
-
// Only check ranges for single alphanumeric characters
|
|
20701
|
-
const eventKey = event.key.toLowerCase();
|
|
20702
|
-
if (eventKey.length !== 1) {
|
|
20703
|
-
return false; // Not a single character key
|
|
20704
|
-
}
|
|
20705
|
-
|
|
20706
|
-
// Only allow a-z and 0-9 ranges
|
|
20707
|
-
const isValidRange =
|
|
20708
|
-
(startChar >= "a" && endChar <= "z") ||
|
|
20709
|
-
(startChar >= "0" && endChar <= "9");
|
|
20710
|
-
|
|
20711
|
-
if (!isValidRange) {
|
|
20712
|
-
return false; // Invalid range pattern
|
|
20713
|
-
}
|
|
20744
|
+
.navi_icon {
|
|
20745
|
+
white-space: nowrap;
|
|
20746
|
+
vertical-align: inherit;
|
|
20714
20747
|
|
|
20715
|
-
|
|
20716
|
-
|
|
20717
|
-
|
|
20748
|
+
&[data-flow-inline] {
|
|
20749
|
+
width: 1em;
|
|
20750
|
+
height: 1em;
|
|
20751
|
+
}
|
|
20752
|
+
&[data-icon-char] {
|
|
20753
|
+
flex-grow: 0 !important;
|
|
20718
20754
|
|
|
20719
|
-
|
|
20720
|
-
|
|
20721
|
-
|
|
20722
|
-
|
|
20755
|
+
svg,
|
|
20756
|
+
img {
|
|
20757
|
+
width: 100%;
|
|
20758
|
+
height: 100%;
|
|
20759
|
+
}
|
|
20760
|
+
svg {
|
|
20761
|
+
overflow: visible;
|
|
20723
20762
|
}
|
|
20724
20763
|
}
|
|
20725
|
-
|
|
20726
|
-
|
|
20727
|
-
if (!isSameKey(event.key, key)) {
|
|
20728
|
-
return false;
|
|
20764
|
+
&[data-interactive] {
|
|
20765
|
+
cursor: pointer;
|
|
20729
20766
|
}
|
|
20730
20767
|
}
|
|
20731
|
-
return true;
|
|
20732
|
-
};
|
|
20733
|
-
// Configuration for mapping shortcut key names to browser event properties
|
|
20734
|
-
const modifierKeyMapping = {
|
|
20735
|
-
metaKey: {
|
|
20736
|
-
names: ["meta"],
|
|
20737
|
-
macNames: ["command", "cmd"],
|
|
20738
|
-
},
|
|
20739
|
-
ctrlKey: {
|
|
20740
|
-
names: ["control", "ctrl"],
|
|
20741
|
-
},
|
|
20742
|
-
shiftKey: {
|
|
20743
|
-
names: ["shift"],
|
|
20744
|
-
},
|
|
20745
|
-
altKey: {
|
|
20746
|
-
names: ["alt"],
|
|
20747
|
-
macNames: ["option"],
|
|
20748
|
-
},
|
|
20749
|
-
};
|
|
20750
|
-
const isSameKey = (browserEventKey, key) => {
|
|
20751
|
-
browserEventKey = browserEventKey.toLowerCase();
|
|
20752
|
-
key = key.toLowerCase();
|
|
20753
20768
|
|
|
20754
|
-
|
|
20755
|
-
|
|
20769
|
+
.navi_icon_char_slot {
|
|
20770
|
+
opacity: 0;
|
|
20771
|
+
cursor: default;
|
|
20772
|
+
user-select: none;
|
|
20756
20773
|
}
|
|
20774
|
+
.navi_text.navi_icon_foreground {
|
|
20775
|
+
position: absolute;
|
|
20776
|
+
inset: 0;
|
|
20777
|
+
display: inline-flex;
|
|
20757
20778
|
|
|
20758
|
-
|
|
20759
|
-
|
|
20760
|
-
|
|
20761
|
-
|
|
20762
|
-
|
|
20779
|
+
& > .navi_text {
|
|
20780
|
+
display: flex;
|
|
20781
|
+
aspect-ratio: 1 / 1;
|
|
20782
|
+
min-width: 0;
|
|
20783
|
+
height: 100%;
|
|
20784
|
+
max-height: 1em;
|
|
20785
|
+
align-items: center;
|
|
20786
|
+
justify-content: center;
|
|
20763
20787
|
}
|
|
20764
20788
|
}
|
|
20765
20789
|
|
|
20766
|
-
|
|
20790
|
+
.navi_icon > svg,
|
|
20791
|
+
.navi_icon > img {
|
|
20792
|
+
width: 100%;
|
|
20793
|
+
height: 100%;
|
|
20794
|
+
backface-visibility: hidden;
|
|
20795
|
+
}
|
|
20796
|
+
.navi_icon[data-width-fixed] > svg,
|
|
20797
|
+
.navi_icon[data-width-fixed] > img {
|
|
20798
|
+
width: 100%;
|
|
20799
|
+
height: auto;
|
|
20800
|
+
}
|
|
20801
|
+
.navi_icon[data-height-fixed] > svg,
|
|
20802
|
+
.navi_icon[data-height-fixed] > img {
|
|
20803
|
+
width: auto;
|
|
20804
|
+
height: 100%;
|
|
20805
|
+
}
|
|
20806
|
+
.navi_icon[data-width-fixed][data-height-fixed] > svg,
|
|
20807
|
+
.navi_icon[data-width-fixed][data-height-fixed] > img {
|
|
20808
|
+
width: 100%;
|
|
20809
|
+
height: 100%;
|
|
20810
|
+
}
|
|
20811
|
+
`;
|
|
20812
|
+
const Icon = ({
|
|
20813
|
+
href,
|
|
20814
|
+
children,
|
|
20815
|
+
charWidth = 1,
|
|
20816
|
+
// 0 (zéro) is the real char width
|
|
20817
|
+
// but 2 zéros gives too big icons
|
|
20818
|
+
// while 1 "W" gives a nice result
|
|
20819
|
+
baseChar = "W",
|
|
20820
|
+
decorative,
|
|
20821
|
+
onClick,
|
|
20822
|
+
...props
|
|
20823
|
+
}) => {
|
|
20824
|
+
import.meta.css = [css$5, "@jsenv/navi/src/text/icon.jsx"];
|
|
20825
|
+
const innerChildren = href ? jsx("svg", {
|
|
20826
|
+
width: "100%",
|
|
20827
|
+
height: "100%",
|
|
20828
|
+
children: jsx("use", {
|
|
20829
|
+
href: href
|
|
20830
|
+
})
|
|
20831
|
+
}) : children;
|
|
20832
|
+
let {
|
|
20833
|
+
flex,
|
|
20834
|
+
grid,
|
|
20835
|
+
width,
|
|
20836
|
+
height
|
|
20837
|
+
} = props;
|
|
20838
|
+
if (width === "auto") {
|
|
20839
|
+
width = undefined;
|
|
20840
|
+
}
|
|
20841
|
+
if (height === "auto") {
|
|
20842
|
+
height = undefined;
|
|
20843
|
+
}
|
|
20844
|
+
const hasExplicitWidth = width !== undefined;
|
|
20845
|
+
const hasExplicitHeight = height !== undefined;
|
|
20846
|
+
const widthFixed = hasExplicitWidth || hasExplicitHeight && (props.square || props.circle || props.aspectRatio);
|
|
20847
|
+
const heightFixed = hasExplicitHeight || hasExplicitWidth && (props.square || props.circle || props.aspectRatio);
|
|
20848
|
+
if (widthFixed || heightFixed) {
|
|
20849
|
+
if (flex === undefined) {
|
|
20850
|
+
flex = "x";
|
|
20851
|
+
}
|
|
20852
|
+
} else if (decorative === undefined && !onClick) {
|
|
20853
|
+
decorative = true;
|
|
20854
|
+
}
|
|
20855
|
+
const ariaProps = decorative ? {
|
|
20856
|
+
"aria-hidden": "true"
|
|
20857
|
+
} : {};
|
|
20858
|
+
const textRef = useRef();
|
|
20859
|
+
if (typeof children === "string") {
|
|
20860
|
+
return jsx(Text, {
|
|
20861
|
+
...props,
|
|
20862
|
+
...ariaProps,
|
|
20863
|
+
"data-icon-text": "",
|
|
20864
|
+
children: children
|
|
20865
|
+
});
|
|
20866
|
+
}
|
|
20867
|
+
if (flex || grid) {
|
|
20868
|
+
return jsx(Box, {
|
|
20869
|
+
square: true,
|
|
20870
|
+
...props,
|
|
20871
|
+
...ariaProps,
|
|
20872
|
+
flex: flex,
|
|
20873
|
+
baseClassName: "navi_icon",
|
|
20874
|
+
"data-width-fixed": widthFixed ? "" : undefined,
|
|
20875
|
+
"data-height-fixed": heightFixed ? "" : undefined,
|
|
20876
|
+
"data-interactive": onClick ? "" : undefined,
|
|
20877
|
+
onClick: onClick,
|
|
20878
|
+
children: innerChildren
|
|
20879
|
+
});
|
|
20880
|
+
}
|
|
20881
|
+
const invisibleText = baseChar.repeat(charWidth);
|
|
20882
|
+
return jsx(SurroundingTextAligner, {
|
|
20883
|
+
align: "center",
|
|
20884
|
+
childRef: textRef,
|
|
20885
|
+
size: props.size,
|
|
20886
|
+
textSize: props.textSize,
|
|
20887
|
+
children: jsxs(Text, {
|
|
20888
|
+
...props,
|
|
20889
|
+
...ariaProps,
|
|
20890
|
+
className: withPropsClassName("navi_icon", props.className),
|
|
20891
|
+
spacing: "pre",
|
|
20892
|
+
"data-icon-char": "",
|
|
20893
|
+
"data-width-fixed": widthFixed ? "" : undefined,
|
|
20894
|
+
"data-height-fixed": heightFixed ? "" : undefined,
|
|
20895
|
+
"data-interactive": onClick ? "" : undefined,
|
|
20896
|
+
onClick: onClick,
|
|
20897
|
+
ref: textRef,
|
|
20898
|
+
children: [jsx("span", {
|
|
20899
|
+
className: "navi_icon_char_slot",
|
|
20900
|
+
"aria-hidden": "true",
|
|
20901
|
+
children: invisibleText
|
|
20902
|
+
}), jsx(Text, {
|
|
20903
|
+
className: "navi_icon_foreground",
|
|
20904
|
+
spacing: "pre",
|
|
20905
|
+
children: innerChildren
|
|
20906
|
+
})]
|
|
20907
|
+
})
|
|
20908
|
+
});
|
|
20767
20909
|
};
|
|
20768
20910
|
|
|
20769
20911
|
const useFormEvents = (
|
|
@@ -21287,7 +21429,7 @@ const useUIState = (uiStateController) => {
|
|
|
21287
21429
|
return trackedUIState;
|
|
21288
21430
|
};
|
|
21289
21431
|
|
|
21290
|
-
installImportMetaCssBuild(import.meta);
|
|
21432
|
+
installImportMetaCssBuild(import.meta);const css$4 = /* css */`
|
|
21291
21433
|
@layer navi {
|
|
21292
21434
|
.navi_button {
|
|
21293
21435
|
--button-outline-width: 1px;
|
|
@@ -21413,6 +21555,7 @@ installImportMetaCssBuild(import.meta);import.meta.css = [/* css */`
|
|
|
21413
21555
|
align-items: inherit;
|
|
21414
21556
|
justify-content: inherit;
|
|
21415
21557
|
color: var(--x-button-color);
|
|
21558
|
+
vertical-align: inherit;
|
|
21416
21559
|
background: var(--x-button-background);
|
|
21417
21560
|
background-color: var(
|
|
21418
21561
|
--x-button-background-color,
|
|
@@ -21536,8 +21679,9 @@ installImportMetaCssBuild(import.meta);import.meta.css = [/* css */`
|
|
|
21536
21679
|
--x-button-border-color: var(--callout-color);
|
|
21537
21680
|
}
|
|
21538
21681
|
}
|
|
21539
|
-
|
|
21682
|
+
`;
|
|
21540
21683
|
const Button = props => {
|
|
21684
|
+
import.meta.css = [css$4, "@jsenv/navi/src/field/button.jsx"];
|
|
21541
21685
|
return renderActionableComponent(props, {
|
|
21542
21686
|
Basic: ButtonBasic,
|
|
21543
21687
|
WithAction: ButtonWithAction,
|
|
@@ -21595,6 +21739,7 @@ const ButtonBasic = props => {
|
|
|
21595
21739
|
icon,
|
|
21596
21740
|
revealOnInteraction = icon,
|
|
21597
21741
|
discrete = icon && !revealOnInteraction,
|
|
21742
|
+
spacing,
|
|
21598
21743
|
children,
|
|
21599
21744
|
...rest
|
|
21600
21745
|
} = props;
|
|
@@ -21608,11 +21753,12 @@ const ButtonBasic = props => {
|
|
|
21608
21753
|
const renderButtonContent = buttonProps => {
|
|
21609
21754
|
return jsxs(Text, {
|
|
21610
21755
|
...buttonProps,
|
|
21756
|
+
spacing: spacing,
|
|
21611
21757
|
className: "navi_button_content",
|
|
21612
21758
|
children: [children, jsx(ButtonShadow, {})]
|
|
21613
21759
|
});
|
|
21614
21760
|
};
|
|
21615
|
-
const renderButtonContentMemoized = useCallback(renderButtonContent, [children]);
|
|
21761
|
+
const renderButtonContentMemoized = useCallback(renderButtonContent, [children, spacing]);
|
|
21616
21762
|
return jsxs(Box, {
|
|
21617
21763
|
"data-readonly-silent": innerLoading ? "" : undefined,
|
|
21618
21764
|
...remainingProps,
|
|
@@ -22100,7 +22246,7 @@ const useDimColorWhen = (elementRef, shouldDim) => {
|
|
|
22100
22246
|
};
|
|
22101
22247
|
|
|
22102
22248
|
installImportMetaCssBuild(import.meta);/* eslint-disable jsenv/no-unknown-params */
|
|
22103
|
-
|
|
22249
|
+
const css$3 = /* css */`
|
|
22104
22250
|
@layer navi {
|
|
22105
22251
|
.navi_link {
|
|
22106
22252
|
--link-border-radius: unset;
|
|
@@ -22372,7 +22518,7 @@ import.meta.css = [/* css */`
|
|
|
22372
22518
|
.navi_title .navi_link[data-reveal-on-interaction] {
|
|
22373
22519
|
top: 0.25em;
|
|
22374
22520
|
}
|
|
22375
|
-
|
|
22521
|
+
`;
|
|
22376
22522
|
const LinkStyleCSSVars = {
|
|
22377
22523
|
"outlineColor": "--link-outline-color",
|
|
22378
22524
|
"borderRadius": "--link-border-radius",
|
|
@@ -22421,6 +22567,7 @@ Object.assign(PSEUDO_CLASSES, {
|
|
|
22421
22567
|
}
|
|
22422
22568
|
});
|
|
22423
22569
|
const Link = props => {
|
|
22570
|
+
import.meta.css = [css$3, "@jsenv/navi/src/nav/link/link.jsx"];
|
|
22424
22571
|
return renderActionableComponent(props, {
|
|
22425
22572
|
Basic: LinkBasic,
|
|
22426
22573
|
WithAction: LinkWithAction
|
|
@@ -22569,6 +22716,7 @@ const LinkPlain = props => {
|
|
|
22569
22716
|
e.detail.setValue(value);
|
|
22570
22717
|
},
|
|
22571
22718
|
preventBoldLayoutShift: currentEffectBold,
|
|
22719
|
+
preventSpaceUnderlines: true,
|
|
22572
22720
|
overflowEllipsis: overflowEllipsis
|
|
22573
22721
|
// Visual
|
|
22574
22722
|
,
|
|
@@ -30403,6 +30551,8 @@ installImportMetaCssBuild(import.meta);const css = /* css */`
|
|
|
30403
30551
|
@layer navi {
|
|
30404
30552
|
}
|
|
30405
30553
|
.navi_text.navi_badge_count {
|
|
30554
|
+
/* Important to prevent anchor from breaking to a new line */
|
|
30555
|
+
white-space: nowrap;
|
|
30406
30556
|
--font-size: 0.7em;
|
|
30407
30557
|
--x-background: var(--background);
|
|
30408
30558
|
--x-background-color: var(--background-color, var(--x-background));
|
|
@@ -30411,9 +30561,9 @@ installImportMetaCssBuild(import.meta);const css = /* css */`
|
|
|
30411
30561
|
--padding-x: 0.5em;
|
|
30412
30562
|
--padding-y: 0.2em;
|
|
30413
30563
|
position: relative;
|
|
30414
|
-
display: inline-block;
|
|
30415
30564
|
color: var(--x-color);
|
|
30416
30565
|
font-size: var(--font-size);
|
|
30566
|
+
vertical-align: inherit;
|
|
30417
30567
|
|
|
30418
30568
|
&[data-dark-background] {
|
|
30419
30569
|
--x-color-contrasting: var(--navi-color-white);
|
|
@@ -30443,11 +30593,12 @@ installImportMetaCssBuild(import.meta);const css = /* css */`
|
|
|
30443
30593
|
|
|
30444
30594
|
/* For ellipse + single char force the circle aspect as it's prettier */
|
|
30445
30595
|
&[data-single-char] {
|
|
30596
|
+
display: inline-block;
|
|
30446
30597
|
aspect-ratio: 1/1;
|
|
30447
|
-
height: 1.
|
|
30598
|
+
height: 1.6em;
|
|
30448
30599
|
padding: 0;
|
|
30449
30600
|
text-align: center;
|
|
30450
|
-
line-height: 1.
|
|
30601
|
+
line-height: 1.6em;
|
|
30451
30602
|
}
|
|
30452
30603
|
}
|
|
30453
30604
|
|
|
@@ -30467,19 +30618,19 @@ installImportMetaCssBuild(import.meta);const css = /* css */`
|
|
|
30467
30618
|
border-radius: 50%;
|
|
30468
30619
|
|
|
30469
30620
|
&[data-single-char] {
|
|
30470
|
-
--x-radius: 1.
|
|
30621
|
+
--x-radius: 1.6em;
|
|
30471
30622
|
--x-number-font-size: unset;
|
|
30472
30623
|
}
|
|
30473
30624
|
&[data-two-chars] {
|
|
30474
|
-
--x-radius:
|
|
30475
|
-
--x-number-font-size:
|
|
30625
|
+
--x-radius: 2em;
|
|
30626
|
+
--x-number-font-size: unset;
|
|
30476
30627
|
}
|
|
30477
30628
|
&[data-three-chars] {
|
|
30478
30629
|
--x-radius: 2.4em;
|
|
30479
30630
|
--x-number-font-size: 0.8em;
|
|
30480
30631
|
}
|
|
30481
30632
|
&[data-four-chars] {
|
|
30482
|
-
--x-radius: 2.
|
|
30633
|
+
--x-radius: 2.4em;
|
|
30483
30634
|
--x-number-font-size: 0.8em;
|
|
30484
30635
|
}
|
|
30485
30636
|
|
|
@@ -30513,6 +30664,7 @@ const BadgeCount = ({
|
|
|
30513
30664
|
// so that visually the interface do not suddently switch from circle to ellipse depending on the count
|
|
30514
30665
|
circle,
|
|
30515
30666
|
max = circle ? MAX_FOR_CIRCLE : Infinity,
|
|
30667
|
+
textSize,
|
|
30516
30668
|
integer,
|
|
30517
30669
|
lang,
|
|
30518
30670
|
loading,
|
|
@@ -30538,25 +30690,37 @@ const BadgeCount = ({
|
|
|
30538
30690
|
circle = false;
|
|
30539
30691
|
}
|
|
30540
30692
|
if (circle) {
|
|
30541
|
-
return
|
|
30542
|
-
|
|
30543
|
-
|
|
30544
|
-
|
|
30545
|
-
|
|
30546
|
-
|
|
30547
|
-
|
|
30693
|
+
return jsx(SurroundingTextAligner, {
|
|
30694
|
+
align: "center",
|
|
30695
|
+
childRef: ref,
|
|
30696
|
+
size: props.size,
|
|
30697
|
+
textSize: textSize,
|
|
30698
|
+
children: jsxs(BadgeCountCircle, {
|
|
30699
|
+
...props,
|
|
30700
|
+
loading: loading,
|
|
30701
|
+
ref: ref,
|
|
30702
|
+
hasOverflow: hasOverflow,
|
|
30703
|
+
charCount: charCount,
|
|
30704
|
+
children: [valueDisplayed, hasOverflow && maxElement]
|
|
30705
|
+
})
|
|
30548
30706
|
});
|
|
30549
30707
|
}
|
|
30550
30708
|
const valueFormatted = typeof valueDisplayed === "number" ? formatNumber(valueDisplayed, {
|
|
30551
30709
|
lang
|
|
30552
30710
|
}) : valueDisplayed;
|
|
30553
|
-
return
|
|
30554
|
-
|
|
30555
|
-
|
|
30556
|
-
|
|
30557
|
-
|
|
30558
|
-
|
|
30559
|
-
|
|
30711
|
+
return jsx(SurroundingTextAligner, {
|
|
30712
|
+
align: "center",
|
|
30713
|
+
childRef: ref,
|
|
30714
|
+
size: props.size,
|
|
30715
|
+
textSize: textSize,
|
|
30716
|
+
children: jsxs(BadgeCountEllipse, {
|
|
30717
|
+
...props,
|
|
30718
|
+
loading: loading,
|
|
30719
|
+
ref: ref,
|
|
30720
|
+
hasOverflow: hasOverflow,
|
|
30721
|
+
charCount: charCount,
|
|
30722
|
+
children: [valueFormatted, hasOverflow && maxElement]
|
|
30723
|
+
})
|
|
30560
30724
|
});
|
|
30561
30725
|
};
|
|
30562
30726
|
const applyMaxToValue = (max, value) => {
|
|
@@ -30597,14 +30761,8 @@ const BadgeCountEllipse = ({
|
|
|
30597
30761
|
spacing: "pre",
|
|
30598
30762
|
children: loading ? jsx(Icon, {
|
|
30599
30763
|
children: jsx(LoadingDots, {})
|
|
30600
|
-
}) :
|
|
30601
|
-
children:
|
|
30602
|
-
style: "user-select: none",
|
|
30603
|
-
children: "\u200B"
|
|
30604
|
-
}), children, jsx("span", {
|
|
30605
|
-
style: "user-select: none",
|
|
30606
|
-
children: "\u200B"
|
|
30607
|
-
})]
|
|
30764
|
+
}) : jsx(Fragment, {
|
|
30765
|
+
children: children
|
|
30608
30766
|
})
|
|
30609
30767
|
});
|
|
30610
30768
|
};
|
|
@@ -30633,17 +30791,11 @@ const BadgeCountCircle = ({
|
|
|
30633
30791
|
spacing: "pre",
|
|
30634
30792
|
children: loading ? jsx(Icon, {
|
|
30635
30793
|
children: jsx(LoadingDots, {})
|
|
30636
|
-
}) :
|
|
30637
|
-
children:
|
|
30638
|
-
style: "user-select: none",
|
|
30639
|
-
children: "\u200B"
|
|
30640
|
-
}), jsx("span", {
|
|
30794
|
+
}) : jsx(Fragment, {
|
|
30795
|
+
children: jsx("span", {
|
|
30641
30796
|
className: "navi_badge_count_text",
|
|
30642
30797
|
children: children
|
|
30643
|
-
})
|
|
30644
|
-
style: "user-select: none",
|
|
30645
|
-
children: "\u200B"
|
|
30646
|
-
})]
|
|
30798
|
+
})
|
|
30647
30799
|
})
|
|
30648
30800
|
});
|
|
30649
30801
|
};
|