@allxsmith/bestax-bulma 5.27.7 → 5.28.0
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/bestax.css +1 -1
- package/dist/bestax.css.map +1 -1
- package/dist/esm/components/HoverCard.js +127 -0
- package/dist/esm/components/HoverCard.js.map +1 -0
- package/dist/esm/form/DateInput.js.map +1 -1
- package/dist/esm/form/DateInputBase.js +2 -3
- package/dist/esm/form/DateInputBase.js.map +1 -1
- package/dist/esm/form/DateTimeInput.js.map +1 -1
- package/dist/esm/form/DateTimeInputBase.js +2 -3
- package/dist/esm/form/DateTimeInputBase.js.map +1 -1
- package/dist/esm/form/Field.js +36 -5
- package/dist/esm/form/Field.js.map +1 -1
- package/dist/esm/form/File.js.map +1 -1
- package/dist/esm/form/FormContext.js +53 -9
- package/dist/esm/form/FormContext.js.map +1 -1
- package/dist/esm/form/Input.js.map +1 -1
- package/dist/esm/form/InputBase.js +6 -5
- package/dist/esm/form/InputBase.js.map +1 -1
- package/dist/esm/form/Numberinput.js.map +1 -1
- package/dist/esm/form/Select.js.map +1 -1
- package/dist/esm/form/SelectBase.js +7 -5
- package/dist/esm/form/SelectBase.js.map +1 -1
- package/dist/esm/form/Slider.js.map +1 -1
- package/dist/esm/form/Taginput.js.map +1 -1
- package/dist/esm/form/TextArea.js.map +1 -1
- package/dist/esm/form/TextAreaBase.js +6 -5
- package/dist/esm/form/TextAreaBase.js.map +1 -1
- package/dist/esm/form/TimeInput.js.map +1 -1
- package/dist/esm/form/TimeInputBase.js +2 -3
- package/dist/esm/form/TimeInputBase.js.map +1 -1
- package/dist/esm/form/useAutoLabelId.js +8 -3
- package/dist/esm/form/useAutoLabelId.js.map +1 -1
- package/dist/esm/helpers/Theme.js +87 -17
- package/dist/esm/helpers/Theme.js.map +1 -1
- package/dist/esm/helpers/devWarnings.js +8 -3
- package/dist/esm/helpers/devWarnings.js.map +1 -1
- package/dist/esm/helpers/useFlexboxClasses.js +1 -1
- package/dist/esm/helpers/useFocusTrap.js +2 -1
- package/dist/esm/helpers/useFocusTrap.js.map +1 -1
- package/dist/esm/helpers/useHoverOpen.js +390 -0
- package/dist/esm/helpers/useHoverOpen.js.map +1 -0
- package/dist/esm/helpers/useOtherClasses.js +1 -1
- package/dist/esm/helpers/useSpacingClasses.js +1 -1
- package/dist/esm/helpers/useTypographyClasses.js +1 -1
- package/dist/esm/helpers/useVisibilityClasses.js +1 -1
- package/dist/extras.css +1 -1
- package/dist/extras.css.map +1 -1
- package/dist/index.cjs +700 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +1 -0
- package/dist/index.esm.js.map +1 -1
- package/dist/types/components/HoverCard.d.ts +136 -0
- package/dist/types/form/DateInput.d.ts +1 -1
- package/dist/types/form/DateTimeInput.d.ts +1 -1
- package/dist/types/form/Field.d.ts +2 -2
- package/dist/types/form/File.d.ts +1 -1
- package/dist/types/form/FormContext.d.ts +47 -8
- package/dist/types/form/Input.d.ts +1 -1
- package/dist/types/form/Numberinput.d.ts +1 -1
- package/dist/types/form/Select.d.ts +1 -1
- package/dist/types/form/Slider.d.ts +1 -1
- package/dist/types/form/Taginput.d.ts +1 -1
- package/dist/types/form/TextArea.d.ts +1 -1
- package/dist/types/form/TimeInput.d.ts +1 -1
- package/dist/types/form/useAutoLabelId.d.ts +4 -2
- package/dist/types/helpers/Theme.d.ts +15 -1
- package/dist/types/helpers/devWarnings.d.ts +8 -0
- package/dist/types/helpers/useFocusTrap.d.ts +9 -0
- package/dist/types/helpers/useHoverOpen.d.ts +98 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types-cjs/components/HoverCard.d.ts +136 -0
- package/dist/types-cjs/form/DateInput.d.ts +1 -1
- package/dist/types-cjs/form/DateTimeInput.d.ts +1 -1
- package/dist/types-cjs/form/Field.d.ts +2 -2
- package/dist/types-cjs/form/File.d.ts +1 -1
- package/dist/types-cjs/form/FormContext.d.ts +47 -8
- package/dist/types-cjs/form/Input.d.ts +1 -1
- package/dist/types-cjs/form/Numberinput.d.ts +1 -1
- package/dist/types-cjs/form/Select.d.ts +1 -1
- package/dist/types-cjs/form/Slider.d.ts +1 -1
- package/dist/types-cjs/form/Taginput.d.ts +1 -1
- package/dist/types-cjs/form/TextArea.d.ts +1 -1
- package/dist/types-cjs/form/TimeInput.d.ts +1 -1
- package/dist/types-cjs/form/useAutoLabelId.d.ts +4 -2
- package/dist/types-cjs/helpers/Theme.d.ts +15 -1
- package/dist/types-cjs/helpers/devWarnings.d.ts +8 -0
- package/dist/types-cjs/helpers/useFocusTrap.d.ts +9 -0
- package/dist/types-cjs/helpers/useHoverOpen.d.ts +98 -0
- package/dist/types-cjs/index.d.ts +1 -0
- package/dist/versions/bestax-no-dark-mode.css +1 -1
- package/dist/versions/bestax-no-dark-mode.css.map +1 -1
- package/dist/versions/bestax-no-helpers-prefixed.css +1 -1
- package/dist/versions/bestax-no-helpers-prefixed.css.map +1 -1
- package/dist/versions/bestax-no-helpers.css +1 -1
- package/dist/versions/bestax-no-helpers.css.map +1 -1
- package/dist/versions/bestax-prefixed.css +1 -1
- package/dist/versions/bestax-prefixed.css.map +1 -1
- package/package.json +18 -18
- package/src/scss/components/_hovercard.scss +118 -0
- package/src/scss/components/_index.scss +1 -0
package/dist/index.cjs
CHANGED
|
@@ -1444,8 +1444,13 @@ function isCustomElement(as) {
|
|
|
1444
1444
|
|
|
1445
1445
|
// INTERNAL: deliberately not exported from src/index.ts.
|
|
1446
1446
|
const warnedKeys = new Set();
|
|
1447
|
-
|
|
1448
|
-
|
|
1447
|
+
/**
|
|
1448
|
+
* Whether this is a development build. Fail closed: with no bundler and no
|
|
1449
|
+
* Node (raw CDN ESM), reading `process` throws and this is false, so
|
|
1450
|
+
* production can never warn by accident. Exported for a caller whose
|
|
1451
|
+
* development-only check costs something before it reaches `warnOnce`, so it
|
|
1452
|
+
* can skip that work in production too.
|
|
1453
|
+
*/
|
|
1449
1454
|
const isDev = () => {
|
|
1450
1455
|
try {
|
|
1451
1456
|
return process.env.NODE_ENV !== 'production';
|
|
@@ -5082,6 +5087,7 @@ const isDetailsSummary = (el) => el.parentElement?.localName === 'details' &&
|
|
|
5082
5087
|
* can read `-1` for an editable region or an `<embed>` that Tab still visits,
|
|
5083
5088
|
* and `0` for a `<video>` without controls that it doesn't. So a valid
|
|
5084
5089
|
* `tabindex` attribute decides, and otherwise the element's kind does.
|
|
5090
|
+
* Internal like `firstTabStop`: `src/index.ts` names what it takes from here.
|
|
5085
5091
|
*/
|
|
5086
5092
|
function isTabbableKind(el) {
|
|
5087
5093
|
const tabindex = el.getAttribute('tabindex');
|
|
@@ -6009,6 +6015,502 @@ const Popover = withSubComponents(PopoverComponent, {
|
|
|
6009
6015
|
Close: PopoverClose,
|
|
6010
6016
|
}, 'Popover');
|
|
6011
6017
|
|
|
6018
|
+
// INTERNAL: deliberately not exported from src/index.ts.
|
|
6019
|
+
/**
|
|
6020
|
+
* Whether focus that just arrived on `el` is keyboard focus, as the browser
|
|
6021
|
+
* decides it for `:focus-visible`: focus from a key or from the browser's
|
|
6022
|
+
* own controls is, focus a press brings isn't, and focus a script moves
|
|
6023
|
+
* follows whichever came last. A browser that can't match the selector
|
|
6024
|
+
* throws, and then every focus counts, so keyboard users can still open it.
|
|
6025
|
+
*/
|
|
6026
|
+
function isKeyboardFocus(el) {
|
|
6027
|
+
try {
|
|
6028
|
+
return el.matches(':focus-visible');
|
|
6029
|
+
}
|
|
6030
|
+
catch {
|
|
6031
|
+
return true;
|
|
6032
|
+
}
|
|
6033
|
+
}
|
|
6034
|
+
/** Whether `node` is `container` or inside it, when there are both. */
|
|
6035
|
+
const within = (container, node) => Boolean(container && node && container.contains(node));
|
|
6036
|
+
/**
|
|
6037
|
+
* Per document, the hooks that want Escape and the one listener they share
|
|
6038
|
+
* while there are any, so a card inside another card doesn't add a second
|
|
6039
|
+
* listener that closes both on one keypress.
|
|
6040
|
+
*/
|
|
6041
|
+
const escapeListeners = new Map();
|
|
6042
|
+
/** Counts openings across the page, so the most recent one is known. */
|
|
6043
|
+
let openings = 0;
|
|
6044
|
+
/**
|
|
6045
|
+
* Adds `entry` to the hooks that want Escape on `doc`, starting the shared
|
|
6046
|
+
* listener when it is the first.
|
|
6047
|
+
*
|
|
6048
|
+
* Escape takes back every open that is on its way, so nothing appears right
|
|
6049
|
+
* after it, and closes one open floating element, chosen by these rules in
|
|
6050
|
+
* turn:
|
|
6051
|
+
*
|
|
6052
|
+
* 1. One that focus is in, on its trigger or inside it, when there is one.
|
|
6053
|
+
* 2. The innermost: one whose floating element holds no other's trigger.
|
|
6054
|
+
* That follows nesting whether either one is portaled, since an inner
|
|
6055
|
+
* trigger sits in the outer floating element either way.
|
|
6056
|
+
* 3. The one that opened last.
|
|
6057
|
+
*
|
|
6058
|
+
* The keypress belongs to whatever has focus. When that is one of the open
|
|
6059
|
+
* floating elements or their triggers, or nothing, the listener stops it in
|
|
6060
|
+
* the capture phase, before anything around (a Popover, a Modal) sees it.
|
|
6061
|
+
* When focus is anywhere else, such as a combobox or a dialog of its own,
|
|
6062
|
+
* the floating element still closes, since hover put it there without
|
|
6063
|
+
* focus, and the key goes on to that owner.
|
|
6064
|
+
*
|
|
6065
|
+
* @returns What to call when the hook stops wanting Escape.
|
|
6066
|
+
*/
|
|
6067
|
+
function wantEscape(doc, entry) {
|
|
6068
|
+
let listener = escapeListeners.get(doc);
|
|
6069
|
+
if (!listener) {
|
|
6070
|
+
const entries = new Set();
|
|
6071
|
+
const handleKeyDown = (event) => {
|
|
6072
|
+
if (event.key !== 'Escape')
|
|
6073
|
+
return;
|
|
6074
|
+
const open = [];
|
|
6075
|
+
// A copy, since taking an open back can take its hook out of the set.
|
|
6076
|
+
for (const each of [...entries]) {
|
|
6077
|
+
if (each.openedAt)
|
|
6078
|
+
open.push(each);
|
|
6079
|
+
else
|
|
6080
|
+
each.cancel();
|
|
6081
|
+
}
|
|
6082
|
+
const focused = getDeepestActiveElement(doc);
|
|
6083
|
+
const holding = open.filter(each => within(each.trigger(), focused) || within(each.floating(), focused));
|
|
6084
|
+
const pool = holding.length > 0 ? holding : open;
|
|
6085
|
+
let top;
|
|
6086
|
+
for (const each of pool) {
|
|
6087
|
+
const holdsAnother = pool.some(other => other !== each && within(each.floating(), other.trigger()));
|
|
6088
|
+
if (!holdsAnother && (!top || each.openedAt > top.openedAt)) {
|
|
6089
|
+
top = each;
|
|
6090
|
+
}
|
|
6091
|
+
}
|
|
6092
|
+
if (!top)
|
|
6093
|
+
return;
|
|
6094
|
+
if (holding.length > 0 || !focused || focused === doc.body) {
|
|
6095
|
+
event.preventDefault();
|
|
6096
|
+
event.stopPropagation();
|
|
6097
|
+
}
|
|
6098
|
+
top.close();
|
|
6099
|
+
};
|
|
6100
|
+
doc.addEventListener('keydown', handleKeyDown, true);
|
|
6101
|
+
listener = {
|
|
6102
|
+
entries,
|
|
6103
|
+
stop: () => {
|
|
6104
|
+
doc.removeEventListener('keydown', handleKeyDown, true);
|
|
6105
|
+
escapeListeners.delete(doc);
|
|
6106
|
+
},
|
|
6107
|
+
};
|
|
6108
|
+
escapeListeners.set(doc, listener);
|
|
6109
|
+
}
|
|
6110
|
+
const shared = listener;
|
|
6111
|
+
shared.entries.add(entry);
|
|
6112
|
+
return () => {
|
|
6113
|
+
shared.entries.delete(entry);
|
|
6114
|
+
if (shared.entries.size === 0)
|
|
6115
|
+
shared.stop();
|
|
6116
|
+
};
|
|
6117
|
+
}
|
|
6118
|
+
/**
|
|
6119
|
+
* Decides when a floating element that opens on hover or keyboard focus asks
|
|
6120
|
+
* to open and to close. It renders nothing, and leaves roles, ARIA and
|
|
6121
|
+
* placement to the component that calls it.
|
|
6122
|
+
*
|
|
6123
|
+
* - The pointer entering, or keyboard focus arriving on the trigger, asks to
|
|
6124
|
+
* open after `openDelay`. A touch pointer doesn't count. Neither does focus
|
|
6125
|
+
* that a press brings to the trigger: it counts when the browser shows it
|
|
6126
|
+
* as `:focus-visible`.
|
|
6127
|
+
* - Focus in the floating element holds it open however it got there,
|
|
6128
|
+
* including focus in content it portals elsewhere, such as a nested card.
|
|
6129
|
+
* Whether a click focuses what it lands on is up to the browser: Safari
|
|
6130
|
+
* and Firefox on macOS don't focus a button on click, so there a clicked
|
|
6131
|
+
* button doesn't hold it once the pointer leaves.
|
|
6132
|
+
* - Once the pointer and focus have both left, it asks to close after
|
|
6133
|
+
* `closeDelay`, and either coming back before then keeps it open. Nothing
|
|
6134
|
+
* else closes it on a timer.
|
|
6135
|
+
* - Escape closes it at once, wherever focus is, and stops there when focus
|
|
6136
|
+
* is in it, on its trigger or nowhere, so a `Popover` or `Modal` around it
|
|
6137
|
+
* keeps its own. With focus somewhere else, the key goes on after closing
|
|
6138
|
+
* it. Inside another one, the inner one closes first. Escape before it
|
|
6139
|
+
* opens takes the opening back and goes on its way. One key listener per
|
|
6140
|
+
* document serves every hook on it, there only while one of them is open
|
|
6141
|
+
* or on its way to open.
|
|
6142
|
+
* - With `openOnTap`, a tap on the trigger opens it at once and the next tap
|
|
6143
|
+
* on the trigger closes it. While a tap holds it open, a press outside the
|
|
6144
|
+
* trigger and the floating element closes it, from a listener on the
|
|
6145
|
+
* document that is there only then. Taps never count as hover, and mouse
|
|
6146
|
+
* and pen work the same with it or without.
|
|
6147
|
+
* - Once it closes, for any reason, whatever was holding it open has to
|
|
6148
|
+
* leave and come back to open it again.
|
|
6149
|
+
* - When the floating element closes with focus inside it, focus goes back
|
|
6150
|
+
* to the trigger, unless the same update put it somewhere else. Handing it
|
|
6151
|
+
* back doesn't count as focus arriving.
|
|
6152
|
+
*
|
|
6153
|
+
* Nothing it scheduled runs after the component unmounts.
|
|
6154
|
+
*
|
|
6155
|
+
* @function useHoverOpen
|
|
6156
|
+
* @param options - The open state, how to ask for another, the delays, and the two elements.
|
|
6157
|
+
* @returns Handlers for the element that holds the trigger.
|
|
6158
|
+
*/
|
|
6159
|
+
function useHoverOpen(options) {
|
|
6160
|
+
const latestRef = React.useRef(options);
|
|
6161
|
+
const stateRef = React.useRef({
|
|
6162
|
+
pointerIn: false,
|
|
6163
|
+
focusIn: false,
|
|
6164
|
+
focusInFloating: false,
|
|
6165
|
+
held: false,
|
|
6166
|
+
opening: false,
|
|
6167
|
+
asked: false,
|
|
6168
|
+
restoring: false,
|
|
6169
|
+
});
|
|
6170
|
+
// In the hooks that want Escape from the moment an open is on its way until
|
|
6171
|
+
// the floating element closes, and out otherwise. Stamped with the order it
|
|
6172
|
+
// opened in.
|
|
6173
|
+
const listenForEscape = React.useCallback(function listen() {
|
|
6174
|
+
const s = stateRef.current;
|
|
6175
|
+
const entry = entryRef.current;
|
|
6176
|
+
const { open, triggerRef } = latestRef.current;
|
|
6177
|
+
if (!open)
|
|
6178
|
+
entry.openedAt = 0;
|
|
6179
|
+
else if (!entry.openedAt) {
|
|
6180
|
+
openings += 1;
|
|
6181
|
+
entry.openedAt = openings;
|
|
6182
|
+
}
|
|
6183
|
+
const wanted = open || s.opening || s.asked;
|
|
6184
|
+
if (wanted === Boolean(s.releaseEscape))
|
|
6185
|
+
return;
|
|
6186
|
+
if (s.releaseEscape) {
|
|
6187
|
+
s.releaseEscape();
|
|
6188
|
+
s.releaseEscape = undefined;
|
|
6189
|
+
return;
|
|
6190
|
+
}
|
|
6191
|
+
const doc = triggerRef.current?.ownerDocument ?? document;
|
|
6192
|
+
s.releaseEscape = wantEscape(doc, entry);
|
|
6193
|
+
}, []);
|
|
6194
|
+
const entryRef = React.useRef({
|
|
6195
|
+
openedAt: 0,
|
|
6196
|
+
trigger: () => latestRef.current.triggerRef.current,
|
|
6197
|
+
floating: () => latestRef.current.floatingRef.current,
|
|
6198
|
+
// Only ever called while an open is on its way, so it is one or the
|
|
6199
|
+
// other: still waiting on its delay, or asked for already.
|
|
6200
|
+
cancel: () => {
|
|
6201
|
+
const s = stateRef.current;
|
|
6202
|
+
clearTimeout(s.timer);
|
|
6203
|
+
// An ask already made is taken back by asking the other way.
|
|
6204
|
+
if (s.asked)
|
|
6205
|
+
latestRef.current.onOpenChange(false);
|
|
6206
|
+
s.opening = false;
|
|
6207
|
+
s.asked = false;
|
|
6208
|
+
listenForEscape();
|
|
6209
|
+
},
|
|
6210
|
+
close: () => {
|
|
6211
|
+
clearTimeout(stateRef.current.timer);
|
|
6212
|
+
latestRef.current.onOpenChange(false);
|
|
6213
|
+
},
|
|
6214
|
+
});
|
|
6215
|
+
// Asks to open or to close. Escape can take an ask to open back until it is
|
|
6216
|
+
// answered: by the commit that follows it, or, when the caller keeps it
|
|
6217
|
+
// closed without rendering at all, once the event that asked is over, by
|
|
6218
|
+
// which time React has rendered any answer the caller set.
|
|
6219
|
+
const ask = React.useCallback((next) => {
|
|
6220
|
+
const s = stateRef.current;
|
|
6221
|
+
s.asked = next;
|
|
6222
|
+
latestRef.current.onOpenChange(next);
|
|
6223
|
+
listenForEscape();
|
|
6224
|
+
if (!next)
|
|
6225
|
+
return;
|
|
6226
|
+
clearTimeout(s.askTimer);
|
|
6227
|
+
s.askTimer = setTimeout(() => {
|
|
6228
|
+
// Still closed: the caller turned it down.
|
|
6229
|
+
if (latestRef.current.open)
|
|
6230
|
+
return;
|
|
6231
|
+
s.asked = false;
|
|
6232
|
+
s.tappedIn = undefined;
|
|
6233
|
+
listenForEscape();
|
|
6234
|
+
});
|
|
6235
|
+
}, [listenForEscape]);
|
|
6236
|
+
// Asks for what the pointer and focus call for, after its delay, when that
|
|
6237
|
+
// changed since the last time and isn't the state already.
|
|
6238
|
+
const settle = React.useCallback(() => {
|
|
6239
|
+
const s = stateRef.current;
|
|
6240
|
+
const held = s.pointerIn || s.focusIn;
|
|
6241
|
+
if (held !== s.held) {
|
|
6242
|
+
s.held = held;
|
|
6243
|
+
clearTimeout(s.timer);
|
|
6244
|
+
const { open, openDelay, closeDelay } = latestRef.current;
|
|
6245
|
+
s.opening = held && !open;
|
|
6246
|
+
if (held !== open) {
|
|
6247
|
+
s.timer = setTimeout(() => {
|
|
6248
|
+
s.opening = false;
|
|
6249
|
+
if (latestRef.current.open !== held)
|
|
6250
|
+
ask(held);
|
|
6251
|
+
else
|
|
6252
|
+
listenForEscape();
|
|
6253
|
+
}, held ? openDelay : closeDelay);
|
|
6254
|
+
}
|
|
6255
|
+
}
|
|
6256
|
+
listenForEscape();
|
|
6257
|
+
}, [listenForEscape, ask]);
|
|
6258
|
+
// While a tap holds the floating element open, a press outside the trigger
|
|
6259
|
+
// and the floating element closes it. Set up again each time it opens or
|
|
6260
|
+
// closes.
|
|
6261
|
+
const listenForPress = React.useCallback(() => {
|
|
6262
|
+
const s = stateRef.current;
|
|
6263
|
+
s.stopPress?.();
|
|
6264
|
+
s.stopPress = undefined;
|
|
6265
|
+
const doc = latestRef.current.open ? s.tappedIn : undefined;
|
|
6266
|
+
if (!doc)
|
|
6267
|
+
return;
|
|
6268
|
+
const handlePointerDown = (event) => {
|
|
6269
|
+
// Inside is a press React brought to the holder, which covers content
|
|
6270
|
+
// the floating element portals elsewhere, or one whose path runs
|
|
6271
|
+
// through the trigger or the floating element, which covers a press
|
|
6272
|
+
// that something inside stopped before React got it to the holder.
|
|
6273
|
+
const { triggerRef, floatingRef } = latestRef.current;
|
|
6274
|
+
if (event === s.pressedInside ||
|
|
6275
|
+
isEventInside(event, triggerRef.current) ||
|
|
6276
|
+
isEventInside(event, floatingRef.current)) {
|
|
6277
|
+
return;
|
|
6278
|
+
}
|
|
6279
|
+
latestRef.current.onOpenChange(false);
|
|
6280
|
+
};
|
|
6281
|
+
doc.addEventListener('pointerdown', handlePointerDown);
|
|
6282
|
+
s.stopPress = () => doc.removeEventListener('pointerdown', handlePointerDown);
|
|
6283
|
+
}, []);
|
|
6284
|
+
useClientLayoutEffect(() => {
|
|
6285
|
+
latestRef.current = options;
|
|
6286
|
+
// The commit after an ask to open answers it, open or still closed. A
|
|
6287
|
+
// tap that asked and still finds it closed was turned down.
|
|
6288
|
+
const s = stateRef.current;
|
|
6289
|
+
if (s.asked) {
|
|
6290
|
+
s.asked = false;
|
|
6291
|
+
if (!options.open)
|
|
6292
|
+
s.tappedIn = undefined;
|
|
6293
|
+
listenForEscape();
|
|
6294
|
+
}
|
|
6295
|
+
});
|
|
6296
|
+
useClientLayoutEffect(() => {
|
|
6297
|
+
const s = stateRef.current;
|
|
6298
|
+
return () => {
|
|
6299
|
+
clearTimeout(s.timer);
|
|
6300
|
+
clearTimeout(s.askTimer);
|
|
6301
|
+
s.opening = false;
|
|
6302
|
+
s.asked = false;
|
|
6303
|
+
s.releaseEscape?.();
|
|
6304
|
+
s.releaseEscape = undefined;
|
|
6305
|
+
s.stopPress?.();
|
|
6306
|
+
s.stopPress = undefined;
|
|
6307
|
+
};
|
|
6308
|
+
}, []);
|
|
6309
|
+
const { open } = options;
|
|
6310
|
+
// Before paint, so a pointer or a key arriving next finds this settled.
|
|
6311
|
+
useClientLayoutEffect(() => {
|
|
6312
|
+
const s = stateRef.current;
|
|
6313
|
+
if (!open) {
|
|
6314
|
+
// The floating element left from under the pointer, if it was there,
|
|
6315
|
+
// and no leave event says so. Focus resting on the trigger has done
|
|
6316
|
+
// its job too, so neither opens it again until it comes back.
|
|
6317
|
+
s.pointerIn = false;
|
|
6318
|
+
s.focusIn = false;
|
|
6319
|
+
s.tappedIn = undefined;
|
|
6320
|
+
if (s.focusInFloating) {
|
|
6321
|
+
s.focusInFloating = false;
|
|
6322
|
+
const trigger = latestRef.current.triggerRef.current;
|
|
6323
|
+
// Focus that went down with the floating element sits on <body>.
|
|
6324
|
+
// Focus somewhere else was put there by the same update, so it stays.
|
|
6325
|
+
if (trigger &&
|
|
6326
|
+
trigger.ownerDocument.activeElement === trigger.ownerDocument.body) {
|
|
6327
|
+
s.restoring = true;
|
|
6328
|
+
trigger.focus();
|
|
6329
|
+
s.restoring = false;
|
|
6330
|
+
}
|
|
6331
|
+
}
|
|
6332
|
+
}
|
|
6333
|
+
settle();
|
|
6334
|
+
listenForPress();
|
|
6335
|
+
}, [open, settle, listenForPress]);
|
|
6336
|
+
return React.useMemo(() => {
|
|
6337
|
+
const inTrigger = (node) => within(latestRef.current.triggerRef.current, node);
|
|
6338
|
+
return {
|
|
6339
|
+
onPointerEnter: event => {
|
|
6340
|
+
if (event.pointerType === 'touch')
|
|
6341
|
+
return;
|
|
6342
|
+
stateRef.current.pointerIn = true;
|
|
6343
|
+
settle();
|
|
6344
|
+
},
|
|
6345
|
+
onPointerLeave: event => {
|
|
6346
|
+
// A touch pointer never set pointerIn, so its leave has nothing to
|
|
6347
|
+
// clear, and on a device with a mouse too it would close a card the
|
|
6348
|
+
// mouse is still holding open.
|
|
6349
|
+
if (event.pointerType === 'touch')
|
|
6350
|
+
return;
|
|
6351
|
+
stateRef.current.pointerIn = false;
|
|
6352
|
+
settle();
|
|
6353
|
+
},
|
|
6354
|
+
onFocus: event => {
|
|
6355
|
+
const s = stateRef.current;
|
|
6356
|
+
// React brings focus here from the trigger and from the floating
|
|
6357
|
+
// element's whole tree, portals included, so focus not in the
|
|
6358
|
+
// trigger is in the floating element, wherever its DOM is.
|
|
6359
|
+
s.focusInFloating = !inTrigger(event.target);
|
|
6360
|
+
if (s.restoring)
|
|
6361
|
+
return;
|
|
6362
|
+
// Any focus in the floating element holds it. On the trigger, only
|
|
6363
|
+
// keyboard focus does, so a click there opens nothing.
|
|
6364
|
+
s.focusIn = s.focusInFloating || isKeyboardFocus(event.target);
|
|
6365
|
+
settle();
|
|
6366
|
+
},
|
|
6367
|
+
onBlur: event => {
|
|
6368
|
+
const s = stateRef.current;
|
|
6369
|
+
const next = event.relatedTarget;
|
|
6370
|
+
// Where focus goes next, as far as the DOM can say. Focus going into
|
|
6371
|
+
// content the floating element portals elsewhere reads as leaving,
|
|
6372
|
+
// and the focus event that follows straight after brings it back.
|
|
6373
|
+
s.focusInFloating = within(latestRef.current.floatingRef.current, next);
|
|
6374
|
+
// Moving between the trigger and the floating element isn't leaving.
|
|
6375
|
+
if (s.focusInFloating || within(event.currentTarget, next))
|
|
6376
|
+
return;
|
|
6377
|
+
s.focusIn = false;
|
|
6378
|
+
settle();
|
|
6379
|
+
},
|
|
6380
|
+
onPointerDown: event => {
|
|
6381
|
+
stateRef.current.pressedInside = event.nativeEvent;
|
|
6382
|
+
},
|
|
6383
|
+
onPointerUp: event => {
|
|
6384
|
+
const now = latestRef.current;
|
|
6385
|
+
// With openOnTap, a tap on the trigger opens it at once, and the
|
|
6386
|
+
// next one closes it. A tap anywhere else does nothing here.
|
|
6387
|
+
if (!now.openOnTap ||
|
|
6388
|
+
event.pointerType !== 'touch' ||
|
|
6389
|
+
!inTrigger(event.target)) {
|
|
6390
|
+
return;
|
|
6391
|
+
}
|
|
6392
|
+
stateRef.current.tappedIn = now.open
|
|
6393
|
+
? undefined
|
|
6394
|
+
: event.target.ownerDocument;
|
|
6395
|
+
ask(!now.open);
|
|
6396
|
+
},
|
|
6397
|
+
};
|
|
6398
|
+
}, [settle, ask]);
|
|
6399
|
+
}
|
|
6400
|
+
|
|
6401
|
+
/** Runs the caller's handler on the wrapper, then the hover card's. */
|
|
6402
|
+
function chain(own, ours) {
|
|
6403
|
+
return event => {
|
|
6404
|
+
own?.(event);
|
|
6405
|
+
ours(event);
|
|
6406
|
+
};
|
|
6407
|
+
}
|
|
6408
|
+
/**
|
|
6409
|
+
* The `HoverCard` component shows a card previewing what's behind a link or a button while the pointer rests on it or keyboard focus is on it.
|
|
6410
|
+
*
|
|
6411
|
+
* Nothing moves focus when the card opens, and nothing traps it. In place,
|
|
6412
|
+
* the card comes right after the trigger, so Tab goes from the trigger into
|
|
6413
|
+
* its links and tabbing past them closes it. The pointer can move onto the
|
|
6414
|
+
* card, Escape closes it from anywhere, and by default touch doesn't open it,
|
|
6415
|
+
* so tapping the trigger just does what the trigger does. `openOnTap` opens
|
|
6416
|
+
* it with a tap, for a trigger with no tap action of its own. For hints with nothing to
|
|
6417
|
+
* click use `Tooltip`, and for content opened on purpose, such as a form,
|
|
6418
|
+
* use `Popover`.
|
|
6419
|
+
*
|
|
6420
|
+
* @function
|
|
6421
|
+
* @param {HoverCardProps} props - Props for the HoverCard component.
|
|
6422
|
+
* @returns {JSX.Element} The trigger in its wrapper, and the card while open.
|
|
6423
|
+
*
|
|
6424
|
+
* @example
|
|
6425
|
+
* <HoverCard trigger={<a href="/people/ada">Ada Lovelace</a>}>
|
|
6426
|
+
* <Title as="p" size="6">Ada Lovelace</Title>
|
|
6427
|
+
* <SubTitle as="p" size="6">Analytical Engine team</SubTitle>
|
|
6428
|
+
* </HoverCard>
|
|
6429
|
+
*/
|
|
6430
|
+
const HoverCard = ({ trigger, children, open: controlledOpen, defaultOpen = false, onOpenChange, openDelay = 600, closeDelay = 300, position = 'bottom-left', appendToBody = false, openOnTap = false, className, contentClassName, ...props }) => {
|
|
6431
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses(props);
|
|
6432
|
+
const { onPointerEnter, onPointerLeave, onPointerDown, onPointerUp, onFocus, onBlur, ...attrs } = rest;
|
|
6433
|
+
const rootRef = React.useRef(null);
|
|
6434
|
+
const cardRef = React.useRef(null);
|
|
6435
|
+
const cardId = `${React.useId()}-card`;
|
|
6436
|
+
const isControlled = controlledOpen !== undefined;
|
|
6437
|
+
const [internalOpen, setInternalOpen] = React.useState(defaultOpen);
|
|
6438
|
+
const isOpen = controlledOpen ?? internalOpen;
|
|
6439
|
+
const requestOpen = React.useCallback((next) => {
|
|
6440
|
+
onOpenChange?.(next);
|
|
6441
|
+
if (!isControlled)
|
|
6442
|
+
setInternalOpen(next);
|
|
6443
|
+
}, [isControlled, onOpenChange]);
|
|
6444
|
+
// A portaled card has no server markup, so it only exists from the commit
|
|
6445
|
+
// after hydration, and nothing measures it before then.
|
|
6446
|
+
const hydrated = useIsHydrated();
|
|
6447
|
+
const cardMounted = isOpen && (hydrated || !appendToBody);
|
|
6448
|
+
const resolved = useAnchoredPosition(rootRef, cardRef, {
|
|
6449
|
+
active: cardMounted,
|
|
6450
|
+
position,
|
|
6451
|
+
fixed: appendToBody,
|
|
6452
|
+
// The gap comes from `--bulma-hover-card-offset` in the stylesheet, so
|
|
6453
|
+
// the coordinates leave it out and `auto` reads it off the card.
|
|
6454
|
+
offset: 0,
|
|
6455
|
+
styledGap: marginGap,
|
|
6456
|
+
});
|
|
6457
|
+
// The trigger renders first inside the wrapper. Read when needed rather
|
|
6458
|
+
// than held, so a trigger that remounted is still the one found.
|
|
6459
|
+
const triggerRef = React.useMemo(() => ({
|
|
6460
|
+
get current() {
|
|
6461
|
+
const first = rootRef.current.firstElementChild;
|
|
6462
|
+
return first && first !== cardRef.current
|
|
6463
|
+
? first
|
|
6464
|
+
: null;
|
|
6465
|
+
},
|
|
6466
|
+
}), []);
|
|
6467
|
+
const hover = useHoverOpen({
|
|
6468
|
+
open: isOpen,
|
|
6469
|
+
onOpenChange: requestOpen,
|
|
6470
|
+
openDelay,
|
|
6471
|
+
closeDelay,
|
|
6472
|
+
triggerRef,
|
|
6473
|
+
floatingRef: cardRef,
|
|
6474
|
+
openOnTap,
|
|
6475
|
+
});
|
|
6476
|
+
// A trigger that can't take focus leaves keyboard users no way to open the
|
|
6477
|
+
// card. It has to be the trigger element itself, not something inside it,
|
|
6478
|
+
// since focus comes back to it and its ARIA is on it. Judged by what the
|
|
6479
|
+
// element is rather than by its styles, which can hide it for now. After
|
|
6480
|
+
// every commit, since the trigger can change, and only in development.
|
|
6481
|
+
React.useEffect(() => {
|
|
6482
|
+
if (!isDev())
|
|
6483
|
+
return;
|
|
6484
|
+
const triggerNode = triggerRef.current;
|
|
6485
|
+
if (!triggerNode)
|
|
6486
|
+
return;
|
|
6487
|
+
if (!triggerNode.matches(':disabled') && isTabbableKind(triggerNode)) {
|
|
6488
|
+
return;
|
|
6489
|
+
}
|
|
6490
|
+
warnOnce('HoverCard:unfocusable-trigger', "[bestax-bulma] <HoverCard> has a trigger that can't take focus, so " +
|
|
6491
|
+
"keyboard users can't open the card. Use a link or a button as the " +
|
|
6492
|
+
'trigger, or give it tabIndex={0}.');
|
|
6493
|
+
});
|
|
6494
|
+
// Expanded only while the card is on the page, so a portaled card that
|
|
6495
|
+
// waits for hydration isn't announced before it exists.
|
|
6496
|
+
const clonedTrigger = React.cloneElement(trigger, {
|
|
6497
|
+
'aria-expanded': cardMounted,
|
|
6498
|
+
'aria-controls': cardMounted ? cardId : undefined,
|
|
6499
|
+
});
|
|
6500
|
+
const rootClasses = classNames(usePrefixedClassNames('hover-card', { 'is-active': cardMounted }), bulmaHelperClasses, className);
|
|
6501
|
+
const cardClasses = classNames(usePrefixedClassNames('hover-card-content', {
|
|
6502
|
+
[`is-${resolved.position}`]: true,
|
|
6503
|
+
'is-portal': appendToBody,
|
|
6504
|
+
}), contentClassName);
|
|
6505
|
+
return (jsxRuntime.jsxs("span", { className: rootClasses, ...attrs, onPointerEnter: chain(onPointerEnter, hover.onPointerEnter), onPointerLeave: chain(onPointerLeave, hover.onPointerLeave), onFocus: chain(onFocus, hover.onFocus), onBlur: chain(onBlur, hover.onBlur), onPointerDown: chain(onPointerDown, hover.onPointerDown), onPointerUp: chain(onPointerUp, hover.onPointerUp), ref: rootRef, children: [clonedTrigger, isOpen && (jsxRuntime.jsx(Portal, { disabled: !appendToBody, children: jsxRuntime.jsx("div", { ref: cardRef, id: cardId, className: cardClasses, style: appendToBody
|
|
6506
|
+
? {
|
|
6507
|
+
position: 'fixed',
|
|
6508
|
+
top: resolved.top,
|
|
6509
|
+
left: resolved.left,
|
|
6510
|
+
}
|
|
6511
|
+
: undefined, children: children }) }))] }));
|
|
6512
|
+
};
|
|
6513
|
+
|
|
6012
6514
|
/**
|
|
6013
6515
|
* Individual Step component for use inside Steps.
|
|
6014
6516
|
*
|
|
@@ -9069,10 +9571,14 @@ const FieldLabelIdContext = React.createContext(undefined);
|
|
|
9069
9571
|
* The id a labeled Field wants its single composed control to adopt (#495).
|
|
9070
9572
|
* `undefined` outside a Field, in unlabeled/grouped/addons Fields, or when the
|
|
9071
9573
|
* user took over the association with an explicit `labelProps.htmlFor`.
|
|
9072
|
-
* Consumed by the single-control bases
|
|
9073
|
-
* and the date and time picker bases
|
|
9074
|
-
* `useAutoLabelId`, by the convenience
|
|
9075
|
-
* own (#939).
|
|
9574
|
+
* Consumed through `useFieldLabelTarget` by the single-control bases
|
|
9575
|
+
* (InputBase, SelectBase, TextAreaBase, and the date and time picker bases
|
|
9576
|
+
* unless inline, #968) and, through `useAutoLabelId`, by the convenience
|
|
9577
|
+
* inputs that render an input of their own (#939). Read it only through
|
|
9578
|
+
* those, which report what they take: a control that read it directly would
|
|
9579
|
+
* report nothing and lose its label (#1004), and
|
|
9580
|
+
* `__tests__/field-label-readers.test.ts` fails on one. Internal; not part of
|
|
9581
|
+
* the public API.
|
|
9076
9582
|
*/
|
|
9077
9583
|
const useFieldLabelId = () => React.useContext(FieldLabelIdContext);
|
|
9078
9584
|
/** Provider for the Field label-target id — used internally by Field. */
|
|
@@ -9098,15 +9604,54 @@ const FieldLabelForContext = React.createContext({});
|
|
|
9098
9604
|
* the outer label names it, and point at that label by its id, as a range
|
|
9099
9605
|
* Slider's thumbs (#981) and Autocomplete's suggestion list (#998) do. The
|
|
9100
9606
|
* `htmlFor` is only ever compared with a control's own id, never adopted, so
|
|
9101
|
-
* it hands out no id for a control.
|
|
9102
|
-
*
|
|
9103
|
-
*
|
|
9104
|
-
* `
|
|
9607
|
+
* it hands out no id for a control. It keeps the generated id after the
|
|
9608
|
+
* label drops a `for` nothing took (#1004), which no control's id matches
|
|
9609
|
+
* then, so the value does not change under the content that reported it.
|
|
9610
|
+
* The `id` is set only while that label renders with it. Unlike
|
|
9611
|
+
* {@link useFieldLabelId}, it is set in a `grouped` or `hasAddons` Field
|
|
9612
|
+
* too, so a label wired by hand names the control its `for` picks in those
|
|
9613
|
+
* rows as well. Consumed through `useAutoLabelId`.
|
|
9105
9614
|
* Internal; not part of the public API.
|
|
9106
9615
|
*/
|
|
9107
9616
|
const useFieldLabelFor = () => React.useContext(FieldLabelForContext);
|
|
9108
9617
|
/** Provider for the Field label's `htmlFor` target and id, used internally by Field. */
|
|
9109
9618
|
const FieldLabelForProvider = FieldLabelForContext.Provider;
|
|
9619
|
+
const FieldLabelForReportContext = React.createContext(undefined);
|
|
9620
|
+
/**
|
|
9621
|
+
* Tells the nearest labeled Field that this content took its generated id
|
|
9622
|
+
* while `takesFor` is true (#1004). Once the Field's content has mounted, its
|
|
9623
|
+
* label keeps the generated `for` only while something has said so, so the
|
|
9624
|
+
* label renders none over a group, a `Checkbox`, `Radio` or `Switch`, an
|
|
9625
|
+
* inner `Field`, an input with an `id` of its own, markup of your own, or
|
|
9626
|
+
* nothing at all. Anything that puts the id from {@link useFieldLabelId} on
|
|
9627
|
+
* an element has to say so, or the label drops the `for` that names it, and
|
|
9628
|
+
* content that takes no id has nothing to do. Set under the same conditions
|
|
9629
|
+
* as {@link useFieldLabelId}, so a label wired by hand and a `grouped` or
|
|
9630
|
+
* `hasAddons` Field hear nothing. It reports from a layout effect, which a
|
|
9631
|
+
* server never runs and a hydrating client runs only after its first render,
|
|
9632
|
+
* so that render writes the `for` the way the server did.
|
|
9633
|
+
* Internal; not part of the public API.
|
|
9634
|
+
*/
|
|
9635
|
+
const useReportFieldLabelFor = (takesFor) => {
|
|
9636
|
+
const report = React.useContext(FieldLabelForReportContext);
|
|
9637
|
+
useClientLayoutEffect(() => (takesFor ? report?.() : undefined), [report, takesFor]);
|
|
9638
|
+
};
|
|
9639
|
+
/** Provider for the labeled Field's report function, used internally by Field. */
|
|
9640
|
+
const FieldLabelForReportProvider = FieldLabelForReportContext.Provider;
|
|
9641
|
+
/**
|
|
9642
|
+
* The id a control with one input of its own renders with: the caller's
|
|
9643
|
+
* `id` when one arrived, otherwise the one a labeled Field offers (#495),
|
|
9644
|
+
* unless it has no input to put it on (`hasInput: false`, as in an `inline`
|
|
9645
|
+
* picker). It also tells that Field when it took the Field's id, through
|
|
9646
|
+
* {@link useReportFieldLabelFor}, so the label keeps its `for` (#1004). Used
|
|
9647
|
+
* by the single-control bases. Internal; not part of the public API.
|
|
9648
|
+
*/
|
|
9649
|
+
const useFieldLabelTarget = (id, hasInput = true) => {
|
|
9650
|
+
const fieldLabelId = useFieldLabelId();
|
|
9651
|
+
const target = id ?? (hasInput ? fieldLabelId : undefined);
|
|
9652
|
+
useReportFieldLabelFor(fieldLabelId !== undefined && target === fieldLabelId);
|
|
9653
|
+
return target;
|
|
9654
|
+
};
|
|
9110
9655
|
const RadiosContext = React.createContext(undefined);
|
|
9111
9656
|
const CheckboxesContext = React.createContext(undefined);
|
|
9112
9657
|
/**
|
|
@@ -9438,13 +9983,43 @@ const FieldComponent = ({ horizontal, grouped, hasAddons, narrow, label, labelSi
|
|
|
9438
9983
|
const labelForTarget = userWiredLabel ? labelProps.htmlFor : targetId;
|
|
9439
9984
|
const ownLabelFor = React.useMemo(() => ({ htmlFor: labelForTarget, id: labelId }), [labelForTarget, labelId]);
|
|
9440
9985
|
const labelFor = label ? ownLabelFor : inheritedLabelFor;
|
|
9986
|
+
// Whether the label keeps its generated `for` (#1004). Content that takes
|
|
9987
|
+
// the Field's id says so from a layout effect, and this Field's own layout
|
|
9988
|
+
// effect runs after its content's in the same commit, so by then every
|
|
9989
|
+
// report has landed. Until it runs, on the server and in the first render
|
|
9990
|
+
// while hydrating, the label writes the `for` as it always has. From then
|
|
9991
|
+
// on it keeps the `for` only while some content holds the id, and content
|
|
9992
|
+
// that mounts or changes later reports when it does. Counted in a ref, and
|
|
9993
|
+
// state is set only once settled, so content that takes the id renders
|
|
9994
|
+
// the Field just once.
|
|
9995
|
+
const takersRef = React.useRef(0);
|
|
9996
|
+
const settledRef = React.useRef(false);
|
|
9997
|
+
const [forTaken, setForTaken] = React.useState(true);
|
|
9998
|
+
const settle = React.useCallback(() => {
|
|
9999
|
+
if (settledRef.current)
|
|
10000
|
+
setForTaken(takersRef.current > 0);
|
|
10001
|
+
}, []);
|
|
10002
|
+
const reportLabelFor = React.useCallback(() => {
|
|
10003
|
+
takersRef.current += 1;
|
|
10004
|
+
settle();
|
|
10005
|
+
return () => {
|
|
10006
|
+
takersRef.current -= 1;
|
|
10007
|
+
settle();
|
|
10008
|
+
};
|
|
10009
|
+
}, [settle]);
|
|
10010
|
+
useClientLayoutEffect(() => {
|
|
10011
|
+
settledRef.current = true;
|
|
10012
|
+
if (targetId)
|
|
10013
|
+
settle();
|
|
10014
|
+
}, [targetId, settle]);
|
|
10015
|
+
const renderedTargetId = forTaken ? targetId : undefined;
|
|
9441
10016
|
let renderedLabel = null;
|
|
9442
10017
|
if (label) {
|
|
9443
10018
|
if (horizontal) {
|
|
9444
|
-
renderedLabel = (jsxRuntime.jsx(FieldLabel, { size: effectiveLabelSize, children: jsxRuntime.jsx("label", { htmlFor:
|
|
10019
|
+
renderedLabel = (jsxRuntime.jsx(FieldLabel, { size: effectiveLabelSize, children: jsxRuntime.jsx("label", { htmlFor: renderedTargetId, ...labelProps, id: labelId, className: classNames(labelClass, labelProps?.className), style: labelProps?.style, children: label }) }));
|
|
9445
10020
|
}
|
|
9446
10021
|
else {
|
|
9447
|
-
renderedLabel = (jsxRuntime.jsx("label", { htmlFor:
|
|
10022
|
+
renderedLabel = (jsxRuntime.jsx("label", { htmlFor: renderedTargetId, ...labelProps, id: labelId, className: classNames(labelClass, labelProps?.className), style: { display: 'block', ...(labelProps?.style || {}) }, children: label }));
|
|
9448
10023
|
}
|
|
9449
10024
|
}
|
|
9450
10025
|
// If horizontal, wrap children in FieldBody (unless the user already provided
|
|
@@ -9467,7 +10042,7 @@ const FieldComponent = ({ horizontal, grouped, hasAddons, narrow, label, labelSi
|
|
|
9467
10042
|
content = jsxRuntime.jsx(FieldBody, { children: children });
|
|
9468
10043
|
}
|
|
9469
10044
|
}
|
|
9470
|
-
return (jsxRuntime.jsx(FieldProvider, { value: true, children: jsxRuntime.jsx(FieldLabelIdProvider, { value: targetId, children: jsxRuntime.jsx(FieldLabelElementIdProvider, { value: targetId ? labelId : undefined, children: jsxRuntime.jsx(FieldLabelForProvider, { value: labelFor, children: jsxRuntime.jsxs("div", { className: fieldClass, ...rest, children: [renderedLabel, content] }) }) }) }) }));
|
|
10045
|
+
return (jsxRuntime.jsx(FieldProvider, { value: true, children: jsxRuntime.jsx(FieldLabelIdProvider, { value: targetId, children: jsxRuntime.jsx(FieldLabelElementIdProvider, { value: targetId ? labelId : undefined, children: jsxRuntime.jsx(FieldLabelForProvider, { value: labelFor, children: jsxRuntime.jsx(FieldLabelForReportProvider, { value: targetId ? reportLabelFor : undefined, children: jsxRuntime.jsxs("div", { className: fieldClass, ...rest, children: [renderedLabel, content] }) }) }) }) }) }));
|
|
9471
10046
|
};
|
|
9472
10047
|
FieldLabel.displayName = 'FieldLabel';
|
|
9473
10048
|
FieldBody.displayName = 'FieldBody';
|
|
@@ -9485,8 +10060,10 @@ const Field = withSubComponents(FieldComponent, {
|
|
|
9485
10060
|
* generation entirely: the user has taken over the association (#495 presence
|
|
9486
10061
|
* semantics). When the control renders no label of its own, it adopts the id
|
|
9487
10062
|
* a labeled Field around it offers, the way the bases do, so that Field's
|
|
9488
|
-
* label names it (#939); a user `id` still wins there.
|
|
9489
|
-
*
|
|
10063
|
+
* label names it (#939); a user `id` still wins there. It tells that Field
|
|
10064
|
+
* when the control took its id, since the Field's label drops a `for`
|
|
10065
|
+
* nothing took (#1004). With `needsLabelId` it also returns the id of
|
|
10066
|
+
* whichever label names the control (#981).
|
|
9490
10067
|
* Internal; not part of the public API.
|
|
9491
10068
|
*/
|
|
9492
10069
|
function useAutoLabelId({ label, id, labelProps, rendersLabel, hasInput = true, needsLabelId = false, }) {
|
|
@@ -9507,6 +10084,9 @@ function useAutoLabelId({ label, id, labelProps, rendersLabel, hasInput = true,
|
|
|
9507
10084
|
const userWired = !!labelProps && 'htmlFor' in labelProps;
|
|
9508
10085
|
const adopted = !active && hasInput ? fieldLabelId : undefined;
|
|
9509
10086
|
const controlId = id ?? (active && !userWired ? generatedId : adopted);
|
|
10087
|
+
// Tells a labeled Field around the control when its label's `for` names
|
|
10088
|
+
// the control, since it drops a `for` nothing took (#1004).
|
|
10089
|
+
useReportFieldLabelFor(fieldLabelId !== undefined && controlId === fieldLabelId);
|
|
9510
10090
|
const ownLabelProps = { htmlFor: controlId, ...labelProps };
|
|
9511
10091
|
const labelTarget = active ? ownLabelProps.htmlFor : fieldLabelFor.htmlFor;
|
|
9512
10092
|
const labelled = !!controlId && labelTarget === controlId;
|
|
@@ -11569,7 +12149,9 @@ const InputBase = React.forwardRef(({ color, size, isRounded, isStatic, isHovere
|
|
|
11569
12149
|
color,
|
|
11570
12150
|
...props,
|
|
11571
12151
|
});
|
|
11572
|
-
|
|
12152
|
+
// A labeled Field's generated id when no user id arrived (rest.id may be
|
|
12153
|
+
// an undefined own key), reported to that Field (#1004).
|
|
12154
|
+
const id = useFieldLabelTarget(rest.id);
|
|
11573
12155
|
const mainClass = usePrefixedClassNames('input', {
|
|
11574
12156
|
[`is-${color}`]: !!color,
|
|
11575
12157
|
[`is-${size}`]: !!size,
|
|
@@ -11581,9 +12163,8 @@ const InputBase = React.forwardRef(({ color, size, isRounded, isStatic, isHovere
|
|
|
11581
12163
|
});
|
|
11582
12164
|
const inputClass = classNames(mainClass, bulmaHelperClasses, className);
|
|
11583
12165
|
return (jsxRuntime.jsx("input", { ref: ref, className: inputClass, disabled: disabled, readOnly: readOnly, ...rest,
|
|
11584
|
-
// After the spread
|
|
11585
|
-
|
|
11586
|
-
id: rest.id ?? fieldLabelId }));
|
|
12166
|
+
// After the spread, which may carry an undefined id key.
|
|
12167
|
+
id: id }));
|
|
11587
12168
|
});
|
|
11588
12169
|
InputBase.displayName = 'InputBase';
|
|
11589
12170
|
|
|
@@ -11600,7 +12181,9 @@ const SelectBase = React.forwardRef(({ color, size, isRounded, isLoading, isActi
|
|
|
11600
12181
|
color,
|
|
11601
12182
|
...props,
|
|
11602
12183
|
});
|
|
11603
|
-
|
|
12184
|
+
// A labeled Field's generated id when no user id arrived (rest.id may be
|
|
12185
|
+
// an undefined own key), reported to that Field (#1004).
|
|
12186
|
+
const id = useFieldLabelTarget(rest.id);
|
|
11604
12187
|
const mainClass = usePrefixedClassNames('select', {
|
|
11605
12188
|
[`is-${color}`]: !!color,
|
|
11606
12189
|
[`is-${size}`]: !!size,
|
|
@@ -11626,9 +12209,9 @@ const SelectBase = React.forwardRef(({ color, size, isRounded, isLoading, isActi
|
|
|
11626
12209
|
selectProps.size = multipleSize;
|
|
11627
12210
|
}
|
|
11628
12211
|
return (jsxRuntime.jsx("div", { className: selectClass, children: jsxRuntime.jsx("select", { ref: ref, className: innerSelectClass || undefined, ...selectProps,
|
|
11629
|
-
// After the spread
|
|
11630
|
-
//
|
|
11631
|
-
id:
|
|
12212
|
+
// After the spread, which may carry an undefined id key; the id
|
|
12213
|
+
// belongs on the inner <select>.
|
|
12214
|
+
id: id, children: children }) }));
|
|
11632
12215
|
});
|
|
11633
12216
|
SelectBase.displayName = 'SelectBase';
|
|
11634
12217
|
|
|
@@ -11645,7 +12228,9 @@ const TextAreaBase = React.forwardRef(({ color, size, isRounded, isStatic, isHov
|
|
|
11645
12228
|
color,
|
|
11646
12229
|
...props,
|
|
11647
12230
|
});
|
|
11648
|
-
|
|
12231
|
+
// A labeled Field's generated id when no user id arrived (rest.id may be
|
|
12232
|
+
// an undefined own key), reported to that Field (#1004).
|
|
12233
|
+
const id = useFieldLabelTarget(rest.id);
|
|
11649
12234
|
// Note: `is-loading` is intentionally NOT applied to the <textarea> itself —
|
|
11650
12235
|
// Bulma documents `<div class="control is-loading">` as the loading pattern
|
|
11651
12236
|
// for textareas, not `<textarea class="textarea is-loading">`. The convenience
|
|
@@ -11662,9 +12247,8 @@ const TextAreaBase = React.forwardRef(({ color, size, isRounded, isStatic, isHov
|
|
|
11662
12247
|
});
|
|
11663
12248
|
const textareaClass = classNames(mainClass, bulmaHelperClasses, className);
|
|
11664
12249
|
return (jsxRuntime.jsx("textarea", { ref: ref, className: textareaClass, disabled: disabled, readOnly: readOnly, rows: rows, ...rest,
|
|
11665
|
-
// After the spread
|
|
11666
|
-
|
|
11667
|
-
id: rest.id ?? fieldLabelId }));
|
|
12250
|
+
// After the spread, which may carry an undefined id key.
|
|
12251
|
+
id: id }));
|
|
11668
12252
|
});
|
|
11669
12253
|
TextAreaBase.displayName = 'TextAreaBase';
|
|
11670
12254
|
|
|
@@ -14159,8 +14743,7 @@ const DateInputBase = React.forwardRef((props, ref) => {
|
|
|
14159
14743
|
// when the caller set none, as InputBase does, and the popover's ids
|
|
14160
14744
|
// follow it. Inline there is no input to name, so nothing takes the
|
|
14161
14745
|
// Field's id or builds on it.
|
|
14162
|
-
const
|
|
14163
|
-
const id = idProp ?? (inline ? undefined : fieldLabelId);
|
|
14746
|
+
const id = useFieldLabelTarget(idProp, !inline);
|
|
14164
14747
|
// The launcher gives way to the loading spinner of a Control this sits in.
|
|
14165
14748
|
const controlLoading = useControlLoading();
|
|
14166
14749
|
const triggerIcon = triggerIconProp ?? !controlLoading;
|
|
@@ -15341,8 +15924,7 @@ const TimeInputBase = React.forwardRef((props, ref) => {
|
|
|
15341
15924
|
// when the caller set none, as InputBase does, and the popover's ids
|
|
15342
15925
|
// follow it. Inline there is no input to name, so nothing takes the
|
|
15343
15926
|
// Field's id or builds on it.
|
|
15344
|
-
const
|
|
15345
|
-
const id = idProp ?? (inline ? undefined : fieldLabelId);
|
|
15927
|
+
const id = useFieldLabelTarget(idProp, !inline);
|
|
15346
15928
|
// The launcher gives way to the loading spinner of a Control this sits in.
|
|
15347
15929
|
const controlLoading = useControlLoading();
|
|
15348
15930
|
const triggerIcon = triggerIconProp ?? !controlLoading;
|
|
@@ -15659,8 +16241,7 @@ const DateTimeInputBase = React.forwardRef((props, ref) => {
|
|
|
15659
16241
|
// when the caller set none, as InputBase does, and the popover's ids
|
|
15660
16242
|
// follow it. Inline there is no input to name, so nothing takes the
|
|
15661
16243
|
// Field's id or builds on it.
|
|
15662
|
-
const
|
|
15663
|
-
const id = idProp ?? (inline ? undefined : fieldLabelId);
|
|
16244
|
+
const id = useFieldLabelTarget(idProp, !inline);
|
|
15664
16245
|
// The launcher gives way to the loading spinner of a Control this sits in.
|
|
15665
16246
|
const controlLoading = useControlLoading();
|
|
15666
16247
|
const triggerIcon = triggerIconProp ?? !controlLoading;
|
|
@@ -16444,8 +17025,15 @@ const GridComponent = ({ isFixed = false, gap, columnGap, rowGap, minCol, fixedC
|
|
|
16444
17025
|
};
|
|
16445
17026
|
const Grid = withSubComponents(GridComponent, { Cell }, 'Grid');
|
|
16446
17027
|
|
|
16447
|
-
// ---
|
|
16448
|
-
|
|
17028
|
+
// --- Bulma v1 CSS variables Theme sets (originally generated from
|
|
17029
|
+
// CSSVAR_KEYS), in two lists by where Bulma declares them ---
|
|
17030
|
+
/**
|
|
17031
|
+
* Variables a value set on an ancestor reaches. Bulma declares them on
|
|
17032
|
+
* `:root` (or a theme scope), or on no component in its plain state, so the
|
|
17033
|
+
* value Theme writes on its wrapper or at `:root` is the one the components
|
|
17034
|
+
* under it use.
|
|
17035
|
+
*/
|
|
17036
|
+
const bulmaGlobalVars = [
|
|
16449
17037
|
// scheme
|
|
16450
17038
|
'--bulma-scheme-h',
|
|
16451
17039
|
'--bulma-scheme-s',
|
|
@@ -16562,6 +17150,36 @@ const bulmaCssVars = [
|
|
|
16562
17150
|
'--bulma-skeleton-block-min-height',
|
|
16563
17151
|
'--bulma-skeleton-lines-gap',
|
|
16564
17152
|
'--bulma-skeleton-line-height',
|
|
17153
|
+
// control
|
|
17154
|
+
'--bulma-control-radius',
|
|
17155
|
+
'--bulma-control-radius-small',
|
|
17156
|
+
'--bulma-control-border-width',
|
|
17157
|
+
'--bulma-control-height',
|
|
17158
|
+
'--bulma-control-line-height',
|
|
17159
|
+
'--bulma-control-padding-vertical',
|
|
17160
|
+
'--bulma-control-padding-horizontal',
|
|
17161
|
+
'--bulma-control-size',
|
|
17162
|
+
'--bulma-control-focus-shadow-l',
|
|
17163
|
+
// columns
|
|
17164
|
+
'--bulma-column-gap',
|
|
17165
|
+
// grid
|
|
17166
|
+
'--bulma-grid-cell-column-start',
|
|
17167
|
+
];
|
|
17168
|
+
/**
|
|
17169
|
+
* Variables Bulma declares on the component's own element, such as
|
|
17170
|
+
* `--bulma-card-radius` on `.card`. An element that declares a variable uses
|
|
17171
|
+
* its own value whatever its ancestors set, so a value Theme writes for one
|
|
17172
|
+
* of these, on its wrapper or at `:root`, never reaches the component.
|
|
17173
|
+
*
|
|
17174
|
+
* They stay in `BulmaVars`, and so stay valid `bulmaVars` keys and variable
|
|
17175
|
+
* props, because taking a key out would be a breaking change. Setting one
|
|
17176
|
+
* warns in development instead; see `warnComponentVars`.
|
|
17177
|
+
*
|
|
17178
|
+
* `scripts/css-var-scopes.test.mjs` holds this split to Bulma's compiled
|
|
17179
|
+
* stylesheet in both directions, so a Bulma upgrade that moves a variable
|
|
17180
|
+
* fails there.
|
|
17181
|
+
*/
|
|
17182
|
+
const bulmaComponentVars = [
|
|
16565
17183
|
// breadcrumb
|
|
16566
17184
|
'--bulma-breadcrumb-item-color',
|
|
16567
17185
|
'--bulma-breadcrumb-item-hover-color',
|
|
@@ -16911,16 +17529,6 @@ const bulmaCssVars = [
|
|
|
16911
17529
|
'--bulma-subtitle-line-height',
|
|
16912
17530
|
'--bulma-subtitle-strong-color',
|
|
16913
17531
|
'--bulma-subtitle-strong-weight',
|
|
16914
|
-
// control
|
|
16915
|
-
'--bulma-control-radius',
|
|
16916
|
-
'--bulma-control-radius-small',
|
|
16917
|
-
'--bulma-control-border-width',
|
|
16918
|
-
'--bulma-control-height',
|
|
16919
|
-
'--bulma-control-line-height',
|
|
16920
|
-
'--bulma-control-padding-vertical',
|
|
16921
|
-
'--bulma-control-padding-horizontal',
|
|
16922
|
-
'--bulma-control-size',
|
|
16923
|
-
'--bulma-control-focus-shadow-l',
|
|
16924
17532
|
// file
|
|
16925
17533
|
'--bulma-file-radius',
|
|
16926
17534
|
'--bulma-file-name-border-color',
|
|
@@ -16971,14 +17579,11 @@ const bulmaCssVars = [
|
|
|
16971
17579
|
'--bulma-input-icon-hover-color',
|
|
16972
17580
|
'--bulma-input-icon-focus-color',
|
|
16973
17581
|
'--bulma-input-radius',
|
|
16974
|
-
// columns
|
|
16975
|
-
'--bulma-column-gap',
|
|
16976
17582
|
// grid
|
|
16977
17583
|
'--bulma-grid-gap',
|
|
16978
17584
|
'--bulma-grid-column-count',
|
|
16979
17585
|
'--bulma-grid-column-min',
|
|
16980
17586
|
'--bulma-grid-cell-column-span',
|
|
16981
|
-
'--bulma-grid-cell-column-start',
|
|
16982
17587
|
// footer
|
|
16983
17588
|
'--bulma-footer-background-color',
|
|
16984
17589
|
'--bulma-footer-color',
|
|
@@ -17004,6 +17609,8 @@ const bulmaCssVars = [
|
|
|
17004
17609
|
'--bulma-section-padding-medium',
|
|
17005
17610
|
'--bulma-section-padding-large',
|
|
17006
17611
|
];
|
|
17612
|
+
/** Every Bulma CSS variable Theme accepts: the two lists above. */
|
|
17613
|
+
const bulmaCssVars = [...bulmaGlobalVars, ...bulmaComponentVars];
|
|
17007
17614
|
/**
|
|
17008
17615
|
* Convert a Bulma CSS variable name to a camelCase prop name.
|
|
17009
17616
|
* @param {string} varName - CSS variable name (e.g., '--bulma-primary-h').
|
|
@@ -17122,6 +17729,44 @@ const themeColumnGapVar = (columnGap) => {
|
|
|
17122
17729
|
`bulmaVars={{ '--bulma-column-gap': '${columnGap}' }} instead.`);
|
|
17123
17730
|
return columnGap;
|
|
17124
17731
|
};
|
|
17732
|
+
/**
|
|
17733
|
+
* `bulmaComponentVars` as a lookup, built by `warnComponentVars` the first
|
|
17734
|
+
* time it is needed.
|
|
17735
|
+
*/
|
|
17736
|
+
let componentVarKeys;
|
|
17737
|
+
/**
|
|
17738
|
+
* Warn in development when a Theme is given variables Bulma declares on the
|
|
17739
|
+
* component itself (`bulmaComponentVars`), whether through `bulmaVars` or a
|
|
17740
|
+
* variable prop such as `cardRadius`. Theme writes them where they can never
|
|
17741
|
+
* win, so nothing changes on the page, and saying so is the only way to find
|
|
17742
|
+
* out short of reading Bulma's stylesheet.
|
|
17743
|
+
*
|
|
17744
|
+
* Keyed by the set of variables, so a Theme that keeps passing the same ones
|
|
17745
|
+
* warns once, and a different set warns again. An empty value writes nothing,
|
|
17746
|
+
* so it is not reported.
|
|
17747
|
+
*
|
|
17748
|
+
* All of this is development-only, so it returns before doing anything in
|
|
17749
|
+
* production, and it builds its lookup on the first development call rather
|
|
17750
|
+
* than when the module loads.
|
|
17751
|
+
*/
|
|
17752
|
+
const warnComponentVars = (vars) => {
|
|
17753
|
+
if (!isDev()) {
|
|
17754
|
+
return;
|
|
17755
|
+
}
|
|
17756
|
+
const keys = (componentVarKeys ?? (componentVarKeys = new Set(bulmaComponentVars)));
|
|
17757
|
+
const inert = Object.entries(vars)
|
|
17758
|
+
.filter(([key, value]) => value && keys.has(key))
|
|
17759
|
+
.map(([key]) => key)
|
|
17760
|
+
.sort();
|
|
17761
|
+
if (inert.length === 0) {
|
|
17762
|
+
return;
|
|
17763
|
+
}
|
|
17764
|
+
const one = inert.length === 1;
|
|
17765
|
+
warnOnce(`Theme:component-vars:${inert.join(',')}`, `[bestax-bulma] <Theme>: Bulma declares ${inert.join(', ')} on the ` +
|
|
17766
|
+
`component itself, so a value set on Theme never reaches ` +
|
|
17767
|
+
`${one ? 'it' : 'them'}. Set ${one ? 'it' : 'them'} on that ` +
|
|
17768
|
+
'component instead, with className or style, or through Sass.');
|
|
17769
|
+
};
|
|
17125
17770
|
/** The one `<style>` element every `isRoot` Theme writes into. */
|
|
17126
17771
|
const ROOT_STYLE_ID = 'bestax-bulma-theme-vars';
|
|
17127
17772
|
/**
|
|
@@ -17283,6 +17928,12 @@ const Theme = ({ bulmaVars = {}, children, className, isRoot = false, colorMode,
|
|
|
17283
17928
|
}
|
|
17284
17929
|
return vars;
|
|
17285
17930
|
}, [bulmaVars, bulmaVarProps, radiusVar, columnGapVar]);
|
|
17931
|
+
// From an effect rather than the render body, so rendering does none of
|
|
17932
|
+
// this work, and a server render none at all. In production it returns
|
|
17933
|
+
// at its development check before looking at the variables.
|
|
17934
|
+
React.useEffect(() => {
|
|
17935
|
+
warnComponentVars(mergedVars);
|
|
17936
|
+
}, [mergedVars]);
|
|
17286
17937
|
// This Theme's place among root Themes; see `rootThemeRules`.
|
|
17287
17938
|
const [rootOrder] = React.useState(() => nextRootOrder++);
|
|
17288
17939
|
// The `:root` rules this Theme contributes, or '' when it contributes none.
|
|
@@ -17803,6 +18454,7 @@ exports.Hero = Hero;
|
|
|
17803
18454
|
exports.HeroBody = HeroBody;
|
|
17804
18455
|
exports.HeroFoot = HeroFoot;
|
|
17805
18456
|
exports.HeroHead = HeroHead;
|
|
18457
|
+
exports.HoverCard = HoverCard;
|
|
17806
18458
|
exports.Icon = Icon;
|
|
17807
18459
|
exports.IconText = IconText;
|
|
17808
18460
|
exports.Image = Image;
|