@reykjavik/hanna-react 0.10.67 → 0.10.69
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/AccordionList.d.ts +12 -4
- package/AccordionList.js +20 -10
- package/ArticleCards.d.ts +2 -2
- package/ArticleCarousel/_ArticleCarouselCard.d.ts +1 -0
- package/ArticleCarousel/_ArticleCarouselCard.js +3 -3
- package/CHANGELOG.md +19 -0
- package/ContentArticle.d.ts +1 -0
- package/ContentArticle.js +1 -1
- package/Heading.d.ts +5 -0
- package/Heading.js +2 -1
- package/Selectbox.d.ts +6 -6
- package/SiteSearchInput.d.ts +1 -1
- package/TextInput.js +8 -1
- package/package.json +2 -2
- package/utils/useMixedControlState.d.ts +86 -24
- package/utils/useMixedControlState.js +75 -138
package/AccordionList.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SSRSupport } from '@hugsmidjan/react/hooks';
|
|
2
2
|
import { SeenProp } from './utils/seenEffect';
|
|
3
|
-
declare type AccordionListItemProps = {
|
|
3
|
+
export declare type AccordionListItemProps = {
|
|
4
4
|
title: string | JSX.Element;
|
|
5
5
|
content: string | JSX.Element | undefined;
|
|
6
6
|
id?: string;
|
|
@@ -8,10 +8,18 @@ declare type AccordionListItemProps = {
|
|
|
8
8
|
};
|
|
9
9
|
export declare type AccordionListProps = {
|
|
10
10
|
items: Array<AccordionListItemProps>;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
/**
|
|
11
|
+
/** Index of the currently open items (controlled use) */
|
|
12
|
+
open?: Array<number>;
|
|
13
|
+
/** Called whenever an AccodrionList item is toggled */
|
|
14
|
+
onToggle?: (data: {
|
|
15
|
+
newOpen: Array<number>;
|
|
16
|
+
index: number;
|
|
17
|
+
opened: boolean;
|
|
18
|
+
}) => void;
|
|
19
|
+
/** Index of those items that should start open (uncontrolled use) */
|
|
14
20
|
defaultOpen?: Array<number>;
|
|
21
|
+
wide?: boolean;
|
|
22
|
+
ssr?: SSRSupport;
|
|
15
23
|
} & SeenProp;
|
|
16
24
|
declare const AccordionList: (props: AccordionListProps) => JSX.Element;
|
|
17
25
|
export default AccordionList;
|
package/AccordionList.js
CHANGED
|
@@ -5,25 +5,35 @@ const react_1 = tslib_1.__importStar(require("react"));
|
|
|
5
5
|
const hooks_1 = require("@hugsmidjan/react/hooks");
|
|
6
6
|
const getBemClass_1 = tslib_1.__importDefault(require("@hugsmidjan/react/utils/getBemClass"));
|
|
7
7
|
const seenEffect_1 = require("./utils/seenEffect");
|
|
8
|
+
const utils_1 = require("./utils");
|
|
8
9
|
const AccordionListItem = (props) => {
|
|
9
|
-
const { title, content, id, disabled = false, ssr } = props;
|
|
10
|
-
// TODO: Add controlled state support to this component, and then switch
|
|
11
|
-
// to usw the hooks exported from `utils/useMixecControlState.ts`
|
|
12
|
-
const [open, setOpen] = (0, react_1.useState)(props.defaultOpen);
|
|
10
|
+
const { title, content, id, disabled = false, ssr, open, onToggle } = props;
|
|
13
11
|
const defaultOpen = (0, react_1.useRef)(props.defaultOpen);
|
|
14
12
|
const domid = (0, hooks_1.useDomid)();
|
|
15
13
|
const isBrowser = (0, hooks_1.useIsBrowserSide)(ssr);
|
|
16
14
|
const itemDisabled = (isBrowser && disabled) || !content;
|
|
17
15
|
return (react_1.default.createElement("div", { className: (0, getBemClass_1.default)('AccordionList__item', [itemDisabled && 'disabled']), id: id, "data-start-open": defaultOpen.current, "data-sprinkled": isBrowser },
|
|
18
|
-
react_1.default.createElement("h3", { className: "AccordionList__title" }, isBrowser ? (react_1.default.createElement("button", { type: "button", className: "AccordionList__button", "aria-controls": domid, "aria-expanded": open || undefined, onClick:
|
|
19
|
-
setOpen(!open);
|
|
20
|
-
}, disabled: itemDisabled }, title)) : (title)),
|
|
16
|
+
react_1.default.createElement("h3", { className: "AccordionList__title" }, isBrowser ? (react_1.default.createElement("button", { type: "button", className: "AccordionList__button", "aria-controls": domid, "aria-expanded": open || undefined, onClick: onToggle, disabled: itemDisabled }, title)) : (title)),
|
|
21
17
|
react_1.default.createElement("div", { id: isBrowser && domid, className: "AccordionList__content", hidden: isBrowser && (!open || itemDisabled) }, content)));
|
|
22
18
|
};
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
19
|
const AccordionList = (props) => {
|
|
25
|
-
const { items, ssr, wide,
|
|
20
|
+
const { items, ssr, wide, startSeen, defaultOpen } = props;
|
|
26
21
|
const [ref] = (0, seenEffect_1.useSeenEffect)(startSeen);
|
|
27
|
-
|
|
22
|
+
const [open, setOpenArray, mode] = (0, utils_1.useMixedControlState)(props, 'open', []);
|
|
23
|
+
const onToggle = (index) => {
|
|
24
|
+
setOpenArray((prevOpen) => {
|
|
25
|
+
const opened = !prevOpen.includes(index);
|
|
26
|
+
const newOpen = opened
|
|
27
|
+
? prevOpen.concat(index)
|
|
28
|
+
: prevOpen.filter((idx) => idx !== index);
|
|
29
|
+
props.onToggle && props.onToggle({ newOpen, index, opened });
|
|
30
|
+
return newOpen;
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
return (react_1.default.createElement("div", { className: (0, getBemClass_1.default)('AccordionList', [wide && 'wide']), ref: ref },
|
|
34
|
+
String(open),
|
|
35
|
+
react_1.default.createElement("br", null),
|
|
36
|
+
String(mode),
|
|
37
|
+
items.map((item, i) => (react_1.default.createElement(AccordionListItem, Object.assign({ key: i }, item, { ssr: ssr, open: open.includes(i), onToggle: () => onToggle(i), defaultOpen: defaultOpen && defaultOpen.includes(i) }))))));
|
|
28
38
|
};
|
|
29
39
|
exports.default = AccordionList;
|
package/ArticleCards.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Cleanup } from '@reykjavik/hanna-utils';
|
|
2
2
|
import { ImageCardListProps, ImageCardProps } from './_abstract/_CardList';
|
|
3
3
|
export declare type ArticleCardProps = ImageCardProps;
|
|
4
|
-
export declare type ArticleCardsProps =
|
|
4
|
+
export declare type ArticleCardsProps = Cleanup<Pick<ImageCardListProps, 'cards' | 'imgPlaceholder'>>;
|
|
5
5
|
declare const ArticleCards: (props: ArticleCardsProps) => JSX.Element;
|
|
6
6
|
export default ArticleCards;
|
|
@@ -9,11 +9,11 @@ const _Image_1 = tslib_1.__importDefault(require("../_abstract/_Image"));
|
|
|
9
9
|
const _Link_1 = require("../_abstract/_Link");
|
|
10
10
|
const constants_1 = require("../constants");
|
|
11
11
|
const ArticleCarouselCard = (props) => {
|
|
12
|
-
const { date, title, summary, href, moreLabel, color, theme, illustration, image } = props;
|
|
12
|
+
const { date, title, summary, href, target, moreLabel, color, theme, illustration, image, } = props;
|
|
13
13
|
const photo = image === null || image === void 0 ? void 0 : image.photo;
|
|
14
14
|
const imageProps = illustration ? { src: (0, assets_1.getIllustrationUrl)(illustration) } : image;
|
|
15
15
|
return (react_1.default.createElement("div", { className: "ArticleCarouselCard", "data-color": color && constants_1.colorFamilies[color], "data-color-theme": !color ? theme && constants_1.themeOptions[theme] : undefined },
|
|
16
|
-
react_1.default.createElement(_Link_1.Link, { className: "ArticleCarouselCard__link", href: href },
|
|
16
|
+
react_1.default.createElement(_Link_1.Link, { className: "ArticleCarouselCard__link", href: href, target: target },
|
|
17
17
|
' ',
|
|
18
18
|
react_1.default.createElement(_Image_1.default, Object.assign({ placeholder: true, className: (0, getBemClass_1.default)('ArticleCarouselCard__illustration', photo && 'photo') }, imageProps)),
|
|
19
19
|
react_1.default.createElement("h3", { className: "ArticleCarouselCard__title" }, title),
|
|
@@ -21,7 +21,7 @@ const ArticleCarouselCard = (props) => {
|
|
|
21
21
|
' ',
|
|
22
22
|
date && react_1.default.createElement("span", { className: "ArticleCarouselCard__date" }, date),
|
|
23
23
|
react_1.default.createElement("div", { className: "ArticleCarouselCard__summary" }, summary),
|
|
24
|
-
moreLabel && (react_1.default.createElement(_Link_1.Link, { className: "ArticleCarouselCard__morelink", href: href, "aria-label": title },
|
|
24
|
+
moreLabel && (react_1.default.createElement(_Link_1.Link, { className: "ArticleCarouselCard__morelink", href: href, target: target, "aria-label": title },
|
|
25
25
|
' ',
|
|
26
26
|
moreLabel,
|
|
27
27
|
' '))));
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,25 @@
|
|
|
4
4
|
|
|
5
5
|
- ... <!-- Add new lines here. -->
|
|
6
6
|
|
|
7
|
+
## 0.10.69
|
|
8
|
+
|
|
9
|
+
_2022-09-27_
|
|
10
|
+
|
|
11
|
+
- feat: Add controlled props `open` and `onToggle` to `AccordionList` — it now
|
|
12
|
+
supports either [controlled or uncontrolled](./README-conventions.md) use.
|
|
13
|
+
- fix: Squash `useMixedControlState` bugs/misbehavior and improve its typing
|
|
14
|
+
|
|
15
|
+
## 0.10.68
|
|
16
|
+
|
|
17
|
+
_2022-09-26_
|
|
18
|
+
|
|
19
|
+
- feat: Add prop `target` to `ArticleCarouselCardProps`
|
|
20
|
+
- feat: Improve `Selectbox`'s `options` and `onSelected` prop generics
|
|
21
|
+
- feat: Add prop `headingTag` to `ContentArticle` to support `<h1/>`
|
|
22
|
+
- feat: Add prop `forceH1` to `Heading`
|
|
23
|
+
- fix: Mark uncontrolled `TextInput`'s with user input `--filled` after reload
|
|
24
|
+
- fix(ts): Botched re-export of `SelectboxOption*` types
|
|
25
|
+
|
|
7
26
|
## 0.10.67
|
|
8
27
|
|
|
9
28
|
_2022-09-14_
|
package/ContentArticle.d.ts
CHANGED
package/ContentArticle.js
CHANGED
|
@@ -13,7 +13,7 @@ const ContentArticle = (props) => {
|
|
|
13
13
|
const [ref] = (0, seenEffect_1.useSeenEffect)(props.startSeen);
|
|
14
14
|
return (react_1.default.createElement("div", { className: "ContentArticle", ref: ref },
|
|
15
15
|
react_1.default.createElement(ArticleMeta_1.default, { items: props.meta }),
|
|
16
|
-
react_1.default.createElement(Heading_1.default,
|
|
16
|
+
react_1.default.createElement(Heading_1.default, { forceH1: props.headlineTag === 'h1' }, props.headline),
|
|
17
17
|
react_1.default.createElement(TextBlock_1.default, { startSeen: true },
|
|
18
18
|
props.topImage && react_1.default.createElement(ContentImage_1.default, Object.assign({}, props.topImage)),
|
|
19
19
|
props.body),
|
package/Heading.d.ts
CHANGED
|
@@ -11,6 +11,11 @@ export declare type HeadingProps = {
|
|
|
11
11
|
Tag?: 'h2' | 'h3';
|
|
12
12
|
size?: HeadingSize;
|
|
13
13
|
children: ReactNode;
|
|
14
|
+
/**
|
|
15
|
+
* Make an exception and render a `<h1/>` element.
|
|
16
|
+
*
|
|
17
|
+
* This prop is ignore if the `Tag` prop is defined. */
|
|
18
|
+
forceH1?: boolean;
|
|
14
19
|
} & ComponentLayoutProps;
|
|
15
20
|
declare const Heading: (props: HeadingProps) => JSX.Element;
|
|
16
21
|
export default Heading;
|
package/Heading.js
CHANGED
|
@@ -10,7 +10,8 @@ const sizes = {
|
|
|
10
10
|
large: 'large',
|
|
11
11
|
};
|
|
12
12
|
const Heading = (props) => {
|
|
13
|
-
const { size = 'normal',
|
|
13
|
+
const { size = 'normal', align, wide, children } = props;
|
|
14
|
+
const Tag = props.Tag || (props.forceH1 ? 'h1' : 'h2');
|
|
14
15
|
return (react_1.default.createElement(Tag, { className: (0, getBemClass_1.default)('Heading', [
|
|
15
16
|
sizes[size],
|
|
16
17
|
align === 'right' && 'align--' + align,
|
package/Selectbox.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { SelectboxProps as _SelectboxProps } from '@hugsmidjan/react/Selectbox';
|
|
2
2
|
import { FormFieldWrappingProps } from './FormField';
|
|
3
|
-
export
|
|
3
|
+
export { type SelectboxOption, type SelectboxOptions as SelectboxOptionList,
|
|
4
4
|
/** @deprecated Use `SelectboxOptionList` instead (Will be removed in v0.11) */
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export declare type SelectboxProps = FormFieldWrappingProps & Omit<_SelectboxProps
|
|
5
|
+
type SelectboxOptions, } from '@hugsmidjan/react/Selectbox';
|
|
6
|
+
declare type OptionOrValue = _SelectboxProps['options'][0];
|
|
7
|
+
export declare type SelectboxProps<O extends OptionOrValue = OptionOrValue> = FormFieldWrappingProps & Omit<_SelectboxProps<O>, 'bem'> & {
|
|
8
8
|
small?: boolean;
|
|
9
9
|
};
|
|
10
|
-
declare const Selectbox: (props: SelectboxProps) => JSX.Element;
|
|
10
|
+
declare const Selectbox: <O extends import("@hugsmidjan/react/__types/Selectbox.privates").OptionOrValue>(props: SelectboxProps<O>) => JSX.Element;
|
|
11
11
|
export default Selectbox;
|
package/SiteSearchInput.d.ts
CHANGED
|
@@ -20,5 +20,5 @@ export declare type SiteSearchInputProps = {
|
|
|
20
20
|
children?: undefined;
|
|
21
21
|
ssr?: SSRSupport;
|
|
22
22
|
} & WrappingProps & InputElmProps;
|
|
23
|
-
declare const SiteSearchInput: React.ForwardRefExoticComponent<Pick<SiteSearchInputProps, "
|
|
23
|
+
declare const SiteSearchInput: React.ForwardRefExoticComponent<Pick<SiteSearchInputProps, "form" | "label" | "slot" | "style" | "title" | "pattern" | "is" | "value" | "defaultValue" | "id" | "ssr" | "children" | "hidden" | "onClick" | "key" | "list" | "autoFocus" | "formAction" | "formEncType" | "formMethod" | "formNoValidate" | "formTarget" | "name" | "defaultChecked" | "suppressContentEditableWarning" | "suppressHydrationWarning" | "accessKey" | "contentEditable" | "contextMenu" | "dir" | "draggable" | "lang" | "placeholder" | "spellCheck" | "tabIndex" | "translate" | "radioGroup" | "role" | "about" | "datatype" | "inlist" | "prefix" | "property" | "resource" | "typeof" | "vocab" | "autoCapitalize" | "autoCorrect" | "autoSave" | "color" | "itemProp" | "itemScope" | "itemType" | "itemID" | "itemRef" | "results" | "security" | "unselectable" | "inputMode" | "aria-activedescendant" | "aria-atomic" | "aria-autocomplete" | "aria-busy" | "aria-checked" | "aria-colcount" | "aria-colindex" | "aria-colspan" | "aria-controls" | "aria-current" | "aria-describedby" | "aria-details" | "aria-disabled" | "aria-dropeffect" | "aria-errormessage" | "aria-expanded" | "aria-flowto" | "aria-grabbed" | "aria-haspopup" | "aria-hidden" | "aria-invalid" | "aria-keyshortcuts" | "aria-label" | "aria-labelledby" | "aria-level" | "aria-live" | "aria-modal" | "aria-multiline" | "aria-multiselectable" | "aria-orientation" | "aria-owns" | "aria-placeholder" | "aria-posinset" | "aria-pressed" | "aria-readonly" | "aria-relevant" | "aria-required" | "aria-roledescription" | "aria-rowcount" | "aria-rowindex" | "aria-rowspan" | "aria-selected" | "aria-setsize" | "aria-sort" | "aria-valuemax" | "aria-valuemin" | "aria-valuenow" | "aria-valuetext" | "dangerouslySetInnerHTML" | "onCopy" | "onCopyCapture" | "onCut" | "onCutCapture" | "onPaste" | "onPasteCapture" | "onCompositionEnd" | "onCompositionEndCapture" | "onCompositionStart" | "onCompositionStartCapture" | "onCompositionUpdate" | "onCompositionUpdateCapture" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onChangeCapture" | "onBeforeInput" | "onBeforeInputCapture" | "onInput" | "onInputCapture" | "onReset" | "onResetCapture" | "onSubmit" | "onSubmitCapture" | "onInvalid" | "onInvalidCapture" | "onLoad" | "onLoadCapture" | "onError" | "onErrorCapture" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyPressCapture" | "onKeyUp" | "onKeyUpCapture" | "onAbort" | "onAbortCapture" | "onCanPlay" | "onCanPlayCapture" | "onCanPlayThrough" | "onCanPlayThroughCapture" | "onDurationChange" | "onDurationChangeCapture" | "onEmptied" | "onEmptiedCapture" | "onEncrypted" | "onEncryptedCapture" | "onEnded" | "onEndedCapture" | "onLoadedData" | "onLoadedDataCapture" | "onLoadedMetadata" | "onLoadedMetadataCapture" | "onLoadStart" | "onLoadStartCapture" | "onPause" | "onPauseCapture" | "onPlay" | "onPlayCapture" | "onPlaying" | "onPlayingCapture" | "onProgress" | "onProgressCapture" | "onRateChange" | "onRateChangeCapture" | "onSeeked" | "onSeekedCapture" | "onSeeking" | "onSeekingCapture" | "onStalled" | "onStalledCapture" | "onSuspend" | "onSuspendCapture" | "onTimeUpdate" | "onTimeUpdateCapture" | "onVolumeChange" | "onVolumeChangeCapture" | "onWaiting" | "onWaitingCapture" | "onAuxClick" | "onAuxClickCapture" | "onClickCapture" | "onContextMenu" | "onContextMenuCapture" | "onDoubleClick" | "onDoubleClickCapture" | "onDrag" | "onDragCapture" | "onDragEnd" | "onDragEndCapture" | "onDragEnter" | "onDragEnterCapture" | "onDragExit" | "onDragExitCapture" | "onDragLeave" | "onDragLeaveCapture" | "onDragOver" | "onDragOverCapture" | "onDragStart" | "onDragStartCapture" | "onDrop" | "onDropCapture" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseMoveCapture" | "onMouseOut" | "onMouseOutCapture" | "onMouseOver" | "onMouseOverCapture" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onSelectCapture" | "onTouchCancel" | "onTouchCancelCapture" | "onTouchEnd" | "onTouchEndCapture" | "onTouchMove" | "onTouchMoveCapture" | "onTouchStart" | "onTouchStartCapture" | "onPointerDown" | "onPointerDownCapture" | "onPointerMove" | "onPointerMoveCapture" | "onPointerUp" | "onPointerUpCapture" | "onPointerCancel" | "onPointerCancelCapture" | "onPointerEnter" | "onPointerEnterCapture" | "onPointerLeave" | "onPointerLeaveCapture" | "onPointerOver" | "onPointerOverCapture" | "onPointerOut" | "onPointerOutCapture" | "onGotPointerCapture" | "onGotPointerCaptureCapture" | "onLostPointerCapture" | "onLostPointerCaptureCapture" | "onScroll" | "onScrollCapture" | "onWheel" | "onWheelCapture" | "onAnimationStart" | "onAnimationStartCapture" | "onAnimationEnd" | "onAnimationEndCapture" | "onAnimationIteration" | "onAnimationIterationCapture" | "onTransitionEnd" | "onTransitionEndCapture" | "size" | "step" | "src" | "multiple" | "alt" | "width" | "accept" | "autoComplete" | "capture" | "checked" | "crossOrigin" | "enterKeyHint" | "height" | "max" | "maxLength" | "min" | "minLength" | "onButtonClick" | "buttonText"> & React.RefAttributes<HTMLInputElement>>;
|
|
24
24
|
export default SiteSearchInput;
|
package/TextInput.js
CHANGED
|
@@ -6,7 +6,8 @@ const getBemClass_1 = tslib_1.__importDefault(require("@hugsmidjan/react/utils/g
|
|
|
6
6
|
const FormField_1 = tslib_1.__importDefault(require("./FormField"));
|
|
7
7
|
const TextInput = (props) => {
|
|
8
8
|
var _a;
|
|
9
|
-
const
|
|
9
|
+
const _inputRef = (0, react_1.useRef)(null);
|
|
10
|
+
const { className, label, assistText, hideLabel, disabled, readOnly, invalid, errorMessage, required, reqText, id, onChange, small, type, ssr, inputRef = _inputRef } = props, inputElementProps = tslib_1.__rest(props, ["className", "label", "assistText", "hideLabel", "disabled", "readOnly", "invalid", "errorMessage", "required", "reqText", "id", "onChange", "small", "type", "ssr", "inputRef"]);
|
|
10
11
|
const { value, defaultValue, placeholder } = inputElementProps;
|
|
11
12
|
const [hasValue, setHasValue] = (0, react_1.useState)(undefined);
|
|
12
13
|
const filled = !!((_a = value !== null && value !== void 0 ? value : hasValue) !== null && _a !== void 0 ? _a : !!defaultValue);
|
|
@@ -22,6 +23,12 @@ const TextInput = (props) => {
|
|
|
22
23
|
// TypeScript is silly sometimes.
|
|
23
24
|
e);
|
|
24
25
|
};
|
|
26
|
+
(0, react_1.useEffect)(() => {
|
|
27
|
+
var _a;
|
|
28
|
+
if ((_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.value) {
|
|
29
|
+
setHasValue(true);
|
|
30
|
+
}
|
|
31
|
+
}, []);
|
|
25
32
|
return (react_1.default.createElement(FormField_1.default, { className: (0, getBemClass_1.default)('TextInput', modifiers, className), ssr: ssr, small: small, label: label, empty: empty, filled: filled, assistText: assistText, hideLabel: hideLabel, disabled: disabled, readOnly: readOnly, invalid: invalid, errorMessage: errorMessage, required: required, reqText: reqText, id: id, renderInput: (className, inputProps, addFocusProps) => multiline ? (react_1.default.createElement("textarea", Object.assign({ className: className.input, onChange: _onChange }, inputProps, addFocusProps(inputElementProps), { ref: inputRef }))) : (react_1.default.createElement("input", Object.assign({ className: className.input, onChange: _onChange, type: type }, inputProps, addFocusProps(inputElementProps), { ref: inputRef }))) }));
|
|
26
33
|
};
|
|
27
34
|
exports.default = TextInput;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reykjavik/hanna-react",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.69",
|
|
4
4
|
"author": "Reykjavík (http://www.reykjavik.is)",
|
|
5
5
|
"contributors": [
|
|
6
6
|
"Hugsmiðjan ehf (http://www.hugsmidjan.is)",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"@hugsmidjan/qj": "^4.10.2",
|
|
17
17
|
"@hugsmidjan/react": "^0.4.17",
|
|
18
18
|
"@reykjavik/hanna-css": "^0.3.7",
|
|
19
|
-
"@reykjavik/hanna-utils": "^0.1.
|
|
19
|
+
"@reykjavik/hanna-utils": "^0.1.14",
|
|
20
20
|
"@types/react": "^17.0.24",
|
|
21
21
|
"@types/react-autosuggest": "^10.1.0",
|
|
22
22
|
"@types/react-datepicker": "^3.0.2",
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Dispatch, SetStateAction } from 'react';
|
|
2
|
+
declare type CtrlMode = 'controlled' | 'uncontrolled' | undefined;
|
|
3
|
+
declare type RetArray<R> = [value: R, setValue: Dispatch<SetStateAction<R>>, mode: CtrlMode];
|
|
2
4
|
declare type DefaultProp<N extends string> = `default${Capitalize<N>}`;
|
|
3
5
|
declare type PropPair<N extends string> = N | DefaultProp<N>;
|
|
4
6
|
declare type StrictKeys<P extends Record<string, unknown>, N extends string> = PropPair<N> extends keyof P ? P : {
|
|
@@ -48,28 +50,88 @@ declare type StrictKeys<P extends Record<string, unknown>, N extends string> = P
|
|
|
48
50
|
* };
|
|
49
51
|
* ```
|
|
50
52
|
*/
|
|
51
|
-
export declare
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
53
|
+
export declare function useMixedControlState<N extends string, P extends {
|
|
54
|
+
[x in PropPair<N>]?: unknown;
|
|
55
|
+
}, D extends Exclude<V, undefined>, V = P[DefaultProp<N>] | P[N]>(
|
|
56
|
+
/** The props object of your component */
|
|
57
|
+
props: StrictKeys<P, N>, // StrictKeys give nicer error messages to users.
|
|
58
|
+
/** Name of the prop for the controlled value */
|
|
59
|
+
name: N,
|
|
60
|
+
/**
|
|
61
|
+
* A last-resort default value to use instead of `undefined`. \
|
|
62
|
+
* **NOTE:** All post-factum changes/updates to this value are ignored!
|
|
63
|
+
*/
|
|
64
|
+
emptyValue: D): RetArray<Exclude<V, undefined>>;
|
|
65
|
+
export declare function useMixedControlState<N extends string, P extends {
|
|
66
|
+
[x in PropPair<N>]?: unknown;
|
|
67
|
+
}, D extends Exclude<V, undefined>, V = P[DefaultProp<N>] | P[N]>(
|
|
68
|
+
/** The props object of your component */
|
|
69
|
+
props: StrictKeys<P, N>, // StrictKeys give nicer error messages to users.
|
|
70
|
+
/** Name of the prop for the controlled value */
|
|
71
|
+
name: N,
|
|
72
|
+
/**
|
|
73
|
+
* A last-resort default value to use instead of `undefined`. \
|
|
74
|
+
* **NOTE:** All post-factum changes/updates to this value are ignored!
|
|
75
|
+
*/
|
|
76
|
+
emptyValue?: D): RetArray<V>;
|
|
77
|
+
export declare namespace useMixedControlState {
|
|
78
|
+
var $warningLogger: ((message: string) => void) | undefined;
|
|
79
|
+
var raw: typeof useRaw;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* A slightly lower-level hook alternative to
|
|
83
|
+
* `useMixedControlState(props, name)`, for cases where you don't
|
|
84
|
+
* have a neatly-/conventionally-shaped props object, or if you need to do
|
|
85
|
+
* some sort of pre-processing of either prop value.
|
|
86
|
+
*
|
|
87
|
+
* ```tsx
|
|
88
|
+
* import { useMixedControlState } from '@reykjavik/hanna-react/utils';
|
|
89
|
+
*
|
|
90
|
+
* declare const props: { visible?: boolean; defaultVisible?: boolean };
|
|
91
|
+
*
|
|
92
|
+
* const [vislble, setVisible] = useMixedControlState.raw(
|
|
93
|
+
* props.vislble,
|
|
94
|
+
* props.defaultVisible,
|
|
95
|
+
* 'visible'
|
|
96
|
+
* );
|
|
97
|
+
* // has the same effect as this:
|
|
98
|
+
* const [visible, setVisible] = useMixedControlState(props, 'visible');
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
declare function useRaw<C, U, D extends Exclude<C | U, undefined>>(
|
|
102
|
+
/** Controlled value. */
|
|
103
|
+
value: C,
|
|
104
|
+
/** Default/initial value for uncontrolled use. */
|
|
105
|
+
defaultValue: U,
|
|
106
|
+
/**
|
|
107
|
+
* Prop name to display more meaningful warnings about when value
|
|
108
|
+
* and defaultValue are both defined, or if the component switches
|
|
109
|
+
* between modes mid-stream.
|
|
110
|
+
*
|
|
111
|
+
* If left undefined, the hook emits more generic/vague warnings
|
|
112
|
+
*/
|
|
113
|
+
warningPropName: string | undefined,
|
|
114
|
+
/**
|
|
115
|
+
* A last-resort default value to use instead of `undefined`. \
|
|
116
|
+
* **NOTE:** All post-factum changes/updates to this value are ignored!
|
|
117
|
+
*/
|
|
118
|
+
emptyValue: D): RetArray<Exclude<C | U, undefined>>;
|
|
119
|
+
declare function useRaw<C, U, D extends C | U>(
|
|
120
|
+
/** Controlled value. */
|
|
121
|
+
value: C,
|
|
122
|
+
/** Default/initial value for uncontrolled use. */
|
|
123
|
+
defaultValue: U,
|
|
124
|
+
/**
|
|
125
|
+
* Prop name to display more meaningful warnings about when value
|
|
126
|
+
* and defaultValue are both defined, or if the component switches
|
|
127
|
+
* between modes mid-stream.
|
|
128
|
+
*
|
|
129
|
+
* If left undefined, the hook emits more generic/vague warnings
|
|
130
|
+
*/
|
|
131
|
+
warningPropName?: string,
|
|
132
|
+
/**
|
|
133
|
+
* A last-resort default value to use instead of `undefined`. \
|
|
134
|
+
* **NOTE:** All post-factum changes/updates to this value are ignored!
|
|
135
|
+
*/
|
|
136
|
+
emptyValue?: D): RetArray<C | U>;
|
|
75
137
|
export {};
|
|
@@ -4,151 +4,87 @@ exports.useMixedControlState = void 0;
|
|
|
4
4
|
const react_1 = require("react");
|
|
5
5
|
const hanna_utils_1 = require("@reykjavik/hanna-utils");
|
|
6
6
|
// ---------------------------------------------------------------------------
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
* };
|
|
49
|
-
* ```
|
|
50
|
-
*/
|
|
51
|
-
const useMixedControlState = (
|
|
52
|
-
/** The props object of your component */
|
|
53
|
-
props,
|
|
54
|
-
/** Name of the prop for the controlled value */
|
|
55
|
-
name,
|
|
56
|
-
/**
|
|
57
|
-
* A last-resort default value for the defaultValue prop
|
|
58
|
-
*
|
|
59
|
-
* Used as uncontrolled default if the `default${capitalize(name)}` value
|
|
60
|
-
* of `props` is missing/undefined.
|
|
61
|
-
*/
|
|
62
|
-
defaultDefault) => {
|
|
63
|
-
let defaultValue = props[`default${(0, hanna_utils_1.capitalize)(name)}`];
|
|
64
|
-
if (defaultValue === undefined) {
|
|
65
|
-
defaultValue = defaultDefault;
|
|
7
|
+
function useMixedControlState(props, name, emptyValue) {
|
|
8
|
+
const value = props[name];
|
|
9
|
+
const defaultValue = props[`default${(0, hanna_utils_1.capitalize)(name)}`];
|
|
10
|
+
return useMixedControlState.raw(value, defaultValue, name, emptyValue);
|
|
11
|
+
}
|
|
12
|
+
exports.useMixedControlState = useMixedControlState;
|
|
13
|
+
// ===========================================================================
|
|
14
|
+
//
|
|
15
|
+
//
|
|
16
|
+
// ===========================================================================
|
|
17
|
+
const defaultWarningLogger = (message) => console.error(message);
|
|
18
|
+
useMixedControlState.$warningLogger = defaultWarningLogger;
|
|
19
|
+
/** Validate sane use of the component, during development. */
|
|
20
|
+
const validateSaneUse = ({ warningPropName, value, defaultValue, lastMode, }) => {
|
|
21
|
+
const warn = useMixedControlState.$warningLogger || defaultWarningLogger;
|
|
22
|
+
if (value !== undefined && defaultValue !== undefined) {
|
|
23
|
+
warn(`WARNING:` +
|
|
24
|
+
` Don't mix` +
|
|
25
|
+
(warningPropName
|
|
26
|
+
? ` \`${warningPropName}\` and \`default${(0, hanna_utils_1.capitalize)(warningPropName)}\` props`
|
|
27
|
+
: 'controlled and uncontrolled mode') +
|
|
28
|
+
`\n` +
|
|
29
|
+
`Use one or the other.`);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const C_to_U = lastMode === 'controlled' && defaultValue !== undefined;
|
|
33
|
+
const U_to_C = lastMode === 'uncontrolled' && value !== undefined;
|
|
34
|
+
if (C_to_U || U_to_C) {
|
|
35
|
+
warn(`WARNING:` +
|
|
36
|
+
(C_to_U
|
|
37
|
+
? `A component seems to be attempting to change ` +
|
|
38
|
+
`from controlled to uncontrolled mode. ` +
|
|
39
|
+
`This is not possible.`
|
|
40
|
+
: `A component is changing ` + `from uncontrolled to controlled mode.`) +
|
|
41
|
+
`\n` +
|
|
42
|
+
`Decide between using ` +
|
|
43
|
+
(warningPropName
|
|
44
|
+
? `\`${warningPropName}\` (controlled) prop` +
|
|
45
|
+
` OR \`default${(0, hanna_utils_1.capitalize)(warningPropName)}\` (uncontrolled)`
|
|
46
|
+
: `either controlled OR uncontrolled mode`) +
|
|
47
|
+
` for the lifetime of the component.`);
|
|
66
48
|
}
|
|
67
|
-
return exports.useMixedControlState.raw(props[name], defaultValue, name);
|
|
68
49
|
};
|
|
69
|
-
exports.useMixedControlState = useMixedControlState;
|
|
70
50
|
// ---------------------------------------------------------------------------
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
* `useMixedControlState(props, name)`, for cases where you don't
|
|
74
|
-
* have a neatly-/conventionally-shaped props object, or if you need to do
|
|
75
|
-
* some sort of pre-processing of either prop value.
|
|
76
|
-
*
|
|
77
|
-
* ```tsx
|
|
78
|
-
* import { useMixedControlState } from '@reykjavik/hanna-react/utils';
|
|
79
|
-
*
|
|
80
|
-
* declare const props: { visible?: boolean; defaultVisible?: boolean };
|
|
81
|
-
*
|
|
82
|
-
* const [vislble, setVisible] = useMixedControlState.raw(
|
|
83
|
-
* props.vislble,
|
|
84
|
-
* props.defaultVisible,
|
|
85
|
-
* 'visible'
|
|
86
|
-
* );
|
|
87
|
-
* // has the same effect as this:
|
|
88
|
-
* const [visible, setVisible] = useMixedControlState(props, 'visible');
|
|
89
|
-
* ```
|
|
90
|
-
*/
|
|
91
|
-
exports.useMixedControlState.raw = (
|
|
92
|
-
/** Controlled value. */
|
|
93
|
-
value,
|
|
94
|
-
/** Default/initial value for uncontrolled use. */
|
|
95
|
-
defaultValue,
|
|
96
|
-
/**
|
|
97
|
-
* Prop name to display more meaningful warnings about when value
|
|
98
|
-
* and defaultValue are both defined, or if the component switches
|
|
99
|
-
* between modes mid-stream.
|
|
100
|
-
*
|
|
101
|
-
* If left undefined, the hook emits more generic/vague warnings
|
|
102
|
-
*/
|
|
103
|
-
warningPropName) => {
|
|
104
|
-
/* eslint-disable react-hooks/rules-of-hooks */
|
|
105
|
-
const meta = (0, react_1.useRef)({
|
|
51
|
+
function useRaw(value, defaultValue, warningPropName, emptyValue) {
|
|
52
|
+
const metaRef = (0, react_1.useRef)({
|
|
106
53
|
lastMode: undefined,
|
|
107
54
|
lastDefault: defaultValue,
|
|
108
55
|
// lastValue: value,
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
: defaultValue !== undefined
|
|
114
|
-
? 'uncontrolled'
|
|
115
|
-
: lastMode;
|
|
116
|
-
// Validate sane use of the component, during development.
|
|
56
|
+
_emptyValue: emptyValue,
|
|
57
|
+
});
|
|
58
|
+
const meta = metaRef.current;
|
|
59
|
+
const { lastMode, lastDefault, _emptyValue /*, lastValue */ } = meta;
|
|
117
60
|
if (process.env.NODE_ENV !== 'production') {
|
|
118
|
-
|
|
119
|
-
console.error(`WARNING:` +
|
|
120
|
-
` Don't mix` +
|
|
121
|
-
(warningPropName
|
|
122
|
-
? ` \`${warningPropName}\` and \`default${(0, hanna_utils_1.capitalize)(warningPropName)}\` props`
|
|
123
|
-
: 'controlled and uncontrolled mode') +
|
|
124
|
-
`\n` +
|
|
125
|
-
`Use one or the other.`);
|
|
126
|
-
}
|
|
127
|
-
if (lastMode && lastMode !== mode) {
|
|
128
|
-
console.error(`WARNING:` +
|
|
129
|
-
`A component is changing from ${lastMode} to ${mode} mode.` +
|
|
130
|
-
`\n` +
|
|
131
|
-
(warningPropName
|
|
132
|
-
? `Decide between using \`${warningPropName}\` (controlled) prop` +
|
|
133
|
-
` OR \`default${(0, hanna_utils_1.capitalize)(warningPropName)}\` (uncontrolled)`
|
|
134
|
-
: `Decide between using either controlled OR uncontrolled mode`) +
|
|
135
|
-
` for the lifetime of the component.`);
|
|
136
|
-
}
|
|
61
|
+
validateSaneUse({ warningPropName, value, defaultValue, lastMode });
|
|
137
62
|
}
|
|
138
|
-
const
|
|
63
|
+
const mode = lastMode === 'controlled'
|
|
64
|
+
? 'controlled'
|
|
65
|
+
: value !== undefined
|
|
66
|
+
? 'controlled'
|
|
67
|
+
: defaultValue !== undefined
|
|
68
|
+
? 'uncontrolled'
|
|
69
|
+
: lastMode;
|
|
70
|
+
const [localValue, _setLocalValue] = (0, react_1.useState)(defaultValue !== undefined ? defaultValue : _emptyValue);
|
|
139
71
|
const setLocalValue = (0, react_1.useCallback)((newState) => {
|
|
140
72
|
if (mode === 'controlled' && typeof newState === 'function') {
|
|
141
|
-
|
|
142
|
-
// because the C and U gernerics are too …err… generic?)
|
|
143
|
-
const action = newState;
|
|
144
|
-
newState = action(value);
|
|
73
|
+
newState = newState(value !== undefined ? value : _emptyValue);
|
|
145
74
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
75
|
+
metaRef.current.lastMode = mode || 'uncontrolled';
|
|
76
|
+
const setterFn = typeof newState === 'function' ? newState : () => newState;
|
|
77
|
+
_setLocalValue((prevState) => {
|
|
78
|
+
let newState = setterFn(prevState);
|
|
79
|
+
newState = newState !== undefined ? newState : _emptyValue;
|
|
80
|
+
if (prevState !== newState) {
|
|
81
|
+
_setLocalValue.$isCalled = true;
|
|
82
|
+
}
|
|
83
|
+
return newState;
|
|
84
|
+
});
|
|
85
|
+
}, [value, mode, _emptyValue]);
|
|
149
86
|
// The mode can change but it should never go back to `undefined` state
|
|
150
|
-
// this is similar to what React does with
|
|
151
|
-
// elements.
|
|
87
|
+
// this is similar to what React does with <input/> and <select/> elements.
|
|
152
88
|
// In dev-mode an WARNING gets logged whenever the mode changes.
|
|
153
89
|
meta.lastMode = mode;
|
|
154
90
|
if (mode === 'uncontrolled') {
|
|
@@ -157,12 +93,13 @@ warningPropName) => {
|
|
|
157
93
|
// controlled mode. Something that should ideally not happen
|
|
158
94
|
// but is worth keeping as sane as possible nonetheless.
|
|
159
95
|
meta.lastDefault = defaultValue;
|
|
160
|
-
if (!_setLocalValue.$
|
|
161
|
-
|
|
96
|
+
if (!_setLocalValue.$isCalled && defaultValue !== lastDefault) {
|
|
97
|
+
// Immediately exits and re-renders the component
|
|
98
|
+
_setLocalValue(defaultValue !== undefined ? defaultValue : _emptyValue);
|
|
162
99
|
}
|
|
163
100
|
}
|
|
164
101
|
// meta.lastValue = value;
|
|
165
102
|
const retValue = mode === 'controlled' ? value : localValue;
|
|
166
|
-
return [retValue, setLocalValue];
|
|
167
|
-
|
|
168
|
-
|
|
103
|
+
return [retValue, setLocalValue, mode];
|
|
104
|
+
}
|
|
105
|
+
useMixedControlState.raw = useRaw;
|