@jsenv/navi 0.29.155 → 0.29.157
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 +370 -164
- package/dist/jsenv_navi.js.map +31 -23
- package/docs/AI_INSTRUCTIONS.md +8 -3
- package/docs/actions.md +22 -0
- package/docs/resource.md +61 -1
- package/docs/route_transitions.md +6 -2
- package/docs/testid.md +65 -1
- package/package.json +1 -1
package/dist/jsenv_navi.js
CHANGED
|
@@ -888,6 +888,157 @@ const isPlainObject$2 = obj => {
|
|
|
888
888
|
return Object.getPrototypeOf(obj) === proto || Object.getPrototypeOf(obj) === null;
|
|
889
889
|
};
|
|
890
890
|
|
|
891
|
+
const isSignal = (value) => {
|
|
892
|
+
return getSignalType(value) !== null;
|
|
893
|
+
};
|
|
894
|
+
|
|
895
|
+
const BRAND_SYMBOL = Symbol.for("preact-signals");
|
|
896
|
+
const getSignalType = (value) => {
|
|
897
|
+
if (!value || typeof value !== "object") {
|
|
898
|
+
return null;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
if (value.brand !== BRAND_SYMBOL) {
|
|
902
|
+
return null;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
if (typeof value._fn === "function") {
|
|
906
|
+
return "computed";
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
return "signal";
|
|
910
|
+
};
|
|
911
|
+
|
|
912
|
+
const MAX_ENTRIES = 5;
|
|
913
|
+
|
|
914
|
+
const stringifyForDisplay = (
|
|
915
|
+
value,
|
|
916
|
+
maxDepth = 2,
|
|
917
|
+
currentDepth = 0,
|
|
918
|
+
options = {},
|
|
919
|
+
) => {
|
|
920
|
+
const { asFunctionArgs = false } = options;
|
|
921
|
+
const indent = " ".repeat(currentDepth);
|
|
922
|
+
const nextIndent = " ".repeat(currentDepth + 1);
|
|
923
|
+
|
|
924
|
+
if (currentDepth >= maxDepth) {
|
|
925
|
+
return typeof value === "object" && value !== null
|
|
926
|
+
? "[Object]"
|
|
927
|
+
: String(value);
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
if (value === null) {
|
|
931
|
+
return "null";
|
|
932
|
+
}
|
|
933
|
+
if (value === undefined) {
|
|
934
|
+
return "undefined";
|
|
935
|
+
}
|
|
936
|
+
if (typeof value === "string") {
|
|
937
|
+
return `"${value}"`;
|
|
938
|
+
}
|
|
939
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
940
|
+
return String(value);
|
|
941
|
+
}
|
|
942
|
+
if (typeof value === "function") {
|
|
943
|
+
return `[Function ${value.name || "anonymous"}]`;
|
|
944
|
+
}
|
|
945
|
+
if (value instanceof Date) {
|
|
946
|
+
return `Date(${value.toISOString()})`;
|
|
947
|
+
}
|
|
948
|
+
if (value instanceof RegExp) {
|
|
949
|
+
return value.toString();
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
if (Array.isArray(value)) {
|
|
953
|
+
const openBracket = asFunctionArgs ? "(" : "[";
|
|
954
|
+
const closeBracket = asFunctionArgs ? ")" : "]";
|
|
955
|
+
|
|
956
|
+
if (value.length === 0) return `${openBracket}${closeBracket}`;
|
|
957
|
+
|
|
958
|
+
// Display arrays with only one element on a single line
|
|
959
|
+
if (value.length === 1) {
|
|
960
|
+
const item = stringifyForDisplay(
|
|
961
|
+
value[0],
|
|
962
|
+
maxDepth,
|
|
963
|
+
currentDepth + 1,
|
|
964
|
+
// Remove asFunctionArgs for nested calls
|
|
965
|
+
{ ...options, asFunctionArgs: false },
|
|
966
|
+
);
|
|
967
|
+
return `${openBracket}${item}${closeBracket}`;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
if (value.length > MAX_ENTRIES) {
|
|
971
|
+
const preview = value
|
|
972
|
+
.slice(0, MAX_ENTRIES)
|
|
973
|
+
.map(
|
|
974
|
+
(v) =>
|
|
975
|
+
`${nextIndent}${stringifyForDisplay(v, maxDepth, currentDepth + 1, { ...options, asFunctionArgs: false })}`,
|
|
976
|
+
);
|
|
977
|
+
return `${openBracket}\n${preview.join(",\n")},\n${nextIndent}...${value.length - MAX_ENTRIES} more\n${indent}${closeBracket}`;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
const items = value.map(
|
|
981
|
+
(v) =>
|
|
982
|
+
`${nextIndent}${stringifyForDisplay(v, maxDepth, currentDepth + 1, { ...options, asFunctionArgs: false })}`,
|
|
983
|
+
);
|
|
984
|
+
return `${openBracket}\n${items.join(",\n")}\n${indent}${closeBracket}`;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
if (typeof value === "object") {
|
|
988
|
+
const signalType = getSignalType(value);
|
|
989
|
+
if (signalType) {
|
|
990
|
+
const signalValue = value.peek();
|
|
991
|
+
const prefix = signalType === "computed" ? "computed" : "signal";
|
|
992
|
+
return `${prefix}(${stringifyForDisplay(signalValue, maxDepth, currentDepth, { ...options, asFunctionArgs: false })})`;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
const entries = Object.entries(value);
|
|
996
|
+
if (entries.length === 0) return "{}";
|
|
997
|
+
|
|
998
|
+
// ✅ Inclure les clés avec valeurs undefined/null
|
|
999
|
+
const allEntries = [];
|
|
1000
|
+
for (const [key, val] of entries) {
|
|
1001
|
+
allEntries.push([key, val]);
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
// Ajouter les clés avec undefined (que Object.entries omet)
|
|
1005
|
+
const descriptor = Object.getOwnPropertyDescriptors(value);
|
|
1006
|
+
for (const [key, desc] of Object.entries(descriptor)) {
|
|
1007
|
+
if (desc.value === undefined && !entries.some(([k]) => k === key)) {
|
|
1008
|
+
allEntries.push([key, undefined]);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// Display objects with only one key on a single line
|
|
1013
|
+
if (allEntries.length === 1) {
|
|
1014
|
+
const [key, val] = allEntries[0];
|
|
1015
|
+
const valueStr = stringifyForDisplay(val, maxDepth, currentDepth + 1, {
|
|
1016
|
+
...options,
|
|
1017
|
+
asFunctionArgs: false,
|
|
1018
|
+
});
|
|
1019
|
+
return `{ ${key}: ${valueStr} }`;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
if (allEntries.length > MAX_ENTRIES) {
|
|
1023
|
+
const preview = allEntries
|
|
1024
|
+
.slice(0, MAX_ENTRIES)
|
|
1025
|
+
.map(
|
|
1026
|
+
([k, v]) =>
|
|
1027
|
+
`${nextIndent}${k}: ${stringifyForDisplay(v, maxDepth, currentDepth + 1, { ...options, asFunctionArgs: false })}`,
|
|
1028
|
+
);
|
|
1029
|
+
return `{\n${preview.join(",\n")},\n${nextIndent}...${allEntries.length - MAX_ENTRIES} more\n${indent}}`;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
const pairs = allEntries.map(
|
|
1033
|
+
([k, v]) =>
|
|
1034
|
+
`${nextIndent}${k}: ${stringifyForDisplay(v, maxDepth, currentDepth + 1, { ...options, asFunctionArgs: false })}`,
|
|
1035
|
+
);
|
|
1036
|
+
return `{\n${pairs.join(",\n")}\n${indent}}`;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
return String(value);
|
|
1040
|
+
};
|
|
1041
|
+
|
|
891
1042
|
/*
|
|
892
1043
|
* Deep structural equality for arbitrary JS values — what `===` can't do but this
|
|
893
1044
|
* codebase constantly needs: memoization cache keys ({ id: 1 } equal to { id: 1 }),
|
|
@@ -898,9 +1049,9 @@ const isPlainObject$2 = obj => {
|
|
|
898
1049
|
* "wrong" for equality purposes: NaN equals NaN, Date compared by time value,
|
|
899
1050
|
* cycles don't loop (a set of the pairs being compared guards circular refs),
|
|
900
1051
|
* and same-type is required
|
|
901
|
-
* before descending. Functions, and objects with nothing enumerable to
|
|
902
|
-
* (a Set, a Map, an element, a URL), are equal by reference only —
|
|
903
|
-
* them says whether two are "the same". Cheap paths run first: reference
|
|
1052
|
+
* before descending. Functions, signals, and objects with nothing enumerable to
|
|
1053
|
+
* compare (a Set, a Map, an element, a URL), are equal by reference only —
|
|
1054
|
+
* nothing in them says whether two are "the same". Cheap paths run first: reference
|
|
904
1055
|
* equality, then the identity short-circuit below, then array length before
|
|
905
1056
|
* element-by-element.
|
|
906
1057
|
*
|
|
@@ -918,6 +1069,7 @@ const isPlainObject$2 = obj => {
|
|
|
918
1069
|
* compareTwoJsValues(a, b); // true immediately, no property walk
|
|
919
1070
|
*/
|
|
920
1071
|
|
|
1072
|
+
|
|
921
1073
|
// Marks objects with a conceptual identity that transcends reference equality —
|
|
922
1074
|
// see the file comment. Symbol.for keeps it one shared symbol across modules.
|
|
923
1075
|
const SYMBOL_IDENTITY = Symbol.for("navi_object_identity");
|
|
@@ -1060,6 +1212,15 @@ const compareTwoJsValues = (
|
|
|
1060
1212
|
if (aIsDate) {
|
|
1061
1213
|
return a.getTime() === b.getTime();
|
|
1062
1214
|
}
|
|
1215
|
+
// A signal is a source of truth, not a value: two of them holding the same
|
|
1216
|
+
// thing right now say nothing about the next tick, and one may change while
|
|
1217
|
+
// the other does not. Their internals (the current value, a version
|
|
1218
|
+
// counter) are enumerable, so without this they would be walked and two
|
|
1219
|
+
// unrelated signals would pass for one — a params signal owned by one
|
|
1220
|
+
// screen would then be handed the action bound to another's.
|
|
1221
|
+
if (isSignal(a) || isSignal(b)) {
|
|
1222
|
+
return false;
|
|
1223
|
+
}
|
|
1063
1224
|
const aKeys = Object.keys(a);
|
|
1064
1225
|
const bKeys = Object.keys(b);
|
|
1065
1226
|
if (aKeys.length !== bKeys.length) {
|
|
@@ -1142,159 +1303,37 @@ const debounceSignal = (
|
|
|
1142
1303
|
clearTimeout(timeoutId);
|
|
1143
1304
|
debouncedSignal.value = latestValue;
|
|
1144
1305
|
};
|
|
1306
|
+
// Whoever reads the debounced value gets the previous one during the delay,
|
|
1307
|
+
// with nothing saying a newer one is on its way. This says it.
|
|
1308
|
+
debouncedSignal.settlingSignal = computed(() => {
|
|
1309
|
+
return !compareTwoJsValues(signalToDebounce.value, debouncedSignal.value);
|
|
1310
|
+
});
|
|
1145
1311
|
|
|
1146
1312
|
return debouncedSignal;
|
|
1147
1313
|
};
|
|
1148
1314
|
|
|
1149
|
-
const
|
|
1150
|
-
return getSignalType(value) !== null;
|
|
1151
|
-
};
|
|
1152
|
-
|
|
1153
|
-
const BRAND_SYMBOL = Symbol.for("preact-signals");
|
|
1154
|
-
const getSignalType = (value) => {
|
|
1155
|
-
if (!value || typeof value !== "object") {
|
|
1156
|
-
return null;
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1159
|
-
if (value.brand !== BRAND_SYMBOL) {
|
|
1160
|
-
return null;
|
|
1161
|
-
}
|
|
1162
|
-
|
|
1163
|
-
if (typeof value._fn === "function") {
|
|
1164
|
-
return "computed";
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
|
-
return "signal";
|
|
1168
|
-
};
|
|
1315
|
+
const debouncedSignalCache = new WeakMap();
|
|
1169
1316
|
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
) => {
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
if (currentDepth >= maxDepth) {
|
|
1183
|
-
return typeof value === "object" && value !== null
|
|
1184
|
-
? "[Object]"
|
|
1185
|
-
: String(value);
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
if (value === null) {
|
|
1189
|
-
return "null";
|
|
1190
|
-
}
|
|
1191
|
-
if (value === undefined) {
|
|
1192
|
-
return "undefined";
|
|
1193
|
-
}
|
|
1194
|
-
if (typeof value === "string") {
|
|
1195
|
-
return `"${value}"`;
|
|
1196
|
-
}
|
|
1197
|
-
if (typeof value === "number" || typeof value === "boolean") {
|
|
1198
|
-
return String(value);
|
|
1199
|
-
}
|
|
1200
|
-
if (typeof value === "function") {
|
|
1201
|
-
return `[Function ${value.name || "anonymous"}]`;
|
|
1202
|
-
}
|
|
1203
|
-
if (value instanceof Date) {
|
|
1204
|
-
return `Date(${value.toISOString()})`;
|
|
1205
|
-
}
|
|
1206
|
-
if (value instanceof RegExp) {
|
|
1207
|
-
return value.toString();
|
|
1208
|
-
}
|
|
1209
|
-
|
|
1210
|
-
if (Array.isArray(value)) {
|
|
1211
|
-
const openBracket = asFunctionArgs ? "(" : "[";
|
|
1212
|
-
const closeBracket = asFunctionArgs ? ")" : "]";
|
|
1213
|
-
|
|
1214
|
-
if (value.length === 0) return `${openBracket}${closeBracket}`;
|
|
1215
|
-
|
|
1216
|
-
// Display arrays with only one element on a single line
|
|
1217
|
-
if (value.length === 1) {
|
|
1218
|
-
const item = stringifyForDisplay(
|
|
1219
|
-
value[0],
|
|
1220
|
-
maxDepth,
|
|
1221
|
-
currentDepth + 1,
|
|
1222
|
-
// Remove asFunctionArgs for nested calls
|
|
1223
|
-
{ ...options, asFunctionArgs: false },
|
|
1224
|
-
);
|
|
1225
|
-
return `${openBracket}${item}${closeBracket}`;
|
|
1226
|
-
}
|
|
1227
|
-
|
|
1228
|
-
if (value.length > MAX_ENTRIES) {
|
|
1229
|
-
const preview = value
|
|
1230
|
-
.slice(0, MAX_ENTRIES)
|
|
1231
|
-
.map(
|
|
1232
|
-
(v) =>
|
|
1233
|
-
`${nextIndent}${stringifyForDisplay(v, maxDepth, currentDepth + 1, { ...options, asFunctionArgs: false })}`,
|
|
1234
|
-
);
|
|
1235
|
-
return `${openBracket}\n${preview.join(",\n")},\n${nextIndent}...${value.length - MAX_ENTRIES} more\n${indent}${closeBracket}`;
|
|
1236
|
-
}
|
|
1237
|
-
|
|
1238
|
-
const items = value.map(
|
|
1239
|
-
(v) =>
|
|
1240
|
-
`${nextIndent}${stringifyForDisplay(v, maxDepth, currentDepth + 1, { ...options, asFunctionArgs: false })}`,
|
|
1241
|
-
);
|
|
1242
|
-
return `${openBracket}\n${items.join(",\n")}\n${indent}${closeBracket}`;
|
|
1317
|
+
/**
|
|
1318
|
+
* The one debounced signal for a given source and delay.
|
|
1319
|
+
*
|
|
1320
|
+
* Two callers asking the same question of the same signal must wait on the same
|
|
1321
|
+
* answer: a debounced signal per caller is a timer per caller, and the actions
|
|
1322
|
+
* derived from them are then several instances of what is one request.
|
|
1323
|
+
*/
|
|
1324
|
+
const getDebouncedSignal = (signalToDebounce, delay) => {
|
|
1325
|
+
let byDelay = debouncedSignalCache.get(signalToDebounce);
|
|
1326
|
+
if (!byDelay) {
|
|
1327
|
+
byDelay = new Map();
|
|
1328
|
+
debouncedSignalCache.set(signalToDebounce, byDelay);
|
|
1243
1329
|
}
|
|
1244
|
-
|
|
1245
|
-
if (
|
|
1246
|
-
|
|
1247
|
-
if (signalType) {
|
|
1248
|
-
const signalValue = value.peek();
|
|
1249
|
-
const prefix = signalType === "computed" ? "computed" : "signal";
|
|
1250
|
-
return `${prefix}(${stringifyForDisplay(signalValue, maxDepth, currentDepth, { ...options, asFunctionArgs: false })})`;
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
const entries = Object.entries(value);
|
|
1254
|
-
if (entries.length === 0) return "{}";
|
|
1255
|
-
|
|
1256
|
-
// ✅ Inclure les clés avec valeurs undefined/null
|
|
1257
|
-
const allEntries = [];
|
|
1258
|
-
for (const [key, val] of entries) {
|
|
1259
|
-
allEntries.push([key, val]);
|
|
1260
|
-
}
|
|
1261
|
-
|
|
1262
|
-
// Ajouter les clés avec undefined (que Object.entries omet)
|
|
1263
|
-
const descriptor = Object.getOwnPropertyDescriptors(value);
|
|
1264
|
-
for (const [key, desc] of Object.entries(descriptor)) {
|
|
1265
|
-
if (desc.value === undefined && !entries.some(([k]) => k === key)) {
|
|
1266
|
-
allEntries.push([key, undefined]);
|
|
1267
|
-
}
|
|
1268
|
-
}
|
|
1269
|
-
|
|
1270
|
-
// Display objects with only one key on a single line
|
|
1271
|
-
if (allEntries.length === 1) {
|
|
1272
|
-
const [key, val] = allEntries[0];
|
|
1273
|
-
const valueStr = stringifyForDisplay(val, maxDepth, currentDepth + 1, {
|
|
1274
|
-
...options,
|
|
1275
|
-
asFunctionArgs: false,
|
|
1276
|
-
});
|
|
1277
|
-
return `{ ${key}: ${valueStr} }`;
|
|
1278
|
-
}
|
|
1279
|
-
|
|
1280
|
-
if (allEntries.length > MAX_ENTRIES) {
|
|
1281
|
-
const preview = allEntries
|
|
1282
|
-
.slice(0, MAX_ENTRIES)
|
|
1283
|
-
.map(
|
|
1284
|
-
([k, v]) =>
|
|
1285
|
-
`${nextIndent}${k}: ${stringifyForDisplay(v, maxDepth, currentDepth + 1, { ...options, asFunctionArgs: false })}`,
|
|
1286
|
-
);
|
|
1287
|
-
return `{\n${preview.join(",\n")},\n${nextIndent}...${allEntries.length - MAX_ENTRIES} more\n${indent}}`;
|
|
1288
|
-
}
|
|
1289
|
-
|
|
1290
|
-
const pairs = allEntries.map(
|
|
1291
|
-
([k, v]) =>
|
|
1292
|
-
`${nextIndent}${k}: ${stringifyForDisplay(v, maxDepth, currentDepth + 1, { ...options, asFunctionArgs: false })}`,
|
|
1293
|
-
);
|
|
1294
|
-
return `{\n${pairs.join(",\n")}\n${indent}}`;
|
|
1330
|
+
const existing = byDelay.get(delay);
|
|
1331
|
+
if (existing) {
|
|
1332
|
+
return existing;
|
|
1295
1333
|
}
|
|
1296
|
-
|
|
1297
|
-
|
|
1334
|
+
const debouncedSignal = debounceSignal(signalToDebounce, { delay });
|
|
1335
|
+
byDelay.set(delay, debouncedSignal);
|
|
1336
|
+
return debouncedSignal;
|
|
1298
1337
|
};
|
|
1299
1338
|
|
|
1300
1339
|
/**
|
|
@@ -3157,6 +3196,10 @@ const SYMBOL_OBJECT_SIGNAL = Symbol.for("navi_object_signal");
|
|
|
3157
3196
|
* routing or lists.
|
|
3158
3197
|
*/
|
|
3159
3198
|
|
|
3199
|
+
// Params waiting to settle exist only under a debounced binding; every other
|
|
3200
|
+
// action answers "no" so nobody has to check whether it can be asked.
|
|
3201
|
+
const NOT_SETTLING_SIGNAL = signal(false);
|
|
3202
|
+
|
|
3160
3203
|
let DEBUG$2 = false;
|
|
3161
3204
|
const enableDebugActions = () => {
|
|
3162
3205
|
DEBUG$2 = true;
|
|
@@ -3816,12 +3859,53 @@ const createAction = (callback, rootOptions = {}) => {
|
|
|
3816
3859
|
...options,
|
|
3817
3860
|
});
|
|
3818
3861
|
};
|
|
3862
|
+
/**
|
|
3863
|
+
* The action instance for these params.
|
|
3864
|
+
*
|
|
3865
|
+
* @param {any} newParamsOrSignal - params, or a signal holding them (the
|
|
3866
|
+
* result then retargets itself as the signal changes), or an object whose
|
|
3867
|
+
* values may be signals.
|
|
3868
|
+
* @param {object} [options]
|
|
3869
|
+
* @param {number} [options.debounce] - milliseconds the params must stay
|
|
3870
|
+
* stable before the instance follows them. What it buys is a screen that
|
|
3871
|
+
* owns its params — a search box, a picker — asking one question instead
|
|
3872
|
+
* of one per keystroke, without an effect: the binding IS the delay.
|
|
3873
|
+
* Only for a signal; params that cannot change have nothing to settle.
|
|
3874
|
+
* During the delay the instance is still the previous one, holding the
|
|
3875
|
+
* previous answer — `paramsSettlingSignal` is what says a newer one is
|
|
3876
|
+
* coming, and `useAsyncData(action, { loading: true })` reads it.
|
|
3877
|
+
*/
|
|
3819
3878
|
const bindParams = (newParamsOrSignal, options = {}) => {
|
|
3879
|
+
const { debounce, ...optionsWithoutDebounce } = options;
|
|
3880
|
+
if (debounce) {
|
|
3881
|
+
if (!isSignal(newParamsOrSignal)) {
|
|
3882
|
+
console.warn(
|
|
3883
|
+
`bindParams({ debounce }) expects a signal, received ${typeof newParamsOrSignal}: params that cannot change have nothing to settle.`,
|
|
3884
|
+
);
|
|
3885
|
+
} else {
|
|
3886
|
+
// The delay lives in a signal derived from theirs, one per (signal,
|
|
3887
|
+
// delay): the cache below then keys on it like on any other signal,
|
|
3888
|
+
// so two call sites asking the same question with the same delay get
|
|
3889
|
+
// the one instance, and two different delays get two.
|
|
3890
|
+
const debouncedParamsSignal = getDebouncedSignal(
|
|
3891
|
+
newParamsOrSignal,
|
|
3892
|
+
debounce,
|
|
3893
|
+
);
|
|
3894
|
+
return bindParams(debouncedParamsSignal, {
|
|
3895
|
+
...optionsWithoutDebounce,
|
|
3896
|
+
syncParams: debouncedParamsSignal.flush,
|
|
3897
|
+
paramsSettlingSignal: debouncedParamsSignal.settlingSignal,
|
|
3898
|
+
});
|
|
3899
|
+
}
|
|
3900
|
+
}
|
|
3820
3901
|
const existingChildAction = childActionWeakMap.get(newParamsOrSignal);
|
|
3821
3902
|
if (existingChildAction) {
|
|
3822
3903
|
return existingChildAction;
|
|
3823
3904
|
}
|
|
3824
|
-
const childAction = _bindParams(
|
|
3905
|
+
const childAction = _bindParams(
|
|
3906
|
+
newParamsOrSignal,
|
|
3907
|
+
optionsWithoutDebounce,
|
|
3908
|
+
);
|
|
3825
3909
|
childActionWeakMap.set(newParamsOrSignal, childAction);
|
|
3826
3910
|
if (childAction !== action) {
|
|
3827
3911
|
// binding that added nothing resolves to the action itself; it must
|
|
@@ -3964,6 +4048,7 @@ const createAction = (callback, rootOptions = {}) => {
|
|
|
3964
4048
|
},
|
|
3965
4049
|
|
|
3966
4050
|
paramsSignal,
|
|
4051
|
+
paramsSettlingSignal: NOT_SETTLING_SIGNAL,
|
|
3967
4052
|
runningStateSignal,
|
|
3968
4053
|
isPrerunSignal,
|
|
3969
4054
|
valueSignal,
|
|
@@ -4297,6 +4382,7 @@ const createActionProxyFromSignal = (
|
|
|
4297
4382
|
inheritData = true,
|
|
4298
4383
|
onChange,
|
|
4299
4384
|
syncParams,
|
|
4385
|
+
paramsSettlingSignal = NOT_SETTLING_SIGNAL,
|
|
4300
4386
|
} = {},
|
|
4301
4387
|
) => {
|
|
4302
4388
|
const actionTargetChangeCallbackSet = new Set();
|
|
@@ -4473,6 +4559,7 @@ const createActionProxyFromSignal = (
|
|
|
4473
4559
|
meta: action.meta,
|
|
4474
4560
|
|
|
4475
4561
|
paramsSignal: proxyParamsSignal,
|
|
4562
|
+
paramsSettlingSignal,
|
|
4476
4563
|
isPrerunSignal: proxySignal("isPrerunSignal", "isPrerun"),
|
|
4477
4564
|
runningStateSignal: proxySignal("runningStateSignal", "runningState"),
|
|
4478
4565
|
errorSignal: proxySignal("errorSignal", "error"),
|
|
@@ -4629,7 +4716,9 @@ const isPlainObject = (obj) => {
|
|
|
4629
4716
|
* @param {number} [options.debounce] - When set, the action is only run once the derived params
|
|
4630
4717
|
* have been stable for this many milliseconds. Useful to avoid firing a backend call on every
|
|
4631
4718
|
* keystroke: set `debounce: 500` and the request is sent only after the user stops interacting
|
|
4632
|
-
* with the filters for 500 ms.
|
|
4719
|
+
* with the filters for 500 ms. During that delay the action still holds the previous answer;
|
|
4720
|
+
* `action.paramsSettlingSignal` is what says a newer one is coming, and
|
|
4721
|
+
* `useAsyncData(action, { loading: true })` reads it.
|
|
4633
4722
|
*
|
|
4634
4723
|
* Example — auto-refresh a result list while the user tweaks filters:
|
|
4635
4724
|
* ```js
|
|
@@ -4659,7 +4748,7 @@ const actionRunEffect = (
|
|
|
4659
4748
|
if (typeof action === "function") {
|
|
4660
4749
|
action = createAction(action);
|
|
4661
4750
|
}
|
|
4662
|
-
|
|
4751
|
+
const actionParamsSignal = computed(() => {
|
|
4663
4752
|
const params = deriveActionParamsFromSignals();
|
|
4664
4753
|
action.debug(
|
|
4665
4754
|
`Derived params for action "${action}": ${stringifyForDisplay(params)}`,
|
|
@@ -4677,14 +4766,8 @@ const actionRunEffect = (
|
|
|
4677
4766
|
}
|
|
4678
4767
|
return params;
|
|
4679
4768
|
});
|
|
4680
|
-
if (debounce) {
|
|
4681
|
-
actionParamsSignal = debounceSignal(actionParamsSignal, {
|
|
4682
|
-
delay: debounce,
|
|
4683
|
-
});
|
|
4684
|
-
}
|
|
4685
|
-
|
|
4686
4769
|
const actionRunnedByThisEffect = action.bindParams(actionParamsSignal, {
|
|
4687
|
-
|
|
4770
|
+
debounce,
|
|
4688
4771
|
onChange: (actionTarget, actionTargetPrevious, { explicitRunIntent }) => {
|
|
4689
4772
|
if (explicitRunIntent) {
|
|
4690
4773
|
// The caller already issued an explicit run/rerun/prerun/reset/abort —
|
|
@@ -5432,6 +5515,11 @@ const CONTROL_ATTRIBUTE_SET = new Set([
|
|
|
5432
5515
|
"spellcheck",
|
|
5433
5516
|
"autoCorrect",
|
|
5434
5517
|
"aria-controls",
|
|
5518
|
+
// The name goes where the role is: the root box has none, so an aria-label
|
|
5519
|
+
// left on it names nothing at all, while the host is what the user focuses
|
|
5520
|
+
// and what a screen reader announces (and what getByRole({ name }) reads).
|
|
5521
|
+
"aria-label",
|
|
5522
|
+
"aria-labelledby",
|
|
5435
5523
|
"tabIndex",
|
|
5436
5524
|
"command",
|
|
5437
5525
|
"commandFor",
|
|
@@ -6419,6 +6507,11 @@ const css$12 = /* css */`
|
|
|
6419
6507
|
* @param {Object} options - Configuration options
|
|
6420
6508
|
* @param {HTMLElement} [options.anchorElement] - Element the callout should follow. If not provided or too big, callout will be centered in viewport
|
|
6421
6509
|
* @param {string} [options.status=""] - Callout status: "info" | "warning" | "error" | "success"
|
|
6510
|
+
* @param {string} [options.testId] - `data-testid` on the callout element. The callout is
|
|
6511
|
+
* drawn by navi, so nothing the caller renders can carry the name a test needs — same
|
|
6512
|
+
* situation as a picker's `popupTestId` (see docs/testid.md). A role is already there
|
|
6513
|
+
* for free (`status`/`alert`); reach for this when the role is not enough to tell two
|
|
6514
|
+
* callouts apart.
|
|
6422
6515
|
* @param {Function} [options.onClose] - Callback when callout is closed
|
|
6423
6516
|
* @param {boolean} [options.closeOnClickOutside] - Whether to close on outside clicks (defaults to true for "info" status)
|
|
6424
6517
|
* @param {boolean} [options.icon=true] - Whether the status icon is shown beside the message.
|
|
@@ -6465,6 +6558,7 @@ const openCallout = (message, {
|
|
|
6465
6558
|
// "success" - positive feedback (e.g., "Changes saved successfully")
|
|
6466
6559
|
// "" - neutral information
|
|
6467
6560
|
status = "",
|
|
6561
|
+
testId,
|
|
6468
6562
|
onClose,
|
|
6469
6563
|
closeOnClickOutside = status === "info",
|
|
6470
6564
|
closeOnFocusLeave = closeOnClickOutside,
|
|
@@ -6642,6 +6736,15 @@ const openCallout = (message, {
|
|
|
6642
6736
|
calloutElement.removeAttribute("data-icon");
|
|
6643
6737
|
}
|
|
6644
6738
|
}
|
|
6739
|
+
if (Object.hasOwn(options, "testId")) {
|
|
6740
|
+
// Per message, like the cross below: a callout is a slot several things
|
|
6741
|
+
// speak through, and the one speaking is what a test is looking at.
|
|
6742
|
+
if (options.testId) {
|
|
6743
|
+
calloutElement.setAttribute("data-testid", options.testId);
|
|
6744
|
+
} else {
|
|
6745
|
+
calloutElement.removeAttribute("data-testid");
|
|
6746
|
+
}
|
|
6747
|
+
}
|
|
6645
6748
|
if (Object.hasOwn(options, "closeButton")) {
|
|
6646
6749
|
// Per message rather than per callout: what replaces a message (a
|
|
6647
6750
|
// constraint taking over a tooltip's callout) brings its own cross back.
|
|
@@ -7042,6 +7145,7 @@ const openCallout = (message, {
|
|
|
7042
7145
|
}
|
|
7043
7146
|
update(message, {
|
|
7044
7147
|
status,
|
|
7148
|
+
testId,
|
|
7045
7149
|
icon,
|
|
7046
7150
|
closeButton
|
|
7047
7151
|
});
|
|
@@ -7698,7 +7802,7 @@ const generateSvgWithoutArrow = (width, height) => {
|
|
|
7698
7802
|
*
|
|
7699
7803
|
* Usage:
|
|
7700
7804
|
* const myToken = createOpenToken();
|
|
7701
|
-
* calloutManager.addOpenToken(myToken, { message, status, anchorElement, event, skipFocus, onClose });
|
|
7805
|
+
* calloutManager.addOpenToken(myToken, { message, status, testId, anchorElement, event, skipFocus, onClose });
|
|
7702
7806
|
* calloutManager.removeOpenToken(myToken, event);
|
|
7703
7807
|
* calloutManager.requestCloseCallout(event, debugReason); // force-close all
|
|
7704
7808
|
* calloutManager.callout // current open callout or null
|
|
@@ -7744,6 +7848,7 @@ const createCalloutManager = (
|
|
|
7744
7848
|
const [, remainingTokenData] = tokens.entries().next().value;
|
|
7745
7849
|
callout.update(remainingTokenData.message, {
|
|
7746
7850
|
status: remainingTokenData.status,
|
|
7851
|
+
testId: remainingTokenData.testId,
|
|
7747
7852
|
icon: remainingTokenData.icon,
|
|
7748
7853
|
closeButton: remainingTokenData.closeButton,
|
|
7749
7854
|
});
|
|
@@ -7770,6 +7875,7 @@ const createCalloutManager = (
|
|
|
7770
7875
|
{
|
|
7771
7876
|
message,
|
|
7772
7877
|
status,
|
|
7878
|
+
testId,
|
|
7773
7879
|
icon,
|
|
7774
7880
|
closeButton,
|
|
7775
7881
|
anchorElement,
|
|
@@ -7784,12 +7890,13 @@ const createCalloutManager = (
|
|
|
7784
7890
|
}
|
|
7785
7891
|
const calloutOptions = {
|
|
7786
7892
|
status,
|
|
7893
|
+
testId,
|
|
7787
7894
|
icon,
|
|
7788
7895
|
closeButton,
|
|
7789
7896
|
closeOnClickOutside: status !== "error",
|
|
7790
7897
|
};
|
|
7791
7898
|
|
|
7792
|
-
tokens.set(token, { message, status, icon, closeButton, onClose });
|
|
7899
|
+
tokens.set(token, { message, status, testId, icon, closeButton, onClose });
|
|
7793
7900
|
if (callout) {
|
|
7794
7901
|
callout.update(message, calloutOptions);
|
|
7795
7902
|
return;
|
|
@@ -15150,8 +15257,12 @@ const createResource = (
|
|
|
15150
15257
|
idKey,
|
|
15151
15258
|
uniqueKeys,
|
|
15152
15259
|
|
|
15260
|
+
// Reactive reads, not hooks: they subscribe the render that calls them to
|
|
15261
|
+
// the store's array signal. Nothing hook-shaped inside, so a loop or a
|
|
15262
|
+
// condition is fine. `useAllByIds` skips the ids the store does not hold.
|
|
15153
15263
|
useArray: () => store.arraySignal.value,
|
|
15154
15264
|
useById: (id) => store.select(idKey, id),
|
|
15265
|
+
useAllByIds: (idArray) => store.selectAll(idArray),
|
|
15155
15266
|
|
|
15156
15267
|
withParams: undefined,
|
|
15157
15268
|
one: undefined,
|
|
@@ -17350,14 +17461,34 @@ const useActionAsyncData = (action, {
|
|
|
17350
17461
|
}
|
|
17351
17462
|
setTick(n => n + 1);
|
|
17352
17463
|
});
|
|
17464
|
+
// A debounced binding waits before it retargets, so nothing above changes
|
|
17465
|
+
// while the delay runs — but what is on screen is already out of date.
|
|
17466
|
+
let settlingNotificationIsInitial = true;
|
|
17467
|
+
const unsubscribeFromParamsSettling = action.paramsSettlingSignal.subscribe(() => {
|
|
17468
|
+
if (settlingNotificationIsInitial) {
|
|
17469
|
+
settlingNotificationIsInitial = false;
|
|
17470
|
+
return;
|
|
17471
|
+
}
|
|
17472
|
+
setTick(n => n + 1);
|
|
17473
|
+
});
|
|
17353
17474
|
return () => {
|
|
17354
17475
|
unsubscribeFromRunningState();
|
|
17355
17476
|
unsubscribeFromData();
|
|
17477
|
+
unsubscribeFromParamsSettling();
|
|
17356
17478
|
};
|
|
17357
17479
|
// Bound to the action, not to the mount: params given as a plain object
|
|
17358
17480
|
// make another action instance, and the component would otherwise stay
|
|
17359
17481
|
// subscribed to the state of the one it no longer reads.
|
|
17360
17482
|
}, [action]);
|
|
17483
|
+
|
|
17484
|
+
// The params moved and the run has not started: under a debounce the action
|
|
17485
|
+
// is still the previous one, COMPLETED with the previous answer. There is no
|
|
17486
|
+
// run to await yet, so nothing can be suspended on — but a component drawing
|
|
17487
|
+
// its own wait can say the answer on screen is on its way out, which is the
|
|
17488
|
+
// whole reason it asked for `loading`.
|
|
17489
|
+
if (loadingEffect === "use" && runningState !== FAILED && action.paramsSettlingSignal.peek()) {
|
|
17490
|
+
return [action.dataSignal.peek(), true, undefined];
|
|
17491
|
+
}
|
|
17361
17492
|
if (runningState === COMPLETED) {
|
|
17362
17493
|
return [action.dataSignal.peek(), false, undefined];
|
|
17363
17494
|
}
|
|
@@ -28081,8 +28212,10 @@ const RouteTransitionArea = ({
|
|
|
28081
28212
|
* @param {object} to - same forms. Going from `from` to `to` plays forward,
|
|
28082
28213
|
* the reverse plays back — unless the reverse is written as a relation of
|
|
28083
28214
|
* its own, which then owns that way (a movement of its own, or `"none"` for
|
|
28084
|
-
* a plain cut).
|
|
28085
|
-
*
|
|
28215
|
+
* a plain cut). Written with the SAME movement it owns that way all the
|
|
28216
|
+
* same, so both crossings play forward and the pair can never say "back":
|
|
28217
|
+
* that pair is warned about. A change between two pages no relation was
|
|
28218
|
+
* written for plays nothing.
|
|
28086
28219
|
* @param {string|{type?: string, duration?: number|string}} [transition] -
|
|
28087
28220
|
* what plays: a type name, or `{ type, duration }` to also say how long
|
|
28088
28221
|
* (`--navi-route-transition-duration` says it for everyone otherwise).
|
|
@@ -28131,6 +28264,7 @@ const defineRouteTransition = (from, to, transition) => {
|
|
|
28131
28264
|
type,
|
|
28132
28265
|
duration
|
|
28133
28266
|
};
|
|
28267
|
+
warnAboutBothWaysWritten(relation);
|
|
28134
28268
|
relations.push(relation);
|
|
28135
28269
|
rebuildWatcher();
|
|
28136
28270
|
return () => {
|
|
@@ -28725,6 +28859,38 @@ const currentPageIndex$1 = pages => {
|
|
|
28725
28859
|
}
|
|
28726
28860
|
return currentIndex;
|
|
28727
28861
|
};
|
|
28862
|
+
|
|
28863
|
+
// Both ways of a pair written with the SAME movement. The exact way travelled
|
|
28864
|
+
// is searched before any reverse (see findRelation), so each way then finds
|
|
28865
|
+
// its own relation and BOTH play forward: the pair loses the one thing a
|
|
28866
|
+
// direction is for. That is not a resolution to fix — it is what makes
|
|
28867
|
+
// reciprocity a default — it is a definition with no reading under which it is
|
|
28868
|
+
// what the author meant, so it is said here, where both relations are known.
|
|
28869
|
+
// Narrow on purpose: a different type is the asymmetry the reverse exists for,
|
|
28870
|
+
// "none" is silence said out loud, and a different duration is a way out taken
|
|
28871
|
+
// slower.
|
|
28872
|
+
const warnAboutBothWaysWritten = ({
|
|
28873
|
+
from,
|
|
28874
|
+
to,
|
|
28875
|
+
type,
|
|
28876
|
+
duration
|
|
28877
|
+
}) => {
|
|
28878
|
+
const reverse = relations.find(candidate => samePage$1(candidate.from, to) && samePage$1(candidate.to, from));
|
|
28879
|
+
if (!reverse) {
|
|
28880
|
+
return;
|
|
28881
|
+
}
|
|
28882
|
+
if (reverse.type !== type || reverse.duration !== duration) {
|
|
28883
|
+
return;
|
|
28884
|
+
}
|
|
28885
|
+
if (type === "none") {
|
|
28886
|
+
return;
|
|
28887
|
+
}
|
|
28888
|
+
// The one already written is named first: that is the order the application
|
|
28889
|
+
// wrote them in, and the order it will find them in to fix them.
|
|
28890
|
+
const written = `${describePage(reverse.from)} → ${describePage(reverse.to)}`;
|
|
28891
|
+
const added = `${describePage(from)} → ${describePage(to)}`;
|
|
28892
|
+
warnOnce(`both-ways-written:${written}|${added}`, `${written} and ${added} are both written with the same movement, so BOTH crossings play forward and this pair can never say "back" — the back button included. A relation written for the exact way travelled wins over being the reverse of another (see findRelation), which is what makes reciprocity the default: write the way back only to give it a DIFFERENT movement, or "none" to silence it. A single crossing that walks the map backwards says so on itself instead: <Link routeTransition={{ direction: "forward" }}>, or navTo(url, { routeTransition: { direction: "forward" } }).`);
|
|
28893
|
+
};
|
|
28728
28894
|
const warnPagesBothCurrent = (pageKept, pageIgnored) => {
|
|
28729
28895
|
const kept = describePage(pageKept);
|
|
28730
28896
|
const ignored = describePage(pageIgnored);
|
|
@@ -58630,8 +58796,15 @@ const PickerCustomResolver = props => {
|
|
|
58630
58796
|
props.variant = "icon";
|
|
58631
58797
|
}
|
|
58632
58798
|
// A door, never a field (see `allowNameless`): the form around it expects
|
|
58633
|
-
// no value from it, and no name.
|
|
58799
|
+
// no value from it, and no name. So the trigger answers to "button" — it is
|
|
58800
|
+
// pressed to read something, never typed into — the same thing a confirm
|
|
58801
|
+
// picker says through `picksNothing` (see picker.jsx). Said as the role
|
|
58802
|
+
// here rather than through that prop: `picksNothing` also takes the right
|
|
58803
|
+
// slot away, and the status icon a callout trigger draws sits in it.
|
|
58634
58804
|
props.allowNameless = true;
|
|
58805
|
+
if (props.role === undefined) {
|
|
58806
|
+
props.role = "button";
|
|
58807
|
+
}
|
|
58635
58808
|
// A word in a sentence asks for a plain tooltip — no icon in the callout,
|
|
58636
58809
|
// no status color; an icon one presses is the callout's own status icon,
|
|
58637
58810
|
// and says "info" like the callout it opens.
|
|
@@ -59315,6 +59488,7 @@ const PickerContentInsidePopup = props => {
|
|
|
59315
59488
|
children: isCallout ? jsx(PickerCalloutPopup, {
|
|
59316
59489
|
...popupProps,
|
|
59317
59490
|
pickerRef: props.ref,
|
|
59491
|
+
testId: popupTestId,
|
|
59318
59492
|
status: calloutStatus,
|
|
59319
59493
|
icon: calloutIcon,
|
|
59320
59494
|
closeButton: calloutCloseButton,
|
|
@@ -59379,6 +59553,7 @@ const PickerCalloutPopup = ({
|
|
|
59379
59553
|
anchor,
|
|
59380
59554
|
openController,
|
|
59381
59555
|
pickerRef,
|
|
59556
|
+
testId,
|
|
59382
59557
|
status,
|
|
59383
59558
|
icon,
|
|
59384
59559
|
closeButton,
|
|
@@ -59404,6 +59579,10 @@ const PickerCalloutPopup = ({
|
|
|
59404
59579
|
const anchorElement = anchor === pickerRef ? undefined : anchor && "current" in anchor ? anchor.current : anchor;
|
|
59405
59580
|
calloutManager.addOpenToken(PICKER_CALLOUT_CONTENT_TOKEN, {
|
|
59406
59581
|
message: hostRef.current,
|
|
59582
|
+
// The popup a `popupTestId` names is this callout: it is the surface the
|
|
59583
|
+
// picker opens, drawn by navi, so it is the one thing the caller cannot
|
|
59584
|
+
// name from its own children.
|
|
59585
|
+
testId,
|
|
59407
59586
|
// "none" is the picker's word for it; the callout's is no status at all.
|
|
59408
59587
|
status: status === "none" ? undefined : status,
|
|
59409
59588
|
icon,
|
|
@@ -67334,6 +67513,11 @@ const PickerButton = props => {
|
|
|
67334
67513
|
// confirm resolver (see picker_confirm.jsx), which is the picker that is
|
|
67335
67514
|
// one — a caller whose popup is a menu of actions says it for themselves.
|
|
67336
67515
|
picksNothing,
|
|
67516
|
+
// Where the caller's own role goes: onto the host, beside the name (see
|
|
67517
|
+
// control_context.js) — a role and a name on two different elements name
|
|
67518
|
+
// nothing at all. Left unsaid, a picker that picks nothing takes "button"
|
|
67519
|
+
// (see PickerInput below).
|
|
67520
|
+
role,
|
|
67337
67521
|
readOnly,
|
|
67338
67522
|
error
|
|
67339
67523
|
} = props;
|
|
@@ -67388,6 +67572,21 @@ const PickerButton = props => {
|
|
|
67388
67572
|
// clearing being a modification like any other.
|
|
67389
67573
|
const interactive = !basePseudoState[":disabled"] && !basePseudoState[":read-only"] && !loading;
|
|
67390
67574
|
usePickerErrorCallout(uiStateController, error);
|
|
67575
|
+
// What the picker really is, said on the element that holds the name and
|
|
67576
|
+
// takes the focus (the host input below): a picker that picks nothing is a
|
|
67577
|
+
// door — its popup is a question (type="confirm") or something to read
|
|
67578
|
+
// (mode="callout", which says it for itself) — and a door is pressed. So it
|
|
67579
|
+
// answers to "button" rather than to the "textbox" its <input> would
|
|
67580
|
+
// otherwise be, which is both what a screen reader announces and what
|
|
67581
|
+
// getByRole("button", { name }) finds. A picker holding a value stays a
|
|
67582
|
+
// textbox: it does hold text.
|
|
67583
|
+
const hostRole = role ?? (picksNothing ? "button" : undefined);
|
|
67584
|
+
// And what that button is called, when the caller wrote it as a drawing
|
|
67585
|
+
// rather than as a word: a door draws no value, so what sits in its value
|
|
67586
|
+
// slot IS its label — the `ui` a caller gave it, or its placeholder. Skipped
|
|
67587
|
+
// the moment the caller names it themselves (aria-label/aria-labelledby),
|
|
67588
|
+
// which is the only way to name a door drawn as an icon.
|
|
67589
|
+
const hostLabelId = hostRole === "button" && inputProps["aria-label"] === undefined && inputProps["aria-labelledby"] === undefined && variant !== "headless" && ui !== "default" ? `${inputProps.id}_picker_label` : undefined;
|
|
67391
67590
|
// What the picker knows about itself, for the pieces it does not place: the
|
|
67392
67591
|
// drawings of its value (Picker.UI.*), and the affordances a caller may put
|
|
67393
67592
|
// in their own `ui` (Picker.Clear) as much as the ones it puts in its slot.
|
|
@@ -67434,6 +67633,7 @@ const PickerButton = props => {
|
|
|
67434
67633
|
basePseudoState: basePseudoState,
|
|
67435
67634
|
styleCSSVars: PickerStyleCSSVars,
|
|
67436
67635
|
variant: undefined,
|
|
67636
|
+
role: undefined,
|
|
67437
67637
|
rightSlotIcon: undefined,
|
|
67438
67638
|
rightSlotIconSize: undefined,
|
|
67439
67639
|
rightSlot: undefined,
|
|
@@ -67482,6 +67682,8 @@ const PickerButton = props => {
|
|
|
67482
67682
|
color: "var(--picker-loader-color)",
|
|
67483
67683
|
inset: -2
|
|
67484
67684
|
}), jsx(PickerInput, {
|
|
67685
|
+
role: hostRole,
|
|
67686
|
+
"aria-labelledby": hostLabelId,
|
|
67485
67687
|
tabIndex: variant === "headless" ? -1 : undefined,
|
|
67486
67688
|
"aria-hidden": variant === "headless" ? "true" : undefined,
|
|
67487
67689
|
...inputProps,
|
|
@@ -67557,6 +67759,10 @@ const PickerButton = props => {
|
|
|
67557
67759
|
}
|
|
67558
67760
|
}), variant === "headless" || ui === "default" ? null : jsx(Text, {
|
|
67559
67761
|
className: "navi_picker_value"
|
|
67762
|
+
// Only when it names the trigger (see hostLabelId above).
|
|
67763
|
+
,
|
|
67764
|
+
|
|
67765
|
+
id: hostLabelId
|
|
67560
67766
|
// Tells the caller's own drawing of the control from the value
|
|
67561
67767
|
// the picker draws itself, so each is written on its own line
|
|
67562
67768
|
// (see .navi_picker_value in the CSS above).
|