@jsenv/navi 0.16.58 → 0.16.59

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import { installImportMetaCss } from "./jsenv_navi_side_effects.js";
2
2
  import { useErrorBoundary, useLayoutEffect, useEffect, useCallback, useRef, useState, useContext, useMemo, useImperativeHandle, useId } from "preact/hooks";
3
3
  import { jsxs, jsx, Fragment } from "preact/jsx-runtime";
4
- import { createIterableWeakSet, mergeOneStyle, stringifyStyle, createPubSub, mergeTwoStyles, normalizeStyles, createGroupTransitionController, getElementSignature, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, resolveCSSSize, findBefore, findAfter, createValueEffect, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, allowWheelThrough, resolveCSSColor, createStyleController, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, hasCSSSizeUnit, activeElementSignal, canInterceptKeys, pickLightOrDark, resolveColorLuminance, initFocusGroup, dragAfterThreshold, getScrollContainer, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement, elementIsFocusable } from "@jsenv/dom";
4
+ import { createIterableWeakSet, mergeOneStyle, stringifyStyle, createPubSub, mergeTwoStyles, normalizeStyles, createGroupTransitionController, getElementSignature, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, findBefore, findAfter, createValueEffect, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, allowWheelThrough, resolveCSSColor, createStyleController, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, hasCSSSizeUnit, resolveCSSSize, activeElementSignal, canInterceptKeys, pickLightOrDark, resolveColorLuminance, initFocusGroup, dragAfterThreshold, getScrollContainer, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement, elementIsFocusable } from "@jsenv/dom";
5
5
  import { prefixFirstAndIndentRemainingLines } from "@jsenv/humanize";
6
6
  import { effect, signal, computed, batch, useSignal } from "@preact/signals";
7
7
  import { createValidity } from "@jsenv/validity";
@@ -1448,6 +1448,7 @@ const createAction = (callback, rootOptions = {}) => {
1448
1448
  for (const key of keyArray) {
1449
1449
  const signalForThisKey = signalMap.get(key);
1450
1450
  if (signalForThisKey) {
1451
+ // eslint-disable-next-line signals/no-conditional-value-read
1451
1452
  params[key] = signalForThisKey.value;
1452
1453
  } else {
1453
1454
  params[key] = staticParams[key];
@@ -12102,691 +12103,141 @@ const renderActionableComponent = (props, {
12102
12103
  });
12103
12104
  };
12104
12105
 
12105
- const useDebounceTrue = (value, delay = 300) => {
12106
- const [debouncedTrue, setDebouncedTrue] = useState(false);
12107
- const timerRef = useRef(null);
12108
-
12109
- useLayoutEffect(() => {
12110
- // If value is true or becomes true, start a timer
12111
- if (value) {
12112
- timerRef.current = setTimeout(() => {
12113
- setDebouncedTrue(true);
12114
- }, delay);
12115
- } else {
12116
- // If value becomes false, clear any pending timer and immediately set to false
12117
- if (timerRef.current) {
12118
- clearTimeout(timerRef.current);
12119
- timerRef.current = null;
12120
- }
12121
- setDebouncedTrue(false);
12122
- }
12123
-
12124
- // Cleanup function
12125
- return () => {
12126
- if (timerRef.current) {
12127
- clearTimeout(timerRef.current);
12128
- }
12129
- };
12130
- }, [value, delay]);
12131
-
12132
- return debouncedTrue;
12133
- };
12106
+ /**
12107
+ * Custom hook creating a stable callback that doesn't trigger re-renders.
12108
+ *
12109
+ * PROBLEM: Parent components often forget to use useCallback, causing library
12110
+ * components to re-render unnecessarily when receiving callback props.
12111
+ *
12112
+ * SOLUTION: Library components can use this hook to create stable callback
12113
+ * references internally, making them defensive against parents who don't
12114
+ * optimize their callbacks. This ensures library components don't force
12115
+ * consumers to think about useCallback.
12116
+ *
12117
+ * USAGE:
12118
+ * ```js
12119
+ * // Parent component (consumer) - no useCallback needed
12120
+ * const Parent = () => {
12121
+ * const [count, setCount] = useState(0);
12122
+ *
12123
+ * // Parent naturally creates new function reference each render
12124
+ * // (forgetting useCallback is common and shouldn't break performance)
12125
+ * return <LibraryButton onClick={(e) => setCount(count + 1)} />;
12126
+ * };
12127
+ *
12128
+ * // Library component - defensive against changing callbacks
12129
+ * const LibraryButton = ({ onClick }) => {
12130
+ * // ✅ Create stable reference from parent's potentially changing callback
12131
+ * const stableClick = useStableCallback(onClick);
12132
+ *
12133
+ * // Internal expensive components won't re-render when parent updates
12134
+ * return <ExpensiveInternalButton onClick={stableClick} />;
12135
+ * };
12136
+ *
12137
+ * // Deep internal component gets stable reference
12138
+ * const ExpensiveInternalButton = memo(({ onClick }) => {
12139
+ * // This won't re-render when Parent's count changes
12140
+ * // But onClick will always call the latest Parent callback
12141
+ * return <button onClick={onClick}>Click me</button>;
12142
+ * });
12143
+ * ```
12144
+ *
12145
+ * Perfect for library components that need performance without burdening consumers.
12146
+ */
12134
12147
 
12135
- const useNetworkSpeed = () => {
12136
- return networkSpeedSignal.value;
12137
- };
12138
12148
 
12139
- const connection =
12140
- window.navigator.connection ||
12141
- window.navigator.mozConnection ||
12142
- window.navigator.webkitConnection;
12149
+ const useStableCallback = (callback, mapper) => {
12150
+ const callbackRef = useRef();
12151
+ callbackRef.current = callback;
12152
+ const stableCallbackRef = useRef();
12143
12153
 
12144
- const getNetworkSpeed = () => {
12145
- // ✅ Network Information API (support moderne)
12146
- if (!connection) {
12147
- return "3g";
12148
- }
12149
- if (connection) {
12150
- const effectiveType = connection.effectiveType;
12151
- if (effectiveType) {
12152
- return effectiveType; // "slow-2g", "2g", "3g", "4g", "5g"
12153
- }
12154
- const downlink = connection.downlink;
12155
- if (downlink) {
12156
- // downlink is in Mbps
12157
- if (downlink < 1) return "slow-2g"; // < 1 Mbps
12158
- if (downlink < 2.5) return "2g"; // 1-2.5 Mbps
12159
- if (downlink < 10) return "3g"; // 2.5-10 Mbps
12160
- return "4g"; // > 10 Mbps
12161
- }
12154
+ // Return original falsy value directly when callback is not a function
12155
+ if (!callback) {
12156
+ return callback;
12162
12157
  }
12163
- return "3g";
12164
- };
12165
-
12166
- const updateNetworkSpeed = () => {
12167
- networkSpeedSignal.value = getNetworkSpeed();
12168
- };
12169
-
12170
- const networkSpeedSignal = signal(getNetworkSpeed());
12171
-
12172
- const setupNetworkMonitoring = () => {
12173
- const cleanupFunctions = [];
12174
-
12175
- // ✅ 1. Écouter les changements natifs
12176
12158
 
12177
- if (connection) {
12178
- connection.addEventListener("change", updateNetworkSpeed);
12179
- cleanupFunctions.push(() => {
12180
- connection.removeEventListener("change", updateNetworkSpeed);
12181
- });
12159
+ const existingStableCallback = stableCallbackRef.current;
12160
+ if (existingStableCallback) {
12161
+ return existingStableCallback;
12182
12162
  }
12183
-
12184
- // 2. Polling de backup (toutes les 60 secondes)
12185
- const pollInterval = setInterval(updateNetworkSpeed, 60000);
12186
- cleanupFunctions.push(() => clearInterval(pollInterval));
12187
-
12188
- // ✅ 3. Vérifier lors de la reprise d'activité
12189
- const handleVisibilityChange = () => {
12190
- if (!document.hidden) {
12191
- updateNetworkSpeed();
12192
- }
12193
- };
12194
-
12195
- document.addEventListener("visibilitychange", handleVisibilityChange);
12196
- cleanupFunctions.push(() => {
12197
- document.removeEventListener("visibilitychange", handleVisibilityChange);
12198
- });
12199
-
12200
- // ✅ 4. Vérifier lors de la reprise de connexion
12201
- const handleOnline = () => {
12202
- updateNetworkSpeed();
12203
- };
12204
-
12205
- window.addEventListener("online", handleOnline);
12206
- cleanupFunctions.push(() => {
12207
- window.removeEventListener("online", handleOnline);
12208
- });
12209
-
12210
- // Cleanup global
12211
- return () => {
12212
- cleanupFunctions.forEach((cleanup) => cleanup());
12163
+ const stableCallback = (...args) => {
12164
+ const currentCallback = callbackRef.current;
12165
+ return currentCallback(...args);
12213
12166
  };
12167
+ stableCallbackRef.current = stableCallback;
12168
+ return stableCallback;
12214
12169
  };
12215
- setupNetworkMonitoring();
12216
-
12217
- installImportMetaCss(import.meta);import.meta.css = /* css */`
12218
- .navi_rectangle_loading {
12219
- position: relative;
12220
- display: flex;
12221
- width: 100%;
12222
- height: 100%;
12223
- opacity: 0;
12224
- }
12225
12170
 
12226
- .navi_rectangle_loading[data-visible] {
12227
- opacity: 1;
12171
+ const DEBUG = {
12172
+ registration: false,
12173
+ // Element registration/unregistration
12174
+ interaction: false,
12175
+ // Click and keyboard interactions
12176
+ selection: false,
12177
+ // Selection state changes (set, add, remove, toggle)
12178
+ navigation: false,
12179
+ // Arrow key navigation and element finding
12180
+ valueExtraction: false // Value extraction from elements
12181
+ };
12182
+ const debug = (category, ...args) => {
12183
+ if (DEBUG[category]) {
12184
+ console.debug(`[selection:${category}]`, ...args);
12228
12185
  }
12229
- `;
12230
- const RectangleLoading = ({
12231
- shouldShowSpinner,
12232
- color = "currentColor",
12233
- radius = 0,
12234
- size = 2
12186
+ };
12187
+ const SelectionContext = createContext();
12188
+ const useSelectionController = ({
12189
+ elementRef,
12190
+ layout,
12191
+ value,
12192
+ onChange,
12193
+ multiple,
12194
+ selectAllName
12235
12195
  }) => {
12236
- const containerRef = useRef(null);
12237
- const [containerWidth, setContainerWidth] = useState(0);
12238
- const [containerHeight, setContainerHeight] = useState(0);
12196
+ if (!elementRef) {
12197
+ throw new Error("useSelectionController: elementRef is required");
12198
+ }
12199
+ onChange = useStableCallback(onChange);
12200
+ const currentValueRef = useRef(value);
12201
+ currentValueRef.current = value;
12202
+ const lastInternalValueRef = useRef(null);
12203
+ const selectionController = useMemo(() => {
12204
+ const innerOnChange = (newValue, ...args) => {
12205
+ lastInternalValueRef.current = newValue;
12206
+ onChange?.(newValue, ...args);
12207
+ };
12208
+ const getCurrentValue = () => currentValueRef.current;
12209
+ if (layout === "grid") {
12210
+ return createGridSelectionController({
12211
+ getCurrentValue,
12212
+ onChange: innerOnChange,
12213
+ enabled: Boolean(onChange),
12214
+ multiple,
12215
+ selectAllName
12216
+ });
12217
+ }
12218
+ return createLinearSelectionController({
12219
+ getCurrentValue,
12220
+ onChange: innerOnChange,
12221
+ layout,
12222
+ elementRef,
12223
+ multiple,
12224
+ enabled: Boolean(onChange),
12225
+ selectAllName
12226
+ });
12227
+ }, [layout, multiple, elementRef]);
12228
+ useEffect(() => {
12229
+ selectionController.element = elementRef.current;
12230
+ }, [selectionController]);
12239
12231
  useLayoutEffect(() => {
12240
- const container = containerRef.current;
12241
- if (!container) {
12242
- return null;
12243
- }
12244
- const {
12245
- width,
12246
- height
12247
- } = container.getBoundingClientRect();
12248
- setContainerWidth(width);
12249
- setContainerHeight(height);
12250
- let animationFrameId = null;
12251
- // Create a resize observer to detect changes in the container's dimensions
12252
- const resizeObserver = new ResizeObserver(entries => {
12253
- // Use requestAnimationFrame to debounce updates
12254
- if (animationFrameId) {
12255
- cancelAnimationFrame(animationFrameId);
12256
- }
12257
- animationFrameId = requestAnimationFrame(() => {
12258
- const [containerEntry] = entries;
12259
- const {
12260
- width,
12261
- height
12262
- } = containerEntry.contentRect;
12263
- setContainerWidth(width);
12264
- setContainerHeight(height);
12265
- });
12266
- });
12267
- resizeObserver.observe(container);
12268
- return () => {
12269
- if (animationFrameId) {
12270
- cancelAnimationFrame(animationFrameId);
12271
- }
12272
- resizeObserver.disconnect();
12273
- };
12274
- }, []);
12275
- return jsx("span", {
12276
- ref: containerRef,
12277
- className: "navi_rectangle_loading",
12278
- "data-visible": shouldShowSpinner ? "" : undefined,
12279
- children: containerWidth > 0 && containerHeight > 0 && jsx(RectangleLoadingSvg, {
12280
- radius: radius,
12281
- color: color,
12282
- width: containerWidth,
12283
- height: containerHeight,
12284
- strokeWidth: size
12285
- })
12286
- });
12287
- };
12288
- const RectangleLoadingSvg = ({
12289
- width,
12290
- height,
12291
- color,
12292
- radius,
12293
- trailColor = "transparent",
12294
- strokeWidth
12295
- }) => {
12296
- const margin = Math.max(2, Math.min(width, height) * 0.03);
12297
-
12298
- // Calculate the drawable area
12299
- const drawableWidth = width - margin * 2;
12300
- const drawableHeight = height - margin * 2;
12301
-
12302
- // ✅ Check if this should be a circle - only if width and height are nearly equal
12303
- const maxPossibleRadius = Math.min(drawableWidth, drawableHeight) / 2;
12304
- const actualRadius = Math.min(radius || Math.min(drawableWidth, drawableHeight) * 0.05, maxPossibleRadius // ✅ Limité au radius maximum possible
12305
- );
12306
- const aspectRatio = Math.max(drawableWidth, drawableHeight) / Math.min(drawableWidth, drawableHeight);
12307
- const isNearlySquare = aspectRatio <= 1.2; // Allow some tolerance for nearly square shapes
12308
- const isCircle = isNearlySquare && actualRadius >= maxPossibleRadius * 0.95;
12309
- let pathLength;
12310
- let rectPath;
12311
- if (isCircle) {
12312
- // ✅ Circle: perimeter = 2πr
12313
- pathLength = 2 * Math.PI * actualRadius;
12314
-
12315
- // ✅ Circle path centered in the drawable area
12316
- const centerX = margin + drawableWidth / 2;
12317
- const centerY = margin + drawableHeight / 2;
12318
- rectPath = `
12319
- M ${centerX + actualRadius},${centerY}
12320
- A ${actualRadius},${actualRadius} 0 1 1 ${centerX - actualRadius},${centerY}
12321
- A ${actualRadius},${actualRadius} 0 1 1 ${centerX + actualRadius},${centerY}
12322
- `;
12323
- } else {
12324
- // ✅ Rectangle: calculate perimeter properly
12325
- const straightEdges = 2 * (drawableWidth - 2 * actualRadius) + 2 * (drawableHeight - 2 * actualRadius);
12326
- const cornerArcs = actualRadius > 0 ? 2 * Math.PI * actualRadius : 0;
12327
- pathLength = straightEdges + cornerArcs;
12328
- rectPath = `
12329
- M ${margin + actualRadius},${margin}
12330
- L ${margin + drawableWidth - actualRadius},${margin}
12331
- A ${actualRadius},${actualRadius} 0 0 1 ${margin + drawableWidth},${margin + actualRadius}
12332
- L ${margin + drawableWidth},${margin + drawableHeight - actualRadius}
12333
- A ${actualRadius},${actualRadius} 0 0 1 ${margin + drawableWidth - actualRadius},${margin + drawableHeight}
12334
- L ${margin + actualRadius},${margin + drawableHeight}
12335
- A ${actualRadius},${actualRadius} 0 0 1 ${margin},${margin + drawableHeight - actualRadius}
12336
- L ${margin},${margin + actualRadius}
12337
- A ${actualRadius},${actualRadius} 0 0 1 ${margin + actualRadius},${margin}
12338
- `;
12339
- }
12340
-
12341
- // Fixed segment size in pixels
12342
- const maxSegmentSize = 40;
12343
- const segmentLength = Math.min(maxSegmentSize, pathLength * 0.25);
12344
- const gapLength = pathLength - segmentLength;
12345
-
12346
- // Vitesse constante en pixels par seconde
12347
- const networkSpeed = useNetworkSpeed();
12348
- const pixelsPerSecond = {
12349
- "slow-2g": 40,
12350
- "2g": 60,
12351
- "3g": 80,
12352
- "4g": 120
12353
- }[networkSpeed] || 80;
12354
- const animationDuration = Math.max(1.5, pathLength / pixelsPerSecond);
12355
-
12356
- // ✅ Calculate correct offset based on actual segment size
12357
- const segmentRatio = segmentLength / pathLength;
12358
- const circleOffset = -animationDuration * segmentRatio;
12359
- return jsxs("svg", {
12360
- width: "100%",
12361
- height: "100%",
12362
- viewBox: `0 0 ${width} ${height}`,
12363
- preserveAspectRatio: "none",
12364
- style: "overflow: visible",
12365
- xmlns: "http://www.w3.org/2000/svg",
12366
- "shape-rendering": "geometricPrecision",
12367
- children: [isCircle ? jsx("circle", {
12368
- cx: margin + drawableWidth / 2,
12369
- cy: margin + drawableHeight / 2,
12370
- r: actualRadius,
12371
- fill: "none",
12372
- stroke: trailColor,
12373
- strokeWidth: strokeWidth
12374
- }) : jsx("rect", {
12375
- x: margin,
12376
- y: margin,
12377
- width: drawableWidth,
12378
- height: drawableHeight,
12379
- fill: "none",
12380
- stroke: trailColor,
12381
- strokeWidth: strokeWidth,
12382
- rx: actualRadius
12383
- }), jsx("path", {
12384
- d: rectPath,
12385
- fill: "none",
12386
- stroke: color,
12387
- strokeWidth: strokeWidth,
12388
- strokeLinecap: "round",
12389
- strokeDasharray: `${segmentLength} ${gapLength}`,
12390
- pathLength: pathLength,
12391
- children: jsx("animate", {
12392
- attributeName: "stroke-dashoffset",
12393
- from: pathLength,
12394
- to: "0",
12395
- dur: `${animationDuration}s`,
12396
- repeatCount: "indefinite",
12397
- begin: "0s"
12398
- })
12399
- }), jsx("circle", {
12400
- r: strokeWidth,
12401
- fill: color,
12402
- children: jsx("animateMotion", {
12403
- path: rectPath,
12404
- dur: `${animationDuration}s`,
12405
- repeatCount: "indefinite",
12406
- rotate: "auto",
12407
- begin: `${circleOffset}s`
12408
- })
12409
- })]
12410
- });
12411
- };
12412
-
12413
- installImportMetaCss(import.meta);import.meta.css = /* css */`
12414
- .navi_loading_rectangle_wrapper {
12415
- position: absolute;
12416
- top: var(--rectangle-top, 0);
12417
- right: var(--rectangle-right, 0);
12418
- bottom: var(--rectangle-bottom, 0);
12419
- left: var(--rectangle-left, 0);
12420
- z-index: 1;
12421
- opacity: 0;
12422
- pointer-events: none;
12423
- }
12424
- .navi_loading_rectangle_wrapper[data-visible] {
12425
- opacity: 1;
12426
- }
12427
- `;
12428
- const LoaderBackground = ({
12429
- loading,
12430
- containerRef,
12431
- targetSelector,
12432
- color,
12433
- inset = 0,
12434
- spacingTop = 0,
12435
- spacingLeft = 0,
12436
- spacingBottom = 0,
12437
- spacingRight = 0,
12438
- children
12439
- }) => {
12440
- if (containerRef) {
12441
- const container = containerRef.current;
12442
- if (!container) {
12443
- return children;
12444
- }
12445
- return createPortal(jsx(LoaderBackgroundWithPortal, {
12446
- container: container,
12447
- loading: loading,
12448
- color: color,
12449
- inset: inset,
12450
- spacingTop: spacingTop,
12451
- spacingLeft: spacingLeft,
12452
- spacingBottom: spacingBottom,
12453
- spacingRight: spacingRight,
12454
- children: children
12455
- }), container);
12456
- }
12457
- return jsx(LoaderBackgroundBasic, {
12458
- targetSelector: targetSelector,
12459
- loading: loading,
12460
- color: color,
12461
- inset: inset,
12462
- spacingTop: spacingTop,
12463
- spacingLeft: spacingLeft,
12464
- spacingBottom: spacingBottom,
12465
- spacingRight: spacingRight,
12466
- children: children
12467
- });
12468
- };
12469
- const LoaderBackgroundWithPortal = ({
12470
- container,
12471
- loading,
12472
- color,
12473
- inset,
12474
- spacingTop,
12475
- spacingLeft,
12476
- spacingBottom,
12477
- spacingRight,
12478
- children
12479
- }) => {
12480
- const shouldShowSpinner = useDebounceTrue(loading, 300);
12481
- if (!shouldShowSpinner) {
12482
- return children;
12483
- }
12484
- container.style.position = "relative";
12485
- let paddingTop = 0;
12486
- if (container.nodeName === "DETAILS") {
12487
- paddingTop = container.querySelector("summary").offsetHeight;
12488
- }
12489
- return jsxs(Fragment, {
12490
- children: [jsx("div", {
12491
- style: {
12492
- position: "absolute",
12493
- top: `${inset + paddingTop + spacingTop}px`,
12494
- bottom: `${inset + spacingBottom}px`,
12495
- left: `${inset + spacingLeft}px`,
12496
- right: `${inset + spacingRight}px`
12497
- },
12498
- children: shouldShowSpinner && jsx(RectangleLoading, {
12499
- color: color
12500
- })
12501
- }), children]
12502
- });
12503
- };
12504
- const LoaderBackgroundBasic = ({
12505
- loading,
12506
- targetSelector,
12507
- color,
12508
- spacingTop,
12509
- spacingLeft,
12510
- spacingBottom,
12511
- spacingRight,
12512
- inset,
12513
- children
12514
- }) => {
12515
- const shouldShowSpinner = useDebounceTrue(loading, 300);
12516
- const rectangleRef = useRef(null);
12517
- const [, setOutlineOffset] = useState(0);
12518
- const [borderRadius, setBorderRadius] = useState(0);
12519
- const [borderTopWidth, setBorderTopWidth] = useState(0);
12520
- const [borderLeftWidth, setBorderLeftWidth] = useState(0);
12521
- const [borderRightWidth, setBorderRightWidth] = useState(0);
12522
- const [borderBottomWidth, setBorderBottomWidth] = useState(0);
12523
- const [marginTop, setMarginTop] = useState(0);
12524
- const [marginBottom, setMarginBottom] = useState(0);
12525
- const [marginLeft, setMarginLeft] = useState(0);
12526
- const [marginRight, setMarginRight] = useState(0);
12527
- const [paddingTop, setPaddingTop] = useState(0);
12528
- const [paddingLeft, setPaddingLeft] = useState(0);
12529
- const [paddingRight, setPaddingRight] = useState(0);
12530
- const [paddingBottom, setPaddingBottom] = useState(0);
12531
- const [currentColor, setCurrentColor] = useState(color);
12532
- useLayoutEffect(() => {
12533
- let animationFrame;
12534
- const updateStyles = () => {
12535
- const rectangle = rectangleRef.current;
12536
- if (!rectangle) {
12537
- return;
12538
- }
12539
- const container = rectangle.parentElement;
12540
- const containedElement = rectangle.nextElementSibling;
12541
- const target = targetSelector ? container.querySelector(targetSelector) : containedElement;
12542
- if (target) {
12543
- const {
12544
- width,
12545
- height
12546
- } = target.getBoundingClientRect();
12547
- const containedComputedStyle = window.getComputedStyle(containedElement);
12548
- const targetComputedStyle = window.getComputedStyle(target);
12549
- const newBorderTopWidth = resolveCSSSize(targetComputedStyle.borderTopWidth);
12550
- const newBorderLeftWidth = resolveCSSSize(targetComputedStyle.borderLeftWidth);
12551
- const newBorderRightWidth = resolveCSSSize(targetComputedStyle.borderRightWidth);
12552
- const newBorderBottomWidth = resolveCSSSize(targetComputedStyle.borderBottomWidth);
12553
- const newBorderRadius = resolveCSSSize(targetComputedStyle.borderRadius, {
12554
- availableSize: Math.min(width, height)
12555
- });
12556
- const newOutlineColor = targetComputedStyle.outlineColor;
12557
- const newBorderColor = targetComputedStyle.borderColor;
12558
- const newDetectedColor = targetComputedStyle.color;
12559
- const newOutlineOffset = resolveCSSSize(targetComputedStyle.outlineOffset);
12560
- const newMarginTop = resolveCSSSize(targetComputedStyle.marginTop);
12561
- const newMarginBottom = resolveCSSSize(targetComputedStyle.marginBottom);
12562
- const newMarginLeft = resolveCSSSize(targetComputedStyle.marginLeft);
12563
- const newMarginRight = resolveCSSSize(targetComputedStyle.marginRight);
12564
- const paddingTop = resolveCSSSize(containedComputedStyle.paddingTop);
12565
- const paddingLeft = resolveCSSSize(containedComputedStyle.paddingLeft);
12566
- const paddingRight = resolveCSSSize(containedComputedStyle.paddingRight);
12567
- const paddingBottom = resolveCSSSize(containedComputedStyle.paddingBottom);
12568
- setBorderTopWidth(newBorderTopWidth);
12569
- setBorderLeftWidth(newBorderLeftWidth);
12570
- setBorderRightWidth(newBorderRightWidth);
12571
- setBorderBottomWidth(newBorderBottomWidth);
12572
- setBorderRadius(newBorderRadius);
12573
- setOutlineOffset(newOutlineOffset);
12574
- setMarginTop(newMarginTop);
12575
- setMarginBottom(newMarginBottom);
12576
- setMarginLeft(newMarginLeft);
12577
- setMarginRight(newMarginRight);
12578
- setPaddingTop(paddingTop);
12579
- setPaddingLeft(paddingLeft);
12580
- setPaddingRight(paddingRight);
12581
- setPaddingBottom(paddingBottom);
12582
- if (color) {
12583
- // const resolvedColor = resolveCSSColor(color, rectangle, "css");
12584
- // console.log(resolvedColor);
12585
- setCurrentColor(color);
12586
- } else if (newOutlineColor && newOutlineColor !== "rgba(0, 0, 0, 0)" && (document.activeElement === containedElement || newBorderColor === "rgba(0, 0, 0, 0)")) {
12587
- setCurrentColor(newOutlineColor);
12588
- } else if (newBorderColor && newBorderColor !== "rgba(0, 0, 0, 0)") {
12589
- setCurrentColor(newBorderColor);
12590
- } else {
12591
- setCurrentColor(newDetectedColor);
12592
- }
12593
- }
12594
- // updateStyles is very cheap so we run it every frame
12595
- animationFrame = requestAnimationFrame(updateStyles);
12596
- };
12597
- updateStyles();
12598
- return () => {
12599
- cancelAnimationFrame(animationFrame);
12600
- };
12601
- }, [color, targetSelector]);
12602
- spacingTop += inset;
12603
- // spacingTop += outlineOffset;
12604
- // spacingTop -= borderTopWidth;
12605
- spacingTop += marginTop;
12606
- spacingLeft += inset;
12607
- // spacingLeft += outlineOffset;
12608
- // spacingLeft -= borderLeftWidth;
12609
- spacingLeft += marginLeft;
12610
- spacingRight += inset;
12611
- // spacingRight += outlineOffset;
12612
- // spacingRight -= borderRightWidth;
12613
- spacingRight += marginRight;
12614
- spacingBottom += inset;
12615
- // spacingBottom += outlineOffset;
12616
- // spacingBottom -= borderBottomWidth;
12617
- spacingBottom += marginBottom;
12618
- if (targetSelector) {
12619
- // oversimplification that actually works
12620
- // (simplified because it assumes the targeted element is a direct child of the contained element which may have padding)
12621
- spacingTop += paddingTop;
12622
- spacingLeft += paddingLeft;
12623
- spacingRight += paddingRight;
12624
- spacingBottom += paddingBottom;
12625
- }
12626
- const maxBorderWidth = Math.max(borderTopWidth, borderLeftWidth, borderRightWidth, borderBottomWidth);
12627
- const halfMaxBorderSize = maxBorderWidth / 2;
12628
- const size = halfMaxBorderSize < 2 ? 2 : halfMaxBorderSize;
12629
- const lineHalfSize = size / 2;
12630
- spacingTop -= lineHalfSize;
12631
- spacingLeft -= lineHalfSize;
12632
- spacingRight -= lineHalfSize;
12633
- spacingBottom -= lineHalfSize;
12634
- return jsxs(Fragment, {
12635
- children: [jsx("span", {
12636
- ref: rectangleRef,
12637
- className: "navi_loading_rectangle_wrapper",
12638
- "data-visible": shouldShowSpinner ? "" : undefined,
12639
- style: {
12640
- "--rectangle-top": `${spacingTop}px`,
12641
- "--rectangle-left": `${spacingLeft}px`,
12642
- "--rectangle-bottom": `${spacingBottom}px`,
12643
- "--rectangle-right": `${spacingRight}px`
12644
- },
12645
- children: loading && jsx(RectangleLoading, {
12646
- shouldShowSpinner: shouldShowSpinner,
12647
- color: currentColor,
12648
- radius: borderRadius,
12649
- size: size
12650
- })
12651
- }), children]
12652
- });
12653
- };
12654
-
12655
- /**
12656
- * Custom hook creating a stable callback that doesn't trigger re-renders.
12657
- *
12658
- * PROBLEM: Parent components often forget to use useCallback, causing library
12659
- * components to re-render unnecessarily when receiving callback props.
12660
- *
12661
- * SOLUTION: Library components can use this hook to create stable callback
12662
- * references internally, making them defensive against parents who don't
12663
- * optimize their callbacks. This ensures library components don't force
12664
- * consumers to think about useCallback.
12665
- *
12666
- * USAGE:
12667
- * ```js
12668
- * // Parent component (consumer) - no useCallback needed
12669
- * const Parent = () => {
12670
- * const [count, setCount] = useState(0);
12671
- *
12672
- * // Parent naturally creates new function reference each render
12673
- * // (forgetting useCallback is common and shouldn't break performance)
12674
- * return <LibraryButton onClick={(e) => setCount(count + 1)} />;
12675
- * };
12676
- *
12677
- * // Library component - defensive against changing callbacks
12678
- * const LibraryButton = ({ onClick }) => {
12679
- * // ✅ Create stable reference from parent's potentially changing callback
12680
- * const stableClick = useStableCallback(onClick);
12681
- *
12682
- * // Internal expensive components won't re-render when parent updates
12683
- * return <ExpensiveInternalButton onClick={stableClick} />;
12684
- * };
12685
- *
12686
- * // Deep internal component gets stable reference
12687
- * const ExpensiveInternalButton = memo(({ onClick }) => {
12688
- * // This won't re-render when Parent's count changes
12689
- * // But onClick will always call the latest Parent callback
12690
- * return <button onClick={onClick}>Click me</button>;
12691
- * });
12692
- * ```
12693
- *
12694
- * Perfect for library components that need performance without burdening consumers.
12695
- */
12696
-
12697
-
12698
- const useStableCallback = (callback, mapper) => {
12699
- const callbackRef = useRef();
12700
- callbackRef.current = callback;
12701
- const stableCallbackRef = useRef();
12702
-
12703
- // Return original falsy value directly when callback is not a function
12704
- if (!callback) {
12705
- return callback;
12706
- }
12707
-
12708
- const existingStableCallback = stableCallbackRef.current;
12709
- if (existingStableCallback) {
12710
- return existingStableCallback;
12711
- }
12712
- const stableCallback = (...args) => {
12713
- const currentCallback = callbackRef.current;
12714
- return currentCallback(...args);
12715
- };
12716
- stableCallbackRef.current = stableCallback;
12717
- return stableCallback;
12718
- };
12719
-
12720
- const DEBUG = {
12721
- registration: false,
12722
- // Element registration/unregistration
12723
- interaction: false,
12724
- // Click and keyboard interactions
12725
- selection: false,
12726
- // Selection state changes (set, add, remove, toggle)
12727
- navigation: false,
12728
- // Arrow key navigation and element finding
12729
- valueExtraction: false // Value extraction from elements
12730
- };
12731
- const debug = (category, ...args) => {
12732
- if (DEBUG[category]) {
12733
- console.debug(`[selection:${category}]`, ...args);
12734
- }
12735
- };
12736
- const SelectionContext = createContext();
12737
- const useSelectionController = ({
12738
- elementRef,
12739
- layout,
12740
- value,
12741
- onChange,
12742
- multiple,
12743
- selectAllName
12744
- }) => {
12745
- if (!elementRef) {
12746
- throw new Error("useSelectionController: elementRef is required");
12747
- }
12748
- onChange = useStableCallback(onChange);
12749
- const currentValueRef = useRef(value);
12750
- currentValueRef.current = value;
12751
- const lastInternalValueRef = useRef(null);
12752
- const selectionController = useMemo(() => {
12753
- const innerOnChange = (newValue, ...args) => {
12754
- lastInternalValueRef.current = newValue;
12755
- onChange?.(newValue, ...args);
12756
- };
12757
- const getCurrentValue = () => currentValueRef.current;
12758
- if (layout === "grid") {
12759
- return createGridSelectionController({
12760
- getCurrentValue,
12761
- onChange: innerOnChange,
12762
- enabled: Boolean(onChange),
12763
- multiple,
12764
- selectAllName
12765
- });
12766
- }
12767
- return createLinearSelectionController({
12768
- getCurrentValue,
12769
- onChange: innerOnChange,
12770
- layout,
12771
- elementRef,
12772
- multiple,
12773
- enabled: Boolean(onChange),
12774
- selectAllName
12775
- });
12776
- }, [layout, multiple, elementRef]);
12777
- useEffect(() => {
12778
- selectionController.element = elementRef.current;
12779
- }, [selectionController]);
12780
- useLayoutEffect(() => {
12781
- selectionController.enabled = Boolean(onChange);
12782
- }, [selectionController, onChange]);
12783
-
12784
- // Smart sync: only update selection when value changes externally
12785
- useEffect(() => {
12786
- // Check if this is an external change (not from our internal onChange)
12787
- const isExternalChange = !compareTwoJsValues(value, lastInternalValueRef.current);
12788
- if (isExternalChange) {
12789
- selectionController.update(value);
12232
+ selectionController.enabled = Boolean(onChange);
12233
+ }, [selectionController, onChange]);
12234
+
12235
+ // Smart sync: only update selection when value changes externally
12236
+ useEffect(() => {
12237
+ // Check if this is an external change (not from our internal onChange)
12238
+ const isExternalChange = !compareTwoJsValues(value, lastInternalValueRef.current);
12239
+ if (isExternalChange) {
12240
+ selectionController.update(value);
12790
12241
  }
12791
12242
  }, [value, selectionController]);
12792
12243
  return selectionController;
@@ -17271,636 +16722,1217 @@ const useConstraints = (elementRef, props, { targetSelector } = {}) => {
17271
16722
  if (minLowerLetterMessage) {
17272
16723
  setupCustomEvent(el, "min-lower-letter", minLowerLetterMessage);
17273
16724
  }
17274
- if (minUpperLetterMessage) {
17275
- setupCustomEvent(el, "min-upper-letter", minUpperLetterMessage);
16725
+ if (minUpperLetterMessage) {
16726
+ setupCustomEvent(el, "min-upper-letter", minUpperLetterMessage);
16727
+ }
16728
+ if (minSpecialCharMessage) {
16729
+ setupCustomEvent(el, "min-special-char", minSpecialCharMessage);
16730
+ }
16731
+ if (availableMessage) {
16732
+ setupCustomEvent(el, "available", availableMessage);
16733
+ }
16734
+ return () => {
16735
+ for (const cleanupCallback of cleanupCallbackSet) {
16736
+ cleanupCallback();
16737
+ }
16738
+ };
16739
+ }, [
16740
+ disabledMessage,
16741
+ requiredMessage,
16742
+ patternMessage,
16743
+ minLengthMessage,
16744
+ maxLengthMessage,
16745
+ typeMessage,
16746
+ minMessage,
16747
+ maxMessage,
16748
+ singleSpaceMessage,
16749
+ sameAsMessage,
16750
+ minDigitMessage,
16751
+ minLowerLetterMessage,
16752
+ minUpperLetterMessage,
16753
+ minSpecialCharMessage,
16754
+ availableMessage,
16755
+ ]);
16756
+
16757
+ return remainingProps;
16758
+ };
16759
+
16760
+ const useInitialTextSelection = (ref, textSelection) => {
16761
+ const deps = [];
16762
+ if (Array.isArray(textSelection)) {
16763
+ deps.push(...textSelection);
16764
+ } else {
16765
+ deps.push(textSelection);
16766
+ }
16767
+ useLayoutEffect(() => {
16768
+ const el = ref.current;
16769
+ if (!el || !textSelection) {
16770
+ return;
16771
+ }
16772
+ const range = document.createRange();
16773
+ const selection = window.getSelection();
16774
+ if (Array.isArray(textSelection)) {
16775
+ if (textSelection.length === 2) {
16776
+ const [start, end] = textSelection;
16777
+ if (typeof start === "number" && typeof end === "number") {
16778
+ // Format: [0, 10] - character indices
16779
+ selectByCharacterIndices(el, range, start, end);
16780
+ } else if (typeof start === "string" && typeof end === "string") {
16781
+ // Format: ["Click on the", "button to return"] - text strings
16782
+ selectByTextStrings(el, range, start, end);
16783
+ }
16784
+ }
16785
+ } else if (typeof textSelection === "string") {
16786
+ // Format: "some text" - select the entire string occurrence
16787
+ selectSingleTextString(el, range, textSelection);
16788
+ }
16789
+ selection.removeAllRanges();
16790
+ selection.addRange(range);
16791
+ }, deps);
16792
+ };
16793
+ const selectByCharacterIndices = (element, range, startIndex, endIndex) => {
16794
+ const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false);
16795
+ let currentIndex = 0;
16796
+ let startNode = null;
16797
+ let startOffset = 0;
16798
+ let endNode = null;
16799
+ let endOffset = 0;
16800
+ while (walker.nextNode()) {
16801
+ const textContent = walker.currentNode.textContent;
16802
+ const nodeLength = textContent.length;
16803
+
16804
+ // Check if start position is in this text node
16805
+ if (!startNode && currentIndex + nodeLength > startIndex) {
16806
+ startNode = walker.currentNode;
16807
+ startOffset = startIndex - currentIndex;
16808
+ }
16809
+
16810
+ // Check if end position is in this text node
16811
+ if (currentIndex + nodeLength >= endIndex) {
16812
+ endNode = walker.currentNode;
16813
+ endOffset = endIndex - currentIndex;
16814
+ break;
16815
+ }
16816
+ currentIndex += nodeLength;
16817
+ }
16818
+ if (startNode && endNode) {
16819
+ range.setStart(startNode, startOffset);
16820
+ range.setEnd(endNode, endOffset);
16821
+ }
16822
+ };
16823
+ const selectSingleTextString = (element, range, text) => {
16824
+ const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false);
16825
+ while (walker.nextNode()) {
16826
+ const textContent = walker.currentNode.textContent;
16827
+ const index = textContent.indexOf(text);
16828
+ if (index !== -1) {
16829
+ range.setStart(walker.currentNode, index);
16830
+ range.setEnd(walker.currentNode, index + text.length);
16831
+ return;
16832
+ }
16833
+ }
16834
+ };
16835
+ const selectByTextStrings = (element, range, startText, endText) => {
16836
+ const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false);
16837
+ let startNode = null;
16838
+ let endNode = null;
16839
+ let foundStart = false;
16840
+ while (walker.nextNode()) {
16841
+ const textContent = walker.currentNode.textContent;
16842
+ if (!foundStart && textContent.includes(startText)) {
16843
+ startNode = walker.currentNode;
16844
+ foundStart = true;
16845
+ }
16846
+ if (foundStart && textContent.includes(endText)) {
16847
+ endNode = walker.currentNode;
16848
+ break;
16849
+ }
16850
+ }
16851
+ if (startNode && endNode) {
16852
+ const startOffset = startNode.textContent.indexOf(startText);
16853
+ const endOffset = endNode.textContent.indexOf(endText) + endText.length;
16854
+ range.setStart(startNode, startOffset);
16855
+ range.setEnd(endNode, endOffset);
16856
+ }
16857
+ };
16858
+
16859
+ installImportMetaCss(import.meta);import.meta.css = /* css */`
16860
+ *[data-navi-space] {
16861
+ /* user-select: none; */
16862
+ }
16863
+
16864
+ .navi_text {
16865
+ position: relative;
16866
+ color: inherit;
16867
+
16868
+ &[data-has-absolute-child] {
16869
+ display: inline-block;
16870
+ }
16871
+ }
16872
+
16873
+ .navi_text_overflow {
16874
+ flex-wrap: wrap;
16875
+ text-overflow: ellipsis;
16876
+ overflow: hidden;
16877
+ }
16878
+
16879
+ .navi_text_overflow_wrapper {
16880
+ display: flex;
16881
+ width: 0;
16882
+ flex-grow: 1;
16883
+ gap: 0.3em;
16884
+ }
16885
+
16886
+ .navi_text_overflow_text {
16887
+ max-width: 100%;
16888
+ text-overflow: ellipsis;
16889
+ overflow: hidden;
16890
+ }
16891
+
16892
+ .navi_custom_space {
16893
+ }
16894
+
16895
+ .navi_text_bold_wrapper {
16896
+ position: relative;
16897
+ display: inline-block;
16898
+ }
16899
+ .navi_text_bold_clone {
16900
+ font-weight: bold;
16901
+ opacity: 0;
16902
+ }
16903
+ .navi_text_bold_foreground {
16904
+ position: absolute;
16905
+ inset: 0;
16906
+ }
16907
+
16908
+ .navi_text_bold_background {
16909
+ position: absolute;
16910
+ top: 0;
16911
+ left: 0;
16912
+ color: currentColor;
16913
+ font-weight: normal;
16914
+ background: currentColor;
16915
+ background-clip: text;
16916
+ -webkit-background-clip: text;
16917
+ transform-origin: center;
16918
+ -webkit-text-fill-color: transparent;
16919
+ opacity: 0;
16920
+ }
16921
+
16922
+ .navi_text[data-bold] {
16923
+ .navi_text_bold_background {
16924
+ opacity: 1;
16925
+ }
16926
+ }
16927
+
16928
+ .navi_text[data-bold-transition] {
16929
+ .navi_text_bold_foreground {
16930
+ transition-property: font-weight;
16931
+ transition-duration: 0.3s;
16932
+ transition-timing-function: ease;
16933
+ }
16934
+
16935
+ .navi_text_bold_background {
16936
+ transition-property: opacity;
16937
+ transition-duration: 0.3s;
16938
+ transition-timing-function: ease;
16939
+ }
16940
+ }
16941
+ `;
16942
+ const REGULAR_SPACE = jsx("span", {
16943
+ "data-navi-space": "",
16944
+ children: " "
16945
+ });
16946
+ const CustomWidthSpace = ({
16947
+ value
16948
+ }) => {
16949
+ return jsx("span", {
16950
+ className: "navi_custom_space",
16951
+ style: `padding-left: ${value}`,
16952
+ children: "\u200B"
16953
+ });
16954
+ };
16955
+ const applySpacingOnTextChildren = (children, spacing) => {
16956
+ if (spacing === "pre" || spacing === "0" || spacing === 0) {
16957
+ return children;
16958
+ }
16959
+ if (!children) {
16960
+ return children;
16961
+ }
16962
+ const childArray = toChildArray(children);
16963
+ const childCount = childArray.length;
16964
+ if (childCount <= 1) {
16965
+ return children;
16966
+ }
16967
+ let separator;
16968
+ if (spacing === undefined) {
16969
+ spacing = REGULAR_SPACE;
16970
+ } else if (typeof spacing === "string") {
16971
+ if (isSizeSpacingScaleKey(spacing)) {
16972
+ separator = jsx(CustomWidthSpace, {
16973
+ value: resolveSpacingSize(spacing)
16974
+ });
16975
+ } else if (hasCSSSizeUnit(spacing)) {
16976
+ separator = jsx(CustomWidthSpace, {
16977
+ value: resolveSpacingSize(spacing)
16978
+ });
16979
+ } else {
16980
+ separator = spacing;
16981
+ }
16982
+ } else if (typeof spacing === "number") {
16983
+ separator = jsx(CustomWidthSpace, {
16984
+ value: spacing
16985
+ });
16986
+ } else {
16987
+ separator = spacing;
16988
+ }
16989
+ const childrenWithGap = [];
16990
+ let i = 0;
16991
+ while (true) {
16992
+ const child = childArray[i];
16993
+ childrenWithGap.push(child);
16994
+ i++;
16995
+ if (i === childCount) {
16996
+ break;
17276
16997
  }
17277
- if (minSpecialCharMessage) {
17278
- setupCustomEvent(el, "min-special-char", minSpecialCharMessage);
16998
+ const currentChild = childArray[i - 1];
16999
+ const nextChild = childArray[i];
17000
+ if (endsWithWhitespace(currentChild)) {
17001
+ continue;
17279
17002
  }
17280
- if (availableMessage) {
17281
- setupCustomEvent(el, "available", availableMessage);
17003
+ if (startsWithWhitespace(nextChild)) {
17004
+ continue;
17282
17005
  }
17283
- return () => {
17284
- for (const cleanupCallback of cleanupCallbackSet) {
17285
- cleanupCallback();
17286
- }
17287
- };
17288
- }, [
17289
- disabledMessage,
17290
- requiredMessage,
17291
- patternMessage,
17292
- minLengthMessage,
17293
- maxLengthMessage,
17294
- typeMessage,
17295
- minMessage,
17296
- maxMessage,
17297
- singleSpaceMessage,
17298
- sameAsMessage,
17299
- minDigitMessage,
17300
- minLowerLetterMessage,
17301
- minUpperLetterMessage,
17302
- minSpecialCharMessage,
17303
- availableMessage,
17304
- ]);
17305
-
17306
- return remainingProps;
17006
+ childrenWithGap.push(separator);
17007
+ }
17008
+ return childrenWithGap;
17307
17009
  };
17308
-
17309
- const useInitialTextSelection = (ref, textSelection) => {
17310
- const deps = [];
17311
- if (Array.isArray(textSelection)) {
17312
- deps.push(...textSelection);
17313
- } else {
17314
- deps.push(textSelection);
17010
+ const endsWithWhitespace = jsxChild => {
17011
+ if (typeof jsxChild === "string") {
17012
+ return /\s$/.test(jsxChild);
17315
17013
  }
17316
- useLayoutEffect(() => {
17317
- const el = ref.current;
17318
- if (!el || !textSelection) {
17319
- return;
17320
- }
17321
- const range = document.createRange();
17322
- const selection = window.getSelection();
17323
- if (Array.isArray(textSelection)) {
17324
- if (textSelection.length === 2) {
17325
- const [start, end] = textSelection;
17326
- if (typeof start === "number" && typeof end === "number") {
17327
- // Format: [0, 10] - character indices
17328
- selectByCharacterIndices(el, range, start, end);
17329
- } else if (typeof start === "string" && typeof end === "string") {
17330
- // Format: ["Click on the", "button to return"] - text strings
17331
- selectByTextStrings(el, range, start, end);
17332
- }
17333
- }
17334
- } else if (typeof textSelection === "string") {
17335
- // Format: "some text" - select the entire string occurrence
17336
- selectSingleTextString(el, range, textSelection);
17337
- }
17338
- selection.removeAllRanges();
17339
- selection.addRange(range);
17340
- }, deps);
17014
+ return false;
17015
+ };
17016
+ const startsWithWhitespace = jsxChild => {
17017
+ if (typeof jsxChild === "string") {
17018
+ return /^\s/.test(jsxChild);
17019
+ }
17020
+ return false;
17021
+ };
17022
+ const OverflowPinnedElementContext = createContext(null);
17023
+ const Text = props => {
17024
+ const {
17025
+ overflowEllipsis,
17026
+ ...rest
17027
+ } = props;
17028
+ if (overflowEllipsis) {
17029
+ return jsx(TextOverflow, {
17030
+ ...rest
17031
+ });
17032
+ }
17033
+ if (props.overflowPinned) {
17034
+ return jsx(TextOverflowPinned, {
17035
+ ...props
17036
+ });
17037
+ }
17038
+ if (props.selectRange) {
17039
+ return jsx(TextWithSelectRange, {
17040
+ ...props
17041
+ });
17042
+ }
17043
+ return jsx(TextBasic, {
17044
+ ...props
17045
+ });
17046
+ };
17047
+ const TextOverflow = ({
17048
+ noWrap,
17049
+ children,
17050
+ ...rest
17051
+ }) => {
17052
+ const [OverflowPinnedElement, setOverflowPinnedElement] = useState(null);
17053
+ return jsx(Text, {
17054
+ column: true,
17055
+ as: "div",
17056
+ nowWrap: noWrap,
17057
+ pre: !noWrap
17058
+ // For paragraph we prefer to keep lines and only hide unbreakable long sections
17059
+ ,
17060
+ preLine: rest.as === "p",
17061
+ ...rest,
17062
+ className: "navi_text_overflow",
17063
+ expandX: true,
17064
+ spacing: "pre",
17065
+ children: jsxs("span", {
17066
+ className: "navi_text_overflow_wrapper",
17067
+ children: [jsx(OverflowPinnedElementContext.Provider, {
17068
+ value: setOverflowPinnedElement,
17069
+ children: jsx(Text, {
17070
+ className: "navi_text_overflow_text",
17071
+ children: children
17072
+ })
17073
+ }), OverflowPinnedElement]
17074
+ })
17075
+ });
17076
+ };
17077
+ const TextOverflowPinned = ({
17078
+ overflowPinned,
17079
+ ...props
17080
+ }) => {
17081
+ const setOverflowPinnedElement = useContext(OverflowPinnedElementContext);
17082
+ const text = jsx(Text, {
17083
+ ...props
17084
+ });
17085
+ if (!setOverflowPinnedElement) {
17086
+ console.warn("<Text overflowPinned> declared outside a <Text overflowEllipsis>");
17087
+ return text;
17088
+ }
17089
+ if (overflowPinned) {
17090
+ setOverflowPinnedElement(text);
17091
+ return null;
17092
+ }
17093
+ setOverflowPinnedElement(null);
17094
+ return text;
17095
+ };
17096
+ const TextWithSelectRange = ({
17097
+ selectRange,
17098
+ ...props
17099
+ }) => {
17100
+ const defaultRef = useRef();
17101
+ const ref = props.ref || defaultRef;
17102
+ useInitialTextSelection(ref, selectRange);
17103
+ return jsx(Text, {
17104
+ ref: ref,
17105
+ ...props
17106
+ });
17107
+ };
17108
+ const TextBasic = ({
17109
+ spacing = " ",
17110
+ boldTransition,
17111
+ boldStable,
17112
+ preventBoldLayoutShift = boldTransition,
17113
+ children,
17114
+ ...rest
17115
+ }) => {
17116
+ const boxProps = {
17117
+ "as": "span",
17118
+ "data-bold-transition": boldTransition ? "" : undefined,
17119
+ ...rest,
17120
+ "baseClassName": withPropsClassName("navi_text", rest.baseClassName)
17121
+ };
17122
+ const shouldPreserveSpacing = rest.as === "pre" || rest.box || rest.column || rest.row;
17123
+ if (shouldPreserveSpacing) {
17124
+ boxProps.spacing = spacing;
17125
+ } else {
17126
+ children = applySpacingOnTextChildren(children, spacing);
17127
+ }
17128
+ if (boldStable) {
17129
+ const {
17130
+ bold
17131
+ } = boxProps;
17132
+ return jsxs(Box, {
17133
+ ...boxProps,
17134
+ bold: undefined,
17135
+ "data-bold": bold ? "" : undefined,
17136
+ "data-has-absolute-child": "",
17137
+ children: [jsx("span", {
17138
+ className: "navi_text_bold_background",
17139
+ "aria-hidden": "true",
17140
+ children: children
17141
+ }), children]
17142
+ });
17143
+ }
17144
+ if (preventBoldLayoutShift) {
17145
+ const alignX = rest.alignX || rest.align || "start";
17146
+
17147
+ // La technique consiste a avoid un double gras qui force une taille
17148
+ // et la version light par dessus en position absolute
17149
+ // on la centre aussi pour donner l'impression que le gras s'applique depuis le centre
17150
+ // ne fonctionne que sur une seul ligne de texte (donc lorsque noWrap est actif)
17151
+ // on pourrait auto-active cela sur une prop genre boldCanChange
17152
+ return jsx(Box, {
17153
+ ...boxProps,
17154
+ children: jsxs("span", {
17155
+ className: "navi_text_bold_wrapper",
17156
+ children: [jsx("span", {
17157
+ className: "navi_text_bold_clone",
17158
+ "aria-hidden": "true",
17159
+ children: children
17160
+ }), jsx("span", {
17161
+ className: "navi_text_bold_foreground",
17162
+ "data-align": alignX,
17163
+ children: children
17164
+ })]
17165
+ })
17166
+ });
17167
+ }
17168
+ return jsx(Box, {
17169
+ ...boxProps,
17170
+ children: children
17171
+ });
17341
17172
  };
17342
- const selectByCharacterIndices = (element, range, startIndex, endIndex) => {
17343
- const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false);
17344
- let currentIndex = 0;
17345
- let startNode = null;
17346
- let startOffset = 0;
17347
- let endNode = null;
17348
- let endOffset = 0;
17349
- while (walker.nextNode()) {
17350
- const textContent = walker.currentNode.textContent;
17351
- const nodeLength = textContent.length;
17352
17173
 
17353
- // Check if start position is in this text node
17354
- if (!startNode && currentIndex + nodeLength > startIndex) {
17355
- startNode = walker.currentNode;
17356
- startOffset = startIndex - currentIndex;
17357
- }
17174
+ installImportMetaCss(import.meta);import.meta.css = /* css */`
17175
+ .navi_icon {
17176
+ display: inline-block;
17177
+ box-sizing: border-box;
17178
+ max-width: 100%;
17179
+ max-height: 100%;
17358
17180
 
17359
- // Check if end position is in this text node
17360
- if (currentIndex + nodeLength >= endIndex) {
17361
- endNode = walker.currentNode;
17362
- endOffset = endIndex - currentIndex;
17363
- break;
17181
+ &[data-flow-inline] {
17182
+ width: 1em;
17183
+ height: 1em;
17184
+ }
17185
+ &[data-icon-char] {
17186
+ flex-grow: 0 !important;
17187
+ line-height: normal;
17364
17188
  }
17365
- currentIndex += nodeLength;
17366
17189
  }
17367
- if (startNode && endNode) {
17368
- range.setStart(startNode, startOffset);
17369
- range.setEnd(endNode, endOffset);
17190
+
17191
+ .navi_icon[data-interactive] {
17192
+ cursor: pointer;
17370
17193
  }
17371
- };
17372
- const selectSingleTextString = (element, range, text) => {
17373
- const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false);
17374
- while (walker.nextNode()) {
17375
- const textContent = walker.currentNode.textContent;
17376
- const index = textContent.indexOf(text);
17377
- if (index !== -1) {
17378
- range.setStart(walker.currentNode, index);
17379
- range.setEnd(walker.currentNode, index + text.length);
17380
- return;
17381
- }
17194
+
17195
+ .navi_icon_char_slot {
17196
+ opacity: 0;
17197
+ cursor: default;
17198
+ user-select: none;
17382
17199
  }
17383
- };
17384
- const selectByTextStrings = (element, range, startText, endText) => {
17385
- const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false);
17386
- let startNode = null;
17387
- let endNode = null;
17388
- let foundStart = false;
17389
- while (walker.nextNode()) {
17390
- const textContent = walker.currentNode.textContent;
17391
- if (!foundStart && textContent.includes(startText)) {
17392
- startNode = walker.currentNode;
17393
- foundStart = true;
17394
- }
17395
- if (foundStart && textContent.includes(endText)) {
17396
- endNode = walker.currentNode;
17397
- break;
17398
- }
17200
+ .navi_icon_foreground {
17201
+ position: absolute;
17202
+ inset: 0;
17399
17203
  }
17400
- if (startNode && endNode) {
17401
- const startOffset = startNode.textContent.indexOf(startText);
17402
- const endOffset = endNode.textContent.indexOf(endText) + endText.length;
17403
- range.setStart(startNode, startOffset);
17404
- range.setEnd(endNode, endOffset);
17204
+ .navi_icon_foreground > .navi_text {
17205
+ display: flex;
17206
+ aspect-ratio: 1 / 1;
17207
+ min-width: 0;
17208
+ height: 100%;
17209
+ max-height: 1em;
17210
+ align-items: center;
17211
+ justify-content: center;
17405
17212
  }
17406
- };
17407
17213
 
17408
- installImportMetaCss(import.meta);import.meta.css = /* css */`
17409
- *[data-navi-space] {
17410
- /* user-select: none; */
17214
+ .navi_icon > svg,
17215
+ .navi_icon > img {
17216
+ width: 100%;
17217
+ height: 100%;
17218
+ backface-visibility: hidden;
17219
+ }
17220
+ .navi_icon[data-has-width] > svg,
17221
+ .navi_icon[data-has-width] > img {
17222
+ width: 100%;
17223
+ height: auto;
17224
+ }
17225
+ .navi_icon[data-has-height] > svg,
17226
+ .navi_icon[data-has-height] > img {
17227
+ width: auto;
17228
+ height: 100%;
17229
+ }
17230
+ .navi_icon[data-has-width][data-has-height] > svg,
17231
+ .navi_icon[data-has-width][data-has-height] > img {
17232
+ width: 100%;
17233
+ height: 100%;
17411
17234
  }
17412
17235
 
17413
- .navi_text {
17414
- position: relative;
17415
- color: inherit;
17416
-
17417
- &[data-has-absolute-child] {
17418
- display: inline-block;
17236
+ .navi_icon[data-icon-char] svg,
17237
+ .navi_icon[data-icon-char] img {
17238
+ width: 100%;
17239
+ height: 100%;
17240
+ }
17241
+ .navi_icon[data-icon-char] svg {
17242
+ overflow: visible;
17243
+ }
17244
+ `;
17245
+ const Icon = ({
17246
+ href,
17247
+ children,
17248
+ charWidth = 1,
17249
+ // 0 (zéro) is the real char width
17250
+ // but 2 zéros gives too big icons
17251
+ // while 1 "W" gives a nice result
17252
+ baseChar = "W",
17253
+ decorative,
17254
+ onClick,
17255
+ ...props
17256
+ }) => {
17257
+ const innerChildren = href ? jsx("svg", {
17258
+ width: "100%",
17259
+ height: "100%",
17260
+ children: jsx("use", {
17261
+ href: href
17262
+ })
17263
+ }) : children;
17264
+ let {
17265
+ box,
17266
+ width,
17267
+ height
17268
+ } = props;
17269
+ if (width === "auto") width = undefined;
17270
+ if (height === "auto") height = undefined;
17271
+ const hasExplicitWidth = width !== undefined;
17272
+ const hasExplicitHeight = height !== undefined;
17273
+ if (!hasExplicitWidth && !hasExplicitHeight) {
17274
+ if (decorative === undefined && !onClick) {
17275
+ decorative = true;
17419
17276
  }
17277
+ } else {
17278
+ box = true;
17420
17279
  }
17421
-
17422
- .navi_text_overflow {
17423
- flex-wrap: wrap;
17424
- text-overflow: ellipsis;
17425
- overflow: hidden;
17280
+ const ariaProps = decorative ? {
17281
+ "aria-hidden": "true"
17282
+ } : {};
17283
+ if (typeof children === "string") {
17284
+ return jsx(Text, {
17285
+ ...props,
17286
+ ...ariaProps,
17287
+ "data-icon-text": "",
17288
+ children: children
17289
+ });
17290
+ }
17291
+ if (box) {
17292
+ return jsx(Box, {
17293
+ square: true,
17294
+ ...props,
17295
+ ...ariaProps,
17296
+ box: box,
17297
+ baseClassName: "navi_icon",
17298
+ "data-has-width": hasExplicitWidth ? "" : undefined,
17299
+ "data-has-height": hasExplicitHeight ? "" : undefined,
17300
+ "data-interactive": onClick ? "" : undefined,
17301
+ onClick: onClick,
17302
+ children: innerChildren
17303
+ });
17426
17304
  }
17305
+ const invisibleText = baseChar.repeat(charWidth);
17306
+ return jsxs(Text, {
17307
+ ...props,
17308
+ ...ariaProps,
17309
+ className: withPropsClassName("navi_icon", props.className),
17310
+ spacing: "pre",
17311
+ "data-icon-char": "",
17312
+ "data-has-width": hasExplicitWidth ? "" : undefined,
17313
+ "data-has-height": hasExplicitHeight ? "" : undefined,
17314
+ "data-interactive": onClick ? "" : undefined,
17315
+ onClick: onClick,
17316
+ children: [jsx("span", {
17317
+ className: "navi_icon_char_slot",
17318
+ "aria-hidden": "true",
17319
+ children: invisibleText
17320
+ }), jsx(Text, {
17321
+ className: "navi_icon_foreground",
17322
+ spacing: "pre",
17323
+ children: innerChildren
17324
+ })]
17325
+ });
17326
+ };
17327
+
17328
+ const EmailSvg = () => {
17329
+ return jsxs("svg", {
17330
+ viewBox: "0 0 24 24",
17331
+ xmlns: "http://www.w3.org/2000/svg",
17332
+ children: [jsx("path", {
17333
+ 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",
17334
+ fill: "none",
17335
+ stroke: "currentColor",
17336
+ "stroke-width": "2"
17337
+ }), jsx("path", {
17338
+ d: "m2 6 8 5 2 1.5 2-1.5 8-5",
17339
+ fill: "none",
17340
+ stroke: "currentColor",
17341
+ "stroke-width": "2",
17342
+ "stroke-linecap": "round",
17343
+ "stroke-linejoin": "round"
17344
+ })]
17345
+ });
17346
+ };
17347
+
17348
+ const LinkBlankTargetSvg = () => {
17349
+ return jsx("svg", {
17350
+ viewBox: "0 0 24 24",
17351
+ xmlns: "http://www.w3.org/2000/svg",
17352
+ children: jsx("path", {
17353
+ 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",
17354
+ stroke: "currentColor",
17355
+ fill: "none",
17356
+ "stroke-width": "2",
17357
+ "stroke-linecap": "round",
17358
+ "stroke-linejoin": "round"
17359
+ })
17360
+ });
17361
+ };
17362
+ const LinkAnchorSvg = () => {
17363
+ return jsx("svg", {
17364
+ viewBox: "0 0 24 24",
17365
+ xmlns: "http://www.w3.org/2000/svg",
17366
+ children: jsxs("g", {
17367
+ children: [jsx("path", {
17368
+ 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",
17369
+ fill: "currentColor"
17370
+ }), jsx("path", {
17371
+ 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",
17372
+ fill: "currentColor"
17373
+ })]
17374
+ })
17375
+ });
17376
+ };
17427
17377
 
17428
- .navi_text_overflow_wrapper {
17429
- display: flex;
17430
- width: 0;
17431
- flex-grow: 1;
17432
- gap: 0.3em;
17433
- }
17378
+ const PhoneSvg = () => {
17379
+ return jsx("svg", {
17380
+ viewBox: "0 0 24 24",
17381
+ xmlns: "http://www.w3.org/2000/svg",
17382
+ children: jsx("path", {
17383
+ 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",
17384
+ fill: "currentColor"
17385
+ })
17386
+ });
17387
+ };
17434
17388
 
17435
- .navi_text_overflow_text {
17436
- max-width: 100%;
17437
- text-overflow: ellipsis;
17438
- overflow: hidden;
17439
- }
17389
+ const useDebounceTrue = (value, delay = 300) => {
17390
+ const [debouncedTrue, setDebouncedTrue] = useState(false);
17391
+ const timerRef = useRef(null);
17440
17392
 
17441
- .navi_custom_space {
17442
- }
17393
+ useLayoutEffect(() => {
17394
+ // If value is true or becomes true, start a timer
17395
+ if (value) {
17396
+ timerRef.current = setTimeout(() => {
17397
+ setDebouncedTrue(true);
17398
+ }, delay);
17399
+ } else {
17400
+ // If value becomes false, clear any pending timer and immediately set to false
17401
+ if (timerRef.current) {
17402
+ clearTimeout(timerRef.current);
17403
+ timerRef.current = null;
17404
+ }
17405
+ setDebouncedTrue(false);
17406
+ }
17443
17407
 
17444
- .navi_text_bold_wrapper {
17445
- position: relative;
17446
- display: inline-block;
17447
- }
17448
- .navi_text_bold_clone {
17449
- font-weight: bold;
17450
- opacity: 0;
17451
- }
17452
- .navi_text_bold_foreground {
17453
- position: absolute;
17454
- inset: 0;
17455
- }
17408
+ // Cleanup function
17409
+ return () => {
17410
+ if (timerRef.current) {
17411
+ clearTimeout(timerRef.current);
17412
+ }
17413
+ };
17414
+ }, [value, delay]);
17456
17415
 
17457
- .navi_text_bold_background {
17458
- position: absolute;
17459
- top: 0;
17460
- left: 0;
17461
- color: currentColor;
17462
- font-weight: normal;
17463
- background: currentColor;
17464
- background-clip: text;
17465
- -webkit-background-clip: text;
17466
- transform-origin: center;
17467
- -webkit-text-fill-color: transparent;
17468
- opacity: 0;
17469
- }
17416
+ return debouncedTrue;
17417
+ };
17470
17418
 
17471
- .navi_text[data-bold] {
17472
- .navi_text_bold_background {
17473
- opacity: 1;
17474
- }
17475
- }
17419
+ const useNetworkSpeed = () => {
17420
+ return networkSpeedSignal.value;
17421
+ };
17476
17422
 
17477
- .navi_text[data-bold-transition] {
17478
- .navi_text_bold_foreground {
17479
- transition-property: font-weight;
17480
- transition-duration: 0.3s;
17481
- transition-timing-function: ease;
17482
- }
17423
+ const connection =
17424
+ window.navigator.connection ||
17425
+ window.navigator.mozConnection ||
17426
+ window.navigator.webkitConnection;
17483
17427
 
17484
- .navi_text_bold_background {
17485
- transition-property: opacity;
17486
- transition-duration: 0.3s;
17487
- transition-timing-function: ease;
17488
- }
17489
- }
17490
- `;
17491
- const REGULAR_SPACE = jsx("span", {
17492
- "data-navi-space": "",
17493
- children: " "
17494
- });
17495
- const CustomWidthSpace = ({
17496
- value
17497
- }) => {
17498
- return jsx("span", {
17499
- className: "navi_custom_space",
17500
- style: `padding-left: ${value}`,
17501
- children: "\u200B"
17502
- });
17503
- };
17504
- const applySpacingOnTextChildren = (children, spacing) => {
17505
- if (spacing === "pre" || spacing === "0" || spacing === 0) {
17506
- return children;
17507
- }
17508
- if (!children) {
17509
- return children;
17510
- }
17511
- const childArray = toChildArray(children);
17512
- const childCount = childArray.length;
17513
- if (childCount <= 1) {
17514
- return children;
17515
- }
17516
- let separator;
17517
- if (spacing === undefined) {
17518
- spacing = REGULAR_SPACE;
17519
- } else if (typeof spacing === "string") {
17520
- if (isSizeSpacingScaleKey(spacing)) {
17521
- separator = jsx(CustomWidthSpace, {
17522
- value: resolveSpacingSize(spacing)
17523
- });
17524
- } else if (hasCSSSizeUnit(spacing)) {
17525
- separator = jsx(CustomWidthSpace, {
17526
- value: resolveSpacingSize(spacing)
17527
- });
17528
- } else {
17529
- separator = spacing;
17530
- }
17531
- } else if (typeof spacing === "number") {
17532
- separator = jsx(CustomWidthSpace, {
17533
- value: spacing
17534
- });
17535
- } else {
17536
- separator = spacing;
17428
+ const getNetworkSpeed = () => {
17429
+ // ✅ Network Information API (support moderne)
17430
+ if (!connection) {
17431
+ return "3g";
17537
17432
  }
17538
- const childrenWithGap = [];
17539
- let i = 0;
17540
- while (true) {
17541
- const child = childArray[i];
17542
- childrenWithGap.push(child);
17543
- i++;
17544
- if (i === childCount) {
17545
- break;
17546
- }
17547
- const currentChild = childArray[i - 1];
17548
- const nextChild = childArray[i];
17549
- if (endsWithWhitespace(currentChild)) {
17550
- continue;
17433
+ if (connection) {
17434
+ const effectiveType = connection.effectiveType;
17435
+ if (effectiveType) {
17436
+ return effectiveType; // "slow-2g", "2g", "3g", "4g", "5g"
17551
17437
  }
17552
- if (startsWithWhitespace(nextChild)) {
17553
- continue;
17438
+ const downlink = connection.downlink;
17439
+ if (downlink) {
17440
+ // downlink is in Mbps
17441
+ if (downlink < 1) return "slow-2g"; // < 1 Mbps
17442
+ if (downlink < 2.5) return "2g"; // 1-2.5 Mbps
17443
+ if (downlink < 10) return "3g"; // 2.5-10 Mbps
17444
+ return "4g"; // > 10 Mbps
17554
17445
  }
17555
- childrenWithGap.push(separator);
17556
- }
17557
- return childrenWithGap;
17558
- };
17559
- const endsWithWhitespace = jsxChild => {
17560
- if (typeof jsxChild === "string") {
17561
- return /\s$/.test(jsxChild);
17562
- }
17563
- return false;
17564
- };
17565
- const startsWithWhitespace = jsxChild => {
17566
- if (typeof jsxChild === "string") {
17567
- return /^\s/.test(jsxChild);
17568
17446
  }
17569
- return false;
17447
+ return "3g";
17570
17448
  };
17571
- const OverflowPinnedElementContext = createContext(null);
17572
- const Text = props => {
17573
- const {
17574
- overflowEllipsis,
17575
- ...rest
17576
- } = props;
17577
- if (overflowEllipsis) {
17578
- return jsx(TextOverflow, {
17579
- ...rest
17580
- });
17581
- }
17582
- if (props.overflowPinned) {
17583
- return jsx(TextOverflowPinned, {
17584
- ...props
17585
- });
17586
- }
17587
- if (props.selectRange) {
17588
- return jsx(TextWithSelectRange, {
17589
- ...props
17590
- });
17591
- }
17592
- return jsx(TextBasic, {
17593
- ...props
17594
- });
17449
+
17450
+ const updateNetworkSpeed = () => {
17451
+ networkSpeedSignal.value = getNetworkSpeed();
17595
17452
  };
17596
- const TextOverflow = ({
17597
- noWrap,
17598
- children,
17599
- ...rest
17600
- }) => {
17601
- const [OverflowPinnedElement, setOverflowPinnedElement] = useState(null);
17602
- return jsx(Text, {
17603
- column: true,
17604
- as: "div",
17605
- nowWrap: noWrap,
17606
- pre: !noWrap
17607
- // For paragraph we prefer to keep lines and only hide unbreakable long sections
17608
- ,
17609
- preLine: rest.as === "p",
17610
- ...rest,
17611
- className: "navi_text_overflow",
17612
- expandX: true,
17613
- spacing: "pre",
17614
- children: jsxs("span", {
17615
- className: "navi_text_overflow_wrapper",
17616
- children: [jsx(OverflowPinnedElementContext.Provider, {
17617
- value: setOverflowPinnedElement,
17618
- children: jsx(Text, {
17619
- className: "navi_text_overflow_text",
17620
- children: children
17621
- })
17622
- }), OverflowPinnedElement]
17623
- })
17453
+
17454
+ const networkSpeedSignal = signal(getNetworkSpeed());
17455
+
17456
+ const setupNetworkMonitoring = () => {
17457
+ const cleanupFunctions = [];
17458
+
17459
+ // 1. Écouter les changements natifs
17460
+
17461
+ if (connection) {
17462
+ connection.addEventListener("change", updateNetworkSpeed);
17463
+ cleanupFunctions.push(() => {
17464
+ connection.removeEventListener("change", updateNetworkSpeed);
17465
+ });
17466
+ }
17467
+
17468
+ // ✅ 2. Polling de backup (toutes les 60 secondes)
17469
+ const pollInterval = setInterval(updateNetworkSpeed, 60000);
17470
+ cleanupFunctions.push(() => clearInterval(pollInterval));
17471
+
17472
+ // ✅ 3. Vérifier lors de la reprise d'activité
17473
+ const handleVisibilityChange = () => {
17474
+ if (!document.hidden) {
17475
+ updateNetworkSpeed();
17476
+ }
17477
+ };
17478
+
17479
+ document.addEventListener("visibilitychange", handleVisibilityChange);
17480
+ cleanupFunctions.push(() => {
17481
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
17624
17482
  });
17625
- };
17626
- const TextOverflowPinned = ({
17627
- overflowPinned,
17628
- ...props
17629
- }) => {
17630
- const setOverflowPinnedElement = useContext(OverflowPinnedElementContext);
17631
- const text = jsx(Text, {
17632
- ...props
17483
+
17484
+ // 4. Vérifier lors de la reprise de connexion
17485
+ const handleOnline = () => {
17486
+ updateNetworkSpeed();
17487
+ };
17488
+
17489
+ window.addEventListener("online", handleOnline);
17490
+ cleanupFunctions.push(() => {
17491
+ window.removeEventListener("online", handleOnline);
17633
17492
  });
17634
- if (!setOverflowPinnedElement) {
17635
- console.warn("<Text overflowPinned> declared outside a <Text overflowEllipsis>");
17636
- return text;
17493
+
17494
+ // Cleanup global
17495
+ return () => {
17496
+ cleanupFunctions.forEach((cleanup) => cleanup());
17497
+ };
17498
+ };
17499
+ setupNetworkMonitoring();
17500
+
17501
+ installImportMetaCss(import.meta);import.meta.css = /* css */`
17502
+ .navi_rectangle_loading {
17503
+ position: relative;
17504
+ display: flex;
17505
+ width: 100%;
17506
+ height: 100%;
17507
+ opacity: 0;
17637
17508
  }
17638
- if (overflowPinned) {
17639
- setOverflowPinnedElement(text);
17640
- return null;
17509
+
17510
+ .navi_rectangle_loading[data-visible] {
17511
+ opacity: 1;
17641
17512
  }
17642
- setOverflowPinnedElement(null);
17643
- return text;
17644
- };
17645
- const TextWithSelectRange = ({
17646
- selectRange,
17647
- ...props
17513
+ `;
17514
+ const RectangleLoading = ({
17515
+ shouldShowSpinner,
17516
+ color = "currentColor",
17517
+ radius = 0,
17518
+ size = 2
17648
17519
  }) => {
17649
- const defaultRef = useRef();
17650
- const ref = props.ref || defaultRef;
17651
- useInitialTextSelection(ref, selectRange);
17652
- return jsx(Text, {
17653
- ref: ref,
17654
- ...props
17520
+ const containerRef = useRef(null);
17521
+ const [containerWidth, setContainerWidth] = useState(0);
17522
+ const [containerHeight, setContainerHeight] = useState(0);
17523
+ useLayoutEffect(() => {
17524
+ const container = containerRef.current;
17525
+ if (!container) {
17526
+ return null;
17527
+ }
17528
+ const {
17529
+ width,
17530
+ height
17531
+ } = container.getBoundingClientRect();
17532
+ setContainerWidth(width);
17533
+ setContainerHeight(height);
17534
+ let animationFrameId = null;
17535
+ // Create a resize observer to detect changes in the container's dimensions
17536
+ const resizeObserver = new ResizeObserver(entries => {
17537
+ // Use requestAnimationFrame to debounce updates
17538
+ if (animationFrameId) {
17539
+ cancelAnimationFrame(animationFrameId);
17540
+ }
17541
+ animationFrameId = requestAnimationFrame(() => {
17542
+ const [containerEntry] = entries;
17543
+ const {
17544
+ width,
17545
+ height
17546
+ } = containerEntry.contentRect;
17547
+ setContainerWidth(width);
17548
+ setContainerHeight(height);
17549
+ });
17550
+ });
17551
+ resizeObserver.observe(container);
17552
+ return () => {
17553
+ if (animationFrameId) {
17554
+ cancelAnimationFrame(animationFrameId);
17555
+ }
17556
+ resizeObserver.disconnect();
17557
+ };
17558
+ }, []);
17559
+ return jsx("span", {
17560
+ ref: containerRef,
17561
+ className: "navi_rectangle_loading",
17562
+ "data-visible": shouldShowSpinner ? "" : undefined,
17563
+ children: containerWidth > 0 && containerHeight > 0 && jsx(RectangleLoadingSvg, {
17564
+ radius: radius,
17565
+ color: color,
17566
+ width: containerWidth,
17567
+ height: containerHeight,
17568
+ strokeWidth: size
17569
+ })
17655
17570
  });
17656
17571
  };
17657
- const TextBasic = ({
17658
- spacing = " ",
17659
- boldTransition,
17660
- boldStable,
17661
- preventBoldLayoutShift = boldTransition,
17662
- children,
17663
- ...rest
17572
+ const RectangleLoadingSvg = ({
17573
+ width,
17574
+ height,
17575
+ color,
17576
+ radius,
17577
+ trailColor = "transparent",
17578
+ strokeWidth
17664
17579
  }) => {
17665
- const boxProps = {
17666
- "as": "span",
17667
- "data-bold-transition": boldTransition ? "" : undefined,
17668
- ...rest,
17669
- "baseClassName": withPropsClassName("navi_text", rest.baseClassName)
17670
- };
17671
- const shouldPreserveSpacing = rest.as === "pre" || rest.box || rest.column || rest.row;
17672
- if (shouldPreserveSpacing) {
17673
- boxProps.spacing = spacing;
17580
+ const margin = Math.max(2, Math.min(width, height) * 0.03);
17581
+
17582
+ // Calculate the drawable area
17583
+ const drawableWidth = width - margin * 2;
17584
+ const drawableHeight = height - margin * 2;
17585
+
17586
+ // Check if this should be a circle - only if width and height are nearly equal
17587
+ const maxPossibleRadius = Math.min(drawableWidth, drawableHeight) / 2;
17588
+ const actualRadius = Math.min(radius || Math.min(drawableWidth, drawableHeight) * 0.05, maxPossibleRadius // ✅ Limité au radius maximum possible
17589
+ );
17590
+ const aspectRatio = Math.max(drawableWidth, drawableHeight) / Math.min(drawableWidth, drawableHeight);
17591
+ const isNearlySquare = aspectRatio <= 1.2; // Allow some tolerance for nearly square shapes
17592
+ const isCircle = isNearlySquare && actualRadius >= maxPossibleRadius * 0.95;
17593
+ let pathLength;
17594
+ let rectPath;
17595
+ if (isCircle) {
17596
+ // ✅ Circle: perimeter = 2πr
17597
+ pathLength = 2 * Math.PI * actualRadius;
17598
+
17599
+ // ✅ Circle path centered in the drawable area
17600
+ const centerX = margin + drawableWidth / 2;
17601
+ const centerY = margin + drawableHeight / 2;
17602
+ rectPath = `
17603
+ M ${centerX + actualRadius},${centerY}
17604
+ A ${actualRadius},${actualRadius} 0 1 1 ${centerX - actualRadius},${centerY}
17605
+ A ${actualRadius},${actualRadius} 0 1 1 ${centerX + actualRadius},${centerY}
17606
+ `;
17674
17607
  } else {
17675
- children = applySpacingOnTextChildren(children, spacing);
17676
- }
17677
- if (boldStable) {
17678
- const {
17679
- bold
17680
- } = boxProps;
17681
- return jsxs(Box, {
17682
- ...boxProps,
17683
- bold: undefined,
17684
- "data-bold": bold ? "" : undefined,
17685
- "data-has-absolute-child": "",
17686
- children: [jsx("span", {
17687
- className: "navi_text_bold_background",
17688
- "aria-hidden": "true",
17689
- children: children
17690
- }), children]
17691
- });
17608
+ // Rectangle: calculate perimeter properly
17609
+ const straightEdges = 2 * (drawableWidth - 2 * actualRadius) + 2 * (drawableHeight - 2 * actualRadius);
17610
+ const cornerArcs = actualRadius > 0 ? 2 * Math.PI * actualRadius : 0;
17611
+ pathLength = straightEdges + cornerArcs;
17612
+ rectPath = `
17613
+ M ${margin + actualRadius},${margin}
17614
+ L ${margin + drawableWidth - actualRadius},${margin}
17615
+ A ${actualRadius},${actualRadius} 0 0 1 ${margin + drawableWidth},${margin + actualRadius}
17616
+ L ${margin + drawableWidth},${margin + drawableHeight - actualRadius}
17617
+ A ${actualRadius},${actualRadius} 0 0 1 ${margin + drawableWidth - actualRadius},${margin + drawableHeight}
17618
+ L ${margin + actualRadius},${margin + drawableHeight}
17619
+ A ${actualRadius},${actualRadius} 0 0 1 ${margin},${margin + drawableHeight - actualRadius}
17620
+ L ${margin},${margin + actualRadius}
17621
+ A ${actualRadius},${actualRadius} 0 0 1 ${margin + actualRadius},${margin}
17622
+ `;
17692
17623
  }
17693
- if (preventBoldLayoutShift) {
17694
- const alignX = rest.alignX || rest.align || "start";
17695
17624
 
17696
- // La technique consiste a avoid un double gras qui force une taille
17697
- // et la version light par dessus en position absolute
17698
- // on la centre aussi pour donner l'impression que le gras s'applique depuis le centre
17699
- // ne fonctionne que sur une seul ligne de texte (donc lorsque noWrap est actif)
17700
- // on pourrait auto-active cela sur une prop genre boldCanChange
17701
- return jsx(Box, {
17702
- ...boxProps,
17703
- children: jsxs("span", {
17704
- className: "navi_text_bold_wrapper",
17705
- children: [jsx("span", {
17706
- className: "navi_text_bold_clone",
17707
- "aria-hidden": "true",
17708
- children: children
17709
- }), jsx("span", {
17710
- className: "navi_text_bold_foreground",
17711
- "data-align": alignX,
17712
- children: children
17713
- })]
17625
+ // Fixed segment size in pixels
17626
+ const maxSegmentSize = 40;
17627
+ const segmentLength = Math.min(maxSegmentSize, pathLength * 0.25);
17628
+ const gapLength = pathLength - segmentLength;
17629
+
17630
+ // Vitesse constante en pixels par seconde
17631
+ const networkSpeed = useNetworkSpeed();
17632
+ const pixelsPerSecond = {
17633
+ "slow-2g": 40,
17634
+ "2g": 60,
17635
+ "3g": 80,
17636
+ "4g": 120
17637
+ }[networkSpeed] || 80;
17638
+ const animationDuration = Math.max(1.5, pathLength / pixelsPerSecond);
17639
+
17640
+ // ✅ Calculate correct offset based on actual segment size
17641
+ const segmentRatio = segmentLength / pathLength;
17642
+ const circleOffset = -animationDuration * segmentRatio;
17643
+ return jsxs("svg", {
17644
+ width: "100%",
17645
+ height: "100%",
17646
+ viewBox: `0 0 ${width} ${height}`,
17647
+ preserveAspectRatio: "none",
17648
+ style: "overflow: visible",
17649
+ xmlns: "http://www.w3.org/2000/svg",
17650
+ "shape-rendering": "geometricPrecision",
17651
+ children: [isCircle ? jsx("circle", {
17652
+ cx: margin + drawableWidth / 2,
17653
+ cy: margin + drawableHeight / 2,
17654
+ r: actualRadius,
17655
+ fill: "none",
17656
+ stroke: trailColor,
17657
+ strokeWidth: strokeWidth
17658
+ }) : jsx("rect", {
17659
+ x: margin,
17660
+ y: margin,
17661
+ width: drawableWidth,
17662
+ height: drawableHeight,
17663
+ fill: "none",
17664
+ stroke: trailColor,
17665
+ strokeWidth: strokeWidth,
17666
+ rx: actualRadius
17667
+ }), jsx("path", {
17668
+ d: rectPath,
17669
+ fill: "none",
17670
+ stroke: color,
17671
+ strokeWidth: strokeWidth,
17672
+ strokeLinecap: "round",
17673
+ strokeDasharray: `${segmentLength} ${gapLength}`,
17674
+ pathLength: pathLength,
17675
+ children: jsx("animate", {
17676
+ attributeName: "stroke-dashoffset",
17677
+ from: pathLength,
17678
+ to: "0",
17679
+ dur: `${animationDuration}s`,
17680
+ repeatCount: "indefinite",
17681
+ begin: "0s"
17714
17682
  })
17715
- });
17716
- }
17717
- return jsx(Box, {
17718
- ...boxProps,
17719
- children: children
17683
+ }), jsx("circle", {
17684
+ r: strokeWidth,
17685
+ fill: color,
17686
+ children: jsx("animateMotion", {
17687
+ path: rectPath,
17688
+ dur: `${animationDuration}s`,
17689
+ repeatCount: "indefinite",
17690
+ rotate: "auto",
17691
+ begin: `${circleOffset}s`
17692
+ })
17693
+ })]
17720
17694
  });
17721
17695
  };
17722
17696
 
17723
17697
  installImportMetaCss(import.meta);import.meta.css = /* css */`
17724
- .navi_icon {
17725
- display: inline-block;
17726
- box-sizing: border-box;
17727
- max-width: 100%;
17728
- max-height: 100%;
17729
-
17730
- &[data-flow-inline] {
17731
- width: 1em;
17732
- height: 1em;
17733
- }
17734
- &[data-icon-char] {
17735
- flex-grow: 0 !important;
17736
- line-height: normal;
17737
- }
17738
- }
17739
-
17740
- .navi_icon[data-interactive] {
17741
- cursor: pointer;
17742
- }
17743
-
17744
- .navi_icon_char_slot {
17745
- opacity: 0;
17746
- cursor: default;
17747
- user-select: none;
17748
- }
17749
- .navi_icon_foreground {
17698
+ .navi_loading_rectangle_wrapper {
17750
17699
  position: absolute;
17751
- inset: 0;
17752
- }
17753
- .navi_icon_foreground > .navi_text {
17754
- display: flex;
17755
- aspect-ratio: 1 / 1;
17756
- min-width: 0;
17757
- height: 100%;
17758
- max-height: 1em;
17759
- align-items: center;
17760
- justify-content: center;
17761
- }
17762
-
17763
- .navi_icon > svg,
17764
- .navi_icon > img {
17765
- width: 100%;
17766
- height: 100%;
17767
- backface-visibility: hidden;
17768
- }
17769
- .navi_icon[data-has-width] > svg,
17770
- .navi_icon[data-has-width] > img {
17771
- width: 100%;
17772
- height: auto;
17773
- }
17774
- .navi_icon[data-has-height] > svg,
17775
- .navi_icon[data-has-height] > img {
17776
- width: auto;
17777
- height: 100%;
17778
- }
17779
- .navi_icon[data-has-width][data-has-height] > svg,
17780
- .navi_icon[data-has-width][data-has-height] > img {
17781
- width: 100%;
17782
- height: 100%;
17783
- }
17784
-
17785
- .navi_icon[data-icon-char] svg,
17786
- .navi_icon[data-icon-char] img {
17787
- width: 100%;
17788
- height: 100%;
17700
+ top: var(--rectangle-top, 0);
17701
+ right: var(--rectangle-right, 0);
17702
+ bottom: var(--rectangle-bottom, 0);
17703
+ left: var(--rectangle-left, 0);
17704
+ z-index: 1;
17705
+ opacity: 0;
17706
+ pointer-events: none;
17789
17707
  }
17790
- .navi_icon[data-icon-char] svg {
17791
- overflow: visible;
17708
+ .navi_loading_rectangle_wrapper[data-visible] {
17709
+ opacity: 1;
17792
17710
  }
17793
17711
  `;
17794
- const Icon = ({
17795
- href,
17796
- children,
17797
- charWidth = 1,
17798
- // 0 (zéro) is the real char width
17799
- // but 2 zéros gives too big icons
17800
- // while 1 "W" gives a nice result
17801
- baseChar = "W",
17802
- decorative,
17803
- onClick,
17804
- ...props
17712
+ const LoaderBackground = ({
17713
+ loading,
17714
+ containerRef,
17715
+ targetSelector,
17716
+ color,
17717
+ inset = 0,
17718
+ spacingTop = 0,
17719
+ spacingLeft = 0,
17720
+ spacingBottom = 0,
17721
+ spacingRight = 0,
17722
+ children
17805
17723
  }) => {
17806
- const innerChildren = href ? jsx("svg", {
17807
- width: "100%",
17808
- height: "100%",
17809
- children: jsx("use", {
17810
- href: href
17811
- })
17812
- }) : children;
17813
- let {
17814
- box,
17815
- width,
17816
- height
17817
- } = props;
17818
- if (width === "auto") width = undefined;
17819
- if (height === "auto") height = undefined;
17820
- const hasExplicitWidth = width !== undefined;
17821
- const hasExplicitHeight = height !== undefined;
17822
- if (!hasExplicitWidth && !hasExplicitHeight) {
17823
- if (decorative === undefined && !onClick) {
17824
- decorative = true;
17724
+ if (containerRef) {
17725
+ const container = containerRef.current;
17726
+ if (!container) {
17727
+ return children;
17825
17728
  }
17826
- } else {
17827
- box = true;
17828
- }
17829
- const ariaProps = decorative ? {
17830
- "aria-hidden": "true"
17831
- } : {};
17832
- if (typeof children === "string") {
17833
- return jsx(Text, {
17834
- ...props,
17835
- ...ariaProps,
17836
- "data-icon-text": "",
17729
+ return createPortal(jsx(LoaderBackgroundWithPortal, {
17730
+ container: container,
17731
+ loading: loading,
17732
+ color: color,
17733
+ inset: inset,
17734
+ spacingTop: spacingTop,
17735
+ spacingLeft: spacingLeft,
17736
+ spacingBottom: spacingBottom,
17737
+ spacingRight: spacingRight,
17837
17738
  children: children
17838
- });
17839
- }
17840
- if (box) {
17841
- return jsx(Box, {
17842
- square: true,
17843
- ...props,
17844
- ...ariaProps,
17845
- box: box,
17846
- baseClassName: "navi_icon",
17847
- "data-has-width": hasExplicitWidth ? "" : undefined,
17848
- "data-has-height": hasExplicitHeight ? "" : undefined,
17849
- "data-interactive": onClick ? "" : undefined,
17850
- onClick: onClick,
17851
- children: innerChildren
17852
- });
17739
+ }), container);
17853
17740
  }
17854
- const invisibleText = baseChar.repeat(charWidth);
17855
- return jsxs(Text, {
17856
- ...props,
17857
- ...ariaProps,
17858
- className: withPropsClassName("navi_icon", props.className),
17859
- spacing: "pre",
17860
- "data-icon-char": "",
17861
- "data-has-width": hasExplicitWidth ? "" : undefined,
17862
- "data-has-height": hasExplicitHeight ? "" : undefined,
17863
- "data-interactive": onClick ? "" : undefined,
17864
- onClick: onClick,
17865
- children: [jsx("span", {
17866
- className: "navi_icon_char_slot",
17867
- "aria-hidden": "true",
17868
- children: invisibleText
17869
- }), jsx(Text, {
17870
- className: "navi_icon_foreground",
17871
- spacing: "pre",
17872
- children: innerChildren
17873
- })]
17741
+ return jsx(LoaderBackgroundBasic, {
17742
+ targetSelector: targetSelector,
17743
+ loading: loading,
17744
+ color: color,
17745
+ inset: inset,
17746
+ spacingTop: spacingTop,
17747
+ spacingLeft: spacingLeft,
17748
+ spacingBottom: spacingBottom,
17749
+ spacingRight: spacingRight,
17750
+ children: children
17874
17751
  });
17875
17752
  };
17876
-
17877
- const LinkBlankTargetSvg = () => {
17878
- return jsx("svg", {
17879
- viewBox: "0 0 24 24",
17880
- xmlns: "http://www.w3.org/2000/svg",
17881
- children: jsx("path", {
17882
- 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",
17883
- stroke: "currentColor",
17884
- fill: "none",
17885
- "stroke-width": "2",
17886
- "stroke-linecap": "round",
17887
- "stroke-linejoin": "round"
17888
- })
17753
+ const LoaderBackgroundWithPortal = ({
17754
+ container,
17755
+ loading,
17756
+ color,
17757
+ inset,
17758
+ spacingTop,
17759
+ spacingLeft,
17760
+ spacingBottom,
17761
+ spacingRight,
17762
+ children
17763
+ }) => {
17764
+ const shouldShowSpinner = useDebounceTrue(loading, 300);
17765
+ if (!shouldShowSpinner) {
17766
+ return children;
17767
+ }
17768
+ container.style.position = "relative";
17769
+ let paddingTop = 0;
17770
+ if (container.nodeName === "DETAILS") {
17771
+ paddingTop = container.querySelector("summary").offsetHeight;
17772
+ }
17773
+ return jsxs(Fragment, {
17774
+ children: [jsx("div", {
17775
+ style: {
17776
+ position: "absolute",
17777
+ top: `${inset + paddingTop + spacingTop}px`,
17778
+ bottom: `${inset + spacingBottom}px`,
17779
+ left: `${inset + spacingLeft}px`,
17780
+ right: `${inset + spacingRight}px`
17781
+ },
17782
+ children: shouldShowSpinner && jsx(RectangleLoading, {
17783
+ color: color
17784
+ })
17785
+ }), children]
17889
17786
  });
17890
17787
  };
17891
- const LinkAnchorSvg = () => {
17892
- return jsx("svg", {
17893
- viewBox: "0 0 24 24",
17894
- xmlns: "http://www.w3.org/2000/svg",
17895
- children: jsxs("g", {
17896
- children: [jsx("path", {
17897
- 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",
17898
- fill: "currentColor"
17899
- }), jsx("path", {
17900
- 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",
17901
- fill: "currentColor"
17902
- })]
17903
- })
17788
+ const LoaderBackgroundBasic = ({
17789
+ loading,
17790
+ targetSelector,
17791
+ color,
17792
+ spacingTop,
17793
+ spacingLeft,
17794
+ spacingBottom,
17795
+ spacingRight,
17796
+ inset,
17797
+ children
17798
+ }) => {
17799
+ const shouldShowSpinner = useDebounceTrue(loading, 300);
17800
+ const rectangleRef = useRef(null);
17801
+ const [, setOutlineOffset] = useState(0);
17802
+ const [borderRadius, setBorderRadius] = useState(0);
17803
+ const [borderTopWidth, setBorderTopWidth] = useState(0);
17804
+ const [borderLeftWidth, setBorderLeftWidth] = useState(0);
17805
+ const [borderRightWidth, setBorderRightWidth] = useState(0);
17806
+ const [borderBottomWidth, setBorderBottomWidth] = useState(0);
17807
+ const [marginTop, setMarginTop] = useState(0);
17808
+ const [marginBottom, setMarginBottom] = useState(0);
17809
+ const [marginLeft, setMarginLeft] = useState(0);
17810
+ const [marginRight, setMarginRight] = useState(0);
17811
+ const [paddingTop, setPaddingTop] = useState(0);
17812
+ const [paddingLeft, setPaddingLeft] = useState(0);
17813
+ const [paddingRight, setPaddingRight] = useState(0);
17814
+ const [paddingBottom, setPaddingBottom] = useState(0);
17815
+ const [currentColor, setCurrentColor] = useState(color);
17816
+ useLayoutEffect(() => {
17817
+ let animationFrame;
17818
+ const updateStyles = () => {
17819
+ const rectangle = rectangleRef.current;
17820
+ if (!rectangle) {
17821
+ return;
17822
+ }
17823
+ const container = rectangle.parentElement;
17824
+ const containedElement = rectangle.nextElementSibling;
17825
+ const target = targetSelector ? container.querySelector(targetSelector) : containedElement;
17826
+ if (target) {
17827
+ const {
17828
+ width,
17829
+ height
17830
+ } = target.getBoundingClientRect();
17831
+ const containedComputedStyle = window.getComputedStyle(containedElement);
17832
+ const targetComputedStyle = window.getComputedStyle(target);
17833
+ const newBorderTopWidth = resolveCSSSize(targetComputedStyle.borderTopWidth);
17834
+ const newBorderLeftWidth = resolveCSSSize(targetComputedStyle.borderLeftWidth);
17835
+ const newBorderRightWidth = resolveCSSSize(targetComputedStyle.borderRightWidth);
17836
+ const newBorderBottomWidth = resolveCSSSize(targetComputedStyle.borderBottomWidth);
17837
+ const newBorderRadius = resolveCSSSize(targetComputedStyle.borderRadius, {
17838
+ availableSize: Math.min(width, height)
17839
+ });
17840
+ const newOutlineColor = targetComputedStyle.outlineColor;
17841
+ const newBorderColor = targetComputedStyle.borderColor;
17842
+ const newDetectedColor = targetComputedStyle.color;
17843
+ const newOutlineOffset = resolveCSSSize(targetComputedStyle.outlineOffset);
17844
+ const newMarginTop = resolveCSSSize(targetComputedStyle.marginTop);
17845
+ const newMarginBottom = resolveCSSSize(targetComputedStyle.marginBottom);
17846
+ const newMarginLeft = resolveCSSSize(targetComputedStyle.marginLeft);
17847
+ const newMarginRight = resolveCSSSize(targetComputedStyle.marginRight);
17848
+ const paddingTop = resolveCSSSize(containedComputedStyle.paddingTop);
17849
+ const paddingLeft = resolveCSSSize(containedComputedStyle.paddingLeft);
17850
+ const paddingRight = resolveCSSSize(containedComputedStyle.paddingRight);
17851
+ const paddingBottom = resolveCSSSize(containedComputedStyle.paddingBottom);
17852
+ setBorderTopWidth(newBorderTopWidth);
17853
+ setBorderLeftWidth(newBorderLeftWidth);
17854
+ setBorderRightWidth(newBorderRightWidth);
17855
+ setBorderBottomWidth(newBorderBottomWidth);
17856
+ setBorderRadius(newBorderRadius);
17857
+ setOutlineOffset(newOutlineOffset);
17858
+ setMarginTop(newMarginTop);
17859
+ setMarginBottom(newMarginBottom);
17860
+ setMarginLeft(newMarginLeft);
17861
+ setMarginRight(newMarginRight);
17862
+ setPaddingTop(paddingTop);
17863
+ setPaddingLeft(paddingLeft);
17864
+ setPaddingRight(paddingRight);
17865
+ setPaddingBottom(paddingBottom);
17866
+ if (color) {
17867
+ // const resolvedColor = resolveCSSColor(color, rectangle, "css");
17868
+ // console.log(resolvedColor);
17869
+ setCurrentColor(color);
17870
+ } else if (newOutlineColor && newOutlineColor !== "rgba(0, 0, 0, 0)" && (document.activeElement === containedElement || newBorderColor === "rgba(0, 0, 0, 0)")) {
17871
+ setCurrentColor(newOutlineColor);
17872
+ } else if (newBorderColor && newBorderColor !== "rgba(0, 0, 0, 0)") {
17873
+ setCurrentColor(newBorderColor);
17874
+ } else {
17875
+ setCurrentColor(newDetectedColor);
17876
+ }
17877
+ }
17878
+ // updateStyles is very cheap so we run it every frame
17879
+ animationFrame = requestAnimationFrame(updateStyles);
17880
+ };
17881
+ updateStyles();
17882
+ return () => {
17883
+ cancelAnimationFrame(animationFrame);
17884
+ };
17885
+ }, [color, targetSelector]);
17886
+ spacingTop += inset;
17887
+ // spacingTop += outlineOffset;
17888
+ // spacingTop -= borderTopWidth;
17889
+ spacingTop += marginTop;
17890
+ spacingLeft += inset;
17891
+ // spacingLeft += outlineOffset;
17892
+ // spacingLeft -= borderLeftWidth;
17893
+ spacingLeft += marginLeft;
17894
+ spacingRight += inset;
17895
+ // spacingRight += outlineOffset;
17896
+ // spacingRight -= borderRightWidth;
17897
+ spacingRight += marginRight;
17898
+ spacingBottom += inset;
17899
+ // spacingBottom += outlineOffset;
17900
+ // spacingBottom -= borderBottomWidth;
17901
+ spacingBottom += marginBottom;
17902
+ if (targetSelector) {
17903
+ // oversimplification that actually works
17904
+ // (simplified because it assumes the targeted element is a direct child of the contained element which may have padding)
17905
+ spacingTop += paddingTop;
17906
+ spacingLeft += paddingLeft;
17907
+ spacingRight += paddingRight;
17908
+ spacingBottom += paddingBottom;
17909
+ }
17910
+ const maxBorderWidth = Math.max(borderTopWidth, borderLeftWidth, borderRightWidth, borderBottomWidth);
17911
+ const halfMaxBorderSize = maxBorderWidth / 2;
17912
+ const size = halfMaxBorderSize < 2 ? 2 : halfMaxBorderSize;
17913
+ const lineHalfSize = size / 2;
17914
+ spacingTop -= lineHalfSize;
17915
+ spacingLeft -= lineHalfSize;
17916
+ spacingRight -= lineHalfSize;
17917
+ spacingBottom -= lineHalfSize;
17918
+ return jsxs(Fragment, {
17919
+ children: [jsx("span", {
17920
+ ref: rectangleRef,
17921
+ className: "navi_loading_rectangle_wrapper",
17922
+ "data-visible": shouldShowSpinner ? "" : undefined,
17923
+ style: {
17924
+ "--rectangle-top": `${spacingTop}px`,
17925
+ "--rectangle-left": `${spacingLeft}px`,
17926
+ "--rectangle-bottom": `${spacingBottom}px`,
17927
+ "--rectangle-right": `${spacingRight}px`
17928
+ },
17929
+ children: loading && jsx(RectangleLoading, {
17930
+ shouldShowSpinner: shouldShowSpinner,
17931
+ color: currentColor,
17932
+ radius: borderRadius,
17933
+ size: size
17934
+ })
17935
+ }), children]
17904
17936
  });
17905
17937
  };
17906
17938
 
@@ -20069,16 +20101,6 @@ const LinkPlain = props => {
20069
20101
  })]
20070
20102
  });
20071
20103
  };
20072
- const PhoneSvg = () => {
20073
- return jsx("svg", {
20074
- viewBox: "0 0 24 24",
20075
- xmlns: "http://www.w3.org/2000/svg",
20076
- children: jsx("path", {
20077
- 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",
20078
- fill: "currentColor"
20079
- })
20080
- });
20081
- };
20082
20104
  const SmsSvg = () => {
20083
20105
  return jsx("svg", {
20084
20106
  viewBox: "0 0 24 24",
@@ -20089,25 +20111,6 @@ const SmsSvg = () => {
20089
20111
  })
20090
20112
  });
20091
20113
  };
20092
- const EmailSvg = () => {
20093
- return jsxs("svg", {
20094
- viewBox: "0 0 24 24",
20095
- xmlns: "http://www.w3.org/2000/svg",
20096
- children: [jsx("path", {
20097
- 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",
20098
- fill: "none",
20099
- stroke: "currentColor",
20100
- "stroke-width": "2"
20101
- }), jsx("path", {
20102
- d: "m2 6 8 5 2 1.5 2-1.5 8-5",
20103
- fill: "none",
20104
- stroke: "currentColor",
20105
- "stroke-width": "2",
20106
- "stroke-linecap": "round",
20107
- "stroke-linejoin": "round"
20108
- })]
20109
- });
20110
- };
20111
20114
  const GithubSvg = () => {
20112
20115
  return jsx("svg", {
20113
20116
  viewBox: "0 0 24 24",
@@ -22825,6 +22828,10 @@ const InputTextualBasic = props => {
22825
22828
  if (icon === undefined) {
22826
22829
  if (type === "search") {
22827
22830
  innerIcon = jsx(SearchSvg, {});
22831
+ } else if (type === "email") {
22832
+ innerIcon = jsx(EmailSvg, {});
22833
+ } else if (type === "tel") {
22834
+ innerIcon = jsx(PhoneSvg, {});
22828
22835
  }
22829
22836
  } else {
22830
22837
  innerIcon = icon;
@@ -27389,6 +27396,69 @@ const Address = ({
27389
27396
  });
27390
27397
  };
27391
27398
 
27399
+ const LoadingDots = ({
27400
+ color = "FF156D"
27401
+ }) => {
27402
+ return jsxs("svg", {
27403
+ viewBox: "0 0 200 200",
27404
+ width: "100%",
27405
+ height: "100%",
27406
+ xmlns: "http://www.w3.org/2000/svg",
27407
+ children: [jsx("rect", {
27408
+ fill: color,
27409
+ stroke: color,
27410
+ "stroke-width": "15",
27411
+ width: "30",
27412
+ height: "30",
27413
+ x: "25",
27414
+ y: "85",
27415
+ children: jsx("animate", {
27416
+ attributeName: "opacity",
27417
+ calcMode: "spline",
27418
+ dur: "2",
27419
+ values: "1;0;1;",
27420
+ keySplines: ".5 0 .5 1;.5 0 .5 1",
27421
+ repeatCount: "indefinite",
27422
+ begin: "-.4"
27423
+ })
27424
+ }), jsx("rect", {
27425
+ fill: color,
27426
+ stroke: color,
27427
+ "stroke-width": "15",
27428
+ width: "30",
27429
+ height: "30",
27430
+ x: "85",
27431
+ y: "85",
27432
+ children: jsx("animate", {
27433
+ attributeName: "opacity",
27434
+ calcMode: "spline",
27435
+ dur: "2",
27436
+ values: "1;0;1;",
27437
+ keySplines: ".5 0 .5 1;.5 0 .5 1",
27438
+ repeatCount: "indefinite",
27439
+ begin: "-.2"
27440
+ })
27441
+ }), jsx("rect", {
27442
+ fill: color,
27443
+ stroke: color,
27444
+ "stroke-width": "15",
27445
+ width: "30",
27446
+ height: "30",
27447
+ x: "145",
27448
+ y: "85",
27449
+ children: jsx("animate", {
27450
+ attributeName: "opacity",
27451
+ calcMode: "spline",
27452
+ dur: "2",
27453
+ values: "1;0;1;",
27454
+ keySplines: ".5 0 .5 1;.5 0 .5 1",
27455
+ repeatCount: "indefinite",
27456
+ begin: "0"
27457
+ })
27458
+ })]
27459
+ });
27460
+ };
27461
+
27392
27462
  const CSS_VAR_NAME = "--x-color-contrasting";
27393
27463
 
27394
27464
  const useContrastingColor = (ref, backgroundElementSelector) => {
@@ -27556,10 +27626,11 @@ const BadgeCountCircle = ({
27556
27626
  ref,
27557
27627
  charCount,
27558
27628
  hasOverflow,
27629
+ loading,
27559
27630
  children,
27560
27631
  ...props
27561
27632
  }) => {
27562
- return jsxs(Text, {
27633
+ return jsx(Text, {
27563
27634
  ref: ref,
27564
27635
  className: "navi_badge_count",
27565
27636
  "data-circle": "",
@@ -27571,18 +27642,20 @@ const BadgeCountCircle = ({
27571
27642
  ...props,
27572
27643
  styleCSSVars: BadgeStyleCSSVars,
27573
27644
  spacing: "pre",
27574
- children: [jsx("span", {
27575
- style: "user-select: none",
27576
- children: "\u200B"
27577
- }), jsx("span", {
27578
- className: "navi_badge_count_frame"
27579
- }), jsx("span", {
27580
- className: "navi_badge_count_text",
27581
- children: children
27582
- }), jsx("span", {
27583
- style: "user-select: none",
27584
- children: "\u200B"
27585
- })]
27645
+ children: loading ? jsx(LoadingDots, {}) : jsxs(Fragment, {
27646
+ children: [jsx("span", {
27647
+ style: "user-select: none",
27648
+ children: "\u200B"
27649
+ }), jsx("span", {
27650
+ className: "navi_badge_count_frame"
27651
+ }), jsx("span", {
27652
+ className: "navi_badge_count_text",
27653
+ children: children
27654
+ }), jsx("span", {
27655
+ style: "user-select: none",
27656
+ children: "\u200B"
27657
+ })]
27658
+ })
27586
27659
  });
27587
27660
  };
27588
27661
  const BadgeCountEllipse = ({