@primer/components
Version:
Primer react components
318 lines (261 loc) • 11.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _react = _interopRequireWildcard(require("react"));
var _props = require("@styled-system/props");
var _focusZone = require("./behaviors/focusZone");
var _useCombinedRefs = require("./hooks/useCombinedRefs");
var _useFocusZone = require("./hooks/useFocusZone");
var _Token = _interopRequireDefault(require("./Token/Token"));
var _hooks = require("./hooks");
var _UnstyledTextInput = _interopRequireDefault(require("./_UnstyledTextInput"));
var _TextInputWrapper = _interopRequireDefault(require("./_TextInputWrapper"));
var _Box = _interopRequireDefault(require("./Box"));
var _Text = _interopRequireDefault(require("./Text"));
var _iterateFocusableElements = require("./utils/iterateFocusableElements");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
const overflowCountFontSizeMap = {
small: 0,
medium: 1,
large: 1,
extralarge: 2
}; // using forwardRef is important so that other components (ex. Autocomplete) can use the ref
function TextInputWithTokensInnerComponent({
icon: IconComponent,
contrast,
className,
block,
disabled,
theme,
sx: sxProp,
tokens,
onTokenRemove,
tokenComponent: TokenComponent,
preventTokenWrapping,
size,
hideTokenRemoveButtons,
maxHeight,
width: widthProp,
minWidth: minWidthProp,
maxWidth: maxWidthProp,
variant: variantProp,
visibleTokenCount,
...rest
}, externalRef) {
const {
onBlur,
onFocus,
onKeyDown,
...inputPropsRest
} = (0, _props.omit)(rest);
const ref = (0, _hooks.useProvidedRefOrCreate)(externalRef);
const localInputRef = (0, _react.useRef)(null);
const combinedInputRef = (0, _useCombinedRefs.useCombinedRefs)(localInputRef, ref);
const [selectedTokenIndex, setSelectedTokenIndex] = (0, _react.useState)();
const [tokensAreTruncated, setTokensAreTruncated] = (0, _react.useState)(Boolean(visibleTokenCount));
const {
containerRef
} = (0, _useFocusZone.useFocusZone)({
focusOutBehavior: 'wrap',
bindKeys: _focusZone.FocusKeys.ArrowHorizontal | _focusZone.FocusKeys.HomeAndEnd,
focusableElementFilter: element => {
return !element.getAttributeNames().includes('aria-hidden');
},
getNextFocusable: direction => {
var _containerRef$current;
if (!selectedTokenIndex && selectedTokenIndex !== 0) {
return undefined;
}
let nextIndex = selectedTokenIndex + 1; // "+ 1" accounts for the first element: the text input
if (direction === 'next') {
nextIndex += 1;
}
if (direction === 'previous') {
nextIndex -= 1;
}
if (nextIndex > tokens.length || nextIndex < 1) {
return combinedInputRef.current || undefined;
}
return (_containerRef$current = containerRef.current) === null || _containerRef$current === void 0 ? void 0 : _containerRef$current.children[nextIndex];
}
}, [selectedTokenIndex]);
const handleTokenRemove = tokenId => {
onTokenRemove(tokenId); // HACK: wait a tick for the the token node to be removed from the DOM
setTimeout(() => {
var _containerRef$current2, _containerRef$current3;
const nextElementToFocus = (_containerRef$current2 = containerRef.current) === null || _containerRef$current2 === void 0 ? void 0 : _containerRef$current2.children[selectedTokenIndex || 0]; // when removing the first token by keying "Backspace" or "Delete",
// `nextFocusableElement` is the div that wraps the input
const firstFocusable = nextElementToFocus && (0, _iterateFocusableElements.isFocusable)(nextElementToFocus) ? nextElementToFocus : Array.from(((_containerRef$current3 = containerRef.current) === null || _containerRef$current3 === void 0 ? void 0 : _containerRef$current3.children) || []).find(el => (0, _iterateFocusableElements.isFocusable)(el));
if (firstFocusable) {
firstFocusable.focus();
} else {
var _ref$current;
// if there are no tokens left, focus the input
(_ref$current = ref.current) === null || _ref$current === void 0 ? void 0 : _ref$current.focus();
}
}, 0);
};
const handleTokenFocus = tokenIndex => () => {
setSelectedTokenIndex(tokenIndex);
};
const handleTokenBlur = () => {
setSelectedTokenIndex(undefined); // HACK: wait a tick and check the focused element before hiding truncated tokens
// this prevents the tokens from hiding when the user is moving focus between tokens,
// but still hides the tokens when the user blurs the token by tabbing out or clicking somewhere else on the page
setTimeout(() => {
var _containerRef$current4;
if (!((_containerRef$current4 = containerRef.current) !== null && _containerRef$current4 !== void 0 && _containerRef$current4.contains(document.activeElement)) && visibleTokenCount) {
setTokensAreTruncated(true);
}
}, 0);
};
const handleTokenKeyUp = event => {
if (event.key === 'Escape') {
var _ref$current2;
(_ref$current2 = ref.current) === null || _ref$current2 === void 0 ? void 0 : _ref$current2.focus();
}
};
const handleInputFocus = event => {
onFocus && onFocus(event);
setSelectedTokenIndex(undefined);
visibleTokenCount && setTokensAreTruncated(false);
};
const handleInputBlur = event => {
onBlur && onBlur(event); // HACK: wait a tick and check the focused element before hiding truncated tokens
// this prevents the tokens from hiding when the user is moving focus from the input to a token,
// but still hides the tokens when the user blurs the input by tabbing out or clicking somewhere else on the page
setTimeout(() => {
var _containerRef$current5;
if (!((_containerRef$current5 = containerRef.current) !== null && _containerRef$current5 !== void 0 && _containerRef$current5.contains(document.activeElement)) && visibleTokenCount) {
setTokensAreTruncated(true);
}
}, 0);
};
const handleInputKeyDown = e => {
var _ref$current3;
if (onKeyDown) {
onKeyDown(e);
}
if ((_ref$current3 = ref.current) !== null && _ref$current3 !== void 0 && _ref$current3.value) {
return;
}
const lastToken = tokens[tokens.length - 1];
if (e.key === 'Backspace' && lastToken) {
handleTokenRemove(lastToken.id);
if (ref.current) {
// TODO: eliminate the first hack by making changes to the Autocomplete component
//
// HACKS:
// 1. Directly setting `ref.current.value` instead of updating state because the autocomplete
// highlight behavior doesn't work correctly if we update the value with a setState action in onChange
// 2. Adding an extra space so that when I backspace, it doesn't delete the last letter
ref.current.value = `${lastToken.text} `;
} // HACK: for some reason we need to wait a tick for `.select()` to work
setTimeout(() => {
var _ref$current4;
(_ref$current4 = ref.current) === null || _ref$current4 === void 0 ? void 0 : _ref$current4.select();
}, 0);
}
};
const focusInput = () => {
var _combinedInputRef$cur;
(_combinedInputRef$cur = combinedInputRef.current) === null || _combinedInputRef$cur === void 0 ? void 0 : _combinedInputRef$cur.focus();
};
const preventTokenClickPropagation = event => {
event.stopPropagation();
};
const visibleTokens = tokensAreTruncated ? tokens.slice(0, visibleTokenCount) : tokens;
return /*#__PURE__*/_react.default.createElement(_TextInputWrapper.default, {
block: block,
className: className,
contrast: contrast,
disabled: disabled,
hasIcon: !!IconComponent,
theme: theme,
width: widthProp,
minWidth: minWidthProp,
maxWidth: maxWidthProp,
variant: variantProp,
onClick: focusInput,
sx: { ...(block ? {
display: 'flex',
width: '100%'
} : {}),
...(maxHeight ? {
maxHeight,
overflow: 'auto'
} : {}),
...(preventTokenWrapping ? {
overflow: 'auto'
} : {}),
...sxProp
}
}, /*#__PURE__*/_react.default.createElement(_Box.default, {
ref: containerRef,
display: "flex",
sx: {
alignItems: 'center',
flexWrap: preventTokenWrapping ? 'nowrap' : 'wrap',
marginLeft: '-0.25rem',
marginBottom: '-0.25rem',
flexGrow: 1,
'> *': {
flexShrink: 0,
marginLeft: '0.25rem',
marginBottom: '0.25rem'
}
}
}, /*#__PURE__*/_react.default.createElement(_Box.default, {
sx: {
order: 1,
flexGrow: 1
}
}, IconComponent && /*#__PURE__*/_react.default.createElement(IconComponent, {
className: "TextInput-icon"
}), /*#__PURE__*/_react.default.createElement(_UnstyledTextInput.default, _extends({
ref: combinedInputRef,
disabled: disabled,
onFocus: handleInputFocus,
onBlur: handleInputBlur,
onKeyDown: handleInputKeyDown,
type: "text",
sx: {
height: '100%'
}
}, inputPropsRest))), TokenComponent ? visibleTokens.map(({
id,
...tokenRest
}, i) => /*#__PURE__*/_react.default.createElement(TokenComponent, _extends({
key: id,
onFocus: handleTokenFocus(i),
onBlur: handleTokenBlur,
onKeyUp: handleTokenKeyUp,
onClick: preventTokenClickPropagation,
isSelected: selectedTokenIndex === i,
onRemove: () => {
handleTokenRemove(id);
},
hideRemoveButton: hideTokenRemoveButtons,
size: size,
tabIndex: 0
}, tokenRest))) : null, tokensAreTruncated ? /*#__PURE__*/_react.default.createElement(_Text.default, {
color: "fg.muted",
fontSize: size && overflowCountFontSizeMap[size]
}, "+", tokens.length - visibleTokens.length) : null));
}
TextInputWithTokensInnerComponent.displayName = "TextInputWithTokensInnerComponent";
const TextInputWithTokens = /*#__PURE__*/_react.default.forwardRef(TextInputWithTokensInnerComponent);
TextInputWithTokens.defaultProps = {
tokenComponent: _Token.default,
size: 'extralarge',
hideTokenRemoveButtons: false,
preventTokenWrapping: false
};
TextInputWithTokens.displayName = 'TextInputWithTokens';
var _default = TextInputWithTokens;
exports.default = _default;