@jsenv/navi 0.17.2 → 0.17.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/jsenv_navi.js +1057 -877
- package/dist/jsenv_navi.js.map +73 -62
- package/package.json +1 -1
package/dist/jsenv_navi.js
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import { installImportMetaCss } from "./jsenv_navi_side_effects.js";
|
|
2
|
+
import { isValidElement, createContext, toChildArray, render, createRef, cloneElement } from "preact";
|
|
2
3
|
import { useErrorBoundary, useLayoutEffect, useEffect, useCallback, useRef, useState, useContext, useMemo, useImperativeHandle, useId } from "preact/hooks";
|
|
3
4
|
import { jsxs, jsx, Fragment } from "preact/jsx-runtime";
|
|
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,
|
|
5
|
+
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, initFocusGroup, elementIsFocusable, pickLightOrDark, resolveColorLuminance, dragAfterThreshold, getScrollContainer, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement } from "@jsenv/dom";
|
|
5
6
|
import { prefixFirstAndIndentRemainingLines } from "@jsenv/humanize";
|
|
6
7
|
import { effect, signal, computed, batch, useSignal } from "@preact/signals";
|
|
7
8
|
import { createValidity } from "@jsenv/validity";
|
|
8
|
-
import { createContext, toChildArray, render, isValidElement, createRef, cloneElement } from "preact";
|
|
9
9
|
import { createPortal, forwardRef } from "preact/compat";
|
|
10
10
|
|
|
11
|
+
const IDLE = { id: "idle" };
|
|
12
|
+
const RUNNING = { id: "running" };
|
|
13
|
+
const ABORTED = { id: "aborted" };
|
|
14
|
+
const FAILED = { id: "failed" };
|
|
15
|
+
const COMPLETED = { id: "completed" };
|
|
16
|
+
|
|
11
17
|
const actionPrivatePropertiesWeakMap = new WeakMap();
|
|
12
18
|
const getActionPrivateProperties = (action) => {
|
|
13
19
|
const actionPrivateProperties = actionPrivatePropertiesWeakMap.get(action);
|
|
@@ -20,11 +26,19 @@ const setActionPrivateProperties = (action, properties) => {
|
|
|
20
26
|
actionPrivatePropertiesWeakMap.set(action, properties);
|
|
21
27
|
};
|
|
22
28
|
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
const
|
|
29
|
+
const getActionStatus = (action) => {
|
|
30
|
+
const { runningStateSignal, errorSignal, computedDataSignal } =
|
|
31
|
+
getActionPrivateProperties(action);
|
|
32
|
+
const runningState = runningStateSignal.value;
|
|
33
|
+
const idle = runningState === IDLE;
|
|
34
|
+
const aborted = runningState === ABORTED;
|
|
35
|
+
const error = errorSignal.value;
|
|
36
|
+
const loading = runningState === RUNNING;
|
|
37
|
+
const completed = runningState === COMPLETED;
|
|
38
|
+
const data = computedDataSignal.value;
|
|
39
|
+
|
|
40
|
+
return { idle, loading, aborted, error, completed, data };
|
|
41
|
+
};
|
|
28
42
|
|
|
29
43
|
const useActionStatus = (action) => {
|
|
30
44
|
if (!action) {
|
|
@@ -96,6 +110,23 @@ const ActionRenderer = ({
|
|
|
96
110
|
children,
|
|
97
111
|
disabled
|
|
98
112
|
}) => {
|
|
113
|
+
if (action === undefined) {
|
|
114
|
+
throw new Error("ActionRenderer requires an action to render, but none was provided.");
|
|
115
|
+
}
|
|
116
|
+
let renderBranches;
|
|
117
|
+
if (typeof children === "function") {
|
|
118
|
+
renderBranches = {
|
|
119
|
+
completed: children
|
|
120
|
+
};
|
|
121
|
+
} else if (isValidElement(children)) {
|
|
122
|
+
renderBranches = {
|
|
123
|
+
always: () => children
|
|
124
|
+
};
|
|
125
|
+
} else if (isPlainObject$1(children)) {
|
|
126
|
+
renderBranches = children;
|
|
127
|
+
} else {
|
|
128
|
+
renderBranches = {};
|
|
129
|
+
}
|
|
99
130
|
const {
|
|
100
131
|
idle: renderIdle = renderIdleDefault,
|
|
101
132
|
loading: renderLoading = renderLoadingDefault,
|
|
@@ -103,15 +134,7 @@ const ActionRenderer = ({
|
|
|
103
134
|
error: renderError = renderErrorDefault,
|
|
104
135
|
completed: renderCompleted,
|
|
105
136
|
always: renderAlways
|
|
106
|
-
} =
|
|
107
|
-
completed: children
|
|
108
|
-
} : children || {};
|
|
109
|
-
if (disabled) {
|
|
110
|
-
return null;
|
|
111
|
-
}
|
|
112
|
-
if (action === undefined) {
|
|
113
|
-
throw new Error("ActionRenderer requires an action to render, but none was provided.");
|
|
114
|
-
}
|
|
137
|
+
} = renderBranches;
|
|
115
138
|
const {
|
|
116
139
|
idle,
|
|
117
140
|
loading,
|
|
@@ -142,7 +165,9 @@ const ActionRenderer = ({
|
|
|
142
165
|
actionUIRenderedPromiseWeakMap.delete(action);
|
|
143
166
|
};
|
|
144
167
|
}, [action]);
|
|
145
|
-
|
|
168
|
+
if (disabled) {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
146
171
|
// If renderAlways is provided, it wins and handles all rendering
|
|
147
172
|
if (renderAlways) {
|
|
148
173
|
return renderAlways({
|
|
@@ -205,6 +230,16 @@ const useUIRenderedPromise = action => {
|
|
|
205
230
|
actionUIRenderedPromiseWeakMap.set(action, promise);
|
|
206
231
|
return promise;
|
|
207
232
|
};
|
|
233
|
+
const isPlainObject$1 = obj => {
|
|
234
|
+
if (typeof obj !== "object" || obj === null) {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
let proto = obj;
|
|
238
|
+
while (Object.getPrototypeOf(proto) !== null) {
|
|
239
|
+
proto = Object.getPrototypeOf(proto);
|
|
240
|
+
}
|
|
241
|
+
return Object.getPrototypeOf(obj) === proto || Object.getPrototypeOf(obj) === null;
|
|
242
|
+
};
|
|
208
243
|
|
|
209
244
|
const isSignal = (value) => {
|
|
210
245
|
return getSignalType(value) !== null;
|
|
@@ -1408,7 +1443,7 @@ const createAction = (callback, rootOptions = {}) => {
|
|
|
1408
1443
|
}
|
|
1409
1444
|
|
|
1410
1445
|
// ✅ CAS 2: Objet -> vérifier s'il contient des signals
|
|
1411
|
-
if (newParamsOrSignal
|
|
1446
|
+
if (isPlainObject(newParamsOrSignal)) {
|
|
1412
1447
|
const staticParams = {};
|
|
1413
1448
|
const signalMap = new Map();
|
|
1414
1449
|
|
|
@@ -1459,7 +1494,7 @@ const createAction = (callback, rootOptions = {}) => {
|
|
|
1459
1494
|
return createActionProxyFromSignal(action, paramsSignal, options);
|
|
1460
1495
|
}
|
|
1461
1496
|
|
|
1462
|
-
// ✅ CAS 3: Primitive -> action enfant
|
|
1497
|
+
// ✅ CAS 3: Primitive or objects like DOMEvents etc -> action enfant
|
|
1463
1498
|
return createChildAction({
|
|
1464
1499
|
params: newParamsOrSignal,
|
|
1465
1500
|
...options,
|
|
@@ -1737,7 +1772,7 @@ const createAction = (callback, rootOptions = {}) => {
|
|
|
1737
1772
|
if (isPrerun && abortSignal.aborted) {
|
|
1738
1773
|
prerunProtectionRegistry.unprotect(action);
|
|
1739
1774
|
}
|
|
1740
|
-
onAbort(e, action);
|
|
1775
|
+
onAbort?.(e, action);
|
|
1741
1776
|
return e;
|
|
1742
1777
|
}
|
|
1743
1778
|
if (e.name === "AbortError") {
|
|
@@ -2114,6 +2149,19 @@ const generateActionName = (name, params) => {
|
|
|
2114
2149
|
return `${name}${argsString}`;
|
|
2115
2150
|
};
|
|
2116
2151
|
|
|
2152
|
+
const isPlainObject = (obj) => {
|
|
2153
|
+
if (typeof obj !== "object" || obj === null) {
|
|
2154
|
+
return false;
|
|
2155
|
+
}
|
|
2156
|
+
let proto = obj;
|
|
2157
|
+
while (Object.getPrototypeOf(proto) !== null) {
|
|
2158
|
+
proto = Object.getPrototypeOf(proto);
|
|
2159
|
+
}
|
|
2160
|
+
return (
|
|
2161
|
+
Object.getPrototypeOf(obj) === proto || Object.getPrototypeOf(obj) === null
|
|
2162
|
+
);
|
|
2163
|
+
};
|
|
2164
|
+
|
|
2117
2165
|
const useActionData = (action) => {
|
|
2118
2166
|
if (!action) {
|
|
2119
2167
|
return undefined;
|
|
@@ -10188,6 +10236,12 @@ const setupPatterns = (patternDefinitions) => {
|
|
|
10188
10236
|
|
|
10189
10237
|
// Phase 1: Create all pattern objects
|
|
10190
10238
|
for (const [key, urlPatternRaw] of Object.entries(patternDefinitions)) {
|
|
10239
|
+
if (typeof urlPatternRaw !== "string") {
|
|
10240
|
+
throw new TypeError(
|
|
10241
|
+
`expects a route pattern string, but received ${urlPatternRaw} for route "${key}".`,
|
|
10242
|
+
);
|
|
10243
|
+
}
|
|
10244
|
+
|
|
10191
10245
|
// Create the unified pattern object
|
|
10192
10246
|
const pattern = createRoutePattern(urlPatternRaw);
|
|
10193
10247
|
|
|
@@ -10402,17 +10456,29 @@ const setupRoutes = (routeDefinition) => {
|
|
|
10402
10456
|
};
|
|
10403
10457
|
|
|
10404
10458
|
const useRouteStatus = (route) => {
|
|
10405
|
-
const {
|
|
10459
|
+
const {
|
|
10460
|
+
urlSignal,
|
|
10461
|
+
matchingSignal,
|
|
10462
|
+
paramsSignal,
|
|
10463
|
+
visitedSignal,
|
|
10464
|
+
actionStatusSignal,
|
|
10465
|
+
} = route;
|
|
10406
10466
|
const url = urlSignal.value;
|
|
10407
10467
|
const matching = matchingSignal.value;
|
|
10408
10468
|
const params = paramsSignal.value;
|
|
10409
10469
|
const visited = visitedSignal.value;
|
|
10470
|
+
const { loading, aborted, error, completed, data } = actionStatusSignal.value;
|
|
10410
10471
|
|
|
10411
10472
|
return {
|
|
10412
10473
|
url,
|
|
10413
10474
|
matching,
|
|
10414
10475
|
params,
|
|
10415
10476
|
visited,
|
|
10477
|
+
loading,
|
|
10478
|
+
aborted,
|
|
10479
|
+
error,
|
|
10480
|
+
completed,
|
|
10481
|
+
data,
|
|
10416
10482
|
};
|
|
10417
10483
|
};
|
|
10418
10484
|
|
|
@@ -10464,6 +10530,8 @@ const updateRoutes = (
|
|
|
10464
10530
|
// state
|
|
10465
10531
|
} = {},
|
|
10466
10532
|
) => {
|
|
10533
|
+
const returnValue = {};
|
|
10534
|
+
|
|
10467
10535
|
const routeMatchInfoSet = new Set();
|
|
10468
10536
|
for (const route of routeSet) {
|
|
10469
10537
|
const routePrivateProperties = getRoutePrivateProperties(route);
|
|
@@ -10511,279 +10579,283 @@ const updateRoutes = (
|
|
|
10511
10579
|
});
|
|
10512
10580
|
}
|
|
10513
10581
|
|
|
10514
|
-
|
|
10515
|
-
const matchingRouteSet = new Set();
|
|
10516
|
-
batch(() => {
|
|
10517
|
-
for (const {
|
|
10518
|
-
route,
|
|
10519
|
-
routePrivateProperties,
|
|
10520
|
-
newMatching,
|
|
10521
|
-
newParams,
|
|
10522
|
-
} of routeMatchInfoSet) {
|
|
10523
|
-
const { updateStatus } = routePrivateProperties;
|
|
10524
|
-
const visited = isVisited(route.url);
|
|
10525
|
-
updateStatus({
|
|
10526
|
-
matching: newMatching,
|
|
10527
|
-
params: newParams,
|
|
10528
|
-
visited,
|
|
10529
|
-
});
|
|
10530
|
-
if (newMatching) {
|
|
10531
|
-
matchingRouteSet.add(route);
|
|
10532
|
-
}
|
|
10533
|
-
}
|
|
10534
|
-
|
|
10582
|
+
{
|
|
10535
10583
|
// URL -> Signal synchronization (moved from individual route effects to eliminate circular dependency)
|
|
10536
10584
|
// Prevent signal-to-URL synchronization during URL-to-signal synchronization
|
|
10537
10585
|
isUpdatingRoutesFromUrl = true;
|
|
10586
|
+
// Apply all signal updates in a batch
|
|
10587
|
+
const matchingRouteSet = new Set();
|
|
10588
|
+
batch(() => {
|
|
10589
|
+
for (const {
|
|
10590
|
+
route,
|
|
10591
|
+
routePrivateProperties,
|
|
10592
|
+
newMatching,
|
|
10593
|
+
newParams,
|
|
10594
|
+
} of routeMatchInfoSet) {
|
|
10595
|
+
const { updateStatus } = routePrivateProperties;
|
|
10596
|
+
const visited = isVisited(route.url);
|
|
10597
|
+
updateStatus({
|
|
10598
|
+
matching: newMatching,
|
|
10599
|
+
params: newParams,
|
|
10600
|
+
visited,
|
|
10601
|
+
});
|
|
10602
|
+
if (newMatching) {
|
|
10603
|
+
matchingRouteSet.add(route);
|
|
10604
|
+
}
|
|
10605
|
+
}
|
|
10538
10606
|
|
|
10539
|
-
|
|
10540
|
-
|
|
10541
|
-
|
|
10542
|
-
|
|
10543
|
-
|
|
10544
|
-
|
|
10545
|
-
|
|
10546
|
-
|
|
10547
|
-
for (const [paramName, connection] of connectionMap) {
|
|
10548
|
-
const { signal: paramSignal, debug } = connection;
|
|
10549
|
-
const rawParams = route.rawParamsSignal.value;
|
|
10550
|
-
const urlParamValue = rawParams[paramName];
|
|
10607
|
+
for (const {
|
|
10608
|
+
route,
|
|
10609
|
+
routePrivateProperties,
|
|
10610
|
+
newMatching,
|
|
10611
|
+
} of routeMatchInfoSet) {
|
|
10612
|
+
const { routePattern } = routePrivateProperties;
|
|
10613
|
+
const { connectionMap } = routePattern;
|
|
10551
10614
|
|
|
10552
|
-
|
|
10553
|
-
|
|
10554
|
-
|
|
10555
|
-
|
|
10615
|
+
for (const [paramName, connection] of connectionMap) {
|
|
10616
|
+
const { signal: paramSignal, debug } = connection;
|
|
10617
|
+
const rawParams = route.rawParamsSignal.value;
|
|
10618
|
+
const urlParamValue = rawParams[paramName];
|
|
10619
|
+
|
|
10620
|
+
if (!newMatching) {
|
|
10621
|
+
// Route doesn't match - check if any matching route extracts this parameter
|
|
10622
|
+
let parameterExtractedByMatchingRoute = false;
|
|
10623
|
+
let matchingRouteInSameFamily = false;
|
|
10624
|
+
|
|
10625
|
+
for (const otherRoute of routeSet) {
|
|
10626
|
+
if (otherRoute === route || !otherRoute.matching) {
|
|
10627
|
+
continue;
|
|
10628
|
+
}
|
|
10629
|
+
const otherRawParams = otherRoute.rawParamsSignal.value;
|
|
10630
|
+
const otherRoutePrivateProperties =
|
|
10631
|
+
getRoutePrivateProperties(otherRoute);
|
|
10556
10632
|
|
|
10557
|
-
|
|
10558
|
-
|
|
10559
|
-
|
|
10560
|
-
|
|
10561
|
-
const otherRawParams = otherRoute.rawParamsSignal.value;
|
|
10562
|
-
const otherRoutePrivateProperties =
|
|
10563
|
-
getRoutePrivateProperties(otherRoute);
|
|
10633
|
+
// Check if this matching route extracts the parameter
|
|
10634
|
+
if (paramName in otherRawParams) {
|
|
10635
|
+
parameterExtractedByMatchingRoute = true;
|
|
10636
|
+
}
|
|
10564
10637
|
|
|
10565
|
-
|
|
10566
|
-
|
|
10567
|
-
|
|
10568
|
-
}
|
|
10638
|
+
// Check if this matching route is in the same family using parent-child relationships
|
|
10639
|
+
const thisPatternObj = routePattern;
|
|
10640
|
+
const otherPatternObj = otherRoutePrivateProperties.routePattern;
|
|
10569
10641
|
|
|
10570
|
-
|
|
10571
|
-
|
|
10572
|
-
|
|
10573
|
-
|
|
10574
|
-
// Routes are in same family if they share a hierarchical relationship:
|
|
10575
|
-
// 1. One is parent/ancestor of the other
|
|
10576
|
-
// 2. They share a common parent/ancestor
|
|
10577
|
-
let inSameFamily = false;
|
|
10578
|
-
|
|
10579
|
-
// Check if other route is ancestor of this route
|
|
10580
|
-
let currentParent = thisPatternObj.parent;
|
|
10581
|
-
while (currentParent) {
|
|
10582
|
-
if (currentParent === otherPatternObj) {
|
|
10583
|
-
inSameFamily = true;
|
|
10584
|
-
break;
|
|
10585
|
-
}
|
|
10586
|
-
currentParent = currentParent.parent;
|
|
10587
|
-
}
|
|
10642
|
+
// Routes are in same family if they share a hierarchical relationship:
|
|
10643
|
+
// 1. One is parent/ancestor of the other
|
|
10644
|
+
// 2. They share a common parent/ancestor
|
|
10645
|
+
let inSameFamily = false;
|
|
10588
10646
|
|
|
10589
|
-
|
|
10590
|
-
|
|
10591
|
-
currentParent = otherPatternObj.parent;
|
|
10647
|
+
// Check if other route is ancestor of this route
|
|
10648
|
+
let currentParent = thisPatternObj.parent;
|
|
10592
10649
|
while (currentParent) {
|
|
10593
|
-
if (currentParent ===
|
|
10650
|
+
if (currentParent === otherPatternObj) {
|
|
10594
10651
|
inSameFamily = true;
|
|
10595
10652
|
break;
|
|
10596
10653
|
}
|
|
10597
10654
|
currentParent = currentParent.parent;
|
|
10598
10655
|
}
|
|
10599
|
-
}
|
|
10600
10656
|
|
|
10601
|
-
|
|
10602
|
-
|
|
10603
|
-
|
|
10604
|
-
|
|
10605
|
-
|
|
10606
|
-
|
|
10607
|
-
|
|
10657
|
+
// Check if this route is ancestor of other route
|
|
10658
|
+
if (!inSameFamily) {
|
|
10659
|
+
currentParent = otherPatternObj.parent;
|
|
10660
|
+
while (currentParent) {
|
|
10661
|
+
if (currentParent === thisPatternObj) {
|
|
10662
|
+
inSameFamily = true;
|
|
10663
|
+
break;
|
|
10664
|
+
}
|
|
10665
|
+
currentParent = currentParent.parent;
|
|
10666
|
+
}
|
|
10608
10667
|
}
|
|
10609
10668
|
|
|
10610
|
-
|
|
10611
|
-
|
|
10612
|
-
|
|
10613
|
-
|
|
10614
|
-
|
|
10669
|
+
// Check if they share a common parent (siblings or cousins)
|
|
10670
|
+
if (!inSameFamily) {
|
|
10671
|
+
const thisAncestors = new Set();
|
|
10672
|
+
currentParent = thisPatternObj.parent;
|
|
10673
|
+
while (currentParent) {
|
|
10674
|
+
thisAncestors.add(currentParent);
|
|
10675
|
+
currentParent = currentParent.parent;
|
|
10676
|
+
}
|
|
10677
|
+
|
|
10678
|
+
currentParent = otherPatternObj.parent;
|
|
10679
|
+
while (currentParent) {
|
|
10680
|
+
if (thisAncestors.has(currentParent)) {
|
|
10681
|
+
inSameFamily = true;
|
|
10682
|
+
break;
|
|
10683
|
+
}
|
|
10684
|
+
currentParent = currentParent.parent;
|
|
10615
10685
|
}
|
|
10616
|
-
currentParent = currentParent.parent;
|
|
10617
10686
|
}
|
|
10618
|
-
}
|
|
10619
10687
|
|
|
10620
|
-
|
|
10621
|
-
|
|
10688
|
+
if (inSameFamily) {
|
|
10689
|
+
matchingRouteInSameFamily = true;
|
|
10690
|
+
}
|
|
10622
10691
|
}
|
|
10623
|
-
}
|
|
10624
10692
|
|
|
10625
|
-
|
|
10626
|
-
|
|
10627
|
-
|
|
10628
|
-
|
|
10629
|
-
|
|
10630
|
-
|
|
10631
|
-
|
|
10632
|
-
|
|
10633
|
-
|
|
10693
|
+
// Only reset signal if:
|
|
10694
|
+
// 1. We're navigating within the same route family (not to completely unrelated routes)
|
|
10695
|
+
// 2. AND no matching route extracts this parameter from URL
|
|
10696
|
+
// 3. AND parameter has no default value (making it truly optional)
|
|
10697
|
+
if (
|
|
10698
|
+
matchingRouteInSameFamily &&
|
|
10699
|
+
!parameterExtractedByMatchingRoute
|
|
10700
|
+
) {
|
|
10701
|
+
const defaultValue = connection.getDefaultValue();
|
|
10702
|
+
if (defaultValue === undefined) {
|
|
10703
|
+
// Parameter is not extracted within same family and has no default - reset it
|
|
10704
|
+
if (debug) {
|
|
10705
|
+
console.debug(
|
|
10706
|
+
`[route] Same family navigation, ${paramName} not extracted and has no default: resetting signal`,
|
|
10707
|
+
);
|
|
10708
|
+
}
|
|
10709
|
+
paramSignal.value = undefined;
|
|
10710
|
+
} else if (debug) {
|
|
10711
|
+
// Parameter has a default value - preserve current signal value
|
|
10634
10712
|
console.debug(
|
|
10635
|
-
`[route]
|
|
10713
|
+
`[route] Parameter ${paramName} has default value ${defaultValue}: preserving signal value: ${paramSignal.value}`,
|
|
10636
10714
|
);
|
|
10637
10715
|
}
|
|
10638
|
-
paramSignal.value = undefined;
|
|
10639
10716
|
} else if (debug) {
|
|
10640
|
-
|
|
10641
|
-
|
|
10642
|
-
|
|
10643
|
-
|
|
10717
|
+
if (!matchingRouteInSameFamily) {
|
|
10718
|
+
console.debug(
|
|
10719
|
+
`[route] Different route family: preserving ${paramName} signal value: ${paramSignal.value}`,
|
|
10720
|
+
);
|
|
10721
|
+
} else {
|
|
10722
|
+
console.debug(
|
|
10723
|
+
`[route] Parameter ${paramName} extracted by matching route: preserving signal value: ${paramSignal.value}`,
|
|
10724
|
+
);
|
|
10725
|
+
}
|
|
10644
10726
|
}
|
|
10645
|
-
|
|
10646
|
-
|
|
10647
|
-
|
|
10648
|
-
|
|
10649
|
-
|
|
10650
|
-
|
|
10727
|
+
continue;
|
|
10728
|
+
}
|
|
10729
|
+
|
|
10730
|
+
// URL -> Signal sync: When route matches, ensure signal matches URL state
|
|
10731
|
+
// URL is the source of truth for explicit parameters
|
|
10732
|
+
const value = paramSignal.peek();
|
|
10733
|
+
if (urlParamValue === undefined) {
|
|
10734
|
+
// No URL parameter - reset signal to its current default value
|
|
10735
|
+
// (handles both static fallback and dynamic default cases)
|
|
10736
|
+
const defaultValue = connection.getDefaultValue();
|
|
10737
|
+
if (connection.isDefaultValue(value)) {
|
|
10738
|
+
// Signal already has correct default value, no sync needed
|
|
10739
|
+
continue;
|
|
10740
|
+
}
|
|
10741
|
+
if (debug) {
|
|
10651
10742
|
console.debug(
|
|
10652
|
-
`[route]
|
|
10743
|
+
`[route] URL->Signal: ${paramName} not in URL, reset signal to default (${defaultValue})`,
|
|
10653
10744
|
);
|
|
10654
10745
|
}
|
|
10746
|
+
paramSignal.value = defaultValue;
|
|
10747
|
+
continue;
|
|
10655
10748
|
}
|
|
10656
|
-
|
|
10657
|
-
|
|
10658
|
-
|
|
10659
|
-
// URL -> Signal sync: When route matches, ensure signal matches URL state
|
|
10660
|
-
// URL is the source of truth for explicit parameters
|
|
10661
|
-
const value = paramSignal.peek();
|
|
10662
|
-
if (urlParamValue === undefined) {
|
|
10663
|
-
// No URL parameter - reset signal to its current default value
|
|
10664
|
-
// (handles both static fallback and dynamic default cases)
|
|
10665
|
-
const defaultValue = connection.getDefaultValue();
|
|
10666
|
-
if (connection.isDefaultValue(value)) {
|
|
10667
|
-
// Signal already has correct default value, no sync needed
|
|
10749
|
+
if (urlParamValue === value) {
|
|
10750
|
+
// Values already match, no sync needed
|
|
10668
10751
|
continue;
|
|
10669
10752
|
}
|
|
10670
10753
|
if (debug) {
|
|
10671
10754
|
console.debug(
|
|
10672
|
-
`[route] URL->Signal: ${paramName}
|
|
10755
|
+
`[route] URL->Signal: ${paramName}=${urlParamValue} in url, sync signal with url`,
|
|
10673
10756
|
);
|
|
10674
10757
|
}
|
|
10675
|
-
paramSignal.value =
|
|
10676
|
-
continue;
|
|
10677
|
-
}
|
|
10678
|
-
if (urlParamValue === value) {
|
|
10679
|
-
// Values already match, no sync needed
|
|
10758
|
+
paramSignal.value = urlParamValue;
|
|
10680
10759
|
continue;
|
|
10681
10760
|
}
|
|
10682
|
-
if (debug) {
|
|
10683
|
-
console.debug(
|
|
10684
|
-
`[route] URL->Signal: ${paramName}=${urlParamValue} in url, sync signal with url`,
|
|
10685
|
-
);
|
|
10686
|
-
}
|
|
10687
|
-
paramSignal.value = urlParamValue;
|
|
10688
|
-
continue;
|
|
10689
10761
|
}
|
|
10690
|
-
}
|
|
10691
|
-
|
|
10692
|
-
|
|
10693
|
-
// Reset flag after URL -> Signal synchronization is complete
|
|
10694
|
-
isUpdatingRoutesFromUrl = false;
|
|
10762
|
+
});
|
|
10763
|
+
// Reset flag after URL -> Signal synchronization is complete
|
|
10764
|
+
isUpdatingRoutesFromUrl = false;
|
|
10695
10765
|
|
|
10696
|
-
|
|
10697
|
-
|
|
10698
|
-
const toLoadSet = new Set();
|
|
10699
|
-
const toReloadSet = new Set();
|
|
10700
|
-
const abortSignalMap = new Map();
|
|
10701
|
-
const routeLoadRequestedMap = new Map();
|
|
10766
|
+
Object.assign(returnValue, { matchingRouteSet });
|
|
10767
|
+
}
|
|
10702
10768
|
|
|
10703
|
-
|
|
10704
|
-
|
|
10705
|
-
|
|
10706
|
-
|
|
10707
|
-
|
|
10708
|
-
|
|
10709
|
-
|
|
10710
|
-
|
|
10711
|
-
|
|
10712
|
-
|
|
10769
|
+
{
|
|
10770
|
+
// must be after paramsSignal.value update to ensure the proxy target is set
|
|
10771
|
+
// (so after the batch call)
|
|
10772
|
+
const toLoadSet = new Set();
|
|
10773
|
+
const toReloadSet = new Set();
|
|
10774
|
+
const abortSignalMap = new Map();
|
|
10775
|
+
const routeLoadRequestedMap = new Map();
|
|
10776
|
+
const shouldLoadOrReload = (route, shouldLoad) => {
|
|
10777
|
+
const routeAction = route.action;
|
|
10778
|
+
const currentAction = routeAction.getCurrentAction();
|
|
10779
|
+
if (shouldLoad) {
|
|
10780
|
+
if (
|
|
10781
|
+
navigationType === "replace" ||
|
|
10782
|
+
currentAction.aborted ||
|
|
10783
|
+
currentAction.error
|
|
10784
|
+
) {
|
|
10785
|
+
shouldLoad = false;
|
|
10786
|
+
}
|
|
10787
|
+
}
|
|
10788
|
+
if (shouldLoad) {
|
|
10789
|
+
toLoadSet.add(currentAction);
|
|
10790
|
+
} else {
|
|
10791
|
+
toReloadSet.add(currentAction);
|
|
10792
|
+
}
|
|
10793
|
+
routeLoadRequestedMap.set(route, currentAction);
|
|
10794
|
+
// Create a new abort controller for this action
|
|
10795
|
+
const actionAbortController = new AbortController();
|
|
10796
|
+
actionAbortControllerWeakMap.set(currentAction, actionAbortController);
|
|
10797
|
+
abortSignalMap.set(currentAction, actionAbortController.signal);
|
|
10798
|
+
};
|
|
10799
|
+
const shouldLoad = (route) => {
|
|
10800
|
+
shouldLoadOrReload(route, true);
|
|
10801
|
+
};
|
|
10802
|
+
const shouldReload = (route) => {
|
|
10803
|
+
shouldLoadOrReload(route, false);
|
|
10804
|
+
};
|
|
10805
|
+
const shouldAbort = (route) => {
|
|
10806
|
+
const routeAction = route.action;
|
|
10807
|
+
const currentAction = routeAction.getCurrentAction();
|
|
10808
|
+
const actionAbortController =
|
|
10809
|
+
actionAbortControllerWeakMap.get(currentAction);
|
|
10810
|
+
if (actionAbortController) {
|
|
10811
|
+
actionAbortController.abort(`route no longer matching`);
|
|
10812
|
+
actionAbortControllerWeakMap.delete(currentAction);
|
|
10813
|
+
}
|
|
10814
|
+
};
|
|
10815
|
+
for (const {
|
|
10816
|
+
route,
|
|
10817
|
+
routePrivateProperties,
|
|
10818
|
+
newMatching,
|
|
10819
|
+
oldMatching,
|
|
10820
|
+
newParams,
|
|
10821
|
+
oldParams,
|
|
10822
|
+
} of routeMatchInfoSet) {
|
|
10823
|
+
const routeAction = route.action;
|
|
10824
|
+
if (!routeAction) {
|
|
10825
|
+
continue;
|
|
10713
10826
|
}
|
|
10714
|
-
}
|
|
10715
|
-
if (shouldLoad) {
|
|
10716
|
-
toLoadSet.add(currentAction);
|
|
10717
|
-
} else {
|
|
10718
|
-
toReloadSet.add(currentAction);
|
|
10719
|
-
}
|
|
10720
|
-
routeLoadRequestedMap.set(route, currentAction);
|
|
10721
|
-
// Create a new abort controller for this action
|
|
10722
|
-
const actionAbortController = new AbortController();
|
|
10723
|
-
actionAbortControllerWeakMap.set(currentAction, actionAbortController);
|
|
10724
|
-
abortSignalMap.set(currentAction, actionAbortController.signal);
|
|
10725
|
-
};
|
|
10726
|
-
|
|
10727
|
-
const shouldLoad = (route) => {
|
|
10728
|
-
shouldLoadOrReload(route, true);
|
|
10729
|
-
};
|
|
10730
|
-
const shouldReload = (route) => {
|
|
10731
|
-
shouldLoadOrReload(route, false);
|
|
10732
|
-
};
|
|
10733
|
-
const shouldAbort = (route) => {
|
|
10734
|
-
const routeAction = route.action;
|
|
10735
|
-
const currentAction = routeAction.getCurrentAction();
|
|
10736
|
-
const actionAbortController =
|
|
10737
|
-
actionAbortControllerWeakMap.get(currentAction);
|
|
10738
|
-
if (actionAbortController) {
|
|
10739
|
-
actionAbortController.abort(`route no longer matching`);
|
|
10740
|
-
actionAbortControllerWeakMap.delete(currentAction);
|
|
10741
|
-
}
|
|
10742
|
-
};
|
|
10743
|
-
|
|
10744
|
-
for (const {
|
|
10745
|
-
route,
|
|
10746
|
-
routePrivateProperties,
|
|
10747
|
-
newMatching,
|
|
10748
|
-
oldMatching,
|
|
10749
|
-
newParams,
|
|
10750
|
-
oldParams,
|
|
10751
|
-
} of routeMatchInfoSet) {
|
|
10752
|
-
const routeAction = route.action;
|
|
10753
|
-
if (!routeAction) {
|
|
10754
|
-
continue;
|
|
10755
|
-
}
|
|
10756
10827
|
|
|
10757
|
-
|
|
10758
|
-
|
|
10759
|
-
|
|
10760
|
-
|
|
10828
|
+
const becomesMatching = newMatching && !oldMatching;
|
|
10829
|
+
const becomesNotMatching = !newMatching && oldMatching;
|
|
10830
|
+
const paramsChangedWhileMatching =
|
|
10831
|
+
newMatching && oldMatching && newParams !== oldParams;
|
|
10761
10832
|
|
|
10762
|
-
|
|
10763
|
-
|
|
10764
|
-
|
|
10765
|
-
|
|
10766
|
-
|
|
10833
|
+
// Handle actions for routes that become matching
|
|
10834
|
+
if (becomesMatching) {
|
|
10835
|
+
shouldLoad(route);
|
|
10836
|
+
continue;
|
|
10837
|
+
}
|
|
10767
10838
|
|
|
10768
|
-
|
|
10769
|
-
|
|
10770
|
-
|
|
10771
|
-
|
|
10772
|
-
|
|
10839
|
+
// Handle actions for routes that become not matching - abort them
|
|
10840
|
+
if (becomesNotMatching && ROUTE_DEACTIVATION_STRATEGY === "abort") {
|
|
10841
|
+
shouldAbort(route);
|
|
10842
|
+
continue;
|
|
10843
|
+
}
|
|
10773
10844
|
|
|
10774
|
-
|
|
10775
|
-
|
|
10776
|
-
|
|
10845
|
+
// Handle parameter changes while route stays matching
|
|
10846
|
+
if (paramsChangedWhileMatching) {
|
|
10847
|
+
shouldReload(route);
|
|
10848
|
+
}
|
|
10777
10849
|
}
|
|
10850
|
+
Object.assign(returnValue, {
|
|
10851
|
+
loadSet: toLoadSet,
|
|
10852
|
+
reloadSet: toReloadSet,
|
|
10853
|
+
abortSignalMap,
|
|
10854
|
+
routeLoadRequestedMap,
|
|
10855
|
+
});
|
|
10778
10856
|
}
|
|
10779
10857
|
|
|
10780
|
-
return
|
|
10781
|
-
loadSet: toLoadSet,
|
|
10782
|
-
reloadSet: toReloadSet,
|
|
10783
|
-
abortSignalMap,
|
|
10784
|
-
routeLoadRequestedMap,
|
|
10785
|
-
matchingRouteSet,
|
|
10786
|
-
};
|
|
10858
|
+
return returnValue;
|
|
10787
10859
|
};
|
|
10788
10860
|
|
|
10789
10861
|
const registerRoute = (routePattern) => {
|
|
@@ -10799,10 +10871,8 @@ const registerRoute = (routePattern) => {
|
|
|
10799
10871
|
matching: false,
|
|
10800
10872
|
params: ROUTE_NOT_MATCHING_PARAMS,
|
|
10801
10873
|
buildUrl: null,
|
|
10802
|
-
bindAction: null,
|
|
10803
10874
|
relativeUrl: null,
|
|
10804
10875
|
url: null,
|
|
10805
|
-
action: null,
|
|
10806
10876
|
matchingSignal: null,
|
|
10807
10877
|
paramsSignal: null,
|
|
10808
10878
|
urlSignal: null,
|
|
@@ -10811,6 +10881,10 @@ const registerRoute = (routePattern) => {
|
|
|
10811
10881
|
toString: () => {
|
|
10812
10882
|
return `route "${cleanPattern}"`;
|
|
10813
10883
|
},
|
|
10884
|
+
|
|
10885
|
+
bindAction: null,
|
|
10886
|
+
action: null,
|
|
10887
|
+
actionStatusSignal: null,
|
|
10814
10888
|
};
|
|
10815
10889
|
routeSet.add(route);
|
|
10816
10890
|
const routePrivateProperties = {
|
|
@@ -11047,7 +11121,14 @@ const registerRoute = (routePattern) => {
|
|
|
11047
11121
|
cleanupCallbackSet.add(disposeRelativeUrlEffect);
|
|
11048
11122
|
cleanupCallbackSet.add(disposeUrlEffect);
|
|
11049
11123
|
|
|
11050
|
-
// action
|
|
11124
|
+
// action
|
|
11125
|
+
route.actionStatusSignal = signal({
|
|
11126
|
+
loading: false,
|
|
11127
|
+
error: null,
|
|
11128
|
+
aborted: false,
|
|
11129
|
+
completed: true,
|
|
11130
|
+
data: undefined,
|
|
11131
|
+
});
|
|
11051
11132
|
route.bindAction = (action) => {
|
|
11052
11133
|
const { store } = action.meta;
|
|
11053
11134
|
if (store) {
|
|
@@ -11077,6 +11158,10 @@ const registerRoute = (routePattern) => {
|
|
|
11077
11158
|
|
|
11078
11159
|
const actionBoundToThisRoute = action.bindParams(route.paramsSignal);
|
|
11079
11160
|
route.action = actionBoundToThisRoute;
|
|
11161
|
+
route.actionStatusSignal = computed(() => {
|
|
11162
|
+
const actionStatus = getActionStatus(actionBoundToThisRoute);
|
|
11163
|
+
return actionStatus;
|
|
11164
|
+
});
|
|
11080
11165
|
return actionBoundToThisRoute;
|
|
11081
11166
|
};
|
|
11082
11167
|
|
|
@@ -11761,7 +11846,8 @@ const useForceRender = () => {
|
|
|
11761
11846
|
* . Tester le code splitting avec .lazy + import dynamique
|
|
11762
11847
|
* pour les elements des routes
|
|
11763
11848
|
*
|
|
11764
|
-
* 3. Ajouter la possibilite d'avoir des
|
|
11849
|
+
* 3. Ajouter la possibilite d'avoir des
|
|
11850
|
+
* sur les routes
|
|
11765
11851
|
* Tester juste les data pour commencer
|
|
11766
11852
|
* On aura ptet besoin d'un useRouteData au lieu de passer par un element qui est une fonction
|
|
11767
11853
|
* pour que react ne re-render pas tout
|
|
@@ -18531,6 +18617,9 @@ const useKeyboardShortcuts = (
|
|
|
18531
18617
|
|
|
18532
18618
|
useEffect(() => {
|
|
18533
18619
|
const element = elementRef.current;
|
|
18620
|
+
if (!element) {
|
|
18621
|
+
return null;
|
|
18622
|
+
}
|
|
18534
18623
|
const shortcutsCopy = [];
|
|
18535
18624
|
for (const shortcutCandidate of shortcuts) {
|
|
18536
18625
|
shortcutsCopy.push({
|
|
@@ -18543,7 +18632,8 @@ const useKeyboardShortcuts = (
|
|
|
18543
18632
|
return false;
|
|
18544
18633
|
}
|
|
18545
18634
|
const { action } = shortcutCandidate;
|
|
18546
|
-
|
|
18635
|
+
const actionWithEvent = action.bindParams(keyboardEvent);
|
|
18636
|
+
return requestAction(element, actionWithEvent, {
|
|
18547
18637
|
actionOrigin: "keyboard_shortcut",
|
|
18548
18638
|
event: keyboardEvent,
|
|
18549
18639
|
requester: document.activeElement,
|
|
@@ -18846,11 +18936,15 @@ const useUIStateController = (
|
|
|
18846
18936
|
getStateFromProp = (prop) => prop,
|
|
18847
18937
|
getPropFromState = (state) => state,
|
|
18848
18938
|
getStateFromParent,
|
|
18939
|
+
persists,
|
|
18849
18940
|
} = {},
|
|
18850
18941
|
) => {
|
|
18851
18942
|
const parentUIStateController = useContext(ParentUIStateControllerContext);
|
|
18852
18943
|
const formContext = useContext(FormContext);
|
|
18853
18944
|
const { id, name, onUIStateChange, action } = props;
|
|
18945
|
+
if (persists === undefined && formContext) {
|
|
18946
|
+
persists = true;
|
|
18947
|
+
}
|
|
18854
18948
|
const uncontrolled = !formContext && !action;
|
|
18855
18949
|
const [navState, setNavState] = useNavState$1(id);
|
|
18856
18950
|
|
|
@@ -18867,7 +18961,7 @@ const useUIStateController = (
|
|
|
18867
18961
|
// not controlled but want an initial state (a value or being checked)
|
|
18868
18962
|
return getStateFromProp(defaultState);
|
|
18869
18963
|
}
|
|
18870
|
-
if (
|
|
18964
|
+
if (persists && navState) {
|
|
18871
18965
|
// not controlled but want to use value from nav state
|
|
18872
18966
|
// (I think this should likely move earlier to win over the hasUIStateProp when it's undefined)
|
|
18873
18967
|
return getStateFromProp(navState);
|
|
@@ -18971,7 +19065,7 @@ const useUIStateController = (
|
|
|
18971
19065
|
getStateFromProp,
|
|
18972
19066
|
setUIState: (prop, e) => {
|
|
18973
19067
|
const newUIState = uiStateController.getStateFromProp(prop);
|
|
18974
|
-
if (
|
|
19068
|
+
if (persists) {
|
|
18975
19069
|
setNavState(prop);
|
|
18976
19070
|
}
|
|
18977
19071
|
const currentUIState = uiStateController.uiState;
|
|
@@ -18991,7 +19085,7 @@ const useUIStateController = (
|
|
|
18991
19085
|
uiStateController.setUIState(currentState, e);
|
|
18992
19086
|
},
|
|
18993
19087
|
actionEnd: () => {
|
|
18994
|
-
if (
|
|
19088
|
+
if (persists) {
|
|
18995
19089
|
setNavState(undefined);
|
|
18996
19090
|
}
|
|
18997
19091
|
},
|
|
@@ -20126,7 +20220,8 @@ const LinkPlain = props => {
|
|
|
20126
20220
|
discrete,
|
|
20127
20221
|
blankTargetIcon,
|
|
20128
20222
|
anchorIcon,
|
|
20129
|
-
|
|
20223
|
+
startIcon,
|
|
20224
|
+
endIcon,
|
|
20130
20225
|
spacing,
|
|
20131
20226
|
revealOnInteraction = Boolean(titleLevel),
|
|
20132
20227
|
hrefFallback = !anchor,
|
|
@@ -20149,29 +20244,29 @@ const LinkPlain = props => {
|
|
|
20149
20244
|
} = getHrefTargetInfo(href);
|
|
20150
20245
|
const innerTarget = target === undefined ? isSameSite ? "_self" : "_blank" : target;
|
|
20151
20246
|
const innerRel = rel === undefined ? isSameSite ? undefined : "noopener noreferrer" : rel;
|
|
20152
|
-
let
|
|
20153
|
-
if (
|
|
20247
|
+
let innerEndIcon;
|
|
20248
|
+
if (endIcon === undefined) {
|
|
20154
20249
|
// Check for special protocol or domain-specific icons first
|
|
20155
20250
|
if (href?.startsWith("tel:")) {
|
|
20156
|
-
|
|
20251
|
+
innerEndIcon = jsx(PhoneSvg, {});
|
|
20157
20252
|
} else if (href?.startsWith("sms:")) {
|
|
20158
|
-
|
|
20253
|
+
innerEndIcon = jsx(SmsSvg, {});
|
|
20159
20254
|
} else if (href?.startsWith("mailto:")) {
|
|
20160
|
-
|
|
20255
|
+
innerEndIcon = jsx(EmailSvg, {});
|
|
20161
20256
|
} else if (href?.includes("github.com")) {
|
|
20162
|
-
|
|
20257
|
+
innerEndIcon = jsx(GithubSvg, {});
|
|
20163
20258
|
} else {
|
|
20164
20259
|
// Fall back to default icon logic
|
|
20165
20260
|
const innerBlankTargetIcon = blankTargetIcon === undefined ? innerTarget === "_blank" : blankTargetIcon;
|
|
20166
20261
|
const innerAnchorIcon = anchorIcon === undefined ? isAnchor : anchorIcon;
|
|
20167
20262
|
if (innerBlankTargetIcon) {
|
|
20168
|
-
|
|
20263
|
+
innerEndIcon = innerBlankTargetIcon === true ? jsx(LinkBlankTargetSvg, {}) : innerBlankTargetIcon;
|
|
20169
20264
|
} else if (innerAnchorIcon) {
|
|
20170
|
-
|
|
20265
|
+
innerEndIcon = innerAnchorIcon === true ? jsx(LinkAnchorSvg, {}) : anchorIcon;
|
|
20171
20266
|
}
|
|
20172
20267
|
}
|
|
20173
20268
|
} else {
|
|
20174
|
-
|
|
20269
|
+
innerEndIcon = endIcon;
|
|
20175
20270
|
}
|
|
20176
20271
|
const innerChildren = children || (hrefFallback ? href : children);
|
|
20177
20272
|
return jsxs(Box, {
|
|
@@ -20225,12 +20320,15 @@ const LinkPlain = props => {
|
|
|
20225
20320
|
}
|
|
20226
20321
|
onKeyDown?.(e);
|
|
20227
20322
|
},
|
|
20228
|
-
children: [jsx(
|
|
20323
|
+
children: [startIcon && jsx(Icon, {
|
|
20324
|
+
marginRight: innerChildren ? "xxs" : undefined,
|
|
20325
|
+
children: startIcon
|
|
20326
|
+
}), jsx(LoaderBackground, {
|
|
20229
20327
|
loading: loading,
|
|
20230
20328
|
color: "var(--link-loader-color)"
|
|
20231
|
-
}), applySpacingOnTextChildren(innerChildren, spacing),
|
|
20329
|
+
}), applySpacingOnTextChildren(innerChildren, spacing), endIcon && jsx(Icon, {
|
|
20232
20330
|
marginLeft: innerChildren ? "xxs" : undefined,
|
|
20233
|
-
children:
|
|
20331
|
+
children: innerEndIcon
|
|
20234
20332
|
})]
|
|
20235
20333
|
});
|
|
20236
20334
|
};
|
|
@@ -20759,151 +20857,565 @@ const TabBasic = ({
|
|
|
20759
20857
|
});
|
|
20760
20858
|
};
|
|
20761
20859
|
|
|
20762
|
-
const
|
|
20763
|
-
|
|
20764
|
-
|
|
20765
|
-
// it's also possible that front is unsync with backend, preventing user to choose a value
|
|
20766
|
-
// that is actually free.
|
|
20767
|
-
// But this is unlikely to happen and user could reload the page to be able to choose that name
|
|
20768
|
-
// that suddenly became available
|
|
20769
|
-
existingValueSet,
|
|
20770
|
-
message = `"{value}" est utilisé. Veuillez entrer une autre valeur.`,
|
|
20860
|
+
const useFocusGroup = (
|
|
20861
|
+
elementRef,
|
|
20862
|
+
{ enabled = true, direction, skipTab, loop, name } = {},
|
|
20771
20863
|
) => {
|
|
20772
|
-
|
|
20773
|
-
|
|
20774
|
-
|
|
20775
|
-
|
|
20776
|
-
|
|
20777
|
-
|
|
20778
|
-
|
|
20779
|
-
|
|
20780
|
-
|
|
20781
|
-
|
|
20782
|
-
|
|
20783
|
-
|
|
20784
|
-
return replaceStringVars(message, {
|
|
20785
|
-
"{value}": fieldValue,
|
|
20786
|
-
});
|
|
20787
|
-
}
|
|
20788
|
-
return "";
|
|
20789
|
-
},
|
|
20790
|
-
};
|
|
20864
|
+
useLayoutEffect(() => {
|
|
20865
|
+
if (!enabled) {
|
|
20866
|
+
return null;
|
|
20867
|
+
}
|
|
20868
|
+
const focusGroup = initFocusGroup(elementRef.current, {
|
|
20869
|
+
direction,
|
|
20870
|
+
skipTab,
|
|
20871
|
+
loop,
|
|
20872
|
+
name,
|
|
20873
|
+
});
|
|
20874
|
+
return focusGroup.cleanup;
|
|
20875
|
+
}, [direction, skipTab, loop, name]);
|
|
20791
20876
|
};
|
|
20792
20877
|
|
|
20793
|
-
const
|
|
20794
|
-
const
|
|
20795
|
-
|
|
20796
|
-
|
|
20797
|
-
|
|
20798
|
-
|
|
20878
|
+
installImportMetaCss(import.meta);const rightArrowPath = "M680-480L360-160l-80-80 240-240-240-240 80-80 320 320z";
|
|
20879
|
+
const downArrowPath = "M480-280L160-600l80-80 240 240 240-240 80 80-320 320z";
|
|
20880
|
+
import.meta.css = /* css */`
|
|
20881
|
+
.navi_summary_marker {
|
|
20882
|
+
width: 1em;
|
|
20883
|
+
height: 1em;
|
|
20884
|
+
line-height: 1em;
|
|
20885
|
+
|
|
20886
|
+
.navi_summary_marker_loading_container {
|
|
20887
|
+
transform: scale(0.3);
|
|
20888
|
+
transition: transform 0.3s linear;
|
|
20889
|
+
|
|
20890
|
+
.navi_summary_marker_background_circle,
|
|
20891
|
+
.navi_summary_marker_foreground_circle {
|
|
20892
|
+
opacity: 0;
|
|
20893
|
+
transition: opacity 0.3s ease-in-out;
|
|
20894
|
+
}
|
|
20895
|
+
|
|
20896
|
+
.navi_summary_marker_foreground_circle {
|
|
20897
|
+
stroke-dasharray: 503 1507; /* ~25% of circle perimeter */
|
|
20898
|
+
stroke-dashoffset: 0;
|
|
20899
|
+
animation: progress-around-circle 1.5s linear infinite;
|
|
20900
|
+
}
|
|
20799
20901
|
}
|
|
20800
|
-
|
|
20801
|
-
|
|
20802
|
-
|
|
20902
|
+
|
|
20903
|
+
.navi_summary_marker_arrow {
|
|
20904
|
+
opacity: 1;
|
|
20905
|
+
transition: opacity 0.3s ease-in-out;
|
|
20906
|
+
animation-duration: 0.3s;
|
|
20907
|
+
animation-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
20908
|
+
animation-fill-mode: forwards;
|
|
20909
|
+
|
|
20910
|
+
&[data-animation-target="down"] {
|
|
20911
|
+
animation-name: morph-to-down;
|
|
20912
|
+
}
|
|
20913
|
+
|
|
20914
|
+
&[data-animation-target="right"] {
|
|
20915
|
+
animation-name: morph-to-right;
|
|
20916
|
+
}
|
|
20803
20917
|
}
|
|
20804
|
-
const value = __validationInterface__.getConstraintValidityState();
|
|
20805
|
-
return value;
|
|
20806
|
-
};
|
|
20807
20918
|
|
|
20808
|
-
|
|
20809
|
-
|
|
20919
|
+
&[data-loading] {
|
|
20920
|
+
.navi_summary_marker_loading_container {
|
|
20921
|
+
transform: scale(1);
|
|
20810
20922
|
|
|
20811
|
-
|
|
20812
|
-
|
|
20813
|
-
|
|
20814
|
-
|
|
20923
|
+
.navi_summary_marker_background_circle {
|
|
20924
|
+
opacity: 0.2;
|
|
20925
|
+
}
|
|
20926
|
+
.navi_summary_marker_foreground_circle {
|
|
20927
|
+
opacity: 1;
|
|
20928
|
+
}
|
|
20929
|
+
}
|
|
20930
|
+
.navi_summary_marker_arrow {
|
|
20931
|
+
opacity: 0;
|
|
20932
|
+
}
|
|
20815
20933
|
}
|
|
20816
|
-
|
|
20817
|
-
|
|
20818
|
-
|
|
20819
|
-
|
|
20934
|
+
}
|
|
20935
|
+
@keyframes progress-around-circle {
|
|
20936
|
+
0% {
|
|
20937
|
+
stroke-dashoffset: 0;
|
|
20938
|
+
}
|
|
20939
|
+
100% {
|
|
20940
|
+
stroke-dashoffset: -2010;
|
|
20941
|
+
}
|
|
20942
|
+
}
|
|
20943
|
+
@keyframes morph-to-down {
|
|
20944
|
+
from {
|
|
20945
|
+
d: path("${rightArrowPath}");
|
|
20946
|
+
}
|
|
20947
|
+
to {
|
|
20948
|
+
d: path("${downArrowPath}");
|
|
20949
|
+
}
|
|
20950
|
+
}
|
|
20951
|
+
@keyframes morph-to-right {
|
|
20952
|
+
from {
|
|
20953
|
+
d: path("${downArrowPath}");
|
|
20954
|
+
}
|
|
20955
|
+
to {
|
|
20956
|
+
d: path("${rightArrowPath}");
|
|
20957
|
+
}
|
|
20958
|
+
}
|
|
20959
|
+
`;
|
|
20960
|
+
const SummaryMarker = ({
|
|
20961
|
+
open,
|
|
20962
|
+
loading
|
|
20963
|
+
}) => {
|
|
20964
|
+
const showLoading = useDebounceTrue(loading, 300);
|
|
20965
|
+
const mountedRef = useRef(false);
|
|
20966
|
+
const prevOpenRef = useRef(open);
|
|
20967
|
+
useLayoutEffect(() => {
|
|
20968
|
+
mountedRef.current = true;
|
|
20969
|
+
return () => {
|
|
20970
|
+
mountedRef.current = false;
|
|
20971
|
+
};
|
|
20820
20972
|
}, []);
|
|
20821
|
-
|
|
20822
|
-
|
|
20973
|
+
const shouldAnimate = mountedRef.current && prevOpenRef.current !== open;
|
|
20974
|
+
prevOpenRef.current = open;
|
|
20975
|
+
return jsx("span", {
|
|
20976
|
+
className: "navi_summary_marker",
|
|
20977
|
+
"data-loading": showLoading ? "" : undefined,
|
|
20978
|
+
children: jsxs("svg", {
|
|
20979
|
+
viewBox: "0 -960 960 960",
|
|
20980
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
20981
|
+
children: [jsxs("g", {
|
|
20982
|
+
className: "navi_summary_marker_loading_container",
|
|
20983
|
+
"transform-origin": "480px -480px",
|
|
20984
|
+
children: [jsx("circle", {
|
|
20985
|
+
className: "navi_summary_marker_background_circle",
|
|
20986
|
+
cx: "480",
|
|
20987
|
+
cy: "-480",
|
|
20988
|
+
r: "320",
|
|
20989
|
+
stroke: "currentColor",
|
|
20990
|
+
fill: "none",
|
|
20991
|
+
strokeWidth: "60",
|
|
20992
|
+
opacity: "0.2"
|
|
20993
|
+
}), jsx("circle", {
|
|
20994
|
+
className: "navi_summary_marker_foreground_circle",
|
|
20995
|
+
cx: "480",
|
|
20996
|
+
cy: "-480",
|
|
20997
|
+
r: "320",
|
|
20998
|
+
stroke: "currentColor",
|
|
20999
|
+
fill: "none",
|
|
21000
|
+
strokeWidth: "60",
|
|
21001
|
+
strokeLinecap: "round",
|
|
21002
|
+
strokeDasharray: "503 1507"
|
|
21003
|
+
})]
|
|
21004
|
+
}), jsx("g", {
|
|
21005
|
+
"transform-origin": "480px -480px",
|
|
21006
|
+
children: jsx("path", {
|
|
21007
|
+
className: "navi_summary_marker_arrow",
|
|
21008
|
+
fill: "currentColor",
|
|
21009
|
+
"data-animation-target": shouldAnimate ? open ? "down" : "right" : undefined,
|
|
21010
|
+
d: open ? downArrowPath : rightArrowPath
|
|
21011
|
+
})
|
|
21012
|
+
})]
|
|
21013
|
+
})
|
|
21014
|
+
});
|
|
20823
21015
|
};
|
|
20824
21016
|
|
|
20825
21017
|
installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
20826
|
-
|
|
20827
|
-
|
|
21018
|
+
.navi_details {
|
|
21019
|
+
position: relative;
|
|
21020
|
+
z-index: 1;
|
|
21021
|
+
display: flex;
|
|
21022
|
+
flex-shrink: 0;
|
|
21023
|
+
flex-direction: column;
|
|
21024
|
+
|
|
21025
|
+
summary {
|
|
21026
|
+
display: flex;
|
|
21027
|
+
flex-shrink: 0;
|
|
21028
|
+
flex-direction: column;
|
|
20828
21029
|
cursor: pointer;
|
|
20829
|
-
|
|
21030
|
+
user-select: none;
|
|
20830
21031
|
|
|
20831
|
-
|
|
20832
|
-
|
|
20833
|
-
|
|
20834
|
-
|
|
21032
|
+
&:focus {
|
|
21033
|
+
z-index: 1;
|
|
21034
|
+
}
|
|
21035
|
+
|
|
21036
|
+
.navi_summary_body {
|
|
21037
|
+
display: flex;
|
|
21038
|
+
width: 100%;
|
|
21039
|
+
flex-direction: row;
|
|
21040
|
+
align-items: center;
|
|
21041
|
+
gap: 0.2em;
|
|
21042
|
+
|
|
21043
|
+
.navi_summary_label {
|
|
21044
|
+
display: flex;
|
|
21045
|
+
padding-right: 10px;
|
|
21046
|
+
flex: 1;
|
|
21047
|
+
align-items: center;
|
|
21048
|
+
gap: 0.2em;
|
|
21049
|
+
}
|
|
21050
|
+
}
|
|
20835
21051
|
}
|
|
20836
21052
|
}
|
|
20837
21053
|
`;
|
|
20838
|
-
const
|
|
20839
|
-
const ReportDisabledOnLabelContext = createContext();
|
|
20840
|
-
const LabelPseudoClasses = [":hover", ":active", ":focus", ":focus-visible", ":read-only", ":disabled", ":-navi-loading"];
|
|
20841
|
-
const Label = props => {
|
|
21054
|
+
const Details = props => {
|
|
20842
21055
|
const {
|
|
20843
|
-
|
|
20844
|
-
|
|
20845
|
-
children,
|
|
20846
|
-
...rest
|
|
21056
|
+
value = "on",
|
|
21057
|
+
persists
|
|
20847
21058
|
} = props;
|
|
20848
|
-
const
|
|
20849
|
-
|
|
20850
|
-
|
|
20851
|
-
|
|
20852
|
-
|
|
20853
|
-
|
|
20854
|
-
|
|
20855
|
-
|
|
20856
|
-
|
|
20857
|
-
|
|
20858
|
-
|
|
20859
|
-
|
|
20860
|
-
|
|
20861
|
-
|
|
20862
|
-
|
|
20863
|
-
|
|
20864
|
-
|
|
20865
|
-
|
|
21059
|
+
const uiStateController = useUIStateController(props, "details", {
|
|
21060
|
+
statePropName: "open",
|
|
21061
|
+
defaultStatePropName: "defaultOpen",
|
|
21062
|
+
fallbackState: false,
|
|
21063
|
+
getStateFromProp: open => open ? value : undefined,
|
|
21064
|
+
getPropFromState: Boolean,
|
|
21065
|
+
persists
|
|
21066
|
+
});
|
|
21067
|
+
const uiState = useUIState(uiStateController);
|
|
21068
|
+
const details = renderActionableComponent(props, {
|
|
21069
|
+
Basic: DetailsBasic,
|
|
21070
|
+
WithAction: DetailsWithAction
|
|
21071
|
+
});
|
|
21072
|
+
return jsx(UIStateControllerContext.Provider, {
|
|
21073
|
+
value: uiStateController,
|
|
21074
|
+
children: jsx(UIStateContext.Provider, {
|
|
21075
|
+
value: uiState,
|
|
21076
|
+
children: details
|
|
20866
21077
|
})
|
|
20867
21078
|
});
|
|
20868
21079
|
};
|
|
21080
|
+
const DetailsBasic = props => {
|
|
21081
|
+
const uiStateController = useContext(UIStateControllerContext);
|
|
21082
|
+
const uiState = useContext(UIStateContext);
|
|
21083
|
+
const {
|
|
21084
|
+
id,
|
|
21085
|
+
label = "Summary",
|
|
21086
|
+
loading,
|
|
21087
|
+
focusGroup,
|
|
21088
|
+
focusGroupDirection,
|
|
21089
|
+
arrowKeyShortcuts = true,
|
|
21090
|
+
openKeyShortcut = "ArrowRight",
|
|
21091
|
+
closeKeyShortcut = "ArrowLeft",
|
|
21092
|
+
onToggle,
|
|
21093
|
+
children,
|
|
21094
|
+
...rest
|
|
21095
|
+
} = props;
|
|
21096
|
+
const defaultRef = useRef();
|
|
21097
|
+
const ref = rest.ref || defaultRef;
|
|
21098
|
+
const open = Boolean(uiState);
|
|
21099
|
+
useFocusGroup(ref, {
|
|
21100
|
+
enabled: focusGroup,
|
|
21101
|
+
name: typeof focusGroup === "string" ? focusGroup : undefined,
|
|
21102
|
+
direction: focusGroupDirection
|
|
21103
|
+
});
|
|
20869
21104
|
|
|
20870
|
-
|
|
20871
|
-
|
|
20872
|
-
|
|
20873
|
-
|
|
20874
|
-
|
|
20875
|
-
|
|
20876
|
-
|
|
20877
|
-
|
|
20878
|
-
|
|
20879
|
-
|
|
20880
|
-
|
|
20881
|
-
|
|
20882
|
-
|
|
20883
|
-
|
|
20884
|
-
--background-color: white;
|
|
20885
|
-
--accent-color: light-dark(#4476ff, #3b82f6);
|
|
20886
|
-
--background-color-checked: var(--accent-color);
|
|
20887
|
-
--border-color-checked: var(--accent-color);
|
|
20888
|
-
--checkmark-color-light: white;
|
|
20889
|
-
--checkmark-color-dark: rgb(55, 55, 55);
|
|
20890
|
-
--checkmark-color: var(--checkmark-color-light);
|
|
20891
|
-
--cursor: pointer;
|
|
20892
|
-
|
|
20893
|
-
--color-mix-light: black;
|
|
20894
|
-
--color-mix-dark: white;
|
|
20895
|
-
--color-mix: var(--color-mix-light);
|
|
21105
|
+
/**
|
|
21106
|
+
* Browser will dispatch "toggle" event even if we set open={true}
|
|
21107
|
+
* When rendering the component for the first time
|
|
21108
|
+
* We have to ensure the initial "toggle" event is ignored.
|
|
21109
|
+
*
|
|
21110
|
+
* If we don't do that code will think the details has changed and run logic accordingly
|
|
21111
|
+
* For example it will try to navigate to the current url while we are already there
|
|
21112
|
+
*
|
|
21113
|
+
* See:
|
|
21114
|
+
* - https://techblog.thescore.com/2024/10/08/why-we-decided-to-change-how-the-details-element-works/
|
|
21115
|
+
* - https://github.com/whatwg/html/issues/4500
|
|
21116
|
+
* - https://stackoverflow.com/questions/58942600/react-html-details-toggles-uncontrollably-when-starts-open
|
|
21117
|
+
*
|
|
21118
|
+
*/
|
|
20896
21119
|
|
|
20897
|
-
|
|
20898
|
-
|
|
20899
|
-
|
|
20900
|
-
|
|
20901
|
-
|
|
20902
|
-
|
|
20903
|
-
|
|
20904
|
-
|
|
20905
|
-
|
|
20906
|
-
|
|
21120
|
+
const summaryRef = useRef(null);
|
|
21121
|
+
useKeyboardShortcuts(ref, [{
|
|
21122
|
+
key: openKeyShortcut,
|
|
21123
|
+
enabled: arrowKeyShortcuts,
|
|
21124
|
+
when: e => document.activeElement === summaryRef.current &&
|
|
21125
|
+
// avoid handling openKeyShortcut twice when keydown occurs inside nested details
|
|
21126
|
+
!e.defaultPrevented,
|
|
21127
|
+
action: e => {
|
|
21128
|
+
const details = ref.current;
|
|
21129
|
+
if (!details.open) {
|
|
21130
|
+
e.preventDefault();
|
|
21131
|
+
details.open = true;
|
|
21132
|
+
return;
|
|
21133
|
+
}
|
|
21134
|
+
const summary = summaryRef.current;
|
|
21135
|
+
const firstFocusableElementInDetails = findAfter(summary, elementIsFocusable, {
|
|
21136
|
+
root: details
|
|
21137
|
+
});
|
|
21138
|
+
if (!firstFocusableElementInDetails) {
|
|
21139
|
+
return;
|
|
21140
|
+
}
|
|
21141
|
+
e.preventDefault();
|
|
21142
|
+
firstFocusableElementInDetails.focus();
|
|
21143
|
+
}
|
|
21144
|
+
}, {
|
|
21145
|
+
key: closeKeyShortcut,
|
|
21146
|
+
enabled: arrowKeyShortcuts,
|
|
21147
|
+
when: () => {
|
|
21148
|
+
const details = ref.current;
|
|
21149
|
+
return details.open;
|
|
21150
|
+
},
|
|
21151
|
+
action: e => {
|
|
21152
|
+
const details = ref.current;
|
|
21153
|
+
const summary = summaryRef.current;
|
|
21154
|
+
if (document.activeElement === summary) {
|
|
21155
|
+
e.preventDefault();
|
|
21156
|
+
summary.focus();
|
|
21157
|
+
details.open = false;
|
|
21158
|
+
} else {
|
|
21159
|
+
e.preventDefault();
|
|
21160
|
+
summary.focus();
|
|
21161
|
+
}
|
|
21162
|
+
}
|
|
21163
|
+
}]);
|
|
21164
|
+
const mountedRef = useRef(false);
|
|
21165
|
+
useEffect(() => {
|
|
21166
|
+
mountedRef.current = true;
|
|
21167
|
+
}, []);
|
|
21168
|
+
return jsxs(Box, {
|
|
21169
|
+
as: "details",
|
|
21170
|
+
...rest,
|
|
21171
|
+
ref: ref,
|
|
21172
|
+
id: id,
|
|
21173
|
+
baseClassName: "navi_details",
|
|
21174
|
+
onToggle: e => {
|
|
21175
|
+
const isOpen = e.newState === "open";
|
|
21176
|
+
if (mountedRef.current) {
|
|
21177
|
+
if (isOpen) {
|
|
21178
|
+
uiStateController.setUIState(true, e);
|
|
21179
|
+
} else {
|
|
21180
|
+
uiStateController.setUIState(false, e);
|
|
21181
|
+
}
|
|
21182
|
+
}
|
|
21183
|
+
onToggle?.(e);
|
|
21184
|
+
},
|
|
21185
|
+
open: open,
|
|
21186
|
+
children: [jsx("summary", {
|
|
21187
|
+
ref: summaryRef,
|
|
21188
|
+
children: jsxs("div", {
|
|
21189
|
+
className: "navi_summary_body",
|
|
21190
|
+
children: [jsx(SummaryMarker, {
|
|
21191
|
+
open: open,
|
|
21192
|
+
loading: loading
|
|
21193
|
+
}), jsx("div", {
|
|
21194
|
+
className: "navi_summary_label",
|
|
21195
|
+
children: label
|
|
21196
|
+
})]
|
|
21197
|
+
})
|
|
21198
|
+
}), children]
|
|
21199
|
+
});
|
|
21200
|
+
};
|
|
21201
|
+
const DetailsWithAction = props => {
|
|
21202
|
+
const uiStateController = useContext(UIStateControllerContext);
|
|
21203
|
+
const {
|
|
21204
|
+
action,
|
|
21205
|
+
loading,
|
|
21206
|
+
onToggle,
|
|
21207
|
+
onCancel,
|
|
21208
|
+
onActionPrevented,
|
|
21209
|
+
onActionStart,
|
|
21210
|
+
onActionAbort,
|
|
21211
|
+
onActionError,
|
|
21212
|
+
onActionEnd,
|
|
21213
|
+
children,
|
|
21214
|
+
...rest
|
|
21215
|
+
} = props;
|
|
21216
|
+
const defaultRef = useRef();
|
|
21217
|
+
const ref = rest.ref || defaultRef;
|
|
21218
|
+
const effectiveAction = useAction(action);
|
|
21219
|
+
const actionStatus = useActionStatus(effectiveAction);
|
|
21220
|
+
const {
|
|
21221
|
+
loading: actionLoading
|
|
21222
|
+
} = actionStatus;
|
|
21223
|
+
const executeAction = useExecuteAction(ref, {
|
|
21224
|
+
// the error will be displayed by actionRenderer inside <details>
|
|
21225
|
+
errorEffect: "none"
|
|
21226
|
+
});
|
|
21227
|
+
useActionEvents(ref, {
|
|
21228
|
+
onCancel: (e, reason) => {
|
|
21229
|
+
if (reason === "blur_invalid") {
|
|
21230
|
+
return;
|
|
21231
|
+
}
|
|
21232
|
+
uiStateController.resetUIState(e);
|
|
21233
|
+
onCancel?.(e, reason);
|
|
21234
|
+
},
|
|
21235
|
+
onPrevented: onActionPrevented,
|
|
21236
|
+
onRequested: e => forwardActionRequested(e, effectiveAction),
|
|
21237
|
+
onAction: executeAction,
|
|
21238
|
+
onStart: onActionStart,
|
|
21239
|
+
onAbort: e => {
|
|
21240
|
+
uiStateController.resetUIState(e);
|
|
21241
|
+
onActionAbort?.(e);
|
|
21242
|
+
},
|
|
21243
|
+
onError: e => {
|
|
21244
|
+
uiStateController.resetUIState(e);
|
|
21245
|
+
onActionError?.(e);
|
|
21246
|
+
},
|
|
21247
|
+
onEnd: e => {
|
|
21248
|
+
onActionEnd?.(e);
|
|
21249
|
+
}
|
|
21250
|
+
});
|
|
21251
|
+
return jsx(DetailsBasic, {
|
|
21252
|
+
...rest,
|
|
21253
|
+
ref: ref,
|
|
21254
|
+
loading: loading || actionLoading,
|
|
21255
|
+
onToggle: toggleEvent => {
|
|
21256
|
+
const isOpen = toggleEvent.newState === "open";
|
|
21257
|
+
if (isOpen) {
|
|
21258
|
+
dispatchActionRequestedCustomEvent(toggleEvent.target, {
|
|
21259
|
+
event: toggleEvent,
|
|
21260
|
+
requester: toggleEvent.target
|
|
21261
|
+
});
|
|
21262
|
+
} else {
|
|
21263
|
+
effectiveAction.abort();
|
|
21264
|
+
}
|
|
21265
|
+
onToggle?.(toggleEvent);
|
|
21266
|
+
},
|
|
21267
|
+
children: jsx(ActionRenderer, {
|
|
21268
|
+
action: effectiveAction,
|
|
21269
|
+
children: children
|
|
21270
|
+
})
|
|
21271
|
+
});
|
|
21272
|
+
};
|
|
21273
|
+
|
|
21274
|
+
const createAvailableConstraint = (
|
|
21275
|
+
// the set might be incomplete (the front usually don't have the full copy of all the items from the backend)
|
|
21276
|
+
// but this is already nice to help user with what we know
|
|
21277
|
+
// it's also possible that front is unsync with backend, preventing user to choose a value
|
|
21278
|
+
// that is actually free.
|
|
21279
|
+
// But this is unlikely to happen and user could reload the page to be able to choose that name
|
|
21280
|
+
// that suddenly became available
|
|
21281
|
+
existingValueSet,
|
|
21282
|
+
message = `"{value}" est utilisé. Veuillez entrer une autre valeur.`,
|
|
21283
|
+
) => {
|
|
21284
|
+
return {
|
|
21285
|
+
name: "available",
|
|
21286
|
+
messageAttribute: "data-available-message",
|
|
21287
|
+
check: (field) => {
|
|
21288
|
+
const fieldValue = field.value;
|
|
21289
|
+
const hasConflict = existingValueSet.has(fieldValue);
|
|
21290
|
+
// console.log({
|
|
21291
|
+
// inputValue,
|
|
21292
|
+
// names: Array.from(otherNameSet.values()),
|
|
21293
|
+
// hasConflict,
|
|
21294
|
+
// });
|
|
21295
|
+
if (hasConflict) {
|
|
21296
|
+
return replaceStringVars(message, {
|
|
21297
|
+
"{value}": fieldValue,
|
|
21298
|
+
});
|
|
21299
|
+
}
|
|
21300
|
+
return "";
|
|
21301
|
+
},
|
|
21302
|
+
};
|
|
21303
|
+
};
|
|
21304
|
+
|
|
21305
|
+
const DEFAULT_VALIDITY_STATE = { valid: true };
|
|
21306
|
+
const useConstraintValidityState = (ref) => {
|
|
21307
|
+
const checkValue = () => {
|
|
21308
|
+
const element = ref.current;
|
|
21309
|
+
if (!element) {
|
|
21310
|
+
return DEFAULT_VALIDITY_STATE;
|
|
21311
|
+
}
|
|
21312
|
+
const { __validationInterface__ } = element;
|
|
21313
|
+
if (!__validationInterface__) {
|
|
21314
|
+
return DEFAULT_VALIDITY_STATE;
|
|
21315
|
+
}
|
|
21316
|
+
const value = __validationInterface__.getConstraintValidityState();
|
|
21317
|
+
return value;
|
|
21318
|
+
};
|
|
21319
|
+
|
|
21320
|
+
const [constraintValidityState, setConstraintValidityState] =
|
|
21321
|
+
useState(checkValue);
|
|
21322
|
+
|
|
21323
|
+
useLayoutEffect(() => {
|
|
21324
|
+
const element = ref.current;
|
|
21325
|
+
if (!element) {
|
|
21326
|
+
return;
|
|
21327
|
+
}
|
|
21328
|
+
setConstraintValidityState(checkValue());
|
|
21329
|
+
element.addEventListener(NAVI_VALIDITY_CHANGE_CUSTOM_EVENT, () => {
|
|
21330
|
+
setConstraintValidityState(checkValue());
|
|
21331
|
+
});
|
|
21332
|
+
}, []);
|
|
21333
|
+
|
|
21334
|
+
return constraintValidityState;
|
|
21335
|
+
};
|
|
21336
|
+
|
|
21337
|
+
installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
21338
|
+
@layer navi {
|
|
21339
|
+
label {
|
|
21340
|
+
cursor: pointer;
|
|
21341
|
+
}
|
|
21342
|
+
|
|
21343
|
+
label[data-readonly],
|
|
21344
|
+
label[data-disabled] {
|
|
21345
|
+
color: rgba(0, 0, 0, 0.5);
|
|
21346
|
+
cursor: default;
|
|
21347
|
+
}
|
|
21348
|
+
}
|
|
21349
|
+
`;
|
|
21350
|
+
const ReportReadOnlyOnLabelContext = createContext();
|
|
21351
|
+
const ReportDisabledOnLabelContext = createContext();
|
|
21352
|
+
const LabelPseudoClasses = [":hover", ":active", ":focus", ":focus-visible", ":read-only", ":disabled", ":-navi-loading"];
|
|
21353
|
+
const Label = props => {
|
|
21354
|
+
const {
|
|
21355
|
+
readOnly,
|
|
21356
|
+
disabled,
|
|
21357
|
+
children,
|
|
21358
|
+
...rest
|
|
21359
|
+
} = props;
|
|
21360
|
+
const [inputReadOnly, setInputReadOnly] = useState(false);
|
|
21361
|
+
const innerReadOnly = readOnly || inputReadOnly;
|
|
21362
|
+
const [inputDisabled, setInputDisabled] = useState(false);
|
|
21363
|
+
const innerDisabled = disabled || inputDisabled;
|
|
21364
|
+
return jsx(Box, {
|
|
21365
|
+
...rest,
|
|
21366
|
+
as: "label",
|
|
21367
|
+
pseudoClasses: LabelPseudoClasses,
|
|
21368
|
+
basePseudoState: {
|
|
21369
|
+
":read-only": innerReadOnly,
|
|
21370
|
+
":disabled": innerDisabled
|
|
21371
|
+
},
|
|
21372
|
+
children: jsx(ReportReadOnlyOnLabelContext.Provider, {
|
|
21373
|
+
value: setInputReadOnly,
|
|
21374
|
+
children: jsx(ReportDisabledOnLabelContext.Provider, {
|
|
21375
|
+
value: setInputDisabled,
|
|
21376
|
+
children: children
|
|
21377
|
+
})
|
|
21378
|
+
})
|
|
21379
|
+
});
|
|
21380
|
+
};
|
|
21381
|
+
|
|
21382
|
+
installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
21383
|
+
@layer navi {
|
|
21384
|
+
.navi_checkbox {
|
|
21385
|
+
--margin: 3px 3px 3px 4px;
|
|
21386
|
+
--outline-offset: 1px;
|
|
21387
|
+
--outline-width: 2px;
|
|
21388
|
+
--border-width: 1px;
|
|
21389
|
+
--border-radius: 2px;
|
|
21390
|
+
--width: 0.815em;
|
|
21391
|
+
--height: 0.815em;
|
|
21392
|
+
|
|
21393
|
+
--outline-color: var(--navi-focus-outline-color);
|
|
21394
|
+
--loader-color: var(--navi-loader-color);
|
|
21395
|
+
--border-color: light-dark(#767676, #8e8e93);
|
|
21396
|
+
--background-color: white;
|
|
21397
|
+
--accent-color: light-dark(#4476ff, #3b82f6);
|
|
21398
|
+
--background-color-checked: var(--accent-color);
|
|
21399
|
+
--border-color-checked: var(--accent-color);
|
|
21400
|
+
--checkmark-color-light: white;
|
|
21401
|
+
--checkmark-color-dark: rgb(55, 55, 55);
|
|
21402
|
+
--checkmark-color: var(--checkmark-color-light);
|
|
21403
|
+
--cursor: pointer;
|
|
21404
|
+
|
|
21405
|
+
--color-mix-light: black;
|
|
21406
|
+
--color-mix-dark: white;
|
|
21407
|
+
--color-mix: var(--color-mix-light);
|
|
21408
|
+
|
|
21409
|
+
/* Hover */
|
|
21410
|
+
--border-color-hover: color-mix(in srgb, var(--border-color) 60%, black);
|
|
21411
|
+
--border-color-hover-checked: color-mix(
|
|
21412
|
+
in srgb,
|
|
21413
|
+
var(--border-color-checked) 80%,
|
|
21414
|
+
var(--color-mix)
|
|
21415
|
+
);
|
|
21416
|
+
--background-color-hover: var(--background-color);
|
|
21417
|
+
--background-color-hover-checked: color-mix(
|
|
21418
|
+
in srgb,
|
|
20907
21419
|
var(--background-color-checked) 80%,
|
|
20908
21420
|
var(--color-mix)
|
|
20909
21421
|
);
|
|
@@ -22638,6 +23150,7 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
|
22638
23150
|
--border-width: 1px;
|
|
22639
23151
|
--outline-width: 1px;
|
|
22640
23152
|
--outer-width: calc(var(--border-width) + var(--outline-width));
|
|
23153
|
+
--font-size: 14px;
|
|
22641
23154
|
|
|
22642
23155
|
/* Default */
|
|
22643
23156
|
--outline-color: var(--navi-focus-outline-color);
|
|
@@ -22657,6 +23170,9 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
|
22657
23170
|
--color-hover: var(--color);
|
|
22658
23171
|
/* Active */
|
|
22659
23172
|
--border-color-active: color-mix(in srgb, var(--border-color) 90%, black);
|
|
23173
|
+
/* Focus */
|
|
23174
|
+
--border-color-focus: var(--border-color);
|
|
23175
|
+
--background-color-focus: var(--background-color);
|
|
22660
23176
|
/* Readonly */
|
|
22661
23177
|
--border-color-readonly: color-mix(
|
|
22662
23178
|
in srgb,
|
|
@@ -22685,6 +23201,8 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
|
22685
23201
|
border-radius: inherit;
|
|
22686
23202
|
cursor: inherit;
|
|
22687
23203
|
|
|
23204
|
+
--start-icon-size: 0;
|
|
23205
|
+
--end-icon-size: 0;
|
|
22688
23206
|
--x-outline-width: var(--outline-width);
|
|
22689
23207
|
--x-border-radius: var(--border-radius);
|
|
22690
23208
|
--x-border-width: var(--border-width);
|
|
@@ -22695,19 +23213,31 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
|
22695
23213
|
--x-color: var(--color);
|
|
22696
23214
|
--x-placeholder-color: var(--placeholder-color);
|
|
22697
23215
|
|
|
23216
|
+
--x-padding-top-base: var(
|
|
23217
|
+
--padding-top,
|
|
23218
|
+
var(--padding-y, var(--padding, 1px))
|
|
23219
|
+
);
|
|
23220
|
+
--x-padding-right-base: var(
|
|
23221
|
+
--padding-right,
|
|
23222
|
+
var(--padding-x, var(--padding, 2px))
|
|
23223
|
+
);
|
|
23224
|
+
--x-padding-bottom-base: var(
|
|
23225
|
+
--padding-bottom,
|
|
23226
|
+
var(--padding-y, var(--padding, 1px))
|
|
23227
|
+
);
|
|
23228
|
+
--x-padding-left-base: var(
|
|
23229
|
+
--padding-left,
|
|
23230
|
+
var(--padding-x, var(--padding, 2px))
|
|
23231
|
+
);
|
|
23232
|
+
|
|
22698
23233
|
.navi_native_input {
|
|
22699
23234
|
box-sizing: border-box;
|
|
22700
|
-
padding-top: var(--padding-top
|
|
22701
|
-
padding-right: var(
|
|
22702
|
-
|
|
22703
|
-
|
|
22704
|
-
);
|
|
22705
|
-
padding-bottom: var(
|
|
22706
|
-
--padding-bottom,
|
|
22707
|
-
var(--padding-y, var(--padding, 1px))
|
|
22708
|
-
);
|
|
22709
|
-
padding-left: var(--padding-left, var(--padding-x, var(--padding, 2px)));
|
|
23235
|
+
padding-top: var(--x-padding-top-base);
|
|
23236
|
+
padding-right: calc(var(--x-padding-right-base) + var(--end-icon-size));
|
|
23237
|
+
padding-bottom: var(--x-padding-bottom-base);
|
|
23238
|
+
padding-left: calc(var(--x-padding-left-base) + var(--start-icon-size));
|
|
22710
23239
|
color: var(--x-color);
|
|
23240
|
+
font-size: var(--font-size);
|
|
22711
23241
|
background-color: var(--x-background-color);
|
|
22712
23242
|
border-width: var(--x-outer-width);
|
|
22713
23243
|
border-width: var(--x-outer-width);
|
|
@@ -22732,17 +23262,19 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
|
22732
23262
|
position: absolute;
|
|
22733
23263
|
top: 0;
|
|
22734
23264
|
bottom: 0;
|
|
22735
|
-
left:
|
|
23265
|
+
left: var(--x-padding-left-base);
|
|
23266
|
+
font-size: var(--font-size);
|
|
22736
23267
|
}
|
|
22737
23268
|
.navi_input_end_button {
|
|
22738
23269
|
position: absolute;
|
|
22739
23270
|
top: 0;
|
|
22740
|
-
right:
|
|
23271
|
+
right: var(--x-padding-right-base);
|
|
22741
23272
|
bottom: 0;
|
|
22742
23273
|
display: inline-flex;
|
|
22743
23274
|
margin: 0;
|
|
22744
23275
|
padding: 0;
|
|
22745
23276
|
justify-content: center;
|
|
23277
|
+
font-size: var(--font-size);
|
|
22746
23278
|
background: none;
|
|
22747
23279
|
border: none;
|
|
22748
23280
|
opacity: 0;
|
|
@@ -22768,17 +23300,48 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
|
22768
23300
|
}
|
|
22769
23301
|
}
|
|
22770
23302
|
}
|
|
22771
|
-
|
|
22772
23303
|
&[data-start-icon] {
|
|
22773
|
-
|
|
22774
|
-
padding-left: 20px;
|
|
22775
|
-
}
|
|
23304
|
+
--start-icon-size: 1em;
|
|
22776
23305
|
}
|
|
22777
23306
|
&[data-end-icon] {
|
|
23307
|
+
--end-icon-size: 1em;
|
|
23308
|
+
}
|
|
23309
|
+
|
|
23310
|
+
/* Readonly */
|
|
23311
|
+
&[data-readonly] {
|
|
23312
|
+
--x-border-color: var(--border-color-readonly);
|
|
23313
|
+
--x-background-color: var(--background-color-readonly);
|
|
23314
|
+
--x-color: var(--color-readonly);
|
|
23315
|
+
}
|
|
23316
|
+
/* Focus */
|
|
23317
|
+
&[data-focus],
|
|
23318
|
+
&[data-focus-visible] {
|
|
23319
|
+
--x-background-color: var(--background-color-focus);
|
|
23320
|
+
--x-border-color: var(--border-color-focus);
|
|
23321
|
+
|
|
22778
23322
|
.navi_native_input {
|
|
22779
|
-
|
|
23323
|
+
outline-width: var(--x-outer-width);
|
|
23324
|
+
outline-offset: calc(-1 * var(--x-outer-width));
|
|
23325
|
+
--x-border-color: var(--x-outline-color);
|
|
22780
23326
|
}
|
|
22781
23327
|
}
|
|
23328
|
+
/* Hover */
|
|
23329
|
+
&[data-hover] {
|
|
23330
|
+
--x-background-color: var(--background-color-hover);
|
|
23331
|
+
--x-border-color: var(--border-color-hover);
|
|
23332
|
+
--x-color: var(--color-hover);
|
|
23333
|
+
}
|
|
23334
|
+
|
|
23335
|
+
/* Disabled */
|
|
23336
|
+
&[data-disabled] {
|
|
23337
|
+
--x-border-color: var(--border-color-disabled);
|
|
23338
|
+
--x-background-color: var(--background-color-disabled);
|
|
23339
|
+
--x-color: var(--color-disabled);
|
|
23340
|
+
}
|
|
23341
|
+
/* Callout (info, warning, error) */
|
|
23342
|
+
&[data-callout] {
|
|
23343
|
+
--x-border-color: var(--callout-color);
|
|
23344
|
+
}
|
|
22782
23345
|
}
|
|
22783
23346
|
|
|
22784
23347
|
.navi_input .navi_native_input::placeholder {
|
|
@@ -22790,29 +23353,6 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
|
22790
23353
|
/* Fortunately we can override it as follow */
|
|
22791
23354
|
-webkit-text-fill-color: var(--x-color) !important;
|
|
22792
23355
|
}
|
|
22793
|
-
/* Readonly */
|
|
22794
|
-
.navi_input[data-readonly] {
|
|
22795
|
-
--x-border-color: var(--border-color-readonly);
|
|
22796
|
-
--x-background-color: var(--background-color-readonly);
|
|
22797
|
-
--x-color: var(--color-readonly);
|
|
22798
|
-
}
|
|
22799
|
-
/* Focus */
|
|
22800
|
-
.navi_input[data-focus] .navi_native_input,
|
|
22801
|
-
.navi_input[data-focus-visible] .navi_native_input {
|
|
22802
|
-
outline-width: var(--x-outer-width);
|
|
22803
|
-
outline-offset: calc(-1 * var(--x-outer-width));
|
|
22804
|
-
--x-border-color: var(--x-outline-color);
|
|
22805
|
-
}
|
|
22806
|
-
/* Disabled */
|
|
22807
|
-
.navi_input[data-disabled] {
|
|
22808
|
-
--x-border-color: var(--border-color-disabled);
|
|
22809
|
-
--x-background-color: var(--background-color-disabled);
|
|
22810
|
-
--x-color: var(--color-disabled);
|
|
22811
|
-
}
|
|
22812
|
-
/* Callout (info, warning, error) */
|
|
22813
|
-
.navi_input[data-callout] {
|
|
22814
|
-
--x-border-color: var(--callout-color);
|
|
22815
|
-
}
|
|
22816
23356
|
`;
|
|
22817
23357
|
const InputTextual = props => {
|
|
22818
23358
|
const uiStateController = useUIStateController(props, "input");
|
|
@@ -22833,10 +23373,14 @@ const InputStyleCSSVars = {
|
|
|
22833
23373
|
"outlineWidth": "--outline-width",
|
|
22834
23374
|
"borderWidth": "--border-width",
|
|
22835
23375
|
"borderRadius": "--border-radius",
|
|
23376
|
+
"padding": "--padding",
|
|
23377
|
+
"paddingX": "--padding-x",
|
|
23378
|
+
"paddingY": "--padding-y",
|
|
22836
23379
|
"paddingTop": "--padding-top",
|
|
22837
23380
|
"paddingRight": "--padding-right",
|
|
22838
23381
|
"paddingBottom": "--padding-bottom",
|
|
22839
23382
|
"paddingLeft": "--padding-left",
|
|
23383
|
+
"background": "--background",
|
|
22840
23384
|
"backgroundColor": "--background-color",
|
|
22841
23385
|
"borderColor": "--border-color",
|
|
22842
23386
|
"color": "--color",
|
|
@@ -22845,7 +23389,12 @@ const InputStyleCSSVars = {
|
|
|
22845
23389
|
borderColor: "--border-color-hover",
|
|
22846
23390
|
color: "--color-hover"
|
|
22847
23391
|
},
|
|
23392
|
+
":focus": {
|
|
23393
|
+
backgroundColor: "--background-color-focus",
|
|
23394
|
+
borderColor: "--border-color-focus"
|
|
23395
|
+
},
|
|
22848
23396
|
":active": {
|
|
23397
|
+
backgroundColor: "--background-color-active",
|
|
22849
23398
|
borderColor: "--border-color-active"
|
|
22850
23399
|
},
|
|
22851
23400
|
":read-only": {
|
|
@@ -24355,24 +24904,6 @@ const createItemTracker = () => {
|
|
|
24355
24904
|
return [useItemTrackerProvider, useTrackItem, useTrackedItem, useTrackedItems];
|
|
24356
24905
|
};
|
|
24357
24906
|
|
|
24358
|
-
const useFocusGroup = (
|
|
24359
|
-
elementRef,
|
|
24360
|
-
{ enabled = true, direction, skipTab, loop, name } = {},
|
|
24361
|
-
) => {
|
|
24362
|
-
useLayoutEffect(() => {
|
|
24363
|
-
if (!enabled) {
|
|
24364
|
-
return null;
|
|
24365
|
-
}
|
|
24366
|
-
const focusGroup = initFocusGroup(elementRef.current, {
|
|
24367
|
-
direction,
|
|
24368
|
-
skipTab,
|
|
24369
|
-
loop,
|
|
24370
|
-
name,
|
|
24371
|
-
});
|
|
24372
|
-
return focusGroup.cleanup;
|
|
24373
|
-
}, [direction, skipTab, loop, name]);
|
|
24374
|
-
};
|
|
24375
|
-
|
|
24376
24907
|
const Z_INDEX_EDITING = 1; /* To go above neighbours, but should not be too big to stay under the sticky cells */
|
|
24377
24908
|
|
|
24378
24909
|
/* needed because cell uses position:relative, sticky must win even if before in DOM order */
|
|
@@ -28136,454 +28667,103 @@ const CodeBox = ({
|
|
|
28136
28667
|
...props
|
|
28137
28668
|
}) => {
|
|
28138
28669
|
return jsx(Text, {
|
|
28139
|
-
as: "pre",
|
|
28140
|
-
...props,
|
|
28141
|
-
children: jsx(Text, {
|
|
28142
|
-
as: "code",
|
|
28143
|
-
children: children
|
|
28144
|
-
})
|
|
28145
|
-
});
|
|
28146
|
-
};
|
|
28147
|
-
|
|
28148
|
-
const Paragraph = props => {
|
|
28149
|
-
return jsx(Text, {
|
|
28150
|
-
marginTop: "md",
|
|
28151
|
-
...props,
|
|
28152
|
-
as: "p",
|
|
28153
|
-
...props
|
|
28154
|
-
});
|
|
28155
|
-
};
|
|
28156
|
-
|
|
28157
|
-
const Image = props => {
|
|
28158
|
-
return jsx(Box, {
|
|
28159
|
-
...props,
|
|
28160
|
-
as: "img"
|
|
28161
|
-
});
|
|
28162
|
-
};
|
|
28163
|
-
|
|
28164
|
-
const Svg = props => {
|
|
28165
|
-
return jsx(Box, {
|
|
28166
|
-
...props,
|
|
28167
|
-
as: "svg"
|
|
28168
|
-
});
|
|
28169
|
-
};
|
|
28170
|
-
|
|
28171
|
-
installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
28172
|
-
.svg_mask_content * {
|
|
28173
|
-
color: black !important;
|
|
28174
|
-
opacity: 1 !important;
|
|
28175
|
-
fill: black !important;
|
|
28176
|
-
fill-opacity: 1 !important;
|
|
28177
|
-
stroke: black !important;
|
|
28178
|
-
stroke-opacity: 1 !important;
|
|
28179
|
-
}
|
|
28180
|
-
`;
|
|
28181
|
-
const SVGMaskOverlay = ({
|
|
28182
|
-
viewBox,
|
|
28183
|
-
children
|
|
28184
|
-
}) => {
|
|
28185
|
-
if (!Array.isArray(children)) {
|
|
28186
|
-
return children;
|
|
28187
|
-
}
|
|
28188
|
-
if (children.length === 1) {
|
|
28189
|
-
return children[0];
|
|
28190
|
-
}
|
|
28191
|
-
if (!viewBox) {
|
|
28192
|
-
console.error("SVGComposition requires an explicit viewBox");
|
|
28193
|
-
return null;
|
|
28194
|
-
}
|
|
28195
|
-
|
|
28196
|
-
// First SVG is the base, all others are overlays
|
|
28197
|
-
const [baseSvg, ...overlaySvgs] = children;
|
|
28198
|
-
|
|
28199
|
-
// Generate unique ID for this instance
|
|
28200
|
-
const instanceId = `svgmo-${Math.random().toString(36).slice(2, 9)}`;
|
|
28201
|
-
|
|
28202
|
-
// Create nested masked elements
|
|
28203
|
-
let maskedElement = baseSvg;
|
|
28204
|
-
|
|
28205
|
-
// Apply each mask in sequence
|
|
28206
|
-
overlaySvgs.forEach((overlaySvg, index) => {
|
|
28207
|
-
const maskId = `mask-${instanceId}-${index}`;
|
|
28208
|
-
maskedElement = jsx("g", {
|
|
28209
|
-
mask: `url(#${maskId})`,
|
|
28210
|
-
children: maskedElement
|
|
28211
|
-
});
|
|
28212
|
-
});
|
|
28213
|
-
return jsxs("svg", {
|
|
28214
|
-
viewBox: viewBox,
|
|
28215
|
-
width: "100%",
|
|
28216
|
-
height: "100%",
|
|
28217
|
-
children: [jsx("defs", {
|
|
28218
|
-
children: overlaySvgs.map((overlaySvg, index) => {
|
|
28219
|
-
const maskId = `mask-${instanceId}-${index}`;
|
|
28220
|
-
|
|
28221
|
-
// IMPORTANT: clone the overlay SVG exactly as is, just add the mask class
|
|
28222
|
-
return jsxs("mask", {
|
|
28223
|
-
id: maskId,
|
|
28224
|
-
children: [jsx("rect", {
|
|
28225
|
-
width: "100%",
|
|
28226
|
-
height: "100%",
|
|
28227
|
-
fill: "white"
|
|
28228
|
-
}), cloneElement(overlaySvg, {
|
|
28229
|
-
className: "svg_mask_content" // Apply styling to make it black
|
|
28230
|
-
})]
|
|
28231
|
-
}, maskId);
|
|
28232
|
-
})
|
|
28233
|
-
}), maskedElement, overlaySvgs]
|
|
28234
|
-
});
|
|
28235
|
-
};
|
|
28236
|
-
|
|
28237
|
-
installImportMetaCss(import.meta);const rightArrowPath = "M680-480L360-160l-80-80 240-240-240-240 80-80 320 320z";
|
|
28238
|
-
const downArrowPath = "M480-280L160-600l80-80 240 240 240-240 80 80-320 320z";
|
|
28239
|
-
import.meta.css = /* css */`
|
|
28240
|
-
.summary_marker {
|
|
28241
|
-
width: 1em;
|
|
28242
|
-
height: 1em;
|
|
28243
|
-
line-height: 1em;
|
|
28244
|
-
}
|
|
28245
|
-
.summary_marker_svg .arrow {
|
|
28246
|
-
animation-duration: 0.3s;
|
|
28247
|
-
animation-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
28248
|
-
animation-fill-mode: forwards;
|
|
28249
|
-
}
|
|
28250
|
-
.summary_marker_svg .arrow[data-animation-target="down"] {
|
|
28251
|
-
animation-name: morph-to-down;
|
|
28252
|
-
}
|
|
28253
|
-
@keyframes morph-to-down {
|
|
28254
|
-
from {
|
|
28255
|
-
d: path("${rightArrowPath}");
|
|
28256
|
-
}
|
|
28257
|
-
to {
|
|
28258
|
-
d: path("${downArrowPath}");
|
|
28259
|
-
}
|
|
28260
|
-
}
|
|
28261
|
-
.summary_marker_svg .arrow[data-animation-target="right"] {
|
|
28262
|
-
animation-name: morph-to-right;
|
|
28263
|
-
}
|
|
28264
|
-
@keyframes morph-to-right {
|
|
28265
|
-
from {
|
|
28266
|
-
d: path("${downArrowPath}");
|
|
28267
|
-
}
|
|
28268
|
-
to {
|
|
28269
|
-
d: path("${rightArrowPath}");
|
|
28270
|
-
}
|
|
28271
|
-
}
|
|
28272
|
-
|
|
28273
|
-
.summary_marker_svg .foreground_circle {
|
|
28274
|
-
stroke-dasharray: 503 1507; /* ~25% of circle perimeter */
|
|
28275
|
-
stroke-dashoffset: 0;
|
|
28276
|
-
animation: progress-around-circle 1.5s linear infinite;
|
|
28277
|
-
}
|
|
28278
|
-
@keyframes progress-around-circle {
|
|
28279
|
-
0% {
|
|
28280
|
-
stroke-dashoffset: 0;
|
|
28281
|
-
}
|
|
28282
|
-
100% {
|
|
28283
|
-
stroke-dashoffset: -2010;
|
|
28284
|
-
}
|
|
28285
|
-
}
|
|
28286
|
-
|
|
28287
|
-
/* fading and scaling */
|
|
28288
|
-
.summary_marker_svg .arrow {
|
|
28289
|
-
opacity: 1;
|
|
28290
|
-
transition: opacity 0.3s ease-in-out;
|
|
28291
|
-
}
|
|
28292
|
-
.summary_marker_svg .loading_container {
|
|
28293
|
-
transform: scale(0.3);
|
|
28294
|
-
transition: transform 0.3s linear;
|
|
28295
|
-
}
|
|
28296
|
-
.summary_marker_svg .background_circle,
|
|
28297
|
-
.summary_marker_svg .foreground_circle {
|
|
28298
|
-
opacity: 0;
|
|
28299
|
-
transition: opacity 0.3s ease-in-out;
|
|
28300
|
-
}
|
|
28301
|
-
.summary_marker_svg[data-loading] .arrow {
|
|
28302
|
-
opacity: 0;
|
|
28303
|
-
}
|
|
28304
|
-
.summary_marker_svg[data-loading] .loading_container {
|
|
28305
|
-
transform: scale(1);
|
|
28306
|
-
}
|
|
28307
|
-
.summary_marker_svg[data-loading] .background_circle {
|
|
28308
|
-
opacity: 0.2;
|
|
28309
|
-
}
|
|
28310
|
-
.summary_marker_svg[data-loading] .foreground_circle {
|
|
28311
|
-
opacity: 1;
|
|
28312
|
-
}
|
|
28313
|
-
`;
|
|
28314
|
-
const SummaryMarker = ({
|
|
28315
|
-
open,
|
|
28316
|
-
loading
|
|
28317
|
-
}) => {
|
|
28318
|
-
const showLoading = useDebounceTrue(loading, 300);
|
|
28319
|
-
const mountedRef = useRef(false);
|
|
28320
|
-
const prevOpenRef = useRef(open);
|
|
28321
|
-
useLayoutEffect(() => {
|
|
28322
|
-
mountedRef.current = true;
|
|
28323
|
-
return () => {
|
|
28324
|
-
mountedRef.current = false;
|
|
28325
|
-
};
|
|
28326
|
-
}, []);
|
|
28327
|
-
const shouldAnimate = mountedRef.current && prevOpenRef.current !== open;
|
|
28328
|
-
prevOpenRef.current = open;
|
|
28329
|
-
return jsx("span", {
|
|
28330
|
-
className: "summary_marker",
|
|
28331
|
-
children: jsxs("svg", {
|
|
28332
|
-
className: "summary_marker_svg",
|
|
28333
|
-
viewBox: "0 -960 960 960",
|
|
28334
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
28335
|
-
"data-loading": open ? showLoading || undefined : undefined,
|
|
28336
|
-
children: [jsxs("g", {
|
|
28337
|
-
className: "loading_container",
|
|
28338
|
-
"transform-origin": "480px -480px",
|
|
28339
|
-
children: [jsx("circle", {
|
|
28340
|
-
className: "background_circle",
|
|
28341
|
-
cx: "480",
|
|
28342
|
-
cy: "-480",
|
|
28343
|
-
r: "320",
|
|
28344
|
-
stroke: "currentColor",
|
|
28345
|
-
fill: "none",
|
|
28346
|
-
strokeWidth: "60",
|
|
28347
|
-
opacity: "0.2"
|
|
28348
|
-
}), jsx("circle", {
|
|
28349
|
-
className: "foreground_circle",
|
|
28350
|
-
cx: "480",
|
|
28351
|
-
cy: "-480",
|
|
28352
|
-
r: "320",
|
|
28353
|
-
stroke: "currentColor",
|
|
28354
|
-
fill: "none",
|
|
28355
|
-
strokeWidth: "60",
|
|
28356
|
-
strokeLinecap: "round",
|
|
28357
|
-
strokeDasharray: "503 1507"
|
|
28358
|
-
})]
|
|
28359
|
-
}), jsx("g", {
|
|
28360
|
-
className: "arrow_container",
|
|
28361
|
-
"transform-origin": "480px -480px",
|
|
28362
|
-
children: jsx("path", {
|
|
28363
|
-
className: "arrow",
|
|
28364
|
-
fill: "currentColor",
|
|
28365
|
-
"data-animation-target": shouldAnimate ? open ? "down" : "right" : undefined,
|
|
28366
|
-
d: open ? downArrowPath : rightArrowPath
|
|
28367
|
-
})
|
|
28368
|
-
})]
|
|
28670
|
+
as: "pre",
|
|
28671
|
+
...props,
|
|
28672
|
+
children: jsx(Text, {
|
|
28673
|
+
as: "code",
|
|
28674
|
+
children: children
|
|
28369
28675
|
})
|
|
28370
28676
|
});
|
|
28371
28677
|
};
|
|
28372
28678
|
|
|
28679
|
+
const Paragraph = props => {
|
|
28680
|
+
return jsx(Text, {
|
|
28681
|
+
marginTop: "md",
|
|
28682
|
+
...props,
|
|
28683
|
+
as: "p",
|
|
28684
|
+
...props
|
|
28685
|
+
});
|
|
28686
|
+
};
|
|
28687
|
+
|
|
28688
|
+
const Image = props => {
|
|
28689
|
+
return jsx(Box, {
|
|
28690
|
+
...props,
|
|
28691
|
+
as: "img"
|
|
28692
|
+
});
|
|
28693
|
+
};
|
|
28694
|
+
|
|
28695
|
+
const Svg = props => {
|
|
28696
|
+
return jsx(Box, {
|
|
28697
|
+
...props,
|
|
28698
|
+
as: "svg"
|
|
28699
|
+
});
|
|
28700
|
+
};
|
|
28701
|
+
|
|
28373
28702
|
installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
28374
|
-
.
|
|
28375
|
-
|
|
28376
|
-
|
|
28377
|
-
|
|
28378
|
-
|
|
28379
|
-
|
|
28703
|
+
.svg_mask_content * {
|
|
28704
|
+
color: black !important;
|
|
28705
|
+
opacity: 1 !important;
|
|
28706
|
+
fill: black !important;
|
|
28707
|
+
fill-opacity: 1 !important;
|
|
28708
|
+
stroke: black !important;
|
|
28709
|
+
stroke-opacity: 1 !important;
|
|
28380
28710
|
}
|
|
28381
|
-
|
|
28382
|
-
|
|
28383
|
-
|
|
28384
|
-
|
|
28385
|
-
|
|
28386
|
-
|
|
28387
|
-
|
|
28711
|
+
`;
|
|
28712
|
+
const SVGMaskOverlay = ({
|
|
28713
|
+
viewBox,
|
|
28714
|
+
children
|
|
28715
|
+
}) => {
|
|
28716
|
+
if (!Array.isArray(children)) {
|
|
28717
|
+
return children;
|
|
28388
28718
|
}
|
|
28389
|
-
.
|
|
28390
|
-
|
|
28391
|
-
width: 100%;
|
|
28392
|
-
flex-direction: row;
|
|
28393
|
-
align-items: center;
|
|
28394
|
-
gap: 0.2em;
|
|
28719
|
+
if (children.length === 1) {
|
|
28720
|
+
return children[0];
|
|
28395
28721
|
}
|
|
28396
|
-
|
|
28397
|
-
|
|
28398
|
-
|
|
28399
|
-
flex: 1;
|
|
28400
|
-
align-items: center;
|
|
28401
|
-
gap: 0.2em;
|
|
28722
|
+
if (!viewBox) {
|
|
28723
|
+
console.error("SVGComposition requires an explicit viewBox");
|
|
28724
|
+
return null;
|
|
28402
28725
|
}
|
|
28403
28726
|
|
|
28404
|
-
|
|
28405
|
-
|
|
28406
|
-
}
|
|
28407
|
-
`;
|
|
28408
|
-
const Details = forwardRef((props, ref) => {
|
|
28409
|
-
return renderActionableComponent(props, ref);
|
|
28410
|
-
});
|
|
28411
|
-
const DetailsBasic = forwardRef((props, ref) => {
|
|
28412
|
-
const {
|
|
28413
|
-
id,
|
|
28414
|
-
label = "Summary",
|
|
28415
|
-
open,
|
|
28416
|
-
loading,
|
|
28417
|
-
className,
|
|
28418
|
-
focusGroup,
|
|
28419
|
-
focusGroupDirection,
|
|
28420
|
-
arrowKeyShortcuts = true,
|
|
28421
|
-
openKeyShortcut = "ArrowRight",
|
|
28422
|
-
closeKeyShortcut = "ArrowLeft",
|
|
28423
|
-
onToggle,
|
|
28424
|
-
children,
|
|
28425
|
-
...rest
|
|
28426
|
-
} = props;
|
|
28427
|
-
const innerRef = useRef();
|
|
28428
|
-
useImperativeHandle(ref, () => innerRef.current);
|
|
28429
|
-
const [navState, setNavState] = useNavState$1(id);
|
|
28430
|
-
const [innerOpen, innerOpenSetter] = useState(open || navState);
|
|
28431
|
-
useFocusGroup(innerRef, {
|
|
28432
|
-
enabled: focusGroup,
|
|
28433
|
-
name: typeof focusGroup === "string" ? focusGroup : undefined,
|
|
28434
|
-
direction: focusGroupDirection
|
|
28435
|
-
});
|
|
28727
|
+
// First SVG is the base, all others are overlays
|
|
28728
|
+
const [baseSvg, ...overlaySvgs] = children;
|
|
28436
28729
|
|
|
28437
|
-
|
|
28438
|
-
|
|
28439
|
-
* When rendering the component for the first time
|
|
28440
|
-
* We have to ensure the initial "toggle" event is ignored.
|
|
28441
|
-
*
|
|
28442
|
-
* If we don't do that code will think the details has changed and run logic accordingly
|
|
28443
|
-
* For example it will try to navigate to the current url while we are already there
|
|
28444
|
-
*
|
|
28445
|
-
* See:
|
|
28446
|
-
* - https://techblog.thescore.com/2024/10/08/why-we-decided-to-change-how-the-details-element-works/
|
|
28447
|
-
* - https://github.com/whatwg/html/issues/4500
|
|
28448
|
-
* - https://stackoverflow.com/questions/58942600/react-html-details-toggles-uncontrollably-when-starts-open
|
|
28449
|
-
*
|
|
28450
|
-
*/
|
|
28730
|
+
// Generate unique ID for this instance
|
|
28731
|
+
const instanceId = `svgmo-${Math.random().toString(36).slice(2, 9)}`;
|
|
28451
28732
|
|
|
28452
|
-
|
|
28453
|
-
|
|
28454
|
-
|
|
28455
|
-
|
|
28456
|
-
|
|
28457
|
-
|
|
28458
|
-
|
|
28459
|
-
|
|
28460
|
-
|
|
28461
|
-
|
|
28462
|
-
e.preventDefault();
|
|
28463
|
-
details.open = true;
|
|
28464
|
-
return;
|
|
28465
|
-
}
|
|
28466
|
-
const summary = summaryRef.current;
|
|
28467
|
-
const firstFocusableElementInDetails = findAfter(summary, elementIsFocusable, {
|
|
28468
|
-
root: details
|
|
28469
|
-
});
|
|
28470
|
-
if (!firstFocusableElementInDetails) {
|
|
28471
|
-
return;
|
|
28472
|
-
}
|
|
28473
|
-
e.preventDefault();
|
|
28474
|
-
firstFocusableElementInDetails.focus();
|
|
28475
|
-
}
|
|
28476
|
-
}, {
|
|
28477
|
-
key: closeKeyShortcut,
|
|
28478
|
-
enabled: arrowKeyShortcuts,
|
|
28479
|
-
when: () => {
|
|
28480
|
-
const details = innerRef.current;
|
|
28481
|
-
return details.open;
|
|
28482
|
-
},
|
|
28483
|
-
action: e => {
|
|
28484
|
-
const details = innerRef.current;
|
|
28485
|
-
const summary = summaryRef.current;
|
|
28486
|
-
if (document.activeElement === summary) {
|
|
28487
|
-
e.preventDefault();
|
|
28488
|
-
summary.focus();
|
|
28489
|
-
details.open = false;
|
|
28490
|
-
} else {
|
|
28491
|
-
e.preventDefault();
|
|
28492
|
-
summary.focus();
|
|
28493
|
-
}
|
|
28494
|
-
}
|
|
28495
|
-
}]);
|
|
28496
|
-
const mountedRef = useRef(false);
|
|
28497
|
-
useEffect(() => {
|
|
28498
|
-
mountedRef.current = true;
|
|
28499
|
-
}, []);
|
|
28500
|
-
return jsxs("details", {
|
|
28501
|
-
...rest,
|
|
28502
|
-
ref: innerRef,
|
|
28503
|
-
id: id,
|
|
28504
|
-
className: ["navi_details", ...(className ? className.split(" ") : [])].join(" "),
|
|
28505
|
-
onToggle: e => {
|
|
28506
|
-
const isOpen = e.newState === "open";
|
|
28507
|
-
if (mountedRef.current) {
|
|
28508
|
-
if (isOpen) {
|
|
28509
|
-
innerOpenSetter(true);
|
|
28510
|
-
setNavState(true);
|
|
28511
|
-
} else {
|
|
28512
|
-
innerOpenSetter(false);
|
|
28513
|
-
setNavState(undefined);
|
|
28514
|
-
}
|
|
28515
|
-
}
|
|
28516
|
-
onToggle?.(e);
|
|
28517
|
-
},
|
|
28518
|
-
open: innerOpen,
|
|
28519
|
-
children: [jsx("summary", {
|
|
28520
|
-
ref: summaryRef,
|
|
28521
|
-
children: jsxs("div", {
|
|
28522
|
-
className: "summary_body",
|
|
28523
|
-
children: [jsx(SummaryMarker, {
|
|
28524
|
-
open: innerOpen,
|
|
28525
|
-
loading: loading
|
|
28526
|
-
}), jsx("div", {
|
|
28527
|
-
className: "summary_label",
|
|
28528
|
-
children: label
|
|
28529
|
-
})]
|
|
28530
|
-
})
|
|
28531
|
-
}), children]
|
|
28532
|
-
});
|
|
28533
|
-
});
|
|
28534
|
-
forwardRef((props, ref) => {
|
|
28535
|
-
const {
|
|
28536
|
-
action,
|
|
28537
|
-
loading,
|
|
28538
|
-
onToggle,
|
|
28539
|
-
onActionPrevented,
|
|
28540
|
-
onActionStart,
|
|
28541
|
-
onActionError,
|
|
28542
|
-
onActionEnd,
|
|
28543
|
-
children,
|
|
28544
|
-
...rest
|
|
28545
|
-
} = props;
|
|
28546
|
-
const innerRef = useRef();
|
|
28547
|
-
useImperativeHandle(ref, () => innerRef.current);
|
|
28548
|
-
const effectiveAction = useAction(action);
|
|
28549
|
-
const {
|
|
28550
|
-
loading: actionLoading
|
|
28551
|
-
} = useActionStatus(effectiveAction);
|
|
28552
|
-
const executeAction = useExecuteAction(innerRef, {
|
|
28553
|
-
// the error will be displayed by actionRenderer inside <details>
|
|
28554
|
-
errorEffect: "none"
|
|
28555
|
-
});
|
|
28556
|
-
useActionEvents(innerRef, {
|
|
28557
|
-
onPrevented: onActionPrevented,
|
|
28558
|
-
onAction: e => {
|
|
28559
|
-
executeAction(e);
|
|
28560
|
-
},
|
|
28561
|
-
onStart: onActionStart,
|
|
28562
|
-
onError: onActionError,
|
|
28563
|
-
onEnd: onActionEnd
|
|
28733
|
+
// Create nested masked elements
|
|
28734
|
+
let maskedElement = baseSvg;
|
|
28735
|
+
|
|
28736
|
+
// Apply each mask in sequence
|
|
28737
|
+
overlaySvgs.forEach((overlaySvg, index) => {
|
|
28738
|
+
const maskId = `mask-${instanceId}-${index}`;
|
|
28739
|
+
maskedElement = jsx("g", {
|
|
28740
|
+
mask: `url(#${maskId})`,
|
|
28741
|
+
children: maskedElement
|
|
28742
|
+
});
|
|
28564
28743
|
});
|
|
28565
|
-
return
|
|
28566
|
-
|
|
28567
|
-
|
|
28568
|
-
|
|
28569
|
-
|
|
28570
|
-
|
|
28571
|
-
|
|
28572
|
-
|
|
28573
|
-
|
|
28574
|
-
|
|
28575
|
-
|
|
28576
|
-
|
|
28577
|
-
|
|
28578
|
-
|
|
28579
|
-
|
|
28580
|
-
|
|
28581
|
-
|
|
28582
|
-
|
|
28583
|
-
|
|
28584
|
-
|
|
28744
|
+
return jsxs("svg", {
|
|
28745
|
+
viewBox: viewBox,
|
|
28746
|
+
width: "100%",
|
|
28747
|
+
height: "100%",
|
|
28748
|
+
children: [jsx("defs", {
|
|
28749
|
+
children: overlaySvgs.map((overlaySvg, index) => {
|
|
28750
|
+
const maskId = `mask-${instanceId}-${index}`;
|
|
28751
|
+
|
|
28752
|
+
// IMPORTANT: clone the overlay SVG exactly as is, just add the mask class
|
|
28753
|
+
return jsxs("mask", {
|
|
28754
|
+
id: maskId,
|
|
28755
|
+
children: [jsx("rect", {
|
|
28756
|
+
width: "100%",
|
|
28757
|
+
height: "100%",
|
|
28758
|
+
fill: "white"
|
|
28759
|
+
}), cloneElement(overlaySvg, {
|
|
28760
|
+
className: "svg_mask_content" // Apply styling to make it black
|
|
28761
|
+
})]
|
|
28762
|
+
}, maskId);
|
|
28763
|
+
})
|
|
28764
|
+
}), maskedElement, overlaySvgs]
|
|
28585
28765
|
});
|
|
28586
|
-
}
|
|
28766
|
+
};
|
|
28587
28767
|
|
|
28588
28768
|
installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
28589
28769
|
@layer navi {
|