@allxsmith/bestax-bulma 5.62.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/EmptyState.js +2 -6
- package/dist/esm/components/EmptyState.js.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/components/Timeline.js +5 -9
- package/dist/esm/components/Timeline.js.map +1 -1
- package/dist/esm/components/TreeView.js +3 -7
- package/dist/esm/components/TreeView.js.map +1 -1
- package/dist/esm/elements/Buttons.js +2 -0
- package/dist/esm/elements/Buttons.js.map +1 -1
- package/dist/esm/elements/CopyButton.js +132 -0
- package/dist/esm/elements/CopyButton.js.map +1 -0
- package/dist/esm/elements/DescriptionList.js +1 -5
- package/dist/esm/elements/DescriptionList.js.map +1 -1
- package/dist/esm/elements/Stat.js +196 -0
- package/dist/esm/elements/Stat.js.map +1 -0
- package/dist/esm/elements/Stats.js +48 -0
- package/dist/esm/elements/Stats.js.map +1 -0
- package/dist/esm/elements/statContext.js +10 -0
- package/dist/esm/elements/statContext.js.map +1 -0
- package/dist/esm/form/Combobox.js +2 -1
- package/dist/esm/form/Combobox.js.map +1 -1
- 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/checkIcon.js +20 -0
- package/dist/esm/helpers/checkIcon.js.map +1 -0
- package/dist/esm/helpers/isGiven.js +13 -0
- package/dist/esm/helpers/isGiven.js.map +1 -0
- package/dist/esm/helpers/statusRegion.js +3 -3
- package/dist/esm/helpers/useClipboard.js +176 -0
- package/dist/esm/helpers/useClipboard.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 +2671 -1544
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +8 -0
- package/dist/index.esm.js.map +1 -1
- package/dist/types/components/TableOfContents.d.ts +87 -0
- package/dist/types/elements/Buttons.d.ts +1 -0
- package/dist/types/elements/CopyButton.d.ts +105 -0
- package/dist/types/elements/Stat.d.ts +242 -0
- package/dist/types/elements/Stats.d.ts +40 -0
- package/dist/types/elements/statContext.d.ts +18 -0
- package/dist/types/form/PasswordInput.d.ts +98 -0
- package/dist/types/form/PasswordInputBase.d.ts +91 -0
- package/dist/types/helpers/checkIcon.d.ts +10 -0
- package/dist/types/helpers/isGiven.d.ts +7 -0
- package/dist/types/helpers/useClipboard.d.ts +65 -0
- package/dist/types/helpers/useScrollSpy.d.ts +61 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types-cjs/components/TableOfContents.d.ts +87 -0
- package/dist/types-cjs/elements/Buttons.d.ts +1 -0
- package/dist/types-cjs/elements/CopyButton.d.ts +105 -0
- package/dist/types-cjs/elements/Stat.d.ts +242 -0
- package/dist/types-cjs/elements/Stats.d.ts +40 -0
- package/dist/types-cjs/elements/statContext.d.ts +18 -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/checkIcon.d.ts +10 -0
- package/dist/types-cjs/helpers/isGiven.d.ts +7 -0
- package/dist/types-cjs/helpers/useClipboard.d.ts +65 -0
- package/dist/types-cjs/helpers/useScrollSpy.d.ts +61 -0
- package/dist/types-cjs/index.d.ts +10 -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/elements/_index.scss +1 -0
- package/src/scss/elements/_stat.scss +153 -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
|
@@ -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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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
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 } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
// INTERNAL: deliberately not exported from src/index.ts.
|
|
6
|
+
/**
|
|
7
|
+
* A check mark, 1em square and drawn in `currentColor`, so it takes the text
|
|
8
|
+
* color around it, the system's in forced colors included. Combobox draws it
|
|
9
|
+
* beside a chosen item, and CopyButton after a copy. It's decorative: put it
|
|
10
|
+
* inside something `aria-hidden`.
|
|
11
|
+
*
|
|
12
|
+
* @function CheckIcon
|
|
13
|
+
* @returns The check mark's `<svg>`.
|
|
14
|
+
*/
|
|
15
|
+
function CheckIcon$1() {
|
|
16
|
+
return (jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", width: "1em", height: "1em", focusable: "false", children: jsx("polyline", { points: "3 8.5 6.5 12 13 4.5" }) }));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export { CheckIcon$1 as CheckIcon };
|
|
20
|
+
//# sourceMappingURL=checkIcon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkIcon.js","sources":["../../../src/helpers/checkIcon.tsx"],"sourcesContent":["// INTERNAL: deliberately not exported from src/index.ts.\n\n/**\n * A check mark, 1em square and drawn in `currentColor`, so it takes the text\n * color around it, the system's in forced colors included. Combobox draws it\n * beside a chosen item, and CopyButton after a copy. It's decorative: put it\n * inside something `aria-hidden`.\n *\n * @function CheckIcon\n * @returns The check mark's `<svg>`.\n */\nexport function CheckIcon() {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n width=\"1em\"\n height=\"1em\"\n focusable=\"false\"\n >\n <polyline points=\"3 8.5 6.5 12 13 4.5\" />\n </svg>\n );\n}\n"],"names":["CheckIcon","_jsx"],"mappings":";;;;AAAA;AAEA;;;;;;;;AAQG;SACaA,WAAS,GAAA;IACvB,QACEC,aACE,KAAK,EAAC,4BAA4B,EAClC,OAAO,EAAC,WAAW,EACnB,IAAI,EAAC,MAAM,EACX,MAAM,EAAC,cAAc,EACrB,WAAW,EAAC,GAAG,EACf,aAAa,EAAC,OAAO,EACrB,cAAc,EAAC,OAAO,EACtB,KAAK,EAAC,KAAK,EACX,MAAM,EAAC,KAAK,EACZ,SAAS,EAAC,OAAO,EAAA,QAAA,EAEjBA,GAAA,CAAA,UAAA,EAAA,EAAU,MAAM,EAAC,qBAAqB,EAAA,CAAG,EAAA,CACrC;AAEV;;"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
|
|
2
|
+
/**
|
|
3
|
+
* INTERNAL, deliberately not exported from `src/index.ts`. Whether a prop
|
|
4
|
+
* was given something to render: `undefined`, `null`, a boolean and `''`
|
|
5
|
+
* weren't, and `0` was, since React renders it.
|
|
6
|
+
*/
|
|
7
|
+
const isGiven = (node) => node !== undefined &&
|
|
8
|
+
node !== null &&
|
|
9
|
+
typeof node !== 'boolean' &&
|
|
10
|
+
node !== '';
|
|
11
|
+
|
|
12
|
+
export { isGiven };
|
|
13
|
+
//# sourceMappingURL=isGiven.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isGiven.js","sources":["../../../src/helpers/isGiven.ts"],"sourcesContent":["import type { ReactNode } from 'react';\n\n/**\n * INTERNAL, deliberately not exported from `src/index.ts`. Whether a prop\n * was given something to render: `undefined`, `null`, a boolean and `''`\n * weren't, and `0` was, since React renders it.\n */\nexport const isGiven = (node: ReactNode) =>\n node !== undefined &&\n node !== null &&\n typeof node !== 'boolean' &&\n node !== '';\n"],"names":[],"mappings":";AAEA;;;;AAIG;AACI,MAAM,OAAO,GAAG,CAAC,IAAe,KACrC,IAAI,KAAK,SAAS;AAClB,IAAA,IAAI,KAAK,IAAI;IACb,OAAO,IAAI,KAAK,SAAS;IACzB,IAAI,KAAK;;"}
|
|
@@ -17,11 +17,11 @@ const announceDelay = 100;
|
|
|
17
17
|
* again.
|
|
18
18
|
*/
|
|
19
19
|
const announcementLifetime = 1000;
|
|
20
|
-
const noAnnouncements$
|
|
20
|
+
const noAnnouncements$2 = [];
|
|
21
21
|
const nothingWritten = [];
|
|
22
22
|
const noAnnouncementsYet = {
|
|
23
23
|
key: 0,
|
|
24
|
-
waiting: noAnnouncements$
|
|
24
|
+
waiting: noAnnouncements$2,
|
|
25
25
|
written: nothingWritten,
|
|
26
26
|
};
|
|
27
27
|
// Hides the region with inline styles rather than a class, so it needs no
|
|
@@ -123,7 +123,7 @@ function useAnnouncements(items, describe) {
|
|
|
123
123
|
setAnnouncements(current => current.key === key
|
|
124
124
|
? {
|
|
125
125
|
...current,
|
|
126
|
-
waiting: noAnnouncements$
|
|
126
|
+
waiting: noAnnouncements$2,
|
|
127
127
|
written: [
|
|
128
128
|
...current.written,
|
|
129
129
|
...current.waiting
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
|
|
2
|
+
'use client';
|
|
3
|
+
import { useState, useRef, useEffect, useCallback } from 'react';
|
|
4
|
+
import { useLatestRef } from './useLatestRef.js';
|
|
5
|
+
|
|
6
|
+
/** How long `copied` lasts when `duration` doesn't say, in ms. */
|
|
7
|
+
const defaultDuration = 2000;
|
|
8
|
+
/**
|
|
9
|
+
* The longest delay `setTimeout` takes, in ms. A longer one, `Infinity`
|
|
10
|
+
* included, runs at once.
|
|
11
|
+
*/
|
|
12
|
+
const longestDelay = 2 ** 31 - 1;
|
|
13
|
+
/**
|
|
14
|
+
* `duration` as a delay `setTimeout` keeps, or `null` for no timer: `null`
|
|
15
|
+
* for `0`, as on Toast, the default when it's missing or `NaN` (or a
|
|
16
|
+
* JavaScript caller's `null`), `0` for a negative one, which ends `copied` at
|
|
17
|
+
* once, and the longest delay for `Infinity` or anything longer.
|
|
18
|
+
*/
|
|
19
|
+
const delayOf = (duration) => {
|
|
20
|
+
if (duration === 0)
|
|
21
|
+
return null;
|
|
22
|
+
return duration == null || Number.isNaN(duration)
|
|
23
|
+
? defaultDuration
|
|
24
|
+
: Math.min(Math.max(duration, 0), longestDelay);
|
|
25
|
+
};
|
|
26
|
+
/** The error a copy reports when nothing could write and nothing said why. */
|
|
27
|
+
const cannotCopy = 'The Clipboard API is missing or refused, and the copy command copied nothing.';
|
|
28
|
+
/**
|
|
29
|
+
* Copies `text` the way browsers did before the Clipboard API: it runs the
|
|
30
|
+
* `copy` command and puts the text on the `copy` event that command fires.
|
|
31
|
+
* Success is the event having fired, not the command's return value, which
|
|
32
|
+
* WebKit reports as `false` for a copy that happened.
|
|
33
|
+
*
|
|
34
|
+
* This is the library's only call to the deprecated `document.execCommand`,
|
|
35
|
+
* so it can go when browsers drop it. Until then it's what writes on a page
|
|
36
|
+
* without the Clipboard API, such as one served over plain `http://`.
|
|
37
|
+
*
|
|
38
|
+
* @param text - The text to copy.
|
|
39
|
+
* @returns Whether the `copy` event fired and took the text.
|
|
40
|
+
*/
|
|
41
|
+
function copyWithCommand(text) {
|
|
42
|
+
let written = false;
|
|
43
|
+
// WebKit enables the copy command only when something is selected or a
|
|
44
|
+
// `beforecopy` listener cancels that event, and nothing is selected here.
|
|
45
|
+
const enable = (event) => event.preventDefault();
|
|
46
|
+
// On `window` while capturing, so it runs before the page's listeners on
|
|
47
|
+
// the document and its elements. The copy is this hook's own, so it's
|
|
48
|
+
// stopped there: a page's own copy handler, such as one that adds a
|
|
49
|
+
// credit line to whatever is copied, never sees it to rewrite it.
|
|
50
|
+
const onCopy = (event) => {
|
|
51
|
+
if (!event.clipboardData)
|
|
52
|
+
return;
|
|
53
|
+
event.clipboardData.setData('text/plain', text);
|
|
54
|
+
event.preventDefault();
|
|
55
|
+
event.stopImmediatePropagation();
|
|
56
|
+
written = true;
|
|
57
|
+
};
|
|
58
|
+
window.addEventListener('beforecopy', enable, true);
|
|
59
|
+
window.addEventListener('copy', onCopy, true);
|
|
60
|
+
try {
|
|
61
|
+
document.execCommand('copy');
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
// An engine that refuses, or no longer has the command, can throw, and
|
|
65
|
+
// the event not firing already says so.
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
window.removeEventListener('beforecopy', enable, true);
|
|
69
|
+
window.removeEventListener('copy', onCopy, true);
|
|
70
|
+
}
|
|
71
|
+
return written;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Writes `text` through the Clipboard API, and through the copy command when
|
|
75
|
+
* that's missing or refuses. Where it's missing, the copy command runs before
|
|
76
|
+
* the first `await`, so it's still inside the click that called it. After a
|
|
77
|
+
* refusal it runs once the refusal arrives, which writes only where the
|
|
78
|
+
* browser still counts the click by then, and only if no later copy has
|
|
79
|
+
* started in the meantime, which it would otherwise write over.
|
|
80
|
+
*
|
|
81
|
+
* @param text - The text to write.
|
|
82
|
+
* @param isLatest - Whether this is still the latest copy.
|
|
83
|
+
* @returns `null` once it's written, or why it couldn't be.
|
|
84
|
+
*/
|
|
85
|
+
async function writeToClipboard(text, isLatest) {
|
|
86
|
+
let refusal = null;
|
|
87
|
+
// Read at the click, never while rendering, so there's always a navigator.
|
|
88
|
+
const { clipboard } = navigator;
|
|
89
|
+
if (clipboard?.writeText) {
|
|
90
|
+
try {
|
|
91
|
+
await clipboard.writeText(text);
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
catch (reason) {
|
|
95
|
+
refusal = reason;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (isLatest() && copyWithCommand(text))
|
|
99
|
+
return null;
|
|
100
|
+
return refusal instanceof Error ? refusal : new Error(cannotCopy);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Copies text to the clipboard through the Clipboard API, falling back to the copy command where that's missing or refused, and keeps a `copied` flag for `duration` after a copy.
|
|
104
|
+
*
|
|
105
|
+
* It announces nothing. A trigger of your own says "Copied" in a
|
|
106
|
+
* `role="status"` region that's in the page before the click; `CopyButton`
|
|
107
|
+
* is this hook with the button, the label and the announcement built in.
|
|
108
|
+
*
|
|
109
|
+
* A failed copy sets `error`, calls `onCopyError`, and ends `copied` if a
|
|
110
|
+
* copy before it had set it. When copies overlap, only the latest one's
|
|
111
|
+
* outcome sets the state and fires the callbacks, whichever finishes first.
|
|
112
|
+
* Nothing changes and no callback fires once the component using it has
|
|
113
|
+
* unmounted, though a write already under way still finishes.
|
|
114
|
+
*
|
|
115
|
+
* @function useClipboard
|
|
116
|
+
* @param options - How long `copied` lasts, and the callbacks.
|
|
117
|
+
* @returns `copy`, which writes, and the `copied` and `error` state.
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* const { copy, copied } = useClipboard();
|
|
121
|
+
* <Button onClick={() => copy(url)}>{copied ? 'Copied' : 'Copy link'}</Button>
|
|
122
|
+
*/
|
|
123
|
+
function useClipboard(options = {}) {
|
|
124
|
+
const [copied, setCopied] = useState(false);
|
|
125
|
+
const [error, setError] = useState(null);
|
|
126
|
+
const optionsRef = useLatestRef(options);
|
|
127
|
+
const copiedRef = useRef(false);
|
|
128
|
+
const timerRef = useRef(undefined);
|
|
129
|
+
const mountedRef = useRef(false);
|
|
130
|
+
// Counts the calls to `copy`, so only the latest one's outcome is kept.
|
|
131
|
+
const latestCallRef = useRef(0);
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
mountedRef.current = true;
|
|
134
|
+
return () => {
|
|
135
|
+
mountedRef.current = false;
|
|
136
|
+
clearTimeout(timerRef.current);
|
|
137
|
+
};
|
|
138
|
+
}, []);
|
|
139
|
+
const copy = useCallback(async (text) => {
|
|
140
|
+
latestCallRef.current += 1;
|
|
141
|
+
const call = latestCallRef.current;
|
|
142
|
+
const isLatest = () => call === latestCallRef.current;
|
|
143
|
+
const failure = await writeToClipboard(text, isLatest);
|
|
144
|
+
// A write overtaken by a later copy, which may finish first, reports
|
|
145
|
+
// through its promise only.
|
|
146
|
+
if (!mountedRef.current || !isLatest()) {
|
|
147
|
+
return failure === null;
|
|
148
|
+
}
|
|
149
|
+
clearTimeout(timerRef.current);
|
|
150
|
+
const { duration, onCopiedChange, onCopyError } = optionsRef.current;
|
|
151
|
+
const wasCopied = copiedRef.current;
|
|
152
|
+
copiedRef.current = failure === null;
|
|
153
|
+
setCopied(failure === null);
|
|
154
|
+
setError(failure);
|
|
155
|
+
if (failure) {
|
|
156
|
+
if (wasCopied)
|
|
157
|
+
onCopiedChange?.(false);
|
|
158
|
+
onCopyError?.(failure);
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
onCopiedChange?.(true);
|
|
162
|
+
const delay = delayOf(duration);
|
|
163
|
+
if (delay !== null) {
|
|
164
|
+
timerRef.current = setTimeout(() => {
|
|
165
|
+
copiedRef.current = false;
|
|
166
|
+
setCopied(false);
|
|
167
|
+
optionsRef.current.onCopiedChange?.(false);
|
|
168
|
+
}, delay);
|
|
169
|
+
}
|
|
170
|
+
return true;
|
|
171
|
+
}, [optionsRef]);
|
|
172
|
+
return { copy, copied, error };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export { useClipboard };
|
|
176
|
+
//# sourceMappingURL=useClipboard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useClipboard.js","sources":["../../../src/helpers/useClipboard.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\nimport { useLatestRef } from './useLatestRef';\n\n/** Options for useClipboard. */\nexport interface UseClipboardOptions {\n /**\n * How long `copied` stays `true` after a copy, in milliseconds. Another\n * copy restarts it. `0` turns the timer off, as Toast's `0` does, so\n * `copied` stays `true` until a later copy fails. A missing or `NaN` value\n * uses the default, a negative one ends it at once, and `Infinity`, or\n * anything longer than the longest delay `setTimeout` takes (2147483647 ms,\n * about 24.8 days), is that longest delay.\n * @defaultValue 2000\n */\n duration?: number;\n /**\n * Called with `true` after each copy that worked, a repeat included, and\n * with `false` when `duration` runs out or a later copy fails.\n */\n onCopiedChange?: (copied: boolean) => void;\n /**\n * Called when neither the Clipboard API nor the copy command could write,\n * with the Clipboard API's own error when it refused, or an `Error` saying\n * neither could.\n */\n onCopyError?: (error: Error) => void;\n}\n\n/** What useClipboard returns. */\nexport interface UseClipboardResult {\n /**\n * Writes `text` to the clipboard, and resolves `true` once it's written or\n * `false` when it couldn't be. It never rejects. Call it from a click or a\n * key press, with no `await` before it: browsers only allow a write\n * shortly after one. A call that a later one overtakes leaves `copied`,\n * `error` and the callbacks to the later one, and doesn't fall back to the\n * copy command once the later one has started.\n */\n copy: (text: string) => Promise<boolean>;\n /**\n * `true` for `duration` after a copy that worked, or, with a `duration` of\n * `0`, until a later copy fails.\n */\n copied: boolean;\n /** Why the last copy failed, or `null` after one that worked. */\n error: Error | null;\n}\n\n/** How long `copied` lasts when `duration` doesn't say, in ms. */\nconst defaultDuration = 2000;\n\n/**\n * The longest delay `setTimeout` takes, in ms. A longer one, `Infinity`\n * included, runs at once.\n */\nconst longestDelay = 2 ** 31 - 1;\n\n/**\n * `duration` as a delay `setTimeout` keeps, or `null` for no timer: `null`\n * for `0`, as on Toast, the default when it's missing or `NaN` (or a\n * JavaScript caller's `null`), `0` for a negative one, which ends `copied` at\n * once, and the longest delay for `Infinity` or anything longer.\n */\nconst delayOf = (duration: number | null | undefined) => {\n if (duration === 0) return null;\n return duration == null || Number.isNaN(duration)\n ? defaultDuration\n : Math.min(Math.max(duration, 0), longestDelay);\n};\n\n/** The error a copy reports when nothing could write and nothing said why. */\nconst cannotCopy =\n 'The Clipboard API is missing or refused, and the copy command copied nothing.';\n\n/**\n * Copies `text` the way browsers did before the Clipboard API: it runs the\n * `copy` command and puts the text on the `copy` event that command fires.\n * Success is the event having fired, not the command's return value, which\n * WebKit reports as `false` for a copy that happened.\n *\n * This is the library's only call to the deprecated `document.execCommand`,\n * so it can go when browsers drop it. Until then it's what writes on a page\n * without the Clipboard API, such as one served over plain `http://`.\n *\n * @param text - The text to copy.\n * @returns Whether the `copy` event fired and took the text.\n */\nfunction copyWithCommand(text: string): boolean {\n let written = false;\n // WebKit enables the copy command only when something is selected or a\n // `beforecopy` listener cancels that event, and nothing is selected here.\n const enable = (event: Event) => event.preventDefault();\n // On `window` while capturing, so it runs before the page's listeners on\n // the document and its elements. The copy is this hook's own, so it's\n // stopped there: a page's own copy handler, such as one that adds a\n // credit line to whatever is copied, never sees it to rewrite it.\n const onCopy = (event: ClipboardEvent) => {\n if (!event.clipboardData) return;\n event.clipboardData.setData('text/plain', text);\n event.preventDefault();\n event.stopImmediatePropagation();\n written = true;\n };\n window.addEventListener('beforecopy', enable, true);\n window.addEventListener('copy', onCopy, true);\n try {\n document.execCommand('copy');\n } catch {\n // An engine that refuses, or no longer has the command, can throw, and\n // the event not firing already says so.\n } finally {\n window.removeEventListener('beforecopy', enable, true);\n window.removeEventListener('copy', onCopy, true);\n }\n return written;\n}\n\n/**\n * Writes `text` through the Clipboard API, and through the copy command when\n * that's missing or refuses. Where it's missing, the copy command runs before\n * the first `await`, so it's still inside the click that called it. After a\n * refusal it runs once the refusal arrives, which writes only where the\n * browser still counts the click by then, and only if no later copy has\n * started in the meantime, which it would otherwise write over.\n *\n * @param text - The text to write.\n * @param isLatest - Whether this is still the latest copy.\n * @returns `null` once it's written, or why it couldn't be.\n */\nasync function writeToClipboard(\n text: string,\n isLatest: () => boolean\n): Promise<Error | null> {\n let refusal: unknown = null;\n // Read at the click, never while rendering, so there's always a navigator.\n const { clipboard } = navigator;\n if (clipboard?.writeText) {\n try {\n await clipboard.writeText(text);\n return null;\n } catch (reason) {\n refusal = reason;\n }\n }\n if (isLatest() && copyWithCommand(text)) return null;\n return refusal instanceof Error ? refusal : new Error(cannotCopy);\n}\n\n/**\n * Copies text to the clipboard through the Clipboard API, falling back to the copy command where that's missing or refused, and keeps a `copied` flag for `duration` after a copy.\n *\n * It announces nothing. A trigger of your own says \"Copied\" in a\n * `role=\"status\"` region that's in the page before the click; `CopyButton`\n * is this hook with the button, the label and the announcement built in.\n *\n * A failed copy sets `error`, calls `onCopyError`, and ends `copied` if a\n * copy before it had set it. When copies overlap, only the latest one's\n * outcome sets the state and fires the callbacks, whichever finishes first.\n * Nothing changes and no callback fires once the component using it has\n * unmounted, though a write already under way still finishes.\n *\n * @function useClipboard\n * @param options - How long `copied` lasts, and the callbacks.\n * @returns `copy`, which writes, and the `copied` and `error` state.\n *\n * @example\n * const { copy, copied } = useClipboard();\n * <Button onClick={() => copy(url)}>{copied ? 'Copied' : 'Copy link'}</Button>\n */\nexport function useClipboard(\n options: UseClipboardOptions = {}\n): UseClipboardResult {\n const [copied, setCopied] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const optionsRef = useLatestRef(options);\n const copiedRef = useRef(false);\n const timerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n const mountedRef = useRef(false);\n // Counts the calls to `copy`, so only the latest one's outcome is kept.\n const latestCallRef = useRef(0);\n\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n clearTimeout(timerRef.current);\n };\n }, []);\n\n const copy = useCallback(\n async (text: string) => {\n latestCallRef.current += 1;\n const call = latestCallRef.current;\n const isLatest = () => call === latestCallRef.current;\n const failure = await writeToClipboard(text, isLatest);\n // A write overtaken by a later copy, which may finish first, reports\n // through its promise only.\n if (!mountedRef.current || !isLatest()) {\n return failure === null;\n }\n clearTimeout(timerRef.current);\n const { duration, onCopiedChange, onCopyError } = optionsRef.current;\n const wasCopied = copiedRef.current;\n copiedRef.current = failure === null;\n setCopied(failure === null);\n setError(failure);\n if (failure) {\n if (wasCopied) onCopiedChange?.(false);\n onCopyError?.(failure);\n return false;\n }\n onCopiedChange?.(true);\n const delay = delayOf(duration);\n if (delay !== null) {\n timerRef.current = setTimeout(() => {\n copiedRef.current = false;\n setCopied(false);\n optionsRef.current.onCopiedChange?.(false);\n }, delay);\n }\n return true;\n },\n [optionsRef]\n );\n\n return { copy, copied, error };\n}\n"],"names":[],"mappings":"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"}
|