@allxsmith/bestax-bulma 5.63.0 → 5.64.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/ScrollArea.js +7 -7
- package/dist/esm/components/ScrollArea.js.map +1 -1
- package/dist/esm/components/TableOfContents.js +85 -0
- package/dist/esm/components/TableOfContents.js.map +1 -0
- package/dist/esm/form/PasswordInput.js +105 -0
- package/dist/esm/form/PasswordInput.js.map +1 -0
- package/dist/esm/form/PasswordInputBase.js +177 -0
- package/dist/esm/form/PasswordInputBase.js.map +1 -0
- package/dist/esm/helpers/useScrollSpy.js +286 -0
- package/dist/esm/helpers/useScrollSpy.js.map +1 -0
- package/dist/extras.css +1 -1
- package/dist/extras.css.map +1 -1
- package/dist/index.cjs +616 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +4 -0
- package/dist/index.esm.js.map +1 -1
- package/dist/types/components/TableOfContents.d.ts +87 -0
- package/dist/types/form/PasswordInput.d.ts +98 -0
- package/dist/types/form/PasswordInputBase.d.ts +91 -0
- package/dist/types/helpers/useScrollSpy.d.ts +61 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types-cjs/components/TableOfContents.d.ts +87 -0
- package/dist/types-cjs/form/PasswordInput.d.ts +98 -0
- package/dist/types-cjs/form/PasswordInputBase.d.ts +91 -0
- package/dist/types-cjs/helpers/useScrollSpy.d.ts +61 -0
- package/dist/types-cjs/index.d.ts +5 -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/CLAUDE.md +3 -0
- package/src/scss/_input-trigger.scss +76 -0
- package/src/scss/components/_index.scss +1 -0
- package/src/scss/components/_tableofcontents.scss +42 -0
- package/src/scss/form/_index.scss +1 -0
- package/src/scss/form/_passwordinput.scss +112 -0
- package/src/scss/form/_picker-popover.scss +24 -63
package/dist/index.cjs
CHANGED
|
@@ -12453,7 +12453,7 @@ const TreeView = withSubComponents(TreeViewRoot, { Item: TreeViewItem }, 'TreeVi
|
|
|
12453
12453
|
const EDGES = ['top', 'bottom', 'start', 'end'];
|
|
12454
12454
|
// Layout can land a fraction of a pixel short of an edge, so anything closer
|
|
12455
12455
|
// than this counts as there.
|
|
12456
|
-
const SLACK = 1;
|
|
12456
|
+
const SLACK$1 = 1;
|
|
12457
12457
|
const sameMeasure = (a, b) => !!a &&
|
|
12458
12458
|
a.overflowing === b.overflowing &&
|
|
12459
12459
|
a.barInline === b.barInline &&
|
|
@@ -12534,8 +12534,8 @@ const ScrollArea = React.forwardRef(({ className, style, textColor, bgColor, hei
|
|
|
12534
12534
|
const scrollsX = orientation !== 'vertical';
|
|
12535
12535
|
const maxTop = el.scrollHeight - el.clientHeight;
|
|
12536
12536
|
const maxLeft = el.scrollWidth - el.clientWidth;
|
|
12537
|
-
const overflowY = scrollsY && maxTop > SLACK;
|
|
12538
|
-
const overflowX = scrollsX && maxLeft > SLACK;
|
|
12537
|
+
const overflowY = scrollsY && maxTop > SLACK$1;
|
|
12538
|
+
const overflowX = scrollsX && maxLeft > SLACK$1;
|
|
12539
12539
|
// Right to left, scrollLeft runs from 0 at the start to negative.
|
|
12540
12540
|
const fromStart = layoutRef.current.rtl
|
|
12541
12541
|
? -el.scrollLeft
|
|
@@ -12543,10 +12543,10 @@ const ScrollArea = React.forwardRef(({ className, style, textColor, bgColor, hei
|
|
|
12543
12543
|
const next = {
|
|
12544
12544
|
overflowing: overflowY || overflowX,
|
|
12545
12545
|
edges: {
|
|
12546
|
-
top: overflowY && el.scrollTop > SLACK,
|
|
12547
|
-
bottom: overflowY && maxTop - el.scrollTop > SLACK,
|
|
12548
|
-
start: overflowX && fromStart > SLACK,
|
|
12549
|
-
end: overflowX && maxLeft - fromStart > SLACK,
|
|
12546
|
+
top: overflowY && el.scrollTop > SLACK$1,
|
|
12547
|
+
bottom: overflowY && maxTop - el.scrollTop > SLACK$1,
|
|
12548
|
+
start: overflowX && fromStart > SLACK$1,
|
|
12549
|
+
end: overflowX && maxLeft - fromStart > SLACK$1,
|
|
12550
12550
|
},
|
|
12551
12551
|
barInline: layoutRef.current.barInline,
|
|
12552
12552
|
barBlock: layoutRef.current.barBlock,
|
|
@@ -15119,6 +15119,359 @@ const Affix = React.forwardRef(({ position = 'bottom-right', portal = false, cla
|
|
|
15119
15119
|
});
|
|
15120
15120
|
Affix.displayName = 'Affix';
|
|
15121
15121
|
|
|
15122
|
+
/**
|
|
15123
|
+
* How long the page has to stay still before a jump counts as over: long
|
|
15124
|
+
* enough to span the gap between two frames of a smooth scroll.
|
|
15125
|
+
*/
|
|
15126
|
+
const SETTLE_MS = 100;
|
|
15127
|
+
/**
|
|
15128
|
+
* A pixel of slack at the line, for a jump that lands a fraction of a pixel
|
|
15129
|
+
* away from it.
|
|
15130
|
+
*/
|
|
15131
|
+
const SLACK = 1;
|
|
15132
|
+
/** The page's `scroll-padding-top` in pixels, or 0 for `auto`. */
|
|
15133
|
+
function scrollPaddingTop() {
|
|
15134
|
+
const root = document.documentElement;
|
|
15135
|
+
const value = getComputedStyle(root).scrollPaddingTop;
|
|
15136
|
+
const number = parseFloat(value);
|
|
15137
|
+
if (!Number.isFinite(number))
|
|
15138
|
+
return 0;
|
|
15139
|
+
// A percentage is of the page's scrollport: the viewport less a classic
|
|
15140
|
+
// scrollbar.
|
|
15141
|
+
return value.endsWith('%') ? (number * root.clientHeight) / 100 : number;
|
|
15142
|
+
}
|
|
15143
|
+
/**
|
|
15144
|
+
* The height of the viewport the reader sees the page through, less a
|
|
15145
|
+
* classic scrollbar: the larger of the root's `clientHeight` and the visual
|
|
15146
|
+
* viewport's height. The visual viewport grows when a phone hides its
|
|
15147
|
+
* toolbar, where the root keeps the height it had with the toolbar shown;
|
|
15148
|
+
* under a pinch zoom it's the smaller, and the root's stands. Its `pageTop`
|
|
15149
|
+
* isn't read, since Firefox can leave it behind `scrollY` for a frame after
|
|
15150
|
+
* it puts back the scroll position on a reload.
|
|
15151
|
+
*/
|
|
15152
|
+
function viewportHeight() {
|
|
15153
|
+
return Math.max(document.documentElement.clientHeight, window.visualViewport?.height ?? 0);
|
|
15154
|
+
}
|
|
15155
|
+
/** Whether the page is scrolled as far down as it goes. */
|
|
15156
|
+
const atEnd = () => window.scrollY > 0 &&
|
|
15157
|
+
window.scrollY + viewportHeight() >=
|
|
15158
|
+
document.documentElement.scrollHeight - 1;
|
|
15159
|
+
/** Whether the browser draws an element: `display: none` draws no box. */
|
|
15160
|
+
const isDrawn = (element) => element.getClientRects().length > 0;
|
|
15161
|
+
/** The id a URL fragment names, such as `#caf%C3%A9`. */
|
|
15162
|
+
function fragmentId(hash) {
|
|
15163
|
+
const raw = hash.slice(1);
|
|
15164
|
+
try {
|
|
15165
|
+
return decodeURIComponent(raw);
|
|
15166
|
+
}
|
|
15167
|
+
catch {
|
|
15168
|
+
return raw;
|
|
15169
|
+
}
|
|
15170
|
+
}
|
|
15171
|
+
/**
|
|
15172
|
+
* Whether the page was opened afresh, as by a link, rather than reloaded or
|
|
15173
|
+
* reached by Back or Forward, where the browser puts it back where it was.
|
|
15174
|
+
* Where the navigation isn't recorded (jsdom), it counts as opened afresh.
|
|
15175
|
+
*/
|
|
15176
|
+
function openedAfresh() {
|
|
15177
|
+
const [entry] = performance.getEntriesByType?.('navigation') ?? [];
|
|
15178
|
+
const type = entry?.type;
|
|
15179
|
+
return type !== 'reload' && type !== 'back_forward';
|
|
15180
|
+
}
|
|
15181
|
+
/**
|
|
15182
|
+
* The link a click was on, if it's a plain click that follows it in this
|
|
15183
|
+
* page: the main button, no modifier key, and no `target` sending it
|
|
15184
|
+
* elsewhere. The path is read rather than the target, so a link inside a
|
|
15185
|
+
* shadow root counts.
|
|
15186
|
+
*/
|
|
15187
|
+
function followedLink(event) {
|
|
15188
|
+
if (event.button !== 0 ||
|
|
15189
|
+
event.metaKey ||
|
|
15190
|
+
event.ctrlKey ||
|
|
15191
|
+
event.shiftKey ||
|
|
15192
|
+
event.altKey) {
|
|
15193
|
+
return undefined;
|
|
15194
|
+
}
|
|
15195
|
+
const link = event
|
|
15196
|
+
.composedPath()
|
|
15197
|
+
.find((node) => node instanceof HTMLAnchorElement);
|
|
15198
|
+
return link && (!link.target || link.target === '_self') ? link : undefined;
|
|
15199
|
+
}
|
|
15200
|
+
/**
|
|
15201
|
+
* The `useScrollSpy` hook returns the id of the section being read: the last
|
|
15202
|
+
* of `ids` whose top is at or above the offset line, or the last one once the
|
|
15203
|
+
* page is scrolled to its end. After a jump to one of them, it's that one
|
|
15204
|
+
* until the reader scrolls on. `null` above the first section, on the server
|
|
15205
|
+
* and until the page hydrates.
|
|
15206
|
+
*
|
|
15207
|
+
* It checks once per frame while the page scrolls, and on resize. A jump is
|
|
15208
|
+
* a plain click on a link to one of the ids (the main button, no modifier
|
|
15209
|
+
* key, opening in this page), noted and never stopped, so the browser jumps
|
|
15210
|
+
* as it always does; and the `#id` a page opened afresh opens at. Back,
|
|
15211
|
+
* Forward and a reload aren't jumps. That `#id` is read once, when `ids`
|
|
15212
|
+
* first has some: a section rendered after that isn't held, even if the
|
|
15213
|
+
* page opened at it, and the reading rule marks where the page is. Once the
|
|
15214
|
+
* page has been still for a
|
|
15215
|
+
* moment, the section stays current only if it's where a jump puts it: its
|
|
15216
|
+
* top at the line, give or take a shift of the layout smaller than its own
|
|
15217
|
+
* height, or, at the end of the page, on screen below the line. Otherwise,
|
|
15218
|
+
* as after a flick right after the click, the reading rule takes over. The
|
|
15219
|
+
* next scroll after that lets it go, the browser's own included, so when
|
|
15220
|
+
* content loading above it makes the browser scroll to keep the reader's
|
|
15221
|
+
* place, the rule takes over too.
|
|
15222
|
+
*
|
|
15223
|
+
* A section that isn't drawn (`display: none`) is never current. A section
|
|
15224
|
+
* that moves while nothing scrolls is seen at the next scroll or resize. It
|
|
15225
|
+
* watches the document only: not a panel that scrolls inside it, and not
|
|
15226
|
+
* sections inside a shadow root, which an `#id` link can't reach either.
|
|
15227
|
+
* Whether a page was opened afresh is read from how the document loaded, so
|
|
15228
|
+
* in an app that changes routes on the client, a route change can briefly
|
|
15229
|
+
* mark the address bar's section until the page settles, when the reading
|
|
15230
|
+
* rule takes over, and after a reload a route change to an `#id` holds
|
|
15231
|
+
* nothing.
|
|
15232
|
+
*
|
|
15233
|
+
* @function
|
|
15234
|
+
* @param {UseScrollSpyOptions} options - The ids to watch, the offset, and a change callback.
|
|
15235
|
+
* @returns {string | null} The current section's id, or `null`.
|
|
15236
|
+
*
|
|
15237
|
+
* @example
|
|
15238
|
+
* const current = useScrollSpy({ ids: ['profile', 'security', 'billing'] });
|
|
15239
|
+
*/
|
|
15240
|
+
function useScrollSpy({ ids, offset, onCurrentChange, }) {
|
|
15241
|
+
const [active, setActive] = React.useState(null);
|
|
15242
|
+
// What was last reported, kept across a re-subscribe so the same id isn't
|
|
15243
|
+
// reported twice.
|
|
15244
|
+
const reportedRef = React.useRef(null);
|
|
15245
|
+
// The section a jump went to. It belongs to the hook rather than to one run
|
|
15246
|
+
// of the effect, so new ids, a new offset or React's development double run
|
|
15247
|
+
// don't lose it, and the `#id` the page opened at is read once, when the
|
|
15248
|
+
// ids first include some.
|
|
15249
|
+
const heldRef = React.useRef(null);
|
|
15250
|
+
const openedRef = React.useRef(false);
|
|
15251
|
+
const onCurrentChangeRef = useLatestRef(onCurrentChange);
|
|
15252
|
+
// The ids as a value, so a new array with the same ids each render doesn't
|
|
15253
|
+
// re-subscribe.
|
|
15254
|
+
const key = JSON.stringify(ids);
|
|
15255
|
+
const fixedOffset = typeof offset === 'number' && Number.isFinite(offset) ? offset : undefined;
|
|
15256
|
+
React.useEffect(() => {
|
|
15257
|
+
const watched = JSON.parse(key);
|
|
15258
|
+
let line = fixedOffset ?? scrollPaddingTop();
|
|
15259
|
+
let settled = true;
|
|
15260
|
+
let settleTimer;
|
|
15261
|
+
let frame = 0;
|
|
15262
|
+
const report = (id) => {
|
|
15263
|
+
if (id === reportedRef.current)
|
|
15264
|
+
return;
|
|
15265
|
+
reportedRef.current = id;
|
|
15266
|
+
setActive(id);
|
|
15267
|
+
onCurrentChangeRef.current?.(id);
|
|
15268
|
+
};
|
|
15269
|
+
/** The element for one of the ids, if it's on the page and drawn. */
|
|
15270
|
+
const sectionOf = (id) => {
|
|
15271
|
+
const element = watched.includes(id) ? document.getElementById(id) : null;
|
|
15272
|
+
return element && isDrawn(element) ? element : null;
|
|
15273
|
+
};
|
|
15274
|
+
/**
|
|
15275
|
+
* Whether a section is where a jump puts it: its top at the line, give or
|
|
15276
|
+
* take a shift of the layout smaller than the section's own height, or,
|
|
15277
|
+
* at the end of the page, as near as the page lets it come, on screen
|
|
15278
|
+
* below the line.
|
|
15279
|
+
*/
|
|
15280
|
+
const atJump = (id) => {
|
|
15281
|
+
const element = sectionOf(id);
|
|
15282
|
+
if (!element)
|
|
15283
|
+
return false;
|
|
15284
|
+
const { top, bottom, height } = element.getBoundingClientRect();
|
|
15285
|
+
if (top <= line + height && bottom >= line)
|
|
15286
|
+
return true;
|
|
15287
|
+
return top > line && top < viewportHeight() && atEnd();
|
|
15288
|
+
};
|
|
15289
|
+
const check = () => {
|
|
15290
|
+
if (heldRef.current !== null)
|
|
15291
|
+
return report(heldRef.current);
|
|
15292
|
+
const drawn = [];
|
|
15293
|
+
for (const id of watched) {
|
|
15294
|
+
const element = document.getElementById(id);
|
|
15295
|
+
if (element && isDrawn(element))
|
|
15296
|
+
drawn.push([id, element]);
|
|
15297
|
+
}
|
|
15298
|
+
let current = null;
|
|
15299
|
+
if (atEnd()) {
|
|
15300
|
+
// A short last section can't reach the line, so the end of the page
|
|
15301
|
+
// stands in for it.
|
|
15302
|
+
if (drawn.length)
|
|
15303
|
+
current = drawn[drawn.length - 1][0];
|
|
15304
|
+
}
|
|
15305
|
+
else {
|
|
15306
|
+
for (const [id, element] of drawn) {
|
|
15307
|
+
if (element.getBoundingClientRect().top <= line + SLACK)
|
|
15308
|
+
current = id;
|
|
15309
|
+
}
|
|
15310
|
+
}
|
|
15311
|
+
report(current);
|
|
15312
|
+
};
|
|
15313
|
+
/** Runs the check in the next frame, once however many events ask. */
|
|
15314
|
+
const schedule = () => {
|
|
15315
|
+
if (frame)
|
|
15316
|
+
return;
|
|
15317
|
+
frame = requestAnimationFrame(() => {
|
|
15318
|
+
frame = 0;
|
|
15319
|
+
check();
|
|
15320
|
+
});
|
|
15321
|
+
};
|
|
15322
|
+
/**
|
|
15323
|
+
* Starts, or starts again, the wait for the page to be still, when a
|
|
15324
|
+
* held section that isn't where a jump puts it is let go.
|
|
15325
|
+
*/
|
|
15326
|
+
const waitToSettle = () => {
|
|
15327
|
+
settled = false;
|
|
15328
|
+
clearTimeout(settleTimer);
|
|
15329
|
+
settleTimer = setTimeout(() => {
|
|
15330
|
+
settled = true;
|
|
15331
|
+
if (heldRef.current !== null && !atJump(heldRef.current)) {
|
|
15332
|
+
heldRef.current = null;
|
|
15333
|
+
}
|
|
15334
|
+
check();
|
|
15335
|
+
}, SETTLE_MS);
|
|
15336
|
+
};
|
|
15337
|
+
/** Holds the section a jump goes to, if it's one of these on the page. */
|
|
15338
|
+
const hold = (id) => {
|
|
15339
|
+
if (!sectionOf(id))
|
|
15340
|
+
return;
|
|
15341
|
+
heldRef.current = id;
|
|
15342
|
+
waitToSettle();
|
|
15343
|
+
};
|
|
15344
|
+
const onScroll = () => {
|
|
15345
|
+
if (heldRef.current !== null) {
|
|
15346
|
+
// Once the page has been still, the next scroll lets the jump go;
|
|
15347
|
+
// until then, each scroll pushes the end of the jump back.
|
|
15348
|
+
if (settled)
|
|
15349
|
+
heldRef.current = null;
|
|
15350
|
+
else
|
|
15351
|
+
waitToSettle();
|
|
15352
|
+
}
|
|
15353
|
+
schedule();
|
|
15354
|
+
};
|
|
15355
|
+
const onResize = () => {
|
|
15356
|
+
line = fixedOffset ?? scrollPaddingTop();
|
|
15357
|
+
schedule();
|
|
15358
|
+
};
|
|
15359
|
+
const onClick = (event) => {
|
|
15360
|
+
const link = followedLink(event);
|
|
15361
|
+
if (link?.hash &&
|
|
15362
|
+
link.href.split('#')[0] === window.location.href.split('#')[0]) {
|
|
15363
|
+
hold(fragmentId(link.hash));
|
|
15364
|
+
check();
|
|
15365
|
+
}
|
|
15366
|
+
};
|
|
15367
|
+
if (!openedRef.current) {
|
|
15368
|
+
// Ids built from content fetched after mount arrive later, so the read
|
|
15369
|
+
// waits until there are some.
|
|
15370
|
+
if (watched.length) {
|
|
15371
|
+
openedRef.current = true;
|
|
15372
|
+
if (openedAfresh())
|
|
15373
|
+
hold(fragmentId(window.location.hash));
|
|
15374
|
+
}
|
|
15375
|
+
}
|
|
15376
|
+
else if (heldRef.current !== null) {
|
|
15377
|
+
// A jump held across new ids carries on if its section still counts.
|
|
15378
|
+
if (sectionOf(heldRef.current))
|
|
15379
|
+
waitToSettle();
|
|
15380
|
+
else
|
|
15381
|
+
heldRef.current = null;
|
|
15382
|
+
}
|
|
15383
|
+
check();
|
|
15384
|
+
window.addEventListener('scroll', onScroll, { passive: true });
|
|
15385
|
+
window.addEventListener('resize', onResize);
|
|
15386
|
+
document.addEventListener('click', onClick, {
|
|
15387
|
+
capture: true,
|
|
15388
|
+
passive: true,
|
|
15389
|
+
});
|
|
15390
|
+
return () => {
|
|
15391
|
+
cancelAnimationFrame(frame);
|
|
15392
|
+
clearTimeout(settleTimer);
|
|
15393
|
+
window.removeEventListener('scroll', onScroll);
|
|
15394
|
+
window.removeEventListener('resize', onResize);
|
|
15395
|
+
document.removeEventListener('click', onClick, { capture: true });
|
|
15396
|
+
};
|
|
15397
|
+
}, [key, fixedOffset, onCurrentChangeRef]);
|
|
15398
|
+
return active;
|
|
15399
|
+
}
|
|
15400
|
+
|
|
15401
|
+
/** Every id in the tree, in page order. */
|
|
15402
|
+
const idsOf = (items) => items.flatMap(item => [item.id, ...idsOf(item.children ?? [])]);
|
|
15403
|
+
/**
|
|
15404
|
+
* The entries as `MenuItem` links, each entry's own entries in a nested
|
|
15405
|
+
* `MenuList`, and the current one marked.
|
|
15406
|
+
*/
|
|
15407
|
+
const entries = (items, active) => items.map(item => {
|
|
15408
|
+
const current = item.id === active;
|
|
15409
|
+
return (jsxRuntime.jsxs(MenuItem, { href: `#${encodeURIComponent(item.id)}`, active: current, "aria-current": current ? 'location' : undefined, children: [item.label, item.children?.length ? (jsxRuntime.jsx(MenuList, { children: entries(item.children, active) })) : null] }, item.id));
|
|
15410
|
+
});
|
|
15411
|
+
/**
|
|
15412
|
+
* The `TableOfContents` component lists a long page's sections as links in Bulma's menu look, and marks the one being read.
|
|
15413
|
+
*
|
|
15414
|
+
* It's a `<nav>` named by its title, holding `MenuList` and `MenuItem` links
|
|
15415
|
+
* to each `#id`, rendered in full on the server. `useScrollSpy` picks the
|
|
15416
|
+
* current section, and its link alone gets `is-active` and
|
|
15417
|
+
* `aria-current="location"`. Use the hook on its own to mark the section in
|
|
15418
|
+
* view somewhere else, such as an app's nav `Menu`.
|
|
15419
|
+
*
|
|
15420
|
+
* @function
|
|
15421
|
+
* @param {TableOfContentsProps} props - Props for the TableOfContents component.
|
|
15422
|
+
* @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<nav>`.
|
|
15423
|
+
* @returns {JSX.Element} The rendered table of contents.
|
|
15424
|
+
*
|
|
15425
|
+
* @example
|
|
15426
|
+
* <TableOfContents
|
|
15427
|
+
* isSticky
|
|
15428
|
+
* items={[
|
|
15429
|
+
* { id: 'install', label: 'Install' },
|
|
15430
|
+
* { id: 'usage', label: 'Usage', children: [{ id: 'props', label: 'Props' }] },
|
|
15431
|
+
* ]}
|
|
15432
|
+
* />
|
|
15433
|
+
*/
|
|
15434
|
+
const TableOfContents = React.forwardRef(({ className, textColor, bgColor, items, onCurrentChange, offset, isSticky, labels, ...props }, ref) => {
|
|
15435
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
15436
|
+
color: textColor,
|
|
15437
|
+
backgroundColor: bgColor,
|
|
15438
|
+
...props,
|
|
15439
|
+
});
|
|
15440
|
+
const titleId = React.useId();
|
|
15441
|
+
const active = useScrollSpy({ ids: idsOf(items), offset, onCurrentChange });
|
|
15442
|
+
const navRef = React.useRef(null);
|
|
15443
|
+
const setRefs = React.useCallback((node) => {
|
|
15444
|
+
navRef.current = node;
|
|
15445
|
+
if (typeof ref === 'function')
|
|
15446
|
+
return ref(node);
|
|
15447
|
+
if (ref)
|
|
15448
|
+
ref.current = node;
|
|
15449
|
+
}, [ref]);
|
|
15450
|
+
// A list taller than the room it has scrolls itself to keep the current
|
|
15451
|
+
// link in view. Only the list moves: scrollIntoView would move the page
|
|
15452
|
+
// too whenever the link is off screen.
|
|
15453
|
+
React.useEffect(() => {
|
|
15454
|
+
const nav = navRef.current;
|
|
15455
|
+
const link = nav.querySelector('a[aria-current="location"]');
|
|
15456
|
+
if (!link || nav.scrollHeight <= nav.clientHeight)
|
|
15457
|
+
return;
|
|
15458
|
+
const top = nav.getBoundingClientRect().top + nav.clientTop;
|
|
15459
|
+
const bottom = top + nav.clientHeight;
|
|
15460
|
+
const at = link.getBoundingClientRect();
|
|
15461
|
+
if (at.top < top)
|
|
15462
|
+
nav.scrollTop -= top - at.top;
|
|
15463
|
+
else if (at.bottom > bottom)
|
|
15464
|
+
nav.scrollTop += at.bottom - bottom;
|
|
15465
|
+
}, [active]);
|
|
15466
|
+
// Bulma's menu, which draws it, and its own class beside it.
|
|
15467
|
+
const menuClass = usePrefixedClassNames('menu');
|
|
15468
|
+
const rootClass = usePrefixedClassNames('table-of-contents', {
|
|
15469
|
+
'is-sticky': !!isSticky,
|
|
15470
|
+
});
|
|
15471
|
+
return (jsxRuntime.jsxs("nav", { ref: setRefs, className: classNames(menuClass, rootClass, bulmaHelperClasses, className), "aria-labelledby": titleId, ...rest, children: [jsxRuntime.jsx(MenuLabel, { id: titleId, children: labels?.title || 'On this page' }), jsxRuntime.jsx(MenuList, { children: entries(items, active) })] }));
|
|
15472
|
+
});
|
|
15473
|
+
TableOfContents.displayName = 'TableOfContents';
|
|
15474
|
+
|
|
15122
15475
|
/**
|
|
15123
15476
|
* The `Block` component renders a simple container with Bulma's `.block` class, adding vertical margin between sections of content.
|
|
15124
15477
|
*
|
|
@@ -22386,6 +22739,167 @@ const TextAreaBase = React.forwardRef(({ color, size, isRounded, isStatic, isHov
|
|
|
22386
22739
|
});
|
|
22387
22740
|
TextAreaBase.displayName = 'TextAreaBase';
|
|
22388
22741
|
|
|
22742
|
+
/** An eye, drawn while the password is hidden. */
|
|
22743
|
+
const EyeIcon = () => (jsxRuntime.jsx("svg", { viewBox: "0 0 16 16", width: "1.25em", height: "1.25em", "aria-hidden": "true", children: jsxRuntime.jsxs("g", { fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntime.jsx("path", { d: "M1.5 8s2.5-4.5 6.5-4.5 6.5 4.5 6.5 4.5-2.5 4.5-6.5 4.5S1.5 8 1.5 8z" }), jsxRuntime.jsx("circle", { cx: "8", cy: "8", r: "2" })] }) }));
|
|
22744
|
+
/** The eye crossed out, drawn while the password shows. */
|
|
22745
|
+
const EyeOffIcon = () => (jsxRuntime.jsx("svg", { viewBox: "0 0 16 16", width: "1.25em", height: "1.25em", "aria-hidden": "true", children: jsxRuntime.jsxs("g", { fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntime.jsx("path", { d: "M1.5 8s2.5-4.5 6.5-4.5 6.5 4.5 6.5 4.5-2.5 4.5-6.5 4.5S1.5 8 1.5 8z" }), jsxRuntime.jsx("circle", { cx: "8", cy: "8", r: "2" }), jsxRuntime.jsx("path", { d: "M2.5 2.5l11 11" })] }) }));
|
|
22746
|
+
/**
|
|
22747
|
+
* Warns in development when the `text` toggle's name doesn't contain the
|
|
22748
|
+
* word on it, which a voice control user says to press it.
|
|
22749
|
+
*/
|
|
22750
|
+
const checkLabelInName = (name, word) => {
|
|
22751
|
+
if (name.toLocaleLowerCase().includes(word.toLocaleLowerCase()))
|
|
22752
|
+
return;
|
|
22753
|
+
warnOnce(`PasswordInput:label-in-name:${name}:${word}`, `[bestax-bulma] A PasswordInput with variant="text" is named ` +
|
|
22754
|
+
`"${name}" while its button says "${word}". A voice control user ` +
|
|
22755
|
+
`says the word they see, so the name has to contain it (WCAG 2.5.3, ` +
|
|
22756
|
+
`Label in Name). Set labels so each name contains its word.`);
|
|
22757
|
+
};
|
|
22758
|
+
/**
|
|
22759
|
+
* The raw PasswordInput: a password field with a button inside it that shows what was typed, without Field/Control wrapping.
|
|
22760
|
+
*
|
|
22761
|
+
* Use `PasswordInput` for the convenience wrapper.
|
|
22762
|
+
*
|
|
22763
|
+
* @function
|
|
22764
|
+
* @param {PasswordInputBaseProps} props - Props for PasswordInputBase.
|
|
22765
|
+
* @param {React.Ref<HTMLInputElement>} ref - Forwarded ref to the `<input>`.
|
|
22766
|
+
* @returns {JSX.Element} The input and its toggle, in a container.
|
|
22767
|
+
*/
|
|
22768
|
+
const PasswordInputBase = React.forwardRef((props, ref) => {
|
|
22769
|
+
const { visible: visibleProp, defaultVisible = false, onVisibleChange, variant = 'icon', labels, autoComplete = 'current-password', spellCheck = false, autoCorrect = 'off', autoCapitalize = 'none', disabled: disabledProp, color, className, style, ...others } = props;
|
|
22770
|
+
// Helper props, `className` and `style` go on the container, so a margin,
|
|
22771
|
+
// a width or a display moves the field and its toggle together. `color`
|
|
22772
|
+
// stays with the input, whose border it colors.
|
|
22773
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses(others);
|
|
22774
|
+
const disabled = useFieldsetDisabled(disabledProp);
|
|
22775
|
+
// The toggle gives way to the loading spinner of a Control this sits in,
|
|
22776
|
+
// which takes its slot, and the field hides the password meanwhile, as
|
|
22777
|
+
// there's no button to hide it with.
|
|
22778
|
+
const controlLoading = useControlLoading();
|
|
22779
|
+
const isText = variant === 'text';
|
|
22780
|
+
const showPassword = labels?.showPassword || 'Show password';
|
|
22781
|
+
const hidePassword = labels?.hidePassword || 'Hide password';
|
|
22782
|
+
const show = labels?.show || 'Show';
|
|
22783
|
+
const hide = labels?.hide || 'Hide';
|
|
22784
|
+
if (isText) {
|
|
22785
|
+
checkLabelInName(showPassword, show);
|
|
22786
|
+
checkLabelInName(hidePassword, hide);
|
|
22787
|
+
}
|
|
22788
|
+
const isControlled = visibleProp !== undefined;
|
|
22789
|
+
const [internalVisible, setInternalVisible] = React.useState(defaultVisible);
|
|
22790
|
+
const visible = isControlled ? visibleProp : internalVisible;
|
|
22791
|
+
// Whether the field shows now: as last reported, until `visible` changes.
|
|
22792
|
+
// Two hide events before React renders, as from an effect, or a caller
|
|
22793
|
+
// holding `visible` that keeps it `true`, report `false` once.
|
|
22794
|
+
const shownRef = React.useRef(visible);
|
|
22795
|
+
useClientLayoutEffect(() => {
|
|
22796
|
+
shownRef.current = visible;
|
|
22797
|
+
}, [visible]);
|
|
22798
|
+
const setVisible = (next) => {
|
|
22799
|
+
shownRef.current = next;
|
|
22800
|
+
if (!isControlled)
|
|
22801
|
+
setInternalVisible(next);
|
|
22802
|
+
onVisibleChange?.(next);
|
|
22803
|
+
};
|
|
22804
|
+
const inputRef = React.useRef(null);
|
|
22805
|
+
// A caller's callback ref may return a cleanup, as React 19 allows. It's
|
|
22806
|
+
// held here and run on detach in place of a call with `null`, on React 18
|
|
22807
|
+
// as well, since this ref is the one React calls.
|
|
22808
|
+
const refCleanup = React.useRef(null);
|
|
22809
|
+
const combinedRef = React.useCallback((node) => {
|
|
22810
|
+
inputRef.current = node;
|
|
22811
|
+
if (typeof ref === 'function') {
|
|
22812
|
+
if (node === null) {
|
|
22813
|
+
const cleanup = refCleanup.current;
|
|
22814
|
+
refCleanup.current = null;
|
|
22815
|
+
if (cleanup)
|
|
22816
|
+
cleanup();
|
|
22817
|
+
else
|
|
22818
|
+
ref(null);
|
|
22819
|
+
return;
|
|
22820
|
+
}
|
|
22821
|
+
const cleanup = ref(node);
|
|
22822
|
+
refCleanup.current =
|
|
22823
|
+
typeof cleanup === 'function' ? cleanup : null;
|
|
22824
|
+
}
|
|
22825
|
+
else if (ref) {
|
|
22826
|
+
ref.current = node;
|
|
22827
|
+
}
|
|
22828
|
+
}, [ref]);
|
|
22829
|
+
// A toggle with focus that leaves while the input stays, as it does when a
|
|
22830
|
+
// Control starts loading, hands focus to the input rather than dropping it
|
|
22831
|
+
// to the page. React lets go of the toggle before it takes it out of the
|
|
22832
|
+
// page, so the toggle still has focus here; when the whole field unmounts,
|
|
22833
|
+
// React has let go of the input first.
|
|
22834
|
+
const toggleRef = React.useRef(null);
|
|
22835
|
+
const setToggle = React.useCallback((node) => {
|
|
22836
|
+
// React lets go only of a toggle it handed over, so there is one.
|
|
22837
|
+
const leaving = toggleRef.current;
|
|
22838
|
+
toggleRef.current = node;
|
|
22839
|
+
if (node)
|
|
22840
|
+
return;
|
|
22841
|
+
const root = leaving.getRootNode();
|
|
22842
|
+
if (root.activeElement === leaving)
|
|
22843
|
+
inputRef.current?.focus();
|
|
22844
|
+
}, []);
|
|
22845
|
+
// Hides the password again: reports `false`, and sets its own state when it
|
|
22846
|
+
// holds it. Inside `flushSync`, React renders that, a caller's own state
|
|
22847
|
+
// update included, before the listener returns, so the browser reads the
|
|
22848
|
+
// form, or saves the page's form state, from a password field. The
|
|
22849
|
+
// component never sets the field's type itself, so a caller holding
|
|
22850
|
+
// `visible` that keeps it `true` keeps the field shown. The one catch: an
|
|
22851
|
+
// event fired while React is rendering or committing, which includes
|
|
22852
|
+
// `useEffect` and `useLayoutEffect` callbacks (a `requestSubmit()` from
|
|
22853
|
+
// an effect), can't flush there, so React warns in development and the
|
|
22854
|
+
// hide lands after the form is read. Only the listeners below call it,
|
|
22855
|
+
// while the input is mounted.
|
|
22856
|
+
const hideRef = useLatestRef(() => {
|
|
22857
|
+
if (!shownRef.current)
|
|
22858
|
+
return;
|
|
22859
|
+
// eslint-disable-next-line @eslint-react/dom-no-flush-sync -- the form is read, or the page saved, before React would render this on its own
|
|
22860
|
+
reactDom.flushSync(() => setVisible(false));
|
|
22861
|
+
});
|
|
22862
|
+
// When the form it belongs to submits or resets: whichever form that is
|
|
22863
|
+
// when it happens, so a form mounted after the field, or mounted again,
|
|
22864
|
+
// counts. Neither event leaves a shadow root, so it listens on the field's
|
|
22865
|
+
// own root, the document or the shadow root it renders in, and in the
|
|
22866
|
+
// capture phase, before any listener on the form.
|
|
22867
|
+
React.useEffect(() => {
|
|
22868
|
+
const input = inputRef.current;
|
|
22869
|
+
const root = input.getRootNode();
|
|
22870
|
+
const onFormEvent = (event) => {
|
|
22871
|
+
if (event.target === input.form)
|
|
22872
|
+
hideRef.current();
|
|
22873
|
+
};
|
|
22874
|
+
root.addEventListener('submit', onFormEvent, true);
|
|
22875
|
+
root.addEventListener('reset', onFormEvent, true);
|
|
22876
|
+
return () => {
|
|
22877
|
+
root.removeEventListener('submit', onFormEvent, true);
|
|
22878
|
+
root.removeEventListener('reset', onFormEvent, true);
|
|
22879
|
+
};
|
|
22880
|
+
}, [hideRef]);
|
|
22881
|
+
// When the page is left, so the history the browser keeps of the form
|
|
22882
|
+
// holds a password field rather than text. It fires on the way into the
|
|
22883
|
+
// back-forward cache too, so a page coming back from there is hidden.
|
|
22884
|
+
React.useEffect(() => {
|
|
22885
|
+
const onPageHide = () => hideRef.current();
|
|
22886
|
+
window.addEventListener('pagehide', onPageHide);
|
|
22887
|
+
return () => window.removeEventListener('pagehide', onPageHide);
|
|
22888
|
+
}, [hideRef]);
|
|
22889
|
+
const containerClass = usePrefixedClassNames('password-input-container', {
|
|
22890
|
+
'is-text': isText,
|
|
22891
|
+
});
|
|
22892
|
+
const toggleClass = usePrefixedClassNames('password-input-toggle');
|
|
22893
|
+
// The icon keeps one name and reports its state; the words change, so
|
|
22894
|
+
// the name follows them and there's no state to report.
|
|
22895
|
+
const toggleName = isText && visible ? hidePassword : showPassword;
|
|
22896
|
+
const glyph = visible ? jsxRuntime.jsx(EyeOffIcon, {}) : jsxRuntime.jsx(EyeIcon, {});
|
|
22897
|
+
return (jsxRuntime.jsxs("div", { className: classNames(containerClass, bulmaHelperClasses, className), style: style, children: [jsxRuntime.jsx(InputBase, { ref: combinedRef, autoComplete: autoComplete, spellCheck: spellCheck, autoCorrect: autoCorrect, autoCapitalize: autoCapitalize, disabled: disabled, color: color, ...rest, type: visible && !controlLoading ? 'text' : 'password' }), !controlLoading && (jsxRuntime.jsx("button", { ref: setToggle, type: "button", className: toggleClass, "aria-label": toggleName, "aria-pressed": isText ? undefined : visible, disabled: disabled,
|
|
22898
|
+
// A pointer press leaves focus, and the caret, in the input.
|
|
22899
|
+
onMouseDown: event => event.preventDefault(), onClick: () => setVisible(!visible), children: isText ? (visible ? hide : show) : glyph }))] }));
|
|
22900
|
+
});
|
|
22901
|
+
PasswordInputBase.displayName = 'PasswordInputBase';
|
|
22902
|
+
|
|
22389
22903
|
/**
|
|
22390
22904
|
* The `Select` component provides a Bulma-styled dropdown for selecting one or more options.
|
|
22391
22905
|
*
|
|
@@ -22520,6 +23034,97 @@ fieldClassName, controlClassName,
|
|
|
22520
23034
|
});
|
|
22521
23035
|
TextArea.displayName = 'TextArea';
|
|
22522
23036
|
|
|
23037
|
+
/** The right-icon props, which the toggle's place leaves no room for. */
|
|
23038
|
+
const RIGHT_ICON_PROPS = [
|
|
23039
|
+
'iconRight',
|
|
23040
|
+
'iconRightName',
|
|
23041
|
+
'iconRightSize',
|
|
23042
|
+
'hasIconsRight',
|
|
23043
|
+
];
|
|
23044
|
+
/**
|
|
23045
|
+
* The `PasswordInput` component is a password field with a button inside it that shows what was typed.
|
|
23046
|
+
*
|
|
23047
|
+
* By default the button is an eye named "Show password" that reports
|
|
23048
|
+
* whether the password shows with `aria-pressed`; `variant="text"` makes it
|
|
23049
|
+
* the words "Show" and "Hide". The field asks for
|
|
23050
|
+
* `autocomplete="current-password"` and no spellcheck, and hides the
|
|
23051
|
+
* password again when its form submits.
|
|
23052
|
+
*
|
|
23053
|
+
* @function
|
|
23054
|
+
* @param {PasswordInputProps} props - Props for PasswordInput.
|
|
23055
|
+
* @returns {JSX.Element} The composed field element.
|
|
23056
|
+
*
|
|
23057
|
+
* @example
|
|
23058
|
+
* <PasswordInput label="Password" />
|
|
23059
|
+
*
|
|
23060
|
+
* @example
|
|
23061
|
+
* <PasswordInput label="New password" autoComplete="new-password" variant="text" />
|
|
23062
|
+
*/
|
|
23063
|
+
const PasswordInput = React.forwardRef((props, ref) => {
|
|
23064
|
+
const {
|
|
23065
|
+
// Field props
|
|
23066
|
+
label, labelSize, labelProps, horizontal,
|
|
23067
|
+
// Control props
|
|
23068
|
+
iconLeft, iconLeftName, iconLeftSize, hasIconsLeft, isLoading, isExpanded, controlSize,
|
|
23069
|
+
// Message props
|
|
23070
|
+
message, messageColor,
|
|
23071
|
+
// Container class overrides
|
|
23072
|
+
fieldClassName, controlClassName,
|
|
23073
|
+
// Not offered, and not passed on
|
|
23074
|
+
iconRight: _iconRight, iconRightName: _iconRightName, iconRightSize: _iconRightSize, hasIconsRight: _hasIconsRight,
|
|
23075
|
+
// Everything else goes to the base
|
|
23076
|
+
...baseProps } = props;
|
|
23077
|
+
const rightIcon = RIGHT_ICON_PROPS.filter(prop => props[prop]);
|
|
23078
|
+
if (rightIcon.length > 0) {
|
|
23079
|
+
const names = rightIcon.join(' ');
|
|
23080
|
+
warnOnce(`PasswordInput:right-icon:${names}`, `[bestax-bulma] <PasswordInput ${names}> draws no right icon: the ` +
|
|
23081
|
+
`visibility toggle sits at that edge. Leave ` +
|
|
23082
|
+
`${rightIcon.length > 1 ? 'them' : 'it'} out, or use a left icon.`);
|
|
23083
|
+
}
|
|
23084
|
+
const insideField = useInsideField();
|
|
23085
|
+
const insideControl = useInsideControl();
|
|
23086
|
+
const ownField = rendersOwnField('PasswordInput', {
|
|
23087
|
+
insideField,
|
|
23088
|
+
insideControl,
|
|
23089
|
+
label,
|
|
23090
|
+
message,
|
|
23091
|
+
horizontal,
|
|
23092
|
+
fieldClassName,
|
|
23093
|
+
});
|
|
23094
|
+
const ownControl = rendersOwnControl('PasswordInput', {
|
|
23095
|
+
insideControl,
|
|
23096
|
+
isLoading,
|
|
23097
|
+
iconLeft,
|
|
23098
|
+
iconLeftName,
|
|
23099
|
+
iconLeftSize,
|
|
23100
|
+
hasIconsLeft,
|
|
23101
|
+
isExpanded,
|
|
23102
|
+
controlSize,
|
|
23103
|
+
controlClassName,
|
|
23104
|
+
});
|
|
23105
|
+
const { controlId, fieldLabelProps } = useAutoLabelId({
|
|
23106
|
+
label,
|
|
23107
|
+
id: baseProps.id,
|
|
23108
|
+
labelProps,
|
|
23109
|
+
rendersLabel: ownField,
|
|
23110
|
+
});
|
|
23111
|
+
const helpClass = usePrefixedClassNames('help', {
|
|
23112
|
+
[`is-${messageColor}`]: !!messageColor,
|
|
23113
|
+
});
|
|
23114
|
+
// The base hides its toggle while the Control it sits in is loading,
|
|
23115
|
+
// whether that is the one rendered below or an enclosing one.
|
|
23116
|
+
let content = (jsxRuntime.jsx(PasswordInputBase, { ref: ref, ...baseProps, id: controlId }));
|
|
23117
|
+
if (ownControl) {
|
|
23118
|
+
content = (jsxRuntime.jsx(Control, { iconLeft: iconLeft, iconLeftName: iconLeftName, iconLeftSize: iconLeftSize, hasIconsLeft: hasIconsLeft, isLoading: isLoading, isExpanded: isExpanded, size: controlSize, className: controlClassName, children: content }));
|
|
23119
|
+
}
|
|
23120
|
+
const messageEl = message ? jsxRuntime.jsx("p", { className: helpClass, children: message }) : null;
|
|
23121
|
+
if (ownField) {
|
|
23122
|
+
return (jsxRuntime.jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [content, messageEl] }));
|
|
23123
|
+
}
|
|
23124
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [content, messageEl] }));
|
|
23125
|
+
});
|
|
23126
|
+
PasswordInput.displayName = 'PasswordInput';
|
|
23127
|
+
|
|
22523
23128
|
function isSameDay(a, b) {
|
|
22524
23129
|
return (a.getFullYear() === b.getFullYear() &&
|
|
22525
23130
|
a.getMonth() === b.getMonth() &&
|
|
@@ -29930,6 +30535,8 @@ exports.PanelIcon = PanelIcon;
|
|
|
29930
30535
|
exports.PanelInputBlock = PanelInputBlock;
|
|
29931
30536
|
exports.PanelTabs = PanelTabs;
|
|
29932
30537
|
exports.Paragraph = Paragraph;
|
|
30538
|
+
exports.PasswordInput = PasswordInput;
|
|
30539
|
+
exports.PasswordInputBase = PasswordInputBase;
|
|
29933
30540
|
exports.PinInput = PinInput;
|
|
29934
30541
|
exports.Popover = Popover;
|
|
29935
30542
|
exports.PopoverBody = PopoverBody;
|
|
@@ -29973,6 +30580,7 @@ exports.TabContentItem = TabContentItem;
|
|
|
29973
30580
|
exports.TabItem = TabItem;
|
|
29974
30581
|
exports.TabList = TabList;
|
|
29975
30582
|
exports.Table = Table;
|
|
30583
|
+
exports.TableOfContents = TableOfContents;
|
|
29976
30584
|
exports.Tabs = Tabs;
|
|
29977
30585
|
exports.TabsContent = TabsContent;
|
|
29978
30586
|
exports.Tag = Tag;
|
|
@@ -30034,6 +30642,7 @@ exports.useNumberFormat = useNumberFormat;
|
|
|
30034
30642
|
exports.useOtherClasses = useOtherClasses;
|
|
30035
30643
|
exports.usePrefixedClass = usePrefixedClass;
|
|
30036
30644
|
exports.usePrefixedClassNames = usePrefixedClassNames;
|
|
30645
|
+
exports.useScrollSpy = useScrollSpy;
|
|
30037
30646
|
exports.useSpacingClasses = useSpacingClasses;
|
|
30038
30647
|
exports.useTypographyClasses = useTypographyClasses;
|
|
30039
30648
|
exports.useVisibilityClasses = useVisibilityClasses;
|