@jsenv/navi 0.16.57 → 0.16.59

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import { installImportMetaCss } from "./jsenv_navi_side_effects.js";
2
2
  import { useErrorBoundary, useLayoutEffect, useEffect, useCallback, useRef, useState, useContext, useMemo, useImperativeHandle, useId } from "preact/hooks";
3
3
  import { jsxs, jsx, Fragment } from "preact/jsx-runtime";
4
- import { createIterableWeakSet, mergeOneStyle, stringifyStyle, createPubSub, mergeTwoStyles, normalizeStyles, createGroupTransitionController, getElementSignature, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, resolveCSSSize, findBefore, findAfter, createValueEffect, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, allowWheelThrough, resolveCSSColor, createStyleController, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, hasCSSSizeUnit, activeElementSignal, canInterceptKeys, pickLightOrDark, resolveColorLuminance, initFocusGroup, dragAfterThreshold, getScrollContainer, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement, elementIsFocusable } from "@jsenv/dom";
4
+ import { createIterableWeakSet, mergeOneStyle, stringifyStyle, createPubSub, mergeTwoStyles, normalizeStyles, createGroupTransitionController, getElementSignature, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, findBefore, findAfter, createValueEffect, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, allowWheelThrough, resolveCSSColor, createStyleController, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, hasCSSSizeUnit, resolveCSSSize, activeElementSignal, canInterceptKeys, pickLightOrDark, resolveColorLuminance, initFocusGroup, dragAfterThreshold, getScrollContainer, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement, elementIsFocusable } from "@jsenv/dom";
5
5
  import { prefixFirstAndIndentRemainingLines } from "@jsenv/humanize";
6
6
  import { effect, signal, computed, batch, useSignal } from "@preact/signals";
7
7
  import { createValidity } from "@jsenv/validity";
@@ -1448,6 +1448,7 @@ const createAction = (callback, rootOptions = {}) => {
1448
1448
  for (const key of keyArray) {
1449
1449
  const signalForThisKey = signalMap.get(key);
1450
1450
  if (signalForThisKey) {
1451
+ // eslint-disable-next-line signals/no-conditional-value-read
1451
1452
  params[key] = signalForThisKey.value;
1452
1453
  } else {
1453
1454
  params[key] = staticParams[key];
@@ -6306,7 +6307,7 @@ const Box = props => {
6306
6307
  // then we'll track ":hover" state changes even for basic elements like <div>
6307
6308
  const pseudoClassesFromStyleSet = new Set();
6308
6309
  boxPseudoNamedStyles = {};
6309
- const assignStyle = (value, name, styleContext, boxStylesTarget, styleOrigin) => {
6310
+ const visitProp = (value, name, styleContext, boxStylesTarget, styleOrigin) => {
6310
6311
  const isPseudoElement = name.startsWith("::");
6311
6312
  const isPseudoClass = name.startsWith(":");
6312
6313
  if (isPseudoElement || isPseudoClass) {
@@ -6324,14 +6325,14 @@ const Box = props => {
6324
6325
  if (isPseudoElement) {
6325
6326
  const pseudoElementStyles = {};
6326
6327
  for (const key of pseudoStyleKeys) {
6327
- assignStyle(value[key], key, pseudoStyleContext, pseudoElementStyles, "pseudo_style");
6328
+ visitProp(value[key], key, pseudoStyleContext, pseudoElementStyles, "pseudo_style");
6328
6329
  }
6329
6330
  boxPseudoNamedStyles[name] = pseudoElementStyles;
6330
6331
  return;
6331
6332
  }
6332
6333
  const pseudoClassStyles = {};
6333
6334
  for (const key of pseudoStyleKeys) {
6334
- assignStyle(value[key], key, pseudoStyleContext, pseudoClassStyles, "pseudo_style");
6335
+ visitProp(value[key], key, pseudoStyleContext, pseudoClassStyles, "pseudo_style");
6335
6336
  boxPseudoNamedStyles[name] = pseudoClassStyles;
6336
6337
  }
6337
6338
  return;
@@ -6395,23 +6396,28 @@ const Box = props => {
6395
6396
  if (baseStyle) {
6396
6397
  for (const key of baseStyle) {
6397
6398
  const value = baseStyle[key];
6398
- assignStyle(value, key, styleContext, boxStyles, "baseStyle");
6399
+ visitProp(value, key, styleContext, boxStyles, "baseStyle");
6399
6400
  }
6400
6401
  }
6401
6402
  for (const propName of remainingPropKeySet) {
6402
6403
  const propValue = rest[propName];
6403
- assignStyle(propValue, propName, styleContext, boxStyles, "prop");
6404
+ const isDataAttribute = propName.startsWith("data-");
6405
+ if (isDataAttribute) {
6406
+ selfForwardedProps[propName] = propValue;
6407
+ continue;
6408
+ }
6409
+ visitProp(propValue, propName, styleContext, boxStyles, "prop");
6404
6410
  }
6405
6411
  if (typeof style === "string") {
6406
6412
  const styleObject = normalizeStyles(style, "css");
6407
6413
  for (const styleName of Object.keys(styleObject)) {
6408
6414
  const styleValue = styleObject[styleName];
6409
- assignStyle(styleValue, styleName, styleContext, boxStyles, "style");
6415
+ visitProp(styleValue, styleName, styleContext, boxStyles, "style");
6410
6416
  }
6411
6417
  } else if (style && typeof style === "object") {
6412
6418
  for (const styleName of Object.keys(style)) {
6413
6419
  const styleValue = style[styleName];
6414
- assignStyle(styleValue, styleName, styleContext, boxStyles, "style");
6420
+ visitProp(styleValue, styleName, styleContext, boxStyles, "style");
6415
6421
  }
6416
6422
  }
6417
6423
  const updateStyle = useCallback(state => {
@@ -12097,691 +12103,141 @@ const renderActionableComponent = (props, {
12097
12103
  });
12098
12104
  };
12099
12105
 
12100
- const useDebounceTrue = (value, delay = 300) => {
12101
- const [debouncedTrue, setDebouncedTrue] = useState(false);
12102
- const timerRef = useRef(null);
12103
-
12104
- useLayoutEffect(() => {
12105
- // If value is true or becomes true, start a timer
12106
- if (value) {
12107
- timerRef.current = setTimeout(() => {
12108
- setDebouncedTrue(true);
12109
- }, delay);
12110
- } else {
12111
- // If value becomes false, clear any pending timer and immediately set to false
12112
- if (timerRef.current) {
12113
- clearTimeout(timerRef.current);
12114
- timerRef.current = null;
12115
- }
12116
- setDebouncedTrue(false);
12117
- }
12118
-
12119
- // Cleanup function
12120
- return () => {
12121
- if (timerRef.current) {
12122
- clearTimeout(timerRef.current);
12123
- }
12124
- };
12125
- }, [value, delay]);
12126
-
12127
- return debouncedTrue;
12128
- };
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
+ */
12129
12147
 
12130
- const useNetworkSpeed = () => {
12131
- return networkSpeedSignal.value;
12132
- };
12133
12148
 
12134
- const connection =
12135
- window.navigator.connection ||
12136
- window.navigator.mozConnection ||
12137
- window.navigator.webkitConnection;
12149
+ const useStableCallback = (callback, mapper) => {
12150
+ const callbackRef = useRef();
12151
+ callbackRef.current = callback;
12152
+ const stableCallbackRef = useRef();
12138
12153
 
12139
- const getNetworkSpeed = () => {
12140
- // ✅ Network Information API (support moderne)
12141
- if (!connection) {
12142
- return "3g";
12143
- }
12144
- if (connection) {
12145
- const effectiveType = connection.effectiveType;
12146
- if (effectiveType) {
12147
- return effectiveType; // "slow-2g", "2g", "3g", "4g", "5g"
12148
- }
12149
- const downlink = connection.downlink;
12150
- if (downlink) {
12151
- // downlink is in Mbps
12152
- if (downlink < 1) return "slow-2g"; // < 1 Mbps
12153
- if (downlink < 2.5) return "2g"; // 1-2.5 Mbps
12154
- if (downlink < 10) return "3g"; // 2.5-10 Mbps
12155
- return "4g"; // > 10 Mbps
12156
- }
12154
+ // Return original falsy value directly when callback is not a function
12155
+ if (!callback) {
12156
+ return callback;
12157
12157
  }
12158
- return "3g";
12159
- };
12160
-
12161
- const updateNetworkSpeed = () => {
12162
- networkSpeedSignal.value = getNetworkSpeed();
12163
- };
12164
-
12165
- const networkSpeedSignal = signal(getNetworkSpeed());
12166
12158
 
12167
- const setupNetworkMonitoring = () => {
12168
- const cleanupFunctions = [];
12169
-
12170
- // ✅ 1. Écouter les changements natifs
12171
-
12172
- if (connection) {
12173
- connection.addEventListener("change", updateNetworkSpeed);
12174
- cleanupFunctions.push(() => {
12175
- connection.removeEventListener("change", updateNetworkSpeed);
12176
- });
12159
+ const existingStableCallback = stableCallbackRef.current;
12160
+ if (existingStableCallback) {
12161
+ return existingStableCallback;
12177
12162
  }
12178
-
12179
- // 2. Polling de backup (toutes les 60 secondes)
12180
- const pollInterval = setInterval(updateNetworkSpeed, 60000);
12181
- cleanupFunctions.push(() => clearInterval(pollInterval));
12182
-
12183
- // ✅ 3. Vérifier lors de la reprise d'activité
12184
- const handleVisibilityChange = () => {
12185
- if (!document.hidden) {
12186
- updateNetworkSpeed();
12187
- }
12188
- };
12189
-
12190
- document.addEventListener("visibilitychange", handleVisibilityChange);
12191
- cleanupFunctions.push(() => {
12192
- document.removeEventListener("visibilitychange", handleVisibilityChange);
12193
- });
12194
-
12195
- // ✅ 4. Vérifier lors de la reprise de connexion
12196
- const handleOnline = () => {
12197
- updateNetworkSpeed();
12198
- };
12199
-
12200
- window.addEventListener("online", handleOnline);
12201
- cleanupFunctions.push(() => {
12202
- window.removeEventListener("online", handleOnline);
12203
- });
12204
-
12205
- // Cleanup global
12206
- return () => {
12207
- cleanupFunctions.forEach((cleanup) => cleanup());
12163
+ const stableCallback = (...args) => {
12164
+ const currentCallback = callbackRef.current;
12165
+ return currentCallback(...args);
12208
12166
  };
12167
+ stableCallbackRef.current = stableCallback;
12168
+ return stableCallback;
12209
12169
  };
12210
- setupNetworkMonitoring();
12211
-
12212
- installImportMetaCss(import.meta);import.meta.css = /* css */`
12213
- .navi_rectangle_loading {
12214
- position: relative;
12215
- display: flex;
12216
- width: 100%;
12217
- height: 100%;
12218
- opacity: 0;
12219
- }
12220
12170
 
12221
- .navi_rectangle_loading[data-visible] {
12222
- 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);
12223
12185
  }
12224
- `;
12225
- const RectangleLoading = ({
12226
- shouldShowSpinner,
12227
- color = "currentColor",
12228
- radius = 0,
12229
- size = 2
12186
+ };
12187
+ const SelectionContext = createContext();
12188
+ const useSelectionController = ({
12189
+ elementRef,
12190
+ layout,
12191
+ value,
12192
+ onChange,
12193
+ multiple,
12194
+ selectAllName
12230
12195
  }) => {
12231
- const containerRef = useRef(null);
12232
- const [containerWidth, setContainerWidth] = useState(0);
12233
- 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]);
12234
12231
  useLayoutEffect(() => {
12235
- const container = containerRef.current;
12236
- if (!container) {
12237
- return null;
12238
- }
12239
- const {
12240
- width,
12241
- height
12242
- } = container.getBoundingClientRect();
12243
- setContainerWidth(width);
12244
- setContainerHeight(height);
12245
- let animationFrameId = null;
12246
- // Create a resize observer to detect changes in the container's dimensions
12247
- const resizeObserver = new ResizeObserver(entries => {
12248
- // Use requestAnimationFrame to debounce updates
12249
- if (animationFrameId) {
12250
- cancelAnimationFrame(animationFrameId);
12251
- }
12252
- animationFrameId = requestAnimationFrame(() => {
12253
- const [containerEntry] = entries;
12254
- const {
12255
- width,
12256
- height
12257
- } = containerEntry.contentRect;
12258
- setContainerWidth(width);
12259
- setContainerHeight(height);
12260
- });
12261
- });
12262
- resizeObserver.observe(container);
12263
- return () => {
12264
- if (animationFrameId) {
12265
- cancelAnimationFrame(animationFrameId);
12266
- }
12267
- resizeObserver.disconnect();
12268
- };
12269
- }, []);
12270
- return jsx("span", {
12271
- ref: containerRef,
12272
- className: "navi_rectangle_loading",
12273
- "data-visible": shouldShowSpinner ? "" : undefined,
12274
- children: containerWidth > 0 && containerHeight > 0 && jsx(RectangleLoadingSvg, {
12275
- radius: radius,
12276
- color: color,
12277
- width: containerWidth,
12278
- height: containerHeight,
12279
- strokeWidth: size
12280
- })
12281
- });
12282
- };
12283
- const RectangleLoadingSvg = ({
12284
- width,
12285
- height,
12286
- color,
12287
- radius,
12288
- trailColor = "transparent",
12289
- strokeWidth
12290
- }) => {
12291
- const margin = Math.max(2, Math.min(width, height) * 0.03);
12292
-
12293
- // Calculate the drawable area
12294
- const drawableWidth = width - margin * 2;
12295
- const drawableHeight = height - margin * 2;
12296
-
12297
- // ✅ Check if this should be a circle - only if width and height are nearly equal
12298
- const maxPossibleRadius = Math.min(drawableWidth, drawableHeight) / 2;
12299
- const actualRadius = Math.min(radius || Math.min(drawableWidth, drawableHeight) * 0.05, maxPossibleRadius // ✅ Limité au radius maximum possible
12300
- );
12301
- const aspectRatio = Math.max(drawableWidth, drawableHeight) / Math.min(drawableWidth, drawableHeight);
12302
- const isNearlySquare = aspectRatio <= 1.2; // Allow some tolerance for nearly square shapes
12303
- const isCircle = isNearlySquare && actualRadius >= maxPossibleRadius * 0.95;
12304
- let pathLength;
12305
- let rectPath;
12306
- if (isCircle) {
12307
- // ✅ Circle: perimeter = 2πr
12308
- pathLength = 2 * Math.PI * actualRadius;
12309
-
12310
- // ✅ Circle path centered in the drawable area
12311
- const centerX = margin + drawableWidth / 2;
12312
- const centerY = margin + drawableHeight / 2;
12313
- rectPath = `
12314
- M ${centerX + actualRadius},${centerY}
12315
- A ${actualRadius},${actualRadius} 0 1 1 ${centerX - actualRadius},${centerY}
12316
- A ${actualRadius},${actualRadius} 0 1 1 ${centerX + actualRadius},${centerY}
12317
- `;
12318
- } else {
12319
- // ✅ Rectangle: calculate perimeter properly
12320
- const straightEdges = 2 * (drawableWidth - 2 * actualRadius) + 2 * (drawableHeight - 2 * actualRadius);
12321
- const cornerArcs = actualRadius > 0 ? 2 * Math.PI * actualRadius : 0;
12322
- pathLength = straightEdges + cornerArcs;
12323
- rectPath = `
12324
- M ${margin + actualRadius},${margin}
12325
- L ${margin + drawableWidth - actualRadius},${margin}
12326
- A ${actualRadius},${actualRadius} 0 0 1 ${margin + drawableWidth},${margin + actualRadius}
12327
- L ${margin + drawableWidth},${margin + drawableHeight - actualRadius}
12328
- A ${actualRadius},${actualRadius} 0 0 1 ${margin + drawableWidth - actualRadius},${margin + drawableHeight}
12329
- L ${margin + actualRadius},${margin + drawableHeight}
12330
- A ${actualRadius},${actualRadius} 0 0 1 ${margin},${margin + drawableHeight - actualRadius}
12331
- L ${margin},${margin + actualRadius}
12332
- A ${actualRadius},${actualRadius} 0 0 1 ${margin + actualRadius},${margin}
12333
- `;
12334
- }
12335
-
12336
- // Fixed segment size in pixels
12337
- const maxSegmentSize = 40;
12338
- const segmentLength = Math.min(maxSegmentSize, pathLength * 0.25);
12339
- const gapLength = pathLength - segmentLength;
12340
-
12341
- // Vitesse constante en pixels par seconde
12342
- const networkSpeed = useNetworkSpeed();
12343
- const pixelsPerSecond = {
12344
- "slow-2g": 40,
12345
- "2g": 60,
12346
- "3g": 80,
12347
- "4g": 120
12348
- }[networkSpeed] || 80;
12349
- const animationDuration = Math.max(1.5, pathLength / pixelsPerSecond);
12350
-
12351
- // ✅ Calculate correct offset based on actual segment size
12352
- const segmentRatio = segmentLength / pathLength;
12353
- const circleOffset = -animationDuration * segmentRatio;
12354
- return jsxs("svg", {
12355
- width: "100%",
12356
- height: "100%",
12357
- viewBox: `0 0 ${width} ${height}`,
12358
- preserveAspectRatio: "none",
12359
- style: "overflow: visible",
12360
- xmlns: "http://www.w3.org/2000/svg",
12361
- "shape-rendering": "geometricPrecision",
12362
- children: [isCircle ? jsx("circle", {
12363
- cx: margin + drawableWidth / 2,
12364
- cy: margin + drawableHeight / 2,
12365
- r: actualRadius,
12366
- fill: "none",
12367
- stroke: trailColor,
12368
- strokeWidth: strokeWidth
12369
- }) : jsx("rect", {
12370
- x: margin,
12371
- y: margin,
12372
- width: drawableWidth,
12373
- height: drawableHeight,
12374
- fill: "none",
12375
- stroke: trailColor,
12376
- strokeWidth: strokeWidth,
12377
- rx: actualRadius
12378
- }), jsx("path", {
12379
- d: rectPath,
12380
- fill: "none",
12381
- stroke: color,
12382
- strokeWidth: strokeWidth,
12383
- strokeLinecap: "round",
12384
- strokeDasharray: `${segmentLength} ${gapLength}`,
12385
- pathLength: pathLength,
12386
- children: jsx("animate", {
12387
- attributeName: "stroke-dashoffset",
12388
- from: pathLength,
12389
- to: "0",
12390
- dur: `${animationDuration}s`,
12391
- repeatCount: "indefinite",
12392
- begin: "0s"
12393
- })
12394
- }), jsx("circle", {
12395
- r: strokeWidth,
12396
- fill: color,
12397
- children: jsx("animateMotion", {
12398
- path: rectPath,
12399
- dur: `${animationDuration}s`,
12400
- repeatCount: "indefinite",
12401
- rotate: "auto",
12402
- begin: `${circleOffset}s`
12403
- })
12404
- })]
12405
- });
12406
- };
12407
-
12408
- installImportMetaCss(import.meta);import.meta.css = /* css */`
12409
- .navi_loading_rectangle_wrapper {
12410
- position: absolute;
12411
- top: var(--rectangle-top, 0);
12412
- right: var(--rectangle-right, 0);
12413
- bottom: var(--rectangle-bottom, 0);
12414
- left: var(--rectangle-left, 0);
12415
- z-index: 1;
12416
- opacity: 0;
12417
- pointer-events: none;
12418
- }
12419
- .navi_loading_rectangle_wrapper[data-visible] {
12420
- opacity: 1;
12421
- }
12422
- `;
12423
- const LoaderBackground = ({
12424
- loading,
12425
- containerRef,
12426
- targetSelector,
12427
- color,
12428
- inset = 0,
12429
- spacingTop = 0,
12430
- spacingLeft = 0,
12431
- spacingBottom = 0,
12432
- spacingRight = 0,
12433
- children
12434
- }) => {
12435
- if (containerRef) {
12436
- const container = containerRef.current;
12437
- if (!container) {
12438
- return children;
12439
- }
12440
- return createPortal(jsx(LoaderBackgroundWithPortal, {
12441
- container: container,
12442
- loading: loading,
12443
- color: color,
12444
- inset: inset,
12445
- spacingTop: spacingTop,
12446
- spacingLeft: spacingLeft,
12447
- spacingBottom: spacingBottom,
12448
- spacingRight: spacingRight,
12449
- children: children
12450
- }), container);
12451
- }
12452
- return jsx(LoaderBackgroundBasic, {
12453
- targetSelector: targetSelector,
12454
- loading: loading,
12455
- color: color,
12456
- inset: inset,
12457
- spacingTop: spacingTop,
12458
- spacingLeft: spacingLeft,
12459
- spacingBottom: spacingBottom,
12460
- spacingRight: spacingRight,
12461
- children: children
12462
- });
12463
- };
12464
- const LoaderBackgroundWithPortal = ({
12465
- container,
12466
- loading,
12467
- color,
12468
- inset,
12469
- spacingTop,
12470
- spacingLeft,
12471
- spacingBottom,
12472
- spacingRight,
12473
- children
12474
- }) => {
12475
- const shouldShowSpinner = useDebounceTrue(loading, 300);
12476
- if (!shouldShowSpinner) {
12477
- return children;
12478
- }
12479
- container.style.position = "relative";
12480
- let paddingTop = 0;
12481
- if (container.nodeName === "DETAILS") {
12482
- paddingTop = container.querySelector("summary").offsetHeight;
12483
- }
12484
- return jsxs(Fragment, {
12485
- children: [jsx("div", {
12486
- style: {
12487
- position: "absolute",
12488
- top: `${inset + paddingTop + spacingTop}px`,
12489
- bottom: `${inset + spacingBottom}px`,
12490
- left: `${inset + spacingLeft}px`,
12491
- right: `${inset + spacingRight}px`
12492
- },
12493
- children: shouldShowSpinner && jsx(RectangleLoading, {
12494
- color: color
12495
- })
12496
- }), children]
12497
- });
12498
- };
12499
- const LoaderBackgroundBasic = ({
12500
- loading,
12501
- targetSelector,
12502
- color,
12503
- spacingTop,
12504
- spacingLeft,
12505
- spacingBottom,
12506
- spacingRight,
12507
- inset,
12508
- children
12509
- }) => {
12510
- const shouldShowSpinner = useDebounceTrue(loading, 300);
12511
- const rectangleRef = useRef(null);
12512
- const [, setOutlineOffset] = useState(0);
12513
- const [borderRadius, setBorderRadius] = useState(0);
12514
- const [borderTopWidth, setBorderTopWidth] = useState(0);
12515
- const [borderLeftWidth, setBorderLeftWidth] = useState(0);
12516
- const [borderRightWidth, setBorderRightWidth] = useState(0);
12517
- const [borderBottomWidth, setBorderBottomWidth] = useState(0);
12518
- const [marginTop, setMarginTop] = useState(0);
12519
- const [marginBottom, setMarginBottom] = useState(0);
12520
- const [marginLeft, setMarginLeft] = useState(0);
12521
- const [marginRight, setMarginRight] = useState(0);
12522
- const [paddingTop, setPaddingTop] = useState(0);
12523
- const [paddingLeft, setPaddingLeft] = useState(0);
12524
- const [paddingRight, setPaddingRight] = useState(0);
12525
- const [paddingBottom, setPaddingBottom] = useState(0);
12526
- const [currentColor, setCurrentColor] = useState(color);
12527
- useLayoutEffect(() => {
12528
- let animationFrame;
12529
- const updateStyles = () => {
12530
- const rectangle = rectangleRef.current;
12531
- if (!rectangle) {
12532
- return;
12533
- }
12534
- const container = rectangle.parentElement;
12535
- const containedElement = rectangle.nextElementSibling;
12536
- const target = targetSelector ? container.querySelector(targetSelector) : containedElement;
12537
- if (target) {
12538
- const {
12539
- width,
12540
- height
12541
- } = target.getBoundingClientRect();
12542
- const containedComputedStyle = window.getComputedStyle(containedElement);
12543
- const targetComputedStyle = window.getComputedStyle(target);
12544
- const newBorderTopWidth = resolveCSSSize(targetComputedStyle.borderTopWidth);
12545
- const newBorderLeftWidth = resolveCSSSize(targetComputedStyle.borderLeftWidth);
12546
- const newBorderRightWidth = resolveCSSSize(targetComputedStyle.borderRightWidth);
12547
- const newBorderBottomWidth = resolveCSSSize(targetComputedStyle.borderBottomWidth);
12548
- const newBorderRadius = resolveCSSSize(targetComputedStyle.borderRadius, {
12549
- availableSize: Math.min(width, height)
12550
- });
12551
- const newOutlineColor = targetComputedStyle.outlineColor;
12552
- const newBorderColor = targetComputedStyle.borderColor;
12553
- const newDetectedColor = targetComputedStyle.color;
12554
- const newOutlineOffset = resolveCSSSize(targetComputedStyle.outlineOffset);
12555
- const newMarginTop = resolveCSSSize(targetComputedStyle.marginTop);
12556
- const newMarginBottom = resolveCSSSize(targetComputedStyle.marginBottom);
12557
- const newMarginLeft = resolveCSSSize(targetComputedStyle.marginLeft);
12558
- const newMarginRight = resolveCSSSize(targetComputedStyle.marginRight);
12559
- const paddingTop = resolveCSSSize(containedComputedStyle.paddingTop);
12560
- const paddingLeft = resolveCSSSize(containedComputedStyle.paddingLeft);
12561
- const paddingRight = resolveCSSSize(containedComputedStyle.paddingRight);
12562
- const paddingBottom = resolveCSSSize(containedComputedStyle.paddingBottom);
12563
- setBorderTopWidth(newBorderTopWidth);
12564
- setBorderLeftWidth(newBorderLeftWidth);
12565
- setBorderRightWidth(newBorderRightWidth);
12566
- setBorderBottomWidth(newBorderBottomWidth);
12567
- setBorderRadius(newBorderRadius);
12568
- setOutlineOffset(newOutlineOffset);
12569
- setMarginTop(newMarginTop);
12570
- setMarginBottom(newMarginBottom);
12571
- setMarginLeft(newMarginLeft);
12572
- setMarginRight(newMarginRight);
12573
- setPaddingTop(paddingTop);
12574
- setPaddingLeft(paddingLeft);
12575
- setPaddingRight(paddingRight);
12576
- setPaddingBottom(paddingBottom);
12577
- if (color) {
12578
- // const resolvedColor = resolveCSSColor(color, rectangle, "css");
12579
- // console.log(resolvedColor);
12580
- setCurrentColor(color);
12581
- } else if (newOutlineColor && newOutlineColor !== "rgba(0, 0, 0, 0)" && (document.activeElement === containedElement || newBorderColor === "rgba(0, 0, 0, 0)")) {
12582
- setCurrentColor(newOutlineColor);
12583
- } else if (newBorderColor && newBorderColor !== "rgba(0, 0, 0, 0)") {
12584
- setCurrentColor(newBorderColor);
12585
- } else {
12586
- setCurrentColor(newDetectedColor);
12587
- }
12588
- }
12589
- // updateStyles is very cheap so we run it every frame
12590
- animationFrame = requestAnimationFrame(updateStyles);
12591
- };
12592
- updateStyles();
12593
- return () => {
12594
- cancelAnimationFrame(animationFrame);
12595
- };
12596
- }, [color, targetSelector]);
12597
- spacingTop += inset;
12598
- // spacingTop += outlineOffset;
12599
- // spacingTop -= borderTopWidth;
12600
- spacingTop += marginTop;
12601
- spacingLeft += inset;
12602
- // spacingLeft += outlineOffset;
12603
- // spacingLeft -= borderLeftWidth;
12604
- spacingLeft += marginLeft;
12605
- spacingRight += inset;
12606
- // spacingRight += outlineOffset;
12607
- // spacingRight -= borderRightWidth;
12608
- spacingRight += marginRight;
12609
- spacingBottom += inset;
12610
- // spacingBottom += outlineOffset;
12611
- // spacingBottom -= borderBottomWidth;
12612
- spacingBottom += marginBottom;
12613
- if (targetSelector) {
12614
- // oversimplification that actually works
12615
- // (simplified because it assumes the targeted element is a direct child of the contained element which may have padding)
12616
- spacingTop += paddingTop;
12617
- spacingLeft += paddingLeft;
12618
- spacingRight += paddingRight;
12619
- spacingBottom += paddingBottom;
12620
- }
12621
- const maxBorderWidth = Math.max(borderTopWidth, borderLeftWidth, borderRightWidth, borderBottomWidth);
12622
- const halfMaxBorderSize = maxBorderWidth / 2;
12623
- const size = halfMaxBorderSize < 2 ? 2 : halfMaxBorderSize;
12624
- const lineHalfSize = size / 2;
12625
- spacingTop -= lineHalfSize;
12626
- spacingLeft -= lineHalfSize;
12627
- spacingRight -= lineHalfSize;
12628
- spacingBottom -= lineHalfSize;
12629
- return jsxs(Fragment, {
12630
- children: [jsx("span", {
12631
- ref: rectangleRef,
12632
- className: "navi_loading_rectangle_wrapper",
12633
- "data-visible": shouldShowSpinner ? "" : undefined,
12634
- style: {
12635
- "--rectangle-top": `${spacingTop}px`,
12636
- "--rectangle-left": `${spacingLeft}px`,
12637
- "--rectangle-bottom": `${spacingBottom}px`,
12638
- "--rectangle-right": `${spacingRight}px`
12639
- },
12640
- children: loading && jsx(RectangleLoading, {
12641
- shouldShowSpinner: shouldShowSpinner,
12642
- color: currentColor,
12643
- radius: borderRadius,
12644
- size: size
12645
- })
12646
- }), children]
12647
- });
12648
- };
12649
-
12650
- /**
12651
- * Custom hook creating a stable callback that doesn't trigger re-renders.
12652
- *
12653
- * PROBLEM: Parent components often forget to use useCallback, causing library
12654
- * components to re-render unnecessarily when receiving callback props.
12655
- *
12656
- * SOLUTION: Library components can use this hook to create stable callback
12657
- * references internally, making them defensive against parents who don't
12658
- * optimize their callbacks. This ensures library components don't force
12659
- * consumers to think about useCallback.
12660
- *
12661
- * USAGE:
12662
- * ```js
12663
- * // Parent component (consumer) - no useCallback needed
12664
- * const Parent = () => {
12665
- * const [count, setCount] = useState(0);
12666
- *
12667
- * // Parent naturally creates new function reference each render
12668
- * // (forgetting useCallback is common and shouldn't break performance)
12669
- * return <LibraryButton onClick={(e) => setCount(count + 1)} />;
12670
- * };
12671
- *
12672
- * // Library component - defensive against changing callbacks
12673
- * const LibraryButton = ({ onClick }) => {
12674
- * // ✅ Create stable reference from parent's potentially changing callback
12675
- * const stableClick = useStableCallback(onClick);
12676
- *
12677
- * // Internal expensive components won't re-render when parent updates
12678
- * return <ExpensiveInternalButton onClick={stableClick} />;
12679
- * };
12680
- *
12681
- * // Deep internal component gets stable reference
12682
- * const ExpensiveInternalButton = memo(({ onClick }) => {
12683
- * // This won't re-render when Parent's count changes
12684
- * // But onClick will always call the latest Parent callback
12685
- * return <button onClick={onClick}>Click me</button>;
12686
- * });
12687
- * ```
12688
- *
12689
- * Perfect for library components that need performance without burdening consumers.
12690
- */
12691
-
12692
-
12693
- const useStableCallback = (callback, mapper) => {
12694
- const callbackRef = useRef();
12695
- callbackRef.current = callback;
12696
- const stableCallbackRef = useRef();
12697
-
12698
- // Return original falsy value directly when callback is not a function
12699
- if (!callback) {
12700
- return callback;
12701
- }
12702
-
12703
- const existingStableCallback = stableCallbackRef.current;
12704
- if (existingStableCallback) {
12705
- return existingStableCallback;
12706
- }
12707
- const stableCallback = (...args) => {
12708
- const currentCallback = callbackRef.current;
12709
- return currentCallback(...args);
12710
- };
12711
- stableCallbackRef.current = stableCallback;
12712
- return stableCallback;
12713
- };
12714
-
12715
- const DEBUG = {
12716
- registration: false,
12717
- // Element registration/unregistration
12718
- interaction: false,
12719
- // Click and keyboard interactions
12720
- selection: false,
12721
- // Selection state changes (set, add, remove, toggle)
12722
- navigation: false,
12723
- // Arrow key navigation and element finding
12724
- valueExtraction: false // Value extraction from elements
12725
- };
12726
- const debug = (category, ...args) => {
12727
- if (DEBUG[category]) {
12728
- console.debug(`[selection:${category}]`, ...args);
12729
- }
12730
- };
12731
- const SelectionContext = createContext();
12732
- const useSelectionController = ({
12733
- elementRef,
12734
- layout,
12735
- value,
12736
- onChange,
12737
- multiple,
12738
- selectAllName
12739
- }) => {
12740
- if (!elementRef) {
12741
- throw new Error("useSelectionController: elementRef is required");
12742
- }
12743
- onChange = useStableCallback(onChange);
12744
- const currentValueRef = useRef(value);
12745
- currentValueRef.current = value;
12746
- const lastInternalValueRef = useRef(null);
12747
- const selectionController = useMemo(() => {
12748
- const innerOnChange = (newValue, ...args) => {
12749
- lastInternalValueRef.current = newValue;
12750
- onChange?.(newValue, ...args);
12751
- };
12752
- const getCurrentValue = () => currentValueRef.current;
12753
- if (layout === "grid") {
12754
- return createGridSelectionController({
12755
- getCurrentValue,
12756
- onChange: innerOnChange,
12757
- enabled: Boolean(onChange),
12758
- multiple,
12759
- selectAllName
12760
- });
12761
- }
12762
- return createLinearSelectionController({
12763
- getCurrentValue,
12764
- onChange: innerOnChange,
12765
- layout,
12766
- elementRef,
12767
- multiple,
12768
- enabled: Boolean(onChange),
12769
- selectAllName
12770
- });
12771
- }, [layout, multiple, elementRef]);
12772
- useEffect(() => {
12773
- selectionController.element = elementRef.current;
12774
- }, [selectionController]);
12775
- useLayoutEffect(() => {
12776
- selectionController.enabled = Boolean(onChange);
12777
- }, [selectionController, onChange]);
12778
-
12779
- // Smart sync: only update selection when value changes externally
12780
- useEffect(() => {
12781
- // Check if this is an external change (not from our internal onChange)
12782
- const isExternalChange = !compareTwoJsValues(value, lastInternalValueRef.current);
12783
- if (isExternalChange) {
12784
- 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);
12785
12241
  }
12786
12242
  }, [value, selectionController]);
12787
12243
  return selectionController;
@@ -16761,24 +16217,58 @@ const installCustomConstraintValidation = (
16761
16217
  if (keydownEvent.key !== "Enter") {
16762
16218
  return;
16763
16219
  }
16764
- if (element.hasAttribute("data-action")) {
16765
- if (wouldKeydownSubmitForm(keydownEvent)) {
16766
- keydownEvent.preventDefault();
16767
- }
16768
- dispatchActionRequestedCustomEvent(element, {
16769
- event: keydownEvent,
16770
- requester: element,
16771
- });
16220
+ const elementWithAction = closestElementWithAction(element);
16221
+ if (!elementWithAction) {
16772
16222
  return;
16773
16223
  }
16774
- const { form } = element;
16775
- if (!form) {
16776
- return;
16224
+
16225
+ const determineClosestFormSubmitTargetForEnterKeyEvent = () => {
16226
+ if (keydownEvent.defaultPrevented) {
16227
+ return null;
16228
+ }
16229
+ const keydownTarget = keydownEvent.target;
16230
+ const { form } = keydownTarget;
16231
+ if (!form) {
16232
+ return null;
16233
+ }
16234
+ if (keydownTarget.tagName === "BUTTON") {
16235
+ if (
16236
+ keydownTarget.type !== "submit" &&
16237
+ keydownTarget.type !== "image"
16238
+ ) {
16239
+ return null;
16240
+ }
16241
+ return keydownTarget;
16242
+ }
16243
+ if (keydownTarget.tagName === "INPUT") {
16244
+ if (
16245
+ ![
16246
+ "text",
16247
+ "email",
16248
+ "password",
16249
+ "search",
16250
+ "number",
16251
+ "url",
16252
+ "tel",
16253
+ ].includes(keydownTarget.type)
16254
+ ) {
16255
+ return null;
16256
+ }
16257
+ // when present, we use first button submitting the form as the requester
16258
+ // not the input, it aligns with browser behavior where
16259
+ // hitting Enter in a text input triggers the first submit button of the form, not the input itself
16260
+ return getFirstButtonSubmittingForm(keydownTarget) || keydownTarget;
16261
+ }
16262
+ return null;
16263
+ };
16264
+ const formSubmitTarget =
16265
+ determineClosestFormSubmitTargetForEnterKeyEvent();
16266
+ if (formSubmitTarget) {
16267
+ keydownEvent.preventDefault();
16777
16268
  }
16778
- keydownEvent.preventDefault();
16779
- dispatchActionRequestedCustomEvent(form, {
16269
+ dispatchActionRequestedCustomEvent(elementWithAction, {
16780
16270
  event: keydownEvent,
16781
- requester: getFirstButtonSubmittingForm(form) || element,
16271
+ requester: formSubmitTarget || element,
16782
16272
  });
16783
16273
  };
16784
16274
  element.addEventListener("keydown", onkeydown);
@@ -16795,29 +16285,44 @@ const installCustomConstraintValidation = (
16795
16285
  if (element.tagName !== "BUTTON") {
16796
16286
  return;
16797
16287
  }
16798
- if (element.hasAttribute("data-action")) {
16799
- if (wouldButtonClickSubmitForm(element, clickEvent)) {
16800
- clickEvent.preventDefault();
16801
- }
16802
- dispatchActionRequestedCustomEvent(element, {
16803
- event: clickEvent,
16804
- requester: element,
16805
- });
16806
- return;
16807
- }
16808
- const { form } = element;
16809
- if (!form) {
16810
- return;
16811
- }
16812
- if (element.type === "reset") {
16288
+ const button = element;
16289
+ const elementWithAction = closestElementWithAction(button);
16290
+ if (!elementWithAction) {
16813
16291
  return;
16814
16292
  }
16815
- if (wouldButtonClickSubmitForm(element, clickEvent)) {
16293
+ const determineClosestFormSubmitTargetForClickEvent = () => {
16294
+ if (clickEvent.defaultPrevented) {
16295
+ return null;
16296
+ }
16297
+ const clickTarget = clickEvent.target;
16298
+ const { form } = clickTarget;
16299
+ if (!form) {
16300
+ return null;
16301
+ }
16302
+ const wouldSubmitFormByType =
16303
+ button.type === "submit" || button.type === "image";
16304
+ if (wouldSubmitFormByType) {
16305
+ return button;
16306
+ }
16307
+ if (button.type) {
16308
+ // "reset", "button" or any other non submit type, it won't submit the form
16309
+ return null;
16310
+ }
16311
+ const firstButtonSubmittingForm = getFirstButtonSubmittingForm(form);
16312
+ if (button !== firstButtonSubmittingForm) {
16313
+ // an other button is explicitly submitting the form, this one would not submit it
16314
+ return null;
16315
+ }
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) {
16816
16321
  clickEvent.preventDefault();
16817
16322
  }
16818
- dispatchActionRequestedCustomEvent(form, {
16323
+ dispatchActionRequestedCustomEvent(elementWithAction, {
16819
16324
  event: clickEvent,
16820
- requester: element,
16325
+ requester: formSubmitTarget || button,
16821
16326
  });
16822
16327
  };
16823
16328
  element.addEventListener("click", onclick);
@@ -16833,13 +16338,14 @@ const installCustomConstraintValidation = (
16833
16338
  break request_on_input_change;
16834
16339
  }
16835
16340
  const stop = listenInputChange(element, (e) => {
16836
- if (element.hasAttribute("data-action")) {
16837
- dispatchActionRequestedCustomEvent(element, {
16838
- event: e,
16839
- requester: element,
16840
- });
16341
+ const elementWithAction = closestElementWithAction(element);
16342
+ if (!elementWithAction) {
16841
16343
  return;
16842
16344
  }
16345
+ dispatchActionRequestedCustomEvent(elementWithAction, {
16346
+ event: e,
16347
+ requester: element,
16348
+ });
16843
16349
  });
16844
16350
  addTeardown(() => {
16845
16351
  stop();
@@ -16937,6 +16443,31 @@ const installCustomConstraintValidation = (
16937
16443
  return validationInterface;
16938
16444
  };
16939
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
+
16940
16471
  const pickConstraint = (a, b) => {
16941
16472
  const aPrio = getConstraintPriority(a);
16942
16473
  const bPrio = getConstraintPriority(b);
@@ -16955,60 +16486,14 @@ const getConstraintPriority = (constraint) => {
16955
16486
  return 1;
16956
16487
  };
16957
16488
 
16958
- const wouldButtonClickSubmitForm = (button, clickEvent) => {
16959
- if (clickEvent.defaultPrevented) {
16960
- return false;
16961
- }
16962
- const { form } = button;
16963
- if (!form) {
16964
- return false;
16965
- }
16966
- if (!button) {
16967
- return false;
16968
- }
16969
- const wouldSubmitFormByType =
16970
- button.type === "submit" || button.type === "image";
16971
- if (wouldSubmitFormByType) {
16972
- return true;
16973
- }
16974
- if (button.type) {
16975
- return false;
16976
- }
16977
- if (getFirstButtonSubmittingForm(form)) {
16978
- // an other button is explicitly submitting the form, this one would not submit it
16979
- return false;
16980
- }
16981
- // this is the only button inside the form without type attribute, so it defaults to type="submit"
16982
- return true;
16983
- };
16984
16489
  const getFirstButtonSubmittingForm = (form) => {
16985
16490
  return form.querySelector(
16986
16491
  `button[type="submit"], input[type="submit"], input[type="image"]`,
16987
16492
  );
16988
16493
  };
16989
16494
 
16990
- const wouldKeydownSubmitForm = (keydownEvent) => {
16991
- if (keydownEvent.defaultPrevented) {
16992
- return false;
16993
- }
16994
- const keydownTarget = keydownEvent.target;
16995
- const { form } = keydownTarget;
16996
- if (!form) {
16997
- return false;
16998
- }
16999
- if (keydownEvent.key !== "Enter") {
17000
- return false;
17001
- }
17002
- const isTextInput =
17003
- keydownTarget.tagName === "INPUT" || keydownTarget.tagName === "TEXTAREA";
17004
- if (!isTextInput) {
17005
- return false;
17006
- }
17007
- return true;
17008
- };
17009
-
17010
16495
  const dispatchActionRequestedCustomEvent = (
17011
- fieldOrForm,
16496
+ elementWithAction,
17012
16497
  { actionOrigin = "action_prop", event, requester },
17013
16498
  ) => {
17014
16499
  const actionRequestedCustomEvent = new CustomEvent("actionrequested", {
@@ -17019,7 +16504,7 @@ const dispatchActionRequestedCustomEvent = (
17019
16504
  requester,
17020
16505
  },
17021
16506
  });
17022
- fieldOrForm.dispatchEvent(actionRequestedCustomEvent);
16507
+ elementWithAction.dispatchEvent(actionRequestedCustomEvent);
17023
16508
  };
17024
16509
  // https://developer.mozilla.org/en-US/docs/Web/HTML/Guides/Constraint_validation
17025
16510
  const requestSubmit = HTMLFormElement.prototype.requestSubmit;
@@ -17321,553 +16806,1133 @@ const selectByCharacterIndices = (element, range, startIndex, endIndex) => {
17321
16806
  startNode = walker.currentNode;
17322
16807
  startOffset = startIndex - currentIndex;
17323
16808
  }
17324
-
17325
- // Check if end position is in this text node
17326
- if (currentIndex + nodeLength >= endIndex) {
17327
- endNode = walker.currentNode;
17328
- endOffset = endIndex - currentIndex;
16809
+
16810
+ // Check if end position is in this text node
16811
+ if (currentIndex + nodeLength >= endIndex) {
16812
+ endNode = walker.currentNode;
16813
+ endOffset = endIndex - currentIndex;
16814
+ break;
16815
+ }
16816
+ currentIndex += nodeLength;
16817
+ }
16818
+ if (startNode && endNode) {
16819
+ range.setStart(startNode, startOffset);
16820
+ range.setEnd(endNode, endOffset);
16821
+ }
16822
+ };
16823
+ const selectSingleTextString = (element, range, text) => {
16824
+ const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false);
16825
+ while (walker.nextNode()) {
16826
+ const textContent = walker.currentNode.textContent;
16827
+ const index = textContent.indexOf(text);
16828
+ if (index !== -1) {
16829
+ range.setStart(walker.currentNode, index);
16830
+ range.setEnd(walker.currentNode, index + text.length);
16831
+ return;
16832
+ }
16833
+ }
16834
+ };
16835
+ const selectByTextStrings = (element, range, startText, endText) => {
16836
+ const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false);
16837
+ let startNode = null;
16838
+ let endNode = null;
16839
+ let foundStart = false;
16840
+ while (walker.nextNode()) {
16841
+ const textContent = walker.currentNode.textContent;
16842
+ if (!foundStart && textContent.includes(startText)) {
16843
+ startNode = walker.currentNode;
16844
+ foundStart = true;
16845
+ }
16846
+ if (foundStart && textContent.includes(endText)) {
16847
+ endNode = walker.currentNode;
16848
+ break;
16849
+ }
16850
+ }
16851
+ if (startNode && endNode) {
16852
+ const startOffset = startNode.textContent.indexOf(startText);
16853
+ const endOffset = endNode.textContent.indexOf(endText) + endText.length;
16854
+ range.setStart(startNode, startOffset);
16855
+ range.setEnd(endNode, endOffset);
16856
+ }
16857
+ };
16858
+
16859
+ installImportMetaCss(import.meta);import.meta.css = /* css */`
16860
+ *[data-navi-space] {
16861
+ /* user-select: none; */
16862
+ }
16863
+
16864
+ .navi_text {
16865
+ position: relative;
16866
+ color: inherit;
16867
+
16868
+ &[data-has-absolute-child] {
16869
+ display: inline-block;
16870
+ }
16871
+ }
16872
+
16873
+ .navi_text_overflow {
16874
+ flex-wrap: wrap;
16875
+ text-overflow: ellipsis;
16876
+ overflow: hidden;
16877
+ }
16878
+
16879
+ .navi_text_overflow_wrapper {
16880
+ display: flex;
16881
+ width: 0;
16882
+ flex-grow: 1;
16883
+ gap: 0.3em;
16884
+ }
16885
+
16886
+ .navi_text_overflow_text {
16887
+ max-width: 100%;
16888
+ text-overflow: ellipsis;
16889
+ overflow: hidden;
16890
+ }
16891
+
16892
+ .navi_custom_space {
16893
+ }
16894
+
16895
+ .navi_text_bold_wrapper {
16896
+ position: relative;
16897
+ display: inline-block;
16898
+ }
16899
+ .navi_text_bold_clone {
16900
+ font-weight: bold;
16901
+ opacity: 0;
16902
+ }
16903
+ .navi_text_bold_foreground {
16904
+ position: absolute;
16905
+ inset: 0;
16906
+ }
16907
+
16908
+ .navi_text_bold_background {
16909
+ position: absolute;
16910
+ top: 0;
16911
+ left: 0;
16912
+ color: currentColor;
16913
+ font-weight: normal;
16914
+ background: currentColor;
16915
+ background-clip: text;
16916
+ -webkit-background-clip: text;
16917
+ transform-origin: center;
16918
+ -webkit-text-fill-color: transparent;
16919
+ opacity: 0;
16920
+ }
16921
+
16922
+ .navi_text[data-bold] {
16923
+ .navi_text_bold_background {
16924
+ opacity: 1;
16925
+ }
16926
+ }
16927
+
16928
+ .navi_text[data-bold-transition] {
16929
+ .navi_text_bold_foreground {
16930
+ transition-property: font-weight;
16931
+ transition-duration: 0.3s;
16932
+ transition-timing-function: ease;
16933
+ }
16934
+
16935
+ .navi_text_bold_background {
16936
+ transition-property: opacity;
16937
+ transition-duration: 0.3s;
16938
+ transition-timing-function: ease;
16939
+ }
16940
+ }
16941
+ `;
16942
+ const REGULAR_SPACE = jsx("span", {
16943
+ "data-navi-space": "",
16944
+ children: " "
16945
+ });
16946
+ const CustomWidthSpace = ({
16947
+ value
16948
+ }) => {
16949
+ return jsx("span", {
16950
+ className: "navi_custom_space",
16951
+ style: `padding-left: ${value}`,
16952
+ children: "\u200B"
16953
+ });
16954
+ };
16955
+ const applySpacingOnTextChildren = (children, spacing) => {
16956
+ if (spacing === "pre" || spacing === "0" || spacing === 0) {
16957
+ return children;
16958
+ }
16959
+ if (!children) {
16960
+ return children;
16961
+ }
16962
+ const childArray = toChildArray(children);
16963
+ const childCount = childArray.length;
16964
+ if (childCount <= 1) {
16965
+ return children;
16966
+ }
16967
+ let separator;
16968
+ if (spacing === undefined) {
16969
+ spacing = REGULAR_SPACE;
16970
+ } else if (typeof spacing === "string") {
16971
+ if (isSizeSpacingScaleKey(spacing)) {
16972
+ separator = jsx(CustomWidthSpace, {
16973
+ value: resolveSpacingSize(spacing)
16974
+ });
16975
+ } else if (hasCSSSizeUnit(spacing)) {
16976
+ separator = jsx(CustomWidthSpace, {
16977
+ value: resolveSpacingSize(spacing)
16978
+ });
16979
+ } else {
16980
+ separator = spacing;
16981
+ }
16982
+ } else if (typeof spacing === "number") {
16983
+ separator = jsx(CustomWidthSpace, {
16984
+ value: spacing
16985
+ });
16986
+ } else {
16987
+ separator = spacing;
16988
+ }
16989
+ const childrenWithGap = [];
16990
+ let i = 0;
16991
+ while (true) {
16992
+ const child = childArray[i];
16993
+ childrenWithGap.push(child);
16994
+ i++;
16995
+ if (i === childCount) {
17329
16996
  break;
17330
16997
  }
17331
- currentIndex += nodeLength;
16998
+ const currentChild = childArray[i - 1];
16999
+ const nextChild = childArray[i];
17000
+ if (endsWithWhitespace(currentChild)) {
17001
+ continue;
17002
+ }
17003
+ if (startsWithWhitespace(nextChild)) {
17004
+ continue;
17005
+ }
17006
+ childrenWithGap.push(separator);
17332
17007
  }
17333
- if (startNode && endNode) {
17334
- range.setStart(startNode, startOffset);
17335
- range.setEnd(endNode, endOffset);
17008
+ return childrenWithGap;
17009
+ };
17010
+ const endsWithWhitespace = jsxChild => {
17011
+ if (typeof jsxChild === "string") {
17012
+ return /\s$/.test(jsxChild);
17336
17013
  }
17014
+ return false;
17337
17015
  };
17338
- const selectSingleTextString = (element, range, text) => {
17339
- const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false);
17340
- while (walker.nextNode()) {
17341
- const textContent = walker.currentNode.textContent;
17342
- const index = textContent.indexOf(text);
17343
- if (index !== -1) {
17344
- range.setStart(walker.currentNode, index);
17345
- range.setEnd(walker.currentNode, index + text.length);
17346
- return;
17347
- }
17016
+ const startsWithWhitespace = jsxChild => {
17017
+ if (typeof jsxChild === "string") {
17018
+ return /^\s/.test(jsxChild);
17348
17019
  }
17020
+ return false;
17349
17021
  };
17350
- const selectByTextStrings = (element, range, startText, endText) => {
17351
- const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false);
17352
- let startNode = null;
17353
- let endNode = null;
17354
- let foundStart = false;
17355
- while (walker.nextNode()) {
17356
- const textContent = walker.currentNode.textContent;
17357
- if (!foundStart && textContent.includes(startText)) {
17358
- startNode = walker.currentNode;
17359
- foundStart = true;
17360
- }
17361
- if (foundStart && textContent.includes(endText)) {
17362
- endNode = walker.currentNode;
17363
- break;
17364
- }
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
+ });
17365
17032
  }
17366
- if (startNode && endNode) {
17367
- const startOffset = startNode.textContent.indexOf(startText);
17368
- const endOffset = endNode.textContent.indexOf(endText) + endText.length;
17369
- range.setStart(startNode, startOffset);
17370
- range.setEnd(endNode, endOffset);
17033
+ if (props.overflowPinned) {
17034
+ return jsx(TextOverflowPinned, {
17035
+ ...props
17036
+ });
17037
+ }
17038
+ if (props.selectRange) {
17039
+ return jsx(TextWithSelectRange, {
17040
+ ...props
17041
+ });
17371
17042
  }
17043
+ return jsx(TextBasic, {
17044
+ ...props
17045
+ });
17372
17046
  };
17373
-
17374
- installImportMetaCss(import.meta);import.meta.css = /* css */`
17375
- *[data-navi-space] {
17376
- /* user-select: none; */
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;
17377
17088
  }
17378
-
17379
- .navi_text {
17380
- position: relative;
17381
- color: inherit;
17382
-
17383
- &[data-has-absolute-child] {
17384
- display: inline-block;
17385
- }
17089
+ if (overflowPinned) {
17090
+ setOverflowPinnedElement(text);
17091
+ return null;
17386
17092
  }
17387
-
17388
- .navi_text_overflow {
17389
- flex-wrap: wrap;
17390
- text-overflow: ellipsis;
17391
- overflow: hidden;
17093
+ setOverflowPinnedElement(null);
17094
+ return text;
17095
+ };
17096
+ const TextWithSelectRange = ({
17097
+ selectRange,
17098
+ ...props
17099
+ }) => {
17100
+ const defaultRef = useRef();
17101
+ const ref = props.ref || defaultRef;
17102
+ useInitialTextSelection(ref, selectRange);
17103
+ return jsx(Text, {
17104
+ ref: ref,
17105
+ ...props
17106
+ });
17107
+ };
17108
+ const TextBasic = ({
17109
+ spacing = " ",
17110
+ boldTransition,
17111
+ boldStable,
17112
+ preventBoldLayoutShift = boldTransition,
17113
+ children,
17114
+ ...rest
17115
+ }) => {
17116
+ const boxProps = {
17117
+ "as": "span",
17118
+ "data-bold-transition": boldTransition ? "" : undefined,
17119
+ ...rest,
17120
+ "baseClassName": withPropsClassName("navi_text", rest.baseClassName)
17121
+ };
17122
+ const shouldPreserveSpacing = rest.as === "pre" || rest.box || rest.column || rest.row;
17123
+ if (shouldPreserveSpacing) {
17124
+ boxProps.spacing = spacing;
17125
+ } else {
17126
+ children = applySpacingOnTextChildren(children, spacing);
17127
+ }
17128
+ if (boldStable) {
17129
+ const {
17130
+ bold
17131
+ } = boxProps;
17132
+ return jsxs(Box, {
17133
+ ...boxProps,
17134
+ bold: undefined,
17135
+ "data-bold": bold ? "" : undefined,
17136
+ "data-has-absolute-child": "",
17137
+ children: [jsx("span", {
17138
+ className: "navi_text_bold_background",
17139
+ "aria-hidden": "true",
17140
+ children: children
17141
+ }), children]
17142
+ });
17392
17143
  }
17144
+ if (preventBoldLayoutShift) {
17145
+ const alignX = rest.alignX || rest.align || "start";
17393
17146
 
17394
- .navi_text_overflow_wrapper {
17395
- display: flex;
17396
- width: 0;
17397
- flex-grow: 1;
17398
- gap: 0.3em;
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
+ });
17399
17167
  }
17168
+ return jsx(Box, {
17169
+ ...boxProps,
17170
+ children: children
17171
+ });
17172
+ };
17400
17173
 
17401
- .navi_text_overflow_text {
17174
+ installImportMetaCss(import.meta);import.meta.css = /* css */`
17175
+ .navi_icon {
17176
+ display: inline-block;
17177
+ box-sizing: border-box;
17402
17178
  max-width: 100%;
17403
- text-overflow: ellipsis;
17404
- overflow: hidden;
17405
- }
17179
+ max-height: 100%;
17406
17180
 
17407
- .navi_custom_space {
17181
+ &[data-flow-inline] {
17182
+ width: 1em;
17183
+ height: 1em;
17184
+ }
17185
+ &[data-icon-char] {
17186
+ flex-grow: 0 !important;
17187
+ line-height: normal;
17188
+ }
17408
17189
  }
17409
17190
 
17410
- .navi_text_bold_wrapper {
17411
- position: relative;
17412
- display: inline-block;
17191
+ .navi_icon[data-interactive] {
17192
+ cursor: pointer;
17413
17193
  }
17414
- .navi_text_bold_clone {
17415
- font-weight: bold;
17194
+
17195
+ .navi_icon_char_slot {
17416
17196
  opacity: 0;
17197
+ cursor: default;
17198
+ user-select: none;
17417
17199
  }
17418
- .navi_text_bold_foreground {
17200
+ .navi_icon_foreground {
17419
17201
  position: absolute;
17420
17202
  inset: 0;
17421
17203
  }
17422
-
17423
- .navi_text_bold_background {
17424
- position: absolute;
17425
- top: 0;
17426
- left: 0;
17427
- color: currentColor;
17428
- font-weight: normal;
17429
- background: currentColor;
17430
- background-clip: text;
17431
- -webkit-background-clip: text;
17432
- transform-origin: center;
17433
- -webkit-text-fill-color: transparent;
17434
- opacity: 0;
17435
- }
17436
-
17437
- .navi_text[data-bold] {
17438
- .navi_text_bold_background {
17439
- opacity: 1;
17440
- }
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;
17441
17212
  }
17442
17213
 
17443
- .navi_text[data-bold-transition] {
17444
- .navi_text_bold_foreground {
17445
- transition-property: font-weight;
17446
- transition-duration: 0.3s;
17447
- transition-timing-function: ease;
17448
- }
17449
-
17450
- .navi_text_bold_background {
17451
- transition-property: opacity;
17452
- transition-duration: 0.3s;
17453
- transition-timing-function: ease;
17454
- }
17455
- }
17456
- `;
17457
- const REGULAR_SPACE = jsx("span", {
17458
- "data-navi-space": "",
17459
- children: " "
17460
- });
17461
- const CustomWidthSpace = ({
17462
- value
17463
- }) => {
17464
- return jsx("span", {
17465
- className: "navi_custom_space",
17466
- style: `padding-left: ${value}`,
17467
- children: "\u200B"
17468
- });
17469
- };
17470
- const applySpacingOnTextChildren = (children, spacing) => {
17471
- if (spacing === "pre" || spacing === "0" || spacing === 0) {
17472
- return children;
17473
- }
17474
- if (!children) {
17475
- return children;
17214
+ .navi_icon > svg,
17215
+ .navi_icon > img {
17216
+ width: 100%;
17217
+ height: 100%;
17218
+ backface-visibility: hidden;
17476
17219
  }
17477
- const childArray = toChildArray(children);
17478
- const childCount = childArray.length;
17479
- if (childCount <= 1) {
17480
- return children;
17220
+ .navi_icon[data-has-width] > svg,
17221
+ .navi_icon[data-has-width] > img {
17222
+ width: 100%;
17223
+ height: auto;
17481
17224
  }
17482
- let separator;
17483
- if (spacing === undefined) {
17484
- spacing = REGULAR_SPACE;
17485
- } else if (typeof spacing === "string") {
17486
- if (isSizeSpacingScaleKey(spacing)) {
17487
- separator = jsx(CustomWidthSpace, {
17488
- value: resolveSpacingSize(spacing)
17489
- });
17490
- } else if (hasCSSSizeUnit(spacing)) {
17491
- separator = jsx(CustomWidthSpace, {
17492
- value: resolveSpacingSize(spacing)
17493
- });
17494
- } else {
17495
- separator = spacing;
17496
- }
17497
- } else if (typeof spacing === "number") {
17498
- separator = jsx(CustomWidthSpace, {
17499
- value: spacing
17500
- });
17501
- } else {
17502
- separator = spacing;
17225
+ .navi_icon[data-has-height] > svg,
17226
+ .navi_icon[data-has-height] > img {
17227
+ width: auto;
17228
+ height: 100%;
17503
17229
  }
17504
- const childrenWithGap = [];
17505
- let i = 0;
17506
- while (true) {
17507
- const child = childArray[i];
17508
- childrenWithGap.push(child);
17509
- i++;
17510
- if (i === childCount) {
17511
- break;
17512
- }
17513
- const currentChild = childArray[i - 1];
17514
- const nextChild = childArray[i];
17515
- if (endsWithWhitespace(currentChild)) {
17516
- continue;
17517
- }
17518
- if (startsWithWhitespace(nextChild)) {
17519
- continue;
17520
- }
17521
- childrenWithGap.push(separator);
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%;
17522
17234
  }
17523
- return childrenWithGap;
17524
- };
17525
- const endsWithWhitespace = jsxChild => {
17526
- if (typeof jsxChild === "string") {
17527
- return /\s$/.test(jsxChild);
17235
+
17236
+ .navi_icon[data-icon-char] svg,
17237
+ .navi_icon[data-icon-char] img {
17238
+ width: 100%;
17239
+ height: 100%;
17528
17240
  }
17529
- return false;
17530
- };
17531
- const startsWithWhitespace = jsxChild => {
17532
- if (typeof jsxChild === "string") {
17533
- return /^\s/.test(jsxChild);
17241
+ .navi_icon[data-icon-char] svg {
17242
+ overflow: visible;
17534
17243
  }
17535
- return false;
17536
- };
17537
- const OverflowPinnedElementContext = createContext(null);
17538
- const Text = props => {
17539
- const {
17540
- overflowEllipsis,
17541
- ...rest
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
17542
17268
  } = props;
17543
- if (overflowEllipsis) {
17544
- return jsx(TextOverflow, {
17545
- ...rest
17546
- });
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;
17276
+ }
17277
+ } else {
17278
+ box = true;
17547
17279
  }
17548
- if (props.overflowPinned) {
17549
- return jsx(TextOverflowPinned, {
17550
- ...props
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
17551
17289
  });
17552
17290
  }
17553
- if (props.selectRange) {
17554
- return jsx(TextWithSelectRange, {
17555
- ...props
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
17556
17303
  });
17557
17304
  }
17558
- return jsx(TextBasic, {
17559
- ...props
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
+ })]
17560
17325
  });
17561
17326
  };
17562
- const TextOverflow = ({
17563
- noWrap,
17564
- children,
17565
- ...rest
17566
- }) => {
17567
- const [OverflowPinnedElement, setOverflowPinnedElement] = useState(null);
17568
- return jsx(Text, {
17569
- column: true,
17570
- as: "div",
17571
- nowWrap: noWrap,
17572
- pre: !noWrap
17573
- // For paragraph we prefer to keep lines and only hide unbreakable long sections
17574
- ,
17575
- preLine: rest.as === "p",
17576
- ...rest,
17577
- className: "navi_text_overflow",
17578
- expandX: true,
17579
- spacing: "pre",
17580
- children: jsxs("span", {
17581
- className: "navi_text_overflow_wrapper",
17582
- children: [jsx(OverflowPinnedElementContext.Provider, {
17583
- value: setOverflowPinnedElement,
17584
- children: jsx(Text, {
17585
- className: "navi_text_overflow_text",
17586
- children: children
17587
- })
17588
- }), OverflowPinnedElement]
17327
+
17328
+ const EmailSvg = () => {
17329
+ return jsxs("svg", {
17330
+ viewBox: "0 0 24 24",
17331
+ xmlns: "http://www.w3.org/2000/svg",
17332
+ children: [jsx("path", {
17333
+ d: "M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z",
17334
+ fill: "none",
17335
+ stroke: "currentColor",
17336
+ "stroke-width": "2"
17337
+ }), jsx("path", {
17338
+ d: "m2 6 8 5 2 1.5 2-1.5 8-5",
17339
+ fill: "none",
17340
+ stroke: "currentColor",
17341
+ "stroke-width": "2",
17342
+ "stroke-linecap": "round",
17343
+ "stroke-linejoin": "round"
17344
+ })]
17345
+ });
17346
+ };
17347
+
17348
+ const LinkBlankTargetSvg = () => {
17349
+ return jsx("svg", {
17350
+ viewBox: "0 0 24 24",
17351
+ xmlns: "http://www.w3.org/2000/svg",
17352
+ children: jsx("path", {
17353
+ d: "M10.0002 5H8.2002C7.08009 5 6.51962 5 6.0918 5.21799C5.71547 5.40973 5.40973 5.71547 5.21799 6.0918C5 6.51962 5 7.08009 5 8.2002V15.8002C5 16.9203 5 17.4801 5.21799 17.9079C5.40973 18.2842 5.71547 18.5905 6.0918 18.7822C6.5192 19 7.07899 19 8.19691 19H15.8031C16.921 19 17.48 19 17.9074 18.7822C18.2837 18.5905 18.5905 18.2839 18.7822 17.9076C19 17.4802 19 16.921 19 15.8031V14M20 9V4M20 4H15M20 4L13 11",
17354
+ stroke: "currentColor",
17355
+ fill: "none",
17356
+ "stroke-width": "2",
17357
+ "stroke-linecap": "round",
17358
+ "stroke-linejoin": "round"
17589
17359
  })
17590
17360
  });
17591
17361
  };
17592
- const TextOverflowPinned = ({
17593
- overflowPinned,
17594
- ...props
17595
- }) => {
17596
- const setOverflowPinnedElement = useContext(OverflowPinnedElementContext);
17597
- const text = jsx(Text, {
17598
- ...props
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
+ })
17599
17375
  });
17600
- if (!setOverflowPinnedElement) {
17601
- console.warn("<Text overflowPinned> declared outside a <Text overflowEllipsis>");
17602
- return text;
17603
- }
17604
- if (overflowPinned) {
17605
- setOverflowPinnedElement(text);
17606
- return null;
17607
- }
17608
- setOverflowPinnedElement(null);
17609
- return text;
17610
17376
  };
17611
- const TextWithSelectRange = ({
17612
- selectRange,
17613
- ...props
17614
- }) => {
17615
- const defaultRef = useRef();
17616
- const ref = props.ref || defaultRef;
17617
- useInitialTextSelection(ref, selectRange);
17618
- return jsx(Text, {
17619
- ref: ref,
17620
- ...props
17377
+
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
+ })
17621
17386
  });
17622
17387
  };
17623
- const TextBasic = ({
17624
- spacing = " ",
17625
- boldTransition,
17626
- boldStable,
17627
- preventBoldLayoutShift = boldTransition,
17628
- children,
17629
- ...rest
17630
- }) => {
17631
- const boxProps = {
17632
- "as": "span",
17633
- "data-bold-transition": boldTransition ? "" : undefined,
17634
- ...rest,
17635
- "baseClassName": withPropsClassName("navi_text", rest.baseClassName)
17636
- };
17637
- const shouldPreserveSpacing = rest.as === "pre" || rest.box || rest.column || rest.row;
17638
- if (shouldPreserveSpacing) {
17639
- boxProps.spacing = spacing;
17640
- } else {
17641
- children = applySpacingOnTextChildren(children, spacing);
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);
17399
+ } else {
17400
+ // If value becomes false, clear any pending timer and immediately set to false
17401
+ if (timerRef.current) {
17402
+ clearTimeout(timerRef.current);
17403
+ timerRef.current = null;
17404
+ }
17405
+ setDebouncedTrue(false);
17406
+ }
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";
17642
17432
  }
17643
- if (boldStable) {
17644
- const {
17645
- bold
17646
- } = boxProps;
17647
- return jsxs(Box, {
17648
- ...boxProps,
17649
- bold: undefined,
17650
- "data-bold": bold ? "" : undefined,
17651
- "data-has-absolute-child": "",
17652
- children: [jsx("span", {
17653
- className: "navi_text_bold_background",
17654
- "aria-hidden": "true",
17655
- children: children
17656
- }), children]
17657
- });
17433
+ if (connection) {
17434
+ const effectiveType = connection.effectiveType;
17435
+ if (effectiveType) {
17436
+ return effectiveType; // "slow-2g", "2g", "3g", "4g", "5g"
17437
+ }
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
17445
+ }
17658
17446
  }
17659
- if (preventBoldLayoutShift) {
17660
- const alignX = rest.alignX || rest.align || "start";
17447
+ return "3g";
17448
+ };
17661
17449
 
17662
- // La technique consiste a avoid un double gras qui force une taille
17663
- // et la version light par dessus en position absolute
17664
- // on la centre aussi pour donner l'impression que le gras s'applique depuis le centre
17665
- // ne fonctionne que sur une seul ligne de texte (donc lorsque noWrap est actif)
17666
- // on pourrait auto-active cela sur une prop genre boldCanChange
17667
- return jsx(Box, {
17668
- ...boxProps,
17669
- children: jsxs("span", {
17670
- className: "navi_text_bold_wrapper",
17671
- children: [jsx("span", {
17672
- className: "navi_text_bold_clone",
17673
- "aria-hidden": "true",
17674
- children: children
17675
- }), jsx("span", {
17676
- className: "navi_text_bold_foreground",
17677
- "data-align": alignX,
17678
- children: children
17679
- })]
17680
- })
17450
+ const updateNetworkSpeed = () => {
17451
+ networkSpeedSignal.value = getNetworkSpeed();
17452
+ };
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);
17681
17465
  });
17682
17466
  }
17683
- return jsx(Box, {
17684
- ...boxProps,
17685
- children: children
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);
17686
17482
  });
17687
- };
17688
17483
 
17689
- installImportMetaCss(import.meta);import.meta.css = /* css */`
17690
- .navi_icon {
17691
- display: inline-block;
17692
- box-sizing: border-box;
17693
- max-width: 100%;
17694
- max-height: 100%;
17484
+ // ✅ 4. Vérifier lors de la reprise de connexion
17485
+ const handleOnline = () => {
17486
+ updateNetworkSpeed();
17487
+ };
17695
17488
 
17696
- &[data-flow-inline] {
17697
- width: 1em;
17698
- height: 1em;
17699
- }
17700
- &[data-icon-char] {
17701
- flex-grow: 0 !important;
17702
- line-height: normal;
17703
- }
17704
- }
17489
+ window.addEventListener("online", handleOnline);
17490
+ cleanupFunctions.push(() => {
17491
+ window.removeEventListener("online", handleOnline);
17492
+ });
17705
17493
 
17706
- .navi_icon[data-interactive] {
17707
- cursor: pointer;
17708
- }
17494
+ // Cleanup global
17495
+ return () => {
17496
+ cleanupFunctions.forEach((cleanup) => cleanup());
17497
+ };
17498
+ };
17499
+ setupNetworkMonitoring();
17709
17500
 
17710
- .navi_icon_char_slot {
17711
- opacity: 0;
17712
- cursor: default;
17713
- user-select: none;
17714
- }
17715
- .navi_icon_foreground {
17716
- position: absolute;
17717
- inset: 0;
17718
- }
17719
- .navi_icon_foreground > .navi_text {
17501
+ installImportMetaCss(import.meta);import.meta.css = /* css */`
17502
+ .navi_rectangle_loading {
17503
+ position: relative;
17720
17504
  display: flex;
17721
- aspect-ratio: 1 / 1;
17722
- min-width: 0;
17723
- height: 100%;
17724
- max-height: 1em;
17725
- align-items: center;
17726
- justify-content: center;
17727
- }
17728
-
17729
- .navi_icon > svg,
17730
- .navi_icon > img {
17731
17505
  width: 100%;
17732
17506
  height: 100%;
17733
- backface-visibility: hidden;
17734
- }
17735
- .navi_icon[data-has-width] > svg,
17736
- .navi_icon[data-has-width] > img {
17737
- width: 100%;
17738
- height: auto;
17507
+ opacity: 0;
17739
17508
  }
17740
- .navi_icon[data-has-height] > svg,
17741
- .navi_icon[data-has-height] > img {
17742
- width: auto;
17743
- height: 100%;
17509
+
17510
+ .navi_rectangle_loading[data-visible] {
17511
+ opacity: 1;
17744
17512
  }
17745
- .navi_icon[data-has-width][data-has-height] > svg,
17746
- .navi_icon[data-has-width][data-has-height] > img {
17747
- width: 100%;
17748
- height: 100%;
17513
+ `;
17514
+ const RectangleLoading = ({
17515
+ shouldShowSpinner,
17516
+ color = "currentColor",
17517
+ radius = 0,
17518
+ size = 2
17519
+ }) => {
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
+ })
17570
+ });
17571
+ };
17572
+ const RectangleLoadingSvg = ({
17573
+ width,
17574
+ height,
17575
+ color,
17576
+ radius,
17577
+ trailColor = "transparent",
17578
+ strokeWidth
17579
+ }) => {
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
+ `;
17607
+ } else {
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
+ `;
17749
17623
  }
17750
17624
 
17751
- .navi_icon[data-icon-char] svg,
17752
- .navi_icon[data-icon-char] img {
17753
- width: 100%;
17754
- height: 100%;
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"
17682
+ })
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
+ })]
17694
+ });
17695
+ };
17696
+
17697
+ installImportMetaCss(import.meta);import.meta.css = /* css */`
17698
+ .navi_loading_rectangle_wrapper {
17699
+ position: absolute;
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;
17755
17707
  }
17756
- .navi_icon[data-icon-char] svg {
17757
- overflow: visible;
17708
+ .navi_loading_rectangle_wrapper[data-visible] {
17709
+ opacity: 1;
17758
17710
  }
17759
17711
  `;
17760
- const Icon = ({
17761
- href,
17762
- children,
17763
- className,
17764
- charWidth = 1,
17765
- // 0 (zéro) is the real char width
17766
- // but 2 zéros gives too big icons
17767
- // while 1 "W" gives a nice result
17768
- baseChar = "W",
17769
- decorative,
17770
- onClick,
17771
- ...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
17772
17723
  }) => {
17773
- const innerChildren = href ? jsx("svg", {
17774
- width: "100%",
17775
- height: "100%",
17776
- children: jsx("use", {
17777
- href: href
17778
- })
17779
- }) : children;
17780
- let {
17781
- box,
17782
- width,
17783
- height
17784
- } = props;
17785
- if (width === "auto") width = undefined;
17786
- if (height === "auto") height = undefined;
17787
- const hasExplicitWidth = width !== undefined;
17788
- const hasExplicitHeight = height !== undefined;
17789
- if (!hasExplicitWidth && !hasExplicitHeight) {
17790
- if (decorative === undefined) {
17791
- decorative = true;
17724
+ if (containerRef) {
17725
+ const container = containerRef.current;
17726
+ if (!container) {
17727
+ return children;
17792
17728
  }
17793
- } else {
17794
- box = true;
17795
- }
17796
- const ariaProps = decorative ? {
17797
- "aria-hidden": "true"
17798
- } : {};
17799
- if (typeof children === "string") {
17800
- return jsx(Text, {
17801
- ...props,
17802
- ...ariaProps,
17803
- "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,
17804
17738
  children: children
17805
- });
17739
+ }), container);
17806
17740
  }
17807
- if (box) {
17808
- return jsx(Box, {
17809
- square: true,
17810
- ...props,
17811
- ...ariaProps,
17812
- box: box,
17813
- baseClassName: "navi_icon",
17814
- "data-has-width": hasExplicitWidth ? "" : undefined,
17815
- "data-has-height": hasExplicitHeight ? "" : undefined,
17816
- "data-interactive": onClick ? "" : undefined,
17817
- onClick: onClick,
17818
- children: innerChildren
17819
- });
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;
17820
17909
  }
17821
- const invisibleText = baseChar.repeat(charWidth);
17822
- return jsxs(Text, {
17823
- ...props,
17824
- ...ariaProps,
17825
- className: withPropsClassName("navi_icon", className),
17826
- spacing: "pre",
17827
- "data-icon-char": "",
17828
- "data-has-width": hasExplicitWidth ? "" : undefined,
17829
- "data-has-height": hasExplicitHeight ? "" : undefined,
17830
- "data-interactive": onClick ? "" : undefined,
17831
- 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, {
17832
17919
  children: [jsx("span", {
17833
- className: "navi_icon_char_slot",
17834
- "aria-hidden": "true",
17835
- children: invisibleText
17836
- }), jsx(Text, {
17837
- className: "navi_icon_foreground",
17838
- spacing: "pre",
17839
- children: innerChildren
17840
- })]
17841
- });
17842
- };
17843
-
17844
- const LinkBlankTargetSvg = () => {
17845
- return jsx("svg", {
17846
- viewBox: "0 0 24 24",
17847
- xmlns: "http://www.w3.org/2000/svg",
17848
- children: jsx("path", {
17849
- 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",
17850
- stroke: "currentColor",
17851
- fill: "none",
17852
- "stroke-width": "2",
17853
- "stroke-linecap": "round",
17854
- "stroke-linejoin": "round"
17855
- })
17856
- });
17857
- };
17858
- const LinkAnchorSvg = () => {
17859
- return jsx("svg", {
17860
- viewBox: "0 0 24 24",
17861
- xmlns: "http://www.w3.org/2000/svg",
17862
- children: jsxs("g", {
17863
- children: [jsx("path", {
17864
- 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",
17865
- fill: "currentColor"
17866
- }), jsx("path", {
17867
- 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",
17868
- fill: "currentColor"
17869
- })]
17870
- })
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]
17871
17936
  });
17872
17937
  };
17873
17938
 
@@ -20036,16 +20101,6 @@ const LinkPlain = props => {
20036
20101
  })]
20037
20102
  });
20038
20103
  };
20039
- const PhoneSvg = () => {
20040
- return jsx("svg", {
20041
- viewBox: "0 0 24 24",
20042
- xmlns: "http://www.w3.org/2000/svg",
20043
- children: jsx("path", {
20044
- 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",
20045
- fill: "currentColor"
20046
- })
20047
- });
20048
- };
20049
20104
  const SmsSvg = () => {
20050
20105
  return jsx("svg", {
20051
20106
  viewBox: "0 0 24 24",
@@ -20056,25 +20111,6 @@ const SmsSvg = () => {
20056
20111
  })
20057
20112
  });
20058
20113
  };
20059
- const EmailSvg = () => {
20060
- return jsxs("svg", {
20061
- viewBox: "0 0 24 24",
20062
- xmlns: "http://www.w3.org/2000/svg",
20063
- children: [jsx("path", {
20064
- 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",
20065
- fill: "none",
20066
- stroke: "currentColor",
20067
- "stroke-width": "2"
20068
- }), jsx("path", {
20069
- d: "m2 6 8 5 2 1.5 2-1.5 8-5",
20070
- fill: "none",
20071
- stroke: "currentColor",
20072
- "stroke-width": "2",
20073
- "stroke-linecap": "round",
20074
- "stroke-linejoin": "round"
20075
- })]
20076
- });
20077
- };
20078
20114
  const GithubSvg = () => {
20079
20115
  return jsx("svg", {
20080
20116
  viewBox: "0 0 24 24",
@@ -22451,6 +22487,15 @@ const InputRangeWithAction = props => {
22451
22487
  });
22452
22488
  };
22453
22489
 
22490
+ const SearchSvg = () => jsx("svg", {
22491
+ viewBox: "0 0 24 24",
22492
+ xmlns: "http://www.w3.org/2000/svg",
22493
+ children: jsx("path", {
22494
+ d: "M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z",
22495
+ fill: "currentColor"
22496
+ })
22497
+ });
22498
+
22454
22499
  installImportMetaCss(import.meta);import.meta.css = /* css */`
22455
22500
  @layer navi {
22456
22501
  .navi_input {
@@ -22514,29 +22559,73 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
22514
22559
  --x-background-color: var(--background-color);
22515
22560
  --x-color: var(--color);
22516
22561
  --x-placeholder-color: var(--placeholder-color);
22517
- }
22518
22562
 
22519
- .navi_input .navi_native_input {
22520
- box-sizing: border-box;
22521
- padding-top: var(--padding-top, var(--padding-y, var(--padding, 1px)));
22522
- padding-right: var(--padding-right, var(--padding-x, var(--padding, 2px)));
22523
- padding-bottom: var(
22524
- --padding-bottom,
22525
- var(--padding-y, var(--padding, 1px))
22526
- );
22527
- padding-left: var(--padding-left, var(--padding-x, var(--padding, 2px)));
22528
- color: var(--x-color);
22529
- background-color: var(--x-background-color);
22530
- border-width: var(--x-outer-width);
22531
- border-width: var(--x-outer-width);
22532
- border-style: solid;
22533
- border-color: transparent;
22534
- border-radius: var(--x-border-radius);
22535
- outline-width: var(--x-border-width);
22536
- outline-style: solid;
22537
- outline-color: var(--x-border-color);
22538
- outline-offset: calc(-1 * (var(--x-border-width)));
22563
+ .navi_native_input {
22564
+ box-sizing: border-box;
22565
+ padding-top: var(--padding-top, var(--padding-y, var(--padding, 1px)));
22566
+ padding-right: var(
22567
+ --padding-right,
22568
+ var(--padding-x, var(--padding, 2px))
22569
+ );
22570
+ padding-bottom: var(
22571
+ --padding-bottom,
22572
+ var(--padding-y, var(--padding, 1px))
22573
+ );
22574
+ padding-left: var(--padding-left, var(--padding-x, var(--padding, 2px)));
22575
+ color: var(--x-color);
22576
+ background-color: var(--x-background-color);
22577
+ border-width: var(--x-outer-width);
22578
+ border-width: var(--x-outer-width);
22579
+ border-style: solid;
22580
+ border-color: transparent;
22581
+ border-radius: var(--x-border-radius);
22582
+ outline-width: var(--x-border-width);
22583
+ outline-style: solid;
22584
+ outline-color: var(--x-border-color);
22585
+ outline-offset: calc(-1 * (var(--x-border-width)));
22586
+
22587
+ &[type="search"] {
22588
+ -webkit-appearance: textfield;
22589
+
22590
+ &::-webkit-search-cancel-button {
22591
+ display: none;
22592
+ }
22593
+ }
22594
+ }
22595
+
22596
+ .navi_start_icon_label {
22597
+ position: absolute;
22598
+ top: 0;
22599
+ bottom: 0;
22600
+ left: 0.25em;
22601
+ }
22602
+ .navi_end_icon_label {
22603
+ position: absolute;
22604
+ top: 0;
22605
+ right: 0.25em;
22606
+ bottom: 0;
22607
+ opacity: 0;
22608
+ pointer-events: none;
22609
+ }
22610
+ &[data-has-value] {
22611
+ .navi_end_icon_label {
22612
+ opacity: 1;
22613
+ pointer-events: auto;
22614
+ }
22615
+ }
22616
+
22617
+ &[data-start-icon] {
22618
+ .navi_native_input {
22619
+ padding-left: 20px;
22620
+ }
22621
+ }
22622
+ &[data-end-icon] {
22623
+ .navi_native_input {
22624
+ padding-right: 20px;
22625
+ }
22626
+ }
22539
22627
  }
22628
+
22540
22629
  .navi_input .navi_native_input::placeholder {
22541
22630
  color: var(--x-placeholder-color);
22542
22631
  }
@@ -22615,7 +22704,52 @@ const InputStyleCSSVars = {
22615
22704
  color: "--color-disabled"
22616
22705
  }
22617
22706
  };
22618
- const InputPseudoClasses = [":hover", ":active", ":focus", ":focus-visible", ":read-only", ":disabled", ":-navi-loading"];
22707
+ const InputPseudoClasses = [":hover", ":active", ":focus", ":focus-visible", ":read-only", ":disabled", ":-navi-loading", ":navi-has-value"];
22708
+ Object.assign(PSEUDO_CLASSES, {
22709
+ ":navi-has-value": {
22710
+ attribute: "data-has-value",
22711
+ setup: (el, callback) => {
22712
+ const onValueChange = () => {
22713
+ callback();
22714
+ };
22715
+
22716
+ // Standard user input (typing)
22717
+ el.addEventListener("input", onValueChange);
22718
+ // Autocomplete, programmatic changes, form restoration
22719
+ el.addEventListener("change", onValueChange);
22720
+ // Form reset - need to check the form
22721
+ const form = el.form;
22722
+ const onFormReset = () => {
22723
+ // Form reset happens asynchronously, check value after reset completes
22724
+ setTimeout(onValueChange, 0);
22725
+ };
22726
+ if (form) {
22727
+ form.addEventListener("reset", onFormReset);
22728
+ }
22729
+
22730
+ // Paste events (some browsers need special handling)
22731
+ el.addEventListener("paste", onValueChange);
22732
+ // Focus events to catch programmatic changes that don't fire other events
22733
+ // (like when value is set before user interaction)
22734
+ el.addEventListener("focus", onValueChange);
22735
+ return () => {
22736
+ el.removeEventListener("input", onValueChange);
22737
+ el.removeEventListener("change", onValueChange);
22738
+ el.removeEventListener("paste", onValueChange);
22739
+ el.removeEventListener("focus", onValueChange);
22740
+ if (form) {
22741
+ form.removeEventListener("reset", onFormReset);
22742
+ }
22743
+ };
22744
+ },
22745
+ test: el => {
22746
+ if (el.value === "") {
22747
+ return false;
22748
+ }
22749
+ return true;
22750
+ }
22751
+ }
22752
+ });
22619
22753
  const InputPseudoElements = ["::-navi-loader"];
22620
22754
  const InputTextualBasic = props => {
22621
22755
  const contextReadOnly = useContext(ReadOnlyContext);
@@ -22634,6 +22768,8 @@ const InputTextualBasic = props => {
22634
22768
  autoFocus,
22635
22769
  autoFocusVisible,
22636
22770
  autoSelect,
22771
+ icon,
22772
+ cancelButton = type === "search",
22637
22773
  ...rest
22638
22774
  } = props;
22639
22775
  const defaultRef = useRef();
@@ -22650,10 +22786,13 @@ const InputTextualBasic = props => {
22650
22786
  });
22651
22787
  const remainingProps = useConstraints(ref, rest);
22652
22788
  const innerOnInput = useStableCallback(onInput);
22789
+ const autoId = useId();
22790
+ const innerId = rest.id || autoId;
22653
22791
  const renderInput = inputProps => {
22654
22792
  return jsx(Box, {
22655
22793
  ...inputProps,
22656
22794
  as: "input",
22795
+ id: innerId,
22657
22796
  ref: ref,
22658
22797
  type: type,
22659
22798
  "data-value": uiState,
@@ -22684,7 +22823,19 @@ const InputTextualBasic = props => {
22684
22823
  baseClassName: "navi_native_input"
22685
22824
  });
22686
22825
  };
22687
- const renderInputMemoized = useCallback(renderInput, [type, uiState, innerValue, innerOnInput]);
22826
+ const renderInputMemoized = useCallback(renderInput, [type, uiState, innerValue, innerOnInput, innerId]);
22827
+ let innerIcon;
22828
+ if (icon === undefined) {
22829
+ if (type === "search") {
22830
+ innerIcon = jsx(SearchSvg, {});
22831
+ } else if (type === "email") {
22832
+ innerIcon = jsx(EmailSvg, {});
22833
+ } else if (type === "tel") {
22834
+ innerIcon = jsx(PhoneSvg, {});
22835
+ }
22836
+ } else {
22837
+ innerIcon = icon;
22838
+ }
22688
22839
  return jsxs(Box, {
22689
22840
  as: "span",
22690
22841
  box: true,
@@ -22700,13 +22851,37 @@ const InputTextualBasic = props => {
22700
22851
  pseudoClasses: InputPseudoClasses,
22701
22852
  pseudoElements: InputPseudoElements,
22702
22853
  hasChildFunction: true,
22854
+ "data-start-icon": innerIcon ? "" : undefined,
22855
+ "data-end-icon": cancelButton ? "" : undefined,
22703
22856
  ...remainingProps,
22704
22857
  ref: undefined,
22705
22858
  children: [jsx(LoaderBackground, {
22706
22859
  loading: innerLoading,
22707
22860
  color: "var(--loader-color)",
22708
22861
  inset: -1
22709
- }), renderInputMemoized]
22862
+ }), innerIcon && jsx(Icon, {
22863
+ as: "label",
22864
+ htmlFor: innerId,
22865
+ className: "navi_start_icon_label",
22866
+ alignY: "center",
22867
+ color: "rgba(28, 43, 52, 0.5)",
22868
+ children: innerIcon
22869
+ }), renderInputMemoized, cancelButton && jsx(Icon, {
22870
+ as: "label",
22871
+ htmlFor: innerId,
22872
+ className: "navi_end_icon_label",
22873
+ alignY: "center",
22874
+ color: "rgba(28, 43, 52, 0.5)",
22875
+ onMousedown: e => {
22876
+ e.preventDefault(); // keep focus on the button
22877
+ },
22878
+ onClick: () => {
22879
+ uiStateController.setUIState("", {
22880
+ trigger: "cancel_button"
22881
+ });
22882
+ },
22883
+ children: jsx(CloseSvg, {})
22884
+ })]
22710
22885
  });
22711
22886
  };
22712
22887
  const InputTextualWithAction = props => {
@@ -27221,6 +27396,69 @@ const Address = ({
27221
27396
  });
27222
27397
  };
27223
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
+
27224
27462
  const CSS_VAR_NAME = "--x-color-contrasting";
27225
27463
 
27226
27464
  const useContrastingColor = (ref, backgroundElementSelector) => {
@@ -27388,10 +27626,11 @@ const BadgeCountCircle = ({
27388
27626
  ref,
27389
27627
  charCount,
27390
27628
  hasOverflow,
27629
+ loading,
27391
27630
  children,
27392
27631
  ...props
27393
27632
  }) => {
27394
- return jsxs(Text, {
27633
+ return jsx(Text, {
27395
27634
  ref: ref,
27396
27635
  className: "navi_badge_count",
27397
27636
  "data-circle": "",
@@ -27403,18 +27642,20 @@ const BadgeCountCircle = ({
27403
27642
  ...props,
27404
27643
  styleCSSVars: BadgeStyleCSSVars,
27405
27644
  spacing: "pre",
27406
- children: [jsx("span", {
27407
- style: "user-select: none",
27408
- children: "\u200B"
27409
- }), jsx("span", {
27410
- className: "navi_badge_count_frame"
27411
- }), jsx("span", {
27412
- className: "navi_badge_count_text",
27413
- children: children
27414
- }), jsx("span", {
27415
- style: "user-select: none",
27416
- children: "\u200B"
27417
- })]
27645
+ children: loading ? jsx(LoadingDots, {}) : jsxs(Fragment, {
27646
+ children: [jsx("span", {
27647
+ style: "user-select: none",
27648
+ children: "\u200B"
27649
+ }), jsx("span", {
27650
+ className: "navi_badge_count_frame"
27651
+ }), jsx("span", {
27652
+ className: "navi_badge_count_text",
27653
+ children: children
27654
+ }), jsx("span", {
27655
+ style: "user-select: none",
27656
+ children: "\u200B"
27657
+ })]
27658
+ })
27418
27659
  });
27419
27660
  };
27420
27661
  const BadgeCountEllipse = ({
@@ -28496,15 +28737,6 @@ const HomeSvg = () => jsx("svg", {
28496
28737
  })
28497
28738
  });
28498
28739
 
28499
- const SearchSvg = () => jsx("svg", {
28500
- viewBox: "0 0 24 24",
28501
- xmlns: "http://www.w3.org/2000/svg",
28502
- children: jsx("path", {
28503
- d: "M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z",
28504
- fill: "currentColor"
28505
- })
28506
- });
28507
-
28508
28740
  const SettingsSvg = () => jsx("svg", {
28509
28741
  viewBox: "0 0 24 24",
28510
28742
  xmlns: "http://www.w3.org/2000/svg",