@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.
Files changed (44) hide show
  1. package/dist/bestax.css +1 -1
  2. package/dist/bestax.css.map +1 -1
  3. package/dist/esm/components/ScrollArea.js +7 -7
  4. package/dist/esm/components/ScrollArea.js.map +1 -1
  5. package/dist/esm/components/TableOfContents.js +85 -0
  6. package/dist/esm/components/TableOfContents.js.map +1 -0
  7. package/dist/esm/form/PasswordInput.js +105 -0
  8. package/dist/esm/form/PasswordInput.js.map +1 -0
  9. package/dist/esm/form/PasswordInputBase.js +177 -0
  10. package/dist/esm/form/PasswordInputBase.js.map +1 -0
  11. package/dist/esm/helpers/useScrollSpy.js +286 -0
  12. package/dist/esm/helpers/useScrollSpy.js.map +1 -0
  13. package/dist/extras.css +1 -1
  14. package/dist/extras.css.map +1 -1
  15. package/dist/index.cjs +616 -7
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.esm.js +4 -0
  18. package/dist/index.esm.js.map +1 -1
  19. package/dist/types/components/TableOfContents.d.ts +87 -0
  20. package/dist/types/form/PasswordInput.d.ts +98 -0
  21. package/dist/types/form/PasswordInputBase.d.ts +91 -0
  22. package/dist/types/helpers/useScrollSpy.d.ts +61 -0
  23. package/dist/types/index.d.ts +5 -0
  24. package/dist/types-cjs/components/TableOfContents.d.ts +87 -0
  25. package/dist/types-cjs/form/PasswordInput.d.ts +98 -0
  26. package/dist/types-cjs/form/PasswordInputBase.d.ts +91 -0
  27. package/dist/types-cjs/helpers/useScrollSpy.d.ts +61 -0
  28. package/dist/types-cjs/index.d.ts +5 -0
  29. package/dist/versions/bestax-no-dark-mode.css +1 -1
  30. package/dist/versions/bestax-no-dark-mode.css.map +1 -1
  31. package/dist/versions/bestax-no-helpers-prefixed.css +1 -1
  32. package/dist/versions/bestax-no-helpers-prefixed.css.map +1 -1
  33. package/dist/versions/bestax-no-helpers.css +1 -1
  34. package/dist/versions/bestax-no-helpers.css.map +1 -1
  35. package/dist/versions/bestax-prefixed.css +1 -1
  36. package/dist/versions/bestax-prefixed.css.map +1 -1
  37. package/package.json +1 -1
  38. package/src/scss/CLAUDE.md +3 -0
  39. package/src/scss/_input-trigger.scss +76 -0
  40. package/src/scss/components/_index.scss +1 -0
  41. package/src/scss/components/_tableofcontents.scss +42 -0
  42. package/src/scss/form/_index.scss +1 -0
  43. package/src/scss/form/_passwordinput.scss +112 -0
  44. package/src/scss/form/_picker-popover.scss +24 -63
@@ -11,7 +11,7 @@ import { warnOnce } from '../helpers/devWarnings.js';
11
11
  const EDGES = ['top', 'bottom', 'start', 'end'];
12
12
  // Layout can land a fraction of a pixel short of an edge, so anything closer
13
13
  // than this counts as there.
14
- const SLACK = 1;
14
+ const SLACK$1 = 1;
15
15
  const sameMeasure = (a, b) => !!a &&
16
16
  a.overflowing === b.overflowing &&
17
17
  a.barInline === b.barInline &&
@@ -92,8 +92,8 @@ const ScrollArea = forwardRef(({ className, style, textColor, bgColor, height, m
92
92
  const scrollsX = orientation !== 'vertical';
93
93
  const maxTop = el.scrollHeight - el.clientHeight;
94
94
  const maxLeft = el.scrollWidth - el.clientWidth;
95
- const overflowY = scrollsY && maxTop > SLACK;
96
- const overflowX = scrollsX && maxLeft > SLACK;
95
+ const overflowY = scrollsY && maxTop > SLACK$1;
96
+ const overflowX = scrollsX && maxLeft > SLACK$1;
97
97
  // Right to left, scrollLeft runs from 0 at the start to negative.
98
98
  const fromStart = layoutRef.current.rtl
99
99
  ? -el.scrollLeft
@@ -101,10 +101,10 @@ const ScrollArea = forwardRef(({ className, style, textColor, bgColor, height, m
101
101
  const next = {
102
102
  overflowing: overflowY || overflowX,
103
103
  edges: {
104
- top: overflowY && el.scrollTop > SLACK,
105
- bottom: overflowY && maxTop - el.scrollTop > SLACK,
106
- start: overflowX && fromStart > SLACK,
107
- end: overflowX && maxLeft - fromStart > SLACK,
104
+ top: overflowY && el.scrollTop > SLACK$1,
105
+ bottom: overflowY && maxTop - el.scrollTop > SLACK$1,
106
+ start: overflowX && fromStart > SLACK$1,
107
+ end: overflowX && maxLeft - fromStart > SLACK$1,
108
108
  },
109
109
  barInline: layoutRef.current.barInline,
110
110
  barBlock: layoutRef.current.barBlock,
@@ -1 +1 @@
1
- {"version":3,"file":"ScrollArea.js","sources":["../../../src/components/ScrollArea.tsx"],"sourcesContent":["import React, {\n forwardRef,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from 'react';\nimport { classNames, usePrefixedClassNames } from '../helpers/classNames';\nimport {\n useBulmaClasses,\n BulmaClassesProps,\n validColors,\n} from '../helpers/useBulmaClasses';\nimport { useClientLayoutEffect } from '../helpers/useClientLayoutEffect';\nimport { useLatestRef } from '../helpers/useLatestRef';\nimport { warnOnce } from '../helpers/devWarnings';\n\n/** Which way the content scrolls. */\nexport type ScrollAreaOrientation = 'vertical' | 'horizontal' | 'both';\n\n/** When the scrollbars show. */\nexport type ScrollAreaScrollbars = 'auto' | 'hover' | 'always';\n\n/** An edge of the content: the block's top and bottom, the line's start and end. */\nexport type ScrollAreaEdge = 'top' | 'bottom' | 'start' | 'end';\n\nconst EDGES: readonly ScrollAreaEdge[] = ['top', 'bottom', 'start', 'end'];\n\n// Layout can land a fraction of a pixel short of an edge, so anything closer\n// than this counts as there.\nconst SLACK = 1;\n\n/**\n * Props for the ScrollArea component.\n *\n * @helperProps The helper props go on the outer `<div>`, the `.scroll-area`\n * frame, with `className` and `style`. The `ref`, the name, `onScroll` and\n * the other attributes go on the `<div>` inside it, the viewport that\n * scrolls.\n *\n * @extraProp {React.Ref<HTMLDivElement>} [ref] - Ref forwarded to the viewport, the element that scrolls, for `scrollTo` and the like.\n * @extraProp {string} [aria-label] - Names the region, such as \"Release notes\". It needs a name, so pass this or `aria-labelledby`, and a development build warns when it has neither. The name is rendered with the region's role, while the content overflows, since ARIA doesn't allow naming a plain `div`.\n * @extraProp {string} [aria-labelledby] - The id of a visible heading that names the region, in place of `aria-label`.\n * @extraProp {React.UIEventHandler<HTMLDivElement>} [onScroll] - Called as the viewport scrolls.\n */\nexport interface ScrollAreaProps\n extends\n Omit<React.HTMLAttributes<HTMLDivElement>, 'color' | 'role' | 'tabIndex'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /** Additional CSS classes for the outer frame. */\n className?: string;\n /** Inline styles for the outer frame. */\n style?: React.CSSProperties;\n /** What scrolls. */\n children?: React.ReactNode;\n /**\n * The area's height. A number is pixels, and a string is any CSS length\n * (`\"20rem\"`, `\"50vh\"`, `\"100%\"`).\n */\n height?: number | string;\n /**\n * The height it grows to with its content, then scrolls past. A number is\n * pixels. Use it in place of `height` for an area that's only as tall as\n * it needs to be.\n */\n maxHeight?: number | string;\n /**\n * Which way it scrolls. `vertical` and `horizontal` clip the other axis,\n * and `both` scrolls whichever overflows.\n */\n orientation?: ScrollAreaOrientation;\n /**\n * When the scrollbars show. `auto`: whenever the content overflows, the\n * way the system shows them. `hover`: colorless until the pointer is over\n * the area or focus is in it, which hides that the area scrolls until\n * then, so keep it for places where the layout makes that plain.\n * `always`: on every axis it scrolls, even when the content fits. Where the\n * system draws its bars over the content (macOS by default, phones and\n * tablets), they still show only while you scroll, and in forced colors\n * they always show.\n */\n showScrollbars?: ScrollAreaScrollbars;\n /**\n * Keeps room for the vertical scrollbar whether or not it shows, so the\n * content doesn't shift when it starts to overflow. It does nothing where\n * the system draws its bars over the content.\n */\n hasStableGutter?: boolean;\n /**\n * Fades the content out toward each edge that has more past it, without\n * fading the scrollbar. It's off in forced colors and when the user asks\n * for more contrast.\n */\n hasFade?: boolean;\n /**\n * Called with the edge when scrolling reaches it, once each time it\n * arrives, such as to load older messages at the top. It isn't called for\n * the edge the area starts at, or when the content changes size.\n */\n onEdgeReach?: (edge: ScrollAreaEdge) => void;\n /** Text color helper. */\n textColor?: (typeof validColors)[number] | 'inherit' | 'current';\n /** Background color helper. */\n bgColor?: (typeof validColors)[number] | 'inherit' | 'current';\n}\n\n/** What a measurement of the viewport found. */\ninterface Measured {\n /** Overflows on an axis it scrolls. */\n overflowing: boolean;\n /** Each edge that has more content past it. */\n edges: Record<ScrollAreaEdge, boolean>;\n /** The vertical scrollbar's width, and the horizontal one's height. */\n barInline: number;\n barBlock: number;\n}\n\nconst sameMeasure = (a: Measured | null, b: Measured): boolean =>\n !!a &&\n a.overflowing === b.overflowing &&\n a.barInline === b.barInline &&\n a.barBlock === b.barBlock &&\n EDGES.every(edge => a.edges[edge] === b.edges[edge]);\n\n/**\n * The `ScrollArea` component renders a box whose content scrolls inside it, on the browser's own scrollbars in Bulma's colors.\n *\n * The bars are the system's, made thin and colored from Bulma's tokens, so\n * they follow dark mode and keep the system's own settings. While the\n * content overflows, the viewport is a region you can tab to and scroll by\n * keyboard, Safari included, named by `aria-label` or `aria-labelledby`.\n * While it fits, it's a plain box. Setting the bars' colors turns off any\n * `::-webkit-scrollbar` styles inside it.\n *\n * @function\n * @param {ScrollAreaProps} props - Props for the ScrollArea component.\n * @param {React.Ref<HTMLDivElement>} ref - Forwarded ref to the viewport.\n * @returns {JSX.Element} The rendered scroll area.\n *\n * @example\n * <ScrollArea height={240} aria-label=\"Release notes\">\n * <Content>…</Content>\n * </ScrollArea>\n *\n * @example\n * <ScrollArea maxHeight=\"20rem\" aria-label=\"Messages\" hasFade>\n * …\n * </ScrollArea>\n */\nexport const ScrollArea = forwardRef<HTMLDivElement, ScrollAreaProps>(\n (\n {\n className,\n style,\n textColor,\n bgColor,\n height,\n maxHeight,\n orientation = 'both',\n showScrollbars = 'auto',\n hasStableGutter,\n hasFade,\n onEdgeReach,\n onScroll,\n onFocus,\n onBlur,\n children,\n ...props\n },\n ref\n ) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses({\n color: textColor,\n backgroundColor: bgColor,\n ...props,\n });\n\n const frameClass = usePrefixedClassNames('scroll-area', {\n 'is-vertical': orientation === 'vertical',\n 'is-horizontal': orientation === 'horizontal',\n 'has-hover-scrollbars': showScrollbars === 'hover',\n 'has-visible-scrollbars': showScrollbars === 'always',\n 'has-stable-gutter': !!hasStableGutter,\n 'has-fade': !!hasFade,\n });\n const viewportClass = usePrefixedClassNames('scroll-area-viewport');\n\n const named = Boolean(rest['aria-label'] || rest['aria-labelledby']);\n useEffect(() => {\n if (!named) {\n warnOnce(\n 'ScrollArea:unnamed',\n '[bestax-bulma] <ScrollArea> has no accessible name. Pass ' +\n 'aria-label or aria-labelledby, so a screen reader can say what ' +\n 'took focus when the area overflows.'\n );\n }\n }, [named]);\n\n const viewportRef = useRef<HTMLDivElement | null>(null);\n const setRefs = useCallback(\n (node: HTMLDivElement | null) => {\n viewportRef.current = node;\n if (typeof ref === 'function') return ref(node);\n if (ref) ref.current = node;\n },\n [ref]\n );\n\n // Nothing is known about the overflow until the client measures it, so\n // the server renders a plain scroller and hydrates as one.\n const [measured, setMeasured] = useState<Measured | null>(null);\n const [focused, setFocused] = useState(false);\n // The last measurement, read by the scroll handler to see which edges\n // it just reached, and the direction and bar sizes, which only change\n // with layout, so a scroll doesn't read computed styles.\n const lastRef = useRef<Measured | null>(null);\n const layoutRef = useRef({ rtl: false, barInline: 0, barBlock: 0 });\n const onEdgeReachRef = useLatestRef(onEdgeReach);\n\n const measure = useCallback(\n (fromScroll: boolean) => {\n const el = viewportRef.current;\n /* istanbul ignore next: measuring only runs while it's mounted */\n if (!el) return;\n const scrollsY = orientation !== 'horizontal';\n const scrollsX = orientation !== 'vertical';\n const maxTop = el.scrollHeight - el.clientHeight;\n const maxLeft = el.scrollWidth - el.clientWidth;\n const overflowY = scrollsY && maxTop > SLACK;\n const overflowX = scrollsX && maxLeft > SLACK;\n // Right to left, scrollLeft runs from 0 at the start to negative.\n const fromStart = layoutRef.current.rtl\n ? -el.scrollLeft\n : el.scrollLeft;\n const next: Measured = {\n overflowing: overflowY || overflowX,\n edges: {\n top: overflowY && el.scrollTop > SLACK,\n bottom: overflowY && maxTop - el.scrollTop > SLACK,\n start: overflowX && fromStart > SLACK,\n end: overflowX && maxLeft - fromStart > SLACK,\n },\n barInline: layoutRef.current.barInline,\n barBlock: layoutRef.current.barBlock,\n };\n const last = lastRef.current;\n lastRef.current = next;\n if (fromScroll && last) {\n for (const edge of EDGES) {\n if (last.edges[edge] && !next.edges[edge]) {\n onEdgeReachRef.current?.(edge);\n }\n }\n }\n setMeasured(prev => (sameMeasure(prev, next) ? prev : next));\n },\n [orientation, onEdgeReachRef]\n );\n\n // Layout: the direction and the bars' sizes, then the overflow. It runs\n // before paint, and again whenever the viewport or anything in it\n // changes size, or the children change.\n useClientLayoutEffect(() => {\n const el = viewportRef.current;\n /* istanbul ignore next: the effect only runs once it's mounted */\n if (!el) return;\n const relayout = () => {\n const cs = getComputedStyle(el);\n const px = (value: string) => parseFloat(value) || 0;\n layoutRef.current = {\n rtl: cs.direction === 'rtl',\n barInline: Math.max(\n 0,\n el.offsetWidth -\n el.clientWidth -\n px(cs.borderLeftWidth) -\n px(cs.borderRightWidth)\n ),\n barBlock: Math.max(\n 0,\n el.offsetHeight -\n el.clientHeight -\n px(cs.borderTopWidth) -\n px(cs.borderBottomWidth)\n ),\n };\n measure(false);\n };\n relayout();\n if (typeof ResizeObserver === 'undefined') return;\n const resizes = new ResizeObserver(relayout);\n const observeAll = () => {\n resizes.disconnect();\n resizes.observe(el);\n for (const child of Array.from(el.children)) resizes.observe(child);\n };\n observeAll();\n // Content that changes can overflow before any size is reported: a\n // child added or taken away, or text React rewrites in place, which\n // resizes no element when the area's height is fixed, and inline\n // children, which a ResizeObserver doesn't report at all.\n const mutations =\n typeof MutationObserver === 'undefined'\n ? null\n : new MutationObserver(records => {\n if (\n records.some(\n record => record.target === el && record.type === 'childList'\n )\n ) {\n observeAll();\n }\n relayout();\n });\n mutations?.observe(el, {\n childList: true,\n characterData: true,\n subtree: true,\n });\n return () => {\n resizes.disconnect();\n mutations?.disconnect();\n };\n }, [measure]);\n\n const handleScroll = (event: React.UIEvent<HTMLDivElement>) => {\n onScroll?.(event);\n measure(true);\n };\n const handleFocus = (event: React.FocusEvent<HTMLDivElement>) => {\n onFocus?.(event);\n if (event.target === event.currentTarget) setFocused(true);\n };\n const handleBlur = (event: React.FocusEvent<HTMLDivElement>) => {\n onBlur?.(event);\n if (event.target === event.currentTarget) setFocused(false);\n };\n\n // A tab stop while it overflows. Taking tabIndex off the focused element\n // would drop focus to the page, so a viewport that stops overflowing\n // keeps it until focus leaves.\n const isRegion = !!measured?.overflowing || focused;\n // The name goes with the role: ARIA prohibits naming a plain div.\n const {\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledby,\n ...viewportRest\n } = rest;\n const edgeAttributes: Record<string, string> = {};\n if (measured) {\n for (const edge of EDGES) {\n if (measured.edges[edge]) edgeAttributes[`data-overflow-${edge}`] = '';\n }\n }\n const viewportStyle = measured\n ? ({\n '--scroll-area-bar-inline-size': `${measured.barInline}px`,\n '--scroll-area-bar-block-size': `${measured.barBlock}px`,\n } as React.CSSProperties)\n : undefined;\n\n return (\n <div\n className={classNames(frameClass, bulmaHelperClasses, className)}\n style={\n height !== undefined || maxHeight !== undefined\n ? {\n ...style,\n // Only what's given, so `maxHeight` alone keeps a height\n // the style sets.\n ...(height !== undefined && { height }),\n ...(maxHeight !== undefined && { maxHeight }),\n }\n : style\n }\n >\n <div\n ref={setRefs}\n className={viewportClass}\n style={viewportStyle}\n {...viewportRest}\n {...edgeAttributes}\n role={isRegion ? 'region' : undefined}\n aria-label={isRegion ? ariaLabel : undefined}\n aria-labelledby={isRegion ? ariaLabelledby : undefined}\n tabIndex={isRegion ? 0 : undefined}\n onScroll={handleScroll}\n onFocus={handleFocus}\n onBlur={handleBlur}\n >\n {children}\n </div>\n </div>\n );\n }\n);\n\nScrollArea.displayName = 'ScrollArea';\n\nexport default ScrollArea;\n"],"names":["_jsx"],"mappings":"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"}
1
+ {"version":3,"file":"ScrollArea.js","sources":["../../../src/components/ScrollArea.tsx"],"sourcesContent":["import React, {\n forwardRef,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from 'react';\nimport { classNames, usePrefixedClassNames } from '../helpers/classNames';\nimport {\n useBulmaClasses,\n BulmaClassesProps,\n validColors,\n} from '../helpers/useBulmaClasses';\nimport { useClientLayoutEffect } from '../helpers/useClientLayoutEffect';\nimport { useLatestRef } from '../helpers/useLatestRef';\nimport { warnOnce } from '../helpers/devWarnings';\n\n/** Which way the content scrolls. */\nexport type ScrollAreaOrientation = 'vertical' | 'horizontal' | 'both';\n\n/** When the scrollbars show. */\nexport type ScrollAreaScrollbars = 'auto' | 'hover' | 'always';\n\n/** An edge of the content: the block's top and bottom, the line's start and end. */\nexport type ScrollAreaEdge = 'top' | 'bottom' | 'start' | 'end';\n\nconst EDGES: readonly ScrollAreaEdge[] = ['top', 'bottom', 'start', 'end'];\n\n// Layout can land a fraction of a pixel short of an edge, so anything closer\n// than this counts as there.\nconst SLACK = 1;\n\n/**\n * Props for the ScrollArea component.\n *\n * @helperProps The helper props go on the outer `<div>`, the `.scroll-area`\n * frame, with `className` and `style`. The `ref`, the name, `onScroll` and\n * the other attributes go on the `<div>` inside it, the viewport that\n * scrolls.\n *\n * @extraProp {React.Ref<HTMLDivElement>} [ref] - Ref forwarded to the viewport, the element that scrolls, for `scrollTo` and the like.\n * @extraProp {string} [aria-label] - Names the region, such as \"Release notes\". It needs a name, so pass this or `aria-labelledby`, and a development build warns when it has neither. The name is rendered with the region's role, while the content overflows, since ARIA doesn't allow naming a plain `div`.\n * @extraProp {string} [aria-labelledby] - The id of a visible heading that names the region, in place of `aria-label`.\n * @extraProp {React.UIEventHandler<HTMLDivElement>} [onScroll] - Called as the viewport scrolls.\n */\nexport interface ScrollAreaProps\n extends\n Omit<React.HTMLAttributes<HTMLDivElement>, 'color' | 'role' | 'tabIndex'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /** Additional CSS classes for the outer frame. */\n className?: string;\n /** Inline styles for the outer frame. */\n style?: React.CSSProperties;\n /** What scrolls. */\n children?: React.ReactNode;\n /**\n * The area's height. A number is pixels, and a string is any CSS length\n * (`\"20rem\"`, `\"50vh\"`, `\"100%\"`).\n */\n height?: number | string;\n /**\n * The height it grows to with its content, then scrolls past. A number is\n * pixels. Use it in place of `height` for an area that's only as tall as\n * it needs to be.\n */\n maxHeight?: number | string;\n /**\n * Which way it scrolls. `vertical` and `horizontal` clip the other axis,\n * and `both` scrolls whichever overflows.\n */\n orientation?: ScrollAreaOrientation;\n /**\n * When the scrollbars show. `auto`: whenever the content overflows, the\n * way the system shows them. `hover`: colorless until the pointer is over\n * the area or focus is in it, which hides that the area scrolls until\n * then, so keep it for places where the layout makes that plain.\n * `always`: on every axis it scrolls, even when the content fits. Where the\n * system draws its bars over the content (macOS by default, phones and\n * tablets), they still show only while you scroll, and in forced colors\n * they always show.\n */\n showScrollbars?: ScrollAreaScrollbars;\n /**\n * Keeps room for the vertical scrollbar whether or not it shows, so the\n * content doesn't shift when it starts to overflow. It does nothing where\n * the system draws its bars over the content.\n */\n hasStableGutter?: boolean;\n /**\n * Fades the content out toward each edge that has more past it, without\n * fading the scrollbar. It's off in forced colors and when the user asks\n * for more contrast.\n */\n hasFade?: boolean;\n /**\n * Called with the edge when scrolling reaches it, once each time it\n * arrives, such as to load older messages at the top. It isn't called for\n * the edge the area starts at, or when the content changes size.\n */\n onEdgeReach?: (edge: ScrollAreaEdge) => void;\n /** Text color helper. */\n textColor?: (typeof validColors)[number] | 'inherit' | 'current';\n /** Background color helper. */\n bgColor?: (typeof validColors)[number] | 'inherit' | 'current';\n}\n\n/** What a measurement of the viewport found. */\ninterface Measured {\n /** Overflows on an axis it scrolls. */\n overflowing: boolean;\n /** Each edge that has more content past it. */\n edges: Record<ScrollAreaEdge, boolean>;\n /** The vertical scrollbar's width, and the horizontal one's height. */\n barInline: number;\n barBlock: number;\n}\n\nconst sameMeasure = (a: Measured | null, b: Measured): boolean =>\n !!a &&\n a.overflowing === b.overflowing &&\n a.barInline === b.barInline &&\n a.barBlock === b.barBlock &&\n EDGES.every(edge => a.edges[edge] === b.edges[edge]);\n\n/**\n * The `ScrollArea` component renders a box whose content scrolls inside it, on the browser's own scrollbars in Bulma's colors.\n *\n * The bars are the system's, made thin and colored from Bulma's tokens, so\n * they follow dark mode and keep the system's own settings. While the\n * content overflows, the viewport is a region you can tab to and scroll by\n * keyboard, Safari included, named by `aria-label` or `aria-labelledby`.\n * While it fits, it's a plain box. Setting the bars' colors turns off any\n * `::-webkit-scrollbar` styles inside it.\n *\n * @function\n * @param {ScrollAreaProps} props - Props for the ScrollArea component.\n * @param {React.Ref<HTMLDivElement>} ref - Forwarded ref to the viewport.\n * @returns {JSX.Element} The rendered scroll area.\n *\n * @example\n * <ScrollArea height={240} aria-label=\"Release notes\">\n * <Content>…</Content>\n * </ScrollArea>\n *\n * @example\n * <ScrollArea maxHeight=\"20rem\" aria-label=\"Messages\" hasFade>\n * …\n * </ScrollArea>\n */\nexport const ScrollArea = forwardRef<HTMLDivElement, ScrollAreaProps>(\n (\n {\n className,\n style,\n textColor,\n bgColor,\n height,\n maxHeight,\n orientation = 'both',\n showScrollbars = 'auto',\n hasStableGutter,\n hasFade,\n onEdgeReach,\n onScroll,\n onFocus,\n onBlur,\n children,\n ...props\n },\n ref\n ) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses({\n color: textColor,\n backgroundColor: bgColor,\n ...props,\n });\n\n const frameClass = usePrefixedClassNames('scroll-area', {\n 'is-vertical': orientation === 'vertical',\n 'is-horizontal': orientation === 'horizontal',\n 'has-hover-scrollbars': showScrollbars === 'hover',\n 'has-visible-scrollbars': showScrollbars === 'always',\n 'has-stable-gutter': !!hasStableGutter,\n 'has-fade': !!hasFade,\n });\n const viewportClass = usePrefixedClassNames('scroll-area-viewport');\n\n const named = Boolean(rest['aria-label'] || rest['aria-labelledby']);\n useEffect(() => {\n if (!named) {\n warnOnce(\n 'ScrollArea:unnamed',\n '[bestax-bulma] <ScrollArea> has no accessible name. Pass ' +\n 'aria-label or aria-labelledby, so a screen reader can say what ' +\n 'took focus when the area overflows.'\n );\n }\n }, [named]);\n\n const viewportRef = useRef<HTMLDivElement | null>(null);\n const setRefs = useCallback(\n (node: HTMLDivElement | null) => {\n viewportRef.current = node;\n if (typeof ref === 'function') return ref(node);\n if (ref) ref.current = node;\n },\n [ref]\n );\n\n // Nothing is known about the overflow until the client measures it, so\n // the server renders a plain scroller and hydrates as one.\n const [measured, setMeasured] = useState<Measured | null>(null);\n const [focused, setFocused] = useState(false);\n // The last measurement, read by the scroll handler to see which edges\n // it just reached, and the direction and bar sizes, which only change\n // with layout, so a scroll doesn't read computed styles.\n const lastRef = useRef<Measured | null>(null);\n const layoutRef = useRef({ rtl: false, barInline: 0, barBlock: 0 });\n const onEdgeReachRef = useLatestRef(onEdgeReach);\n\n const measure = useCallback(\n (fromScroll: boolean) => {\n const el = viewportRef.current;\n /* istanbul ignore next: measuring only runs while it's mounted */\n if (!el) return;\n const scrollsY = orientation !== 'horizontal';\n const scrollsX = orientation !== 'vertical';\n const maxTop = el.scrollHeight - el.clientHeight;\n const maxLeft = el.scrollWidth - el.clientWidth;\n const overflowY = scrollsY && maxTop > SLACK;\n const overflowX = scrollsX && maxLeft > SLACK;\n // Right to left, scrollLeft runs from 0 at the start to negative.\n const fromStart = layoutRef.current.rtl\n ? -el.scrollLeft\n : el.scrollLeft;\n const next: Measured = {\n overflowing: overflowY || overflowX,\n edges: {\n top: overflowY && el.scrollTop > SLACK,\n bottom: overflowY && maxTop - el.scrollTop > SLACK,\n start: overflowX && fromStart > SLACK,\n end: overflowX && maxLeft - fromStart > SLACK,\n },\n barInline: layoutRef.current.barInline,\n barBlock: layoutRef.current.barBlock,\n };\n const last = lastRef.current;\n lastRef.current = next;\n if (fromScroll && last) {\n for (const edge of EDGES) {\n if (last.edges[edge] && !next.edges[edge]) {\n onEdgeReachRef.current?.(edge);\n }\n }\n }\n setMeasured(prev => (sameMeasure(prev, next) ? prev : next));\n },\n [orientation, onEdgeReachRef]\n );\n\n // Layout: the direction and the bars' sizes, then the overflow. It runs\n // before paint, and again whenever the viewport or anything in it\n // changes size, or the children change.\n useClientLayoutEffect(() => {\n const el = viewportRef.current;\n /* istanbul ignore next: the effect only runs once it's mounted */\n if (!el) return;\n const relayout = () => {\n const cs = getComputedStyle(el);\n const px = (value: string) => parseFloat(value) || 0;\n layoutRef.current = {\n rtl: cs.direction === 'rtl',\n barInline: Math.max(\n 0,\n el.offsetWidth -\n el.clientWidth -\n px(cs.borderLeftWidth) -\n px(cs.borderRightWidth)\n ),\n barBlock: Math.max(\n 0,\n el.offsetHeight -\n el.clientHeight -\n px(cs.borderTopWidth) -\n px(cs.borderBottomWidth)\n ),\n };\n measure(false);\n };\n relayout();\n if (typeof ResizeObserver === 'undefined') return;\n const resizes = new ResizeObserver(relayout);\n const observeAll = () => {\n resizes.disconnect();\n resizes.observe(el);\n for (const child of Array.from(el.children)) resizes.observe(child);\n };\n observeAll();\n // Content that changes can overflow before any size is reported: a\n // child added or taken away, or text React rewrites in place, which\n // resizes no element when the area's height is fixed, and inline\n // children, which a ResizeObserver doesn't report at all.\n const mutations =\n typeof MutationObserver === 'undefined'\n ? null\n : new MutationObserver(records => {\n if (\n records.some(\n record => record.target === el && record.type === 'childList'\n )\n ) {\n observeAll();\n }\n relayout();\n });\n mutations?.observe(el, {\n childList: true,\n characterData: true,\n subtree: true,\n });\n return () => {\n resizes.disconnect();\n mutations?.disconnect();\n };\n }, [measure]);\n\n const handleScroll = (event: React.UIEvent<HTMLDivElement>) => {\n onScroll?.(event);\n measure(true);\n };\n const handleFocus = (event: React.FocusEvent<HTMLDivElement>) => {\n onFocus?.(event);\n if (event.target === event.currentTarget) setFocused(true);\n };\n const handleBlur = (event: React.FocusEvent<HTMLDivElement>) => {\n onBlur?.(event);\n if (event.target === event.currentTarget) setFocused(false);\n };\n\n // A tab stop while it overflows. Taking tabIndex off the focused element\n // would drop focus to the page, so a viewport that stops overflowing\n // keeps it until focus leaves.\n const isRegion = !!measured?.overflowing || focused;\n // The name goes with the role: ARIA prohibits naming a plain div.\n const {\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledby,\n ...viewportRest\n } = rest;\n const edgeAttributes: Record<string, string> = {};\n if (measured) {\n for (const edge of EDGES) {\n if (measured.edges[edge]) edgeAttributes[`data-overflow-${edge}`] = '';\n }\n }\n const viewportStyle = measured\n ? ({\n '--scroll-area-bar-inline-size': `${measured.barInline}px`,\n '--scroll-area-bar-block-size': `${measured.barBlock}px`,\n } as React.CSSProperties)\n : undefined;\n\n return (\n <div\n className={classNames(frameClass, bulmaHelperClasses, className)}\n style={\n height !== undefined || maxHeight !== undefined\n ? {\n ...style,\n // Only what's given, so `maxHeight` alone keeps a height\n // the style sets.\n ...(height !== undefined && { height }),\n ...(maxHeight !== undefined && { maxHeight }),\n }\n : style\n }\n >\n <div\n ref={setRefs}\n className={viewportClass}\n style={viewportStyle}\n {...viewportRest}\n {...edgeAttributes}\n role={isRegion ? 'region' : undefined}\n aria-label={isRegion ? ariaLabel : undefined}\n aria-labelledby={isRegion ? ariaLabelledby : undefined}\n tabIndex={isRegion ? 0 : undefined}\n onScroll={handleScroll}\n onFocus={handleFocus}\n onBlur={handleBlur}\n >\n {children}\n </div>\n </div>\n );\n }\n);\n\nScrollArea.displayName = 'ScrollArea';\n\nexport default ScrollArea;\n"],"names":["SLACK","_jsx"],"mappings":"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"}
@@ -0,0 +1,85 @@
1
+ /* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
2
+ 'use client';
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+ import { forwardRef, useId, useRef, useCallback, useEffect } from 'react';
5
+ import { usePrefixedClassNames, classNames } from '../helpers/classNames.js';
6
+ import { useBulmaClasses } from '../helpers/useBulmaClasses.js';
7
+ import { useScrollSpy } from '../helpers/useScrollSpy.js';
8
+ import { MenuLabel, MenuList, MenuItem } from './Menu.js';
9
+
10
+ /** Every id in the tree, in page order. */
11
+ const idsOf = (items) => items.flatMap(item => [item.id, ...idsOf(item.children ?? [])]);
12
+ /**
13
+ * The entries as `MenuItem` links, each entry's own entries in a nested
14
+ * `MenuList`, and the current one marked.
15
+ */
16
+ const entries = (items, active) => items.map(item => {
17
+ const current = item.id === active;
18
+ return (jsxs(MenuItem, { href: `#${encodeURIComponent(item.id)}`, active: current, "aria-current": current ? 'location' : undefined, children: [item.label, item.children?.length ? (jsx(MenuList, { children: entries(item.children, active) })) : null] }, item.id));
19
+ });
20
+ /**
21
+ * The `TableOfContents` component lists a long page's sections as links in Bulma's menu look, and marks the one being read.
22
+ *
23
+ * It's a `<nav>` named by its title, holding `MenuList` and `MenuItem` links
24
+ * to each `#id`, rendered in full on the server. `useScrollSpy` picks the
25
+ * current section, and its link alone gets `is-active` and
26
+ * `aria-current="location"`. Use the hook on its own to mark the section in
27
+ * view somewhere else, such as an app's nav `Menu`.
28
+ *
29
+ * @function
30
+ * @param {TableOfContentsProps} props - Props for the TableOfContents component.
31
+ * @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<nav>`.
32
+ * @returns {JSX.Element} The rendered table of contents.
33
+ *
34
+ * @example
35
+ * <TableOfContents
36
+ * isSticky
37
+ * items={[
38
+ * { id: 'install', label: 'Install' },
39
+ * { id: 'usage', label: 'Usage', children: [{ id: 'props', label: 'Props' }] },
40
+ * ]}
41
+ * />
42
+ */
43
+ const TableOfContents = forwardRef(({ className, textColor, bgColor, items, onCurrentChange, offset, isSticky, labels, ...props }, ref) => {
44
+ const { bulmaHelperClasses, rest } = useBulmaClasses({
45
+ color: textColor,
46
+ backgroundColor: bgColor,
47
+ ...props,
48
+ });
49
+ const titleId = useId();
50
+ const active = useScrollSpy({ ids: idsOf(items), offset, onCurrentChange });
51
+ const navRef = useRef(null);
52
+ const setRefs = useCallback((node) => {
53
+ navRef.current = node;
54
+ if (typeof ref === 'function')
55
+ return ref(node);
56
+ if (ref)
57
+ ref.current = node;
58
+ }, [ref]);
59
+ // A list taller than the room it has scrolls itself to keep the current
60
+ // link in view. Only the list moves: scrollIntoView would move the page
61
+ // too whenever the link is off screen.
62
+ useEffect(() => {
63
+ const nav = navRef.current;
64
+ const link = nav.querySelector('a[aria-current="location"]');
65
+ if (!link || nav.scrollHeight <= nav.clientHeight)
66
+ return;
67
+ const top = nav.getBoundingClientRect().top + nav.clientTop;
68
+ const bottom = top + nav.clientHeight;
69
+ const at = link.getBoundingClientRect();
70
+ if (at.top < top)
71
+ nav.scrollTop -= top - at.top;
72
+ else if (at.bottom > bottom)
73
+ nav.scrollTop += at.bottom - bottom;
74
+ }, [active]);
75
+ // Bulma's menu, which draws it, and its own class beside it.
76
+ const menuClass = usePrefixedClassNames('menu');
77
+ const rootClass = usePrefixedClassNames('table-of-contents', {
78
+ 'is-sticky': !!isSticky,
79
+ });
80
+ return (jsxs("nav", { ref: setRefs, className: classNames(menuClass, rootClass, bulmaHelperClasses, className), "aria-labelledby": titleId, ...rest, children: [jsx(MenuLabel, { id: titleId, children: labels?.title || 'On this page' }), jsx(MenuList, { children: entries(items, active) })] }));
81
+ });
82
+ TableOfContents.displayName = 'TableOfContents';
83
+
84
+ export { TableOfContents, useScrollSpy };
85
+ //# sourceMappingURL=TableOfContents.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TableOfContents.js","sources":["../../../src/components/TableOfContents.tsx"],"sourcesContent":["import React, {\n forwardRef,\n useCallback,\n useEffect,\n useId,\n useRef,\n} from 'react';\nimport { classNames, usePrefixedClassNames } from '../helpers/classNames';\nimport {\n useBulmaClasses,\n BulmaClassesProps,\n validColors,\n} from '../helpers/useBulmaClasses';\nimport { useScrollSpy } from '../helpers/useScrollSpy';\nimport { MenuItem, MenuLabel, MenuList } from './Menu';\n\nexport { useScrollSpy } from '../helpers/useScrollSpy';\nexport type { UseScrollSpyOptions } from '../helpers/useScrollSpy';\n\n/** One entry in a TableOfContents. */\nexport interface TableOfContentsItem {\n /** **Required.** The `id` of the section or heading it links to, without the `#`. Unique on the page. */\n id: string;\n /** **Required.** What the link says, usually the heading's own text. */\n label: React.ReactNode;\n /** Entries under this one, drawn as a nested list. */\n children?: TableOfContentsItem[];\n}\n\n/** The visible strings, in your app's language. */\nexport interface TableOfContentsLabels {\n /**\n * The list's visible title, which also names its `<nav>`.\n * @defaultValue 'On this page'\n */\n title?: string;\n}\n\n/**\n * Props for the TableOfContents component. The helper props, the other\n * attributes and the `ref` land on the `<nav>`.\n *\n * @extraProp {React.Ref<HTMLElement>} [ref] - Ref forwarded to the `<nav>`.\n */\nexport interface TableOfContentsProps\n extends\n Omit<React.HTMLAttributes<HTMLElement>, 'color' | 'children'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /** Additional CSS classes to apply. */\n className?: string;\n /**\n * **Required.** The sections, in page order, as links to their ids. Each\n * link is a plain `<a href=\"#id\">`, so following one is the browser's own\n * jump: put `scroll-padding-top` on the page to land clear of a sticky\n * header, and smooth scrolling, if you want it, in page CSS inside\n * `@media (prefers-reduced-motion: no-preference)`.\n */\n items: TableOfContentsItem[];\n /**\n * Called with the current section's id when it changes, or `null` above\n * the first section. Never called on the server or before hydration.\n */\n onCurrentChange?: (id: string | null) => void;\n /**\n * Pixels from the viewport's top where a section counts as reached.\n * Unset, it's the page's `scroll-padding-top`, so the highlight agrees\n * with where a jump lands under a sticky header.\n */\n offset?: number;\n /**\n * Sticks to the top of the viewport as the page scrolls, at\n * `--bulma-table-of-contents-sticky-top`, and scrolls itself when it's\n * taller than the viewport. Adds `is-sticky`.\n */\n isSticky?: boolean;\n /** The visible strings, in your app's language. */\n labels?: TableOfContentsLabels;\n /** Text color helper. */\n textColor?: (typeof validColors)[number] | 'inherit' | 'current';\n /** Background color helper. */\n bgColor?: (typeof validColors)[number] | 'inherit' | 'current';\n}\n\n/** Every id in the tree, in page order. */\nconst idsOf = (items: TableOfContentsItem[]): string[] =>\n items.flatMap(item => [item.id, ...idsOf(item.children ?? [])]);\n\n/**\n * The entries as `MenuItem` links, each entry's own entries in a nested\n * `MenuList`, and the current one marked.\n */\nconst entries = (items: TableOfContentsItem[], active: string | null) =>\n items.map(item => {\n const current = item.id === active;\n return (\n <MenuItem\n key={item.id}\n href={`#${encodeURIComponent(item.id)}`}\n active={current}\n aria-current={current ? 'location' : undefined}\n >\n {item.label}\n {item.children?.length ? (\n <MenuList>{entries(item.children, active)}</MenuList>\n ) : null}\n </MenuItem>\n );\n });\n\n/**\n * The `TableOfContents` component lists a long page's sections as links in Bulma's menu look, and marks the one being read.\n *\n * It's a `<nav>` named by its title, holding `MenuList` and `MenuItem` links\n * to each `#id`, rendered in full on the server. `useScrollSpy` picks the\n * current section, and its link alone gets `is-active` and\n * `aria-current=\"location\"`. Use the hook on its own to mark the section in\n * view somewhere else, such as an app's nav `Menu`.\n *\n * @function\n * @param {TableOfContentsProps} props - Props for the TableOfContents component.\n * @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<nav>`.\n * @returns {JSX.Element} The rendered table of contents.\n *\n * @example\n * <TableOfContents\n * isSticky\n * items={[\n * { id: 'install', label: 'Install' },\n * { id: 'usage', label: 'Usage', children: [{ id: 'props', label: 'Props' }] },\n * ]}\n * />\n */\nexport const TableOfContents = forwardRef<HTMLElement, TableOfContentsProps>(\n (\n {\n className,\n textColor,\n bgColor,\n items,\n onCurrentChange,\n offset,\n isSticky,\n labels,\n ...props\n },\n ref\n ) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses({\n color: textColor,\n backgroundColor: bgColor,\n ...props,\n });\n const titleId = useId();\n const active = useScrollSpy({ ids: idsOf(items), offset, onCurrentChange });\n\n const navRef = useRef<HTMLElement | null>(null);\n const setRefs = useCallback(\n (node: HTMLElement | null) => {\n navRef.current = node;\n if (typeof ref === 'function') return ref(node);\n if (ref) ref.current = node;\n },\n [ref]\n );\n\n // A list taller than the room it has scrolls itself to keep the current\n // link in view. Only the list moves: scrollIntoView would move the page\n // too whenever the link is off screen.\n useEffect(() => {\n const nav = navRef.current!;\n const link = nav.querySelector('a[aria-current=\"location\"]');\n if (!link || nav.scrollHeight <= nav.clientHeight) return;\n const top = nav.getBoundingClientRect().top + nav.clientTop;\n const bottom = top + nav.clientHeight;\n const at = link.getBoundingClientRect();\n if (at.top < top) nav.scrollTop -= top - at.top;\n else if (at.bottom > bottom) nav.scrollTop += at.bottom - bottom;\n }, [active]);\n\n // Bulma's menu, which draws it, and its own class beside it.\n const menuClass = usePrefixedClassNames('menu');\n const rootClass = usePrefixedClassNames('table-of-contents', {\n 'is-sticky': !!isSticky,\n });\n\n return (\n <nav\n ref={setRefs}\n className={classNames(\n menuClass,\n rootClass,\n bulmaHelperClasses,\n className\n )}\n aria-labelledby={titleId}\n {...rest}\n >\n <MenuLabel id={titleId}>{labels?.title || 'On this page'}</MenuLabel>\n <MenuList>{entries(items, active)}</MenuList>\n </nav>\n );\n }\n);\n\nTableOfContents.displayName = 'TableOfContents';\n\nexport default TableOfContents;\n"],"names":["_jsxs","_jsx"],"mappings":"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"}
@@ -0,0 +1,105 @@
1
+ /* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
2
+ 'use client';
3
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
+ import { forwardRef } from 'react';
5
+ import { usePrefixedClassNames } from '../helpers/classNames.js';
6
+ import { warnOnce } from '../helpers/devWarnings.js';
7
+ import { Field } from './Field.js';
8
+ import { Control } from './Control.js';
9
+ import { PasswordInputBase } from './PasswordInputBase.js';
10
+ import { useInsideField, useInsideControl, rendersOwnField, rendersOwnControl } from './FormContext.js';
11
+ import { useAutoLabelId } from './useAutoLabelId.js';
12
+
13
+ /** The right-icon props, which the toggle's place leaves no room for. */
14
+ const RIGHT_ICON_PROPS = [
15
+ 'iconRight',
16
+ 'iconRightName',
17
+ 'iconRightSize',
18
+ 'hasIconsRight',
19
+ ];
20
+ /**
21
+ * The `PasswordInput` component is a password field with a button inside it that shows what was typed.
22
+ *
23
+ * By default the button is an eye named "Show password" that reports
24
+ * whether the password shows with `aria-pressed`; `variant="text"` makes it
25
+ * the words "Show" and "Hide". The field asks for
26
+ * `autocomplete="current-password"` and no spellcheck, and hides the
27
+ * password again when its form submits.
28
+ *
29
+ * @function
30
+ * @param {PasswordInputProps} props - Props for PasswordInput.
31
+ * @returns {JSX.Element} The composed field element.
32
+ *
33
+ * @example
34
+ * <PasswordInput label="Password" />
35
+ *
36
+ * @example
37
+ * <PasswordInput label="New password" autoComplete="new-password" variant="text" />
38
+ */
39
+ const PasswordInput = forwardRef((props, ref) => {
40
+ const {
41
+ // Field props
42
+ label, labelSize, labelProps, horizontal,
43
+ // Control props
44
+ iconLeft, iconLeftName, iconLeftSize, hasIconsLeft, isLoading, isExpanded, controlSize,
45
+ // Message props
46
+ message, messageColor,
47
+ // Container class overrides
48
+ fieldClassName, controlClassName,
49
+ // Not offered, and not passed on
50
+ iconRight: _iconRight, iconRightName: _iconRightName, iconRightSize: _iconRightSize, hasIconsRight: _hasIconsRight,
51
+ // Everything else goes to the base
52
+ ...baseProps } = props;
53
+ const rightIcon = RIGHT_ICON_PROPS.filter(prop => props[prop]);
54
+ if (rightIcon.length > 0) {
55
+ const names = rightIcon.join(' ');
56
+ warnOnce(`PasswordInput:right-icon:${names}`, `[bestax-bulma] <PasswordInput ${names}> draws no right icon: the ` +
57
+ `visibility toggle sits at that edge. Leave ` +
58
+ `${rightIcon.length > 1 ? 'them' : 'it'} out, or use a left icon.`);
59
+ }
60
+ const insideField = useInsideField();
61
+ const insideControl = useInsideControl();
62
+ const ownField = rendersOwnField('PasswordInput', {
63
+ insideField,
64
+ insideControl,
65
+ label,
66
+ message,
67
+ horizontal,
68
+ fieldClassName,
69
+ });
70
+ const ownControl = rendersOwnControl('PasswordInput', {
71
+ insideControl,
72
+ isLoading,
73
+ iconLeft,
74
+ iconLeftName,
75
+ iconLeftSize,
76
+ hasIconsLeft,
77
+ isExpanded,
78
+ controlSize,
79
+ controlClassName,
80
+ });
81
+ const { controlId, fieldLabelProps } = useAutoLabelId({
82
+ label,
83
+ id: baseProps.id,
84
+ labelProps,
85
+ rendersLabel: ownField,
86
+ });
87
+ const helpClass = usePrefixedClassNames('help', {
88
+ [`is-${messageColor}`]: !!messageColor,
89
+ });
90
+ // The base hides its toggle while the Control it sits in is loading,
91
+ // whether that is the one rendered below or an enclosing one.
92
+ let content = (jsx(PasswordInputBase, { ref: ref, ...baseProps, id: controlId }));
93
+ if (ownControl) {
94
+ content = (jsx(Control, { iconLeft: iconLeft, iconLeftName: iconLeftName, iconLeftSize: iconLeftSize, hasIconsLeft: hasIconsLeft, isLoading: isLoading, isExpanded: isExpanded, size: controlSize, className: controlClassName, children: content }));
95
+ }
96
+ const messageEl = message ? jsx("p", { className: helpClass, children: message }) : null;
97
+ if (ownField) {
98
+ return (jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [content, messageEl] }));
99
+ }
100
+ return (jsxs(Fragment, { children: [content, messageEl] }));
101
+ });
102
+ PasswordInput.displayName = 'PasswordInput';
103
+
104
+ export { PasswordInput };
105
+ //# sourceMappingURL=PasswordInput.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PasswordInput.js","sources":["../../../src/form/PasswordInput.tsx"],"sourcesContent":["import React, { forwardRef } from 'react';\nimport { usePrefixedClassNames } from '../helpers/classNames';\nimport { warnOnce } from '../helpers/devWarnings';\nimport { Field, FieldProps } from './Field';\nimport { Control, ControlBaseProps } from './Control';\nimport { PasswordInputBase, PasswordInputBaseProps } from './PasswordInputBase';\nimport {\n useInsideField,\n useInsideControl,\n rendersOwnField,\n rendersOwnControl,\n} from './FormContext';\nimport { useAutoLabelId } from './useAutoLabelId';\n\n/**\n * Props for the PasswordInput component: PasswordInputBase's, plus Input's\n * Field and Control props, except the right icon, whose place the toggle\n * takes. Inside an existing `Control` it renders no `Control` of its own, so\n * its Control-level props do nothing there and warn in development; set them\n * on that `Control` instead. Inside an outer `Field`, or a `Control` with no\n * `Field` around it, it renders no `Field` of its own either. The exception\n * is `label`, `message`, `horizontal` or `fieldClassName` in that bare\n * `Control`: it keeps a `Field` for them, nested in the `.control`, and warns\n * in development. Wrap the `Control` in a `Field` instead, and set the\n * `label`, `horizontal` and class name on that `Field`.\n *\n * @helperProps Helper props go on the `.password-input-container` `<div>`, with\n * `className` and `style`, so they move and size the field and its toggle\n * together. The other attributes go on the `<input>`.\n * @extraProp {string} [autoComplete=\"current-password\"] - Tells the browser and password managers this is an existing password. Pass `\"new-password\"` where the user makes one up, on a sign-up or change-password form.\n * @extraProp {boolean} [spellCheck=false] - Off, so a browser's spellcheck never sees the password once it shows.\n * @extraProp {string} [autoCorrect=\"off\"] - Off, so a phone's keyboard doesn't change what's typed.\n * @extraProp {string} [autoCapitalize=\"none\"] - Off, so a phone's keyboard doesn't capitalize the first letter.\n * @extraProp {string} [value] - The password, controlled. React writes a controlled input's value into its `value` attribute, as it does on any input, so the password sits in the page's markup. Uncontrolled, read with `FormData`, the field never puts what's typed there.\n * @extraProp {React.Ref<HTMLInputElement>} [ref] - Ref forwarded to the `<input>`.\n */\nexport interface PasswordInputProps extends PasswordInputBaseProps {\n /** Field label. Automatically associated with the input via `htmlFor`, using your `id` when provided, otherwise a generated one. Dropped inside an outer `Field`, whose own label associates instead when that `Field` generates a target id (not `grouped`/`hasAddons`, no explicit `labelProps.htmlFor`) and you set no `id` here. */\n label?: React.ReactNode;\n /** Size for the label. */\n labelSize?: FieldProps['labelSize'];\n /** Props for the label element when the component renders its own `Field`; dropped inside an outer `Field` (use that `Field`'s `labelProps` instead). An explicit `htmlFor` key, even `undefined`, overrides the automatic association and no id is generated. */\n labelProps?: FieldProps['labelProps'];\n /** Horizontal field layout. */\n horizontal?: boolean;\n /**\n * Icon props for the left icon.\n * Bulma gives control icons `pointer-events: none`, so a clickable node here\n * never receives a click.\n */\n iconLeft?: ControlBaseProps['iconLeft'];\n /** Shortcut for the left icon's name. */\n iconLeftName?: string;\n /** Shortcut for the left icon's size. */\n iconLeftSize?: ControlBaseProps['iconLeftSize'];\n /** Force the left icon container. */\n hasIconsLeft?: boolean;\n /**\n * Shows a loading spinner on the `Control` it renders, at the edge the\n * toggle uses, so the toggle isn't drawn meanwhile, and a shown password is\n * hidden until it's back. Inside your own\n * `Control` it renders none, so this draws nothing and warns in\n * development; set `isLoading` on that `Control`, which hides the toggle\n * too.\n */\n isLoading?: boolean;\n /** Expand the control. */\n isExpanded?: boolean;\n /** Control size. */\n controlSize?: ControlBaseProps['size'];\n /** Help/validation message below the input. */\n message?: React.ReactNode;\n /** Bulma color for the message. */\n messageColor?: 'primary' | 'link' | 'info' | 'success' | 'warning' | 'danger';\n /** Additional CSS classes for the Field. */\n fieldClassName?: string;\n /** Additional CSS classes for the Control. */\n controlClassName?: string;\n /** Not offered: the toggle takes the right icon's place. Warns in development if passed. @internal */\n iconRight?: never;\n /** Not offered, as `iconRight`. @internal */\n iconRightName?: never;\n /** Not offered, as `iconRight`. @internal */\n iconRightSize?: never;\n /** Not offered, as `iconRight`. @internal */\n hasIconsRight?: never;\n}\n\n/** The right-icon props, which the toggle's place leaves no room for. */\nconst RIGHT_ICON_PROPS = [\n 'iconRight',\n 'iconRightName',\n 'iconRightSize',\n 'hasIconsRight',\n] as const;\n\n/**\n * The `PasswordInput` component is a password field with a button inside it that shows what was typed.\n *\n * By default the button is an eye named \"Show password\" that reports\n * whether the password shows with `aria-pressed`; `variant=\"text\"` makes it\n * the words \"Show\" and \"Hide\". The field asks for\n * `autocomplete=\"current-password\"` and no spellcheck, and hides the\n * password again when its form submits.\n *\n * @function\n * @param {PasswordInputProps} props - Props for PasswordInput.\n * @returns {JSX.Element} The composed field element.\n *\n * @example\n * <PasswordInput label=\"Password\" />\n *\n * @example\n * <PasswordInput label=\"New password\" autoComplete=\"new-password\" variant=\"text\" />\n */\nexport const PasswordInput = forwardRef<HTMLInputElement, PasswordInputProps>(\n (props, ref) => {\n const {\n // Field props\n label,\n labelSize,\n labelProps,\n horizontal,\n // Control props\n iconLeft,\n iconLeftName,\n iconLeftSize,\n hasIconsLeft,\n isLoading,\n isExpanded,\n controlSize,\n // Message props\n message,\n messageColor,\n // Container class overrides\n fieldClassName,\n controlClassName,\n // Not offered, and not passed on\n iconRight: _iconRight,\n iconRightName: _iconRightName,\n iconRightSize: _iconRightSize,\n hasIconsRight: _hasIconsRight,\n // Everything else goes to the base\n ...baseProps\n } = props;\n\n const rightIcon = RIGHT_ICON_PROPS.filter(prop => props[prop]);\n if (rightIcon.length > 0) {\n const names = rightIcon.join(' ');\n warnOnce(\n `PasswordInput:right-icon:${names}`,\n `[bestax-bulma] <PasswordInput ${names}> draws no right icon: the ` +\n `visibility toggle sits at that edge. Leave ` +\n `${rightIcon.length > 1 ? 'them' : 'it'} out, or use a left icon.`\n );\n }\n\n const insideField = useInsideField();\n const insideControl = useInsideControl();\n const ownField = rendersOwnField('PasswordInput', {\n insideField,\n insideControl,\n label,\n message,\n horizontal,\n fieldClassName,\n });\n const ownControl = rendersOwnControl('PasswordInput', {\n insideControl,\n isLoading,\n iconLeft,\n iconLeftName,\n iconLeftSize,\n hasIconsLeft,\n isExpanded,\n controlSize,\n controlClassName,\n });\n const { controlId, fieldLabelProps } = useAutoLabelId({\n label,\n id: baseProps.id,\n labelProps,\n rendersLabel: ownField,\n });\n const helpClass = usePrefixedClassNames('help', {\n [`is-${messageColor}`]: !!messageColor,\n });\n\n // The base hides its toggle while the Control it sits in is loading,\n // whether that is the one rendered below or an enclosing one.\n let content: React.ReactNode = (\n <PasswordInputBase ref={ref} {...baseProps} id={controlId} />\n );\n\n if (ownControl) {\n content = (\n <Control\n iconLeft={iconLeft}\n iconLeftName={iconLeftName}\n iconLeftSize={iconLeftSize}\n hasIconsLeft={hasIconsLeft}\n isLoading={isLoading}\n isExpanded={isExpanded}\n size={controlSize}\n className={controlClassName}\n >\n {content}\n </Control>\n );\n }\n\n const messageEl = message ? <p className={helpClass}>{message}</p> : null;\n\n if (ownField) {\n return (\n <Field\n label={label}\n labelSize={labelSize}\n labelProps={fieldLabelProps}\n horizontal={horizontal}\n className={fieldClassName}\n >\n {content}\n {messageEl}\n </Field>\n );\n }\n\n return (\n <>\n {content}\n {messageEl}\n </>\n );\n }\n);\n\nPasswordInput.displayName = 'PasswordInput';\n\nexport default PasswordInput;\n"],"names":["_jsx","_jsxs","_Fragment"],"mappings":"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"}
@@ -0,0 +1,177 @@
1
+ /* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
2
+ 'use client';
3
+ import { jsx, jsxs } from 'react/jsx-runtime';
4
+ import { forwardRef, useState, useRef, useCallback, useEffect } from 'react';
5
+ import { flushSync } from 'react-dom';
6
+ import { usePrefixedClassNames, classNames } from '../helpers/classNames.js';
7
+ import { warnOnce } from '../helpers/devWarnings.js';
8
+ import { useBulmaClasses } from '../helpers/useBulmaClasses.js';
9
+ import { useClientLayoutEffect } from '../helpers/useClientLayoutEffect.js';
10
+ import { useLatestRef } from '../helpers/useLatestRef.js';
11
+ import { InputBase } from './InputBase.js';
12
+ import { useControlLoading } from './controlLoading.js';
13
+ import { useFieldsetDisabled } from './FormContext.js';
14
+
15
+ /** An eye, drawn while the password is hidden. */
16
+ const EyeIcon = () => (jsx("svg", { viewBox: "0 0 16 16", width: "1.25em", height: "1.25em", "aria-hidden": "true", children: jsxs("g", { fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [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" }), jsx("circle", { cx: "8", cy: "8", r: "2" })] }) }));
17
+ /** The eye crossed out, drawn while the password shows. */
18
+ const EyeOffIcon = () => (jsx("svg", { viewBox: "0 0 16 16", width: "1.25em", height: "1.25em", "aria-hidden": "true", children: jsxs("g", { fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [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" }), jsx("circle", { cx: "8", cy: "8", r: "2" }), jsx("path", { d: "M2.5 2.5l11 11" })] }) }));
19
+ /**
20
+ * Warns in development when the `text` toggle's name doesn't contain the
21
+ * word on it, which a voice control user says to press it.
22
+ */
23
+ const checkLabelInName = (name, word) => {
24
+ if (name.toLocaleLowerCase().includes(word.toLocaleLowerCase()))
25
+ return;
26
+ warnOnce(`PasswordInput:label-in-name:${name}:${word}`, `[bestax-bulma] A PasswordInput with variant="text" is named ` +
27
+ `"${name}" while its button says "${word}". A voice control user ` +
28
+ `says the word they see, so the name has to contain it (WCAG 2.5.3, ` +
29
+ `Label in Name). Set labels so each name contains its word.`);
30
+ };
31
+ /**
32
+ * The raw PasswordInput: a password field with a button inside it that shows what was typed, without Field/Control wrapping.
33
+ *
34
+ * Use `PasswordInput` for the convenience wrapper.
35
+ *
36
+ * @function
37
+ * @param {PasswordInputBaseProps} props - Props for PasswordInputBase.
38
+ * @param {React.Ref<HTMLInputElement>} ref - Forwarded ref to the `<input>`.
39
+ * @returns {JSX.Element} The input and its toggle, in a container.
40
+ */
41
+ const PasswordInputBase = forwardRef((props, ref) => {
42
+ 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;
43
+ // Helper props, `className` and `style` go on the container, so a margin,
44
+ // a width or a display moves the field and its toggle together. `color`
45
+ // stays with the input, whose border it colors.
46
+ const { bulmaHelperClasses, rest } = useBulmaClasses(others);
47
+ const disabled = useFieldsetDisabled(disabledProp);
48
+ // The toggle gives way to the loading spinner of a Control this sits in,
49
+ // which takes its slot, and the field hides the password meanwhile, as
50
+ // there's no button to hide it with.
51
+ const controlLoading = useControlLoading();
52
+ const isText = variant === 'text';
53
+ const showPassword = labels?.showPassword || 'Show password';
54
+ const hidePassword = labels?.hidePassword || 'Hide password';
55
+ const show = labels?.show || 'Show';
56
+ const hide = labels?.hide || 'Hide';
57
+ if (isText) {
58
+ checkLabelInName(showPassword, show);
59
+ checkLabelInName(hidePassword, hide);
60
+ }
61
+ const isControlled = visibleProp !== undefined;
62
+ const [internalVisible, setInternalVisible] = useState(defaultVisible);
63
+ const visible = isControlled ? visibleProp : internalVisible;
64
+ // Whether the field shows now: as last reported, until `visible` changes.
65
+ // Two hide events before React renders, as from an effect, or a caller
66
+ // holding `visible` that keeps it `true`, report `false` once.
67
+ const shownRef = useRef(visible);
68
+ useClientLayoutEffect(() => {
69
+ shownRef.current = visible;
70
+ }, [visible]);
71
+ const setVisible = (next) => {
72
+ shownRef.current = next;
73
+ if (!isControlled)
74
+ setInternalVisible(next);
75
+ onVisibleChange?.(next);
76
+ };
77
+ const inputRef = useRef(null);
78
+ // A caller's callback ref may return a cleanup, as React 19 allows. It's
79
+ // held here and run on detach in place of a call with `null`, on React 18
80
+ // as well, since this ref is the one React calls.
81
+ const refCleanup = useRef(null);
82
+ const combinedRef = useCallback((node) => {
83
+ inputRef.current = node;
84
+ if (typeof ref === 'function') {
85
+ if (node === null) {
86
+ const cleanup = refCleanup.current;
87
+ refCleanup.current = null;
88
+ if (cleanup)
89
+ cleanup();
90
+ else
91
+ ref(null);
92
+ return;
93
+ }
94
+ const cleanup = ref(node);
95
+ refCleanup.current =
96
+ typeof cleanup === 'function' ? cleanup : null;
97
+ }
98
+ else if (ref) {
99
+ ref.current = node;
100
+ }
101
+ }, [ref]);
102
+ // A toggle with focus that leaves while the input stays, as it does when a
103
+ // Control starts loading, hands focus to the input rather than dropping it
104
+ // to the page. React lets go of the toggle before it takes it out of the
105
+ // page, so the toggle still has focus here; when the whole field unmounts,
106
+ // React has let go of the input first.
107
+ const toggleRef = useRef(null);
108
+ const setToggle = useCallback((node) => {
109
+ // React lets go only of a toggle it handed over, so there is one.
110
+ const leaving = toggleRef.current;
111
+ toggleRef.current = node;
112
+ if (node)
113
+ return;
114
+ const root = leaving.getRootNode();
115
+ if (root.activeElement === leaving)
116
+ inputRef.current?.focus();
117
+ }, []);
118
+ // Hides the password again: reports `false`, and sets its own state when it
119
+ // holds it. Inside `flushSync`, React renders that, a caller's own state
120
+ // update included, before the listener returns, so the browser reads the
121
+ // form, or saves the page's form state, from a password field. The
122
+ // component never sets the field's type itself, so a caller holding
123
+ // `visible` that keeps it `true` keeps the field shown. The one catch: an
124
+ // event fired while React is rendering or committing, which includes
125
+ // `useEffect` and `useLayoutEffect` callbacks (a `requestSubmit()` from
126
+ // an effect), can't flush there, so React warns in development and the
127
+ // hide lands after the form is read. Only the listeners below call it,
128
+ // while the input is mounted.
129
+ const hideRef = useLatestRef(() => {
130
+ if (!shownRef.current)
131
+ return;
132
+ // 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
133
+ flushSync(() => setVisible(false));
134
+ });
135
+ // When the form it belongs to submits or resets: whichever form that is
136
+ // when it happens, so a form mounted after the field, or mounted again,
137
+ // counts. Neither event leaves a shadow root, so it listens on the field's
138
+ // own root, the document or the shadow root it renders in, and in the
139
+ // capture phase, before any listener on the form.
140
+ useEffect(() => {
141
+ const input = inputRef.current;
142
+ const root = input.getRootNode();
143
+ const onFormEvent = (event) => {
144
+ if (event.target === input.form)
145
+ hideRef.current();
146
+ };
147
+ root.addEventListener('submit', onFormEvent, true);
148
+ root.addEventListener('reset', onFormEvent, true);
149
+ return () => {
150
+ root.removeEventListener('submit', onFormEvent, true);
151
+ root.removeEventListener('reset', onFormEvent, true);
152
+ };
153
+ }, [hideRef]);
154
+ // When the page is left, so the history the browser keeps of the form
155
+ // holds a password field rather than text. It fires on the way into the
156
+ // back-forward cache too, so a page coming back from there is hidden.
157
+ useEffect(() => {
158
+ const onPageHide = () => hideRef.current();
159
+ window.addEventListener('pagehide', onPageHide);
160
+ return () => window.removeEventListener('pagehide', onPageHide);
161
+ }, [hideRef]);
162
+ const containerClass = usePrefixedClassNames('password-input-container', {
163
+ 'is-text': isText,
164
+ });
165
+ const toggleClass = usePrefixedClassNames('password-input-toggle');
166
+ // The icon keeps one name and reports its state; the words change, so
167
+ // the name follows them and there's no state to report.
168
+ const toggleName = isText && visible ? hidePassword : showPassword;
169
+ const glyph = visible ? jsx(EyeOffIcon, {}) : jsx(EyeIcon, {});
170
+ return (jsxs("div", { className: classNames(containerClass, bulmaHelperClasses, className), style: style, children: [jsx(InputBase, { ref: combinedRef, autoComplete: autoComplete, spellCheck: spellCheck, autoCorrect: autoCorrect, autoCapitalize: autoCapitalize, disabled: disabled, color: color, ...rest, type: visible && !controlLoading ? 'text' : 'password' }), !controlLoading && (jsx("button", { ref: setToggle, type: "button", className: toggleClass, "aria-label": toggleName, "aria-pressed": isText ? undefined : visible, disabled: disabled,
171
+ // A pointer press leaves focus, and the caret, in the input.
172
+ onMouseDown: event => event.preventDefault(), onClick: () => setVisible(!visible), children: isText ? (visible ? hide : show) : glyph }))] }));
173
+ });
174
+ PasswordInputBase.displayName = 'PasswordInputBase';
175
+
176
+ export { PasswordInputBase };
177
+ //# sourceMappingURL=PasswordInputBase.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PasswordInputBase.js","sources":["../../../src/form/PasswordInputBase.tsx"],"sourcesContent":["import { forwardRef, useCallback, useEffect, useRef, useState } from 'react';\nimport { flushSync } from 'react-dom';\nimport { classNames, usePrefixedClassNames } from '../helpers/classNames';\nimport { warnOnce } from '../helpers/devWarnings';\nimport { useBulmaClasses } from '../helpers/useBulmaClasses';\nimport { useClientLayoutEffect } from '../helpers/useClientLayoutEffect';\nimport { useLatestRef } from '../helpers/useLatestRef';\nimport { InputBase, InputBaseProps } from './InputBase';\nimport { useControlLoading } from './controlLoading';\nimport { useFieldsetDisabled } from './FormContext';\n\n/** How PasswordInput's visibility toggle draws itself. */\nexport type PasswordInputVariant = 'icon' | 'text';\n\n/** The strings PasswordInput's toggle shows and announces, for translating. */\nexport interface PasswordInputLabels {\n /**\n * The toggle's name. With the `icon` variant it's the name in both states,\n * and `aria-pressed` says whether the password shows. With `text`, it's the\n * name while the password is hidden, and it has to contain `show`, the word\n * on the button.\n * @defaultValue 'Show password'\n */\n showPassword?: string;\n /**\n * The `text` variant's name while the password shows. It has to contain\n * `hide`.\n * @defaultValue 'Hide password'\n */\n hidePassword?: string;\n /**\n * The `text` variant's word while the password is hidden.\n * @defaultValue 'Show'\n */\n show?: string;\n /**\n * The `text` variant's word while the password shows.\n * @defaultValue 'Hide'\n */\n hide?: string;\n}\n\n/**\n * Props for PasswordInputBase, the input and its toggle without a Field or a\n * Control.\n *\n * @helperProps Helper props go on the `.password-input-container` `<div>`, with\n * `className` and `style`, so they move and size the field and its toggle\n * together. The other attributes go on the `<input>`.\n * @extraProp {string} [autoComplete=\"current-password\"] - Tells the browser and password managers this is an existing password. Pass `\"new-password\"` where the user makes one up, on a sign-up or change-password form.\n * @extraProp {boolean} [spellCheck=false] - Off, so a browser's spellcheck never sees the password once it shows.\n * @extraProp {string} [autoCorrect=\"off\"] - Off, so a phone's keyboard doesn't change what's typed.\n * @extraProp {string} [autoCapitalize=\"none\"] - Off, so a phone's keyboard doesn't capitalize the first letter.\n * @extraProp {string} [value] - The password, controlled. React writes a controlled input's value into its `value` attribute, as it does on any input, so the password sits in the page's markup. Uncontrolled, read with `FormData`, the field never puts what's typed there.\n * @extraProp {React.Ref<HTMLInputElement>} [ref] - Ref forwarded to the `<input>`.\n */\nexport interface PasswordInputBaseProps extends Omit<InputBaseProps, 'type'> {\n /**\n * Whether the password shows, when you hold the state. Setting it makes\n * the visibility controlled: the toggle only calls `onVisibleChange`, and\n * so does hiding again on its own. Apply that `false` and the field is a\n * password field by the time the form reads it; keep `visible` true and\n * the field stays shown.\n */\n visible?: boolean;\n /**\n * Whether the password shows at first, when the input holds its own state.\n * @defaultValue false\n */\n defaultVisible?: boolean;\n /**\n * Called with the new visibility on each toggle, and with `false` when the\n * form submits or resets, or the page is left, which it also is on the way\n * into the back-forward cache. React renders those hides at once, a state\n * update you make here included, so the form reads a password field. One\n * that fires while React is rendering or committing, which includes your\n * `useEffect` and `useLayoutEffect` callbacks, such as a `requestSubmit()`\n * from an effect, can't render there, and lands just after the form is\n * read: hide the password first, or submit from an event handler.\n */\n onVisibleChange?: (visible: boolean) => void;\n /**\n * How the toggle draws itself: an eye icon with a fixed name and\n * `aria-pressed`, or the words \"Show\" and \"Hide\" with a name that follows\n * them.\n * @defaultValue 'icon'\n */\n variant?: PasswordInputVariant;\n /** Translations for the toggle. */\n labels?: PasswordInputLabels;\n}\n\n/** An eye, drawn while the password is hidden. */\nconst EyeIcon = () => (\n <svg viewBox=\"0 0 16 16\" width=\"1.25em\" height=\"1.25em\" aria-hidden=\"true\">\n <g\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <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\" />\n <circle cx=\"8\" cy=\"8\" r=\"2\" />\n </g>\n </svg>\n);\n\n/** The eye crossed out, drawn while the password shows. */\nconst EyeOffIcon = () => (\n <svg viewBox=\"0 0 16 16\" width=\"1.25em\" height=\"1.25em\" aria-hidden=\"true\">\n <g\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <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\" />\n <circle cx=\"8\" cy=\"8\" r=\"2\" />\n <path d=\"M2.5 2.5l11 11\" />\n </g>\n </svg>\n);\n\n/**\n * Warns in development when the `text` toggle's name doesn't contain the\n * word on it, which a voice control user says to press it.\n */\nconst checkLabelInName = (name: string, word: string) => {\n if (name.toLocaleLowerCase().includes(word.toLocaleLowerCase())) return;\n warnOnce(\n `PasswordInput:label-in-name:${name}:${word}`,\n `[bestax-bulma] A PasswordInput with variant=\"text\" is named ` +\n `\"${name}\" while its button says \"${word}\". A voice control user ` +\n `says the word they see, so the name has to contain it (WCAG 2.5.3, ` +\n `Label in Name). Set labels so each name contains its word.`\n );\n};\n\n/**\n * The raw PasswordInput: a password field with a button inside it that shows what was typed, without Field/Control wrapping.\n *\n * Use `PasswordInput` for the convenience wrapper.\n *\n * @function\n * @param {PasswordInputBaseProps} props - Props for PasswordInputBase.\n * @param {React.Ref<HTMLInputElement>} ref - Forwarded ref to the `<input>`.\n * @returns {JSX.Element} The input and its toggle, in a container.\n */\nexport const PasswordInputBase = forwardRef<\n HTMLInputElement,\n PasswordInputBaseProps\n>((props, ref) => {\n const {\n visible: visibleProp,\n defaultVisible = false,\n onVisibleChange,\n variant = 'icon',\n labels,\n autoComplete = 'current-password',\n spellCheck = false,\n autoCorrect = 'off',\n autoCapitalize = 'none',\n disabled: disabledProp,\n color,\n className,\n style,\n ...others\n } = props;\n // Helper props, `className` and `style` go on the container, so a margin,\n // a width or a display moves the field and its toggle together. `color`\n // stays with the input, whose border it colors.\n const { bulmaHelperClasses, rest } = useBulmaClasses(others);\n const disabled = useFieldsetDisabled(disabledProp);\n // The toggle gives way to the loading spinner of a Control this sits in,\n // which takes its slot, and the field hides the password meanwhile, as\n // there's no button to hide it with.\n const controlLoading = useControlLoading();\n const isText = variant === 'text';\n\n const showPassword = labels?.showPassword || 'Show password';\n const hidePassword = labels?.hidePassword || 'Hide password';\n const show = labels?.show || 'Show';\n const hide = labels?.hide || 'Hide';\n if (isText) {\n checkLabelInName(showPassword, show);\n checkLabelInName(hidePassword, hide);\n }\n\n const isControlled = visibleProp !== undefined;\n const [internalVisible, setInternalVisible] = useState(defaultVisible);\n const visible = isControlled ? visibleProp : internalVisible;\n // Whether the field shows now: as last reported, until `visible` changes.\n // Two hide events before React renders, as from an effect, or a caller\n // holding `visible` that keeps it `true`, report `false` once.\n const shownRef = useRef(visible);\n useClientLayoutEffect(() => {\n shownRef.current = visible;\n }, [visible]);\n const setVisible = (next: boolean) => {\n shownRef.current = next;\n if (!isControlled) setInternalVisible(next);\n onVisibleChange?.(next);\n };\n\n const inputRef = useRef<HTMLInputElement | null>(null);\n // A caller's callback ref may return a cleanup, as React 19 allows. It's\n // held here and run on detach in place of a call with `null`, on React 18\n // as well, since this ref is the one React calls.\n const refCleanup = useRef<(() => void) | null>(null);\n const combinedRef = useCallback(\n (node: HTMLInputElement | null) => {\n inputRef.current = node;\n if (typeof ref === 'function') {\n if (node === null) {\n const cleanup = refCleanup.current;\n refCleanup.current = null;\n if (cleanup) cleanup();\n else ref(null);\n return;\n }\n const cleanup: unknown = ref(node);\n refCleanup.current =\n typeof cleanup === 'function' ? (cleanup as () => void) : null;\n } else if (ref) {\n ref.current = node;\n }\n },\n [ref]\n );\n\n // A toggle with focus that leaves while the input stays, as it does when a\n // Control starts loading, hands focus to the input rather than dropping it\n // to the page. React lets go of the toggle before it takes it out of the\n // page, so the toggle still has focus here; when the whole field unmounts,\n // React has let go of the input first.\n const toggleRef = useRef<HTMLButtonElement | null>(null);\n const setToggle = useCallback((node: HTMLButtonElement | null) => {\n // React lets go only of a toggle it handed over, so there is one.\n const leaving = toggleRef.current as HTMLButtonElement;\n toggleRef.current = node;\n if (node) return;\n const root = leaving.getRootNode() as Document | ShadowRoot;\n if (root.activeElement === leaving) inputRef.current?.focus();\n }, []);\n\n // Hides the password again: reports `false`, and sets its own state when it\n // holds it. Inside `flushSync`, React renders that, a caller's own state\n // update included, before the listener returns, so the browser reads the\n // form, or saves the page's form state, from a password field. The\n // component never sets the field's type itself, so a caller holding\n // `visible` that keeps it `true` keeps the field shown. The one catch: an\n // event fired while React is rendering or committing, which includes\n // `useEffect` and `useLayoutEffect` callbacks (a `requestSubmit()` from\n // an effect), can't flush there, so React warns in development and the\n // hide lands after the form is read. Only the listeners below call it,\n // while the input is mounted.\n const hideRef = useLatestRef(() => {\n if (!shownRef.current) return;\n // 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\n flushSync(() => setVisible(false));\n });\n\n // When the form it belongs to submits or resets: whichever form that is\n // when it happens, so a form mounted after the field, or mounted again,\n // counts. Neither event leaves a shadow root, so it listens on the field's\n // own root, the document or the shadow root it renders in, and in the\n // capture phase, before any listener on the form.\n useEffect(() => {\n const input = inputRef.current as HTMLInputElement;\n const root = input.getRootNode();\n const onFormEvent = (event: Event) => {\n if (event.target === input.form) hideRef.current();\n };\n root.addEventListener('submit', onFormEvent, true);\n root.addEventListener('reset', onFormEvent, true);\n return () => {\n root.removeEventListener('submit', onFormEvent, true);\n root.removeEventListener('reset', onFormEvent, true);\n };\n }, [hideRef]);\n\n // When the page is left, so the history the browser keeps of the form\n // holds a password field rather than text. It fires on the way into the\n // back-forward cache too, so a page coming back from there is hidden.\n useEffect(() => {\n const onPageHide = () => hideRef.current();\n window.addEventListener('pagehide', onPageHide);\n return () => window.removeEventListener('pagehide', onPageHide);\n }, [hideRef]);\n\n const containerClass = usePrefixedClassNames('password-input-container', {\n 'is-text': isText,\n });\n const toggleClass = usePrefixedClassNames('password-input-toggle');\n\n // The icon keeps one name and reports its state; the words change, so\n // the name follows them and there's no state to report.\n const toggleName = isText && visible ? hidePassword : showPassword;\n const glyph = visible ? <EyeOffIcon /> : <EyeIcon />;\n\n return (\n <div\n className={classNames(containerClass, bulmaHelperClasses, className)}\n style={style}\n >\n <InputBase\n ref={combinedRef}\n autoComplete={autoComplete}\n spellCheck={spellCheck}\n autoCorrect={autoCorrect}\n autoCapitalize={autoCapitalize}\n disabled={disabled}\n color={color}\n {...rest}\n type={visible && !controlLoading ? 'text' : 'password'}\n />\n {!controlLoading && (\n <button\n ref={setToggle}\n type=\"button\"\n className={toggleClass}\n aria-label={toggleName}\n aria-pressed={isText ? undefined : visible}\n disabled={disabled}\n // A pointer press leaves focus, and the caret, in the input.\n onMouseDown={event => event.preventDefault()}\n onClick={() => setVisible(!visible)}\n >\n {isText ? (visible ? hide : show) : glyph}\n </button>\n )}\n </div>\n );\n});\n\nPasswordInputBase.displayName = 'PasswordInputBase';\n\nexport default PasswordInputBase;\n"],"names":["_jsx","_jsxs"],"mappings":"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"}