@jsenv/navi 0.21.7 → 0.22.0
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 +2015 -1866
- package/dist/jsenv_navi.js.map +131 -95
- 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,2031 +18734,2174 @@ 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
|
-
/* user-select: none; */
|
|
18840
|
-
padding-left: 0.25em;
|
|
18841
|
-
}
|
|
18889
|
+
const updateNetworkSpeed = () => {
|
|
18890
|
+
networkSpeedSignal.value = getNetworkSpeed();
|
|
18891
|
+
};
|
|
18842
18892
|
|
|
18843
|
-
|
|
18844
|
-
position: relative;
|
|
18845
|
-
border-radius: var(--x-border-radius);
|
|
18893
|
+
const networkSpeedSignal = signal(getNetworkSpeed());
|
|
18846
18894
|
|
|
18847
|
-
|
|
18848
|
-
|
|
18849
|
-
&[data-capitalize] {
|
|
18850
|
-
&::first-letter {
|
|
18851
|
-
text-transform: uppercase;
|
|
18852
|
-
}
|
|
18853
|
-
.navi_text_bold_clone::first-letter {
|
|
18854
|
-
text-transform: uppercase;
|
|
18855
|
-
}
|
|
18856
|
-
.navi_text_bold_foreground::first-letter {
|
|
18857
|
-
text-transform: uppercase;
|
|
18858
|
-
}
|
|
18859
|
-
}
|
|
18895
|
+
const setupNetworkMonitoring = () => {
|
|
18896
|
+
const cleanupFunctions = [];
|
|
18860
18897
|
|
|
18861
|
-
|
|
18862
|
-
.navi_text_bold_clone,
|
|
18863
|
-
.navi_text_bold_foreground {
|
|
18864
|
-
display: inherit;
|
|
18865
|
-
width: inherit;
|
|
18866
|
-
min-width: inherit;
|
|
18867
|
-
height: inherit;
|
|
18868
|
-
min-height: inherit;
|
|
18869
|
-
flex-grow: inherit;
|
|
18870
|
-
align-items: inherit;
|
|
18871
|
-
justify-content: inherit;
|
|
18872
|
-
gap: inherit;
|
|
18873
|
-
text-align: inherit;
|
|
18874
|
-
border-radius: inherit;
|
|
18875
|
-
}
|
|
18898
|
+
// ✅ 1. Écouter les changements natifs
|
|
18876
18899
|
|
|
18877
|
-
|
|
18878
|
-
|
|
18879
|
-
|
|
18880
|
-
|
|
18881
|
-
|
|
18900
|
+
if (connection) {
|
|
18901
|
+
connection.addEventListener("change", updateNetworkSpeed);
|
|
18902
|
+
cleanupFunctions.push(() => {
|
|
18903
|
+
connection.removeEventListener("change", updateNetworkSpeed);
|
|
18904
|
+
});
|
|
18905
|
+
}
|
|
18882
18906
|
|
|
18883
|
-
|
|
18884
|
-
|
|
18885
|
-
|
|
18886
|
-
flex-grow: 1;
|
|
18887
|
-
gap: 0.3em;
|
|
18907
|
+
// ✅ 2. Polling de backup (toutes les 60 secondes)
|
|
18908
|
+
const pollInterval = setInterval(updateNetworkSpeed, 60000);
|
|
18909
|
+
cleanupFunctions.push(() => clearInterval(pollInterval));
|
|
18888
18910
|
|
|
18889
|
-
|
|
18890
|
-
|
|
18891
|
-
|
|
18892
|
-
|
|
18893
|
-
}
|
|
18894
|
-
}
|
|
18911
|
+
// ✅ 3. Vérifier lors de la reprise d'activité
|
|
18912
|
+
const handleVisibilityChange = () => {
|
|
18913
|
+
if (!document.hidden) {
|
|
18914
|
+
updateNetworkSpeed();
|
|
18895
18915
|
}
|
|
18916
|
+
};
|
|
18896
18917
|
|
|
18897
|
-
|
|
18898
|
-
|
|
18899
|
-
|
|
18900
|
-
|
|
18901
|
-
but are hidden so only the skeleton is visible. */
|
|
18902
|
-
visibility: hidden;
|
|
18903
|
-
|
|
18904
|
-
/* When there are no children a placeholder "W" is injected (see JSX).
|
|
18905
|
-
It must stretch to the full available width so the skeleton
|
|
18906
|
-
fills the container rather than collapsing to a single character. */
|
|
18907
|
-
.navi_text_skeleton_children_placeholder {
|
|
18908
|
-
display: inline-flex;
|
|
18909
|
-
width: 100%;
|
|
18910
|
-
}
|
|
18911
|
-
|
|
18912
|
-
/* Three-level structure to respect padding AND border-radius:
|
|
18913
|
-
|
|
18914
|
-
1. navi_text_skeleton_container — absolutely fills the border box
|
|
18915
|
-
(inset:0), then applies padding:inherit so its content box equals
|
|
18916
|
-
the parent's content box. line-height:normal prevents the container
|
|
18917
|
-
from inheriting a large line-height that would make it taller than
|
|
18918
|
-
the border box. border-radius:inherit passes the radius down.
|
|
18919
|
-
visibility:visible overrides the parent's visibility:hidden.
|
|
18920
|
-
|
|
18921
|
-
2. navi_text_skeleton_inset — a relative block that fills 100% of the
|
|
18922
|
-
container's content box (= parent's content box). It is the
|
|
18923
|
-
positioned ancestor for the absolutely placed skeleton bar.
|
|
18924
|
-
border-radius:inherit chains the radius further down.
|
|
18925
|
-
|
|
18926
|
-
3. navi_text_skeleton — the visible gradient bar. position:absolute
|
|
18927
|
-
inset:0 fills the inset box precisely. border-radius:inherit
|
|
18928
|
-
finally applies the radius at this level, which is now correctly
|
|
18929
|
-
sized to the content area. */
|
|
18930
|
-
.navi_text_skeleton_container {
|
|
18931
|
-
position: absolute;
|
|
18932
|
-
inset: 0;
|
|
18933
|
-
padding: inherit;
|
|
18934
|
-
line-height: normal;
|
|
18935
|
-
border-radius: inherit;
|
|
18936
|
-
visibility: visible;
|
|
18937
|
-
}
|
|
18938
|
-
|
|
18939
|
-
.navi_text_skeleton_inset {
|
|
18940
|
-
position: relative;
|
|
18941
|
-
display: inline-flex;
|
|
18942
|
-
width: 100%;
|
|
18943
|
-
height: 100%;
|
|
18944
|
-
border-radius: inherit;
|
|
18945
|
-
}
|
|
18918
|
+
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
18919
|
+
cleanupFunctions.push(() => {
|
|
18920
|
+
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
18921
|
+
});
|
|
18946
18922
|
|
|
18947
|
-
|
|
18948
|
-
|
|
18949
|
-
|
|
18950
|
-
|
|
18951
|
-
90deg,
|
|
18952
|
-
#e0e0e0 25%,
|
|
18953
|
-
#f0f0f0 50%,
|
|
18954
|
-
#e0e0e0 75%
|
|
18955
|
-
);
|
|
18956
|
-
background-size: 200% 100%;
|
|
18957
|
-
border-radius: inherit;
|
|
18958
|
-
}
|
|
18923
|
+
// ✅ 4. Vérifier lors de la reprise de connexion
|
|
18924
|
+
const handleOnline = () => {
|
|
18925
|
+
updateNetworkSpeed();
|
|
18926
|
+
};
|
|
18959
18927
|
|
|
18960
|
-
|
|
18961
|
-
|
|
18962
|
-
|
|
18963
|
-
|
|
18964
|
-
}
|
|
18965
|
-
}
|
|
18966
|
-
}
|
|
18928
|
+
window.addEventListener("online", handleOnline);
|
|
18929
|
+
cleanupFunctions.push(() => {
|
|
18930
|
+
window.removeEventListener("online", handleOnline);
|
|
18931
|
+
});
|
|
18967
18932
|
|
|
18968
|
-
|
|
18969
|
-
|
|
18970
|
-
|
|
18971
|
-
|
|
18972
|
-
|
|
18973
|
-
|
|
18974
|
-
}
|
|
18975
|
-
}
|
|
18933
|
+
// Cleanup global
|
|
18934
|
+
return () => {
|
|
18935
|
+
cleanupFunctions.forEach((cleanup) => cleanup());
|
|
18936
|
+
};
|
|
18937
|
+
};
|
|
18938
|
+
setupNetworkMonitoring();
|
|
18976
18939
|
|
|
18977
|
-
|
|
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 {
|
|
18978
18958
|
position: relative;
|
|
18979
|
-
display:
|
|
18980
|
-
|
|
18981
|
-
|
|
18982
|
-
font-weight: bold;
|
|
18983
|
-
opacity: 0;
|
|
18984
|
-
}
|
|
18985
|
-
.navi_text_bold_foreground {
|
|
18986
|
-
position: absolute;
|
|
18987
|
-
inset: 0;
|
|
18988
|
-
}
|
|
18989
|
-
}
|
|
18990
|
-
|
|
18991
|
-
.navi_text_bold_background {
|
|
18992
|
-
position: absolute;
|
|
18993
|
-
top: 0;
|
|
18994
|
-
left: 0;
|
|
18995
|
-
color: currentColor;
|
|
18996
|
-
font-weight: normal;
|
|
18997
|
-
background: currentColor;
|
|
18998
|
-
background-clip: text;
|
|
18999
|
-
-webkit-background-clip: text;
|
|
19000
|
-
transform-origin: center;
|
|
19001
|
-
-webkit-text-fill-color: transparent;
|
|
18959
|
+
display: flex;
|
|
18960
|
+
width: 100%;
|
|
18961
|
+
height: 100%;
|
|
19002
18962
|
opacity: 0;
|
|
19003
18963
|
}
|
|
19004
18964
|
|
|
19005
|
-
.
|
|
19006
|
-
|
|
19007
|
-
opacity: 1;
|
|
19008
|
-
}
|
|
19009
|
-
}
|
|
19010
|
-
|
|
19011
|
-
.navi_text[data-bold-transition] {
|
|
19012
|
-
.navi_text_bold_foreground {
|
|
19013
|
-
transition-property: font-weight;
|
|
19014
|
-
transition-duration: 0.3s;
|
|
19015
|
-
transition-timing-function: ease;
|
|
19016
|
-
}
|
|
19017
|
-
|
|
19018
|
-
.navi_text_bold_background {
|
|
19019
|
-
transition-property: opacity;
|
|
19020
|
-
transition-duration: 0.3s;
|
|
19021
|
-
transition-timing-function: ease;
|
|
19022
|
-
}
|
|
18965
|
+
.navi_rectangle_loading[data-visible] {
|
|
18966
|
+
opacity: 1;
|
|
19023
18967
|
}
|
|
19024
|
-
|
|
19025
|
-
|
|
19026
|
-
|
|
19027
|
-
|
|
19028
|
-
|
|
19029
|
-
|
|
19030
|
-
"data-navi-space": "",
|
|
19031
|
-
children: "\u200B"
|
|
19032
|
-
});
|
|
19033
|
-
const CustomWidthSpace = ({
|
|
19034
|
-
value
|
|
18968
|
+
`, "@jsenv/navi/src/graphic/loader/rectangle_loading.jsx"];
|
|
18969
|
+
const RectangleLoading = ({
|
|
18970
|
+
shouldShowSpinner,
|
|
18971
|
+
color = "currentColor",
|
|
18972
|
+
radius = 0,
|
|
18973
|
+
size = 2
|
|
19035
18974
|
}) => {
|
|
19036
|
-
|
|
19037
|
-
|
|
19038
|
-
|
|
19039
|
-
|
|
19040
|
-
|
|
19041
|
-
|
|
19042
|
-
|
|
19043
|
-
if (spacing === "pre" || spacing === "0" || spacing === 0) {
|
|
19044
|
-
return children;
|
|
19045
|
-
}
|
|
19046
|
-
if (!children) {
|
|
19047
|
-
return children;
|
|
19048
|
-
}
|
|
19049
|
-
const childArray = toChildArray(children);
|
|
19050
|
-
const childCount = childArray.length;
|
|
19051
|
-
if (childCount <= 1) {
|
|
19052
|
-
return children;
|
|
19053
|
-
}
|
|
19054
|
-
let separator;
|
|
19055
|
-
if (spacing === undefined) {
|
|
19056
|
-
spacing = REGULAR_SPACE;
|
|
19057
|
-
} else if (typeof spacing === "string") {
|
|
19058
|
-
if (isSizeSpacingScaleKey(spacing)) {
|
|
19059
|
-
separator = jsx(CustomWidthSpace, {
|
|
19060
|
-
value: resolveSpacingSize(spacing)
|
|
19061
|
-
});
|
|
19062
|
-
} else if (hasCSSSizeUnit(spacing)) {
|
|
19063
|
-
separator = jsx(CustomWidthSpace, {
|
|
19064
|
-
value: resolveSpacingSize(spacing)
|
|
19065
|
-
});
|
|
19066
|
-
} else {
|
|
19067
|
-
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;
|
|
19068
18982
|
}
|
|
19069
|
-
|
|
19070
|
-
|
|
19071
|
-
|
|
19072
|
-
});
|
|
19073
|
-
|
|
19074
|
-
|
|
19075
|
-
|
|
19076
|
-
|
|
19077
|
-
|
|
19078
|
-
|
|
19079
|
-
|
|
19080
|
-
|
|
19081
|
-
|
|
19082
|
-
|
|
19083
|
-
|
|
19084
|
-
|
|
19085
|
-
|
|
19086
|
-
|
|
19087
|
-
|
|
19088
|
-
|
|
19089
|
-
|
|
19090
|
-
|
|
19091
|
-
}
|
|
19092
|
-
return childrenWithGap;
|
|
19093
|
-
};
|
|
19094
|
-
const outsideTextFlowSet = new Set();
|
|
19095
|
-
const markAsOutsideTextFlow = jsxElement => {
|
|
19096
|
-
outsideTextFlowSet.add(jsxElement);
|
|
19097
|
-
};
|
|
19098
|
-
const isMarkedAsOutsideTextFlow = jsxElement => {
|
|
19099
|
-
return outsideTextFlowSet.has(jsxElement.type);
|
|
19100
|
-
};
|
|
19101
|
-
const isPreactNode = jsxChild => {
|
|
19102
|
-
return jsxChild !== null && typeof jsxChild === "object" && jsxChild.type !== undefined;
|
|
19103
|
-
};
|
|
19104
|
-
const shouldInjectSpacingBetween = (left, right) => {
|
|
19105
|
-
const leftIsNode = isPreactNode(left);
|
|
19106
|
-
const rightIsNode = isPreactNode(right);
|
|
19107
|
-
// only inject spacing when at least one side is a preact node
|
|
19108
|
-
if (!leftIsNode && !rightIsNode) {
|
|
19109
|
-
return false;
|
|
19110
|
-
}
|
|
19111
|
-
if (leftIsNode && isMarkedAsOutsideTextFlow(left)) {
|
|
19112
|
-
return false;
|
|
19113
|
-
}
|
|
19114
|
-
if (rightIsNode && isMarkedAsOutsideTextFlow(right)) {
|
|
19115
|
-
return false;
|
|
19116
|
-
}
|
|
19117
|
-
if (rightIsNode && right.props && right.props.overflowPinned) {
|
|
19118
|
-
return false;
|
|
19119
|
-
}
|
|
19120
|
-
if (typeof left === "string" && /\s$/.test(left)) {
|
|
19121
|
-
return false;
|
|
19122
|
-
}
|
|
19123
|
-
if (typeof right === "string" && /^\s/.test(right)) {
|
|
19124
|
-
return false;
|
|
19125
|
-
}
|
|
19126
|
-
return true;
|
|
19127
|
-
};
|
|
19128
|
-
const OverflowPinnedElementContext = createContext(null);
|
|
19129
|
-
const Text = props => {
|
|
19130
|
-
import.meta.css = [css$3, "@jsenv/navi/src/text/text.jsx"];
|
|
19131
|
-
if (props.loading || props.skeleton) {
|
|
19132
|
-
return jsx(TextSkeleton, {
|
|
19133
|
-
...props
|
|
19134
|
-
});
|
|
19135
|
-
}
|
|
19136
|
-
if (props.overflowEllipsis) {
|
|
19137
|
-
return jsx(TextOverflow, {
|
|
19138
|
-
...props
|
|
19139
|
-
});
|
|
19140
|
-
}
|
|
19141
|
-
if (props.overflowPinned) {
|
|
19142
|
-
return jsx(TextOverflowPinned, {
|
|
19143
|
-
...props
|
|
19144
|
-
});
|
|
19145
|
-
}
|
|
19146
|
-
if (props.selectRange) {
|
|
19147
|
-
return jsx(TextWithSelectRange, {
|
|
19148
|
-
...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
|
+
});
|
|
19149
19005
|
});
|
|
19150
|
-
|
|
19151
|
-
|
|
19152
|
-
|
|
19153
|
-
|
|
19154
|
-
}
|
|
19155
|
-
|
|
19156
|
-
|
|
19157
|
-
|
|
19158
|
-
|
|
19159
|
-
|
|
19160
|
-
|
|
19161
|
-
|
|
19162
|
-
|
|
19163
|
-
|
|
19164
|
-
|
|
19165
|
-
|
|
19166
|
-
|
|
19167
|
-
|
|
19168
|
-
})
|
|
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
|
|
19169
19024
|
})
|
|
19170
19025
|
});
|
|
19171
|
-
// When there are no children, inject a full-width placeholder so the element
|
|
19172
|
-
// has measurable height driven by the current font-size/line-height, and the
|
|
19173
|
-
// skeleton fills the available width instead of shrinking to a single char.
|
|
19174
|
-
const hasChildren = children !== null && children !== undefined && children !== false;
|
|
19175
|
-
const innerChildren = hasChildren ? children : jsx("span", {
|
|
19176
|
-
className: "navi_text_skeleton_children_placeholder",
|
|
19177
|
-
"aria-hidden": "true",
|
|
19178
|
-
children: "W"
|
|
19179
|
-
});
|
|
19180
|
-
return jsx(Text, {
|
|
19181
|
-
"data-skeleton": "",
|
|
19182
|
-
"data-loading": loading ? "" : undefined,
|
|
19183
|
-
...props,
|
|
19184
|
-
skeleton: undefined,
|
|
19185
|
-
childrenOutsideFlow: skeletonOverlay,
|
|
19186
|
-
children: innerChildren
|
|
19187
|
-
});
|
|
19188
19026
|
};
|
|
19189
|
-
const
|
|
19190
|
-
|
|
19191
|
-
|
|
19192
|
-
|
|
19193
|
-
|
|
19027
|
+
const RectangleLoadingSvg = ({
|
|
19028
|
+
width,
|
|
19029
|
+
height,
|
|
19030
|
+
color,
|
|
19031
|
+
radius,
|
|
19032
|
+
trailColor = "transparent",
|
|
19033
|
+
strokeWidth
|
|
19194
19034
|
}) => {
|
|
19195
|
-
const
|
|
19196
|
-
return jsx(Text, {
|
|
19197
|
-
flex: true,
|
|
19198
|
-
block: true,
|
|
19199
|
-
as: "div",
|
|
19200
|
-
nowWrap: noWrap,
|
|
19201
|
-
pre: !noWrap
|
|
19202
|
-
// For paragraph we prefer to keep lines and only hide unbreakable long sections
|
|
19203
|
-
,
|
|
19035
|
+
const margin = Math.max(2, Math.min(width, height) * 0.03);
|
|
19204
19036
|
|
|
19205
|
-
|
|
19206
|
-
|
|
19207
|
-
|
|
19208
|
-
"data-text-overflow": "",
|
|
19209
|
-
spacing: "pre",
|
|
19210
|
-
children: jsxs("span", {
|
|
19211
|
-
className: "navi_text_overflow_wrapper",
|
|
19212
|
-
children: [jsx(OverflowPinnedElementContext.Provider, {
|
|
19213
|
-
value: setOverflowPinnedElement,
|
|
19214
|
-
children: jsx(Text, {
|
|
19215
|
-
className: "navi_text_overflow_text",
|
|
19216
|
-
spacing: spacing,
|
|
19217
|
-
children: children
|
|
19218
|
-
})
|
|
19219
|
-
}), OverflowPinnedElement]
|
|
19220
|
-
})
|
|
19221
|
-
});
|
|
19222
|
-
};
|
|
19223
|
-
const TextOverflowPinned = ({
|
|
19224
|
-
overflowPinned,
|
|
19225
|
-
...props
|
|
19226
|
-
}) => {
|
|
19227
|
-
const setOverflowPinnedElement = useContext(OverflowPinnedElementContext);
|
|
19228
|
-
const text = jsx(Text, {
|
|
19229
|
-
...props,
|
|
19230
|
-
"data-overflow-pinned": ""
|
|
19231
|
-
});
|
|
19232
|
-
if (!setOverflowPinnedElement) {
|
|
19233
|
-
console.warn("<Text overflowPinned> declared outside a <Text overflowEllipsis>");
|
|
19234
|
-
return text;
|
|
19235
|
-
}
|
|
19236
|
-
if (overflowPinned) {
|
|
19237
|
-
setOverflowPinnedElement(text);
|
|
19238
|
-
return null;
|
|
19239
|
-
}
|
|
19240
|
-
setOverflowPinnedElement(null);
|
|
19241
|
-
return text;
|
|
19242
|
-
};
|
|
19243
|
-
const TextWithSelectRange = ({
|
|
19244
|
-
selectRange,
|
|
19245
|
-
...props
|
|
19246
|
-
}) => {
|
|
19247
|
-
const defaultRef = useRef();
|
|
19248
|
-
const ref = props.ref || defaultRef;
|
|
19249
|
-
useInitialTextSelection(ref, selectRange);
|
|
19250
|
-
return jsx(Text, {
|
|
19251
|
-
ref: ref,
|
|
19252
|
-
...props
|
|
19253
|
-
});
|
|
19254
|
-
};
|
|
19255
|
-
const TextBasic = ({
|
|
19256
|
-
spacing = REGULAR_SPACE,
|
|
19257
|
-
boldTransition,
|
|
19258
|
-
boldStable,
|
|
19259
|
-
preventBoldLayoutShift = boldTransition,
|
|
19260
|
-
capitalize,
|
|
19261
|
-
children,
|
|
19262
|
-
childrenOutsideFlow,
|
|
19263
|
-
...rest
|
|
19264
|
-
}) => {
|
|
19265
|
-
const boxProps = {
|
|
19266
|
-
"as": "span",
|
|
19267
|
-
"data-bold-transition": boldTransition ? "" : undefined,
|
|
19268
|
-
"data-capitalize": capitalize ? "" : undefined,
|
|
19269
|
-
...rest,
|
|
19270
|
-
"baseClassName": withPropsClassName("navi_text", rest.baseClassName)
|
|
19271
|
-
};
|
|
19272
|
-
const shouldPreserveSpacing = rest.as === "pre" || rest.flex || rest.grid;
|
|
19273
|
-
if (shouldPreserveSpacing) {
|
|
19274
|
-
boxProps.spacing = spacing;
|
|
19275
|
-
} else {
|
|
19276
|
-
children = applySpacingOnTextChildren(children, spacing);
|
|
19277
|
-
}
|
|
19278
|
-
if (boldStable) {
|
|
19279
|
-
const {
|
|
19280
|
-
bold
|
|
19281
|
-
} = boxProps;
|
|
19282
|
-
return jsxs(Box, {
|
|
19283
|
-
...boxProps,
|
|
19284
|
-
bold: undefined,
|
|
19285
|
-
"data-bold": bold ? "" : undefined,
|
|
19286
|
-
children: [jsx("span", {
|
|
19287
|
-
className: "navi_text_bold_background",
|
|
19288
|
-
"aria-hidden": "true",
|
|
19289
|
-
children: children
|
|
19290
|
-
}), children, childrenOutsideFlow]
|
|
19291
|
-
});
|
|
19292
|
-
}
|
|
19293
|
-
if (preventBoldLayoutShift) {
|
|
19294
|
-
const alignX = rest.alignX || rest.align || "start";
|
|
19037
|
+
// Calculate the drawable area
|
|
19038
|
+
const drawableWidth = width - margin * 2;
|
|
19039
|
+
const drawableHeight = height - margin * 2;
|
|
19295
19040
|
|
|
19296
|
-
|
|
19297
|
-
|
|
19298
|
-
|
|
19299
|
-
|
|
19300
|
-
// on pourrait auto-active cela sur une prop genre boldCanChange
|
|
19301
|
-
return jsxs(Box, {
|
|
19302
|
-
...boxProps,
|
|
19303
|
-
children: [jsxs("span", {
|
|
19304
|
-
className: "navi_text_bold_wrapper",
|
|
19305
|
-
children: [jsx("span", {
|
|
19306
|
-
className: "navi_text_bold_clone",
|
|
19307
|
-
"aria-hidden": "true",
|
|
19308
|
-
children: children
|
|
19309
|
-
}), jsx("span", {
|
|
19310
|
-
className: "navi_text_bold_foreground",
|
|
19311
|
-
"data-align": alignX,
|
|
19312
|
-
children: children
|
|
19313
|
-
})]
|
|
19314
|
-
}), childrenOutsideFlow]
|
|
19315
|
-
});
|
|
19316
|
-
}
|
|
19317
|
-
return jsxs(Box, {
|
|
19318
|
-
...boxProps,
|
|
19319
|
-
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)
|
|
19320
19045
|
});
|
|
19321
|
-
|
|
19322
|
-
|
|
19323
|
-
|
|
19324
|
-
|
|
19325
|
-
|
|
19326
|
-
|
|
19327
|
-
|
|
19328
|
-
|
|
19329
|
-
|
|
19330
|
-
|
|
19331
|
-
}
|
|
19332
|
-
}
|
|
19333
|
-
|
|
19334
|
-
.navi_icon {
|
|
19335
|
-
&[data-flow-inline] {
|
|
19336
|
-
width: 1em;
|
|
19337
|
-
height: 1em;
|
|
19338
|
-
}
|
|
19339
|
-
&[data-icon-char] {
|
|
19340
|
-
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;
|
|
19341
19056
|
|
|
19342
|
-
|
|
19343
|
-
|
|
19344
|
-
|
|
19345
|
-
|
|
19346
|
-
}
|
|
19347
|
-
|
|
19348
|
-
|
|
19349
|
-
|
|
19350
|
-
|
|
19351
|
-
|
|
19352
|
-
|
|
19353
|
-
|
|
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
|
+
`;
|
|
19354
19081
|
}
|
|
19355
19082
|
|
|
19356
|
-
|
|
19357
|
-
|
|
19358
|
-
|
|
19359
|
-
|
|
19360
|
-
}
|
|
19361
|
-
.navi_text.navi_icon_foreground {
|
|
19362
|
-
position: absolute;
|
|
19363
|
-
inset: 0;
|
|
19364
|
-
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;
|
|
19365
19087
|
|
|
19366
|
-
|
|
19367
|
-
|
|
19368
|
-
|
|
19369
|
-
|
|
19370
|
-
|
|
19371
|
-
|
|
19372
|
-
|
|
19373
|
-
|
|
19374
|
-
|
|
19375
|
-
}
|
|
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);
|
|
19376
19097
|
|
|
19377
|
-
|
|
19378
|
-
|
|
19379
|
-
|
|
19380
|
-
|
|
19381
|
-
backface-visibility: hidden;
|
|
19382
|
-
}
|
|
19383
|
-
.navi_icon[data-width-fixed] > svg,
|
|
19384
|
-
.navi_icon[data-width-fixed] > img {
|
|
19385
|
-
width: 100%;
|
|
19386
|
-
height: auto;
|
|
19387
|
-
}
|
|
19388
|
-
.navi_icon[data-height-fixed] > svg,
|
|
19389
|
-
.navi_icon[data-height-fixed] > img {
|
|
19390
|
-
width: auto;
|
|
19391
|
-
height: 100%;
|
|
19392
|
-
}
|
|
19393
|
-
.navi_icon[data-width-fixed][data-height-fixed] > svg,
|
|
19394
|
-
.navi_icon[data-width-fixed][data-height-fixed] > img {
|
|
19395
|
-
width: 100%;
|
|
19396
|
-
height: 100%;
|
|
19397
|
-
}
|
|
19398
|
-
`;
|
|
19399
|
-
const Icon = ({
|
|
19400
|
-
href,
|
|
19401
|
-
children,
|
|
19402
|
-
charWidth = 1,
|
|
19403
|
-
// 0 (zéro) is the real char width
|
|
19404
|
-
// but 2 zéros gives too big icons
|
|
19405
|
-
// while 1 "W" gives a nice result
|
|
19406
|
-
baseChar = "W",
|
|
19407
|
-
decorative,
|
|
19408
|
-
onClick,
|
|
19409
|
-
...props
|
|
19410
|
-
}) => {
|
|
19411
|
-
import.meta.css = [css$2, "@jsenv/navi/src/graphic/icon.jsx"];
|
|
19412
|
-
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", {
|
|
19413
19102
|
width: "100%",
|
|
19414
19103
|
height: "100%",
|
|
19415
|
-
|
|
19416
|
-
|
|
19417
|
-
|
|
19418
|
-
}) : children;
|
|
19419
|
-
let {
|
|
19420
|
-
flex,
|
|
19421
|
-
grid,
|
|
19422
|
-
width,
|
|
19423
|
-
height
|
|
19424
|
-
} = props;
|
|
19425
|
-
if (width === "auto") {
|
|
19426
|
-
width = undefined;
|
|
19427
|
-
}
|
|
19428
|
-
if (height === "auto") {
|
|
19429
|
-
height = undefined;
|
|
19430
|
-
}
|
|
19431
|
-
const hasExplicitWidth = width !== undefined;
|
|
19432
|
-
const hasExplicitHeight = height !== undefined;
|
|
19433
|
-
const widthFixed = hasExplicitWidth || hasExplicitHeight && (props.square || props.circle || props.aspectRatio);
|
|
19434
|
-
const heightFixed = hasExplicitHeight || hasExplicitWidth && (props.square || props.circle || props.aspectRatio);
|
|
19435
|
-
if (widthFixed || heightFixed) {
|
|
19436
|
-
if (flex === undefined) {
|
|
19437
|
-
flex = "x";
|
|
19438
|
-
}
|
|
19439
|
-
} else if (decorative === undefined && !onClick) {
|
|
19440
|
-
decorative = true;
|
|
19441
|
-
}
|
|
19442
|
-
const ariaProps = decorative ? {
|
|
19443
|
-
"aria-hidden": "true"
|
|
19444
|
-
} : {};
|
|
19445
|
-
if (typeof children === "string") {
|
|
19446
|
-
return jsx(Text, {
|
|
19447
|
-
...props,
|
|
19448
|
-
...ariaProps,
|
|
19449
|
-
"data-icon-text": "",
|
|
19450
|
-
children: children
|
|
19451
|
-
});
|
|
19452
|
-
}
|
|
19453
|
-
if (flex || grid) {
|
|
19454
|
-
return jsx(Box, {
|
|
19455
|
-
square: true,
|
|
19456
|
-
...props,
|
|
19457
|
-
...ariaProps,
|
|
19458
|
-
flex: flex,
|
|
19459
|
-
baseClassName: "navi_icon",
|
|
19460
|
-
"data-width-fixed": widthFixed ? "" : undefined,
|
|
19461
|
-
"data-height-fixed": heightFixed ? "" : undefined,
|
|
19462
|
-
"data-interactive": onClick ? "" : undefined,
|
|
19463
|
-
onClick: onClick,
|
|
19464
|
-
children: innerChildren
|
|
19465
|
-
});
|
|
19466
|
-
}
|
|
19467
|
-
const invisibleText = baseChar.repeat(charWidth);
|
|
19468
|
-
return jsxs(Text, {
|
|
19469
|
-
...props,
|
|
19470
|
-
...ariaProps,
|
|
19471
|
-
className: withPropsClassName("navi_icon", props.className),
|
|
19472
|
-
spacing: "pre",
|
|
19473
|
-
"data-icon-char": "",
|
|
19474
|
-
"data-width-fixed": widthFixed ? "" : undefined,
|
|
19475
|
-
"data-height-fixed": heightFixed ? "" : undefined,
|
|
19476
|
-
"data-interactive": onClick ? "" : undefined,
|
|
19477
|
-
onClick: onClick,
|
|
19478
|
-
children: [jsx("span", {
|
|
19479
|
-
className: "navi_icon_char_slot",
|
|
19480
|
-
"aria-hidden": "true",
|
|
19481
|
-
children: invisibleText
|
|
19482
|
-
}), jsx(Text, {
|
|
19483
|
-
className: "navi_icon_foreground",
|
|
19484
|
-
spacing: "pre",
|
|
19485
|
-
children: innerChildren
|
|
19486
|
-
})]
|
|
19487
|
-
});
|
|
19488
|
-
};
|
|
19489
|
-
|
|
19490
|
-
const EmailSvg = () => {
|
|
19491
|
-
return jsxs("svg", {
|
|
19492
|
-
viewBox: "0 0 24 24",
|
|
19104
|
+
viewBox: `0 0 ${width} ${height}`,
|
|
19105
|
+
preserveAspectRatio: "none",
|
|
19106
|
+
style: "overflow: visible",
|
|
19493
19107
|
xmlns: "http://www.w3.org/2000/svg",
|
|
19494
|
-
|
|
19495
|
-
|
|
19108
|
+
"shape-rendering": "geometricPrecision",
|
|
19109
|
+
children: [isCircle ? jsx("circle", {
|
|
19110
|
+
cx: margin + drawableWidth / 2,
|
|
19111
|
+
cy: margin + drawableHeight / 2,
|
|
19112
|
+
r: actualRadius,
|
|
19496
19113
|
fill: "none",
|
|
19497
|
-
stroke:
|
|
19498
|
-
|
|
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
|
|
19499
19125
|
}), jsx("path", {
|
|
19500
|
-
d:
|
|
19126
|
+
d: rectPath,
|
|
19501
19127
|
fill: "none",
|
|
19502
|
-
stroke:
|
|
19503
|
-
|
|
19504
|
-
|
|
19505
|
-
|
|
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
|
+
})
|
|
19506
19151
|
})]
|
|
19507
19152
|
});
|
|
19508
19153
|
};
|
|
19509
19154
|
|
|
19510
|
-
|
|
19511
|
-
|
|
19512
|
-
|
|
19513
|
-
|
|
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
|
-
|
|
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
|
|
19547
19212
|
});
|
|
19548
19213
|
};
|
|
19549
|
-
const
|
|
19550
|
-
|
|
19551
|
-
|
|
19552
|
-
|
|
19553
|
-
|
|
19554
|
-
|
|
19555
|
-
|
|
19556
|
-
|
|
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]
|
|
19557
19249
|
});
|
|
19558
19250
|
};
|
|
19559
|
-
const
|
|
19560
|
-
|
|
19561
|
-
|
|
19562
|
-
|
|
19563
|
-
|
|
19564
|
-
|
|
19565
|
-
|
|
19566
|
-
|
|
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]
|
|
19567
19324
|
});
|
|
19568
19325
|
};
|
|
19569
19326
|
|
|
19570
|
-
|
|
19571
|
-
|
|
19572
|
-
|
|
19573
|
-
|
|
19574
|
-
|
|
19575
|
-
|
|
19576
|
-
|
|
19577
|
-
|
|
19578
|
-
});
|
|
19579
|
-
|
|
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
|
+
}
|
|
19580
19341
|
|
|
19581
|
-
const
|
|
19582
|
-
|
|
19583
|
-
|
|
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);
|
|
19584
19355
|
|
|
19585
|
-
|
|
19586
|
-
|
|
19587
|
-
|
|
19588
|
-
timerRef.current = setTimeout(() => {
|
|
19589
|
-
setDebouncedTrue(true);
|
|
19590
|
-
}, delay);
|
|
19591
|
-
} else {
|
|
19592
|
-
// If value becomes false, clear any pending timer and immediately set to false
|
|
19593
|
-
if (timerRef.current) {
|
|
19594
|
-
clearTimeout(timerRef.current);
|
|
19595
|
-
timerRef.current = null;
|
|
19596
|
-
}
|
|
19597
|
-
setDebouncedTrue(false);
|
|
19598
|
-
}
|
|
19356
|
+
const add = (valueToAdd, valueArray = value) => {
|
|
19357
|
+
setValue(addIntoArray(valueArray, valueToAdd));
|
|
19358
|
+
};
|
|
19599
19359
|
|
|
19600
|
-
|
|
19601
|
-
|
|
19602
|
-
|
|
19603
|
-
clearTimeout(timerRef.current);
|
|
19604
|
-
}
|
|
19605
|
-
};
|
|
19606
|
-
}, [value, delay]);
|
|
19360
|
+
const remove = (valueToRemove, valueArray = value) => {
|
|
19361
|
+
setValue(removeFromArray(valueArray, valueToRemove));
|
|
19362
|
+
};
|
|
19607
19363
|
|
|
19608
|
-
|
|
19364
|
+
const result = [boundAction, value, setValue];
|
|
19365
|
+
result.add = add;
|
|
19366
|
+
result.remove = remove;
|
|
19367
|
+
return result;
|
|
19609
19368
|
};
|
|
19610
|
-
|
|
19611
|
-
const
|
|
19612
|
-
return
|
|
19369
|
+
// used by <details> to just call their action
|
|
19370
|
+
const useAction = (action, paramsSignal) => {
|
|
19371
|
+
return useBoundAction(action, paramsSignal);
|
|
19613
19372
|
};
|
|
19614
19373
|
|
|
19615
|
-
const
|
|
19616
|
-
|
|
19617
|
-
|
|
19618
|
-
|
|
19619
|
-
|
|
19620
|
-
|
|
19621
|
-
// ✅ Network Information API (support moderne)
|
|
19622
|
-
if (!connection) {
|
|
19623
|
-
return "3g";
|
|
19374
|
+
const useBoundAction = (action, actionParamsSignal) => {
|
|
19375
|
+
const actionRef = useRef();
|
|
19376
|
+
const actionCallbackRef = useRef();
|
|
19377
|
+
|
|
19378
|
+
if (!action) {
|
|
19379
|
+
return null;
|
|
19624
19380
|
}
|
|
19625
|
-
if (
|
|
19626
|
-
|
|
19627
|
-
|
|
19628
|
-
|
|
19381
|
+
if (isFunctionButNotAnActionFunction(action)) {
|
|
19382
|
+
actionCallbackRef.current = action;
|
|
19383
|
+
const existingAction = actionRef.current;
|
|
19384
|
+
if (existingAction) {
|
|
19385
|
+
return existingAction;
|
|
19629
19386
|
}
|
|
19630
|
-
const
|
|
19631
|
-
|
|
19632
|
-
|
|
19633
|
-
|
|
19634
|
-
|
|
19635
|
-
|
|
19636
|
-
|
|
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;
|
|
19637
19401
|
}
|
|
19402
|
+
const actionBoundToParams =
|
|
19403
|
+
actionFromFunction.bindParams(actionParamsSignal);
|
|
19404
|
+
actionRef.current = actionBoundToParams;
|
|
19405
|
+
return actionBoundToParams;
|
|
19638
19406
|
}
|
|
19639
|
-
|
|
19407
|
+
if (actionParamsSignal) {
|
|
19408
|
+
return action.bindParams(actionParamsSignal);
|
|
19409
|
+
}
|
|
19410
|
+
return action;
|
|
19640
19411
|
};
|
|
19641
19412
|
|
|
19642
|
-
const
|
|
19643
|
-
|
|
19413
|
+
const isFunctionButNotAnActionFunction = (action) => {
|
|
19414
|
+
return typeof action === "function" && !action.isAction;
|
|
19644
19415
|
};
|
|
19645
19416
|
|
|
19646
|
-
const
|
|
19417
|
+
const ErrorBoundaryContext = createContext(null);
|
|
19647
19418
|
|
|
19648
|
-
const
|
|
19649
|
-
const
|
|
19419
|
+
const useResetErrorBoundary = () => {
|
|
19420
|
+
const resetErrorBoundary = useContext(ErrorBoundaryContext);
|
|
19421
|
+
return resetErrorBoundary;
|
|
19422
|
+
};
|
|
19650
19423
|
|
|
19651
|
-
|
|
19424
|
+
const addCustomMessage = (element, key, message, options) => {
|
|
19425
|
+
const customConstraintValidation =
|
|
19426
|
+
element.__validationInterface__ ||
|
|
19427
|
+
(element.__validationInterface__ =
|
|
19428
|
+
installCustomConstraintValidation(element));
|
|
19652
19429
|
|
|
19653
|
-
|
|
19654
|
-
|
|
19655
|
-
cleanupFunctions.push(() => {
|
|
19656
|
-
connection.removeEventListener("change", updateNetworkSpeed);
|
|
19657
|
-
});
|
|
19658
|
-
}
|
|
19430
|
+
return customConstraintValidation.addCustomMessage(key, message, options);
|
|
19431
|
+
};
|
|
19659
19432
|
|
|
19660
|
-
|
|
19661
|
-
const
|
|
19662
|
-
|
|
19433
|
+
const removeCustomMessage = (element, key) => {
|
|
19434
|
+
const customConstraintValidation = element.__validationInterface__;
|
|
19435
|
+
if (!customConstraintValidation) {
|
|
19436
|
+
return;
|
|
19437
|
+
}
|
|
19438
|
+
customConstraintValidation.removeCustomMessage(key);
|
|
19439
|
+
};
|
|
19663
19440
|
|
|
19664
|
-
|
|
19665
|
-
|
|
19666
|
-
|
|
19667
|
-
|
|
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;
|
|
19668
19456
|
}
|
|
19669
|
-
};
|
|
19670
|
-
|
|
19671
|
-
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
19672
|
-
cleanupFunctions.push(() => {
|
|
19673
|
-
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
19674
|
-
});
|
|
19457
|
+
}, [error]);
|
|
19675
19458
|
|
|
19676
|
-
|
|
19677
|
-
const
|
|
19678
|
-
|
|
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
|
+
});
|
|
19679
19488
|
};
|
|
19680
|
-
|
|
19681
|
-
|
|
19682
|
-
|
|
19683
|
-
|
|
19684
|
-
|
|
19685
|
-
|
|
19686
|
-
// Cleanup global
|
|
19687
|
-
return () => {
|
|
19688
|
-
cleanupFunctions.forEach((cleanup) => cleanup());
|
|
19489
|
+
const removeErrorMessage = () => {
|
|
19490
|
+
const validationMessageTarget = validationMessageTargetRef.current;
|
|
19491
|
+
if (validationMessageTarget) {
|
|
19492
|
+
removeCustomMessage(validationMessageTarget, "action_error");
|
|
19493
|
+
}
|
|
19689
19494
|
};
|
|
19690
|
-
};
|
|
19691
|
-
setupNetworkMonitoring();
|
|
19692
|
-
|
|
19693
|
-
installImportMetaCssBuild(import.meta);/**
|
|
19694
|
-
* RectangleLoading Component
|
|
19695
|
-
*
|
|
19696
|
-
* A responsive loading indicator that dynamically adjusts to fit its container.
|
|
19697
|
-
* Displays an animated outline with a traveling dot that follows the container's shape.
|
|
19698
|
-
*
|
|
19699
|
-
* Features:
|
|
19700
|
-
* - Adapts to any container dimensions using ResizeObserver
|
|
19701
|
-
* - Scales stroke width, margins and corner radius proportionally
|
|
19702
|
-
* - Animates using native SVG animations for smooth performance
|
|
19703
|
-
* - High-quality SVG rendering with proper path calculations
|
|
19704
|
-
*
|
|
19705
|
-
* @param {Object} props - Component props
|
|
19706
|
-
* @param {string} [props.color="#383a36"] - Color of the loading indicator
|
|
19707
|
-
* @param {number} [props.radius=0] - Corner radius of the rectangle (px)
|
|
19708
|
-
*/
|
|
19709
|
-
import.meta.css = [/* css */`
|
|
19710
|
-
.navi_rectangle_loading {
|
|
19711
|
-
position: relative;
|
|
19712
|
-
display: flex;
|
|
19713
|
-
width: 100%;
|
|
19714
|
-
height: 100%;
|
|
19715
|
-
opacity: 0;
|
|
19716
|
-
}
|
|
19717
19495
|
|
|
19718
|
-
.navi_rectangle_loading[data-visible] {
|
|
19719
|
-
opacity: 1;
|
|
19720
|
-
}
|
|
19721
|
-
`, "@jsenv/navi/src/graphic/loader/rectangle_loading.jsx"];
|
|
19722
|
-
const RectangleLoading = ({
|
|
19723
|
-
shouldShowSpinner,
|
|
19724
|
-
color = "currentColor",
|
|
19725
|
-
radius = 0,
|
|
19726
|
-
size = 2
|
|
19727
|
-
}) => {
|
|
19728
|
-
const containerRef = useRef(null);
|
|
19729
|
-
const [containerWidth, setContainerWidth] = useState(0);
|
|
19730
|
-
const [containerHeight, setContainerHeight] = useState(0);
|
|
19731
19496
|
useLayoutEffect(() => {
|
|
19732
|
-
const
|
|
19733
|
-
if (!
|
|
19497
|
+
const element = elementRef.current;
|
|
19498
|
+
if (!element) {
|
|
19734
19499
|
return null;
|
|
19735
19500
|
}
|
|
19736
|
-
const
|
|
19737
|
-
|
|
19738
|
-
|
|
19739
|
-
}
|
|
19740
|
-
|
|
19741
|
-
|
|
19742
|
-
|
|
19743
|
-
|
|
19744
|
-
|
|
19745
|
-
|
|
19746
|
-
|
|
19747
|
-
|
|
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);
|
|
19509
|
+
return () => {
|
|
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();
|
|
19748
19535
|
}
|
|
19749
|
-
|
|
19750
|
-
|
|
19751
|
-
|
|
19752
|
-
|
|
19753
|
-
|
|
19754
|
-
|
|
19755
|
-
|
|
19756
|
-
|
|
19536
|
+
removeErrorMessage();
|
|
19537
|
+
setError(null);
|
|
19538
|
+
|
|
19539
|
+
const validationMessageTarget = requester || elementRef.current;
|
|
19540
|
+
validationMessageTargetRef.current = validationMessageTarget;
|
|
19541
|
+
|
|
19542
|
+
dispatchCustomEvent("actionstart", {
|
|
19543
|
+
detail: sharedActionEventDetail,
|
|
19757
19544
|
});
|
|
19758
|
-
});
|
|
19759
|
-
resizeObserver.observe(container);
|
|
19760
|
-
return () => {
|
|
19761
|
-
if (animationFrameId) {
|
|
19762
|
-
cancelAnimationFrame(animationFrameId);
|
|
19763
|
-
}
|
|
19764
|
-
resizeObserver.disconnect();
|
|
19765
|
-
};
|
|
19766
|
-
}, []);
|
|
19767
|
-
return jsx("span", {
|
|
19768
|
-
ref: containerRef,
|
|
19769
|
-
className: "navi_rectangle_loading",
|
|
19770
|
-
"data-visible": shouldShowSpinner ? "" : undefined,
|
|
19771
|
-
children: containerWidth > 0 && containerHeight > 0 && jsx(RectangleLoadingSvg, {
|
|
19772
|
-
radius: radius,
|
|
19773
|
-
color: color,
|
|
19774
|
-
width: containerWidth,
|
|
19775
|
-
height: containerHeight,
|
|
19776
|
-
strokeWidth: size
|
|
19777
|
-
})
|
|
19778
|
-
});
|
|
19779
|
-
};
|
|
19780
|
-
const RectangleLoadingSvg = ({
|
|
19781
|
-
width,
|
|
19782
|
-
height,
|
|
19783
|
-
color,
|
|
19784
|
-
radius,
|
|
19785
|
-
trailColor = "transparent",
|
|
19786
|
-
strokeWidth
|
|
19787
|
-
}) => {
|
|
19788
|
-
const margin = Math.max(2, Math.min(width, height) * 0.03);
|
|
19789
19545
|
|
|
19790
|
-
|
|
19791
|
-
|
|
19792
|
-
|
|
19546
|
+
return action[method]({
|
|
19547
|
+
reason: `"${event.type}" event on ${(() => {
|
|
19548
|
+
const target = event.target;
|
|
19549
|
+
const tagName = target.tagName.toLowerCase();
|
|
19793
19550
|
|
|
19794
|
-
|
|
19795
|
-
|
|
19796
|
-
|
|
19797
|
-
availableSize: Math.min(width, height)
|
|
19798
|
-
});
|
|
19799
|
-
const actualRadius = Math.min(radius || Math.min(drawableWidth, drawableHeight) * 0.05, maxPossibleRadius // ✅ Limité au radius maximum possible
|
|
19800
|
-
);
|
|
19801
|
-
const aspectRatio = Math.max(drawableWidth, drawableHeight) / Math.min(drawableWidth, drawableHeight);
|
|
19802
|
-
const isNearlySquare = aspectRatio <= 1.2; // Allow some tolerance for nearly square shapes
|
|
19803
|
-
const isCircle = isNearlySquare && actualRadius >= maxPossibleRadius * 0.95;
|
|
19804
|
-
let pathLength;
|
|
19805
|
-
let rectPath;
|
|
19806
|
-
if (isCircle) {
|
|
19807
|
-
// ✅ Circle: perimeter = 2πr
|
|
19808
|
-
pathLength = 2 * Math.PI * actualRadius;
|
|
19551
|
+
if (target.id) {
|
|
19552
|
+
return `${tagName}#${target.id}`;
|
|
19553
|
+
}
|
|
19809
19554
|
|
|
19810
|
-
|
|
19811
|
-
|
|
19812
|
-
|
|
19813
|
-
|
|
19814
|
-
M ${centerX + actualRadius},${centerY}
|
|
19815
|
-
A ${actualRadius},${actualRadius} 0 1 1 ${centerX - actualRadius},${centerY}
|
|
19816
|
-
A ${actualRadius},${actualRadius} 0 1 1 ${centerX + actualRadius},${centerY}
|
|
19817
|
-
`;
|
|
19818
|
-
} else {
|
|
19819
|
-
// ✅ Rectangle: calculate perimeter properly
|
|
19820
|
-
const straightEdges = 2 * (drawableWidth - 2 * actualRadius) + 2 * (drawableHeight - 2 * actualRadius);
|
|
19821
|
-
const cornerArcs = actualRadius > 0 ? 2 * Math.PI * actualRadius : 0;
|
|
19822
|
-
pathLength = straightEdges + cornerArcs;
|
|
19823
|
-
rectPath = `
|
|
19824
|
-
M ${margin + actualRadius},${margin}
|
|
19825
|
-
L ${margin + drawableWidth - actualRadius},${margin}
|
|
19826
|
-
A ${actualRadius},${actualRadius} 0 0 1 ${margin + drawableWidth},${margin + actualRadius}
|
|
19827
|
-
L ${margin + drawableWidth},${margin + drawableHeight - actualRadius}
|
|
19828
|
-
A ${actualRadius},${actualRadius} 0 0 1 ${margin + drawableWidth - actualRadius},${margin + drawableHeight}
|
|
19829
|
-
L ${margin + actualRadius},${margin + drawableHeight}
|
|
19830
|
-
A ${actualRadius},${actualRadius} 0 0 1 ${margin},${margin + drawableHeight - actualRadius}
|
|
19831
|
-
L ${margin},${margin + actualRadius}
|
|
19832
|
-
A ${actualRadius},${actualRadius} 0 0 1 ${margin + actualRadius},${margin}
|
|
19833
|
-
`;
|
|
19834
|
-
}
|
|
19555
|
+
const uiName = target.getAttribute("data-ui-name");
|
|
19556
|
+
if (uiName) {
|
|
19557
|
+
return `${tagName}[data-ui-name="${uiName}"]`;
|
|
19558
|
+
}
|
|
19835
19559
|
|
|
19836
|
-
|
|
19837
|
-
|
|
19838
|
-
|
|
19839
|
-
|
|
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
|
+
);
|
|
19840
19616
|
|
|
19841
|
-
|
|
19842
|
-
|
|
19843
|
-
const pixelsPerSecond = {
|
|
19844
|
-
"slow-2g": 40,
|
|
19845
|
-
"2g": 60,
|
|
19846
|
-
"3g": 80,
|
|
19847
|
-
"4g": 120
|
|
19848
|
-
}[networkSpeed] || 80;
|
|
19849
|
-
const animationDuration = Math.max(1.5, pathLength / pixelsPerSecond);
|
|
19617
|
+
return executeAction;
|
|
19618
|
+
};
|
|
19850
19619
|
|
|
19851
|
-
|
|
19852
|
-
|
|
19853
|
-
|
|
19854
|
-
|
|
19855
|
-
|
|
19856
|
-
|
|
19857
|
-
|
|
19858
|
-
preserveAspectRatio: "none",
|
|
19859
|
-
style: "overflow: visible",
|
|
19860
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
19861
|
-
"shape-rendering": "geometricPrecision",
|
|
19862
|
-
children: [isCircle ? jsx("circle", {
|
|
19863
|
-
cx: margin + drawableWidth / 2,
|
|
19864
|
-
cy: margin + drawableHeight / 2,
|
|
19865
|
-
r: actualRadius,
|
|
19866
|
-
fill: "none",
|
|
19867
|
-
stroke: trailColor,
|
|
19868
|
-
strokeWidth: strokeWidth
|
|
19869
|
-
}) : jsx("rect", {
|
|
19870
|
-
x: margin,
|
|
19871
|
-
y: margin,
|
|
19872
|
-
width: drawableWidth,
|
|
19873
|
-
height: drawableHeight,
|
|
19874
|
-
fill: "none",
|
|
19875
|
-
stroke: trailColor,
|
|
19876
|
-
strokeWidth: strokeWidth,
|
|
19877
|
-
rx: actualRadius
|
|
19878
|
-
}), jsx("path", {
|
|
19879
|
-
d: rectPath,
|
|
19880
|
-
fill: "none",
|
|
19881
|
-
stroke: color,
|
|
19882
|
-
strokeWidth: strokeWidth,
|
|
19883
|
-
strokeLinecap: "round",
|
|
19884
|
-
strokeDasharray: `${segmentLength} ${gapLength}`,
|
|
19885
|
-
pathLength: pathLength,
|
|
19886
|
-
children: jsx("animate", {
|
|
19887
|
-
attributeName: "stroke-dashoffset",
|
|
19888
|
-
from: pathLength,
|
|
19889
|
-
to: "0",
|
|
19890
|
-
dur: `${animationDuration}s`,
|
|
19891
|
-
repeatCount: "indefinite",
|
|
19892
|
-
begin: "0s"
|
|
19893
|
-
})
|
|
19894
|
-
}), jsx("circle", {
|
|
19895
|
-
r: strokeWidth,
|
|
19896
|
-
fill: color,
|
|
19897
|
-
children: jsx("animateMotion", {
|
|
19898
|
-
path: rectPath,
|
|
19899
|
-
dur: `${animationDuration}s`,
|
|
19900
|
-
repeatCount: "indefinite",
|
|
19901
|
-
rotate: "auto",
|
|
19902
|
-
begin: `${circleOffset}s`
|
|
19903
|
-
})
|
|
19904
|
-
})]
|
|
19905
|
-
});
|
|
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);
|
|
19906
19627
|
};
|
|
19628
|
+
const isMac = detectMac();
|
|
19907
19629
|
|
|
19908
|
-
|
|
19909
|
-
|
|
19910
|
-
|
|
19911
|
-
|
|
19912
|
-
|
|
19913
|
-
|
|
19914
|
-
|
|
19915
|
-
|
|
19916
|
-
|
|
19917
|
-
|
|
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
|
+
};
|
|
19918
19645
|
|
|
19919
|
-
|
|
19920
|
-
|
|
19921
|
-
|
|
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
|
+
};
|
|
19656
|
+
|
|
19657
|
+
const areShortcutArraysEqual = (arrayA, arrayB) => {
|
|
19658
|
+
if (arrayA.length !== arrayB.length) {
|
|
19659
|
+
return false;
|
|
19922
19660
|
}
|
|
19923
|
-
|
|
19924
|
-
|
|
19925
|
-
|
|
19926
|
-
|
|
19927
|
-
targetSelector,
|
|
19928
|
-
color,
|
|
19929
|
-
inset = 0,
|
|
19930
|
-
borderRadius = 0,
|
|
19931
|
-
spacingTop = 0,
|
|
19932
|
-
spacingLeft = 0,
|
|
19933
|
-
spacingBottom = 0,
|
|
19934
|
-
spacingRight = 0,
|
|
19935
|
-
children
|
|
19936
|
-
}) => {
|
|
19937
|
-
if (containerRef) {
|
|
19938
|
-
const container = containerRef.current;
|
|
19939
|
-
if (!container) {
|
|
19940
|
-
return children;
|
|
19661
|
+
|
|
19662
|
+
for (let i = 0; i < arrayA.length; i++) {
|
|
19663
|
+
if (!areShortcutsEqual(arrayA[i], arrayB[i])) {
|
|
19664
|
+
return false;
|
|
19941
19665
|
}
|
|
19942
|
-
return createPortal(jsx(LoaderBackgroundWithPortal, {
|
|
19943
|
-
container: container,
|
|
19944
|
-
loading: loading,
|
|
19945
|
-
color: color,
|
|
19946
|
-
inset: inset,
|
|
19947
|
-
spacingTop: spacingTop,
|
|
19948
|
-
spacingLeft: spacingLeft,
|
|
19949
|
-
spacingBottom: spacingBottom,
|
|
19950
|
-
spacingRight: spacingRight,
|
|
19951
|
-
children: children
|
|
19952
|
-
}), container);
|
|
19953
19666
|
}
|
|
19954
|
-
|
|
19955
|
-
|
|
19956
|
-
loading: loading,
|
|
19957
|
-
color: color,
|
|
19958
|
-
inset: inset,
|
|
19959
|
-
borderRadius: borderRadius,
|
|
19960
|
-
spacingTop: spacingTop,
|
|
19961
|
-
spacingLeft: spacingLeft,
|
|
19962
|
-
spacingBottom: spacingBottom,
|
|
19963
|
-
spacingRight: spacingRight,
|
|
19964
|
-
children: children
|
|
19965
|
-
});
|
|
19667
|
+
|
|
19668
|
+
return true;
|
|
19966
19669
|
};
|
|
19967
|
-
|
|
19968
|
-
|
|
19969
|
-
|
|
19970
|
-
|
|
19971
|
-
|
|
19972
|
-
|
|
19973
|
-
|
|
19974
|
-
|
|
19975
|
-
|
|
19976
|
-
spacingRight,
|
|
19977
|
-
children
|
|
19978
|
-
}) => {
|
|
19979
|
-
const shouldShowSpinner = useDebounceTrue(loading, 300);
|
|
19980
|
-
if (!shouldShowSpinner) {
|
|
19981
|
-
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
|
+
}
|
|
19982
19679
|
}
|
|
19983
|
-
|
|
19984
|
-
|
|
19985
|
-
if (
|
|
19986
|
-
|
|
19680
|
+
|
|
19681
|
+
// Only update if shortcuts have actually changed
|
|
19682
|
+
if (!areShortcutArraysEqual(currentActiveShortcuts, activeShortcuts)) {
|
|
19683
|
+
activeShortcutsSignal.value = activeShortcuts;
|
|
19987
19684
|
}
|
|
19988
|
-
return jsxs(Fragment, {
|
|
19989
|
-
children: [jsx("div", {
|
|
19990
|
-
style: {
|
|
19991
|
-
position: "absolute",
|
|
19992
|
-
top: `${inset + paddingTop + spacingTop}px`,
|
|
19993
|
-
bottom: `${inset + spacingBottom}px`,
|
|
19994
|
-
left: `${inset + spacingLeft}px`,
|
|
19995
|
-
right: `${inset + spacingRight}px`
|
|
19996
|
-
},
|
|
19997
|
-
children: shouldShowSpinner && jsx(RectangleLoading, {
|
|
19998
|
-
color: color,
|
|
19999
|
-
radius: borderRadius
|
|
20000
|
-
})
|
|
20001
|
-
}), children]
|
|
20002
|
-
});
|
|
20003
19685
|
};
|
|
20004
|
-
|
|
20005
|
-
|
|
20006
|
-
|
|
20007
|
-
|
|
20008
|
-
|
|
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
|
-
spacingLeft += marginLeft;
|
|
20035
|
-
spacingRight += inset;
|
|
20036
|
-
// spacingRight += outlineOffset;
|
|
20037
|
-
// spacingRight -= borderRightWidth;
|
|
20038
|
-
spacingRight += marginRight;
|
|
20039
|
-
spacingBottom += inset;
|
|
20040
|
-
// spacingBottom += outlineOffset;
|
|
20041
|
-
// spacingBottom -= borderBottomWidth;
|
|
20042
|
-
spacingBottom += marginBottom;
|
|
20043
|
-
if (targetSelector) {
|
|
20044
|
-
// oversimplification that actually works
|
|
20045
|
-
// (simplified because it assumes the targeted element is a direct child of the contained element which may have padding)
|
|
20046
|
-
spacingTop += paddingTop;
|
|
20047
|
-
spacingLeft += paddingLeft;
|
|
20048
|
-
spacingRight += paddingRight;
|
|
20049
|
-
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
|
+
);
|
|
20050
19716
|
}
|
|
20051
|
-
|
|
20052
|
-
const
|
|
20053
|
-
const
|
|
20054
|
-
|
|
20055
|
-
|
|
20056
|
-
|
|
20057
|
-
|
|
20058
|
-
|
|
20059
|
-
|
|
20060
|
-
|
|
20061
|
-
|
|
20062
|
-
|
|
20063
|
-
|
|
20064
|
-
|
|
20065
|
-
|
|
20066
|
-
|
|
20067
|
-
|
|
20068
|
-
|
|
20069
|
-
|
|
20070
|
-
|
|
20071
|
-
|
|
20072
|
-
|
|
20073
|
-
|
|
20074
|
-
|
|
20075
|
-
|
|
20076
|
-
|
|
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
|
+
},
|
|
20077
19774
|
});
|
|
20078
|
-
};
|
|
20079
19775
|
|
|
20080
|
-
|
|
20081
|
-
|
|
20082
|
-
|
|
20083
|
-
|
|
20084
|
-
|
|
20085
|
-
|
|
20086
|
-
|
|
20087
|
-
|
|
20088
|
-
|
|
20089
|
-
const externalValueRef = useRef(externalValue);
|
|
20090
|
-
if (externalValue !== externalValueRef.current) {
|
|
20091
|
-
externalValueRef.current = externalValue;
|
|
20092
|
-
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);
|
|
20093
19785
|
}
|
|
20094
19786
|
|
|
20095
|
-
|
|
20096
|
-
|
|
20097
|
-
|
|
20098
|
-
|
|
20099
|
-
action,
|
|
20100
|
-
name,
|
|
20101
|
-
externalValue,
|
|
20102
|
-
fallbackValue,
|
|
20103
|
-
defaultValue,
|
|
20104
|
-
) => {
|
|
20105
|
-
const [boundAction, value, setValue] = useActionBoundToOneParam(
|
|
20106
|
-
action,
|
|
20107
|
-
name);
|
|
20108
|
-
|
|
20109
|
-
const add = (valueToAdd, valueArray = value) => {
|
|
20110
|
-
setValue(addIntoArray(valueArray, valueToAdd));
|
|
20111
|
-
};
|
|
20112
|
-
|
|
20113
|
-
const remove = (valueToRemove, valueArray = value) => {
|
|
20114
|
-
setValue(removeFromArray(valueArray, valueToRemove));
|
|
20115
|
-
};
|
|
20116
|
-
|
|
20117
|
-
const result = [boundAction, value, setValue];
|
|
20118
|
-
result.add = add;
|
|
20119
|
-
result.remove = remove;
|
|
20120
|
-
return result;
|
|
20121
|
-
};
|
|
20122
|
-
// used by <details> to just call their action
|
|
20123
|
-
const useAction = (action, paramsSignal) => {
|
|
20124
|
-
return useBoundAction(action, paramsSignal);
|
|
20125
|
-
};
|
|
20126
|
-
|
|
20127
|
-
const useBoundAction = (action, actionParamsSignal) => {
|
|
20128
|
-
const actionRef = useRef();
|
|
20129
|
-
const actionCallbackRef = useRef();
|
|
20130
|
-
|
|
20131
|
-
if (!action) {
|
|
20132
|
-
return null;
|
|
20133
|
-
}
|
|
20134
|
-
if (isFunctionButNotAnActionFunction(action)) {
|
|
20135
|
-
actionCallbackRef.current = action;
|
|
20136
|
-
const existingAction = actionRef.current;
|
|
20137
|
-
if (existingAction) {
|
|
20138
|
-
return existingAction;
|
|
19787
|
+
useEffect(() => {
|
|
19788
|
+
const element = elementRef.current;
|
|
19789
|
+
if (!element) {
|
|
19790
|
+
return null;
|
|
20139
19791
|
}
|
|
20140
|
-
const
|
|
20141
|
-
|
|
20142
|
-
|
|
20143
|
-
|
|
20144
|
-
|
|
20145
|
-
|
|
20146
|
-
|
|
20147
|
-
|
|
20148
|
-
|
|
20149
|
-
|
|
20150
|
-
|
|
20151
|
-
|
|
20152
|
-
|
|
20153
|
-
|
|
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
|
+
});
|
|
20154
19816
|
}
|
|
20155
|
-
const actionBoundToParams =
|
|
20156
|
-
actionFromFunction.bindParams(actionParamsSignal);
|
|
20157
|
-
actionRef.current = actionBoundToParams;
|
|
20158
|
-
return actionBoundToParams;
|
|
20159
|
-
}
|
|
20160
|
-
if (actionParamsSignal) {
|
|
20161
|
-
return action.bindParams(actionParamsSignal);
|
|
20162
|
-
}
|
|
20163
|
-
return action;
|
|
20164
|
-
};
|
|
20165
|
-
|
|
20166
|
-
const isFunctionButNotAnActionFunction = (action) => {
|
|
20167
|
-
return typeof action === "function" && !action.isAction;
|
|
20168
|
-
};
|
|
20169
|
-
|
|
20170
|
-
const ErrorBoundaryContext = createContext(null);
|
|
20171
|
-
|
|
20172
|
-
const useResetErrorBoundary = () => {
|
|
20173
|
-
const resetErrorBoundary = useContext(ErrorBoundaryContext);
|
|
20174
|
-
return resetErrorBoundary;
|
|
20175
|
-
};
|
|
20176
19817
|
|
|
20177
|
-
|
|
20178
|
-
const customConstraintValidation =
|
|
20179
|
-
element.__validationInterface__ ||
|
|
20180
|
-
(element.__validationInterface__ =
|
|
20181
|
-
installCustomConstraintValidation(element));
|
|
19818
|
+
addShortcuts(element, shortcuts);
|
|
20182
19819
|
|
|
20183
|
-
|
|
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]);
|
|
20184
19829
|
};
|
|
20185
19830
|
|
|
20186
|
-
const
|
|
20187
|
-
|
|
20188
|
-
|
|
20189
|
-
return;
|
|
19831
|
+
const applyKeyboardShortcuts = (shortcuts, keyboardEvent) => {
|
|
19832
|
+
if (!canInterceptKeys(keyboardEvent)) {
|
|
19833
|
+
return null;
|
|
20190
19834
|
}
|
|
20191
|
-
|
|
20192
|
-
};
|
|
20193
|
-
|
|
20194
|
-
|
|
20195
|
-
elementRef,
|
|
20196
|
-
{
|
|
20197
|
-
errorEffect = "show_validation_message", // "show_validation_message" or "throw"
|
|
20198
|
-
errorMapping,
|
|
20199
|
-
} = {},
|
|
20200
|
-
) => {
|
|
20201
|
-
// see https://medium.com/trabe/catching-asynchronous-errors-in-react-using-error-boundaries-5e8a5fd7b971
|
|
20202
|
-
// and https://codepen.io/dmail/pen/XJJqeGp?editors=0010
|
|
20203
|
-
// To change if https://github.com/preactjs/preact/issues/4754 lands
|
|
20204
|
-
const [error, setError] = useState(null);
|
|
20205
|
-
const resetErrorBoundary = useResetErrorBoundary();
|
|
20206
|
-
useLayoutEffect(() => {
|
|
20207
|
-
if (error) {
|
|
20208
|
-
throw error;
|
|
19835
|
+
for (const shortcutCandidate of shortcuts) {
|
|
19836
|
+
let { enabled = true, key } = shortcutCandidate;
|
|
19837
|
+
if (!enabled) {
|
|
19838
|
+
continue;
|
|
20209
19839
|
}
|
|
20210
|
-
}, [error]);
|
|
20211
19840
|
|
|
20212
|
-
|
|
20213
|
-
|
|
20214
|
-
|
|
20215
|
-
|
|
20216
|
-
if (errorMapping) {
|
|
20217
|
-
const errorMappingResult = errorMapping(error);
|
|
20218
|
-
if (typeof errorMappingResult === "string") {
|
|
20219
|
-
message = errorMappingResult;
|
|
20220
|
-
} else if (Error.isError(errorMappingResult)) {
|
|
20221
|
-
message = errorMappingResult;
|
|
20222
|
-
} else if (isValidElement(errorMappingResult)) {
|
|
20223
|
-
message = errorMappingResult;
|
|
20224
|
-
} else if (
|
|
20225
|
-
typeof errorMappingResult === "object" &&
|
|
20226
|
-
errorMappingResult !== null
|
|
20227
|
-
) {
|
|
20228
|
-
message = errorMappingResult.message || error.message;
|
|
20229
|
-
calloutAnchor = errorMappingResult.target || calloutAnchor;
|
|
19841
|
+
if (typeof key === "function") {
|
|
19842
|
+
const keyReturnValue = key(keyboardEvent);
|
|
19843
|
+
if (!keyReturnValue) {
|
|
19844
|
+
continue;
|
|
20230
19845
|
}
|
|
20231
|
-
|
|
20232
|
-
message = error;
|
|
19846
|
+
key = keyReturnValue;
|
|
20233
19847
|
}
|
|
20234
|
-
|
|
20235
|
-
|
|
20236
|
-
|
|
20237
|
-
// so whenever user tries to submit the form the error is cleared
|
|
20238
|
-
// (Hitting enter key, clicking on submit button, etc. would allow to re-submit the form in error state)
|
|
20239
|
-
removeOnRequestAction: true,
|
|
20240
|
-
});
|
|
20241
|
-
};
|
|
20242
|
-
const removeErrorMessage = () => {
|
|
20243
|
-
const validationMessageTarget = validationMessageTargetRef.current;
|
|
20244
|
-
if (validationMessageTarget) {
|
|
20245
|
-
removeCustomMessage(validationMessageTarget, "action_error");
|
|
19848
|
+
if (!key) {
|
|
19849
|
+
console.error(shortcutCandidate);
|
|
19850
|
+
throw new TypeError(`key is required in keyboard shortcut, got ${key}`);
|
|
20246
19851
|
}
|
|
20247
|
-
};
|
|
20248
19852
|
|
|
20249
|
-
|
|
20250
|
-
|
|
20251
|
-
|
|
20252
|
-
|
|
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
|
+
}
|
|
20253
19863
|
}
|
|
20254
|
-
|
|
20255
|
-
|
|
20256
|
-
|
|
19864
|
+
|
|
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
|
+
);
|
|
19876
|
+
|
|
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();
|
|
20257
19886
|
}
|
|
20258
|
-
|
|
20259
|
-
|
|
20260
|
-
|
|
20261
|
-
|
|
20262
|
-
|
|
20263
|
-
|
|
20264
|
-
|
|
20265
|
-
});
|
|
19887
|
+
return shortcutCandidate;
|
|
19888
|
+
}
|
|
19889
|
+
return null;
|
|
19890
|
+
};
|
|
19891
|
+
const containsPlatformSpecificKeys = (combination) => {
|
|
19892
|
+
const lowerCombination = combination.toLowerCase();
|
|
19893
|
+
const macSpecificKeys = ["command", "cmd"];
|
|
20266
19894
|
|
|
20267
|
-
|
|
20268
|
-
|
|
20269
|
-
|
|
20270
|
-
|
|
20271
|
-
const { action, actionOrigin, requester, event, method } =
|
|
20272
|
-
actionEvent.detail;
|
|
20273
|
-
const sharedActionEventDetail = {
|
|
20274
|
-
action,
|
|
20275
|
-
actionOrigin,
|
|
20276
|
-
requester,
|
|
20277
|
-
event,
|
|
20278
|
-
method,
|
|
20279
|
-
};
|
|
19895
|
+
return macSpecificKeys.some((key) => lowerCombination.includes(key));
|
|
19896
|
+
};
|
|
19897
|
+
const generateCrossPlatformCombination = (combination) => {
|
|
19898
|
+
let crossPlatform = combination;
|
|
20280
19899
|
|
|
20281
|
-
|
|
20282
|
-
|
|
20283
|
-
|
|
20284
|
-
|
|
20285
|
-
|
|
20286
|
-
|
|
20287
|
-
|
|
19900
|
+
if (isMac) {
|
|
19901
|
+
// No need to convert anything TO Windows/Linux-specific format since we're on Mac
|
|
19902
|
+
return null;
|
|
19903
|
+
}
|
|
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);
|
|
20288
19923
|
}
|
|
20289
|
-
removeErrorMessage();
|
|
20290
|
-
setError(null);
|
|
20291
19924
|
|
|
20292
|
-
|
|
20293
|
-
|
|
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
|
+
}
|
|
19933
|
+
}
|
|
19934
|
+
if (modifierFound) {
|
|
19935
|
+
continue;
|
|
19936
|
+
}
|
|
20294
19937
|
|
|
20295
|
-
|
|
20296
|
-
|
|
20297
|
-
|
|
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
|
+
}
|
|
20298
19947
|
|
|
20299
|
-
|
|
20300
|
-
|
|
20301
|
-
|
|
20302
|
-
|
|
19948
|
+
// Only allow a-z and 0-9 ranges
|
|
19949
|
+
const isValidRange =
|
|
19950
|
+
(startChar >= "a" && endChar <= "z") ||
|
|
19951
|
+
(startChar >= "0" && endChar <= "9");
|
|
20303
19952
|
|
|
20304
|
-
|
|
20305
|
-
|
|
20306
|
-
|
|
19953
|
+
if (!isValidRange) {
|
|
19954
|
+
return false; // Invalid range pattern
|
|
19955
|
+
}
|
|
20307
19956
|
|
|
20308
|
-
|
|
20309
|
-
|
|
20310
|
-
|
|
20311
|
-
}
|
|
19957
|
+
const eventKeyCode = eventKey.charCodeAt(0);
|
|
19958
|
+
const startCode = startChar.charCodeAt(0);
|
|
19959
|
+
const endCode = endChar.charCodeAt(0);
|
|
20312
19960
|
|
|
20313
|
-
|
|
20314
|
-
|
|
20315
|
-
|
|
20316
|
-
|
|
20317
|
-
|
|
20318
|
-
|
|
20319
|
-
// but other side effects might do this
|
|
20320
|
-
elementRef.current
|
|
20321
|
-
) {
|
|
20322
|
-
dispatchCustomEvent("actionabort", {
|
|
20323
|
-
detail: {
|
|
20324
|
-
...sharedActionEventDetail,
|
|
20325
|
-
reason,
|
|
20326
|
-
},
|
|
20327
|
-
});
|
|
20328
|
-
}
|
|
20329
|
-
},
|
|
20330
|
-
onError: (error) => {
|
|
20331
|
-
if (
|
|
20332
|
-
// at this stage the action side effect might have removed the <element> from the DOM
|
|
20333
|
-
// (in theory no because action side effect are batched to happen after)
|
|
20334
|
-
// but other side effects might do this
|
|
20335
|
-
elementRef.current
|
|
20336
|
-
) {
|
|
20337
|
-
dispatchCustomEvent("actionerror", {
|
|
20338
|
-
detail: {
|
|
20339
|
-
...sharedActionEventDetail,
|
|
20340
|
-
error,
|
|
20341
|
-
},
|
|
20342
|
-
});
|
|
20343
|
-
}
|
|
20344
|
-
if (errorEffect === "show_validation_message") {
|
|
20345
|
-
addErrorMessage(error);
|
|
20346
|
-
} else if (errorEffect === "throw") {
|
|
20347
|
-
setError(error);
|
|
20348
|
-
}
|
|
20349
|
-
},
|
|
20350
|
-
onComplete: (data) => {
|
|
20351
|
-
if (
|
|
20352
|
-
// at this stage the action side effect might have removed the <element> from the DOM
|
|
20353
|
-
// (in theory no because action side effect are batched to happen after)
|
|
20354
|
-
// but other side effects might do this
|
|
20355
|
-
elementRef.current
|
|
20356
|
-
) {
|
|
20357
|
-
dispatchCustomEvent("actionend", {
|
|
20358
|
-
detail: {
|
|
20359
|
-
...sharedActionEventDetail,
|
|
20360
|
-
data,
|
|
20361
|
-
},
|
|
20362
|
-
});
|
|
20363
|
-
}
|
|
20364
|
-
},
|
|
20365
|
-
});
|
|
20366
|
-
},
|
|
20367
|
-
[errorEffect],
|
|
20368
|
-
);
|
|
19961
|
+
if (eventKeyCode >= startCode && eventKeyCode <= endCode) {
|
|
19962
|
+
continue; // Range matched
|
|
19963
|
+
}
|
|
19964
|
+
return false; // Range not matched
|
|
19965
|
+
}
|
|
19966
|
+
}
|
|
20369
19967
|
|
|
20370
|
-
|
|
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
|
+
},
|
|
20371
19991
|
};
|
|
19992
|
+
const isSameKey = (browserEventKey, key) => {
|
|
19993
|
+
browserEventKey = browserEventKey.toLowerCase();
|
|
19994
|
+
key = key.toLowerCase();
|
|
20372
19995
|
|
|
20373
|
-
|
|
20374
|
-
|
|
20375
|
-
if (window.navigator.userAgentData) {
|
|
20376
|
-
return window.navigator.userAgentData.platform === "macOS";
|
|
19996
|
+
if (browserEventKey === key) {
|
|
19997
|
+
return true;
|
|
20377
19998
|
}
|
|
20378
|
-
// Fallback to userAgent string parsing
|
|
20379
|
-
return /Mac|iPhone|iPad|iPod/.test(window.navigator.userAgent);
|
|
20380
|
-
};
|
|
20381
|
-
const isMac = detectMac();
|
|
20382
19999
|
|
|
20383
|
-
//
|
|
20384
|
-
|
|
20385
|
-
const
|
|
20386
|
-
|
|
20387
|
-
|
|
20388
|
-
|
|
20389
|
-
|
|
20390
|
-
|
|
20391
|
-
|
|
20392
|
-
"delete": { alias: ["del"] },
|
|
20393
|
-
// Platform-specific mappings
|
|
20394
|
-
...(isMac
|
|
20395
|
-
? { delete: { alias: ["backspace"] } }
|
|
20396
|
-
: { backspace: { alias: ["delete"] } }),
|
|
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
|
+
}
|
|
20007
|
+
|
|
20008
|
+
return false;
|
|
20397
20009
|
};
|
|
20398
20010
|
|
|
20399
|
-
const
|
|
20400
|
-
|
|
20011
|
+
installImportMetaCssBuild(import.meta);const css$7 = /* css */`
|
|
20012
|
+
.navi_text_aligner_anchor {
|
|
20013
|
+
vertical-align: baseline;
|
|
20014
|
+
user-select: none;
|
|
20015
|
+
overflow: hidden;
|
|
20016
|
+
}
|
|
20017
|
+
`;
|
|
20401
20018
|
|
|
20402
|
-
|
|
20403
|
-
|
|
20404
|
-
|
|
20405
|
-
|
|
20406
|
-
|
|
20407
|
-
|
|
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
|
+
}) => {
|
|
20039
|
+
import.meta.css = [css$7, "@jsenv/navi/src/text/surrounding_text_aligner.jsx"];
|
|
20040
|
+
const anchorRef = useRef();
|
|
20041
|
+
useLayoutEffect(() => {
|
|
20042
|
+
const anchorEl = anchorRef.current;
|
|
20043
|
+
const childEl = childRef.current;
|
|
20044
|
+
if (!anchorEl || !childEl) {
|
|
20045
|
+
return;
|
|
20046
|
+
}
|
|
20047
|
+
const topOffset = computeTopOffset({
|
|
20048
|
+
anchorEl,
|
|
20049
|
+
childEl,
|
|
20050
|
+
align
|
|
20051
|
+
});
|
|
20052
|
+
if (topOffset) {
|
|
20053
|
+
childEl.style.position = "relative";
|
|
20054
|
+
childEl.style.top = `${topOffset}px`;
|
|
20055
|
+
} else {
|
|
20056
|
+
childEl.style.position = "";
|
|
20057
|
+
childEl.style.top = "";
|
|
20058
|
+
}
|
|
20059
|
+
});
|
|
20060
|
+
return jsxs(Fragment, {
|
|
20061
|
+
children: [children, jsx("span", {
|
|
20062
|
+
ref: anchorRef,
|
|
20063
|
+
className: "navi_text_aligner_anchor",
|
|
20064
|
+
children: "\u200B"
|
|
20065
|
+
})]
|
|
20066
|
+
});
|
|
20408
20067
|
};
|
|
20409
|
-
|
|
20410
|
-
|
|
20411
|
-
|
|
20412
|
-
|
|
20068
|
+
const computeTopOffset = ({
|
|
20069
|
+
anchorEl,
|
|
20070
|
+
childEl,
|
|
20071
|
+
align
|
|
20072
|
+
}) => {
|
|
20073
|
+
if (align === "baseline") {
|
|
20074
|
+
return 0;
|
|
20413
20075
|
}
|
|
20414
|
-
|
|
20415
|
-
|
|
20416
|
-
|
|
20417
|
-
|
|
20418
|
-
|
|
20076
|
+
// Only correct when the anchor lives in an inline formatting context.
|
|
20077
|
+
// If the parent is a flex/grid container, inline layout rules don't apply
|
|
20078
|
+
// and our font-metrics model is invalid.
|
|
20079
|
+
const parentDisplay = getComputedStyle(anchorEl.parentElement).display;
|
|
20080
|
+
if (parentDisplay !== "inline" && parentDisplay !== "inline-block" && parentDisplay !== "block") {
|
|
20081
|
+
return 0;
|
|
20419
20082
|
}
|
|
20083
|
+
const anchorStyle = getComputedStyle(anchorEl);
|
|
20084
|
+
const anchorMetrics = measureFontAscDesc("M", anchorStyle);
|
|
20085
|
+
const [anchorABA, anchorABD] = anchorMetrics.actual;
|
|
20086
|
+
const anchorActH = anchorABA + anchorABD;
|
|
20087
|
+
const [, anchorFBBD] = anchorMetrics.font;
|
|
20088
|
+
|
|
20089
|
+
// Estimate the baseline Y from the anchor's bounding rect.
|
|
20090
|
+
// For an inline span, the font cell bottom is always at the element's bottom edge
|
|
20091
|
+
// (regardless of vertical-align), so baseline = rect.bottom - fontBoundingBoxDescent.
|
|
20092
|
+
const anchorRect = anchorEl.getBoundingClientRect();
|
|
20093
|
+
const baselineY = anchorRect.bottom - anchorFBBD;
|
|
20094
|
+
const anchorInkTopY = baselineY - anchorABA;
|
|
20095
|
+
|
|
20096
|
+
// Measure the child's current rect, then subtract any previously applied top correction
|
|
20097
|
+
// to recover its natural position — avoiding a style reset + reflow.
|
|
20098
|
+
const childRect = childEl.getBoundingClientRect();
|
|
20099
|
+
const childH = childRect.height;
|
|
20100
|
+
const previousTop = parseFloat(childEl.style.top) || 0;
|
|
20101
|
+
const childNaturalTop = childRect.top - previousTop;
|
|
20102
|
+
|
|
20103
|
+
// Compute desired child top Y based on align intention.
|
|
20104
|
+
let desiredChildTopY = 0;
|
|
20105
|
+
if (align === "center") {
|
|
20106
|
+
const anchorInkCenterY = anchorInkTopY + anchorActH / 2;
|
|
20107
|
+
desiredChildTopY = anchorInkCenterY - childH / 2;
|
|
20108
|
+
} else if (align === "start") {
|
|
20109
|
+
desiredChildTopY = anchorInkTopY;
|
|
20110
|
+
} else if (align === "end") {
|
|
20111
|
+
desiredChildTopY = anchorInkTopY + anchorActH - childH;
|
|
20112
|
+
}
|
|
20113
|
+
return desiredChildTopY - childNaturalTop;
|
|
20114
|
+
};
|
|
20115
|
+
const canvas = document.createElement("canvas");
|
|
20116
|
+
const measureFontAscDesc = (text, computedStyle) => {
|
|
20117
|
+
const ctx = canvas.getContext("2d");
|
|
20118
|
+
ctx.font = `${computedStyle.fontWeight} ${computedStyle.fontSize} ${computedStyle.fontFamily}`;
|
|
20119
|
+
const metrics = ctx.measureText(text);
|
|
20120
|
+
return {
|
|
20121
|
+
actual: [metrics.actualBoundingBoxAscent, metrics.actualBoundingBoxDescent],
|
|
20122
|
+
font: [metrics.fontBoundingBoxAscent, metrics.fontBoundingBoxDescent]
|
|
20123
|
+
};
|
|
20124
|
+
};
|
|
20420
20125
|
|
|
20421
|
-
|
|
20126
|
+
const useInitialTextSelection = (ref, textSelection) => {
|
|
20127
|
+
const deps = [];
|
|
20128
|
+
if (Array.isArray(textSelection)) {
|
|
20129
|
+
deps.push(...textSelection);
|
|
20130
|
+
} else {
|
|
20131
|
+
deps.push(textSelection);
|
|
20132
|
+
}
|
|
20133
|
+
useLayoutEffect(() => {
|
|
20134
|
+
const el = ref.current;
|
|
20135
|
+
if (!el || !textSelection) {
|
|
20136
|
+
return;
|
|
20137
|
+
}
|
|
20138
|
+
const range = document.createRange();
|
|
20139
|
+
const selection = window.getSelection();
|
|
20140
|
+
if (Array.isArray(textSelection)) {
|
|
20141
|
+
if (textSelection.length === 2) {
|
|
20142
|
+
const [start, end] = textSelection;
|
|
20143
|
+
if (typeof start === "number" && typeof end === "number") {
|
|
20144
|
+
// Format: [0, 10] - character indices
|
|
20145
|
+
selectByCharacterIndices(el, range, start, end);
|
|
20146
|
+
} else if (typeof start === "string" && typeof end === "string") {
|
|
20147
|
+
// Format: ["Click on the", "button to return"] - text strings
|
|
20148
|
+
selectByTextStrings(el, range, start, end);
|
|
20149
|
+
}
|
|
20150
|
+
}
|
|
20151
|
+
} else if (typeof textSelection === "string") {
|
|
20152
|
+
// Format: "some text" - select the entire string occurrence
|
|
20153
|
+
selectSingleTextString(el, range, textSelection);
|
|
20154
|
+
}
|
|
20155
|
+
selection.removeAllRanges();
|
|
20156
|
+
selection.addRange(range);
|
|
20157
|
+
}, deps);
|
|
20422
20158
|
};
|
|
20159
|
+
const selectByCharacterIndices = (element, range, startIndex, endIndex) => {
|
|
20160
|
+
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false);
|
|
20161
|
+
let currentIndex = 0;
|
|
20162
|
+
let startNode = null;
|
|
20163
|
+
let startOffset = 0;
|
|
20164
|
+
let endNode = null;
|
|
20165
|
+
let endOffset = 0;
|
|
20166
|
+
while (walker.nextNode()) {
|
|
20167
|
+
const textContent = walker.currentNode.textContent;
|
|
20168
|
+
const nodeLength = textContent.length;
|
|
20423
20169
|
|
|
20424
|
-
|
|
20425
|
-
|
|
20426
|
-
|
|
20427
|
-
|
|
20428
|
-
for (const [element, { shortcuts }] of shortcutsMap) {
|
|
20429
|
-
if (element === activeElement || element.contains(activeElement)) {
|
|
20430
|
-
activeShortcuts.push(...shortcuts);
|
|
20170
|
+
// Check if start position is in this text node
|
|
20171
|
+
if (!startNode && currentIndex + nodeLength > startIndex) {
|
|
20172
|
+
startNode = walker.currentNode;
|
|
20173
|
+
startOffset = startIndex - currentIndex;
|
|
20431
20174
|
}
|
|
20432
|
-
}
|
|
20433
20175
|
|
|
20434
|
-
|
|
20435
|
-
|
|
20436
|
-
|
|
20176
|
+
// Check if end position is in this text node
|
|
20177
|
+
if (currentIndex + nodeLength >= endIndex) {
|
|
20178
|
+
endNode = walker.currentNode;
|
|
20179
|
+
endOffset = endIndex - currentIndex;
|
|
20180
|
+
break;
|
|
20181
|
+
}
|
|
20182
|
+
currentIndex += nodeLength;
|
|
20183
|
+
}
|
|
20184
|
+
if (startNode && endNode) {
|
|
20185
|
+
range.setStart(startNode, startOffset);
|
|
20186
|
+
range.setEnd(endNode, endOffset);
|
|
20437
20187
|
}
|
|
20438
20188
|
};
|
|
20439
|
-
|
|
20440
|
-
|
|
20441
|
-
|
|
20442
|
-
|
|
20443
|
-
|
|
20444
|
-
|
|
20445
|
-
|
|
20446
|
-
|
|
20189
|
+
const selectSingleTextString = (element, range, text) => {
|
|
20190
|
+
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false);
|
|
20191
|
+
while (walker.nextNode()) {
|
|
20192
|
+
const textContent = walker.currentNode.textContent;
|
|
20193
|
+
const index = textContent.indexOf(text);
|
|
20194
|
+
if (index !== -1) {
|
|
20195
|
+
range.setStart(walker.currentNode, index);
|
|
20196
|
+
range.setEnd(walker.currentNode, index + text.length);
|
|
20197
|
+
return;
|
|
20198
|
+
}
|
|
20199
|
+
}
|
|
20447
20200
|
};
|
|
20448
|
-
const
|
|
20449
|
-
|
|
20450
|
-
|
|
20201
|
+
const selectByTextStrings = (element, range, startText, endText) => {
|
|
20202
|
+
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false);
|
|
20203
|
+
let startNode = null;
|
|
20204
|
+
let endNode = null;
|
|
20205
|
+
let foundStart = false;
|
|
20206
|
+
while (walker.nextNode()) {
|
|
20207
|
+
const textContent = walker.currentNode.textContent;
|
|
20208
|
+
if (!foundStart && textContent.includes(startText)) {
|
|
20209
|
+
startNode = walker.currentNode;
|
|
20210
|
+
foundStart = true;
|
|
20211
|
+
}
|
|
20212
|
+
if (foundStart && textContent.includes(endText)) {
|
|
20213
|
+
endNode = walker.currentNode;
|
|
20214
|
+
break;
|
|
20215
|
+
}
|
|
20216
|
+
}
|
|
20217
|
+
if (startNode && endNode) {
|
|
20218
|
+
const startOffset = startNode.textContent.indexOf(startText);
|
|
20219
|
+
const endOffset = endNode.textContent.indexOf(endText) + endText.length;
|
|
20220
|
+
range.setStart(startNode, startOffset);
|
|
20221
|
+
range.setEnd(endNode, endOffset);
|
|
20222
|
+
}
|
|
20451
20223
|
};
|
|
20452
20224
|
|
|
20453
|
-
|
|
20454
|
-
|
|
20455
|
-
|
|
20456
|
-
|
|
20457
|
-
|
|
20458
|
-
|
|
20459
|
-
|
|
20460
|
-
|
|
20461
|
-
onActionEnd,
|
|
20462
|
-
allowConcurrentActions,
|
|
20463
|
-
} = {},
|
|
20464
|
-
) => {
|
|
20465
|
-
if (!elementRef) {
|
|
20466
|
-
throw new Error(
|
|
20467
|
-
"useKeyboardShortcuts requires an elementRef to attach shortcuts to.",
|
|
20468
|
-
);
|
|
20225
|
+
installImportMetaCssBuild(import.meta);/* eslint-disable jsenv/no-unknown-params */
|
|
20226
|
+
const css$6 = /* css */`
|
|
20227
|
+
@layer navi {
|
|
20228
|
+
.navi_text {
|
|
20229
|
+
&[data-skeleton] {
|
|
20230
|
+
border-radius: 0.2em;
|
|
20231
|
+
}
|
|
20232
|
+
}
|
|
20469
20233
|
}
|
|
20470
20234
|
|
|
20471
|
-
|
|
20472
|
-
|
|
20473
|
-
|
|
20474
|
-
|
|
20475
|
-
|
|
20476
|
-
|
|
20477
|
-
|
|
20478
|
-
|
|
20479
|
-
|
|
20480
|
-
|
|
20235
|
+
*[data-navi-space] {
|
|
20236
|
+
}
|
|
20237
|
+
|
|
20238
|
+
.navi_text {
|
|
20239
|
+
position: relative;
|
|
20240
|
+
|
|
20241
|
+
/* There is a chrome specific bug that prevents text-transform: capitalize to be applied in nested DOM structure */
|
|
20242
|
+
/* The CSS below ensure capitalize is propagated to the bold clones */
|
|
20243
|
+
&[data-capitalize] {
|
|
20244
|
+
&::first-letter {
|
|
20245
|
+
text-transform: uppercase;
|
|
20481
20246
|
}
|
|
20482
|
-
|
|
20483
|
-
|
|
20484
|
-
// setAction(() => actionEvent.detail.action);
|
|
20485
|
-
executeAction(actionEvent, {
|
|
20486
|
-
requester: document.activeElement,
|
|
20487
|
-
});
|
|
20488
|
-
},
|
|
20489
|
-
onStart: (e) => {
|
|
20490
|
-
const { shortcut } = e.detail.meta || {};
|
|
20491
|
-
if (!shortcut) {
|
|
20492
|
-
return;
|
|
20247
|
+
.navi_text_bold_clone::first-letter {
|
|
20248
|
+
text-transform: uppercase;
|
|
20493
20249
|
}
|
|
20494
|
-
|
|
20495
|
-
|
|
20250
|
+
.navi_text_bold_foreground::first-letter {
|
|
20251
|
+
text-transform: uppercase;
|
|
20496
20252
|
}
|
|
20497
|
-
|
|
20498
|
-
|
|
20499
|
-
|
|
20500
|
-
|
|
20501
|
-
|
|
20502
|
-
|
|
20503
|
-
|
|
20253
|
+
}
|
|
20254
|
+
|
|
20255
|
+
.navi_text_bold_wrapper,
|
|
20256
|
+
.navi_text_bold_clone,
|
|
20257
|
+
.navi_text_bold_foreground {
|
|
20258
|
+
display: inherit;
|
|
20259
|
+
width: inherit;
|
|
20260
|
+
min-width: inherit;
|
|
20261
|
+
height: inherit;
|
|
20262
|
+
min-height: inherit;
|
|
20263
|
+
flex-grow: inherit;
|
|
20264
|
+
align-items: inherit;
|
|
20265
|
+
justify-content: inherit;
|
|
20266
|
+
gap: inherit;
|
|
20267
|
+
text-align: inherit;
|
|
20268
|
+
border-radius: inherit;
|
|
20269
|
+
}
|
|
20270
|
+
|
|
20271
|
+
&[data-text-overflow] {
|
|
20272
|
+
min-width: 0;
|
|
20273
|
+
flex-wrap: wrap;
|
|
20274
|
+
text-overflow: ellipsis;
|
|
20275
|
+
overflow: hidden;
|
|
20276
|
+
|
|
20277
|
+
.navi_text_overflow_wrapper {
|
|
20278
|
+
display: flex;
|
|
20279
|
+
width: 100%;
|
|
20280
|
+
flex-grow: 1;
|
|
20281
|
+
gap: 0.3em;
|
|
20282
|
+
|
|
20283
|
+
.navi_text_overflow_text {
|
|
20284
|
+
max-width: 100%;
|
|
20285
|
+
text-overflow: ellipsis;
|
|
20286
|
+
overflow: hidden;
|
|
20287
|
+
}
|
|
20288
|
+
}
|
|
20289
|
+
}
|
|
20290
|
+
|
|
20291
|
+
&[data-skeleton] {
|
|
20292
|
+
/* Children stay in the DOM to preserve natural layout dimensions,
|
|
20293
|
+
but are hidden so only the skeleton is visible. */
|
|
20294
|
+
visibility: hidden;
|
|
20295
|
+
|
|
20296
|
+
/* When there are no children a placeholder "W" is injected (see JSX).
|
|
20297
|
+
It must stretch to the full available width so the skeleton
|
|
20298
|
+
fills the container rather than collapsing to a single character. */
|
|
20299
|
+
.navi_text_skeleton_children_placeholder {
|
|
20300
|
+
display: inline-flex;
|
|
20301
|
+
width: 100%;
|
|
20302
|
+
}
|
|
20303
|
+
|
|
20304
|
+
/* Three-level structure to respect padding AND border-radius:
|
|
20305
|
+
|
|
20306
|
+
1. navi_text_skeleton_container — absolutely fills the border box
|
|
20307
|
+
(inset:0), then applies padding:inherit so its content box equals
|
|
20308
|
+
the parent's content box. line-height:normal prevents the container
|
|
20309
|
+
from inheriting a large line-height that would make it taller than
|
|
20310
|
+
the border box. border-radius:inherit passes the radius down.
|
|
20311
|
+
visibility:visible overrides the parent's visibility:hidden.
|
|
20312
|
+
|
|
20313
|
+
2. navi_text_skeleton_inset — a relative block that fills 100% of the
|
|
20314
|
+
container's content box (= parent's content box). It is the
|
|
20315
|
+
positioned ancestor for the absolutely placed skeleton bar.
|
|
20316
|
+
border-radius:inherit chains the radius further down.
|
|
20317
|
+
|
|
20318
|
+
3. navi_text_skeleton — the visible gradient bar. position:absolute
|
|
20319
|
+
inset:0 fills the inset box precisely. border-radius:inherit
|
|
20320
|
+
finally applies the radius at this level, which is now correctly
|
|
20321
|
+
sized to the content area. */
|
|
20322
|
+
.navi_text_skeleton_container {
|
|
20323
|
+
position: absolute;
|
|
20324
|
+
inset: 0;
|
|
20325
|
+
padding: inherit;
|
|
20326
|
+
line-height: normal;
|
|
20327
|
+
border-radius: inherit;
|
|
20328
|
+
visibility: visible;
|
|
20504
20329
|
}
|
|
20505
|
-
|
|
20506
|
-
|
|
20507
|
-
|
|
20508
|
-
|
|
20509
|
-
|
|
20510
|
-
|
|
20511
|
-
|
|
20512
|
-
return;
|
|
20330
|
+
|
|
20331
|
+
.navi_text_skeleton_inset {
|
|
20332
|
+
position: relative;
|
|
20333
|
+
display: inline-flex;
|
|
20334
|
+
width: 100%;
|
|
20335
|
+
height: 100%;
|
|
20336
|
+
border-radius: inherit;
|
|
20513
20337
|
}
|
|
20514
|
-
|
|
20515
|
-
|
|
20516
|
-
|
|
20517
|
-
|
|
20518
|
-
|
|
20519
|
-
|
|
20520
|
-
|
|
20521
|
-
|
|
20338
|
+
|
|
20339
|
+
.navi_text_skeleton {
|
|
20340
|
+
position: absolute;
|
|
20341
|
+
inset: 0;
|
|
20342
|
+
background: linear-gradient(
|
|
20343
|
+
90deg,
|
|
20344
|
+
#e0e0e0 25%,
|
|
20345
|
+
#f0f0f0 50%,
|
|
20346
|
+
#e0e0e0 75%
|
|
20347
|
+
);
|
|
20348
|
+
background-size: 200% 100%;
|
|
20349
|
+
border-radius: inherit;
|
|
20522
20350
|
}
|
|
20523
|
-
shortcutActionIsBusyRef.current = false;
|
|
20524
|
-
shortcut.onEnd?.(e);
|
|
20525
|
-
onActionEnd?.(e);
|
|
20526
|
-
},
|
|
20527
|
-
});
|
|
20528
20351
|
|
|
20529
|
-
|
|
20530
|
-
|
|
20531
|
-
|
|
20532
|
-
|
|
20533
|
-
|
|
20534
|
-
|
|
20535
|
-
shortcut.confirmMessage,
|
|
20536
|
-
);
|
|
20537
|
-
shortcut.action = useAction(shortcut.action);
|
|
20352
|
+
&[data-loading] {
|
|
20353
|
+
.navi_text_skeleton {
|
|
20354
|
+
animation: navi_text_skeleton_shimmer 1.5s infinite;
|
|
20355
|
+
}
|
|
20356
|
+
}
|
|
20357
|
+
}
|
|
20538
20358
|
}
|
|
20539
20359
|
|
|
20540
|
-
|
|
20541
|
-
|
|
20542
|
-
|
|
20543
|
-
return null;
|
|
20360
|
+
@keyframes navi_text_skeleton_shimmer {
|
|
20361
|
+
0% {
|
|
20362
|
+
background-position: 200% 0;
|
|
20544
20363
|
}
|
|
20545
|
-
|
|
20546
|
-
|
|
20547
|
-
shortcutsCopy.push({
|
|
20548
|
-
...shortcutCandidate,
|
|
20549
|
-
handler: (keyboardEvent) => {
|
|
20550
|
-
if (shortcutCandidate.handler) {
|
|
20551
|
-
return shortcutCandidate.handler(keyboardEvent);
|
|
20552
|
-
}
|
|
20553
|
-
if (shortcutActionIsBusyRef.current) {
|
|
20554
|
-
return false;
|
|
20555
|
-
}
|
|
20556
|
-
const { action } = shortcutCandidate;
|
|
20557
|
-
const actionWithEvent = action.bindParams(keyboardEvent);
|
|
20558
|
-
return requestAction(element, actionWithEvent, {
|
|
20559
|
-
actionOrigin: "keyboard_shortcut",
|
|
20560
|
-
event: keyboardEvent,
|
|
20561
|
-
requester: document.activeElement,
|
|
20562
|
-
confirmMessage: shortcutCandidate.confirmMessage,
|
|
20563
|
-
meta: {
|
|
20564
|
-
shortcut: shortcutCandidate,
|
|
20565
|
-
},
|
|
20566
|
-
});
|
|
20567
|
-
},
|
|
20568
|
-
});
|
|
20364
|
+
100% {
|
|
20365
|
+
background-position: -200% 0;
|
|
20569
20366
|
}
|
|
20367
|
+
}
|
|
20570
20368
|
|
|
20571
|
-
|
|
20369
|
+
.navi_text_bold_wrapper {
|
|
20370
|
+
position: relative;
|
|
20371
|
+
display: inline-block;
|
|
20572
20372
|
|
|
20573
|
-
|
|
20574
|
-
|
|
20575
|
-
|
|
20576
|
-
|
|
20577
|
-
|
|
20578
|
-
|
|
20579
|
-
|
|
20580
|
-
}
|
|
20581
|
-
}
|
|
20582
|
-
};
|
|
20373
|
+
.navi_text_bold_clone {
|
|
20374
|
+
font-weight: bold;
|
|
20375
|
+
opacity: 0;
|
|
20376
|
+
}
|
|
20377
|
+
.navi_text_bold_foreground {
|
|
20378
|
+
position: absolute;
|
|
20379
|
+
inset: 0;
|
|
20380
|
+
}
|
|
20381
|
+
}
|
|
20583
20382
|
|
|
20584
|
-
|
|
20585
|
-
|
|
20586
|
-
|
|
20383
|
+
.navi_text_bold_background {
|
|
20384
|
+
position: absolute;
|
|
20385
|
+
top: 0;
|
|
20386
|
+
left: 0;
|
|
20387
|
+
color: currentColor;
|
|
20388
|
+
font-weight: normal;
|
|
20389
|
+
background: currentColor;
|
|
20390
|
+
background-clip: text;
|
|
20391
|
+
-webkit-background-clip: text;
|
|
20392
|
+
transform-origin: center;
|
|
20393
|
+
-webkit-text-fill-color: transparent;
|
|
20394
|
+
opacity: 0;
|
|
20587
20395
|
}
|
|
20588
|
-
for (const shortcutCandidate of shortcuts) {
|
|
20589
|
-
let { enabled = true, key } = shortcutCandidate;
|
|
20590
|
-
if (!enabled) {
|
|
20591
|
-
continue;
|
|
20592
|
-
}
|
|
20593
20396
|
|
|
20594
|
-
|
|
20595
|
-
|
|
20596
|
-
|
|
20597
|
-
continue;
|
|
20598
|
-
}
|
|
20599
|
-
key = keyReturnValue;
|
|
20600
|
-
}
|
|
20601
|
-
if (!key) {
|
|
20602
|
-
console.error(shortcutCandidate);
|
|
20603
|
-
throw new TypeError(`key is required in keyboard shortcut, got ${key}`);
|
|
20397
|
+
.navi_text[data-bold] {
|
|
20398
|
+
.navi_text_bold_background {
|
|
20399
|
+
opacity: 1;
|
|
20604
20400
|
}
|
|
20401
|
+
}
|
|
20605
20402
|
|
|
20606
|
-
|
|
20607
|
-
|
|
20608
|
-
|
|
20609
|
-
|
|
20610
|
-
|
|
20611
|
-
} else {
|
|
20612
|
-
actualCombination = key;
|
|
20613
|
-
if (containsPlatformSpecificKeys(key)) {
|
|
20614
|
-
crossPlatformCombination = generateCrossPlatformCombination(key);
|
|
20615
|
-
}
|
|
20403
|
+
.navi_text[data-bold-transition] {
|
|
20404
|
+
.navi_text_bold_foreground {
|
|
20405
|
+
transition-property: font-weight;
|
|
20406
|
+
transition-duration: 0.3s;
|
|
20407
|
+
transition-timing-function: ease;
|
|
20616
20408
|
}
|
|
20617
20409
|
|
|
20618
|
-
|
|
20619
|
-
|
|
20620
|
-
|
|
20621
|
-
|
|
20622
|
-
const matchesCrossPlatform =
|
|
20623
|
-
crossPlatformCombination &&
|
|
20624
|
-
crossPlatformCombination !== actualCombination &&
|
|
20625
|
-
keyboardEventIsMatchingKeyCombination(
|
|
20626
|
-
keyboardEvent,
|
|
20627
|
-
crossPlatformCombination,
|
|
20628
|
-
);
|
|
20629
|
-
|
|
20630
|
-
if (!matchesActual && !matchesCrossPlatform) {
|
|
20631
|
-
continue;
|
|
20410
|
+
.navi_text_bold_background {
|
|
20411
|
+
transition-property: opacity;
|
|
20412
|
+
transition-duration: 0.3s;
|
|
20413
|
+
transition-timing-function: ease;
|
|
20632
20414
|
}
|
|
20633
|
-
|
|
20634
|
-
|
|
20415
|
+
}
|
|
20416
|
+
`;
|
|
20417
|
+
const REGULAR_SPACE = jsx("span", {
|
|
20418
|
+
"data-navi-space": "",
|
|
20419
|
+
children: " "
|
|
20420
|
+
});
|
|
20421
|
+
// A space that uses padding-left instead of a real space character.
|
|
20422
|
+
// This avoids the underline that browsers draw under spaces inside links.
|
|
20423
|
+
const FAKE_SPACE = jsx("span", {
|
|
20424
|
+
"data-navi-space": "",
|
|
20425
|
+
style: "padding-left: 0.25em",
|
|
20426
|
+
children: "\u200B"
|
|
20427
|
+
});
|
|
20428
|
+
const CustomWidthSpace = ({
|
|
20429
|
+
value,
|
|
20430
|
+
useRealSpaceChar
|
|
20431
|
+
}) => {
|
|
20432
|
+
if (useRealSpaceChar) {
|
|
20433
|
+
return jsxs("span", {
|
|
20434
|
+
children: [jsx("span", {
|
|
20435
|
+
style: "font-size: 0",
|
|
20436
|
+
children: " "
|
|
20437
|
+
}), jsx("span", {
|
|
20438
|
+
style: `padding-left: ${value}`,
|
|
20439
|
+
children: "\u200B"
|
|
20440
|
+
})]
|
|
20441
|
+
});
|
|
20442
|
+
}
|
|
20443
|
+
return jsx("span", {
|
|
20444
|
+
style: `padding-left: ${value}`,
|
|
20445
|
+
children: "\u200B"
|
|
20446
|
+
});
|
|
20447
|
+
};
|
|
20448
|
+
const applySpacingOnTextChildren = (children, spacing, defaultSpace) => {
|
|
20449
|
+
if (spacing === "pre" || spacing === "0" || spacing === 0) {
|
|
20450
|
+
return children;
|
|
20451
|
+
}
|
|
20452
|
+
if (!children) {
|
|
20453
|
+
return children;
|
|
20454
|
+
}
|
|
20455
|
+
const childArray = toChildArray(children);
|
|
20456
|
+
const childCount = childArray.length;
|
|
20457
|
+
if (childCount <= 1) {
|
|
20458
|
+
return children;
|
|
20459
|
+
}
|
|
20460
|
+
const useRealSpaceChar = defaultSpace !== FAKE_SPACE;
|
|
20461
|
+
let separator;
|
|
20462
|
+
if (spacing === REGULAR_SPACE || spacing === FAKE_SPACE) {
|
|
20463
|
+
separator = defaultSpace;
|
|
20464
|
+
} else if (typeof spacing === "string") {
|
|
20465
|
+
if (isSizeSpacingScaleKey(spacing) || hasCSSSizeUnit(spacing)) {
|
|
20466
|
+
separator = jsx(CustomWidthSpace, {
|
|
20467
|
+
value: resolveSpacingSize(spacing),
|
|
20468
|
+
useRealSpaceChar: useRealSpaceChar
|
|
20469
|
+
});
|
|
20470
|
+
} else {
|
|
20471
|
+
separator = spacing;
|
|
20472
|
+
}
|
|
20473
|
+
} else if (typeof spacing === "number") {
|
|
20474
|
+
separator = jsx(CustomWidthSpace, {
|
|
20475
|
+
value: `${spacing}px`,
|
|
20476
|
+
useRealSpaceChar: useRealSpaceChar
|
|
20477
|
+
});
|
|
20478
|
+
} else {
|
|
20479
|
+
separator = spacing;
|
|
20480
|
+
}
|
|
20481
|
+
const childrenWithGap = [];
|
|
20482
|
+
let i = 0;
|
|
20483
|
+
while (true) {
|
|
20484
|
+
const child = childArray[i];
|
|
20485
|
+
childrenWithGap.push(child);
|
|
20486
|
+
i++;
|
|
20487
|
+
if (i === childCount) {
|
|
20488
|
+
break;
|
|
20635
20489
|
}
|
|
20636
|
-
const
|
|
20637
|
-
|
|
20638
|
-
|
|
20490
|
+
const currentChild = childArray[i - 1];
|
|
20491
|
+
const nextChild = childArray[i];
|
|
20492
|
+
if (!shouldInjectSpacingBetween(currentChild, nextChild)) {
|
|
20493
|
+
continue;
|
|
20639
20494
|
}
|
|
20640
|
-
|
|
20495
|
+
childrenWithGap.push(separator);
|
|
20641
20496
|
}
|
|
20642
|
-
return
|
|
20497
|
+
return childrenWithGap;
|
|
20643
20498
|
};
|
|
20644
|
-
const
|
|
20645
|
-
|
|
20646
|
-
|
|
20647
|
-
|
|
20648
|
-
return macSpecificKeys.some((key) => lowerCombination.includes(key));
|
|
20499
|
+
const outsideTextFlowSet = new Set();
|
|
20500
|
+
const markAsOutsideTextFlow = jsxElement => {
|
|
20501
|
+
outsideTextFlowSet.add(jsxElement);
|
|
20649
20502
|
};
|
|
20650
|
-
const
|
|
20651
|
-
|
|
20503
|
+
const isMarkedAsOutsideTextFlow = jsxElement => {
|
|
20504
|
+
return outsideTextFlowSet.has(jsxElement.type);
|
|
20505
|
+
};
|
|
20506
|
+
const isPreactNode = jsxChild => {
|
|
20507
|
+
return jsxChild !== null && typeof jsxChild === "object" && jsxChild.type !== undefined;
|
|
20508
|
+
};
|
|
20509
|
+
const shouldInjectSpacingBetween = (left, right) => {
|
|
20510
|
+
const leftIsNode = isPreactNode(left);
|
|
20511
|
+
const rightIsNode = isPreactNode(right);
|
|
20512
|
+
// only inject spacing when at least one side is a preact node
|
|
20513
|
+
if (!leftIsNode && !rightIsNode) {
|
|
20514
|
+
return false;
|
|
20515
|
+
}
|
|
20516
|
+
if (leftIsNode && isMarkedAsOutsideTextFlow(left)) {
|
|
20517
|
+
return false;
|
|
20518
|
+
}
|
|
20519
|
+
if (rightIsNode && isMarkedAsOutsideTextFlow(right)) {
|
|
20520
|
+
return false;
|
|
20521
|
+
}
|
|
20522
|
+
if (rightIsNode && right.props && right.props.overflowPinned) {
|
|
20523
|
+
return false;
|
|
20524
|
+
}
|
|
20525
|
+
if (typeof left === "string" && /\s$/.test(left)) {
|
|
20526
|
+
return false;
|
|
20527
|
+
}
|
|
20528
|
+
if (typeof right === "string" && /^\s/.test(right)) {
|
|
20529
|
+
return false;
|
|
20530
|
+
}
|
|
20531
|
+
return true;
|
|
20532
|
+
};
|
|
20533
|
+
const OverflowPinnedElementContext = createContext(null);
|
|
20534
|
+
const Text = props => {
|
|
20535
|
+
import.meta.css = [css$6, "@jsenv/navi/src/text/text.jsx"];
|
|
20536
|
+
if (props.loading || props.skeleton) {
|
|
20537
|
+
return jsx(TextSkeleton, {
|
|
20538
|
+
...props
|
|
20539
|
+
});
|
|
20540
|
+
}
|
|
20541
|
+
if (props.overflowEllipsis) {
|
|
20542
|
+
return jsx(TextOverflow, {
|
|
20543
|
+
...props
|
|
20544
|
+
});
|
|
20545
|
+
}
|
|
20546
|
+
if (props.overflowPinned) {
|
|
20547
|
+
return jsx(TextOverflowPinned, {
|
|
20548
|
+
...props
|
|
20549
|
+
});
|
|
20550
|
+
}
|
|
20551
|
+
if (props.selectRange) {
|
|
20552
|
+
return jsx(TextWithSelectRange, {
|
|
20553
|
+
...props
|
|
20554
|
+
});
|
|
20555
|
+
}
|
|
20556
|
+
return jsx(TextBasic, {
|
|
20557
|
+
...props
|
|
20558
|
+
});
|
|
20559
|
+
};
|
|
20560
|
+
const TextSkeleton = ({
|
|
20561
|
+
loading,
|
|
20562
|
+
children,
|
|
20563
|
+
...props
|
|
20564
|
+
}) => {
|
|
20565
|
+
// Three-level structure — see CSS comment on [data-skeleton] for details.
|
|
20566
|
+
const skeletonOverlay = jsx("span", {
|
|
20567
|
+
className: "navi_text_skeleton_container",
|
|
20568
|
+
"aria-hidden": "true",
|
|
20569
|
+
children: jsx("span", {
|
|
20570
|
+
className: "navi_text_skeleton_inset",
|
|
20571
|
+
children: jsx("span", {
|
|
20572
|
+
className: "navi_text_skeleton"
|
|
20573
|
+
})
|
|
20574
|
+
})
|
|
20575
|
+
});
|
|
20576
|
+
// When there are no children, inject a full-width placeholder so the element
|
|
20577
|
+
// has measurable height driven by the current font-size/line-height, and the
|
|
20578
|
+
// skeleton fills the available width instead of shrinking to a single char.
|
|
20579
|
+
const hasChildren = children !== null && children !== undefined && children !== false;
|
|
20580
|
+
const innerChildren = hasChildren ? children : jsx("span", {
|
|
20581
|
+
className: "navi_text_skeleton_children_placeholder",
|
|
20582
|
+
"aria-hidden": "true",
|
|
20583
|
+
children: "W"
|
|
20584
|
+
});
|
|
20585
|
+
return jsx(Text, {
|
|
20586
|
+
"data-skeleton": "",
|
|
20587
|
+
"data-loading": loading ? "" : undefined,
|
|
20588
|
+
...props,
|
|
20589
|
+
skeleton: undefined,
|
|
20590
|
+
childrenOutsideFlow: skeletonOverlay,
|
|
20591
|
+
children: innerChildren
|
|
20592
|
+
});
|
|
20593
|
+
};
|
|
20594
|
+
const TextOverflow = ({
|
|
20595
|
+
noWrap,
|
|
20596
|
+
spacing,
|
|
20597
|
+
children,
|
|
20598
|
+
...rest
|
|
20599
|
+
}) => {
|
|
20600
|
+
const [OverflowPinnedElement, setOverflowPinnedElement] = useState(null);
|
|
20601
|
+
return jsx(Text, {
|
|
20602
|
+
flex: true,
|
|
20603
|
+
block: true,
|
|
20604
|
+
as: "div",
|
|
20605
|
+
nowWrap: noWrap,
|
|
20606
|
+
pre: !noWrap
|
|
20607
|
+
// For paragraph we prefer to keep lines and only hide unbreakable long sections
|
|
20608
|
+
,
|
|
20652
20609
|
|
|
20653
|
-
|
|
20654
|
-
|
|
20610
|
+
preLine: rest.as === "p",
|
|
20611
|
+
...rest,
|
|
20612
|
+
overflowEllipsis: undefined,
|
|
20613
|
+
"data-text-overflow": "",
|
|
20614
|
+
spacing: "pre",
|
|
20615
|
+
children: jsxs("span", {
|
|
20616
|
+
className: "navi_text_overflow_wrapper",
|
|
20617
|
+
children: [jsx(OverflowPinnedElementContext.Provider, {
|
|
20618
|
+
value: setOverflowPinnedElement,
|
|
20619
|
+
children: jsx(Text, {
|
|
20620
|
+
className: "navi_text_overflow_text",
|
|
20621
|
+
spacing: spacing,
|
|
20622
|
+
children: children
|
|
20623
|
+
})
|
|
20624
|
+
}), OverflowPinnedElement]
|
|
20625
|
+
})
|
|
20626
|
+
});
|
|
20627
|
+
};
|
|
20628
|
+
const TextOverflowPinned = ({
|
|
20629
|
+
overflowPinned,
|
|
20630
|
+
...props
|
|
20631
|
+
}) => {
|
|
20632
|
+
const setOverflowPinnedElement = useContext(OverflowPinnedElementContext);
|
|
20633
|
+
const text = jsx(Text, {
|
|
20634
|
+
...props,
|
|
20635
|
+
"data-overflow-pinned": ""
|
|
20636
|
+
});
|
|
20637
|
+
if (!setOverflowPinnedElement) {
|
|
20638
|
+
console.warn("<Text overflowPinned> declared outside a <Text overflowEllipsis>");
|
|
20639
|
+
return text;
|
|
20640
|
+
}
|
|
20641
|
+
if (overflowPinned) {
|
|
20642
|
+
setOverflowPinnedElement(text);
|
|
20655
20643
|
return null;
|
|
20656
20644
|
}
|
|
20657
|
-
|
|
20658
|
-
|
|
20659
|
-
crossPlatform = crossPlatform.replace(/\bcmd\b/gi, "control");
|
|
20660
|
-
|
|
20661
|
-
return crossPlatform;
|
|
20645
|
+
setOverflowPinnedElement(null);
|
|
20646
|
+
return text;
|
|
20662
20647
|
};
|
|
20663
|
-
const
|
|
20664
|
-
|
|
20665
|
-
|
|
20666
|
-
|
|
20667
|
-
|
|
20668
|
-
|
|
20669
|
-
|
|
20670
|
-
|
|
20671
|
-
|
|
20648
|
+
const TextWithSelectRange = ({
|
|
20649
|
+
selectRange,
|
|
20650
|
+
...props
|
|
20651
|
+
}) => {
|
|
20652
|
+
const defaultRef = useRef();
|
|
20653
|
+
const ref = props.ref || defaultRef;
|
|
20654
|
+
useInitialTextSelection(ref, selectRange);
|
|
20655
|
+
return jsx(Text, {
|
|
20656
|
+
ref: ref,
|
|
20657
|
+
...props
|
|
20658
|
+
});
|
|
20659
|
+
};
|
|
20660
|
+
const TextBasic = ({
|
|
20661
|
+
spacing,
|
|
20662
|
+
preventSpaceUnderlines = false,
|
|
20663
|
+
boldTransition,
|
|
20664
|
+
boldStable,
|
|
20665
|
+
preventBoldLayoutShift = boldTransition,
|
|
20666
|
+
capitalize,
|
|
20667
|
+
children,
|
|
20668
|
+
childrenOutsideFlow,
|
|
20669
|
+
...rest
|
|
20670
|
+
}) => {
|
|
20671
|
+
const defaultSpace = preventSpaceUnderlines ? FAKE_SPACE : REGULAR_SPACE;
|
|
20672
|
+
const resolvedSpacing = spacing ?? defaultSpace;
|
|
20673
|
+
const boxProps = {
|
|
20674
|
+
"as": "span",
|
|
20675
|
+
"data-bold-transition": boldTransition ? "" : undefined,
|
|
20676
|
+
"data-capitalize": capitalize ? "" : undefined,
|
|
20677
|
+
...rest,
|
|
20678
|
+
"baseClassName": withPropsClassName("navi_text", rest.baseClassName)
|
|
20679
|
+
};
|
|
20680
|
+
const shouldPreserveSpacing = rest.as === "pre" || rest.flex || rest.grid;
|
|
20681
|
+
if (shouldPreserveSpacing) {
|
|
20682
|
+
boxProps.spacing = resolvedSpacing;
|
|
20683
|
+
} else {
|
|
20684
|
+
children = applySpacingOnTextChildren(children, resolvedSpacing, defaultSpace);
|
|
20685
|
+
}
|
|
20686
|
+
if (boldStable) {
|
|
20687
|
+
const {
|
|
20688
|
+
bold
|
|
20689
|
+
} = boxProps;
|
|
20690
|
+
return jsxs(Box, {
|
|
20691
|
+
...boxProps,
|
|
20692
|
+
bold: undefined,
|
|
20693
|
+
"data-bold": bold ? "" : undefined,
|
|
20694
|
+
children: [jsx("span", {
|
|
20695
|
+
className: "navi_text_bold_background",
|
|
20696
|
+
"aria-hidden": "true",
|
|
20697
|
+
children: children
|
|
20698
|
+
}), children, childrenOutsideFlow]
|
|
20699
|
+
});
|
|
20700
|
+
}
|
|
20701
|
+
if (preventBoldLayoutShift) {
|
|
20702
|
+
const alignX = rest.alignX || rest.align || "start";
|
|
20672
20703
|
|
|
20673
|
-
|
|
20674
|
-
|
|
20675
|
-
|
|
20676
|
-
|
|
20704
|
+
// La technique consiste a avoid un double gras qui force une taille
|
|
20705
|
+
// et la version light par dessus en position absolute
|
|
20706
|
+
// on la centre aussi pour donner l'impression que le gras s'applique depuis le centre
|
|
20707
|
+
// ne fonctionne que sur une seule ligne de texte (donc lorsque noWrap est actif)
|
|
20708
|
+
// on pourrait auto-active cela sur une prop genre boldCanChange
|
|
20709
|
+
return jsxs(Box, {
|
|
20710
|
+
...boxProps,
|
|
20711
|
+
children: [jsxs("span", {
|
|
20712
|
+
className: "navi_text_bold_wrapper",
|
|
20713
|
+
children: [jsx("span", {
|
|
20714
|
+
className: "navi_text_bold_clone",
|
|
20715
|
+
"aria-hidden": "true",
|
|
20716
|
+
children: children
|
|
20717
|
+
}), jsx("span", {
|
|
20718
|
+
className: "navi_text_bold_foreground",
|
|
20719
|
+
"data-align": alignX,
|
|
20720
|
+
children: children
|
|
20721
|
+
})]
|
|
20722
|
+
}), childrenOutsideFlow]
|
|
20723
|
+
});
|
|
20724
|
+
}
|
|
20725
|
+
return jsxs(Box, {
|
|
20726
|
+
...boxProps,
|
|
20727
|
+
children: [children, childrenOutsideFlow]
|
|
20728
|
+
});
|
|
20729
|
+
};
|
|
20677
20730
|
|
|
20678
|
-
|
|
20679
|
-
|
|
20680
|
-
|
|
20681
|
-
|
|
20682
|
-
|
|
20683
|
-
|
|
20684
|
-
|
|
20685
|
-
|
|
20686
|
-
}
|
|
20687
|
-
if (modifierFound) {
|
|
20688
|
-
continue;
|
|
20731
|
+
installImportMetaCssBuild(import.meta);const css$5 = /* css */`
|
|
20732
|
+
@layer navi {
|
|
20733
|
+
/* Ensure data attributes from box.jsx can win to update display */
|
|
20734
|
+
.navi_icon {
|
|
20735
|
+
display: inline-block;
|
|
20736
|
+
box-sizing: border-box;
|
|
20737
|
+
max-width: 100%;
|
|
20738
|
+
max-height: 100%;
|
|
20689
20739
|
}
|
|
20740
|
+
}
|
|
20690
20741
|
|
|
20691
|
-
|
|
20692
|
-
|
|
20693
|
-
|
|
20694
|
-
if (dash === "-") {
|
|
20695
|
-
// Only check ranges for single alphanumeric characters
|
|
20696
|
-
const eventKey = event.key.toLowerCase();
|
|
20697
|
-
if (eventKey.length !== 1) {
|
|
20698
|
-
return false; // Not a single character key
|
|
20699
|
-
}
|
|
20700
|
-
|
|
20701
|
-
// Only allow a-z and 0-9 ranges
|
|
20702
|
-
const isValidRange =
|
|
20703
|
-
(startChar >= "a" && endChar <= "z") ||
|
|
20704
|
-
(startChar >= "0" && endChar <= "9");
|
|
20705
|
-
|
|
20706
|
-
if (!isValidRange) {
|
|
20707
|
-
return false; // Invalid range pattern
|
|
20708
|
-
}
|
|
20742
|
+
.navi_icon {
|
|
20743
|
+
white-space: nowrap;
|
|
20744
|
+
vertical-align: inherit;
|
|
20709
20745
|
|
|
20710
|
-
|
|
20711
|
-
|
|
20712
|
-
|
|
20746
|
+
&[data-flow-inline] {
|
|
20747
|
+
width: 1em;
|
|
20748
|
+
height: 1em;
|
|
20749
|
+
}
|
|
20750
|
+
&[data-icon-char] {
|
|
20751
|
+
flex-grow: 0 !important;
|
|
20713
20752
|
|
|
20714
|
-
|
|
20715
|
-
|
|
20716
|
-
|
|
20717
|
-
|
|
20753
|
+
svg,
|
|
20754
|
+
img {
|
|
20755
|
+
width: 100%;
|
|
20756
|
+
height: 100%;
|
|
20757
|
+
}
|
|
20758
|
+
svg {
|
|
20759
|
+
overflow: visible;
|
|
20718
20760
|
}
|
|
20719
20761
|
}
|
|
20720
|
-
|
|
20721
|
-
|
|
20722
|
-
if (!isSameKey(event.key, key)) {
|
|
20723
|
-
return false;
|
|
20762
|
+
&[data-interactive] {
|
|
20763
|
+
cursor: pointer;
|
|
20724
20764
|
}
|
|
20725
20765
|
}
|
|
20726
|
-
return true;
|
|
20727
|
-
};
|
|
20728
|
-
// Configuration for mapping shortcut key names to browser event properties
|
|
20729
|
-
const modifierKeyMapping = {
|
|
20730
|
-
metaKey: {
|
|
20731
|
-
names: ["meta"],
|
|
20732
|
-
macNames: ["command", "cmd"],
|
|
20733
|
-
},
|
|
20734
|
-
ctrlKey: {
|
|
20735
|
-
names: ["control", "ctrl"],
|
|
20736
|
-
},
|
|
20737
|
-
shiftKey: {
|
|
20738
|
-
names: ["shift"],
|
|
20739
|
-
},
|
|
20740
|
-
altKey: {
|
|
20741
|
-
names: ["alt"],
|
|
20742
|
-
macNames: ["option"],
|
|
20743
|
-
},
|
|
20744
|
-
};
|
|
20745
|
-
const isSameKey = (browserEventKey, key) => {
|
|
20746
|
-
browserEventKey = browserEventKey.toLowerCase();
|
|
20747
|
-
key = key.toLowerCase();
|
|
20748
20766
|
|
|
20749
|
-
|
|
20750
|
-
|
|
20767
|
+
.navi_icon_char_slot {
|
|
20768
|
+
opacity: 0;
|
|
20769
|
+
cursor: default;
|
|
20770
|
+
user-select: none;
|
|
20751
20771
|
}
|
|
20772
|
+
.navi_text.navi_icon_foreground {
|
|
20773
|
+
position: absolute;
|
|
20774
|
+
inset: 0;
|
|
20775
|
+
display: inline-flex;
|
|
20752
20776
|
|
|
20753
|
-
|
|
20754
|
-
|
|
20755
|
-
|
|
20756
|
-
|
|
20757
|
-
|
|
20777
|
+
& > .navi_text {
|
|
20778
|
+
display: flex;
|
|
20779
|
+
aspect-ratio: 1 / 1;
|
|
20780
|
+
min-width: 0;
|
|
20781
|
+
height: 100%;
|
|
20782
|
+
max-height: 1em;
|
|
20783
|
+
align-items: center;
|
|
20784
|
+
justify-content: center;
|
|
20758
20785
|
}
|
|
20759
20786
|
}
|
|
20760
20787
|
|
|
20761
|
-
|
|
20788
|
+
.navi_icon > svg,
|
|
20789
|
+
.navi_icon > img {
|
|
20790
|
+
width: 100%;
|
|
20791
|
+
height: 100%;
|
|
20792
|
+
backface-visibility: hidden;
|
|
20793
|
+
}
|
|
20794
|
+
.navi_icon[data-width-fixed] > svg,
|
|
20795
|
+
.navi_icon[data-width-fixed] > img {
|
|
20796
|
+
width: 100%;
|
|
20797
|
+
height: auto;
|
|
20798
|
+
}
|
|
20799
|
+
.navi_icon[data-height-fixed] > svg,
|
|
20800
|
+
.navi_icon[data-height-fixed] > img {
|
|
20801
|
+
width: auto;
|
|
20802
|
+
height: 100%;
|
|
20803
|
+
}
|
|
20804
|
+
.navi_icon[data-width-fixed][data-height-fixed] > svg,
|
|
20805
|
+
.navi_icon[data-width-fixed][data-height-fixed] > img {
|
|
20806
|
+
width: 100%;
|
|
20807
|
+
height: 100%;
|
|
20808
|
+
}
|
|
20809
|
+
`;
|
|
20810
|
+
const Icon = ({
|
|
20811
|
+
href,
|
|
20812
|
+
children,
|
|
20813
|
+
charWidth = 1,
|
|
20814
|
+
// 0 (zéro) is the real char width
|
|
20815
|
+
// but 2 zéros gives too big icons
|
|
20816
|
+
// while 1 "W" gives a nice result
|
|
20817
|
+
baseChar = "W",
|
|
20818
|
+
decorative,
|
|
20819
|
+
onClick,
|
|
20820
|
+
...props
|
|
20821
|
+
}) => {
|
|
20822
|
+
import.meta.css = [css$5, "@jsenv/navi/src/text/icon.jsx"];
|
|
20823
|
+
const innerChildren = href ? jsx("svg", {
|
|
20824
|
+
width: "100%",
|
|
20825
|
+
height: "100%",
|
|
20826
|
+
children: jsx("use", {
|
|
20827
|
+
href: href
|
|
20828
|
+
})
|
|
20829
|
+
}) : children;
|
|
20830
|
+
let {
|
|
20831
|
+
flex,
|
|
20832
|
+
grid,
|
|
20833
|
+
width,
|
|
20834
|
+
height
|
|
20835
|
+
} = props;
|
|
20836
|
+
if (width === "auto") {
|
|
20837
|
+
width = undefined;
|
|
20838
|
+
}
|
|
20839
|
+
if (height === "auto") {
|
|
20840
|
+
height = undefined;
|
|
20841
|
+
}
|
|
20842
|
+
const hasExplicitWidth = width !== undefined;
|
|
20843
|
+
const hasExplicitHeight = height !== undefined;
|
|
20844
|
+
const widthFixed = hasExplicitWidth || hasExplicitHeight && (props.square || props.circle || props.aspectRatio);
|
|
20845
|
+
const heightFixed = hasExplicitHeight || hasExplicitWidth && (props.square || props.circle || props.aspectRatio);
|
|
20846
|
+
if (widthFixed || heightFixed) {
|
|
20847
|
+
if (flex === undefined) {
|
|
20848
|
+
flex = "x";
|
|
20849
|
+
}
|
|
20850
|
+
} else if (decorative === undefined && !onClick) {
|
|
20851
|
+
decorative = true;
|
|
20852
|
+
}
|
|
20853
|
+
const ariaProps = decorative ? {
|
|
20854
|
+
"aria-hidden": "true"
|
|
20855
|
+
} : {};
|
|
20856
|
+
const textRef = useRef();
|
|
20857
|
+
if (typeof children === "string") {
|
|
20858
|
+
return jsx(Text, {
|
|
20859
|
+
...props,
|
|
20860
|
+
...ariaProps,
|
|
20861
|
+
"data-icon-text": "",
|
|
20862
|
+
children: children
|
|
20863
|
+
});
|
|
20864
|
+
}
|
|
20865
|
+
if (flex || grid) {
|
|
20866
|
+
return jsx(Box, {
|
|
20867
|
+
square: true,
|
|
20868
|
+
...props,
|
|
20869
|
+
...ariaProps,
|
|
20870
|
+
flex: flex,
|
|
20871
|
+
baseClassName: "navi_icon",
|
|
20872
|
+
"data-width-fixed": widthFixed ? "" : undefined,
|
|
20873
|
+
"data-height-fixed": heightFixed ? "" : undefined,
|
|
20874
|
+
"data-interactive": onClick ? "" : undefined,
|
|
20875
|
+
onClick: onClick,
|
|
20876
|
+
children: innerChildren
|
|
20877
|
+
});
|
|
20878
|
+
}
|
|
20879
|
+
const invisibleText = baseChar.repeat(charWidth);
|
|
20880
|
+
return jsx(SurroundingTextAligner, {
|
|
20881
|
+
align: "center",
|
|
20882
|
+
childRef: textRef,
|
|
20883
|
+
children: jsxs(Text, {
|
|
20884
|
+
...props,
|
|
20885
|
+
...ariaProps,
|
|
20886
|
+
className: withPropsClassName("navi_icon", props.className),
|
|
20887
|
+
spacing: "pre",
|
|
20888
|
+
"data-icon-char": "",
|
|
20889
|
+
"data-width-fixed": widthFixed ? "" : undefined,
|
|
20890
|
+
"data-height-fixed": heightFixed ? "" : undefined,
|
|
20891
|
+
"data-interactive": onClick ? "" : undefined,
|
|
20892
|
+
onClick: onClick,
|
|
20893
|
+
ref: textRef,
|
|
20894
|
+
children: [jsx("span", {
|
|
20895
|
+
className: "navi_icon_char_slot",
|
|
20896
|
+
"aria-hidden": "true",
|
|
20897
|
+
children: invisibleText
|
|
20898
|
+
}), jsx(Text, {
|
|
20899
|
+
className: "navi_icon_foreground",
|
|
20900
|
+
spacing: "pre",
|
|
20901
|
+
children: innerChildren
|
|
20902
|
+
})]
|
|
20903
|
+
})
|
|
20904
|
+
});
|
|
20762
20905
|
};
|
|
20763
20906
|
|
|
20764
20907
|
const useFormEvents = (
|
|
@@ -21282,7 +21425,7 @@ const useUIState = (uiStateController) => {
|
|
|
21282
21425
|
return trackedUIState;
|
|
21283
21426
|
};
|
|
21284
21427
|
|
|
21285
|
-
installImportMetaCssBuild(import.meta);
|
|
21428
|
+
installImportMetaCssBuild(import.meta);const css$4 = /* css */`
|
|
21286
21429
|
@layer navi {
|
|
21287
21430
|
.navi_button {
|
|
21288
21431
|
--button-outline-width: 1px;
|
|
@@ -21408,6 +21551,7 @@ installImportMetaCssBuild(import.meta);import.meta.css = [/* css */`
|
|
|
21408
21551
|
align-items: inherit;
|
|
21409
21552
|
justify-content: inherit;
|
|
21410
21553
|
color: var(--x-button-color);
|
|
21554
|
+
vertical-align: inherit;
|
|
21411
21555
|
background: var(--x-button-background);
|
|
21412
21556
|
background-color: var(
|
|
21413
21557
|
--x-button-background-color,
|
|
@@ -21531,8 +21675,9 @@ installImportMetaCssBuild(import.meta);import.meta.css = [/* css */`
|
|
|
21531
21675
|
--x-button-border-color: var(--callout-color);
|
|
21532
21676
|
}
|
|
21533
21677
|
}
|
|
21534
|
-
|
|
21678
|
+
`;
|
|
21535
21679
|
const Button = props => {
|
|
21680
|
+
import.meta.css = [css$4, "@jsenv/navi/src/field/button.jsx"];
|
|
21536
21681
|
return renderActionableComponent(props, {
|
|
21537
21682
|
Basic: ButtonBasic,
|
|
21538
21683
|
WithAction: ButtonWithAction,
|
|
@@ -21590,6 +21735,7 @@ const ButtonBasic = props => {
|
|
|
21590
21735
|
icon,
|
|
21591
21736
|
revealOnInteraction = icon,
|
|
21592
21737
|
discrete = icon && !revealOnInteraction,
|
|
21738
|
+
spacing,
|
|
21593
21739
|
children,
|
|
21594
21740
|
...rest
|
|
21595
21741
|
} = props;
|
|
@@ -21603,11 +21749,12 @@ const ButtonBasic = props => {
|
|
|
21603
21749
|
const renderButtonContent = buttonProps => {
|
|
21604
21750
|
return jsxs(Text, {
|
|
21605
21751
|
...buttonProps,
|
|
21752
|
+
spacing: spacing,
|
|
21606
21753
|
className: "navi_button_content",
|
|
21607
21754
|
children: [children, jsx(ButtonShadow, {})]
|
|
21608
21755
|
});
|
|
21609
21756
|
};
|
|
21610
|
-
const renderButtonContentMemoized = useCallback(renderButtonContent, [children]);
|
|
21757
|
+
const renderButtonContentMemoized = useCallback(renderButtonContent, [children, spacing]);
|
|
21611
21758
|
return jsxs(Box, {
|
|
21612
21759
|
"data-readonly-silent": innerLoading ? "" : undefined,
|
|
21613
21760
|
...remainingProps,
|
|
@@ -22095,7 +22242,7 @@ const useDimColorWhen = (elementRef, shouldDim) => {
|
|
|
22095
22242
|
};
|
|
22096
22243
|
|
|
22097
22244
|
installImportMetaCssBuild(import.meta);/* eslint-disable jsenv/no-unknown-params */
|
|
22098
|
-
|
|
22245
|
+
const css$3 = /* css */`
|
|
22099
22246
|
@layer navi {
|
|
22100
22247
|
.navi_link {
|
|
22101
22248
|
--link-border-radius: unset;
|
|
@@ -22367,7 +22514,7 @@ import.meta.css = [/* css */`
|
|
|
22367
22514
|
.navi_title .navi_link[data-reveal-on-interaction] {
|
|
22368
22515
|
top: 0.25em;
|
|
22369
22516
|
}
|
|
22370
|
-
|
|
22517
|
+
`;
|
|
22371
22518
|
const LinkStyleCSSVars = {
|
|
22372
22519
|
"outlineColor": "--link-outline-color",
|
|
22373
22520
|
"borderRadius": "--link-border-radius",
|
|
@@ -22416,6 +22563,7 @@ Object.assign(PSEUDO_CLASSES, {
|
|
|
22416
22563
|
}
|
|
22417
22564
|
});
|
|
22418
22565
|
const Link = props => {
|
|
22566
|
+
import.meta.css = [css$3, "@jsenv/navi/src/nav/link/link.jsx"];
|
|
22419
22567
|
return renderActionableComponent(props, {
|
|
22420
22568
|
Basic: LinkBasic,
|
|
22421
22569
|
WithAction: LinkWithAction
|
|
@@ -22564,6 +22712,7 @@ const LinkPlain = props => {
|
|
|
22564
22712
|
e.detail.setValue(value);
|
|
22565
22713
|
},
|
|
22566
22714
|
preventBoldLayoutShift: currentEffectBold,
|
|
22715
|
+
preventSpaceUnderlines: true,
|
|
22567
22716
|
overflowEllipsis: overflowEllipsis
|
|
22568
22717
|
// Visual
|
|
22569
22718
|
,
|
|
@@ -22676,7 +22825,7 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
22676
22825
|
* TabList component with support for horizontal and vertical layouts
|
|
22677
22826
|
* https://dribbble.com/search/tabs
|
|
22678
22827
|
*/
|
|
22679
|
-
|
|
22828
|
+
const css$2 = /* css */`
|
|
22680
22829
|
@layer navi {
|
|
22681
22830
|
.navi_nav {
|
|
22682
22831
|
--nav-border: none;
|
|
@@ -22785,7 +22934,7 @@ import.meta.css = [/* css */`
|
|
|
22785
22934
|
}
|
|
22786
22935
|
}
|
|
22787
22936
|
}
|
|
22788
|
-
|
|
22937
|
+
`;
|
|
22789
22938
|
const NavStyleCSSVars = {
|
|
22790
22939
|
border: "--nav-border",
|
|
22791
22940
|
borderRadius: "--nav-border-radius",
|
|
@@ -22810,6 +22959,7 @@ const Nav = ({
|
|
|
22810
22959
|
panelBorderConnection,
|
|
22811
22960
|
...props
|
|
22812
22961
|
}) => {
|
|
22962
|
+
import.meta.css = [css$2, "@jsenv/navi/src/nav/link/nav.jsx"];
|
|
22813
22963
|
children = toChildArray(children);
|
|
22814
22964
|
return jsx(Box, {
|
|
22815
22965
|
as: "nav",
|
|
@@ -30397,6 +30547,8 @@ installImportMetaCssBuild(import.meta);const css = /* css */`
|
|
|
30397
30547
|
@layer navi {
|
|
30398
30548
|
}
|
|
30399
30549
|
.navi_text.navi_badge_count {
|
|
30550
|
+
/* Important to prevent anchor from breaking to a new line */
|
|
30551
|
+
white-space: nowrap;
|
|
30400
30552
|
--font-size: 0.7em;
|
|
30401
30553
|
--x-background: var(--background);
|
|
30402
30554
|
--x-background-color: var(--background-color, var(--x-background));
|
|
@@ -30405,9 +30557,9 @@ installImportMetaCssBuild(import.meta);const css = /* css */`
|
|
|
30405
30557
|
--padding-x: 0.5em;
|
|
30406
30558
|
--padding-y: 0.2em;
|
|
30407
30559
|
position: relative;
|
|
30408
|
-
display: inline-block;
|
|
30409
30560
|
color: var(--x-color);
|
|
30410
30561
|
font-size: var(--font-size);
|
|
30562
|
+
vertical-align: inherit;
|
|
30411
30563
|
|
|
30412
30564
|
&[data-dark-background] {
|
|
30413
30565
|
--x-color-contrasting: var(--navi-color-white);
|
|
@@ -30437,11 +30589,12 @@ installImportMetaCssBuild(import.meta);const css = /* css */`
|
|
|
30437
30589
|
|
|
30438
30590
|
/* For ellipse + single char force the circle aspect as it's prettier */
|
|
30439
30591
|
&[data-single-char] {
|
|
30592
|
+
display: inline-block;
|
|
30440
30593
|
aspect-ratio: 1/1;
|
|
30441
|
-
height: 1.
|
|
30594
|
+
height: 1.6em;
|
|
30442
30595
|
padding: 0;
|
|
30443
30596
|
text-align: center;
|
|
30444
|
-
line-height: 1.
|
|
30597
|
+
line-height: 1.6em;
|
|
30445
30598
|
}
|
|
30446
30599
|
}
|
|
30447
30600
|
|
|
@@ -30461,19 +30614,19 @@ installImportMetaCssBuild(import.meta);const css = /* css */`
|
|
|
30461
30614
|
border-radius: 50%;
|
|
30462
30615
|
|
|
30463
30616
|
&[data-single-char] {
|
|
30464
|
-
--x-radius: 1.
|
|
30617
|
+
--x-radius: 1.6em;
|
|
30465
30618
|
--x-number-font-size: unset;
|
|
30466
30619
|
}
|
|
30467
30620
|
&[data-two-chars] {
|
|
30468
|
-
--x-radius:
|
|
30469
|
-
--x-number-font-size:
|
|
30621
|
+
--x-radius: 2em;
|
|
30622
|
+
--x-number-font-size: unset;
|
|
30470
30623
|
}
|
|
30471
30624
|
&[data-three-chars] {
|
|
30472
30625
|
--x-radius: 2.4em;
|
|
30473
30626
|
--x-number-font-size: 0.8em;
|
|
30474
30627
|
}
|
|
30475
30628
|
&[data-four-chars] {
|
|
30476
|
-
--x-radius: 2.
|
|
30629
|
+
--x-radius: 2.4em;
|
|
30477
30630
|
--x-number-font-size: 0.8em;
|
|
30478
30631
|
}
|
|
30479
30632
|
|
|
@@ -30532,25 +30685,33 @@ const BadgeCount = ({
|
|
|
30532
30685
|
circle = false;
|
|
30533
30686
|
}
|
|
30534
30687
|
if (circle) {
|
|
30535
|
-
return
|
|
30536
|
-
|
|
30537
|
-
|
|
30538
|
-
|
|
30539
|
-
|
|
30540
|
-
|
|
30541
|
-
|
|
30688
|
+
return jsx(SurroundingTextAligner, {
|
|
30689
|
+
align: "center",
|
|
30690
|
+
childRef: ref,
|
|
30691
|
+
children: jsxs(BadgeCountCircle, {
|
|
30692
|
+
...props,
|
|
30693
|
+
loading: loading,
|
|
30694
|
+
ref: ref,
|
|
30695
|
+
hasOverflow: hasOverflow,
|
|
30696
|
+
charCount: charCount,
|
|
30697
|
+
children: [valueDisplayed, hasOverflow && maxElement]
|
|
30698
|
+
})
|
|
30542
30699
|
});
|
|
30543
30700
|
}
|
|
30544
30701
|
const valueFormatted = typeof valueDisplayed === "number" ? formatNumber(valueDisplayed, {
|
|
30545
30702
|
lang
|
|
30546
30703
|
}) : valueDisplayed;
|
|
30547
|
-
return
|
|
30548
|
-
|
|
30549
|
-
|
|
30550
|
-
|
|
30551
|
-
|
|
30552
|
-
|
|
30553
|
-
|
|
30704
|
+
return jsx(SurroundingTextAligner, {
|
|
30705
|
+
align: "center",
|
|
30706
|
+
childRef: ref,
|
|
30707
|
+
children: jsxs(BadgeCountEllipse, {
|
|
30708
|
+
...props,
|
|
30709
|
+
loading: loading,
|
|
30710
|
+
ref: ref,
|
|
30711
|
+
hasOverflow: hasOverflow,
|
|
30712
|
+
charCount: charCount,
|
|
30713
|
+
children: [valueFormatted, hasOverflow && maxElement]
|
|
30714
|
+
})
|
|
30554
30715
|
});
|
|
30555
30716
|
};
|
|
30556
30717
|
const applyMaxToValue = (max, value) => {
|
|
@@ -30591,14 +30752,8 @@ const BadgeCountEllipse = ({
|
|
|
30591
30752
|
spacing: "pre",
|
|
30592
30753
|
children: loading ? jsx(Icon, {
|
|
30593
30754
|
children: jsx(LoadingDots, {})
|
|
30594
|
-
}) :
|
|
30595
|
-
children:
|
|
30596
|
-
style: "user-select: none",
|
|
30597
|
-
children: "\u200B"
|
|
30598
|
-
}), children, jsx("span", {
|
|
30599
|
-
style: "user-select: none",
|
|
30600
|
-
children: "\u200B"
|
|
30601
|
-
})]
|
|
30755
|
+
}) : jsx(Fragment, {
|
|
30756
|
+
children: children
|
|
30602
30757
|
})
|
|
30603
30758
|
});
|
|
30604
30759
|
};
|
|
@@ -30627,17 +30782,11 @@ const BadgeCountCircle = ({
|
|
|
30627
30782
|
spacing: "pre",
|
|
30628
30783
|
children: loading ? jsx(Icon, {
|
|
30629
30784
|
children: jsx(LoadingDots, {})
|
|
30630
|
-
}) :
|
|
30631
|
-
children:
|
|
30632
|
-
style: "user-select: none",
|
|
30633
|
-
children: "\u200B"
|
|
30634
|
-
}), jsx("span", {
|
|
30785
|
+
}) : jsx(Fragment, {
|
|
30786
|
+
children: jsx("span", {
|
|
30635
30787
|
className: "navi_badge_count_text",
|
|
30636
30788
|
children: children
|
|
30637
|
-
})
|
|
30638
|
-
style: "user-select: none",
|
|
30639
|
-
children: "\u200B"
|
|
30640
|
-
})]
|
|
30789
|
+
})
|
|
30641
30790
|
})
|
|
30642
30791
|
});
|
|
30643
30792
|
};
|