@jsenv/navi 0.29.418 → 0.29.419
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/README.md +1 -1
- package/dist/dev/jsenv_navi.js +192 -43
- package/dist/dev/jsenv_navi.js.map +10 -10
- package/dist/jsenv_navi.js +190 -41
- package/dist/jsenv_navi.js.map +10 -10
- package/docs/AI_INSTRUCTIONS.md +177 -261
- package/docs/actions.md +164 -251
- package/docs/autofocus.md +66 -83
- package/docs/control_object.md +117 -214
- package/docs/control_value.md +121 -185
- package/docs/create_and_edit.md +159 -254
- package/docs/css_architecture.md +196 -260
- package/docs/data_states.md +41 -44
- package/docs/dialog_shape.md +91 -134
- package/docs/drag_interactions.md +115 -174
- package/docs/drag_to_travel.md +10 -9
- package/docs/dynamic_import.md +119 -136
- package/docs/error_handling.md +75 -127
- package/docs/field_validation.md +49 -89
- package/docs/form_changed.md +61 -78
- package/docs/group.md +141 -0
- package/docs/i18n.md +31 -76
- package/docs/interactions.md +6 -4
- package/docs/list_action.md +36 -51
- package/docs/list_refresh.md +73 -115
- package/docs/mobile_touch.md +56 -62
- package/docs/navigation.md +377 -723
- package/docs/network_policy.md +61 -75
- package/docs/pan_zoom.md +62 -103
- package/docs/popup_backdrop.md +128 -168
- package/docs/popup_lift.md +179 -268
- package/docs/popup_open.md +380 -651
- package/docs/resource.md +228 -408
- package/docs/route_transitions.md +333 -482
- package/docs/safe_area.md +102 -89
- package/docs/scroll.md +175 -335
- package/docs/state_binding.md +2 -1
- package/docs/testid.md +18 -23
- package/docs/typography.md +111 -141
- package/docs/view_transitions.md +106 -104
- package/docs/z_index.md +61 -109
- package/package.json +2 -2
- package/docs/badge_list.md +0 -83
- package/docs/control_group.md +0 -168
- package/docs/mobile_layout_pitfalls.md +0 -145
package/README.md
CHANGED
|
@@ -57,7 +57,7 @@ Validation goes beyond native browser constraints: custom rules, better error po
|
|
|
57
57
|
|
|
58
58
|
Some values are moved rather than typed: `Spin` puts the way back and the way on around a value one step at a time, and `SpinGroup` makes several of them read as one — `TimeSpin` ("07h30") and `TimeRangeSpin` ("de 7h à 21h", end after start) are built that way. `Wheel` is the scroll picker a phone shows for the same job, with `TimeWheel` and `TimeRangeWheel` built on it.
|
|
59
59
|
|
|
60
|
-
`Group` makes several controls read as one framed object, `Editable` edits a value in place, exactly where it is written.
|
|
60
|
+
`Group` makes several controls read as one framed object (see [docs/group.md](./docs/group.md)), `Editable` edits a value in place, exactly where it is written.
|
|
61
61
|
|
|
62
62
|
## List
|
|
63
63
|
|
package/dist/dev/jsenv_navi.js
CHANGED
|
@@ -15063,7 +15063,11 @@ const createOpenController = (
|
|
|
15063
15063
|
const applyChange = () => {
|
|
15064
15064
|
// Recorded with the change itself: what the DOM shows is what a render
|
|
15065
15065
|
// landing between the ask and the picture must draw (see `openedInDom`).
|
|
15066
|
-
|
|
15066
|
+
// An opening records it inside the change, once its content is built
|
|
15067
|
+
// (see open() below).
|
|
15068
|
+
if (!opened) {
|
|
15069
|
+
controller.openedInDom = false;
|
|
15070
|
+
}
|
|
15067
15071
|
change();
|
|
15068
15072
|
};
|
|
15069
15073
|
const { transitionChange } = controller;
|
|
@@ -15343,15 +15347,17 @@ const createOpenController = (
|
|
|
15343
15347
|
controller.mountContent?.();
|
|
15344
15348
|
// Only now — after the content has been built, before openEffect
|
|
15345
15349
|
// shows it. Dialog/Popover recompute aria-expanded and navi-hidden
|
|
15346
|
-
// from
|
|
15350
|
+
// from `openedInDom` on every render, and mountContent above renders
|
|
15347
15351
|
// synchronously: flipping it any earlier commits an already-open DOM
|
|
15348
15352
|
// (aria-expanded "true", navi-hidden gone) before openEffect has run
|
|
15349
15353
|
// a single statement, so the "closed" frame it pins to transition
|
|
15350
15354
|
// from is in fact the open one and the entrance animation has
|
|
15351
|
-
// nothing to play
|
|
15352
|
-
//
|
|
15353
|
-
//
|
|
15354
|
-
// popup_content_mount.js and
|
|
15355
|
+
// nothing to play — on every first opening that builds its content.
|
|
15356
|
+
// It also gives the content it just built the opening it is
|
|
15357
|
+
// documented to observe — mounted while the popup reads as closed,
|
|
15358
|
+
// told it opened right after (see popup_content_mount.js and
|
|
15359
|
+
// use_displayed_layout_effect.js).
|
|
15360
|
+
controller.openedInDom = true;
|
|
15355
15361
|
controller.opened = true;
|
|
15356
15362
|
// Which press it opened during, so the release of that press is not
|
|
15357
15363
|
// read as somebody dismissing it (see openedDuringThisPress).
|
|
@@ -15760,7 +15766,14 @@ const useOpenPropsEffectOnOpenController = (
|
|
|
15760
15766
|
lastRunOpenRef.current = open;
|
|
15761
15767
|
|
|
15762
15768
|
if (isFirstRun) {
|
|
15763
|
-
|
|
15769
|
+
// "interaction" is said even over what the caller holds reading open: a
|
|
15770
|
+
// popup built by the request that opens it — a dialog standing in until
|
|
15771
|
+
// its signal said open, see DialogStandIn in dialog.jsx — mounts BECAUSE
|
|
15772
|
+
// of that opening.
|
|
15773
|
+
const mountOpenReason =
|
|
15774
|
+
open && defaultOpen === "interaction"
|
|
15775
|
+
? "interaction"
|
|
15776
|
+
: open || defaultOpen;
|
|
15764
15777
|
if (mountOpenReason) {
|
|
15765
15778
|
// Whether this popup being open is something that just happened, or
|
|
15766
15779
|
// something that was already true when the page appeared. "interaction"
|
|
@@ -28693,6 +28706,18 @@ const useAsyncData = (promiseOrAction, {
|
|
|
28693
28706
|
|
|
28694
28707
|
// ─── useAction ────────────────────────────────────────────────────────────────
|
|
28695
28708
|
|
|
28709
|
+
// subscribe() calls back at once with the value the signal holds; what these
|
|
28710
|
+
// subscriptions are for is what changes after.
|
|
28711
|
+
const subscribeToChanges = (signal, onChange) => {
|
|
28712
|
+
let initial = true;
|
|
28713
|
+
return signal.subscribe(value => {
|
|
28714
|
+
if (initial) {
|
|
28715
|
+
initial = false;
|
|
28716
|
+
return;
|
|
28717
|
+
}
|
|
28718
|
+
onChange(value);
|
|
28719
|
+
});
|
|
28720
|
+
};
|
|
28696
28721
|
const LoadingContext = createContext(null);
|
|
28697
28722
|
const actionPendingPromiseWeakMap = new WeakMap();
|
|
28698
28723
|
const dismissedActionWeakSet = new WeakSet();
|
|
@@ -28737,6 +28762,15 @@ const useActionAsyncData = (action, {
|
|
|
28737
28762
|
const runningState = action.runningStateSignal.peek();
|
|
28738
28763
|
const [, setTick] = useState(0);
|
|
28739
28764
|
const routePage = useContext(RoutePageContext);
|
|
28765
|
+
// What this render read of the action, for the subscriptions below to tell
|
|
28766
|
+
// a change that came between the render and them from none at all.
|
|
28767
|
+
const readRef = useRef(null);
|
|
28768
|
+
readRef.current = {
|
|
28769
|
+
runningState,
|
|
28770
|
+
data: action.dataSignal.peek(),
|
|
28771
|
+
params: action.paramsSignal.peek(),
|
|
28772
|
+
settling: action.paramsSettlingSignal.peek()
|
|
28773
|
+
};
|
|
28740
28774
|
useEffect(() => {
|
|
28741
28775
|
let stopOwingRender = null;
|
|
28742
28776
|
const rerender = () => {
|
|
@@ -28764,7 +28798,12 @@ const useActionAsyncData = (action, {
|
|
|
28764
28798
|
}
|
|
28765
28799
|
setTick(n => n + 1);
|
|
28766
28800
|
};
|
|
28767
|
-
|
|
28801
|
+
// A run under way is a new try: an error dismissed before it is not the
|
|
28802
|
+
// one it may end on.
|
|
28803
|
+
if (action.runningStateSignal.peek() === RUNNING) {
|
|
28804
|
+
dismissedActionWeakSet.delete(action);
|
|
28805
|
+
}
|
|
28806
|
+
const unsubscribeFromRunningState = subscribeToChanges(action.runningStateSignal, state => {
|
|
28768
28807
|
if (state === RUNNING) {
|
|
28769
28808
|
dismissedActionWeakSet.delete(action);
|
|
28770
28809
|
}
|
|
@@ -28775,38 +28814,24 @@ const useActionAsyncData = (action, {
|
|
|
28775
28814
|
// (a PUT upserting an item that a GET_MANY list already holds) changes the
|
|
28776
28815
|
// data while this action stays COMPLETED. Subscribing here re-renders
|
|
28777
28816
|
// through the same controlled path as the run state, instead of `.value`.
|
|
28778
|
-
|
|
28779
|
-
const unsubscribeFromData = action.dataSignal.subscribe(() => {
|
|
28780
|
-
if (dataNotificationIsInitial) {
|
|
28781
|
-
// subscribe() calls back synchronously with the current value
|
|
28782
|
-
dataNotificationIsInitial = false;
|
|
28783
|
-
return;
|
|
28784
|
-
}
|
|
28785
|
-
rerender();
|
|
28786
|
-
});
|
|
28817
|
+
const unsubscribeFromData = subscribeToChanges(action.dataSignal, rerender);
|
|
28787
28818
|
// The params say WHICH question this is, and this hook reads them: to know
|
|
28788
28819
|
// there is nothing to ask for, and to start the run it owns. A binding
|
|
28789
28820
|
// retargeting from no question to a question — a filter chosen, a first
|
|
28790
28821
|
// character typed — is announced by nothing above: a fresh target is IDLE
|
|
28791
28822
|
// holding no data, the very state the hook already sees.
|
|
28792
|
-
|
|
28793
|
-
const unsubscribeFromParams = action.paramsSignal.subscribe(() => {
|
|
28794
|
-
if (paramsNotificationIsInitial) {
|
|
28795
|
-
paramsNotificationIsInitial = false;
|
|
28796
|
-
return;
|
|
28797
|
-
}
|
|
28798
|
-
rerender();
|
|
28799
|
-
});
|
|
28823
|
+
const unsubscribeFromParams = subscribeToChanges(action.paramsSignal, rerender);
|
|
28800
28824
|
// A debounced binding waits before it retargets, so nothing above changes
|
|
28801
28825
|
// while the delay runs — but what is on screen is already out of date.
|
|
28802
|
-
|
|
28803
|
-
|
|
28804
|
-
|
|
28805
|
-
|
|
28806
|
-
|
|
28807
|
-
|
|
28826
|
+
const unsubscribeFromParamsSettling = subscribeToChanges(action.paramsSettlingSignal, rerender);
|
|
28827
|
+
// Subscribed once the browser has painted, so the action may have moved
|
|
28828
|
+
// since the render: a run that very render started may be over already,
|
|
28829
|
+
// and nothing would say so again. What the render read, against what is
|
|
28830
|
+
// there now — and nothing moved, nothing to draw again.
|
|
28831
|
+
const read = readRef.current;
|
|
28832
|
+
if (action.runningStateSignal.peek() !== read.runningState || action.dataSignal.peek() !== read.data || action.paramsSignal.peek() !== read.params || action.paramsSettlingSignal.peek() !== read.settling) {
|
|
28808
28833
|
rerender();
|
|
28809
|
-
}
|
|
28834
|
+
}
|
|
28810
28835
|
return () => {
|
|
28811
28836
|
unsubscribeFromRunningState();
|
|
28812
28837
|
unsubscribeFromData();
|
|
@@ -40384,6 +40409,17 @@ const useDisplayedLayoutEffect = (ref, callback, deps) => {
|
|
|
40384
40409
|
decisionDeferredToRef.current = popupMounting;
|
|
40385
40410
|
return;
|
|
40386
40411
|
}
|
|
40412
|
+
if (
|
|
40413
|
+
(el.tagName === "DIALOG" || el.hasAttribute("popover")) &&
|
|
40414
|
+
!isAncestorOpen(el)
|
|
40415
|
+
) {
|
|
40416
|
+
// Itself a closed <dialog> or [popover]: display:none by the UA's own
|
|
40417
|
+
// rules, which the markup says without asking the layout — asked, every
|
|
40418
|
+
// closed popup mounting would force a style recalculation, and a list
|
|
40419
|
+
// whose rows each hold a few mounts hundreds of them. The observer
|
|
40420
|
+
// below fires as for any element the ancestor hides.
|
|
40421
|
+
return;
|
|
40422
|
+
}
|
|
40387
40423
|
if (!isDisplayedDespiteClosedAncestor(el, ancestor)) {
|
|
40388
40424
|
// Ancestor is closed and took this element off screen with it — skip
|
|
40389
40425
|
// now; the observeAncestorOpenState call below will fire once it
|
|
@@ -52642,9 +52678,9 @@ const FixedBar = ({
|
|
|
52642
52678
|
* A page that overflows horizontally is a bug wherever it happens, and on
|
|
52643
52679
|
* Chrome Android it is a catastrophic one: the layout viewport inflates to the
|
|
52644
52680
|
* content and `position: fixed` centering goes with it (see
|
|
52645
|
-
* docs/
|
|
52646
|
-
* `overflow-x: clip`, and it works — at the price of making the
|
|
52647
|
-
* invisible: nothing sticks out anymore, so nothing says a fixed width, a
|
|
52681
|
+
* docs/safe_area.md#a-document-wider-than-the-screen). The remedy there is a
|
|
52682
|
+
* wrapper in `overflow-x: clip`, and it works — at the price of making the
|
|
52683
|
+
* cause invisible: nothing sticks out anymore, so nothing says a fixed width, a
|
|
52648
52684
|
* `min-width` or an unbreakable string is still oversized. This puts the
|
|
52649
52685
|
* signal back, without giving up the net.
|
|
52650
52686
|
*
|
|
@@ -65657,7 +65693,9 @@ const css$E = /* css */`
|
|
|
65657
65693
|
* @param {"always"|"idle"|"from-first-open"|"while-opened"} [props.mount] - When
|
|
65658
65694
|
* `children` are built and thrown away (see popup_content_mount.js).
|
|
65659
65695
|
* `"from-first-open"` (the default) builds them on the first open and keeps
|
|
65660
|
-
* them afterwards
|
|
65696
|
+
* them afterwards — and the dialog itself is built then too: until its first
|
|
65697
|
+
* request (an open, a press on what opens it, its signal saying open) it is
|
|
65698
|
+
* a bare `<dialog>` holding its id (see docs/popup_open.md). `"always"` builds them right away, for content something
|
|
65661
65699
|
* depends on while the popup is still closed: a value read off it, fields a
|
|
65662
65700
|
* surrounding form collects on submit, a size measured from outside.
|
|
65663
65701
|
* `"idle"` builds them in a browser idle moment after load — "always" minus
|
|
@@ -65678,11 +65716,122 @@ const Dialog = props => {
|
|
|
65678
65716
|
...props
|
|
65679
65717
|
});
|
|
65680
65718
|
}
|
|
65681
|
-
return jsx(
|
|
65719
|
+
return jsx(DialogStandIn, {
|
|
65682
65720
|
...props
|
|
65683
65721
|
});
|
|
65684
65722
|
};
|
|
65685
65723
|
|
|
65724
|
+
// A dialog nobody has asked for yet stands in as its bare <dialog>. That
|
|
65725
|
+
// element is all that reaches a closed dialog before its first opening: a
|
|
65726
|
+
// command finds it by id (`commandfor`) and asks it to open, a press on what
|
|
65727
|
+
// opens it announces itself on it (navi_open_press), --navi-toggle reads its
|
|
65728
|
+
// aria-expanded. The dialog proper — its open controller, its box, its focus
|
|
65729
|
+
// and pseudo-class wiring — is built at the first request: an open, a press
|
|
65730
|
+
// about to open it, its signal or its `open` saying open. A list whose rows
|
|
65731
|
+
// each hold a few dialogs mounts hundreds of them, and would pay for every one
|
|
65732
|
+
// at every mount, for openings that mostly never come. Built once, the dialog
|
|
65733
|
+
// stays: the next opening finds it there.
|
|
65734
|
+
const DialogStandIn = props => {
|
|
65735
|
+
const {
|
|
65736
|
+
ref,
|
|
65737
|
+
id,
|
|
65738
|
+
signal,
|
|
65739
|
+
value,
|
|
65740
|
+
open
|
|
65741
|
+
} = props;
|
|
65742
|
+
const ownRef = useRef();
|
|
65743
|
+
const dialogRef = ref || ownRef;
|
|
65744
|
+
// Why the dialog proper exists, once it does; never undone.
|
|
65745
|
+
const builtRef = useRef(null);
|
|
65746
|
+
const mountedRef = useRef(false);
|
|
65747
|
+
const isMount = !mountedRef.current;
|
|
65748
|
+
mountedRef.current = true;
|
|
65749
|
+
// Its own reading of the signal: the dialog of one card among many, told
|
|
65750
|
+
// apart by `value`, follows whether IT is the one open rather than every
|
|
65751
|
+
// write the others cause.
|
|
65752
|
+
const openedSignal = useMemo(() => signal ? computed(() => readOpened(signal.value, value)) : null, [signal, value]);
|
|
65753
|
+
const openedBySignal = openedSignal ? openedSignal.value : false;
|
|
65754
|
+
if (builtRef.current === null) {
|
|
65755
|
+
if (isMount && (needsDialogFromMount(props) || open || openedBySignal)) {
|
|
65756
|
+
builtRef.current = "mount";
|
|
65757
|
+
} else if (open || openedBySignal) {
|
|
65758
|
+
// Asked open while this stood in: the opening is happening now, and it
|
|
65759
|
+
// plays like any other (see defaultOpen "interaction").
|
|
65760
|
+
builtRef.current = "interaction";
|
|
65761
|
+
}
|
|
65762
|
+
}
|
|
65763
|
+
const [, setBuildCount] = useState(0);
|
|
65764
|
+
const warmRef = useRef(null);
|
|
65765
|
+
const build = reason => {
|
|
65766
|
+
if (builtRef.current !== null) {
|
|
65767
|
+
return;
|
|
65768
|
+
}
|
|
65769
|
+
builtRef.current = reason;
|
|
65770
|
+
flushSyncRendering(() => {
|
|
65771
|
+
setBuildCount(count => count + 1);
|
|
65772
|
+
});
|
|
65773
|
+
};
|
|
65774
|
+
const builtReason = builtRef.current;
|
|
65775
|
+
if (builtReason !== null) {
|
|
65776
|
+
return jsx(UncontrolledDialog, {
|
|
65777
|
+
...props,
|
|
65778
|
+
ref: dialogRef,
|
|
65779
|
+
defaultOpen: builtReason === "interaction" ? "interaction" : props.defaultOpen
|
|
65780
|
+
});
|
|
65781
|
+
}
|
|
65782
|
+
return jsx("dialog", {
|
|
65783
|
+
ref: dialogRef,
|
|
65784
|
+
id: id,
|
|
65785
|
+
className: "navi_dialog"
|
|
65786
|
+
// eslint-disable-next-line react/no-unknown-property
|
|
65787
|
+
,
|
|
65788
|
+
"navi-hidden": ""
|
|
65789
|
+
// eslint-disable-next-line react/no-unknown-property
|
|
65790
|
+
,
|
|
65791
|
+
"navi-out-of-flow": "",
|
|
65792
|
+
"aria-expanded": "false",
|
|
65793
|
+
"data-testid": props["data-testid"]
|
|
65794
|
+
// eslint-disable-next-line react/no-unknown-property
|
|
65795
|
+
,
|
|
65796
|
+
onnavi_request_open: e => {
|
|
65797
|
+
// Built in this same event, and asked again: the opening is the one
|
|
65798
|
+
// the dialog proper would have had — its source, its anchor, its value.
|
|
65799
|
+
build("request");
|
|
65800
|
+
const opened = dispatchCustomEvent(dialogRef.current, "navi_request_open", e.detail);
|
|
65801
|
+
if (!opened) {
|
|
65802
|
+
e.preventDefault();
|
|
65803
|
+
}
|
|
65804
|
+
}
|
|
65805
|
+
// eslint-disable-next-line react/no-unknown-property
|
|
65806
|
+
,
|
|
65807
|
+
onnavi_open_press: () => {
|
|
65808
|
+
// What the opening will read is asked for at once, as the dialog
|
|
65809
|
+
// proper does (see open_controller.js); the dialog itself two frames
|
|
65810
|
+
// later, so the pressed trigger is painted first — the click that
|
|
65811
|
+
// follows opens it built, or builds it itself when it comes sooner.
|
|
65812
|
+
if (signal) {
|
|
65813
|
+
preloadState(signal, readOpenValue(value));
|
|
65814
|
+
}
|
|
65815
|
+
if (warmRef.current) {
|
|
65816
|
+
return;
|
|
65817
|
+
}
|
|
65818
|
+
warmRef.current = requestFrameAfterNext(() => {
|
|
65819
|
+
warmRef.current = null;
|
|
65820
|
+
build("press");
|
|
65821
|
+
});
|
|
65822
|
+
}
|
|
65823
|
+
});
|
|
65824
|
+
};
|
|
65825
|
+
// What needs the dialog proper from the start: an opening at mount, content
|
|
65826
|
+
// built ahead of any opening, a history entry to read, an anchor whose hover
|
|
65827
|
+
// or focus builds the content ahead of the click.
|
|
65828
|
+
const needsDialogFromMount = props => {
|
|
65829
|
+
if (props.defaultOpen || props.navState || props.anchor !== undefined) {
|
|
65830
|
+
return true;
|
|
65831
|
+
}
|
|
65832
|
+
return props.mount !== undefined && props.mount !== "from-first-open";
|
|
65833
|
+
};
|
|
65834
|
+
|
|
65686
65835
|
// No openController passed: this Dialog is used declaratively (e.g. driven
|
|
65687
65836
|
// by --navi-toggle/--navi-open/--navi-close commands, the `open` prop, or
|
|
65688
65837
|
// `defaultOpen`) rather than owned by a parent component.
|
|
@@ -77527,10 +77676,10 @@ const useBadgeRegistry = (children, enabled) => {
|
|
|
77527
77676
|
* list renders nothing at all. In a <Picker> this is the only placeholder
|
|
77528
77677
|
* the user gets — a picker given a `ui` does not draw its own — so pass the
|
|
77529
77678
|
* placeholder text: `fallback="Select skills…"`. Plain text is the right
|
|
77530
|
-
* choice there: it reads at the picker's size, in the
|
|
77531
|
-
*
|
|
77532
|
-
* transparent <Badge> matches the slot to the pixel instead, at the
|
|
77533
|
-
* badge-sized text. See docs/
|
|
77679
|
+
* choice there: it reads at the picker's size, in the normal text color (a
|
|
77680
|
+
* caller's `ui` is never greyed as a placeholder), and the box stays the same
|
|
77681
|
+
* height. A transparent <Badge> matches the slot to the pixel instead, at the
|
|
77682
|
+
* price of badge-sized text. See docs/typography.md#badgelist.
|
|
77534
77683
|
* @param {boolean} [shrinkWrap]
|
|
77535
77684
|
* Narrows the list down to its widest row so the last row isn't ragged.
|
|
77536
77685
|
* Defaults to true inside a <Picker> — the trigger draws a border around the
|
|
@@ -79731,8 +79880,8 @@ const PickerFirstResolver = props => {
|
|
|
79731
79880
|
* filter", "anywhere"), so its empty look is the caller's. A `ui` must
|
|
79732
79881
|
* therefore say on its own that the picker is empty, and take the same room
|
|
79733
79882
|
* empty as it does filled. A <BadgeList> gets both from its `fallback` — the
|
|
79734
|
-
* placeholder text, as plain text, see docs/
|
|
79735
|
-
* nothing in the slot at all.
|
|
79883
|
+
* placeholder text, as plain text, see docs/typography.md#badgelist.
|
|
79884
|
+
* `"default"` draws nothing in the slot at all.
|
|
79736
79885
|
*
|
|
79737
79886
|
* Under `variant="icon"` there is no value to draw, so this IS the trigger:
|
|
79738
79887
|
* left out it is the icon the right slot would have shown (`rightSlotIcon`,
|