@allxsmith/bestax-bulma 5.18.0 → 5.18.1
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/extras.css +1 -1
- package/dist/extras.css.map +1 -1
- package/dist/index.cjs +418 -93
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +418 -93
- package/dist/index.esm.js.map +1 -1
- package/dist/types/components/Avatar.d.ts +14 -3
- package/dist/types/components/Dropdown.d.ts +1 -1
- package/dist/types/elements/Notification.d.ts +1 -3
- package/dist/types/form/Checkbox.d.ts +26 -2
- package/dist/types/form/DateTimeInputBase.d.ts +6 -1
- package/dist/types/form/Radio.d.ts +28 -2
- package/dist/types/form/Switch.d.ts +26 -2
- package/dist/types/form/TimeInputBase.d.ts +5 -1
- package/dist/types/helpers/Theme.d.ts +27 -1
- package/dist/types/helpers/colorDeprecations.d.ts +4 -1
- package/dist/types/helpers/devWarnings.d.ts +8 -0
- package/dist/types-cjs/components/Avatar.d.ts +14 -3
- package/dist/types-cjs/components/Dropdown.d.ts +1 -1
- package/dist/types-cjs/elements/Notification.d.ts +1 -3
- package/dist/types-cjs/form/Checkbox.d.ts +26 -2
- package/dist/types-cjs/form/DateTimeInputBase.d.ts +6 -1
- package/dist/types-cjs/form/Radio.d.ts +28 -2
- package/dist/types-cjs/form/Switch.d.ts +26 -2
- package/dist/types-cjs/form/TimeInputBase.d.ts +5 -1
- package/dist/types-cjs/helpers/Theme.d.ts +27 -1
- package/dist/types-cjs/helpers/colorDeprecations.d.ts +4 -1
- package/dist/types-cjs/helpers/devWarnings.d.ts +8 -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 +1 -1
- package/src/scss/form/_timeinput.scss +42 -14
package/dist/index.cjs
CHANGED
|
@@ -1252,6 +1252,30 @@ function isCustomElement(as) {
|
|
|
1252
1252
|
return typeof as === 'string' && as.includes('-');
|
|
1253
1253
|
}
|
|
1254
1254
|
|
|
1255
|
+
// INTERNAL: deliberately not exported from src/index.ts.
|
|
1256
|
+
const warnedKeys = new Set();
|
|
1257
|
+
// Fail closed: with no bundler and no Node (raw CDN ESM), reading `process`
|
|
1258
|
+
// throws and warnings stay off, so production can never warn by accident.
|
|
1259
|
+
const isDev = () => {
|
|
1260
|
+
try {
|
|
1261
|
+
return process.env.NODE_ENV !== 'production';
|
|
1262
|
+
}
|
|
1263
|
+
catch {
|
|
1264
|
+
return false;
|
|
1265
|
+
}
|
|
1266
|
+
};
|
|
1267
|
+
/**
|
|
1268
|
+
* Logs a console warning in development, once per `key` for the life of the
|
|
1269
|
+
* page. Safe to call during render: a re-render, or a second instance hitting
|
|
1270
|
+
* the same case, finds the key already recorded and stays quiet.
|
|
1271
|
+
*/
|
|
1272
|
+
const warnOnce = (key, message) => {
|
|
1273
|
+
if (!isDev() || warnedKeys.has(key))
|
|
1274
|
+
return;
|
|
1275
|
+
warnedKeys.add(key);
|
|
1276
|
+
console.warn(message);
|
|
1277
|
+
};
|
|
1278
|
+
|
|
1255
1279
|
const avatarColors = [
|
|
1256
1280
|
'primary',
|
|
1257
1281
|
'link',
|
|
@@ -1297,6 +1321,18 @@ const NON_INTERACTIVE_ROLES = [
|
|
|
1297
1321
|
'presentation',
|
|
1298
1322
|
'none',
|
|
1299
1323
|
];
|
|
1324
|
+
/**
|
|
1325
|
+
* Elements other than `a` that declare `target` themselves, so on them it is
|
|
1326
|
+
* the element's own attribute and not a link attribute Avatar withholds for
|
|
1327
|
+
* want of one. The link attribute warning leaves `target` out on these, the way
|
|
1328
|
+
* it leaves `rel` out everywhere. `area` and `base` are void elements, which
|
|
1329
|
+
* Avatar's content rules out, so `form` is the one a caller can reach.
|
|
1330
|
+
*/
|
|
1331
|
+
const ELEMENTS_WITH_OWN_TARGET = [
|
|
1332
|
+
'form',
|
|
1333
|
+
'area',
|
|
1334
|
+
'base',
|
|
1335
|
+
];
|
|
1300
1336
|
/**
|
|
1301
1337
|
* Derives a small set of initials from a name (e.g. "Ada Lovelace" -> "AL").
|
|
1302
1338
|
*/
|
|
@@ -1432,6 +1468,50 @@ const Avatar = React.forwardRef(function Avatar(avatarProps, ref) {
|
|
|
1432
1468
|
// Only forward link attributes when rendering an anchor or a custom (non-DOM)
|
|
1433
1469
|
// component; a plain `as="div"` must not receive a stray `href`/`target`/`rel`.
|
|
1434
1470
|
const isLinkLike = Tag === 'a' || typeof Tag !== 'string' || isCustomElement(Tag);
|
|
1471
|
+
// A plain element like a `div` declares no `href` or `target`, so the type
|
|
1472
|
+
// accepts them as Avatar's own while the drop below withholds them, and
|
|
1473
|
+
// without this they would vanish in silence (#733). Forwarding them would put
|
|
1474
|
+
// them where HTML has no such attribute, so the drop stays and development
|
|
1475
|
+
// reports it instead, naming only the ones actually passed.
|
|
1476
|
+
//
|
|
1477
|
+
// `rel` is withheld too but left out here. React declares it on every
|
|
1478
|
+
// element, so on a plain `as` it is that element's own attribute, and
|
|
1479
|
+
// "render it as a link" would be the wrong advice for it. `target` is the
|
|
1480
|
+
// same on an element that declares it, such as `form`, so it is left out
|
|
1481
|
+
// there. Whether to forward either one to such an element is a separate
|
|
1482
|
+
// question from this warning.
|
|
1483
|
+
//
|
|
1484
|
+
// Only for an `as` the caller wrote. Without one the element is Avatar's own
|
|
1485
|
+
// choice, and a message naming an `as` they never passed sends them looking
|
|
1486
|
+
// for it. A value counts when it is truthy, the same test that picks `'a'`
|
|
1487
|
+
// over `'figure'`, so an empty `href` from data asks for no link and draws no
|
|
1488
|
+
// warning. The key is the element plus the attributes passed, so a re-render
|
|
1489
|
+
// or a list of avatars warns once, while a different combination on the same
|
|
1490
|
+
// element still gets its own warning rather than hiding behind the first.
|
|
1491
|
+
//
|
|
1492
|
+
// Development-only is `warnOnce`'s job, as it is for the colour warnings, so
|
|
1493
|
+
// this block has no production check of its own. One here would have to read
|
|
1494
|
+
// `process` safely, and a `typeof process` test is not something a bundler
|
|
1495
|
+
// replaces: in a browser it is false, and the warning never fired in the
|
|
1496
|
+
// development builds it exists for. The cost is that production still
|
|
1497
|
+
// collects the attributes and builds the message for an avatar that needs
|
|
1498
|
+
// it, which is small and limited to the case the warning is about.
|
|
1499
|
+
if (as != null && !isLinkLike) {
|
|
1500
|
+
const dropped = Object.entries({
|
|
1501
|
+
href,
|
|
1502
|
+
target: ELEMENTS_WITH_OWN_TARGET.includes(as) ? undefined : target,
|
|
1503
|
+
})
|
|
1504
|
+
.filter(([, value]) => value)
|
|
1505
|
+
.map(([key]) => key);
|
|
1506
|
+
if (dropped.length > 0) {
|
|
1507
|
+
warnOnce(`Avatar:link-props-on-${as}:${dropped.join('+')}`, `[bestax-bulma] <Avatar as="${as}" ${dropped.join(' ')}>: this ` +
|
|
1508
|
+
`<${as}> renders without ` +
|
|
1509
|
+
`${dropped.map(key => `"${key}"`).join(' and ')}, because Avatar ` +
|
|
1510
|
+
`passes link attributes on only to a target that can be a link (an ` +
|
|
1511
|
+
`"a", a custom element, or a component). To make it a link, render ` +
|
|
1512
|
+
`it as="a" or pass a link component to "as".`);
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1435
1515
|
// Present-only, not `{ href, target, rel }`. An unconditional spread hands the
|
|
1436
1516
|
// target these keys whatever the caller passed, and a key existing is not free:
|
|
1437
1517
|
// a target that tests for one sees a link where there is none, and one that
|
|
@@ -1822,12 +1902,12 @@ const CardComponent = ({ className, children, textColor, color, bgColor, hasShad
|
|
|
1822
1902
|
* The set withheld from a non-anchor `Card.FooterItem` (`span`/`button`): the
|
|
1823
1903
|
* derived anchor-only attributes, minus `type` (also valid on a `<button>` as
|
|
1824
1904
|
* `submit`/`button`/`reset`, so stripping it there would remove a working
|
|
1825
|
-
* attribute
|
|
1826
|
-
*
|
|
1827
|
-
* `HTMLAttributes` for every element, so the derived set alone
|
|
1828
|
-
* withhold it — the same addition `Level.Item` makes).
|
|
1905
|
+
* attribute; one set for both tags means a `type` also reaches a `<span>`,
|
|
1906
|
+
* which `Dropdown.Item` avoids by choosing its set per tag), plus `rel` (React
|
|
1907
|
+
* declares it on `HTMLAttributes` for every element, so the derived set alone
|
|
1908
|
+
* would not withhold it — the same addition `Level.Item` makes).
|
|
1829
1909
|
*/
|
|
1830
|
-
const STRIP_FROM_NON_ANCHOR$
|
|
1910
|
+
const STRIP_FROM_NON_ANCHOR$1 = (() => {
|
|
1831
1911
|
const { type: _type, ...rest } = ANCHOR_ONLY_ATTRS;
|
|
1832
1912
|
return { ...rest, rel: true };
|
|
1833
1913
|
})();
|
|
@@ -1967,7 +2047,7 @@ const CardFooterItem = ({ as = 'span', className, children, color, bgColor, text
|
|
|
1967
2047
|
if (as === 'a') {
|
|
1968
2048
|
return (jsxRuntime.jsx("a", { className: itemClasses, ...omitAttrs(rest, STRIP_FROM_NON_BUTTON), children: children }));
|
|
1969
2049
|
}
|
|
1970
|
-
const forwarded = omitAttrs(rest, STRIP_FROM_NON_ANCHOR$
|
|
2050
|
+
const forwarded = omitAttrs(rest, STRIP_FROM_NON_ANCHOR$1);
|
|
1971
2051
|
if (as === 'button') {
|
|
1972
2052
|
const forwardedType = forwarded.type;
|
|
1973
2053
|
return (jsxRuntime.jsx("button", { className: itemClasses, ...forwarded,
|
|
@@ -2225,6 +2305,35 @@ const DropdownComponent = React.forwardRef(function DropdownComponent({ label, c
|
|
|
2225
2305
|
e.preventDefault();
|
|
2226
2306
|
items[items.length - 1].focus();
|
|
2227
2307
|
break;
|
|
2308
|
+
case 'Enter':
|
|
2309
|
+
case ' ': {
|
|
2310
|
+
// Activate the focused item by clicking it, so its `onClick` runs and
|
|
2311
|
+
// closing follows `closeOnClick` as it does for the mouse. A caller's
|
|
2312
|
+
// own key handler that prevented the default has claimed the key, and
|
|
2313
|
+
// is left to it rather than followed by a second activation.
|
|
2314
|
+
if (currentIndex < 0 || e.defaultPrevented)
|
|
2315
|
+
break;
|
|
2316
|
+
const item = items[currentIndex];
|
|
2317
|
+
// Leave the browser's own activation alone, or the item runs twice: a
|
|
2318
|
+
// `<button>` answers both keys, and a link with an `href` answers
|
|
2319
|
+
// Enter. A link does not answer Space, so Space on a link is handled
|
|
2320
|
+
// here too, which also keeps the page from scrolling.
|
|
2321
|
+
if (item.tagName === 'BUTTON')
|
|
2322
|
+
break;
|
|
2323
|
+
if (e.key === 'Enter' &&
|
|
2324
|
+
item.tagName === 'A' &&
|
|
2325
|
+
item.hasAttribute('href')) {
|
|
2326
|
+
break;
|
|
2327
|
+
}
|
|
2328
|
+
e.preventDefault();
|
|
2329
|
+
// A held key sends a keydown per auto-repeat. Clicking on each would
|
|
2330
|
+
// toggle a checkbox item over and over while `closeOnClick` is off,
|
|
2331
|
+
// so only the first press activates. The default is still prevented
|
|
2332
|
+
// on the repeats above, so a held Space does not scroll the page.
|
|
2333
|
+
if (!e.repeat)
|
|
2334
|
+
item.click();
|
|
2335
|
+
break;
|
|
2336
|
+
}
|
|
2228
2337
|
}
|
|
2229
2338
|
};
|
|
2230
2339
|
const dropdownClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
|
|
@@ -2234,19 +2343,16 @@ const DropdownComponent = React.forwardRef(function DropdownComponent({ label, c
|
|
|
2234
2343
|
* The anchor's attributes, minus the one a `<button>` legitimately takes.
|
|
2235
2344
|
*
|
|
2236
2345
|
* `type` stays: `as="button"` is a supported form and `type="submit"` is valid
|
|
2237
|
-
* there, so stripping it would remove a working attribute.
|
|
2238
|
-
*
|
|
2239
|
-
*
|
|
2240
|
-
*
|
|
2241
|
-
*
|
|
2242
|
-
* This set differs from `Level.Item`'s in both directions, not just one. The
|
|
2243
|
-
* `type` above, which Level strips and this component keeps, and `rel`, which
|
|
2244
|
-
* Level adds and this component does not: React declares `rel` on
|
|
2346
|
+
* there, so stripping it would remove a working attribute. A `<div>` takes no
|
|
2347
|
+
* `type`, so it gets the whole `ANCHOR_ONLY_ATTRS` set instead. The set is
|
|
2348
|
+
* chosen by the rendered tag because that is where the reason lives (#692).
|
|
2349
|
+
*
|
|
2350
|
+
* Neither set withholds `rel`, which `Level.Item` does: React declares `rel` on
|
|
2245
2351
|
* `HTMLAttributes` for every element, so withholding it would diverge from
|
|
2246
|
-
* React's own typing
|
|
2352
|
+
* React's own typing, the call #641 recorded for `Navbar.Link`. Level
|
|
2247
2353
|
* withholds it anyway, because it always has.
|
|
2248
2354
|
*/
|
|
2249
|
-
const
|
|
2355
|
+
const STRIP_FROM_BUTTON = (() => {
|
|
2250
2356
|
const { type: _type, ...rest } = ANCHOR_ONLY_ATTRS;
|
|
2251
2357
|
return rest;
|
|
2252
2358
|
})();
|
|
@@ -2276,12 +2382,22 @@ const DropdownItem = ((itemProps) => {
|
|
|
2276
2382
|
// are not interchangeable and neither derives from the other.
|
|
2277
2383
|
//
|
|
2278
2384
|
// Menu's condition also admits a custom component and a custom element, which
|
|
2279
|
-
// own their prop contracts.
|
|
2280
|
-
//
|
|
2281
|
-
const forwarded = Component === 'a'
|
|
2385
|
+
// own their prop contracts. Here the set follows the tag: an `<a>` keeps all
|
|
2386
|
+
// of them, a `<button>` keeps `type`, and any other `as` keeps none of them.
|
|
2387
|
+
const forwarded = Component === 'a'
|
|
2388
|
+
? rest
|
|
2389
|
+
: Component === 'button'
|
|
2390
|
+
? omitAttrs(rest, STRIP_FROM_BUTTON)
|
|
2391
|
+
: omitAttrs(rest, ANCHOR_ONLY_ATTRS);
|
|
2282
2392
|
return (jsxRuntime.jsx(Component, { className: classNames(usePrefixedClassNames('dropdown-item', {
|
|
2283
2393
|
'is-active': active,
|
|
2284
|
-
}), bulmaHelperClasses, className),
|
|
2394
|
+
}), bulmaHelperClasses, className), "data-testid": "dropdown-item", ...forwarded,
|
|
2395
|
+
// After `forwarded` for the same reason as `role` and `type` below: a
|
|
2396
|
+
// spread carrying `tabIndex: undefined` would otherwise erase the
|
|
2397
|
+
// default. The item keeps its menu role and its place in the arrow-key
|
|
2398
|
+
// order, but a `<div>` or an anchor without an `href` cannot take focus
|
|
2399
|
+
// without a tabindex, so the arrow keys stall on it.
|
|
2400
|
+
tabIndex: forwarded.tabIndex ?? 0,
|
|
2285
2401
|
// After `forwarded` for the same reason as `type` below: a spread
|
|
2286
2402
|
// carrying `role: undefined` would otherwise erase the default, and an
|
|
2287
2403
|
// item with no role drops out of the menu and its arrow-key order.
|
|
@@ -3163,23 +3279,6 @@ const UNSTYLED_MODIFIER_COLORS = [
|
|
|
3163
3279
|
'grey-lighter',
|
|
3164
3280
|
];
|
|
3165
3281
|
const CSS_BACKED = 'primary, link, info, success, warning, danger, black, white, light, dark';
|
|
3166
|
-
const warnedKeys = new Set();
|
|
3167
|
-
// Fail closed: with no bundler and no Node (raw CDN ESM), reading `process`
|
|
3168
|
-
// throws and warnings stay off, so production can never warn by accident.
|
|
3169
|
-
const isDev = () => {
|
|
3170
|
-
try {
|
|
3171
|
-
return process.env.NODE_ENV !== 'production';
|
|
3172
|
-
}
|
|
3173
|
-
catch {
|
|
3174
|
-
return false;
|
|
3175
|
-
}
|
|
3176
|
-
};
|
|
3177
|
-
const warnOnce = (key, message) => {
|
|
3178
|
-
if (!isDev() || warnedKeys.has(key))
|
|
3179
|
-
return;
|
|
3180
|
-
warnedKeys.add(key);
|
|
3181
|
-
console.warn(message);
|
|
3182
|
-
};
|
|
3183
3282
|
/**
|
|
3184
3283
|
* Dev warning for a `color` value whose `is-<color>` modifier has no shipped
|
|
3185
3284
|
* CSS. `extraUnstyled` covers per-component dead values beyond the shared
|
|
@@ -4768,11 +4867,26 @@ let toasts = [];
|
|
|
4768
4867
|
// Queued toasts: FIFO queue, one at a time
|
|
4769
4868
|
let queuedToasts = [];
|
|
4770
4869
|
let currentQueuedToast = null;
|
|
4870
|
+
// The server has nowhere to portal a toast to, and its copy of this module is
|
|
4871
|
+
// shared by every request, so server rendering reads an empty list. Hydration
|
|
4872
|
+
// reads it too, which keeps the first client render matching the server's.
|
|
4873
|
+
const noToasts = [];
|
|
4874
|
+
const getServerToasts = () => noToasts;
|
|
4875
|
+
// What a container shows right now: the stacked toasts, then the queued one on
|
|
4876
|
+
// screen. ToastContainer renders from this rather than from updates alone, so
|
|
4877
|
+
// toasts raised before it mounted still appear. It is replaced rather than
|
|
4878
|
+
// mutated, and only when listeners are notified, because useSyncExternalStore
|
|
4879
|
+
// needs the same array back between changes. An empty list is `noToasts`
|
|
4880
|
+
// itself, so a container that hydrates with nothing to show reads the same
|
|
4881
|
+
// snapshot the server did and has no reason to render again.
|
|
4882
|
+
let visibleToasts = noToasts;
|
|
4883
|
+
const getVisibleToasts = () => visibleToasts;
|
|
4771
4884
|
const notifyListeners = () => {
|
|
4772
4885
|
const allVisible = [...toasts];
|
|
4773
4886
|
if (currentQueuedToast) {
|
|
4774
4887
|
allVisible.push(currentQueuedToast);
|
|
4775
4888
|
}
|
|
4889
|
+
visibleToasts = allVisible.length > 0 ? allVisible : noToasts;
|
|
4776
4890
|
toastListeners.forEach(listener => listener([...allVisible]));
|
|
4777
4891
|
};
|
|
4778
4892
|
const processQueuedToast = () => {
|
|
@@ -4884,10 +4998,9 @@ const toast = {
|
|
|
4884
4998
|
* @returns {JSX.Element | null} The rendered toast container, or null if empty.
|
|
4885
4999
|
*/
|
|
4886
5000
|
const ToastContainer = ({ position = 'top-right', }) => {
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
}, []);
|
|
5001
|
+
// Starts from the toasts already showing instead of an empty list, then
|
|
5002
|
+
// follows changes.
|
|
5003
|
+
const toastList = React.useSyncExternalStore(toast.subscribe, getVisibleToasts, getServerToasts);
|
|
4891
5004
|
if (typeof document === 'undefined' || toastList.length === 0) {
|
|
4892
5005
|
return null;
|
|
4893
5006
|
}
|
|
@@ -5039,6 +5152,14 @@ const Dialog = React.forwardRef(({ isOpen, title, message, type = 'default', con
|
|
|
5039
5152
|
Dialog.displayName = 'Dialog';
|
|
5040
5153
|
let dialogListeners = new Set();
|
|
5041
5154
|
let currentDialog = null;
|
|
5155
|
+
// DialogContainer renders from the open dialog rather than from updates alone,
|
|
5156
|
+
// so a dialog raised before it mounted still opens once it does.
|
|
5157
|
+
const getCurrentDialog = () => currentDialog;
|
|
5158
|
+
// The server renders no dialog: its copy of this module is shared by every
|
|
5159
|
+
// request, so the dialog it holds may belong to another page. Hydration reads
|
|
5160
|
+
// the same empty value, which keeps the first client render matching the
|
|
5161
|
+
// server's, and the dialog opens right after.
|
|
5162
|
+
const getServerDialog = () => null;
|
|
5042
5163
|
const notifyDialogListeners = () => {
|
|
5043
5164
|
dialogListeners.forEach(listener => listener(currentDialog));
|
|
5044
5165
|
};
|
|
@@ -5100,10 +5221,8 @@ const dialog = {
|
|
|
5100
5221
|
* @returns {JSX.Element | null} The rendered dialog, or null if none is active.
|
|
5101
5222
|
*/
|
|
5102
5223
|
const DialogContainer = () => {
|
|
5103
|
-
|
|
5104
|
-
React.
|
|
5105
|
-
return dialog.subscribe(setCurrent);
|
|
5106
|
-
}, []);
|
|
5224
|
+
// Starts from the dialog already open instead of none, then follows changes.
|
|
5225
|
+
const current = React.useSyncExternalStore(dialog.subscribe, getCurrentDialog, getServerDialog);
|
|
5107
5226
|
if (!current) {
|
|
5108
5227
|
return null;
|
|
5109
5228
|
}
|
|
@@ -6177,11 +6296,28 @@ let notifications = [];
|
|
|
6177
6296
|
// Queue support
|
|
6178
6297
|
let queuedNotifications = [];
|
|
6179
6298
|
let currentQueuedNotification = null;
|
|
6299
|
+
// The server has nowhere to portal a notification to, and its copy of this
|
|
6300
|
+
// module is shared by every request, so server rendering reads an empty list.
|
|
6301
|
+
// Hydration reads it too, which keeps the first client render matching the
|
|
6302
|
+
// server's.
|
|
6303
|
+
const noNotifications = [];
|
|
6304
|
+
const getServerNotifications = () => noNotifications;
|
|
6305
|
+
// What a container shows right now: the stacked notifications, then the queued
|
|
6306
|
+
// one on screen. NotificationContainer renders from this rather than from
|
|
6307
|
+
// updates alone, so notifications raised before it mounted still appear. It is
|
|
6308
|
+
// replaced rather than mutated, and only when listeners are notified, because
|
|
6309
|
+
// useSyncExternalStore needs the same array back between changes. An empty
|
|
6310
|
+
// list is `noNotifications` itself, so a container that hydrates with nothing
|
|
6311
|
+
// to show reads the same snapshot the server did and has no reason to render
|
|
6312
|
+
// again.
|
|
6313
|
+
let visibleNotifications = noNotifications;
|
|
6314
|
+
const getVisibleNotifications = () => visibleNotifications;
|
|
6180
6315
|
const notifyNotificationListeners = () => {
|
|
6181
6316
|
const allVisible = [...notifications];
|
|
6182
6317
|
if (currentQueuedNotification) {
|
|
6183
6318
|
allVisible.push(currentQueuedNotification);
|
|
6184
6319
|
}
|
|
6320
|
+
visibleNotifications = allVisible.length > 0 ? allVisible : noNotifications;
|
|
6185
6321
|
notificationListeners.forEach(listener => listener([...allVisible]));
|
|
6186
6322
|
};
|
|
6187
6323
|
const processQueuedNotification = () => {
|
|
@@ -6317,19 +6453,16 @@ const NotificationItem = ({ instance, onClose }) => {
|
|
|
6317
6453
|
};
|
|
6318
6454
|
/**
|
|
6319
6455
|
* Container component for rendering programmatic notifications.
|
|
6320
|
-
* Place once at your app root to enable the notification API.
|
|
6321
|
-
* before calling `notification`: a notification shown while no container is
|
|
6322
|
-
* mounted doesn't appear when one mounts, only alongside the next call.
|
|
6456
|
+
* Place once at your app root to enable the notification API.
|
|
6323
6457
|
*
|
|
6324
6458
|
* @function
|
|
6325
6459
|
* @param {{ position?: NotificationPosition }} props - Container props.
|
|
6326
6460
|
* @returns {JSX.Element | null} The rendered notification container, or null if empty.
|
|
6327
6461
|
*/
|
|
6328
6462
|
const NotificationContainer = ({ position = 'top-right' }) => {
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
}, []);
|
|
6463
|
+
// Starts from the notifications already showing instead of an empty list,
|
|
6464
|
+
// then follows changes.
|
|
6465
|
+
const items = React.useSyncExternalStore(notification.subscribe, getVisibleNotifications, getServerNotifications);
|
|
6333
6466
|
if (typeof document === 'undefined' || items.length === 0) {
|
|
6334
6467
|
return null;
|
|
6335
6468
|
}
|
|
@@ -6895,7 +7028,21 @@ const RadiosProvider = RadiosContext.Provider;
|
|
|
6895
7028
|
const CheckboxesProvider = CheckboxesContext.Provider;
|
|
6896
7029
|
|
|
6897
7030
|
/**
|
|
6898
|
-
*
|
|
7031
|
+
* The values the Checkbox `color` prop accepts, as a readonly tuple.
|
|
7032
|
+
*
|
|
7033
|
+
* `CheckboxProps['color']` is typed from it, so the two list the same values.
|
|
7034
|
+
* Map over it to build a color picker, or check a value that arrives at
|
|
7035
|
+
* runtime before passing it in: the component adds no color class for a value
|
|
7036
|
+
* outside the tuple.
|
|
7037
|
+
*
|
|
7038
|
+
* @example
|
|
7039
|
+
* import { Checkbox, checkboxColors } from '@allxsmith/bestax-bulma';
|
|
7040
|
+
*
|
|
7041
|
+
* checkboxColors.map(color => (
|
|
7042
|
+
* <Checkbox key={color} color={color}>
|
|
7043
|
+
* {color}
|
|
7044
|
+
* </Checkbox>
|
|
7045
|
+
* ));
|
|
6899
7046
|
*/
|
|
6900
7047
|
const checkboxColors = [
|
|
6901
7048
|
'primary',
|
|
@@ -6906,7 +7053,17 @@ const checkboxColors = [
|
|
|
6906
7053
|
'danger',
|
|
6907
7054
|
];
|
|
6908
7055
|
/**
|
|
6909
|
-
*
|
|
7056
|
+
* The values the Checkbox `size` prop accepts, as a readonly tuple.
|
|
7057
|
+
*
|
|
7058
|
+
* `CheckboxProps['size']` is typed from it, so the two list the same values.
|
|
7059
|
+
* Use it to offer a size choice or to check a value that arrives at runtime:
|
|
7060
|
+
* the component adds no size class for a value outside the tuple. These are
|
|
7061
|
+
* element sizes, not the spacing scale in `validSizes`.
|
|
7062
|
+
*
|
|
7063
|
+
* @example
|
|
7064
|
+
* import { checkboxSizes } from '@allxsmith/bestax-bulma';
|
|
7065
|
+
*
|
|
7066
|
+
* type CheckboxSize = (typeof checkboxSizes)[number];
|
|
6910
7067
|
*/
|
|
6911
7068
|
const checkboxSizes = ['small', 'normal', 'medium', 'large'];
|
|
6912
7069
|
/**
|
|
@@ -7351,7 +7508,23 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
7351
7508
|
File.displayName = 'File';
|
|
7352
7509
|
|
|
7353
7510
|
/**
|
|
7354
|
-
*
|
|
7511
|
+
* The values the Radio `color` prop accepts, as a readonly tuple.
|
|
7512
|
+
*
|
|
7513
|
+
* `RadioProps['color']` is typed from it, so the two list the same values.
|
|
7514
|
+
* Map over it to build a color picker, or check a value that arrives at
|
|
7515
|
+
* runtime before passing it in: the component adds no color class for a value
|
|
7516
|
+
* outside the tuple.
|
|
7517
|
+
*
|
|
7518
|
+
* @example
|
|
7519
|
+
* import { Radio, Radios, radioColors } from '@allxsmith/bestax-bulma';
|
|
7520
|
+
*
|
|
7521
|
+
* <Radios name="accent" defaultValue="primary">
|
|
7522
|
+
* {radioColors.map(color => (
|
|
7523
|
+
* <Radio key={color} value={color} color={color}>
|
|
7524
|
+
* {color}
|
|
7525
|
+
* </Radio>
|
|
7526
|
+
* ))}
|
|
7527
|
+
* </Radios>;
|
|
7355
7528
|
*/
|
|
7356
7529
|
const radioColors = [
|
|
7357
7530
|
'primary',
|
|
@@ -7362,7 +7535,17 @@ const radioColors = [
|
|
|
7362
7535
|
'danger',
|
|
7363
7536
|
];
|
|
7364
7537
|
/**
|
|
7365
|
-
*
|
|
7538
|
+
* The values the Radio `size` prop accepts, as a readonly tuple.
|
|
7539
|
+
*
|
|
7540
|
+
* `RadioProps['size']` is typed from it, so the two list the same values. Use
|
|
7541
|
+
* it to offer a size choice or to check a value that arrives at runtime: the
|
|
7542
|
+
* component adds no size class for a value outside the tuple. These are
|
|
7543
|
+
* element sizes, not the spacing scale in `validSizes`.
|
|
7544
|
+
*
|
|
7545
|
+
* @example
|
|
7546
|
+
* import { radioSizes } from '@allxsmith/bestax-bulma';
|
|
7547
|
+
*
|
|
7548
|
+
* type RadioSize = (typeof radioSizes)[number];
|
|
7366
7549
|
*/
|
|
7367
7550
|
const radioSizes = ['small', 'normal', 'medium', 'large'];
|
|
7368
7551
|
/**
|
|
@@ -7493,7 +7676,21 @@ const RadiosComponent = ({ label, labelSize, labelProps, horizontal, message, me
|
|
|
7493
7676
|
const Radios = withSubComponents(RadiosComponent, { Radio }, 'Radios');
|
|
7494
7677
|
|
|
7495
7678
|
/**
|
|
7496
|
-
*
|
|
7679
|
+
* The values the Switch `color` and `passiveType` props accept, as a readonly
|
|
7680
|
+
* tuple.
|
|
7681
|
+
*
|
|
7682
|
+
* Both props are typed from it, so they list the same values. Map over it to
|
|
7683
|
+
* build a color picker, or check a value that arrives at runtime before
|
|
7684
|
+
* passing it in: the component adds no class for a value outside the tuple.
|
|
7685
|
+
*
|
|
7686
|
+
* @example
|
|
7687
|
+
* import { Switch, switchColors } from '@allxsmith/bestax-bulma';
|
|
7688
|
+
*
|
|
7689
|
+
* switchColors.map(color => (
|
|
7690
|
+
* <Switch key={color} color={color} defaultChecked>
|
|
7691
|
+
* {color}
|
|
7692
|
+
* </Switch>
|
|
7693
|
+
* ));
|
|
7497
7694
|
*/
|
|
7498
7695
|
const switchColors = [
|
|
7499
7696
|
'primary',
|
|
@@ -7504,7 +7701,17 @@ const switchColors = [
|
|
|
7504
7701
|
'danger',
|
|
7505
7702
|
];
|
|
7506
7703
|
/**
|
|
7507
|
-
*
|
|
7704
|
+
* The values the Switch `size` prop accepts, as a readonly tuple.
|
|
7705
|
+
*
|
|
7706
|
+
* `SwitchProps['size']` is typed from it, so the two list the same values. Use
|
|
7707
|
+
* it to offer a size choice or to check a value that arrives at runtime: the
|
|
7708
|
+
* component adds no size class for a value outside the tuple. These are
|
|
7709
|
+
* element sizes, not the spacing scale in `validSizes`.
|
|
7710
|
+
*
|
|
7711
|
+
* @example
|
|
7712
|
+
* import { switchSizes } from '@allxsmith/bestax-bulma';
|
|
7713
|
+
*
|
|
7714
|
+
* type SwitchSize = (typeof switchSizes)[number];
|
|
7508
7715
|
*/
|
|
7509
7716
|
const switchSizes = ['small', 'normal', 'medium', 'large'];
|
|
7510
7717
|
/**
|
|
@@ -13000,17 +13207,130 @@ function cssVarToProp(varName) {
|
|
|
13000
13207
|
.join('');
|
|
13001
13208
|
}
|
|
13002
13209
|
/**
|
|
13003
|
-
*
|
|
13210
|
+
* Prop names `cssVarToProp` would mint that are already `BulmaOtherProps`
|
|
13211
|
+
* helper props: `--bulma-shadow` becomes `shadow` and `--bulma-radius` becomes
|
|
13212
|
+
* `radius`. They stay out of `bulmaVarPropMap`, so on Theme each is the helper
|
|
13213
|
+
* prop it is on every other component, and both variables are still reachable
|
|
13214
|
+
* through `bulmaVars`.
|
|
13004
13215
|
*
|
|
13005
|
-
*
|
|
13006
|
-
*
|
|
13007
|
-
*
|
|
13008
|
-
|
|
13009
|
-
|
|
13216
|
+
* `radius` was missed here once and set `--bulma-radius` while typed as the
|
|
13217
|
+
* helper (#694). It still writes that variable, so what it did then keeps
|
|
13218
|
+
* working; see `themeRadiusVar`.
|
|
13219
|
+
*/
|
|
13220
|
+
const helperPropNames = ['shadow', 'radius'];
|
|
13221
|
+
/**
|
|
13222
|
+
* Mapping of camelCase prop names to their Bulma CSS variable counterparts,
|
|
13223
|
+
* minus the `helperPropNames` above.
|
|
13010
13224
|
*/
|
|
13011
13225
|
const bulmaVarPropMap = Object.fromEntries(bulmaCssVars
|
|
13012
13226
|
.map(cssVar => [cssVarToProp(cssVar), cssVar])
|
|
13013
|
-
.filter(([prop]) => prop
|
|
13227
|
+
.filter(([prop]) => !helperPropNames.includes(prop)));
|
|
13228
|
+
/**
|
|
13229
|
+
* What each helper value of `radius` writes to `--bulma-radius` on a Theme,
|
|
13230
|
+
* alongside its class.
|
|
13231
|
+
*
|
|
13232
|
+
* Before #694, `radius="radiusless"` wrote `--bulma-radius: radiusless`. That
|
|
13233
|
+
* is not a length, and a declaration reading an invalid variable falls back
|
|
13234
|
+
* to its property's initial value, which for a border radius is 0. So
|
|
13235
|
+
* everything inside the Theme that takes its radius from that variable lost
|
|
13236
|
+
* it, and under `isRoot` so did everything on the page that does. Writing a
|
|
13237
|
+
* real 0 keeps what
|
|
13238
|
+
* people see and makes it valid CSS. Keyed by the helper values, so adding
|
|
13239
|
+
* one means saying what it writes.
|
|
13240
|
+
*/
|
|
13241
|
+
const radiusHelperVars = {
|
|
13242
|
+
radiusless: '0',
|
|
13243
|
+
};
|
|
13244
|
+
/**
|
|
13245
|
+
* What `radius` writes to `--bulma-radius` on a Theme, or `undefined` when it
|
|
13246
|
+
* writes nothing.
|
|
13247
|
+
*
|
|
13248
|
+
* A helper value writes its `radiusHelperVars` entry. Any other non-empty
|
|
13249
|
+
* string is written as given: before #694 every `radius` on Theme set the
|
|
13250
|
+
* variable whatever its type said, and a JavaScript caller passing a length
|
|
13251
|
+
* got the radius it asked for. That keeps working so nothing breaks, but the
|
|
13252
|
+
* type rejects it, so it warns in development and points at `bulmaVars`.
|
|
13253
|
+
*
|
|
13254
|
+
* Only a string writes anything, because only a string ever set the variable
|
|
13255
|
+
* to something usable: an empty value or zero was dropped, and a boolean or
|
|
13256
|
+
* any other bare number is not a length. The helper ignores those too, the
|
|
13257
|
+
* way it does on every other component.
|
|
13258
|
+
*/
|
|
13259
|
+
const themeRadiusVar = (radius) => {
|
|
13260
|
+
if (typeof radius !== 'string' || radius === '') {
|
|
13261
|
+
return undefined;
|
|
13262
|
+
}
|
|
13263
|
+
if (validRadii.includes(radius)) {
|
|
13264
|
+
return radiusHelperVars[radius];
|
|
13265
|
+
}
|
|
13266
|
+
warnOnce('Theme:radius-variable', `[bestax-bulma] <Theme radius="${radius}">: setting --bulma-radius ` +
|
|
13267
|
+
'through the radius prop is deprecated and will stop working in a ' +
|
|
13268
|
+
'future major version. On Theme, as on every other component, radius ' +
|
|
13269
|
+
`is the "${validRadii.join('", "')}" helper. Set the variable with ` +
|
|
13270
|
+
`bulmaVars={{ '--bulma-radius': '${radius}' }} instead.`);
|
|
13271
|
+
return radius;
|
|
13272
|
+
};
|
|
13273
|
+
/** The one `<style>` element every `isRoot` Theme writes into. */
|
|
13274
|
+
const ROOT_STYLE_ID = 'bestax-bulma-theme-vars';
|
|
13275
|
+
/**
|
|
13276
|
+
* The `:root` rules of every mounted `isRoot` Theme that has any, keyed by the
|
|
13277
|
+
* Theme's render order.
|
|
13278
|
+
*
|
|
13279
|
+
* Root Themes share one `<style>` element, so each keeps its rules here and
|
|
13280
|
+
* the element is rebuilt from all of them whenever one mounts, changes or
|
|
13281
|
+
* unmounts. Before this each Theme overwrote the element with only its own
|
|
13282
|
+
* rules, and the first to unmount removed it for all of them (#736).
|
|
13283
|
+
*
|
|
13284
|
+
* The key is a number each Theme takes when it first renders. React renders a
|
|
13285
|
+
* parent before its children and an earlier sibling before a later one, so an
|
|
13286
|
+
* inner or later-mounted root Theme sorts later, comes later in the
|
|
13287
|
+
* stylesheet, and wins a variable two of them set, as an inner scoped Theme
|
|
13288
|
+
* does. Effects would give the wrong answer for nesting: React runs a child's
|
|
13289
|
+
* effects before its parent's. Only the relative order matters, so a number
|
|
13290
|
+
* skipped by StrictMode calling the initializer twice, or by a render React
|
|
13291
|
+
* throws away, is harmless. The key never changes, so an update keeps its
|
|
13292
|
+
* place and re-rendering one Theme never changes which one wins.
|
|
13293
|
+
*/
|
|
13294
|
+
const rootThemeRules = new Map();
|
|
13295
|
+
let nextRootOrder = 0;
|
|
13296
|
+
/**
|
|
13297
|
+
* Write every registered root Theme's rules into the shared element, creating
|
|
13298
|
+
* it when needed and removing it once no Theme has any rules left.
|
|
13299
|
+
*/
|
|
13300
|
+
const renderRootThemeRules = () => {
|
|
13301
|
+
let element = document.getElementById(ROOT_STYLE_ID);
|
|
13302
|
+
if (rootThemeRules.size === 0) {
|
|
13303
|
+
element?.remove();
|
|
13304
|
+
return;
|
|
13305
|
+
}
|
|
13306
|
+
if (!element) {
|
|
13307
|
+
element = document.createElement('style');
|
|
13308
|
+
element.id = ROOT_STYLE_ID;
|
|
13309
|
+
document.head.appendChild(element);
|
|
13310
|
+
}
|
|
13311
|
+
element.textContent = [...rootThemeRules]
|
|
13312
|
+
.sort(([a], [b]) => a - b)
|
|
13313
|
+
.map(([, rules]) => rules)
|
|
13314
|
+
.join('\n');
|
|
13315
|
+
};
|
|
13316
|
+
/**
|
|
13317
|
+
* Record one Theme's `:root` rules, where an empty string means it has none,
|
|
13318
|
+
* and rebuild the shared element if that changed anything. A Theme with no
|
|
13319
|
+
* rules never touches the element, which is what it did before the registry
|
|
13320
|
+
* too.
|
|
13321
|
+
*/
|
|
13322
|
+
const setRootThemeRules = (order, rules) => {
|
|
13323
|
+
if ((rootThemeRules.get(order) ?? '') === rules) {
|
|
13324
|
+
return;
|
|
13325
|
+
}
|
|
13326
|
+
if (rules) {
|
|
13327
|
+
rootThemeRules.set(order, rules);
|
|
13328
|
+
}
|
|
13329
|
+
else {
|
|
13330
|
+
rootThemeRules.delete(order);
|
|
13331
|
+
}
|
|
13332
|
+
renderRootThemeRules();
|
|
13333
|
+
};
|
|
13014
13334
|
/**
|
|
13015
13335
|
* Theme component that injects Bulma CSS variables either globally or locally.
|
|
13016
13336
|
*
|
|
@@ -13027,7 +13347,11 @@ const bulmaVarPropMap = Object.fromEntries(bulmaCssVars
|
|
|
13027
13347
|
* <Button color="primary">Themed</Button>
|
|
13028
13348
|
* </Theme>
|
|
13029
13349
|
*/
|
|
13030
|
-
const Theme = ({ bulmaVars = {}, children, className, isRoot = false, colorMode, ...restProps }) => {
|
|
13350
|
+
const Theme = ({ bulmaVars = {}, children, className, isRoot = false, colorMode, radius, ...restProps }) => {
|
|
13351
|
+
const radiusVar = themeRadiusVar(radius);
|
|
13352
|
+
const radiusHelper = validRadii.includes(radius)
|
|
13353
|
+
? radius
|
|
13354
|
+
: undefined;
|
|
13031
13355
|
// Extract Bulma variable props from restProps
|
|
13032
13356
|
const { bulmaVarProps, otherProps } = React.useMemo(() => {
|
|
13033
13357
|
const varProps = {};
|
|
@@ -13042,8 +13366,12 @@ const Theme = ({ bulmaVars = {}, children, className, isRoot = false, colorMode,
|
|
|
13042
13366
|
}
|
|
13043
13367
|
return { bulmaVarProps: varProps, otherProps: otherPropsObj };
|
|
13044
13368
|
}, [restProps]);
|
|
13045
|
-
// Use Bulma classes for styling (only when not isRoot)
|
|
13046
|
-
|
|
13369
|
+
// Use Bulma classes for styling (only when not isRoot). Only a helper value
|
|
13370
|
+
// of `radius` reaches the helper; a legacy string went to the variable.
|
|
13371
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
13372
|
+
...otherProps,
|
|
13373
|
+
radius: radiusHelper,
|
|
13374
|
+
});
|
|
13047
13375
|
// Merge bulmaVars and individual props, with props taking precedence
|
|
13048
13376
|
const mergedVars = React.useMemo(() => {
|
|
13049
13377
|
const vars = { ...bulmaVars };
|
|
@@ -13052,37 +13380,34 @@ const Theme = ({ bulmaVars = {}, children, className, isRoot = false, colorMode,
|
|
|
13052
13380
|
vars[cssVar] = bulmaVarProps[propName];
|
|
13053
13381
|
}
|
|
13054
13382
|
}
|
|
13383
|
+
if (radiusVar !== undefined) {
|
|
13384
|
+
vars['--bulma-radius'] = radiusVar;
|
|
13385
|
+
}
|
|
13055
13386
|
return vars;
|
|
13056
|
-
}, [bulmaVars, bulmaVarProps]);
|
|
13057
|
-
//
|
|
13058
|
-
React.
|
|
13387
|
+
}, [bulmaVars, bulmaVarProps, radiusVar]);
|
|
13388
|
+
// This Theme's place among root Themes; see `rootThemeRules`.
|
|
13389
|
+
const [rootOrder] = React.useState(() => nextRootOrder++);
|
|
13390
|
+
// The `:root` rules this Theme contributes, or '' when it contributes none.
|
|
13391
|
+
const rootRules = React.useMemo(() => {
|
|
13059
13392
|
if (!isRoot) {
|
|
13060
|
-
return;
|
|
13061
|
-
}
|
|
13062
|
-
const validVars = Object.entries(mergedVars).filter(([key, value]) => bulmaCssVars.includes(key) && value);
|
|
13063
|
-
if (validVars.length === 0) {
|
|
13064
|
-
return;
|
|
13065
|
-
}
|
|
13066
|
-
// Create and inject a style element for global CSS variables
|
|
13067
|
-
const styleId = 'bestax-bulma-theme-vars';
|
|
13068
|
-
let styleElement = document.getElementById(styleId);
|
|
13069
|
-
if (!styleElement) {
|
|
13070
|
-
styleElement = document.createElement('style');
|
|
13071
|
-
styleElement.id = styleId;
|
|
13072
|
-
document.head.appendChild(styleElement);
|
|
13393
|
+
return '';
|
|
13073
13394
|
}
|
|
13074
|
-
const cssRules =
|
|
13395
|
+
const cssRules = Object.entries(mergedVars)
|
|
13396
|
+
.filter(([key, value]) => bulmaCssVars.includes(key) && value)
|
|
13075
13397
|
.map(([key, value]) => `${key}: ${value};`)
|
|
13076
13398
|
.join(' ');
|
|
13077
|
-
|
|
13078
|
-
// Cleanup function to remove the style element when component unmounts
|
|
13079
|
-
return () => {
|
|
13080
|
-
const element = document.getElementById(styleId);
|
|
13081
|
-
if (element) {
|
|
13082
|
-
element.remove();
|
|
13083
|
-
}
|
|
13084
|
-
};
|
|
13399
|
+
return cssRules ? `:root { ${cssRules} }` : '';
|
|
13085
13400
|
}, [mergedVars, isRoot]);
|
|
13401
|
+
// Inject CSS variables globally at :root level, alongside any other root
|
|
13402
|
+
// Themes. Clearing `isRoot` or every variable passes '', which withdraws
|
|
13403
|
+
// this Theme's rules.
|
|
13404
|
+
React.useEffect(() => {
|
|
13405
|
+
setRootThemeRules(rootOrder, rootRules);
|
|
13406
|
+
}, [rootOrder, rootRules]);
|
|
13407
|
+
// Withdraw them on unmount. Kept apart from the effect above so a change
|
|
13408
|
+
// rewrites the shared element in place rather than removing and recreating
|
|
13409
|
+
// it between the cleanup and the next run.
|
|
13410
|
+
React.useEffect(() => () => setRootThemeRules(rootOrder, ''), [rootOrder]);
|
|
13086
13411
|
// Toggle Bulma's light/dark scheme by writing the `data-theme` attribute on
|
|
13087
13412
|
// the document root (<html>). This is always global, even on a scoped Theme.
|
|
13088
13413
|
// `'system'` removes the attribute so Bulma follows the OS preference.
|