@jsenv/navi 0.16.58 → 0.16.60

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;
@@ -16862,1045 +16313,1626 @@ const installCustomConstraintValidation = (
16862
16313
  // an other button is explicitly submitting the form, this one would not submit it
16863
16314
  return null;
16864
16315
  }
16865
- // this is the only button inside the form without type attribute, so it defaults to type="submit"
16866
- return button;
16867
- };
16868
- const formSubmitTarget = determineClosestFormSubmitTargetForClickEvent();
16869
- if (formSubmitTarget) {
16870
- clickEvent.preventDefault();
16316
+ // this is the only button inside the form without type attribute, so it defaults to type="submit"
16317
+ return button;
16318
+ };
16319
+ const formSubmitTarget = determineClosestFormSubmitTargetForClickEvent();
16320
+ if (formSubmitTarget) {
16321
+ clickEvent.preventDefault();
16322
+ }
16323
+ dispatchActionRequestedCustomEvent(elementWithAction, {
16324
+ event: clickEvent,
16325
+ requester: formSubmitTarget || button,
16326
+ });
16327
+ };
16328
+ element.addEventListener("click", onclick);
16329
+ addTeardown(() => {
16330
+ element.removeEventListener("click", onclick);
16331
+ });
16332
+ }
16333
+
16334
+ request_on_input_change: {
16335
+ const isInput =
16336
+ element.tagName === "INPUT" || element.tagName === "TEXTAREA";
16337
+ if (!isInput) {
16338
+ break request_on_input_change;
16339
+ }
16340
+ const stop = listenInputChange(element, (e) => {
16341
+ const elementWithAction = closestElementWithAction(element);
16342
+ if (!elementWithAction) {
16343
+ return;
16344
+ }
16345
+ dispatchActionRequestedCustomEvent(elementWithAction, {
16346
+ event: e,
16347
+ requester: element,
16348
+ });
16349
+ });
16350
+ addTeardown(() => {
16351
+ stop();
16352
+ });
16353
+ }
16354
+
16355
+ request_on_checkbox_change: {
16356
+ const isCheckbox =
16357
+ element.tagName === "INPUT" && element.type === "checkbox";
16358
+ if (!isCheckbox) {
16359
+ break request_on_checkbox_change;
16360
+ }
16361
+ const onchange = (e) => {
16362
+ if (element.parentNode.hasAttribute("data-action")) {
16363
+ dispatchActionRequestedCustomEvent(element, {
16364
+ event: e,
16365
+ requester: element,
16366
+ });
16367
+ return;
16368
+ }
16369
+ };
16370
+ element.addEventListener("change", onchange);
16371
+ addTeardown(() => {
16372
+ element.removeEventListener("change", onchange);
16373
+ });
16374
+ }
16375
+
16376
+ execute_on_form_submit: {
16377
+ if (!isForm) {
16378
+ break execute_on_form_submit;
16379
+ }
16380
+ // We will dispatch "action" when "submit" occurs (code called from.submit() to bypass validation)
16381
+ const form = element;
16382
+ const removeListener = addEventListener(form, "submit", (e) => {
16383
+ e.preventDefault();
16384
+ const actionCustomEvent = new CustomEvent("action", {
16385
+ detail: {
16386
+ action: null,
16387
+ event: e,
16388
+ method: "rerun",
16389
+ requester: form,
16390
+ meta: {
16391
+ isSubmit: true,
16392
+ },
16393
+ },
16394
+ });
16395
+ form.dispatchEvent(actionCustomEvent);
16396
+ });
16397
+ addTeardown(() => {
16398
+ removeListener();
16399
+ });
16400
+ }
16401
+
16402
+ {
16403
+ const onkeydown = (e) => {
16404
+ if (e.key === "Escape") {
16405
+ if (!closeElementValidationMessage("escape_key")) {
16406
+ dispatchCancelCustomEvent({ detail: { reason: "escape_key" } });
16407
+ }
16408
+ }
16409
+ };
16410
+ element.addEventListener("keydown", onkeydown);
16411
+ addTeardown(() => {
16412
+ element.removeEventListener("keydown", onkeydown);
16413
+ });
16414
+ }
16415
+
16416
+ {
16417
+ const onblur = () => {
16418
+ if (element.value === "") {
16419
+ dispatchCancelCustomEvent({
16420
+ detail: {
16421
+ reason: "blur_empty",
16422
+ },
16423
+ });
16424
+ return;
16425
+ }
16426
+ // if we have failed constraint, we cancel too
16427
+ if (failedConstraintInfo) {
16428
+ dispatchCancelCustomEvent({
16429
+ detail: {
16430
+ reason: "blur_invalid",
16431
+ failedConstraintInfo,
16432
+ },
16433
+ });
16434
+ return;
16435
+ }
16436
+ };
16437
+ element.addEventListener("blur", onblur);
16438
+ addTeardown(() => {
16439
+ element.removeEventListener("blur", onblur);
16440
+ });
16441
+ }
16442
+
16443
+ return validationInterface;
16444
+ };
16445
+
16446
+ // When interacting with an element we want to find the closest element
16447
+ // eventually handling the action
16448
+ // 1. <button> itself has an action
16449
+ // 2. <button> is inside a <form> with an action
16450
+ // 3. <button> is inside a wrapper <div> with an action (data-action is not necessarly on the interactive element itself, it can be on a wrapper, we want to support that)
16451
+ // 4. <button> is inside a <fieldset> or any element that catches the action like a <form> would
16452
+ // In examples above <button> can also be <input> etc..
16453
+ const closestElementWithAction = (el) => {
16454
+ if (el.hasAttribute("data-action")) {
16455
+ return el;
16456
+ }
16457
+ const closestDataActionElement = el.closest("[data-action]");
16458
+ if (!closestDataActionElement) {
16459
+ return null;
16460
+ }
16461
+ const visualSelector = closestDataActionElement.getAttribute(
16462
+ "data-visual-selector",
16463
+ );
16464
+ if (!visualSelector) {
16465
+ return closestDataActionElement;
16466
+ }
16467
+ const visualElement = closestDataActionElement.querySelector(visualSelector);
16468
+ return visualElement;
16469
+ };
16470
+
16471
+ const pickConstraint = (a, b) => {
16472
+ const aPrio = getConstraintPriority(a);
16473
+ const bPrio = getConstraintPriority(b);
16474
+ if (aPrio > bPrio) {
16475
+ return a;
16476
+ }
16477
+ return b;
16478
+ };
16479
+ const getConstraintPriority = (constraint) => {
16480
+ if (constraint.name === "required") {
16481
+ return 100;
16482
+ }
16483
+ if (STANDARD_CONSTRAINT_SET.has(constraint)) {
16484
+ return 10;
16485
+ }
16486
+ return 1;
16487
+ };
16488
+
16489
+ const getFirstButtonSubmittingForm = (form) => {
16490
+ return form.querySelector(
16491
+ `button[type="submit"], input[type="submit"], input[type="image"]`,
16492
+ );
16493
+ };
16494
+
16495
+ const dispatchActionRequestedCustomEvent = (
16496
+ elementWithAction,
16497
+ { actionOrigin = "action_prop", event, requester },
16498
+ ) => {
16499
+ const actionRequestedCustomEvent = new CustomEvent("actionrequested", {
16500
+ cancelable: true,
16501
+ detail: {
16502
+ actionOrigin,
16503
+ event,
16504
+ requester,
16505
+ },
16506
+ });
16507
+ elementWithAction.dispatchEvent(actionRequestedCustomEvent);
16508
+ };
16509
+ // https://developer.mozilla.org/en-US/docs/Web/HTML/Guides/Constraint_validation
16510
+ const requestSubmit = HTMLFormElement.prototype.requestSubmit;
16511
+ HTMLFormElement.prototype.requestSubmit = function (submitter) {
16512
+ const form = this;
16513
+ const isInstrumented = formInstrumentedWeakSet.has(form);
16514
+ if (!isInstrumented) {
16515
+ requestSubmit.call(form, submitter);
16516
+ return;
16517
+ }
16518
+ const programmaticEvent = new CustomEvent("programmatic_requestsubmit", {
16519
+ cancelable: true,
16520
+ detail: {
16521
+ submitter,
16522
+ },
16523
+ });
16524
+ dispatchActionRequestedCustomEvent(form, {
16525
+ event: programmaticEvent,
16526
+ requester: submitter,
16527
+ });
16528
+
16529
+ // When all fields are valid calling the native requestSubmit would let browser go through the
16530
+ // standard form validation steps leading to form submission.
16531
+ // We don't want that because we have our own action system to handle forms
16532
+ // If we did that the form submission would happen in parallel of our action system
16533
+ // and because we listen to "submit" event to dispatch "action" event
16534
+ // we would end up with two actions being executed.
16535
+ //
16536
+ // In case we have discrepencies in our implementation compared to the browser standard
16537
+ // this also prevent the native validation message to show up.
16538
+
16539
+ // requestSubmit.call(this, submitter);
16540
+ };
16541
+
16542
+ // const submit = HTMLFormElement.prototype.submit;
16543
+ // HTMLFormElement.prototype.submit = function (...args) {
16544
+ // const form = this;
16545
+ // if (form.hasAttribute("data-method")) {
16546
+ // console.warn("You must use form.requestSubmit() instead of form.submit()");
16547
+ // return form.requestSubmit();
16548
+ // }
16549
+ // return submit.apply(this, args);
16550
+ // };
16551
+
16552
+ const addEventListener = (element, event, callback) => {
16553
+ element.addEventListener(event, callback);
16554
+ return () => {
16555
+ element.removeEventListener(event, callback);
16556
+ };
16557
+ };
16558
+
16559
+ const useCustomValidationRef = (elementRef, targetSelector) => {
16560
+ const customValidationRef = useRef();
16561
+
16562
+ useLayoutEffect(() => {
16563
+ const element = elementRef.current;
16564
+ if (!element) {
16565
+ console.warn(
16566
+ "useCustomValidationRef: elementRef.current is null, make sure to pass a ref to an element",
16567
+ );
16568
+ /* can happen if the component does this for instance:
16569
+ const Component = () => {
16570
+ const ref = useRef(null)
16571
+
16572
+ if (something) {
16573
+ return <input ref={ref} />
16574
+ }
16575
+ return <span></span>
16871
16576
  }
16872
- dispatchActionRequestedCustomEvent(elementWithAction, {
16873
- event: clickEvent,
16874
- requester: formSubmitTarget || button,
16875
- });
16876
- };
16877
- element.addEventListener("click", onclick);
16878
- addTeardown(() => {
16879
- element.removeEventListener("click", onclick);
16880
- });
16881
- }
16882
16577
 
16883
- request_on_input_change: {
16884
- const isInput =
16885
- element.tagName === "INPUT" || element.tagName === "TEXTAREA";
16886
- if (!isInput) {
16887
- break request_on_input_change;
16578
+ usually it's better to split the component in two but hey
16579
+ */
16580
+ return null;
16888
16581
  }
16889
- const stop = listenInputChange(element, (e) => {
16890
- const elementWithAction = closestElementWithAction(element);
16891
- if (!elementWithAction) {
16892
- return;
16582
+ let target;
16583
+ if (targetSelector) {
16584
+ target = element.querySelector(targetSelector);
16585
+ if (!target) {
16586
+ console.warn(
16587
+ `useCustomValidationRef: targetSelector "${targetSelector}" did not match in element`,
16588
+ );
16589
+ return null;
16893
16590
  }
16894
- dispatchActionRequestedCustomEvent(elementWithAction, {
16895
- event: e,
16896
- requester: element,
16897
- });
16898
- });
16899
- addTeardown(() => {
16900
- stop();
16901
- });
16591
+ } else {
16592
+ target = element;
16593
+ }
16594
+ const unsubscribe = subscribe(element, target);
16595
+ const validationInterface = element.__validationInterface__;
16596
+ customValidationRef.current = validationInterface;
16597
+ return () => {
16598
+ unsubscribe();
16599
+ };
16600
+ }, [targetSelector]);
16601
+
16602
+ return customValidationRef;
16603
+ };
16604
+
16605
+ const subscribeCountWeakMap = new WeakMap();
16606
+ const subscribe = (element, target) => {
16607
+ if (element.__validationInterface__) {
16608
+ let subscribeCount = subscribeCountWeakMap.get(element);
16609
+ subscribeCountWeakMap.set(element, subscribeCount + 1);
16610
+ } else {
16611
+ installCustomConstraintValidation(element, target);
16612
+ subscribeCountWeakMap.set(element, 1);
16902
16613
  }
16614
+ return () => {
16615
+ unsubscribe(element);
16616
+ };
16617
+ };
16903
16618
 
16904
- request_on_checkbox_change: {
16905
- const isCheckbox =
16906
- element.tagName === "INPUT" && element.type === "checkbox";
16907
- if (!isCheckbox) {
16908
- break request_on_checkbox_change;
16619
+ const unsubscribe = (element) => {
16620
+ const subscribeCount = subscribeCountWeakMap.get(element);
16621
+ if (subscribeCount === 1) {
16622
+ element.__validationInterface__.uninstall();
16623
+ subscribeCountWeakMap.delete(element);
16624
+ } else {
16625
+ subscribeCountWeakMap.set(element, subscribeCount - 1);
16626
+ }
16627
+ };
16628
+
16629
+ const NO_CONSTRAINTS = [];
16630
+ const useConstraints = (elementRef, props, { targetSelector } = {}) => {
16631
+ const {
16632
+ constraints = NO_CONSTRAINTS,
16633
+ disabledMessage,
16634
+ requiredMessage,
16635
+ patternMessage,
16636
+ minLengthMessage,
16637
+ maxLengthMessage,
16638
+ typeMessage,
16639
+ minMessage,
16640
+ maxMessage,
16641
+ singleSpaceMessage,
16642
+ sameAsMessage,
16643
+ minDigitMessage,
16644
+ minLowerLetterMessage,
16645
+ minUpperLetterMessage,
16646
+ minSpecialCharMessage,
16647
+ availableMessage,
16648
+ ...remainingProps
16649
+ } = props;
16650
+
16651
+ const customValidationRef = useCustomValidationRef(
16652
+ elementRef,
16653
+ targetSelector,
16654
+ );
16655
+ useLayoutEffect(() => {
16656
+ const customValidation = customValidationRef.current;
16657
+ const cleanupCallbackSet = new Set();
16658
+ for (const constraint of constraints) {
16659
+ const unregister = customValidation.registerConstraint(constraint);
16660
+ cleanupCallbackSet.add(unregister);
16909
16661
  }
16910
- const onchange = (e) => {
16911
- if (element.parentNode.hasAttribute("data-action")) {
16912
- dispatchActionRequestedCustomEvent(element, {
16913
- event: e,
16914
- requester: element,
16915
- });
16916
- return;
16662
+ return () => {
16663
+ for (const cleanupCallback of cleanupCallbackSet) {
16664
+ cleanupCallback();
16917
16665
  }
16918
16666
  };
16919
- element.addEventListener("change", onchange);
16920
- addTeardown(() => {
16921
- element.removeEventListener("change", onchange);
16922
- });
16923
- }
16667
+ }, constraints);
16924
16668
 
16925
- execute_on_form_submit: {
16926
- if (!isForm) {
16927
- break execute_on_form_submit;
16669
+ useLayoutEffect(() => {
16670
+ const el = elementRef.current;
16671
+ if (!el) {
16672
+ return null;
16928
16673
  }
16929
- // We will dispatch "action" when "submit" occurs (code called from.submit() to bypass validation)
16930
- const form = element;
16931
- const removeListener = addEventListener(form, "submit", (e) => {
16932
- e.preventDefault();
16933
- const actionCustomEvent = new CustomEvent("action", {
16934
- detail: {
16935
- action: null,
16936
- event: e,
16937
- method: "rerun",
16938
- requester: form,
16939
- meta: {
16940
- isSubmit: true,
16941
- },
16942
- },
16674
+ const cleanupCallbackSet = new Set();
16675
+ const setupCustomEvent = (el, constraintName, Component) => {
16676
+ const attrName = `data-${constraintName}-message-event`;
16677
+ const customEventName = `${constraintName}_message_jsx`;
16678
+ el.setAttribute(attrName, customEventName);
16679
+ const onCustomEvent = (e) => {
16680
+ e.detail.render(Component);
16681
+ };
16682
+ el.addEventListener(customEventName, onCustomEvent);
16683
+ cleanupCallbackSet.add(() => {
16684
+ el.removeEventListener(customEventName, onCustomEvent);
16685
+ el.removeAttribute(attrName);
16943
16686
  });
16944
- form.dispatchEvent(actionCustomEvent);
16945
- });
16946
- addTeardown(() => {
16947
- removeListener();
16948
- });
16949
- }
16950
-
16951
- {
16952
- const onkeydown = (e) => {
16953
- if (e.key === "Escape") {
16954
- if (!closeElementValidationMessage("escape_key")) {
16955
- dispatchCancelCustomEvent({ detail: { reason: "escape_key" } });
16956
- }
16957
- }
16958
16687
  };
16959
- element.addEventListener("keydown", onkeydown);
16960
- addTeardown(() => {
16961
- element.removeEventListener("keydown", onkeydown);
16962
- });
16963
- }
16964
16688
 
16965
- {
16966
- const onblur = () => {
16967
- if (element.value === "") {
16968
- dispatchCancelCustomEvent({
16969
- detail: {
16970
- reason: "blur_empty",
16971
- },
16972
- });
16973
- return;
16974
- }
16975
- // if we have failed constraint, we cancel too
16976
- if (failedConstraintInfo) {
16977
- dispatchCancelCustomEvent({
16978
- detail: {
16979
- reason: "blur_invalid",
16980
- failedConstraintInfo,
16981
- },
16982
- });
16983
- return;
16689
+ if (disabledMessage) {
16690
+ setupCustomEvent(el, "disabled", disabledMessage);
16691
+ }
16692
+ if (requiredMessage) {
16693
+ setupCustomEvent(el, "required", requiredMessage);
16694
+ }
16695
+ if (patternMessage) {
16696
+ setupCustomEvent(el, "pattern", patternMessage);
16697
+ }
16698
+ if (minLengthMessage) {
16699
+ setupCustomEvent(el, "min-length", minLengthMessage);
16700
+ }
16701
+ if (maxLengthMessage) {
16702
+ setupCustomEvent(el, "max-length", maxLengthMessage);
16703
+ }
16704
+ if (typeMessage) {
16705
+ setupCustomEvent(el, "type", typeMessage);
16706
+ }
16707
+ if (minMessage) {
16708
+ setupCustomEvent(el, "min", minMessage);
16709
+ }
16710
+ if (maxMessage) {
16711
+ setupCustomEvent(el, "max", maxMessage);
16712
+ }
16713
+ if (singleSpaceMessage) {
16714
+ setupCustomEvent(el, "single-space", singleSpaceMessage);
16715
+ }
16716
+ if (sameAsMessage) {
16717
+ setupCustomEvent(el, "same-as", sameAsMessage);
16718
+ }
16719
+ if (minDigitMessage) {
16720
+ setupCustomEvent(el, "min-digit", minDigitMessage);
16721
+ }
16722
+ if (minLowerLetterMessage) {
16723
+ setupCustomEvent(el, "min-lower-letter", minLowerLetterMessage);
16724
+ }
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();
16984
16737
  }
16985
16738
  };
16986
- element.addEventListener("blur", onblur);
16987
- addTeardown(() => {
16988
- element.removeEventListener("blur", onblur);
16989
- });
16990
- }
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
+ ]);
16991
16756
 
16992
- return validationInterface;
16757
+ return remainingProps;
16993
16758
  };
16994
16759
 
16995
- // When interacting with an element we want to find the closest element
16996
- // eventually handling the action
16997
- // 1. <button> itself has an action
16998
- // 2. <button> is inside a <form> with an action
16999
- // 3. <button> is inside a wrapper <div> with an action (data-action is not necessarly on the interactive element itself, it can be on a wrapper, we want to support that)
17000
- // 4. <button> is inside a <fieldset> or any element that catches the action like a <form> would
17001
- // In examples above <button> can also be <input> etc..
17002
- const closestElementWithAction = (el) => {
17003
- if (el.hasAttribute("data-action")) {
17004
- return el;
17005
- }
17006
- const closestDataActionElement = el.closest("[data-action]");
17007
- if (!closestDataActionElement) {
17008
- return null;
17009
- }
17010
- const visualSelector = closestDataActionElement.getAttribute(
17011
- "data-visual-selector",
17012
- );
17013
- if (!visualSelector) {
17014
- return closestDataActionElement;
16760
+ const useInitialTextSelection = (ref, textSelection) => {
16761
+ const deps = [];
16762
+ if (Array.isArray(textSelection)) {
16763
+ deps.push(...textSelection);
16764
+ } else {
16765
+ deps.push(textSelection);
17015
16766
  }
17016
- const visualElement = closestDataActionElement.querySelector(visualSelector);
17017
- return visualElement;
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);
17018
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;
17019
16803
 
17020
- const pickConstraint = (a, b) => {
17021
- const aPrio = getConstraintPriority(a);
17022
- const bPrio = getConstraintPriority(b);
17023
- if (aPrio > bPrio) {
17024
- return a;
17025
- }
17026
- return b;
17027
- };
17028
- const getConstraintPriority = (constraint) => {
17029
- if (constraint.name === "required") {
17030
- return 100;
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;
17031
16817
  }
17032
- if (STANDARD_CONSTRAINT_SET.has(constraint)) {
17033
- return 10;
16818
+ if (startNode && endNode) {
16819
+ range.setStart(startNode, startOffset);
16820
+ range.setEnd(endNode, endOffset);
17034
16821
  }
17035
- return 1;
17036
- };
17037
-
17038
- const getFirstButtonSubmittingForm = (form) => {
17039
- return form.querySelector(
17040
- `button[type="submit"], input[type="submit"], input[type="image"]`,
17041
- );
17042
16822
  };
17043
-
17044
- const dispatchActionRequestedCustomEvent = (
17045
- elementWithAction,
17046
- { actionOrigin = "action_prop", event, requester },
17047
- ) => {
17048
- const actionRequestedCustomEvent = new CustomEvent("actionrequested", {
17049
- cancelable: true,
17050
- detail: {
17051
- actionOrigin,
17052
- event,
17053
- requester,
17054
- },
17055
- });
17056
- elementWithAction.dispatchEvent(actionRequestedCustomEvent);
17057
- };
17058
- // https://developer.mozilla.org/en-US/docs/Web/HTML/Guides/Constraint_validation
17059
- const requestSubmit = HTMLFormElement.prototype.requestSubmit;
17060
- HTMLFormElement.prototype.requestSubmit = function (submitter) {
17061
- const form = this;
17062
- const isInstrumented = formInstrumentedWeakSet.has(form);
17063
- if (!isInstrumented) {
17064
- requestSubmit.call(form, submitter);
17065
- return;
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
+ }
17066
16833
  }
17067
- const programmaticEvent = new CustomEvent("programmatic_requestsubmit", {
17068
- cancelable: true,
17069
- detail: {
17070
- submitter,
17071
- },
17072
- });
17073
- dispatchActionRequestedCustomEvent(form, {
17074
- event: programmaticEvent,
17075
- requester: submitter,
17076
- });
17077
-
17078
- // When all fields are valid calling the native requestSubmit would let browser go through the
17079
- // standard form validation steps leading to form submission.
17080
- // We don't want that because we have our own action system to handle forms
17081
- // If we did that the form submission would happen in parallel of our action system
17082
- // and because we listen to "submit" event to dispatch "action" event
17083
- // we would end up with two actions being executed.
17084
- //
17085
- // In case we have discrepencies in our implementation compared to the browser standard
17086
- // this also prevent the native validation message to show up.
17087
-
17088
- // requestSubmit.call(this, submitter);
17089
16834
  };
17090
-
17091
- // const submit = HTMLFormElement.prototype.submit;
17092
- // HTMLFormElement.prototype.submit = function (...args) {
17093
- // const form = this;
17094
- // if (form.hasAttribute("data-method")) {
17095
- // console.warn("You must use form.requestSubmit() instead of form.submit()");
17096
- // return form.requestSubmit();
17097
- // }
17098
- // return submit.apply(this, args);
17099
- // };
17100
-
17101
- const addEventListener = (element, event, callback) => {
17102
- element.addEventListener(event, callback);
17103
- return () => {
17104
- element.removeEventListener(event, callback);
17105
- };
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
+ }
17106
16857
  };
17107
16858
 
17108
- const useCustomValidationRef = (elementRef, targetSelector) => {
17109
- const customValidationRef = useRef();
17110
-
17111
- useLayoutEffect(() => {
17112
- const element = elementRef.current;
17113
- if (!element) {
17114
- console.warn(
17115
- "useCustomValidationRef: elementRef.current is null, make sure to pass a ref to an element",
17116
- );
17117
- /* can happen if the component does this for instance:
17118
- const Component = () => {
17119
- const ref = useRef(null)
17120
-
17121
- if (something) {
17122
- return <input ref={ref} />
17123
- }
17124
- return <span></span>
17125
- }
17126
-
17127
- usually it's better to split the component in two but hey
17128
- */
17129
- return null;
17130
- }
17131
- let target;
17132
- if (targetSelector) {
17133
- target = element.querySelector(targetSelector);
17134
- if (!target) {
17135
- console.warn(
17136
- `useCustomValidationRef: targetSelector "${targetSelector}" did not match in element`,
17137
- );
17138
- return null;
17139
- }
17140
- } else {
17141
- target = element;
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;
17142
16870
  }
17143
- const unsubscribe = subscribe(element, target);
17144
- const validationInterface = element.__validationInterface__;
17145
- customValidationRef.current = validationInterface;
17146
- return () => {
17147
- unsubscribe();
17148
- };
17149
- }, [targetSelector]);
16871
+ }
17150
16872
 
17151
- return customValidationRef;
17152
- };
16873
+ .navi_text_overflow {
16874
+ flex-wrap: wrap;
16875
+ text-overflow: ellipsis;
16876
+ overflow: hidden;
16877
+ }
17153
16878
 
17154
- const subscribeCountWeakMap = new WeakMap();
17155
- const subscribe = (element, target) => {
17156
- if (element.__validationInterface__) {
17157
- let subscribeCount = subscribeCountWeakMap.get(element);
17158
- subscribeCountWeakMap.set(element, subscribeCount + 1);
17159
- } else {
17160
- installCustomConstraintValidation(element, target);
17161
- subscribeCountWeakMap.set(element, 1);
16879
+ .navi_text_overflow_wrapper {
16880
+ display: flex;
16881
+ width: 0;
16882
+ flex-grow: 1;
16883
+ gap: 0.3em;
17162
16884
  }
17163
- return () => {
17164
- unsubscribe(element);
17165
- };
17166
- };
17167
16885
 
17168
- const unsubscribe = (element) => {
17169
- const subscribeCount = subscribeCountWeakMap.get(element);
17170
- if (subscribeCount === 1) {
17171
- element.__validationInterface__.uninstall();
17172
- subscribeCountWeakMap.delete(element);
17173
- } else {
17174
- subscribeCountWeakMap.set(element, subscribeCount - 1);
16886
+ .navi_text_overflow_text {
16887
+ max-width: 100%;
16888
+ text-overflow: ellipsis;
16889
+ overflow: hidden;
17175
16890
  }
17176
- };
17177
16891
 
17178
- const NO_CONSTRAINTS = [];
17179
- const useConstraints = (elementRef, props, { targetSelector } = {}) => {
17180
- const {
17181
- constraints = NO_CONSTRAINTS,
17182
- disabledMessage,
17183
- requiredMessage,
17184
- patternMessage,
17185
- minLengthMessage,
17186
- maxLengthMessage,
17187
- typeMessage,
17188
- minMessage,
17189
- maxMessage,
17190
- singleSpaceMessage,
17191
- sameAsMessage,
17192
- minDigitMessage,
17193
- minLowerLetterMessage,
17194
- minUpperLetterMessage,
17195
- minSpecialCharMessage,
17196
- availableMessage,
17197
- ...remainingProps
17198
- } = props;
16892
+ .navi_custom_space {
16893
+ }
17199
16894
 
17200
- const customValidationRef = useCustomValidationRef(
17201
- elementRef,
17202
- targetSelector,
17203
- );
17204
- useLayoutEffect(() => {
17205
- const customValidation = customValidationRef.current;
17206
- const cleanupCallbackSet = new Set();
17207
- for (const constraint of constraints) {
17208
- const unregister = customValidation.registerConstraint(constraint);
17209
- cleanupCallbackSet.add(unregister);
17210
- }
17211
- return () => {
17212
- for (const cleanupCallback of cleanupCallbackSet) {
17213
- cleanupCallback();
17214
- }
17215
- };
17216
- }, constraints);
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
+ }
17217
16907
 
17218
- useLayoutEffect(() => {
17219
- const el = elementRef.current;
17220
- if (!el) {
17221
- return null;
17222
- }
17223
- const cleanupCallbackSet = new Set();
17224
- const setupCustomEvent = (el, constraintName, Component) => {
17225
- const attrName = `data-${constraintName}-message-event`;
17226
- const customEventName = `${constraintName}_message_jsx`;
17227
- el.setAttribute(attrName, customEventName);
17228
- const onCustomEvent = (e) => {
17229
- e.detail.render(Component);
17230
- };
17231
- el.addEventListener(customEventName, onCustomEvent);
17232
- cleanupCallbackSet.add(() => {
17233
- el.removeEventListener(customEventName, onCustomEvent);
17234
- el.removeAttribute(attrName);
17235
- });
17236
- };
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
+ }
17237
16921
 
17238
- if (disabledMessage) {
17239
- setupCustomEvent(el, "disabled", disabledMessage);
17240
- }
17241
- if (requiredMessage) {
17242
- setupCustomEvent(el, "required", requiredMessage);
17243
- }
17244
- if (patternMessage) {
17245
- setupCustomEvent(el, "pattern", patternMessage);
17246
- }
17247
- if (minLengthMessage) {
17248
- setupCustomEvent(el, "min-length", minLengthMessage);
17249
- }
17250
- if (maxLengthMessage) {
17251
- setupCustomEvent(el, "max-length", maxLengthMessage);
17252
- }
17253
- if (typeMessage) {
17254
- setupCustomEvent(el, "type", typeMessage);
17255
- }
17256
- if (minMessage) {
17257
- setupCustomEvent(el, "min", minMessage);
17258
- }
17259
- if (maxMessage) {
17260
- setupCustomEvent(el, "max", maxMessage);
17261
- }
17262
- if (singleSpaceMessage) {
17263
- setupCustomEvent(el, "single-space", singleSpaceMessage);
16922
+ .navi_text[data-bold] {
16923
+ .navi_text_bold_background {
16924
+ opacity: 1;
17264
16925
  }
17265
- if (sameAsMessage) {
17266
- setupCustomEvent(el, "same-as", sameAsMessage);
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;
17267
16933
  }
17268
- if (minDigitMessage) {
17269
- setupCustomEvent(el, "min-digit", minDigitMessage);
16934
+
16935
+ .navi_text_bold_background {
16936
+ transition-property: opacity;
16937
+ transition-duration: 0.3s;
16938
+ transition-timing-function: ease;
17270
16939
  }
17271
- if (minLowerLetterMessage) {
17272
- setupCustomEvent(el, "min-lower-letter", minLowerLetterMessage);
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;
17273
16981
  }
17274
- if (minUpperLetterMessage) {
17275
- setupCustomEvent(el, "min-upper-letter", minUpperLetterMessage);
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;
17341
17015
  };
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
-
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
- }
17358
-
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;
17364
- }
17365
- currentIndex += nodeLength;
17016
+ const startsWithWhitespace = jsxChild => {
17017
+ if (typeof jsxChild === "string") {
17018
+ return /^\s/.test(jsxChild);
17366
17019
  }
17367
- if (startNode && endNode) {
17368
- range.setStart(startNode, startOffset);
17369
- range.setEnd(endNode, endOffset);
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
+ });
17370
17042
  }
17043
+ return jsx(TextBasic, {
17044
+ ...props
17045
+ });
17371
17046
  };
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
- }
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;
17382
17092
  }
17093
+ setOverflowPinnedElement(null);
17094
+ return text;
17383
17095
  };
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
- }
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);
17399
17127
  }
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);
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
+ });
17405
17167
  }
17168
+ return jsx(Box, {
17169
+ ...boxProps,
17170
+ children: children
17171
+ });
17406
17172
  };
17407
17173
 
17408
17174
  installImportMetaCss(import.meta);import.meta.css = /* css */`
17409
- *[data-navi-space] {
17410
- /* user-select: none; */
17411
- }
17412
-
17413
- .navi_text {
17414
- position: relative;
17415
- color: inherit;
17175
+ .navi_icon {
17176
+ display: inline-block;
17177
+ box-sizing: border-box;
17178
+ max-width: 100%;
17179
+ max-height: 100%;
17416
17180
 
17417
- &[data-has-absolute-child] {
17418
- display: inline-block;
17181
+ &[data-flow-inline] {
17182
+ width: 1em;
17183
+ height: 1em;
17184
+ }
17185
+ &[data-icon-char] {
17186
+ flex-grow: 0 !important;
17187
+ line-height: normal;
17419
17188
  }
17420
17189
  }
17421
17190
 
17422
- .navi_text_overflow {
17423
- flex-wrap: wrap;
17424
- text-overflow: ellipsis;
17425
- overflow: hidden;
17191
+ .navi_icon[data-interactive] {
17192
+ cursor: pointer;
17426
17193
  }
17427
17194
 
17428
- .navi_text_overflow_wrapper {
17429
- display: flex;
17430
- width: 0;
17431
- flex-grow: 1;
17432
- gap: 0.3em;
17195
+ .navi_icon_char_slot {
17196
+ opacity: 0;
17197
+ cursor: default;
17198
+ user-select: none;
17433
17199
  }
17434
-
17435
- .navi_text_overflow_text {
17436
- max-width: 100%;
17437
- text-overflow: ellipsis;
17438
- overflow: hidden;
17200
+ .navi_icon_foreground {
17201
+ position: absolute;
17202
+ inset: 0;
17439
17203
  }
17440
-
17441
- .navi_custom_space {
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;
17442
17212
  }
17443
17213
 
17444
- .navi_text_bold_wrapper {
17445
- position: relative;
17446
- display: inline-block;
17214
+ .navi_icon > svg,
17215
+ .navi_icon > img {
17216
+ width: 100%;
17217
+ height: 100%;
17218
+ backface-visibility: hidden;
17447
17219
  }
17448
- .navi_text_bold_clone {
17449
- font-weight: bold;
17450
- opacity: 0;
17220
+ .navi_icon[data-has-width] > svg,
17221
+ .navi_icon[data-has-width] > img {
17222
+ width: 100%;
17223
+ height: auto;
17451
17224
  }
17452
- .navi_text_bold_foreground {
17453
- position: absolute;
17454
- inset: 0;
17225
+ .navi_icon[data-has-height] > svg,
17226
+ .navi_icon[data-has-height] > img {
17227
+ width: auto;
17228
+ height: 100%;
17455
17229
  }
17456
-
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;
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%;
17469
17234
  }
17470
17235
 
17471
- .navi_text[data-bold] {
17472
- .navi_text_bold_background {
17473
- opacity: 1;
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;
17474
17276
  }
17277
+ } else {
17278
+ box = true;
17279
+ }
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
+ });
17475
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
+ };
17476
17327
 
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
- }
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
+ };
17483
17377
 
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"
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
+ })
17502
17386
  });
17503
17387
  };
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
- });
17388
+
17389
+ const useDebounceTrue = (value, delay = 300) => {
17390
+ const [debouncedTrue, setDebouncedTrue] = useState(false);
17391
+ const timerRef = useRef(null);
17392
+
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);
17528
17399
  } else {
17529
- separator = spacing;
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);
17530
17406
  }
17531
- } else if (typeof spacing === "number") {
17532
- separator = jsx(CustomWidthSpace, {
17533
- value: spacing
17534
- });
17535
- } else {
17536
- separator = spacing;
17407
+
17408
+ // Cleanup function
17409
+ return () => {
17410
+ if (timerRef.current) {
17411
+ clearTimeout(timerRef.current);
17412
+ }
17413
+ };
17414
+ }, [value, delay]);
17415
+
17416
+ return debouncedTrue;
17417
+ };
17418
+
17419
+ const useNetworkSpeed = () => {
17420
+ return networkSpeedSignal.value;
17421
+ };
17422
+
17423
+ const connection =
17424
+ window.navigator.connection ||
17425
+ window.navigator.mozConnection ||
17426
+ window.navigator.webkitConnection;
17427
+
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
17446
  }
17557
- return childrenWithGap;
17558
- };
17559
- const endsWithWhitespace = jsxChild => {
17560
- if (typeof jsxChild === "string") {
17561
- return /\s$/.test(jsxChild);
17562
- }
17563
- return false;
17447
+ return "3g";
17564
17448
  };
17565
- const startsWithWhitespace = jsxChild => {
17566
- if (typeof jsxChild === "string") {
17567
- return /^\s/.test(jsxChild);
17568
- }
17569
- return false;
17449
+
17450
+ const updateNetworkSpeed = () => {
17451
+ networkSpeedSignal.value = getNetworkSpeed();
17570
17452
  };
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
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);
17590
17465
  });
17591
17466
  }
17592
- return jsx(TextBasic, {
17593
- ...props
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);
17594
17482
  });
17595
- };
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
- })
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);
17624
17492
  });
17493
+
17494
+ // Cleanup global
17495
+ return () => {
17496
+ cleanupFunctions.forEach((cleanup) => cleanup());
17497
+ };
17625
17498
  };
17626
- const TextOverflowPinned = ({
17627
- overflowPinned,
17628
- ...props
17629
- }) => {
17630
- const setOverflowPinnedElement = useContext(OverflowPinnedElementContext);
17631
- const text = jsx(Text, {
17632
- ...props
17633
- });
17634
- if (!setOverflowPinnedElement) {
17635
- console.warn("<Text overflowPinned> declared outside a <Text overflowEllipsis>");
17636
- return text;
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
- });
17739
+ }), container);
17839
17740
  }
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
- });
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
17751
+ });
17752
+ };
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]
17786
+ });
17787
+ };
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;
17853
17909
  }
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,
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, {
17865
17919
  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
- })]
17874
- });
17875
- };
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
- })
17889
- });
17890
- };
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
- })
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) => {
@@ -27425,63 +27495,73 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
27425
27495
  @layer navi {
27426
27496
  }
27427
27497
  .navi_badge_count {
27428
- --x-size: 1.5em;
27429
- --x-border-radius: var(--border-radius);
27430
- --x-number-font-size: var(--font-size);
27498
+ --x-background: var(--background);
27499
+ --x-background-color: var(--background-color);
27431
27500
  position: relative;
27432
27501
  display: inline-block;
27433
-
27434
27502
  color: var(--color, var(--x-color-contrasting));
27435
27503
  font-size: var(--font-size);
27436
27504
  vertical-align: middle;
27437
- border-radius: var(--x-border-radius);
27438
- }
27439
- .navi_count_badge_overflow {
27440
- position: relative;
27441
- top: -0.1em;
27442
- }
27443
- /* Ellipse */
27444
- .navi_badge_count[data-ellipse] {
27445
- padding-right: 0.4em;
27446
- padding-left: 0.4em;
27447
- background: var(--background);
27448
- background-color: var(--background-color, var(--background));
27449
- border-radius: 1em;
27450
- }
27451
- /* Circle */
27452
- .navi_badge_count[data-circle] {
27453
- width: var(--x-size);
27454
- height: var(--x-size);
27455
- }
27456
- .navi_badge_count_frame {
27457
- position: absolute;
27458
- top: 50%;
27459
- left: 0;
27460
- width: 100%;
27461
- height: 100%;
27462
- background: var(--background);
27463
- background-color: var(--background-color, var(--background));
27464
- border-radius: inherit;
27465
- transform: translateY(-50%);
27466
- }
27467
- .navi_badge_count_text {
27468
- position: absolute;
27469
- top: 50%;
27470
- left: 50%;
27471
- font-size: var(--x-number-font-size, inherit);
27472
- transform: translate(-50%, -50%);
27473
- }
27474
- .navi_badge_count[data-single-char] {
27475
- --x-border-radius: 100%;
27476
- --x-number-font-size: unset;
27477
- }
27478
- .navi_badge_count[data-two-chars] {
27479
- --x-border-radius: 100%;
27480
- --x-number-font-size: 0.8em;
27481
- }
27482
- .navi_badge_count[data-three-chars] {
27483
- --x-border-radius: 100%;
27484
- --x-number-font-size: 0.6em;
27505
+
27506
+ .navi_count_badge_overflow {
27507
+ position: relative;
27508
+ top: -0.1em;
27509
+ }
27510
+
27511
+ /* Ellipse */
27512
+ &[data-ellipse] {
27513
+ padding-right: 0.4em;
27514
+ padding-left: 0.4em;
27515
+ background: var(--x-background);
27516
+ background-color: var(--x-background-color, var(--x-background));
27517
+ border-radius: 1em;
27518
+ &[data-loading] {
27519
+ --x-background: transparent;
27520
+ }
27521
+ }
27522
+
27523
+ /* Circle */
27524
+ &[data-circle] {
27525
+ --x-size: 1.5em;
27526
+ --x-border-radius: var(--border-radius);
27527
+ --x-number-font-size: var(--font-size);
27528
+
27529
+ width: var(--x-size);
27530
+ height: var(--x-size);
27531
+ border-radius: var(--x-border-radius);
27532
+ &[data-single-char] {
27533
+ --x-border-radius: 100%;
27534
+ --x-number-font-size: unset;
27535
+ }
27536
+ &[data-two-chars] {
27537
+ --x-border-radius: 100%;
27538
+ --x-number-font-size: 0.8em;
27539
+ }
27540
+ &[data-three-chars] {
27541
+ --x-border-radius: 100%;
27542
+ --x-number-font-size: 0.6em;
27543
+ }
27544
+
27545
+ .navi_badge_count_frame {
27546
+ position: absolute;
27547
+ top: 50%;
27548
+ left: 0;
27549
+ width: 100%;
27550
+ height: 100%;
27551
+ background: var(--x-background);
27552
+ background-color: var(--x-background-color, var(--x-background));
27553
+ border-radius: inherit;
27554
+ transform: translateY(-50%);
27555
+ }
27556
+
27557
+ .navi_badge_count_text {
27558
+ position: absolute;
27559
+ top: 50%;
27560
+ left: 50%;
27561
+ font-size: var(--x-number-font-size, inherit);
27562
+ transform: translate(-50%, -50%);
27563
+ }
27564
+ }
27485
27565
  }
27486
27566
  `;
27487
27567
  const BadgeStyleCSSVars = {
@@ -27502,11 +27582,11 @@ const BadgeCountOverflow = () => jsx("span", {
27502
27582
  const MAX_CHAR_AS_CIRCLE = 3;
27503
27583
  const BadgeCount = ({
27504
27584
  children,
27505
- max = 99,
27506
27585
  maxElement = jsx(BadgeCountOverflow, {}),
27507
27586
  // When you use max="none" (or max > 99) it might be a good idea to force ellipse
27508
27587
  // so that visually the interface do not suddently switch from circle to ellipse depending on the count
27509
27588
  ellipse,
27589
+ max = ellipse ? Infinity : 99,
27510
27590
  ...props
27511
27591
  }) => {
27512
27592
  const defaultRef = useRef();
@@ -27556,10 +27636,11 @@ const BadgeCountCircle = ({
27556
27636
  ref,
27557
27637
  charCount,
27558
27638
  hasOverflow,
27639
+ loading,
27559
27640
  children,
27560
27641
  ...props
27561
27642
  }) => {
27562
- return jsxs(Text, {
27643
+ return jsx(Text, {
27563
27644
  ref: ref,
27564
27645
  className: "navi_badge_count",
27565
27646
  "data-circle": "",
@@ -27571,42 +27652,50 @@ const BadgeCountCircle = ({
27571
27652
  ...props,
27572
27653
  styleCSSVars: BadgeStyleCSSVars,
27573
27654
  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
- })]
27655
+ children: loading ? jsx(LoadingDots, {}) : jsxs(Fragment, {
27656
+ children: [jsx("span", {
27657
+ style: "user-select: none",
27658
+ children: "\u200B"
27659
+ }), jsx("span", {
27660
+ className: "navi_badge_count_frame"
27661
+ }), jsx("span", {
27662
+ className: "navi_badge_count_text",
27663
+ children: children
27664
+ }), jsx("span", {
27665
+ style: "user-select: none",
27666
+ children: "\u200B"
27667
+ })]
27668
+ })
27586
27669
  });
27587
27670
  };
27588
27671
  const BadgeCountEllipse = ({
27589
27672
  ref,
27673
+ loading,
27590
27674
  children,
27591
27675
  hasOverflow,
27592
27676
  ...props
27593
27677
  }) => {
27594
- return jsxs(Text, {
27678
+ return jsx(Text, {
27595
27679
  ref: ref,
27596
27680
  className: "navi_badge_count",
27597
27681
  bold: true,
27598
27682
  "data-ellipse": "",
27599
27683
  "data-value-overflow": hasOverflow ? "" : undefined,
27684
+ "data-loading": loading ? "" : undefined,
27600
27685
  ...props,
27601
27686
  styleCSSVars: BadgeStyleCSSVars,
27602
27687
  spacing: "pre",
27603
- children: [jsx("span", {
27604
- style: "user-select: none",
27605
- children: "\u200B"
27606
- }), children, jsx("span", {
27607
- style: "user-select: none",
27608
- children: "\u200B"
27609
- })]
27688
+ children: loading ? jsx(Icon, {
27689
+ children: jsx(LoadingDots, {})
27690
+ }) : jsxs(Fragment, {
27691
+ children: [jsx("span", {
27692
+ style: "user-select: none",
27693
+ children: "\u200B"
27694
+ }), children, jsx("span", {
27695
+ style: "user-select: none",
27696
+ children: "\u200B"
27697
+ })]
27698
+ })
27610
27699
  });
27611
27700
  };
27612
27701