mirror of
https://github.com/hex248/ob248.com.git
synced 2026-02-07 18:23:04 +00:00
308 lines
10 KiB
JavaScript
308 lines
10 KiB
JavaScript
"use client";
|
|
|
|
// src/password-toggle-field.tsx
|
|
import * as React from "react";
|
|
import { flushSync } from "react-dom";
|
|
import { composeEventHandlers } from "@radix-ui/primitive";
|
|
import { useControllableState } from "@radix-ui/react-use-controllable-state";
|
|
import { Primitive } from "@radix-ui/react-primitive";
|
|
import { useComposedRefs } from "@radix-ui/react-compose-refs";
|
|
import { useId } from "@radix-ui/react-id";
|
|
import { useIsHydrated } from "@radix-ui/react-use-is-hydrated";
|
|
import { useEffectEvent } from "@radix-ui/react-use-effect-event";
|
|
import { createContextScope } from "@radix-ui/react-context";
|
|
import { jsx } from "react/jsx-runtime";
|
|
var PASSWORD_TOGGLE_FIELD_NAME = "PasswordToggleField";
|
|
var [createPasswordToggleFieldContext] = createContextScope(PASSWORD_TOGGLE_FIELD_NAME);
|
|
var [PasswordToggleFieldProvider, usePasswordToggleFieldContext] = createPasswordToggleFieldContext(PASSWORD_TOGGLE_FIELD_NAME);
|
|
var INITIAL_FOCUS_STATE = {
|
|
clickTriggered: false,
|
|
selectionStart: null,
|
|
selectionEnd: null
|
|
};
|
|
var PasswordToggleField = ({
|
|
__scopePasswordToggleField,
|
|
...props
|
|
}) => {
|
|
const baseId = useId(props.id);
|
|
const defaultInputId = `${baseId}-input`;
|
|
const [inputIdState, setInputIdState] = React.useState(defaultInputId);
|
|
const inputId = inputIdState ?? defaultInputId;
|
|
const syncInputId = React.useCallback(
|
|
(providedId) => setInputIdState(providedId != null ? String(providedId) : null),
|
|
[]
|
|
);
|
|
const { visible: visibleProp, defaultVisible, onVisiblityChange, children } = props;
|
|
const [visible = false, setVisible] = useControllableState({
|
|
caller: PASSWORD_TOGGLE_FIELD_NAME,
|
|
prop: visibleProp,
|
|
defaultProp: defaultVisible ?? false,
|
|
onChange: onVisiblityChange
|
|
});
|
|
const inputRef = React.useRef(null);
|
|
const focusState = React.useRef(INITIAL_FOCUS_STATE);
|
|
return /* @__PURE__ */ jsx(
|
|
PasswordToggleFieldProvider,
|
|
{
|
|
scope: __scopePasswordToggleField,
|
|
inputId,
|
|
inputRef,
|
|
setVisible,
|
|
syncInputId,
|
|
visible,
|
|
focusState,
|
|
children
|
|
}
|
|
);
|
|
};
|
|
PasswordToggleField.displayName = PASSWORD_TOGGLE_FIELD_NAME;
|
|
var PASSWORD_TOGGLE_FIELD_INPUT_NAME = PASSWORD_TOGGLE_FIELD_NAME + "Input";
|
|
var PasswordToggleFieldInput = React.forwardRef(
|
|
({
|
|
__scopePasswordToggleField,
|
|
autoComplete = "current-password",
|
|
autoCapitalize = "off",
|
|
spellCheck = false,
|
|
id: idProp,
|
|
...props
|
|
}, forwardedRef) => {
|
|
const { visible, inputRef, inputId, syncInputId, setVisible, focusState } = usePasswordToggleFieldContext(PASSWORD_TOGGLE_FIELD_INPUT_NAME, __scopePasswordToggleField);
|
|
React.useEffect(() => {
|
|
syncInputId(idProp);
|
|
}, [idProp, syncInputId]);
|
|
const _setVisible = useEffectEvent(setVisible);
|
|
React.useEffect(() => {
|
|
const inputElement = inputRef.current;
|
|
const form = inputElement?.form;
|
|
if (!form) {
|
|
return;
|
|
}
|
|
const controller = new AbortController();
|
|
form.addEventListener(
|
|
"reset",
|
|
(event) => {
|
|
if (!event.defaultPrevented) {
|
|
_setVisible(false);
|
|
}
|
|
},
|
|
{ signal: controller.signal }
|
|
);
|
|
form.addEventListener(
|
|
"submit",
|
|
() => {
|
|
_setVisible(false);
|
|
},
|
|
{ signal: controller.signal }
|
|
);
|
|
return () => {
|
|
controller.abort();
|
|
};
|
|
}, [inputRef, _setVisible]);
|
|
return /* @__PURE__ */ jsx(
|
|
Primitive.input,
|
|
{
|
|
...props,
|
|
id: idProp ?? inputId,
|
|
autoCapitalize,
|
|
autoComplete,
|
|
ref: useComposedRefs(forwardedRef, inputRef),
|
|
spellCheck,
|
|
type: visible ? "text" : "password",
|
|
onBlur: composeEventHandlers(props.onBlur, (event) => {
|
|
const { selectionStart, selectionEnd } = event.currentTarget;
|
|
focusState.current.selectionStart = selectionStart;
|
|
focusState.current.selectionEnd = selectionEnd;
|
|
})
|
|
}
|
|
);
|
|
}
|
|
);
|
|
PasswordToggleFieldInput.displayName = PASSWORD_TOGGLE_FIELD_INPUT_NAME;
|
|
var PASSWORD_TOGGLE_FIELD_TOGGLE_NAME = PASSWORD_TOGGLE_FIELD_NAME + "Toggle";
|
|
var PasswordToggleFieldToggle = React.forwardRef(
|
|
({
|
|
__scopePasswordToggleField,
|
|
onClick,
|
|
onPointerDown,
|
|
onPointerCancel,
|
|
onPointerUp,
|
|
onFocus,
|
|
children,
|
|
"aria-label": ariaLabelProp,
|
|
"aria-controls": ariaControls,
|
|
"aria-hidden": ariaHidden,
|
|
tabIndex,
|
|
...props
|
|
}, forwardedRef) => {
|
|
const { setVisible, visible, inputRef, inputId, focusState } = usePasswordToggleFieldContext(
|
|
PASSWORD_TOGGLE_FIELD_TOGGLE_NAME,
|
|
__scopePasswordToggleField
|
|
);
|
|
const [internalAriaLabel, setInternalAriaLabel] = React.useState(void 0);
|
|
const elementRef = React.useRef(null);
|
|
const ref = useComposedRefs(forwardedRef, elementRef);
|
|
const isHydrated = useIsHydrated();
|
|
React.useEffect(() => {
|
|
const element = elementRef.current;
|
|
if (!element || ariaLabelProp) {
|
|
setInternalAriaLabel(void 0);
|
|
return;
|
|
}
|
|
const DEFAULT_ARIA_LABEL = visible ? "Hide password" : "Show password";
|
|
function checkForInnerTextLabel(textContent) {
|
|
const text = textContent ? textContent : void 0;
|
|
setInternalAriaLabel(text ? void 0 : DEFAULT_ARIA_LABEL);
|
|
}
|
|
checkForInnerTextLabel(element.textContent);
|
|
const observer = new MutationObserver((entries) => {
|
|
let textContent;
|
|
for (const entry of entries) {
|
|
if (entry.type === "characterData") {
|
|
if (element.textContent) {
|
|
textContent = element.textContent;
|
|
}
|
|
}
|
|
}
|
|
checkForInnerTextLabel(textContent);
|
|
});
|
|
observer.observe(element, { characterData: true, subtree: true });
|
|
return () => {
|
|
observer.disconnect();
|
|
};
|
|
}, [visible, ariaLabelProp]);
|
|
const ariaLabel = ariaLabelProp || internalAriaLabel;
|
|
if (!isHydrated) {
|
|
ariaHidden ??= true;
|
|
tabIndex ??= -1;
|
|
} else {
|
|
ariaControls ??= inputId;
|
|
}
|
|
React.useEffect(() => {
|
|
let cleanup = () => {
|
|
};
|
|
const ownerWindow = elementRef.current?.ownerDocument?.defaultView || window;
|
|
const reset = () => focusState.current.clickTriggered = false;
|
|
const handlePointerUp = () => cleanup = requestIdleCallback(ownerWindow, reset);
|
|
ownerWindow.addEventListener("pointerup", handlePointerUp);
|
|
return () => {
|
|
cleanup();
|
|
ownerWindow.removeEventListener("pointerup", handlePointerUp);
|
|
};
|
|
}, [focusState]);
|
|
return /* @__PURE__ */ jsx(
|
|
Primitive.button,
|
|
{
|
|
"aria-controls": ariaControls,
|
|
"aria-hidden": ariaHidden,
|
|
"aria-label": ariaLabel,
|
|
ref,
|
|
id: inputId,
|
|
...props,
|
|
onPointerDown: composeEventHandlers(onPointerDown, () => {
|
|
focusState.current.clickTriggered = true;
|
|
}),
|
|
onPointerCancel: (event) => {
|
|
onPointerCancel?.(event);
|
|
focusState.current = INITIAL_FOCUS_STATE;
|
|
},
|
|
onClick: (event) => {
|
|
onClick?.(event);
|
|
if (event.defaultPrevented) {
|
|
focusState.current = INITIAL_FOCUS_STATE;
|
|
return;
|
|
}
|
|
flushSync(() => {
|
|
setVisible((s) => !s);
|
|
});
|
|
if (focusState.current.clickTriggered) {
|
|
const input = inputRef.current;
|
|
if (input) {
|
|
const { selectionStart, selectionEnd } = focusState.current;
|
|
input.focus();
|
|
if (selectionStart !== null || selectionEnd !== null) {
|
|
requestAnimationFrame(() => {
|
|
if (input.ownerDocument.activeElement === input) {
|
|
input.selectionStart = selectionStart;
|
|
input.selectionEnd = selectionEnd;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
focusState.current = INITIAL_FOCUS_STATE;
|
|
},
|
|
onPointerUp: (event) => {
|
|
onPointerUp?.(event);
|
|
setTimeout(() => {
|
|
focusState.current = INITIAL_FOCUS_STATE;
|
|
}, 50);
|
|
},
|
|
type: "button",
|
|
children
|
|
}
|
|
);
|
|
}
|
|
);
|
|
PasswordToggleFieldToggle.displayName = PASSWORD_TOGGLE_FIELD_TOGGLE_NAME;
|
|
var PASSWORD_TOGGLE_FIELD_SLOT_NAME = PASSWORD_TOGGLE_FIELD_NAME + "Slot";
|
|
var PasswordToggleFieldSlot = ({
|
|
__scopePasswordToggleField,
|
|
...props
|
|
}) => {
|
|
const { visible } = usePasswordToggleFieldContext(
|
|
PASSWORD_TOGGLE_FIELD_SLOT_NAME,
|
|
__scopePasswordToggleField
|
|
);
|
|
return "render" in props ? (
|
|
//
|
|
props.render({ visible })
|
|
) : visible ? props.visible : props.hidden;
|
|
};
|
|
PasswordToggleFieldSlot.displayName = PASSWORD_TOGGLE_FIELD_SLOT_NAME;
|
|
var PASSWORD_TOGGLE_FIELD_ICON_NAME = PASSWORD_TOGGLE_FIELD_NAME + "Icon";
|
|
var PasswordToggleFieldIcon = React.forwardRef(
|
|
({
|
|
__scopePasswordToggleField,
|
|
// @ts-expect-error
|
|
children,
|
|
...props
|
|
}, forwardedRef) => {
|
|
const { visible } = usePasswordToggleFieldContext(
|
|
PASSWORD_TOGGLE_FIELD_ICON_NAME,
|
|
__scopePasswordToggleField
|
|
);
|
|
const { visible: visibleIcon, hidden: hiddenIcon, ...domProps } = props;
|
|
return /* @__PURE__ */ jsx(Primitive.svg, { ...domProps, ref: forwardedRef, "aria-hidden": true, asChild: true, children: visible ? visibleIcon : hiddenIcon });
|
|
}
|
|
);
|
|
PasswordToggleFieldIcon.displayName = PASSWORD_TOGGLE_FIELD_ICON_NAME;
|
|
function requestIdleCallback(window2, callback, options) {
|
|
if (window2.requestIdleCallback) {
|
|
const id2 = window2.requestIdleCallback(callback, options);
|
|
return () => {
|
|
window2.cancelIdleCallback(id2);
|
|
};
|
|
}
|
|
const start = Date.now();
|
|
const id = window2.setTimeout(() => {
|
|
const timeRemaining = () => Math.max(0, 50 - (Date.now() - start));
|
|
callback({ didTimeout: false, timeRemaining });
|
|
}, 1);
|
|
return () => {
|
|
window2.clearTimeout(id);
|
|
};
|
|
}
|
|
export {
|
|
PasswordToggleFieldIcon as Icon,
|
|
PasswordToggleFieldInput as Input,
|
|
PasswordToggleField,
|
|
PasswordToggleFieldIcon,
|
|
PasswordToggleFieldInput,
|
|
PasswordToggleFieldSlot,
|
|
PasswordToggleFieldToggle,
|
|
PasswordToggleField as Root,
|
|
PasswordToggleFieldSlot as Slot,
|
|
PasswordToggleFieldToggle as Toggle
|
|
};
|
|
//# sourceMappingURL=index.mjs.map
|