@jsenv/navi 0.29.91 → 0.29.93
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 +520 -188
- package/dist/jsenv_navi.js.map +39 -25
- package/docs/AI_INSTRUCTIONS.md +9 -0
- package/docs/actions.md +4 -4
- package/docs/control_object.md +12 -0
- package/docs/control_value.md +50 -0
- package/package.json +1 -1
package/dist/jsenv_navi.js
CHANGED
|
@@ -342,11 +342,11 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
342
342
|
*
|
|
343
343
|
* Two decisions worth knowing before reading:
|
|
344
344
|
*
|
|
345
|
-
* - **navi places the target itself, every time.**
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
345
|
+
* - **navi places the target itself, every time.** The scroll the browser
|
|
346
|
+
* would have done — target against the top edge, instantly — is applied
|
|
347
|
+
* here, after layout, so one rule holds whether the target was there all
|
|
348
|
+
* along or arrived late; `setUrlTargetOptions` lets an app pick another
|
|
349
|
+
* alignment or a smooth behavior. A page with nothing to scroll simply
|
|
350
350
|
* does not move, which is the whole of the "the list already fits on screen"
|
|
351
351
|
* case — the transient mark alone then says which one was meant.
|
|
352
352
|
*
|
|
@@ -381,8 +381,8 @@ const css$12 = /* css */`
|
|
|
381
381
|
`;
|
|
382
382
|
import.meta.css = [css$12, "@jsenv/navi/src/nav/url_target/url_target.js"];
|
|
383
383
|
let urlTargetOptions = {
|
|
384
|
-
block: "
|
|
385
|
-
behavior: "
|
|
384
|
+
block: "start",
|
|
385
|
+
behavior: "instant",
|
|
386
386
|
markDuration: 2000,
|
|
387
387
|
graceAfterIdle: 1000,
|
|
388
388
|
maxWait: 10_000
|
|
@@ -392,12 +392,12 @@ let urlTargetOptions = {
|
|
|
392
392
|
* Adjusts how navi answers the element designated by the URL hash.
|
|
393
393
|
*
|
|
394
394
|
* @param {object} options
|
|
395
|
-
* @param {"start"|"center"|"end"|"nearest"} [options.block="
|
|
396
|
-
* Vertical alignment of the scroll. "
|
|
397
|
-
*
|
|
398
|
-
*
|
|
399
|
-
*
|
|
400
|
-
*
|
|
395
|
+
* @param {"start"|"center"|"end"|"nearest"} [options.block="start"]
|
|
396
|
+
* Vertical alignment of the scroll. "start" by default — the alignment the
|
|
397
|
+
* browser itself uses when it answers a fragment.
|
|
398
|
+
* @param {ScrollBehavior} [options.behavior="instant"]
|
|
399
|
+
* "instant" by default, like the browser. When set to "smooth", it is
|
|
400
|
+
* overridden with "instant" under `prefers-reduced-motion: reduce`.
|
|
401
401
|
* @param {number} [options.markDuration=2000]
|
|
402
402
|
* How long, in ms, the element carries `data-url-target`. Published to CSS as
|
|
403
403
|
* `--navi-url-target-duration`.
|
|
@@ -23905,7 +23905,15 @@ const useUIStateController = (
|
|
|
23905
23905
|
// Uses the in-memory registry instead of DOM queries so this works even
|
|
23906
23906
|
// when sibling items are virtualized (not in the DOM).
|
|
23907
23907
|
// Form scoping is preserved by comparing parentUIStateController references.
|
|
23908
|
-
|
|
23908
|
+
// Checked, not truthy: a radio holding `false` (two rows asking a
|
|
23909
|
+
// yes/no) is as checked as one holding a name, and the row that was
|
|
23910
|
+
// checked before has to let go all the same.
|
|
23911
|
+
if (
|
|
23912
|
+
isRadio &&
|
|
23913
|
+
newUIState !== undefined &&
|
|
23914
|
+
controller.name &&
|
|
23915
|
+
!controlProxyFor
|
|
23916
|
+
) {
|
|
23909
23917
|
const siblings = getRadioSiblings(controller);
|
|
23910
23918
|
if (siblings) {
|
|
23911
23919
|
const siblingUncheckEvent = new CustomEvent(
|
|
@@ -24200,12 +24208,25 @@ const useUIStateController = (
|
|
|
24200
24208
|
} = controlInfo;
|
|
24201
24209
|
controller.value = value;
|
|
24202
24210
|
controller.defaultValue = defaultValue;
|
|
24211
|
+
// An optimistic control with work in flight keeps the user's intent on
|
|
24212
|
+
// screen: an incoming external state is the echo of an intermediate
|
|
24213
|
+
// commit (the first of two queued toggles landing), and the queued
|
|
24214
|
+
// request about to go out was built on top of the UI state —
|
|
24215
|
+
// overwriting it would both flash the superseded value and send it.
|
|
24216
|
+
// `controller.state` is still taken below: it is the last known good
|
|
24217
|
+
// state, the rollback target if the chain fails.
|
|
24218
|
+
const optimisticWorkInFlight = Boolean(
|
|
24219
|
+
props.optimistic &&
|
|
24220
|
+
(controller.actionInFlight || controller.queuedActionAllowedEvent),
|
|
24221
|
+
);
|
|
24203
24222
|
if (hasStateProp) {
|
|
24204
24223
|
controller.hasStateProp = true;
|
|
24205
24224
|
const currentState = controller.state;
|
|
24206
24225
|
if (!compareTwoJsValues(state, currentState)) {
|
|
24207
24226
|
controller.state = state;
|
|
24208
|
-
|
|
24227
|
+
if (!optimisticWorkInFlight) {
|
|
24228
|
+
controller.setUIState(state, new CustomEvent("state_prop_change"));
|
|
24229
|
+
}
|
|
24209
24230
|
}
|
|
24210
24231
|
} else {
|
|
24211
24232
|
if (controller.hasStateProp) {
|
|
@@ -24222,10 +24243,12 @@ const useUIStateController = (
|
|
|
24222
24243
|
const currentState = controller.state;
|
|
24223
24244
|
if (!compareTwoJsValues(stateFromSignal, currentState)) {
|
|
24224
24245
|
controller.state = stateFromSignal;
|
|
24225
|
-
|
|
24226
|
-
|
|
24227
|
-
|
|
24228
|
-
|
|
24246
|
+
if (!optimisticWorkInFlight) {
|
|
24247
|
+
controller.setUIState(
|
|
24248
|
+
stateFromSignal,
|
|
24249
|
+
new CustomEvent("state_prop_change"),
|
|
24250
|
+
);
|
|
24251
|
+
}
|
|
24229
24252
|
}
|
|
24230
24253
|
}
|
|
24231
24254
|
}
|
|
@@ -24485,6 +24508,7 @@ const useUIGroupStateController = (
|
|
|
24485
24508
|
childControlFilter,
|
|
24486
24509
|
aggregateChildStates,
|
|
24487
24510
|
distributeChildUIState,
|
|
24511
|
+
distributeChildStates,
|
|
24488
24512
|
wantRequesterButtonState,
|
|
24489
24513
|
uiActionInternal,
|
|
24490
24514
|
allowCapture = false,
|
|
@@ -24501,7 +24525,7 @@ const useUIGroupStateController = (
|
|
|
24501
24525
|
// whatever IT says — a "HH:MM", an ISO duration — and the shape checks in
|
|
24502
24526
|
// setUIState below are about the default shape, not about that one.
|
|
24503
24527
|
const stateShapeIsTheDefaultOne =
|
|
24504
|
-
!aggregateChildStates && !distributeChildUIState;
|
|
24528
|
+
!aggregateChildStates && !distributeChildUIState && !distributeChildStates;
|
|
24505
24529
|
const defaults = GROUP_DEFAULTS[controlType] ?? GROUP_DEFAULTS[stateType];
|
|
24506
24530
|
const resolvedChildControlFilter =
|
|
24507
24531
|
childControlFilter ?? defaults?.childControlFilter ?? null;
|
|
@@ -24509,6 +24533,12 @@ const useUIGroupStateController = (
|
|
|
24509
24533
|
aggregateChildStates ?? defaults?.aggregateChildStates;
|
|
24510
24534
|
const resolvedDistributeChildUIState =
|
|
24511
24535
|
distributeChildUIState ?? defaults?.distributeChildUIState;
|
|
24536
|
+
// The plural half of the pair: `aggregateChildStates` already sees all the
|
|
24537
|
+
// children at once, and a group whose value is not one key per child usually
|
|
24538
|
+
// needs the same view on the way down — which of four seats each player takes
|
|
24539
|
+
// cannot be decided one seat at a time. Given both, the plural one wins.
|
|
24540
|
+
const resolvedDistributeChildStates =
|
|
24541
|
+
distributeChildStates ?? defaults?.distributeChildStates;
|
|
24512
24542
|
if (
|
|
24513
24543
|
typeof resolvedAggregateChildStates !== "function" ||
|
|
24514
24544
|
typeof resolvedDistributeChildUIState !== "function"
|
|
@@ -24763,6 +24793,48 @@ const useUIGroupStateController = (
|
|
|
24763
24793
|
// Where the group puts a value on ONE child: the only place that knows
|
|
24764
24794
|
// what each child gets, and the only one that sees a child it cannot
|
|
24765
24795
|
// place — see warnChildAnswersForItself.
|
|
24796
|
+
// One pass over every child, which is what a plural distribute needs:
|
|
24797
|
+
// it is asked once, sees the whole group, and answers for all of them.
|
|
24798
|
+
placeChildrenUIState: (groupUIState, e) => {
|
|
24799
|
+
if (!resolvedDistributeChildStates) {
|
|
24800
|
+
for (const childUIStateController of childUIStateControllerArray) {
|
|
24801
|
+
controller.placeChildUIState(
|
|
24802
|
+
childUIStateController,
|
|
24803
|
+
groupUIState,
|
|
24804
|
+
e,
|
|
24805
|
+
);
|
|
24806
|
+
}
|
|
24807
|
+
return;
|
|
24808
|
+
}
|
|
24809
|
+
const monitoredChildren = childUIStateControllerArray.filter(
|
|
24810
|
+
shouldPropagateStateToChild,
|
|
24811
|
+
);
|
|
24812
|
+
const stateByChild = resolvedDistributeChildStates(
|
|
24813
|
+
groupUIState,
|
|
24814
|
+
monitoredChildren,
|
|
24815
|
+
);
|
|
24816
|
+
if (!stateByChild) {
|
|
24817
|
+
return;
|
|
24818
|
+
}
|
|
24819
|
+
for (const childUIStateController of monitoredChildren) {
|
|
24820
|
+
if (!stateByChild.has(childUIStateController)) {
|
|
24821
|
+
// Not named by the answer: left where it is, the way
|
|
24822
|
+
// CANNOT_DERIVE leaves a child a per-child distribute says
|
|
24823
|
+
// nothing about.
|
|
24824
|
+
continue;
|
|
24825
|
+
}
|
|
24826
|
+
if (
|
|
24827
|
+
childUIStateController.hasStateProp &&
|
|
24828
|
+
!childUIStateController.props.signal
|
|
24829
|
+
) {
|
|
24830
|
+
continue;
|
|
24831
|
+
}
|
|
24832
|
+
childUIStateController.setUIState(
|
|
24833
|
+
stateByChild.get(childUIStateController),
|
|
24834
|
+
e,
|
|
24835
|
+
);
|
|
24836
|
+
}
|
|
24837
|
+
},
|
|
24766
24838
|
placeChildUIState: (childUIStateController, groupUIState, e) => {
|
|
24767
24839
|
if (!shouldPropagateStateToChild(childUIStateController)) {
|
|
24768
24840
|
return;
|
|
@@ -24824,13 +24896,7 @@ const useUIGroupStateController = (
|
|
|
24824
24896
|
detail: {},
|
|
24825
24897
|
});
|
|
24826
24898
|
chainEvent(propagateDownEvent, e);
|
|
24827
|
-
|
|
24828
|
-
controller.placeChildUIState(
|
|
24829
|
-
childUIStateController,
|
|
24830
|
-
newUIState,
|
|
24831
|
-
propagateDownEvent,
|
|
24832
|
-
);
|
|
24833
|
-
}
|
|
24899
|
+
controller.placeChildrenUIState(newUIState, propagateDownEvent);
|
|
24834
24900
|
const groupUIState = aggregateGroupUIState(newUIState);
|
|
24835
24901
|
if (e.type === "initial_state_push") {
|
|
24836
24902
|
controller.syncInternalState(groupUIState);
|
|
@@ -24905,11 +24971,21 @@ const useUIGroupStateController = (
|
|
|
24905
24971
|
const initialEvent = new CustomEvent("initial_state_push", {
|
|
24906
24972
|
detail: {},
|
|
24907
24973
|
});
|
|
24908
|
-
|
|
24909
|
-
|
|
24910
|
-
|
|
24911
|
-
|
|
24912
|
-
|
|
24974
|
+
if (resolvedDistributeChildStates) {
|
|
24975
|
+
// A group answering for all its children at once has to be asked
|
|
24976
|
+
// again now that there is one more: what each of them shows may
|
|
24977
|
+
// depend on who else is there.
|
|
24978
|
+
controller.placeChildrenUIState(
|
|
24979
|
+
stateToPlaceChildFrom,
|
|
24980
|
+
initialEvent,
|
|
24981
|
+
);
|
|
24982
|
+
} else {
|
|
24983
|
+
controller.placeChildUIState(
|
|
24984
|
+
childUIStateController,
|
|
24985
|
+
stateToPlaceChildFrom,
|
|
24986
|
+
initialEvent,
|
|
24987
|
+
);
|
|
24988
|
+
}
|
|
24913
24989
|
}
|
|
24914
24990
|
onChange(new CustomEvent(`${childControlType}_mount`), {
|
|
24915
24991
|
notifyExternal: "silent",
|
|
@@ -25084,13 +25160,7 @@ const useUIGroupStateController = (
|
|
|
25084
25160
|
"propagate_down_set_ui_state",
|
|
25085
25161
|
{ detail: {} },
|
|
25086
25162
|
);
|
|
25087
|
-
|
|
25088
|
-
controller.placeChildUIState(
|
|
25089
|
-
childUIStateController,
|
|
25090
|
-
groupUIState,
|
|
25091
|
-
propagateDownEvent,
|
|
25092
|
-
);
|
|
25093
|
-
}
|
|
25163
|
+
controller.placeChildrenUIState(groupUIState, propagateDownEvent);
|
|
25094
25164
|
controller.syncInternalState(groupUIState);
|
|
25095
25165
|
};
|
|
25096
25166
|
if (
|
|
@@ -25381,6 +25451,7 @@ const useUIFacadeStateController = (props, realUIStateController) => {
|
|
|
25381
25451
|
return {
|
|
25382
25452
|
controller: facadeUIStateController,
|
|
25383
25453
|
realUIStateController,
|
|
25454
|
+
props,
|
|
25384
25455
|
};
|
|
25385
25456
|
},
|
|
25386
25457
|
// ── update: runs every render after the first ─────────────────────────
|
|
@@ -25394,6 +25465,7 @@ const useUIFacadeStateController = (props, realUIStateController) => {
|
|
|
25394
25465
|
|
|
25395
25466
|
return {
|
|
25396
25467
|
realUIStateController,
|
|
25468
|
+
props,
|
|
25397
25469
|
};
|
|
25398
25470
|
},
|
|
25399
25471
|
);
|
|
@@ -26444,7 +26516,11 @@ const createControlInfo = (props, {
|
|
|
26444
26516
|
if (typeProp === "checkbox" || typeProp === "radio") {
|
|
26445
26517
|
statePropName = "checked";
|
|
26446
26518
|
defaultStatePropName = "defaultChecked";
|
|
26447
|
-
|
|
26519
|
+
// "on" is what HTML sends for a checkbox given no value of its own — the
|
|
26520
|
+
// default of an ABSENT prop, not of a falsy one: `value={false}` and
|
|
26521
|
+
// `value={0}` are values, and two rows holding true and false are how a
|
|
26522
|
+
// list asks a yes/no question.
|
|
26523
|
+
value = props.value === undefined ? "on" : props.value;
|
|
26448
26524
|
signalHoldsChecked = true;
|
|
26449
26525
|
if (signal) {
|
|
26450
26526
|
if (props.defaultChecked) {
|
|
@@ -26629,6 +26705,7 @@ const useControlgroupProps = (props, {
|
|
|
26629
26705
|
childControlFilter,
|
|
26630
26706
|
aggregateChildStates,
|
|
26631
26707
|
distributeChildUIState,
|
|
26708
|
+
distributeChildStates,
|
|
26632
26709
|
wantRequesterButtonState,
|
|
26633
26710
|
uiActionInternal,
|
|
26634
26711
|
allowCapture = false,
|
|
@@ -26642,6 +26719,7 @@ const useControlgroupProps = (props, {
|
|
|
26642
26719
|
childControlFilter,
|
|
26643
26720
|
aggregateChildStates,
|
|
26644
26721
|
distributeChildUIState,
|
|
26722
|
+
distributeChildStates,
|
|
26645
26723
|
wantRequesterButtonState,
|
|
26646
26724
|
uiActionInternal,
|
|
26647
26725
|
allowCapture,
|
|
@@ -27156,7 +27234,6 @@ const useInteractiveProps = (props, {
|
|
|
27156
27234
|
uiStateController.actionInFlight = false;
|
|
27157
27235
|
uiStateController.runningAction = null;
|
|
27158
27236
|
const queuedEvent = uiStateController.queuedActionAllowedEvent;
|
|
27159
|
-
uiStateController.queuedActionAllowedEvent = null;
|
|
27160
27237
|
if (!queuedEvent) {
|
|
27161
27238
|
return;
|
|
27162
27239
|
}
|
|
@@ -27164,12 +27241,20 @@ const useInteractiveProps = (props, {
|
|
|
27164
27241
|
// A failure abandons the queue: the UI is rolled back to the last
|
|
27165
27242
|
// known state (resetOnError above), and what was queued was built
|
|
27166
27243
|
// on top of the state that just failed.
|
|
27244
|
+
uiStateController.queuedActionAllowedEvent = null;
|
|
27167
27245
|
return;
|
|
27168
27246
|
}
|
|
27169
27247
|
// A microtask later, not right here: this runs inside the batch()
|
|
27170
27248
|
// that settles the action (see watchActionCompletion for the same
|
|
27171
27249
|
// constraint).
|
|
27172
27250
|
queueMicrotask(() => {
|
|
27251
|
+
// Cleared here, right before the dispatch, never earlier: between
|
|
27252
|
+
// the outcome and this microtask a render can slip in (the echo
|
|
27253
|
+
// of what the settled run committed), and the external-state gate
|
|
27254
|
+
// in ui_state_controller.js must still see work in flight or it
|
|
27255
|
+
// would overwrite the UI state the queued request is about to
|
|
27256
|
+
// send.
|
|
27257
|
+
uiStateController.queuedActionAllowedEvent = null;
|
|
27173
27258
|
executeAction(queuedEvent);
|
|
27174
27259
|
});
|
|
27175
27260
|
});
|
|
@@ -45833,7 +45918,6 @@ const useFocusGroup = (
|
|
|
45833
45918
|
};
|
|
45834
45919
|
|
|
45835
45920
|
installImportMetaCssBuild(import.meta);const rightArrowPath = "M680-480L360-160l-80-80 240-240-240-240 80-80 320 320z";
|
|
45836
|
-
const downArrowPath = "M480-280L160-600l80-80 240 240 240-240 80 80-320 320z";
|
|
45837
45921
|
const css$L = /* css */`
|
|
45838
45922
|
.navi_summary_marker {
|
|
45839
45923
|
width: 1em;
|
|
@@ -45858,22 +45942,31 @@ const css$L = /* css */`
|
|
|
45858
45942
|
}
|
|
45859
45943
|
}
|
|
45860
45944
|
|
|
45861
|
-
|
|
45862
|
-
|
|
45863
|
-
|
|
45864
|
-
|
|
45865
|
-
|
|
45866
|
-
animation-fill-mode: forwards;
|
|
45945
|
+
/* One chevron, rotated: the transition only ever plays on a direction
|
|
45946
|
+
change, so the first paint shows the resting direction with no
|
|
45947
|
+
movement. */
|
|
45948
|
+
.navi_summary_marker_arrow_group {
|
|
45949
|
+
transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
45867
45950
|
|
|
45868
|
-
&[data-
|
|
45869
|
-
|
|
45951
|
+
&[data-direction="right"] {
|
|
45952
|
+
transform: rotate(0deg);
|
|
45870
45953
|
}
|
|
45871
|
-
|
|
45872
|
-
|
|
45873
|
-
|
|
45954
|
+
&[data-direction="down"] {
|
|
45955
|
+
transform: rotate(90deg);
|
|
45956
|
+
}
|
|
45957
|
+
&[data-direction="up"] {
|
|
45958
|
+
transform: rotate(-90deg);
|
|
45959
|
+
}
|
|
45960
|
+
&[data-direction="left"] {
|
|
45961
|
+
transform: rotate(180deg);
|
|
45874
45962
|
}
|
|
45875
45963
|
}
|
|
45876
45964
|
|
|
45965
|
+
.navi_summary_marker_arrow {
|
|
45966
|
+
opacity: 1;
|
|
45967
|
+
transition: opacity 0.3s ease-in-out;
|
|
45968
|
+
}
|
|
45969
|
+
|
|
45877
45970
|
&[data-loading] {
|
|
45878
45971
|
.navi_summary_marker_loading_container {
|
|
45879
45972
|
transform: scale(1);
|
|
@@ -45898,39 +45991,25 @@ const css$L = /* css */`
|
|
|
45898
45991
|
stroke-dashoffset: -2010;
|
|
45899
45992
|
}
|
|
45900
45993
|
}
|
|
45901
|
-
@keyframes morph-to-down {
|
|
45902
|
-
from {
|
|
45903
|
-
d: path("${rightArrowPath}");
|
|
45904
|
-
}
|
|
45905
|
-
to {
|
|
45906
|
-
d: path("${downArrowPath}");
|
|
45907
|
-
}
|
|
45908
|
-
}
|
|
45909
|
-
@keyframes morph-to-right {
|
|
45910
|
-
from {
|
|
45911
|
-
d: path("${downArrowPath}");
|
|
45912
|
-
}
|
|
45913
|
-
to {
|
|
45914
|
-
d: path("${rightArrowPath}");
|
|
45915
|
-
}
|
|
45916
|
-
}
|
|
45917
45994
|
`;
|
|
45995
|
+
|
|
45996
|
+
/**
|
|
45997
|
+
* @type {import("ignore:preact").FunctionComponent<{
|
|
45998
|
+
* open?: boolean,
|
|
45999
|
+
* loading?: boolean,
|
|
46000
|
+
* openDirection?: "down" | "up" | "left",
|
|
46001
|
+
* }>}
|
|
46002
|
+
* @param openDirection - Where the chevron points while open; closed always
|
|
46003
|
+
* points right. "down" fits content revealed below (the <details> shape),
|
|
46004
|
+
* "up" content revealed above, "left" content revealed beside.
|
|
46005
|
+
*/
|
|
45918
46006
|
const SummaryMarker = ({
|
|
45919
46007
|
open,
|
|
45920
|
-
loading
|
|
46008
|
+
loading,
|
|
46009
|
+
openDirection = "down"
|
|
45921
46010
|
}) => {
|
|
45922
46011
|
import.meta.css = [css$L, "@jsenv/navi/src/control/details/summary_marker.jsx"];
|
|
45923
46012
|
const showLoading = useDebounceTrue(loading, 300);
|
|
45924
|
-
const mountedRef = useRef(false);
|
|
45925
|
-
const prevOpenRef = useRef(open);
|
|
45926
|
-
useLayoutEffect(() => {
|
|
45927
|
-
mountedRef.current = true;
|
|
45928
|
-
return () => {
|
|
45929
|
-
mountedRef.current = false;
|
|
45930
|
-
};
|
|
45931
|
-
}, []);
|
|
45932
|
-
const shouldAnimate = mountedRef.current && prevOpenRef.current !== open;
|
|
45933
|
-
prevOpenRef.current = open;
|
|
45934
46013
|
return jsx("span", {
|
|
45935
46014
|
className: "navi_summary_marker",
|
|
45936
46015
|
"data-loading": showLoading ? "" : undefined,
|
|
@@ -45961,12 +46040,13 @@ const SummaryMarker = ({
|
|
|
45961
46040
|
strokeDasharray: "503 1507"
|
|
45962
46041
|
})]
|
|
45963
46042
|
}), jsx("g", {
|
|
46043
|
+
className: "navi_summary_marker_arrow_group",
|
|
46044
|
+
"data-direction": open ? openDirection : "right",
|
|
45964
46045
|
"transform-origin": "480px -480px",
|
|
45965
46046
|
children: jsx("path", {
|
|
45966
46047
|
className: "navi_summary_marker_arrow",
|
|
45967
46048
|
fill: "currentColor",
|
|
45968
|
-
|
|
45969
|
-
d: open ? downArrowPath : rightArrowPath
|
|
46049
|
+
d: rightArrowPath
|
|
45970
46050
|
})
|
|
45971
46051
|
})]
|
|
45972
46052
|
})
|
|
@@ -46196,21 +46276,32 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
46196
46276
|
* </Expandable>
|
|
46197
46277
|
*
|
|
46198
46278
|
* Content after UI expands below (the <details> shape), Content before UI
|
|
46199
|
-
* expands above; `layout="column"` puts the parts side by side
|
|
46200
|
-
* content then expanding horizontally. The
|
|
46201
|
-
*
|
|
46202
|
-
*
|
|
46203
|
-
*
|
|
46204
|
-
*
|
|
46205
|
-
*
|
|
46279
|
+
* expands above; `layout="column"` puts the parts side by side (sharing
|
|
46280
|
+
* their height), the content then expanding horizontally. The marker
|
|
46281
|
+
* chevron follows: it points right while closed and toward where the
|
|
46282
|
+
* content went while open (down, up, or left). The common shape has a
|
|
46283
|
+
* shorthand: `ui` prop + children as content.
|
|
46284
|
+
* - the UI part is the focusable toggle itself (role button, Space/Enter,
|
|
46285
|
+
* arrow keys) and accepts any markup: controls inside it keep their own
|
|
46286
|
+
* behavior, the marker is purely decorative.
|
|
46287
|
+
*
|
|
46288
|
+
* Reach for it knowingly: expanding in-flow SHIFTS the layout — everything
|
|
46289
|
+
* below (or beside) moves when it opens. A Popover, Dialog, Picker or Callout
|
|
46290
|
+
* answers the same click on its own layer, moving nothing, which is usually
|
|
46291
|
+
* the better UX: a layout that stays where it is reads and operates better —
|
|
46292
|
+
* all the more on small screens, mobile first of all, where the shift can
|
|
46293
|
+
* push most of the page away. Expandable is for content that genuinely
|
|
46294
|
+
* belongs in the flow (a tree, a changelog, a settings group read top to
|
|
46295
|
+
* bottom).
|
|
46206
46296
|
*
|
|
46207
46297
|
* What <details> gives for free is rebuilt here:
|
|
46208
46298
|
* - a "toggle" event (a real ToggleEvent when the browser has it) dispatched on
|
|
46209
46299
|
* the root whenever the state actually changes — but never on mount, unlike
|
|
46210
46300
|
* the native one (see the workaround comment in details.jsx);
|
|
46211
46301
|
* - `--navi-toggle`/`--navi-open`/`--navi-close` commands work against it: the
|
|
46212
|
-
* root
|
|
46213
|
-
* the `navi_command`/`navi_request_open`/`navi_request_close`
|
|
46302
|
+
* root and the UI part carry `aria-expanded` (what the command system reads)
|
|
46303
|
+
* and answer the `navi_command`/`navi_request_open`/`navi_request_close`
|
|
46304
|
+
* events.
|
|
46214
46305
|
*
|
|
46215
46306
|
* Content is not built until the first expansion and stays built afterwards —
|
|
46216
46307
|
* same policy, same prop names as popups (see popup_content_mount.js):
|
|
@@ -46218,12 +46309,15 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
46218
46309
|
* once the collapse settles (so a closing animation still plays on real
|
|
46219
46310
|
* content).
|
|
46220
46311
|
*
|
|
46221
|
-
* The
|
|
46222
|
-
*
|
|
46223
|
-
*
|
|
46224
|
-
*
|
|
46225
|
-
*
|
|
46226
|
-
*
|
|
46312
|
+
* The animation is a REVEAL, not a resize: the expandable's own footprint
|
|
46313
|
+
* grows/shrinks progressively (the content's grid track interpolates
|
|
46314
|
+
* 0fr <-> 1fr — rows for the stacked layout, columns for `layout="column"`),
|
|
46315
|
+
* but the content inside is laid out at its final size for the whole movement
|
|
46316
|
+
* (its animated dimension is frozen to the measured final value, see the
|
|
46317
|
+
* [opened] effect) and the container simply uncovers it. Text never rewraps
|
|
46318
|
+
* mid-animation. The content is revealed from its UI side (pinned against the
|
|
46319
|
+
* UI when it comes first). Once settled open the clipping is released, so a
|
|
46320
|
+
* popover or focus ring inside is not cut at the edges.
|
|
46227
46321
|
*/
|
|
46228
46322
|
const css$J = /* css */`
|
|
46229
46323
|
.navi_expandable {
|
|
@@ -46241,27 +46335,16 @@ const css$J = /* css */`
|
|
|
46241
46335
|
cursor: pointer;
|
|
46242
46336
|
user-select: none;
|
|
46243
46337
|
|
|
46244
|
-
|
|
46245
|
-
only the marker button actually holds it. */
|
|
46246
|
-
&:has(> .navi_expandable_toggle:focus-visible) {
|
|
46338
|
+
&:focus-visible {
|
|
46247
46339
|
border-radius: 4px;
|
|
46248
46340
|
outline: 2px solid AccentColor;
|
|
46249
46341
|
outline-offset: 1px;
|
|
46250
46342
|
}
|
|
46251
46343
|
|
|
46252
|
-
> .
|
|
46344
|
+
> .navi_expandable_marker {
|
|
46253
46345
|
display: flex;
|
|
46254
|
-
|
|
46346
|
+
flex-shrink: 0;
|
|
46255
46347
|
align-items: center;
|
|
46256
|
-
color: inherit;
|
|
46257
|
-
font: inherit;
|
|
46258
|
-
background: none;
|
|
46259
|
-
border: none;
|
|
46260
|
-
cursor: pointer;
|
|
46261
|
-
|
|
46262
|
-
&:focus-visible {
|
|
46263
|
-
outline: none;
|
|
46264
|
-
}
|
|
46265
46348
|
}
|
|
46266
46349
|
|
|
46267
46350
|
> .navi_expandable_ui_label {
|
|
@@ -46273,25 +46356,57 @@ const css$J = /* css */`
|
|
|
46273
46356
|
}
|
|
46274
46357
|
|
|
46275
46358
|
> .navi_expandable_content_container {
|
|
46359
|
+
position: relative;
|
|
46276
46360
|
display: grid;
|
|
46277
46361
|
grid-template-rows: 0fr;
|
|
46278
|
-
|
|
46279
|
-
|
|
46362
|
+
/* The clip lives here, on the moving box, because the content inside
|
|
46363
|
+
keeps its final size during the animation (see the top comment) and
|
|
46364
|
+
overflows the track on purpose. One-sided (a clip-path with the free
|
|
46365
|
+
sides pushed far out) rather than overflow: hidden: only the side
|
|
46366
|
+
being revealed hides anything, so a badge sticking out of the other
|
|
46367
|
+
sides is visible from the very first frame of the movement. */
|
|
46368
|
+
clip-path: inset(-9999px -9999px 0 -9999px);
|
|
46369
|
+
|
|
46370
|
+
/* The sizer is what lets the track actually collapse: min-height 0 on
|
|
46371
|
+
an auto-sized item zeroes its min-content contribution. The frozen
|
|
46372
|
+
content cannot play that role itself — a definite height makes the
|
|
46373
|
+
contribution definite too, and the track then never goes below it. */
|
|
46374
|
+
> .navi_expandable_content_sizer {
|
|
46375
|
+
display: grid;
|
|
46280
46376
|
min-height: 0;
|
|
46281
|
-
overflow: hidden;
|
|
46282
46377
|
}
|
|
46283
46378
|
}
|
|
46284
46379
|
&[aria-expanded="true"] > .navi_expandable_content_container {
|
|
46285
46380
|
grid-template-rows: 1fr;
|
|
46286
46381
|
}
|
|
46382
|
+
/* Content before the UI: revealed against the UI side — the edge touching
|
|
46383
|
+
the UI stays, the far edge is what gets uncovered. Said at BOTH levels:
|
|
46384
|
+
a transitioning fr resolves once for the container's own size and once
|
|
46385
|
+
more inside it (the row is fraction² high), so the row must be glued to
|
|
46386
|
+
the container's UI edge and the oversized frozen content to the row's —
|
|
46387
|
+
anchoring only the inner one leaves the content following the drifting
|
|
46388
|
+
fraction² row. */
|
|
46389
|
+
&[data-content-first]:not([data-layout="column"])
|
|
46390
|
+
> .navi_expandable_content_container {
|
|
46391
|
+
align-content: end;
|
|
46392
|
+
clip-path: inset(0 -9999px -9999px -9999px);
|
|
46287
46393
|
|
|
46288
|
-
|
|
46289
|
-
|
|
46394
|
+
> .navi_expandable_content_sizer {
|
|
46395
|
+
align-content: end;
|
|
46396
|
+
}
|
|
46397
|
+
}
|
|
46398
|
+
|
|
46399
|
+
/* The parts sit side by side, sharing their height: the UI part becomes a
|
|
46400
|
+
vertical strip and the content expands horizontally, on the columns
|
|
46401
|
+
track — the rows track collapses too, so a closed expandable is only as
|
|
46402
|
+
tall as its UI (the content, unmounted or 0-wide, says nothing about
|
|
46403
|
+
the height it will bring). */
|
|
46290
46404
|
&[data-layout="column"] {
|
|
46291
46405
|
flex-direction: row;
|
|
46292
46406
|
|
|
46293
46407
|
> .navi_expandable_ui {
|
|
46294
46408
|
flex-direction: column;
|
|
46409
|
+
align-items: center;
|
|
46295
46410
|
|
|
46296
46411
|
> .navi_expandable_ui_label {
|
|
46297
46412
|
flex-direction: column;
|
|
@@ -46299,17 +46414,34 @@ const css$J = /* css */`
|
|
|
46299
46414
|
}
|
|
46300
46415
|
> .navi_expandable_content_container {
|
|
46301
46416
|
grid-template-columns: 0fr;
|
|
46302
|
-
grid-template-rows:
|
|
46417
|
+
grid-template-rows: 0fr;
|
|
46418
|
+
/* Both tracks reveal: clip the far side of each (right and bottom),
|
|
46419
|
+
the UI side and the top stay free. */
|
|
46420
|
+
clip-path: inset(-9999px 0 0 -9999px);
|
|
46303
46421
|
|
|
46304
|
-
> .
|
|
46422
|
+
> .navi_expandable_content_sizer {
|
|
46305
46423
|
min-width: 0;
|
|
46306
|
-
min-height:
|
|
46424
|
+
min-height: 0;
|
|
46307
46425
|
}
|
|
46308
46426
|
}
|
|
46309
46427
|
&[aria-expanded="true"] > .navi_expandable_content_container {
|
|
46310
46428
|
grid-template-columns: 1fr;
|
|
46429
|
+
grid-template-rows: 1fr;
|
|
46430
|
+
}
|
|
46431
|
+
/* mountWhenClosed: the content is built and width-frozen while closed
|
|
46432
|
+
(see the component), so it can size the height at all times — the
|
|
46433
|
+
expandable then keeps one stable height and only the width reveals. */
|
|
46434
|
+
&[data-closed-content-sized] > .navi_expandable_content_container {
|
|
46311
46435
|
grid-template-rows: none;
|
|
46312
46436
|
}
|
|
46437
|
+
&[data-content-first] > .navi_expandable_content_container {
|
|
46438
|
+
justify-content: end;
|
|
46439
|
+
clip-path: inset(-9999px -9999px 0 0);
|
|
46440
|
+
|
|
46441
|
+
> .navi_expandable_content_sizer {
|
|
46442
|
+
justify-content: end;
|
|
46443
|
+
}
|
|
46444
|
+
}
|
|
46313
46445
|
}
|
|
46314
46446
|
|
|
46315
46447
|
&[data-animation] > .navi_expandable_content_container {
|
|
@@ -46323,16 +46455,21 @@ const css$J = /* css */`
|
|
|
46323
46455
|
transition: none;
|
|
46324
46456
|
}
|
|
46325
46457
|
}
|
|
46326
|
-
/* Settled open: stop clipping, so a popover, a focus ring or a
|
|
46327
|
-
element inside the content can spill out
|
|
46328
|
-
|
|
46329
|
-
|
|
46330
|
-
|
|
46331
|
-
|
|
46332
|
-
|
|
46458
|
+
/* Settled open: stop clipping entirely, so a popover, a focus ring or a
|
|
46459
|
+
dragged element inside the content can spill out on any side. Settled
|
|
46460
|
+
closed: clip every side — the collapsed box must show nothing, a
|
|
46461
|
+
stick-out included. In between (any movement, opening or closing) the
|
|
46462
|
+
one-sided clips above apply. */
|
|
46463
|
+
&[aria-expanded="true"][data-settled] > .navi_expandable_content_container {
|
|
46464
|
+
clip-path: none;
|
|
46465
|
+
}
|
|
46466
|
+
&:not([aria-expanded="true"])[data-settled]
|
|
46467
|
+
> .navi_expandable_content_container {
|
|
46468
|
+
clip-path: inset(0 0 0 0);
|
|
46333
46469
|
}
|
|
46334
46470
|
&[data-content-scrolls]
|
|
46335
46471
|
> .navi_expandable_content_container
|
|
46472
|
+
> .navi_expandable_content_sizer
|
|
46336
46473
|
> .navi_expandable_content {
|
|
46337
46474
|
max-height: var(--navi-expandable-max-content-height);
|
|
46338
46475
|
overflow-y: auto;
|
|
@@ -46387,21 +46524,22 @@ const useExpandableContext = partName => {
|
|
|
46387
46524
|
* object (`{ loading, error, completed, ... }`) — see ActionRenderer.
|
|
46388
46525
|
* @param loading - Shows the loading spinner on the marker regardless of
|
|
46389
46526
|
* `action`'s own loading state.
|
|
46390
|
-
* @param animation - Off by default. `true` plays the
|
|
46391
|
-
*
|
|
46392
|
-
* (0.3s).
|
|
46527
|
+
* @param animation - Off by default. `true` plays the reveal transition;
|
|
46528
|
+
* duration comes from `--navi-expandable-animation-duration` (0.3s).
|
|
46393
46529
|
* @param layout - `"row"` (default): the parts stack, the content expands
|
|
46394
|
-
* vertically. `"column"`: the parts sit side by side
|
|
46395
|
-
* horizontally next to the UI part.
|
|
46396
|
-
* @param autoFocus - Off by default (the focus stays on the
|
|
46530
|
+
* vertically. `"column"`: the parts sit side by side sharing their height,
|
|
46531
|
+
* the content expands horizontally next to the UI part.
|
|
46532
|
+
* @param autoFocus - Off by default (the focus stays on the UI part when
|
|
46397
46533
|
* opening). `true` moves the focus into the content on open — the
|
|
46398
46534
|
* `[autofocus]` element if any, the first focusable otherwise. Whatever the
|
|
46399
46535
|
* setting, closing while the focus is inside the content hands it back to
|
|
46400
|
-
* the
|
|
46536
|
+
* the UI part (it would otherwise be lost to the closed, inert content).
|
|
46401
46537
|
* @param maxContentHeight - Caps the content height; taller content scrolls
|
|
46402
46538
|
* inside the expandable instead of growing it.
|
|
46403
46539
|
* @param mountWhenClosed - Builds the content right away instead of on first
|
|
46404
|
-
* expansion.
|
|
46540
|
+
* expansion. In layout="column" it also gives the closed expandable its
|
|
46541
|
+
* content's height (the content is kept laid out at its open width), so
|
|
46542
|
+
* opening only reveals the width instead of changing the height too.
|
|
46405
46543
|
* @param unmountWhenClosed - Throws the content away once the collapse
|
|
46406
46544
|
* settles — after the closing animation, so it still plays on real content —
|
|
46407
46545
|
* and rebuilds it from scratch on every expansion.
|
|
@@ -46431,10 +46569,10 @@ const Expandable = props => {
|
|
|
46431
46569
|
const defaultRef = useRef();
|
|
46432
46570
|
const rootRef = ref || defaultRef;
|
|
46433
46571
|
const uiRef = useRef();
|
|
46434
|
-
const toggleButtonRef = useRef();
|
|
46435
46572
|
const contentContainerRef = useRef();
|
|
46436
46573
|
const contentId = useId();
|
|
46437
|
-
const
|
|
46574
|
+
const isColumn = layout === "column";
|
|
46575
|
+
const closedContentSized = Boolean(isColumn && mountWhenClosed);
|
|
46438
46576
|
// Reading .value during render is what subscribes the expandable to it.
|
|
46439
46577
|
const openRequested = signal ? signal.value : open;
|
|
46440
46578
|
const [opened, setOpened] = useState(() => Boolean(openRequested === undefined ? defaultOpen : openRequested));
|
|
@@ -46455,16 +46593,44 @@ const Expandable = props => {
|
|
|
46455
46593
|
const [settled, setSettled] = useState(true);
|
|
46456
46594
|
|
|
46457
46595
|
// Read before the close touches the DOM: flipping the content to inert can
|
|
46458
|
-
// blur what it held, so by effect time the focus to hand back to the
|
|
46596
|
+
// blur what it held, so by effect time the focus to hand back to the UI part
|
|
46459
46597
|
// could already be gone.
|
|
46460
46598
|
const focusedBeforeCloseRef = useRef(null);
|
|
46461
|
-
// The pointer press that is about to toggle
|
|
46462
|
-
// the click ever fires
|
|
46463
|
-
//
|
|
46599
|
+
// The pointer press that is about to toggle can blur the focused field
|
|
46600
|
+
// before the click ever fires — so what held the focus has to be remembered
|
|
46601
|
+
// at pointerdown time.
|
|
46464
46602
|
const focusedAtPointerDownRef = useRef(null);
|
|
46465
46603
|
const onUIPointerDown = () => {
|
|
46466
46604
|
focusedAtPointerDownRef.current = document.activeElement;
|
|
46467
46605
|
};
|
|
46606
|
+
|
|
46607
|
+
// The content keeps its final size while the track animates (see the top
|
|
46608
|
+
// comment): its animated dimension is pinned to a measured pixel value, and
|
|
46609
|
+
// released once the movement settles.
|
|
46610
|
+
const freezeContentSize = () => {
|
|
46611
|
+
const contentContainer = contentContainerRef.current;
|
|
46612
|
+
const contentElement = contentContainer ? contentContainer.firstElementChild.firstElementChild : null;
|
|
46613
|
+
if (!contentElement) {
|
|
46614
|
+
return;
|
|
46615
|
+
}
|
|
46616
|
+
const rect = contentElement.getBoundingClientRect();
|
|
46617
|
+
if (isColumn) {
|
|
46618
|
+
// Both, not just the width: a max-height-capped content otherwise
|
|
46619
|
+
// follows the collapsing rows track down instead of holding its size.
|
|
46620
|
+
contentElement.style.width = `${rect.width}px`;
|
|
46621
|
+
contentElement.style.height = `${rect.height}px`;
|
|
46622
|
+
} else {
|
|
46623
|
+
contentElement.style.height = `${rect.height}px`;
|
|
46624
|
+
}
|
|
46625
|
+
};
|
|
46626
|
+
|
|
46627
|
+
// Where the last paint left the track, measured before the toggle commits:
|
|
46628
|
+
// 0 when fully closed, partway when reopening during a collapse. Read here
|
|
46629
|
+
// rather than in the effect — a layout read after the commit would also be
|
|
46630
|
+
// the first style recalc of the open state, starting the track transition
|
|
46631
|
+
// right there; once canceled (to measure the final size), a new transition
|
|
46632
|
+
// to the same end value refuses to start and the reveal jumps.
|
|
46633
|
+
const revealStartSizeRef = useRef(null);
|
|
46468
46634
|
const toggleTo = nextOpen => {
|
|
46469
46635
|
nextOpen = Boolean(nextOpen);
|
|
46470
46636
|
if (nextOpen === openedRef.current) {
|
|
@@ -46472,10 +46638,20 @@ const Expandable = props => {
|
|
|
46472
46638
|
}
|
|
46473
46639
|
openedRef.current = nextOpen;
|
|
46474
46640
|
if (nextOpen) {
|
|
46641
|
+
if (animation) {
|
|
46642
|
+
const contentContainer = contentContainerRef.current;
|
|
46643
|
+
revealStartSizeRef.current = contentContainer ? contentContainer.getBoundingClientRect() : null;
|
|
46644
|
+
}
|
|
46475
46645
|
setContentMounted(true);
|
|
46476
46646
|
} else {
|
|
46477
46647
|
const activeElement = document.activeElement;
|
|
46478
46648
|
focusedBeforeCloseRef.current = !activeElement || activeElement === document.body ? focusedAtPointerDownRef.current : activeElement;
|
|
46649
|
+
if (animation) {
|
|
46650
|
+
// Now, while the content is still fully laid out — by effect time the
|
|
46651
|
+
// track is already heading to 0 (the opening case measures in the
|
|
46652
|
+
// effect instead, where the just-mounted content exists).
|
|
46653
|
+
freezeContentSize();
|
|
46654
|
+
}
|
|
46479
46655
|
}
|
|
46480
46656
|
focusedAtPointerDownRef.current = null;
|
|
46481
46657
|
setOpened(nextOpen);
|
|
@@ -46524,10 +46700,9 @@ const Expandable = props => {
|
|
|
46524
46700
|
}, [openRequested]);
|
|
46525
46701
|
|
|
46526
46702
|
// A state change: tell the world (the "toggle" event), move the focus, and
|
|
46527
|
-
//
|
|
46528
|
-
//
|
|
46529
|
-
//
|
|
46530
|
-
// open).
|
|
46703
|
+
// set up the reveal. Skipped on mount — nothing changed, so neither the
|
|
46704
|
+
// event nor a transition exists (and a page must not have its focus stolen
|
|
46705
|
+
// by an expandable that was simply already open).
|
|
46531
46706
|
const isFirstOpenedRunRef = useRef(true);
|
|
46532
46707
|
useLayoutEffect(() => {
|
|
46533
46708
|
if (isFirstOpenedRunRef.current) {
|
|
@@ -46547,13 +46722,57 @@ const Expandable = props => {
|
|
|
46547
46722
|
const focusedBeforeClose = focusedBeforeCloseRef.current;
|
|
46548
46723
|
focusedBeforeCloseRef.current = null;
|
|
46549
46724
|
if (focusedBeforeClose && contentContainerRef.current && contentContainerRef.current.contains(focusedBeforeClose)) {
|
|
46550
|
-
|
|
46725
|
+
uiRef.current.focus();
|
|
46551
46726
|
}
|
|
46552
46727
|
}
|
|
46553
46728
|
if (!animation) {
|
|
46554
46729
|
return undefined;
|
|
46555
46730
|
}
|
|
46556
|
-
const
|
|
46731
|
+
const contentContainer = contentContainerRef.current;
|
|
46732
|
+
const contentElement = contentContainer.firstElementChild.firstElementChild;
|
|
46733
|
+
if (opened) {
|
|
46734
|
+
// The reveal needs the content at its final size before the track
|
|
46735
|
+
// starts moving, and the final size only exists in the open state —
|
|
46736
|
+
// the reflow trick (see instructions.md, CSS section), with transitions
|
|
46737
|
+
// suppressed BEFORE the first layout read: this effect runs pre-paint,
|
|
46738
|
+
// so any earlier read would itself be the first recalc of the open
|
|
46739
|
+
// state and would start the track transition (see revealStartSizeRef).
|
|
46740
|
+
contentContainer.style.transitionProperty = "none";
|
|
46741
|
+
const finalRect = contentElement.getBoundingClientRect();
|
|
46742
|
+
if (isColumn) {
|
|
46743
|
+
contentElement.style.width = `${finalRect.width}px`;
|
|
46744
|
+
contentElement.style.height = `${finalRect.height}px`;
|
|
46745
|
+
} else {
|
|
46746
|
+
contentElement.style.height = `${finalRect.height}px`;
|
|
46747
|
+
}
|
|
46748
|
+
// Put the tracks back where the last paint left them and let the
|
|
46749
|
+
// transition play from there. In fr — px does not interpolate with fr.
|
|
46750
|
+
const startRect = revealStartSizeRef.current;
|
|
46751
|
+
revealStartSizeRef.current = null;
|
|
46752
|
+
const startFrOf = (startSize, finalSize) => finalSize > 0 ? startSize / finalSize : 0;
|
|
46753
|
+
if (isColumn) {
|
|
46754
|
+
contentContainer.style.gridTemplateColumns = `${startFrOf(startRect ? startRect.width : 0, finalRect.width)}fr`;
|
|
46755
|
+
if (!closedContentSized) {
|
|
46756
|
+
// The height opens alongside the width (a closed column expandable
|
|
46757
|
+
// is only as tall as its UI) — unless the closed content already
|
|
46758
|
+
// sizes it, where only the width has anywhere to go.
|
|
46759
|
+
contentContainer.style.gridTemplateRows = `${startFrOf(startRect ? startRect.height : 0, finalRect.height)}fr`;
|
|
46760
|
+
}
|
|
46761
|
+
} else {
|
|
46762
|
+
contentContainer.style.gridTemplateRows = `${startFrOf(startRect ? startRect.height : 0, finalRect.height)}fr`;
|
|
46763
|
+
}
|
|
46764
|
+
// That starting frame must be genuinely rendered to transition from it,
|
|
46765
|
+
// and transitions re-enabled BEFORE the flip back to the open value —
|
|
46766
|
+
// same order as popover.jsx's own reflow trick.
|
|
46767
|
+
contentContainer.getBoundingClientRect();
|
|
46768
|
+
contentContainer.style.transitionProperty = "";
|
|
46769
|
+
contentContainer.style.gridTemplateColumns = "";
|
|
46770
|
+
contentContainer.style.gridTemplateRows = "";
|
|
46771
|
+
}
|
|
46772
|
+
// (closing froze the content in toggleTo, while it was still laid out)
|
|
46773
|
+
const cancel = whenTransitionSettles(contentContainer, () => {
|
|
46774
|
+
contentElement.style.width = "";
|
|
46775
|
+
contentElement.style.height = "";
|
|
46557
46776
|
setSettled(true);
|
|
46558
46777
|
});
|
|
46559
46778
|
return cancel;
|
|
@@ -46569,6 +46788,30 @@ const Expandable = props => {
|
|
|
46569
46788
|
}
|
|
46570
46789
|
}, [mountWhenClosed]);
|
|
46571
46790
|
|
|
46791
|
+
// closedContentSized (column + mountWhenClosed): the closed content sizes
|
|
46792
|
+
// the height (see the CSS), which is only right if it lies at its OPEN
|
|
46793
|
+
// width — at its natural closed width (a 0-wide track) it would wrap
|
|
46794
|
+
// against nothing and stack word by word. So while closed, its width is
|
|
46795
|
+
// frozen to a silently measured open width.
|
|
46796
|
+
useLayoutEffect(() => {
|
|
46797
|
+
if (!closedContentSized || opened || !settled || !contentMounted) {
|
|
46798
|
+
return;
|
|
46799
|
+
}
|
|
46800
|
+
const contentContainer = contentContainerRef.current;
|
|
46801
|
+
const contentElement = contentContainer.firstElementChild.firstElementChild;
|
|
46802
|
+
contentContainer.style.transitionProperty = "none";
|
|
46803
|
+
contentContainer.style.gridTemplateColumns = "1fr";
|
|
46804
|
+
contentElement.style.width = "";
|
|
46805
|
+
const openRect = contentElement.getBoundingClientRect();
|
|
46806
|
+
contentElement.style.width = `${openRect.width}px`;
|
|
46807
|
+
contentContainer.style.gridTemplateColumns = "";
|
|
46808
|
+
// The closed frame must be committed while transitions are still off —
|
|
46809
|
+
// re-enabled in the same recalc, the 1fr-to-0fr trip back from the silent
|
|
46810
|
+
// measurement above would play as a second closing animation.
|
|
46811
|
+
contentContainer.getBoundingClientRect();
|
|
46812
|
+
contentContainer.style.transitionProperty = "";
|
|
46813
|
+
}, [closedContentSized, opened, settled, contentMounted]);
|
|
46814
|
+
|
|
46572
46815
|
// Mounted already open: the content is visible, its data is due.
|
|
46573
46816
|
useEffect(() => {
|
|
46574
46817
|
if (openedRef.current && hasAction) {
|
|
@@ -46594,7 +46837,7 @@ const Expandable = props => {
|
|
|
46594
46837
|
key
|
|
46595
46838
|
} = keyboardEvent;
|
|
46596
46839
|
if (key === openKeyShortcut) {
|
|
46597
|
-
if (document.activeElement !==
|
|
46840
|
+
if (document.activeElement !== uiRef.current) {
|
|
46598
46841
|
return;
|
|
46599
46842
|
}
|
|
46600
46843
|
if (!openedRef.current) {
|
|
@@ -46614,19 +46857,19 @@ const Expandable = props => {
|
|
|
46614
46857
|
if (!openedRef.current) {
|
|
46615
46858
|
return;
|
|
46616
46859
|
}
|
|
46617
|
-
const
|
|
46618
|
-
if (document.activeElement ===
|
|
46860
|
+
const uiElement = uiRef.current;
|
|
46861
|
+
if (document.activeElement === uiElement) {
|
|
46619
46862
|
keyboardEvent.preventDefault();
|
|
46620
46863
|
toggleTo(false);
|
|
46621
46864
|
} else {
|
|
46622
46865
|
keyboardEvent.preventDefault();
|
|
46623
|
-
|
|
46866
|
+
uiElement.focus();
|
|
46624
46867
|
}
|
|
46625
46868
|
}
|
|
46626
46869
|
};
|
|
46627
46870
|
const onUIClick = clickEvent => {
|
|
46628
46871
|
// A navi control inside the UI part cancels the click it consumed (see
|
|
46629
|
-
// click_to_expand.js — the
|
|
46872
|
+
// click_to_expand.js — the UI part's own aria-expanded is what it finds).
|
|
46630
46873
|
if (clickEvent.defaultPrevented) {
|
|
46631
46874
|
return;
|
|
46632
46875
|
}
|
|
@@ -46635,32 +46878,62 @@ const Expandable = props => {
|
|
|
46635
46878
|
} = clickEvent;
|
|
46636
46879
|
if (target.nodeType === 1) {
|
|
46637
46880
|
const interactiveElement = target.closest(UI_INTERACTIVE_SELECTOR);
|
|
46638
|
-
if (interactiveElement && interactiveElement !==
|
|
46881
|
+
if (interactiveElement && interactiveElement !== uiRef.current && uiRef.current.contains(interactiveElement)) {
|
|
46639
46882
|
return;
|
|
46640
46883
|
}
|
|
46641
46884
|
}
|
|
46642
46885
|
toggleTo(!openedRef.current);
|
|
46643
46886
|
};
|
|
46887
|
+
|
|
46888
|
+
// Space/Enter on the UI part itself (role button) — a key pressed on a
|
|
46889
|
+
// control inside it belongs to that control.
|
|
46890
|
+
const onUIKeyDown = keyboardEvent => {
|
|
46891
|
+
if (keyboardEvent.defaultPrevented) {
|
|
46892
|
+
return;
|
|
46893
|
+
}
|
|
46894
|
+
if (keyboardEvent.target !== uiRef.current) {
|
|
46895
|
+
return;
|
|
46896
|
+
}
|
|
46897
|
+
const {
|
|
46898
|
+
key
|
|
46899
|
+
} = keyboardEvent;
|
|
46900
|
+
if (key === " " || key === "Enter") {
|
|
46901
|
+
keyboardEvent.preventDefault();
|
|
46902
|
+
toggleTo(!openedRef.current);
|
|
46903
|
+
}
|
|
46904
|
+
};
|
|
46905
|
+
|
|
46906
|
+
// Where the content went, so the marker can point at it while open (closed
|
|
46907
|
+
// always points right): below by default, above when the content part comes
|
|
46908
|
+
// first, beside for layout="column" (the chevron then points back toward
|
|
46909
|
+
// the UI: left).
|
|
46910
|
+
const childArray = toChildArray(children);
|
|
46911
|
+
const firstPart = childArray.find(child => child && (child.type === ExpandableUI || child.type === ExpandableContent));
|
|
46912
|
+
const hasParts = Boolean(firstPart);
|
|
46913
|
+
const contentFirst = hasParts && firstPart.type === ExpandableContent;
|
|
46914
|
+
const openDirection = isColumn ? "left" : contentFirst ? "up" : "down";
|
|
46644
46915
|
const expandableContextValue = {
|
|
46645
46916
|
opened,
|
|
46646
46917
|
loading: loading || hasAction && actionLoading,
|
|
46647
46918
|
contentMounted,
|
|
46648
46919
|
hasAction,
|
|
46649
46920
|
effectiveAction,
|
|
46921
|
+
openDirection,
|
|
46922
|
+
toggleTo,
|
|
46650
46923
|
onUIClick,
|
|
46651
46924
|
onUIPointerDown,
|
|
46925
|
+
onUIKeyDown,
|
|
46652
46926
|
uiRef,
|
|
46653
|
-
toggleButtonRef,
|
|
46654
46927
|
contentContainerRef,
|
|
46655
|
-
contentId
|
|
46656
|
-
uiId
|
|
46928
|
+
contentId
|
|
46657
46929
|
};
|
|
46658
46930
|
|
|
46659
46931
|
// Explicit parts win; the `ui` prop + children is the shorthand for the
|
|
46660
|
-
// common shape (UI above, content below).
|
|
46661
|
-
|
|
46662
|
-
|
|
46663
|
-
|
|
46932
|
+
// common shape (UI above, content below). Parts are cloned on every render:
|
|
46933
|
+
// reference-stable children would be bailed out of the commit, leaving
|
|
46934
|
+
// their context subscription to re-render them asynchronously — after the
|
|
46935
|
+
// [opened] effect above, which measures the content they render.
|
|
46936
|
+
const body = hasParts ? childArray.map(child => child && (child.type === ExpandableUI || child.type === ExpandableContent) ? cloneElement(child) : child) : jsxs(Fragment$1, {
|
|
46664
46937
|
children: [jsx(ExpandableUI, {
|
|
46665
46938
|
children: ui
|
|
46666
46939
|
}), jsx(ExpandableContent, {
|
|
@@ -46671,10 +46944,12 @@ const Expandable = props => {
|
|
|
46671
46944
|
ref: rootRef,
|
|
46672
46945
|
baseClassName: "navi_expandable",
|
|
46673
46946
|
"aria-expanded": opened ? "true" : "false",
|
|
46674
|
-
"data-layout":
|
|
46947
|
+
"data-layout": isColumn ? "column" : undefined,
|
|
46948
|
+
"data-content-first": contentFirst ? "" : undefined,
|
|
46675
46949
|
"data-animation": animation ? "" : undefined,
|
|
46676
46950
|
"data-settled": settled ? "" : undefined,
|
|
46677
46951
|
"data-content-scrolls": maxContentHeight === undefined ? undefined : "",
|
|
46952
|
+
"data-closed-content-sized": closedContentSized ? "" : undefined,
|
|
46678
46953
|
...rest,
|
|
46679
46954
|
// The protocol every command target answers (see commands.js): a
|
|
46680
46955
|
// `--navi-toggle`/`--navi-open`/`--navi-close` lands here as a
|
|
@@ -46707,10 +46982,12 @@ const Expandable = props => {
|
|
|
46707
46982
|
};
|
|
46708
46983
|
|
|
46709
46984
|
/**
|
|
46710
|
-
* The always-visible part that reveals the content: the
|
|
46711
|
-
*
|
|
46712
|
-
*
|
|
46713
|
-
*
|
|
46985
|
+
* The always-visible part that reveals the content: the focusable toggle
|
|
46986
|
+
* itself (role button — click, Space/Enter, arrow keys), holding the marker
|
|
46987
|
+
* plus whatever it is given — any markup, a function of `{ open }` included.
|
|
46988
|
+
* Controls inside it keep their own behavior and do not toggle. Its position
|
|
46989
|
+
* among the parts decides where the content goes (before the content: content
|
|
46990
|
+
* below/right; after it: content above/left).
|
|
46714
46991
|
*
|
|
46715
46992
|
* @type {import("ignore:preact").FunctionComponent<{
|
|
46716
46993
|
* children?: import("ignore:preact").ComponentChildren | ((state: { open: boolean }) => import("ignore:preact").ComponentChildren),
|
|
@@ -46723,32 +47000,50 @@ const ExpandableUI = ({
|
|
|
46723
47000
|
const {
|
|
46724
47001
|
opened,
|
|
46725
47002
|
loading,
|
|
47003
|
+
openDirection,
|
|
47004
|
+
toggleTo,
|
|
46726
47005
|
onUIClick,
|
|
46727
47006
|
onUIPointerDown,
|
|
47007
|
+
onUIKeyDown,
|
|
46728
47008
|
uiRef,
|
|
46729
|
-
|
|
46730
|
-
contentId,
|
|
46731
|
-
uiId
|
|
47009
|
+
contentId
|
|
46732
47010
|
} = useExpandableContext("UI");
|
|
46733
47011
|
return jsxs("div", {
|
|
46734
47012
|
ref: uiRef,
|
|
46735
47013
|
className: "navi_expandable_ui",
|
|
47014
|
+
role: "button",
|
|
47015
|
+
tabIndex: 0,
|
|
47016
|
+
"aria-expanded": opened,
|
|
47017
|
+
"aria-controls": contentId,
|
|
46736
47018
|
onClick: onUIClick,
|
|
46737
47019
|
onPointerDown: onUIPointerDown,
|
|
47020
|
+
onKeyDown: onUIKeyDown
|
|
47021
|
+
// A command from a control inside the UI part resolves its target to
|
|
47022
|
+
// the closest [aria-expanded] — this very element (see commands.js's
|
|
47023
|
+
// resolveClosestExpandable) — so it answers the protocol too. Spread as
|
|
47024
|
+
// an object: eslint's known-DOM-property check doesn't apply to navi's
|
|
47025
|
+
// own custom events.
|
|
47026
|
+
,
|
|
47027
|
+
|
|
47028
|
+
onnavi_command: e => {
|
|
47029
|
+
onNaviCommand(e);
|
|
47030
|
+
},
|
|
47031
|
+
onnavi_request_open: () => {
|
|
47032
|
+
toggleTo(true);
|
|
47033
|
+
},
|
|
47034
|
+
onnavi_request_close: () => {
|
|
47035
|
+
toggleTo(false);
|
|
47036
|
+
},
|
|
46738
47037
|
...rest,
|
|
46739
|
-
children: [jsx("
|
|
46740
|
-
|
|
46741
|
-
|
|
46742
|
-
className: "navi_expandable_toggle",
|
|
46743
|
-
"aria-expanded": opened,
|
|
46744
|
-
"aria-controls": contentId,
|
|
46745
|
-
"aria-labelledby": uiId,
|
|
47038
|
+
children: [jsx("span", {
|
|
47039
|
+
className: "navi_expandable_marker",
|
|
47040
|
+
"aria-hidden": "true",
|
|
46746
47041
|
children: jsx(SummaryMarker, {
|
|
46747
47042
|
open: opened,
|
|
46748
|
-
loading: loading
|
|
47043
|
+
loading: loading,
|
|
47044
|
+
openDirection: openDirection
|
|
46749
47045
|
})
|
|
46750
47046
|
}), jsx("div", {
|
|
46751
|
-
id: uiId,
|
|
46752
47047
|
className: "navi_expandable_ui_label",
|
|
46753
47048
|
children: typeof children === "function" ? children({
|
|
46754
47049
|
open: opened
|
|
@@ -46789,8 +47084,11 @@ const ExpandableContent = ({
|
|
|
46789
47084
|
inert: opened ? undefined : true,
|
|
46790
47085
|
...rest,
|
|
46791
47086
|
children: jsx("div", {
|
|
46792
|
-
className: "
|
|
46793
|
-
children:
|
|
47087
|
+
className: "navi_expandable_content_sizer",
|
|
47088
|
+
children: jsx("div", {
|
|
47089
|
+
className: "navi_expandable_content",
|
|
47090
|
+
children: contentMounted ? content : null
|
|
47091
|
+
})
|
|
46794
47092
|
})
|
|
46795
47093
|
});
|
|
46796
47094
|
};
|
|
@@ -46798,8 +47096,8 @@ Expandable.UI = ExpandableUI;
|
|
|
46798
47096
|
Expandable.Content = ExpandableContent;
|
|
46799
47097
|
|
|
46800
47098
|
// What a click inside the UI part must not toggle: it was aimed at the
|
|
46801
|
-
// control, not at the row. The
|
|
46802
|
-
// the call site.
|
|
47099
|
+
// control, not at the row. The UI part itself matches [role='button'] and is
|
|
47100
|
+
// the one exception, excluded at the call site.
|
|
46803
47101
|
const UI_INTERACTIVE_SELECTOR = ["a[href]", "button", "input", "select", "textarea", "label", "[role='button']", "[contenteditable='']", "[contenteditable='true']", "audio[controls]", "video[controls]"].join(", ");
|
|
46804
47102
|
const createToggleEvent = open => {
|
|
46805
47103
|
const newState = open ? "open" : "closed";
|
|
@@ -46884,7 +47182,8 @@ const ControlGroup = props => {
|
|
|
46884
47182
|
stateType: "object",
|
|
46885
47183
|
cascadeValidationToChildren: true,
|
|
46886
47184
|
aggregateChildStates: props.aggregateChildStates,
|
|
46887
|
-
distributeChildUIState: props.distributeChildUIState
|
|
47185
|
+
distributeChildUIState: props.distributeChildUIState,
|
|
47186
|
+
distributeChildStates: props.distributeChildStates
|
|
46888
47187
|
});
|
|
46889
47188
|
const {
|
|
46890
47189
|
children
|
|
@@ -46898,6 +47197,7 @@ const ControlGroup = props => {
|
|
|
46898
47197
|
,
|
|
46899
47198
|
aggregateChildStates: undefined,
|
|
46900
47199
|
distributeChildUIState: undefined,
|
|
47200
|
+
distributeChildStates: undefined,
|
|
46901
47201
|
pseudoClasses: CONTROL_GROUP_PSEUDO_CLASSES,
|
|
46902
47202
|
children: jsx(ControlgroupChildrenWrapper, {
|
|
46903
47203
|
...childrenWrapperProps,
|
|
@@ -50335,7 +50635,7 @@ const seedDefaultValueFromSignal = (props) => {
|
|
|
50335
50635
|
}
|
|
50336
50636
|
};
|
|
50337
50637
|
|
|
50338
|
-
const resolveInputProps = (props) => {
|
|
50638
|
+
const resolveInputProps = (props, { controlType = "input" } = {}) => {
|
|
50339
50639
|
// `signal` carries a bound state signal. It is left on `props` on purpose:
|
|
50340
50640
|
// `createControlInfo` (control_hooks.jsx) reads it to seed the state and to
|
|
50341
50641
|
// follow it, and `onUIAction` (ui_state_controller.js) writes user
|
|
@@ -50352,8 +50652,20 @@ const resolveInputProps = (props) => {
|
|
|
50352
50652
|
}
|
|
50353
50653
|
}
|
|
50354
50654
|
if (props.type === undefined && signalOptions.type !== undefined) {
|
|
50355
|
-
|
|
50655
|
+
const typeFromSignal =
|
|
50356
50656
|
VALIDITY_TYPE_TO_INPUT_TYPE[signalOptions.type] ?? signalOptions.type;
|
|
50657
|
+
// What a signal says is what its value IS; what a control's `type` says
|
|
50658
|
+
// is what the control is. They usually agree — a date-typed signal wants
|
|
50659
|
+
// a date field — but a boolean one maps to a checkbox, and a picker made
|
|
50660
|
+
// into a checkbox is not a picker with a different look: it is another
|
|
50661
|
+
// control, with no popup to open. A picker asked to hold a yes/no keeps
|
|
50662
|
+
// its two rows and stays itself.
|
|
50663
|
+
const wouldChangeWhatTheControlIs =
|
|
50664
|
+
controlType === "picker" &&
|
|
50665
|
+
(typeFromSignal === "checkbox" || typeFromSignal === "radio");
|
|
50666
|
+
if (!wouldChangeWhatTheControlIs) {
|
|
50667
|
+
props.type = typeFromSignal;
|
|
50668
|
+
}
|
|
50357
50669
|
}
|
|
50358
50670
|
}
|
|
50359
50671
|
|
|
@@ -55179,6 +55491,20 @@ const PickerCustomResolver = props => {
|
|
|
55179
55491
|
...props
|
|
55180
55492
|
});
|
|
55181
55493
|
}
|
|
55494
|
+
if (props.type === undefined) {
|
|
55495
|
+
// A picker with a popup of its own holds whatever the control inside it
|
|
55496
|
+
// holds — a boolean, a number, an id — and a field with no type is read
|
|
55497
|
+
// back off the DOM, where every value is a string. "false" then matches no
|
|
55498
|
+
// row, the popup empties, and that emptiness climbs back into the picker:
|
|
55499
|
+
// a value survives its own round trip only while it is text. "navi_js" is
|
|
55500
|
+
// how a field says its value is a JS one, kept beside the DOM (see
|
|
55501
|
+
// controller_registry.js) — the same thing type="array"/"object" already
|
|
55502
|
+
// say for their shapes.
|
|
55503
|
+
return jsx(PickerCustom, {
|
|
55504
|
+
...props,
|
|
55505
|
+
type: "navi_js"
|
|
55506
|
+
});
|
|
55507
|
+
}
|
|
55182
55508
|
return jsx(PickerCustom, {
|
|
55183
55509
|
...props
|
|
55184
55510
|
});
|
|
@@ -62190,7 +62516,7 @@ const PickerObjectUI = () => {
|
|
|
62190
62516
|
value,
|
|
62191
62517
|
placeholder
|
|
62192
62518
|
} = useContext(PickerContext);
|
|
62193
|
-
if (
|
|
62519
|
+
if (uiStateHoldsNothing(value)) {
|
|
62194
62520
|
if (!placeholder) {
|
|
62195
62521
|
return null;
|
|
62196
62522
|
}
|
|
@@ -62227,7 +62553,7 @@ const PickerArrayUI = () => {
|
|
|
62227
62553
|
placeholder,
|
|
62228
62554
|
maxLines
|
|
62229
62555
|
} = useContext(PickerContext);
|
|
62230
|
-
if (
|
|
62556
|
+
if (uiStateHoldsNothing(value)) {
|
|
62231
62557
|
if (!placeholder) {
|
|
62232
62558
|
return null;
|
|
62233
62559
|
}
|
|
@@ -63259,7 +63585,9 @@ const PickerFirstResolver = props => {
|
|
|
63259
63585
|
const Next = useNextResolver();
|
|
63260
63586
|
const defaultRef = useRef(null);
|
|
63261
63587
|
props.ref = props.ref || defaultRef;
|
|
63262
|
-
resolveInputProps(props
|
|
63588
|
+
resolveInputProps(props, {
|
|
63589
|
+
controlType: "picker"
|
|
63590
|
+
});
|
|
63263
63591
|
return jsx(Next, {
|
|
63264
63592
|
...props
|
|
63265
63593
|
});
|
|
@@ -64335,7 +64663,8 @@ const SpinGroup = props => {
|
|
|
64335
64663
|
stateType: "object",
|
|
64336
64664
|
cascadeValidationToChildren: true,
|
|
64337
64665
|
aggregateChildStates: props.aggregateChildStates,
|
|
64338
|
-
distributeChildUIState: props.distributeChildUIState
|
|
64666
|
+
distributeChildUIState: props.distributeChildUIState,
|
|
64667
|
+
distributeChildStates: props.distributeChildStates
|
|
64339
64668
|
});
|
|
64340
64669
|
const {
|
|
64341
64670
|
children
|
|
@@ -64352,6 +64681,7 @@ const SpinGroup = props => {
|
|
|
64352
64681
|
// not reach the DOM as unknown attributes.
|
|
64353
64682
|
aggregateChildStates: undefined,
|
|
64354
64683
|
distributeChildUIState: undefined,
|
|
64684
|
+
distributeChildStates: undefined,
|
|
64355
64685
|
baseClassName: "navi_spin_group",
|
|
64356
64686
|
pseudoClasses: SPIN_GROUP_PSEUDO_CLASSES
|
|
64357
64687
|
// What the frame and what sits between the spins are drawn from: the
|
|
@@ -68863,7 +69193,8 @@ const WheelGroup = props => {
|
|
|
68863
69193
|
stateType: "object",
|
|
68864
69194
|
cascadeValidationToChildren: true,
|
|
68865
69195
|
aggregateChildStates: props.aggregateChildStates,
|
|
68866
|
-
distributeChildUIState: props.distributeChildUIState
|
|
69196
|
+
distributeChildUIState: props.distributeChildUIState,
|
|
69197
|
+
distributeChildStates: props.distributeChildStates
|
|
68867
69198
|
});
|
|
68868
69199
|
const {
|
|
68869
69200
|
children
|
|
@@ -68922,6 +69253,7 @@ const WheelGroup = props => {
|
|
|
68922
69253
|
zoom: undefined,
|
|
68923
69254
|
aggregateChildStates: undefined,
|
|
68924
69255
|
distributeChildUIState: undefined,
|
|
69256
|
+
distributeChildStates: undefined,
|
|
68925
69257
|
baseClassName: "navi_wheel_group",
|
|
68926
69258
|
"data-horizontal": horizontal ? "" : undefined,
|
|
68927
69259
|
style: groupStyle,
|