@mpen/react-basic-inputs 0.1.6 → 0.1.8
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/bundle.cjs +91 -28
- package/dist/bundle.d.ts +1 -0
- package/dist/bundle.mjs +92 -31
- package/dist/components/RadioMenu.d.ts +30 -0
- package/dist/components/TextArea.d.ts +9 -3
- package/dist/util/assert.d.ts +1 -0
- package/dist/util/key-fixer.d.ts +8 -0
- package/package.json +4 -1
package/dist/bundle.cjs
CHANGED
|
@@ -63,13 +63,6 @@ var useUpdateEffect = function(effect, deps) {
|
|
|
63
63
|
|
|
64
64
|
var useUpdateEffect$1 = useUpdateEffect;
|
|
65
65
|
|
|
66
|
-
const defaultMakeInvalidValueOption = value => ({
|
|
67
|
-
value,
|
|
68
|
-
text: String(value),
|
|
69
|
-
disabled: true,
|
|
70
|
-
key: INVALID_OPTION_KEY
|
|
71
|
-
});
|
|
72
|
-
|
|
73
66
|
function defaultMakeKey(opt, idx) {
|
|
74
67
|
if (opt.key != null) {
|
|
75
68
|
return resolveValue(opt.key, opt, idx);
|
|
@@ -79,6 +72,30 @@ function defaultMakeKey(opt, idx) {
|
|
|
79
72
|
return idx;
|
|
80
73
|
}
|
|
81
74
|
|
|
75
|
+
class KeyFixer {
|
|
76
|
+
usedKeys=new Map;
|
|
77
|
+
fix(opt, idx) {
|
|
78
|
+
let fixedKey = defaultMakeKey(opt, idx);
|
|
79
|
+
for (;;) {
|
|
80
|
+
let suffix = this.usedKeys.get(fixedKey);
|
|
81
|
+
if (suffix === undefined) {
|
|
82
|
+
this.usedKeys.set(fixedKey, 1);
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
this.usedKeys.set(fixedKey, ++suffix);
|
|
86
|
+
fixedKey = `${fixedKey}(${suffix})`;
|
|
87
|
+
}
|
|
88
|
+
return fixedKey;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const defaultMakeInvalidValueOption = value => ({
|
|
93
|
+
value,
|
|
94
|
+
text: String(value),
|
|
95
|
+
disabled: true,
|
|
96
|
+
key: INVALID_OPTION_KEY
|
|
97
|
+
});
|
|
98
|
+
|
|
82
99
|
const PLACEHOLDER_KEY = "3c9369b7-0a5e-46ea-93c2-e8b9fec67fdb";
|
|
83
100
|
|
|
84
101
|
const INVALID_OPTION_KEY = "1a53f789-77f5-4ce6-a829-b00e563f1ee8";
|
|
@@ -132,23 +149,14 @@ function Select({options, value, invalidValueOption = defaultMakeInvalidValueOpt
|
|
|
132
149
|
useUpdateEffect$1((() => {
|
|
133
150
|
refreshSelectedIndex();
|
|
134
151
|
}), [ refreshSelectedIndex ]);
|
|
135
|
-
const
|
|
152
|
+
const fixer = new KeyFixer;
|
|
136
153
|
return jsxRuntime.jsx("select", {
|
|
137
154
|
...selectAttrs,
|
|
138
155
|
onChange: handleChange,
|
|
139
156
|
ref: setRef,
|
|
140
157
|
children: fixedOptions.map(((opt, idx) => {
|
|
141
158
|
const {value, text, key, ...optAttrs} = opt;
|
|
142
|
-
|
|
143
|
-
for (;;) {
|
|
144
|
-
let suffix = usedKeys.get(fixedKey);
|
|
145
|
-
if (suffix === undefined) {
|
|
146
|
-
usedKeys.set(fixedKey, 1);
|
|
147
|
-
break;
|
|
148
|
-
}
|
|
149
|
-
usedKeys.set(fixedKey, ++suffix);
|
|
150
|
-
fixedKey = `${fixedKey}(${suffix})`;
|
|
151
|
-
}
|
|
159
|
+
const fixedKey = fixer.fix(opt, idx);
|
|
152
160
|
return React.createElement("option", {
|
|
153
161
|
...optAttrs,
|
|
154
162
|
key: fixedKey
|
|
@@ -215,35 +223,43 @@ function TextInput({formatOnChange = collapseWhitespace, ...otherProps}) {
|
|
|
215
223
|
});
|
|
216
224
|
}
|
|
217
225
|
|
|
218
|
-
const TextArea = React.forwardRef((function TextArea({onInput, style, ...rest}, fwdRef) {
|
|
226
|
+
const TextArea = React.forwardRef((function TextArea({onInput, style, initialHeight = "auto", ...rest}, fwdRef) {
|
|
219
227
|
const ref = React.useRef(null);
|
|
220
|
-
const [height, setHeight] = React.useState(
|
|
221
|
-
const adjustHeight = () => {
|
|
228
|
+
const [height, setHeight] = React.useState(initialHeight);
|
|
229
|
+
const adjustHeight = React.useCallback((() => {
|
|
222
230
|
const textarea = ref.current;
|
|
223
231
|
if (!textarea) return;
|
|
224
|
-
textarea.style.height =
|
|
232
|
+
textarea.style.height = initialHeight;
|
|
225
233
|
const newHeight = `${textarea.scrollHeight}px`;
|
|
226
234
|
setHeight(newHeight);
|
|
227
235
|
textarea.style.height = newHeight;
|
|
228
|
-
};
|
|
236
|
+
}), [ initialHeight ]);
|
|
229
237
|
React.useImperativeHandle(fwdRef, (() => ({
|
|
230
238
|
element: ref.current,
|
|
231
|
-
|
|
232
|
-
})), [
|
|
239
|
+
adjustHeight
|
|
240
|
+
})), [ adjustHeight ]);
|
|
233
241
|
const input = useEventHandler((ev => {
|
|
234
242
|
adjustHeight();
|
|
235
243
|
onInput?.(ev);
|
|
236
244
|
}));
|
|
237
245
|
React.useLayoutEffect((() => {
|
|
238
246
|
adjustHeight();
|
|
239
|
-
|
|
247
|
+
const textarea = ref.current;
|
|
248
|
+
if (!textarea) return;
|
|
249
|
+
const resizeObserver = new ResizeObserver((entries => {
|
|
250
|
+
adjustHeight();
|
|
251
|
+
}));
|
|
252
|
+
resizeObserver.observe(textarea);
|
|
253
|
+
return () => {
|
|
254
|
+
resizeObserver.unobserve(textarea);
|
|
255
|
+
};
|
|
256
|
+
}), [ adjustHeight ]);
|
|
240
257
|
return jsxRuntime.jsx("textarea", {
|
|
241
|
-
rows: 1,
|
|
242
258
|
...rest,
|
|
243
259
|
style: {
|
|
244
|
-
...style,
|
|
245
260
|
overflow: "hidden",
|
|
246
261
|
resize: "none",
|
|
262
|
+
...style,
|
|
247
263
|
height
|
|
248
264
|
},
|
|
249
265
|
onInput: input,
|
|
@@ -251,8 +267,55 @@ const TextArea = React.forwardRef((function TextArea({onInput, style, ...rest},
|
|
|
251
267
|
});
|
|
252
268
|
}));
|
|
253
269
|
|
|
270
|
+
function RadioMenu(menu) {
|
|
271
|
+
const defaultId = React.useId();
|
|
272
|
+
const name = menu.name ?? defaultId;
|
|
273
|
+
const eq = menu.valueEquals ?? Object.is;
|
|
274
|
+
const fixedOptions = menu.options ?? [];
|
|
275
|
+
const fixer = new KeyFixer;
|
|
276
|
+
const onChange = useEventHandler((ev => {
|
|
277
|
+
const selectedIndex = Number(ev.target.value);
|
|
278
|
+
const selectedOption = fixedOptions[selectedIndex];
|
|
279
|
+
if (selectedOption != null && menu.onChange != null) {
|
|
280
|
+
menu.onChange({
|
|
281
|
+
value: selectedOption.value,
|
|
282
|
+
index: selectedIndex,
|
|
283
|
+
type: "change",
|
|
284
|
+
timeStamp: ev.timeStamp,
|
|
285
|
+
target: ev.target
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}));
|
|
289
|
+
return jsxRuntime.jsx("ul", {
|
|
290
|
+
className: menu.className,
|
|
291
|
+
children: fixedOptions.map(((opt, idx) => {
|
|
292
|
+
const {value, text, key, itemClassName, ...rest} = opt;
|
|
293
|
+
const fixedKey = fixer.fix(opt, idx);
|
|
294
|
+
if (menu.value !== undefined) {
|
|
295
|
+
rest.checked = eq(value, menu.value);
|
|
296
|
+
}
|
|
297
|
+
return jsxRuntime.jsx("li", {
|
|
298
|
+
className: itemClassName,
|
|
299
|
+
children: jsxRuntime.jsxs("label", {
|
|
300
|
+
children: [ jsxRuntime.jsx("input", {
|
|
301
|
+
...rest,
|
|
302
|
+
value: idx,
|
|
303
|
+
onChange,
|
|
304
|
+
name,
|
|
305
|
+
type: "radio"
|
|
306
|
+
}), jsxRuntime.jsx("span", {
|
|
307
|
+
children: text
|
|
308
|
+
}) ]
|
|
309
|
+
})
|
|
310
|
+
}, fixedKey);
|
|
311
|
+
}))
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
254
315
|
exports.Input = Input;
|
|
255
316
|
|
|
317
|
+
exports.RadioMenu = RadioMenu;
|
|
318
|
+
|
|
256
319
|
exports.Select = Select;
|
|
257
320
|
|
|
258
321
|
exports.TextArea = TextArea;
|
package/dist/bundle.d.ts
CHANGED
package/dist/bundle.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
|
|
3
|
-
import { useDebugValue, useRef, useInsertionEffect, useEffect, useMemo, useCallback, createElement, forwardRef, useState, useImperativeHandle, useLayoutEffect } from "react";
|
|
3
|
+
import { useDebugValue, useRef, useInsertionEffect, useEffect, useMemo, useCallback, createElement, forwardRef, useState, useImperativeHandle, useLayoutEffect, useId } from "react";
|
|
4
4
|
|
|
5
5
|
const NOOP = Object.freeze((() => {}));
|
|
6
6
|
|
|
@@ -61,13 +61,6 @@ var useUpdateEffect = function(effect, deps) {
|
|
|
61
61
|
|
|
62
62
|
var useUpdateEffect$1 = useUpdateEffect;
|
|
63
63
|
|
|
64
|
-
const defaultMakeInvalidValueOption = value => ({
|
|
65
|
-
value,
|
|
66
|
-
text: String(value),
|
|
67
|
-
disabled: true,
|
|
68
|
-
key: INVALID_OPTION_KEY
|
|
69
|
-
});
|
|
70
|
-
|
|
71
64
|
function defaultMakeKey(opt, idx) {
|
|
72
65
|
if (opt.key != null) {
|
|
73
66
|
return resolveValue(opt.key, opt, idx);
|
|
@@ -77,6 +70,30 @@ function defaultMakeKey(opt, idx) {
|
|
|
77
70
|
return idx;
|
|
78
71
|
}
|
|
79
72
|
|
|
73
|
+
class KeyFixer {
|
|
74
|
+
usedKeys=new Map;
|
|
75
|
+
fix(opt, idx) {
|
|
76
|
+
let fixedKey = defaultMakeKey(opt, idx);
|
|
77
|
+
for (;;) {
|
|
78
|
+
let suffix = this.usedKeys.get(fixedKey);
|
|
79
|
+
if (suffix === undefined) {
|
|
80
|
+
this.usedKeys.set(fixedKey, 1);
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
this.usedKeys.set(fixedKey, ++suffix);
|
|
84
|
+
fixedKey = `${fixedKey}(${suffix})`;
|
|
85
|
+
}
|
|
86
|
+
return fixedKey;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const defaultMakeInvalidValueOption = value => ({
|
|
91
|
+
value,
|
|
92
|
+
text: String(value),
|
|
93
|
+
disabled: true,
|
|
94
|
+
key: INVALID_OPTION_KEY
|
|
95
|
+
});
|
|
96
|
+
|
|
80
97
|
const PLACEHOLDER_KEY = "3c9369b7-0a5e-46ea-93c2-e8b9fec67fdb";
|
|
81
98
|
|
|
82
99
|
const INVALID_OPTION_KEY = "1a53f789-77f5-4ce6-a829-b00e563f1ee8";
|
|
@@ -130,23 +147,14 @@ function Select({options, value, invalidValueOption = defaultMakeInvalidValueOpt
|
|
|
130
147
|
useUpdateEffect$1((() => {
|
|
131
148
|
refreshSelectedIndex();
|
|
132
149
|
}), [ refreshSelectedIndex ]);
|
|
133
|
-
const
|
|
150
|
+
const fixer = new KeyFixer;
|
|
134
151
|
return jsx("select", {
|
|
135
152
|
...selectAttrs,
|
|
136
153
|
onChange: handleChange,
|
|
137
154
|
ref: setRef,
|
|
138
155
|
children: fixedOptions.map(((opt, idx) => {
|
|
139
156
|
const {value, text, key, ...optAttrs} = opt;
|
|
140
|
-
|
|
141
|
-
for (;;) {
|
|
142
|
-
let suffix = usedKeys.get(fixedKey);
|
|
143
|
-
if (suffix === undefined) {
|
|
144
|
-
usedKeys.set(fixedKey, 1);
|
|
145
|
-
break;
|
|
146
|
-
}
|
|
147
|
-
usedKeys.set(fixedKey, ++suffix);
|
|
148
|
-
fixedKey = `${fixedKey}(${suffix})`;
|
|
149
|
-
}
|
|
157
|
+
const fixedKey = fixer.fix(opt, idx);
|
|
150
158
|
return createElement("option", {
|
|
151
159
|
...optAttrs,
|
|
152
160
|
key: fixedKey
|
|
@@ -213,35 +221,43 @@ function TextInput({formatOnChange = collapseWhitespace, ...otherProps}) {
|
|
|
213
221
|
});
|
|
214
222
|
}
|
|
215
223
|
|
|
216
|
-
const TextArea = forwardRef((function TextArea({onInput, style, ...rest}, fwdRef) {
|
|
224
|
+
const TextArea = forwardRef((function TextArea({onInput, style, initialHeight = "auto", ...rest}, fwdRef) {
|
|
217
225
|
const ref = useRef(null);
|
|
218
|
-
const [height, setHeight] = useState(
|
|
219
|
-
const adjustHeight = () => {
|
|
226
|
+
const [height, setHeight] = useState(initialHeight);
|
|
227
|
+
const adjustHeight = useCallback((() => {
|
|
220
228
|
const textarea = ref.current;
|
|
221
229
|
if (!textarea) return;
|
|
222
|
-
textarea.style.height =
|
|
230
|
+
textarea.style.height = initialHeight;
|
|
223
231
|
const newHeight = `${textarea.scrollHeight}px`;
|
|
224
232
|
setHeight(newHeight);
|
|
225
233
|
textarea.style.height = newHeight;
|
|
226
|
-
};
|
|
234
|
+
}), [ initialHeight ]);
|
|
227
235
|
useImperativeHandle(fwdRef, (() => ({
|
|
228
236
|
element: ref.current,
|
|
229
|
-
|
|
230
|
-
})), [
|
|
237
|
+
adjustHeight
|
|
238
|
+
})), [ adjustHeight ]);
|
|
231
239
|
const input = useEventHandler((ev => {
|
|
232
240
|
adjustHeight();
|
|
233
241
|
onInput?.(ev);
|
|
234
242
|
}));
|
|
235
243
|
useLayoutEffect((() => {
|
|
236
244
|
adjustHeight();
|
|
237
|
-
|
|
245
|
+
const textarea = ref.current;
|
|
246
|
+
if (!textarea) return;
|
|
247
|
+
const resizeObserver = new ResizeObserver((entries => {
|
|
248
|
+
adjustHeight();
|
|
249
|
+
}));
|
|
250
|
+
resizeObserver.observe(textarea);
|
|
251
|
+
return () => {
|
|
252
|
+
resizeObserver.unobserve(textarea);
|
|
253
|
+
};
|
|
254
|
+
}), [ adjustHeight ]);
|
|
238
255
|
return jsx("textarea", {
|
|
239
|
-
rows: 1,
|
|
240
256
|
...rest,
|
|
241
257
|
style: {
|
|
242
|
-
...style,
|
|
243
258
|
overflow: "hidden",
|
|
244
259
|
resize: "none",
|
|
260
|
+
...style,
|
|
245
261
|
height
|
|
246
262
|
},
|
|
247
263
|
onInput: input,
|
|
@@ -249,4 +265,49 @@ const TextArea = forwardRef((function TextArea({onInput, style, ...rest}, fwdRef
|
|
|
249
265
|
});
|
|
250
266
|
}));
|
|
251
267
|
|
|
252
|
-
|
|
268
|
+
function RadioMenu(menu) {
|
|
269
|
+
const defaultId = useId();
|
|
270
|
+
const name = menu.name ?? defaultId;
|
|
271
|
+
const eq = menu.valueEquals ?? Object.is;
|
|
272
|
+
const fixedOptions = menu.options ?? [];
|
|
273
|
+
const fixer = new KeyFixer;
|
|
274
|
+
const onChange = useEventHandler((ev => {
|
|
275
|
+
const selectedIndex = Number(ev.target.value);
|
|
276
|
+
const selectedOption = fixedOptions[selectedIndex];
|
|
277
|
+
if (selectedOption != null && menu.onChange != null) {
|
|
278
|
+
menu.onChange({
|
|
279
|
+
value: selectedOption.value,
|
|
280
|
+
index: selectedIndex,
|
|
281
|
+
type: "change",
|
|
282
|
+
timeStamp: ev.timeStamp,
|
|
283
|
+
target: ev.target
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}));
|
|
287
|
+
return jsx("ul", {
|
|
288
|
+
className: menu.className,
|
|
289
|
+
children: fixedOptions.map(((opt, idx) => {
|
|
290
|
+
const {value, text, key, itemClassName, ...rest} = opt;
|
|
291
|
+
const fixedKey = fixer.fix(opt, idx);
|
|
292
|
+
if (menu.value !== undefined) {
|
|
293
|
+
rest.checked = eq(value, menu.value);
|
|
294
|
+
}
|
|
295
|
+
return jsx("li", {
|
|
296
|
+
className: itemClassName,
|
|
297
|
+
children: jsxs("label", {
|
|
298
|
+
children: [ jsx("input", {
|
|
299
|
+
...rest,
|
|
300
|
+
value: idx,
|
|
301
|
+
onChange,
|
|
302
|
+
name,
|
|
303
|
+
type: "radio"
|
|
304
|
+
}), jsx("span", {
|
|
305
|
+
children: text
|
|
306
|
+
}) ]
|
|
307
|
+
})
|
|
308
|
+
}, fixedKey);
|
|
309
|
+
}))
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export { Input, RadioMenu, Select, TextArea, TextInput };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { EventCallback, HtmlInputElement, NonNil, OverrideProps } from '../types/utility';
|
|
2
|
+
import { Key, ReactNode } from 'react';
|
|
3
|
+
import { Resolvable } from '../util/resolvable';
|
|
4
|
+
import { JSX } from 'react/jsx-runtime';
|
|
5
|
+
export type RadioMenuOption<T extends NonNil> = OverrideProps<'input', {
|
|
6
|
+
value: T;
|
|
7
|
+
text: ReactNode;
|
|
8
|
+
key?: Resolvable<Key, [RadioMenuOption<T>, number]>;
|
|
9
|
+
itemClassName?: string;
|
|
10
|
+
}, 'type' | 'children' | 'checked' | 'name'>;
|
|
11
|
+
export type RadioMenuChangeEvent<T> = {
|
|
12
|
+
value: T;
|
|
13
|
+
index: number;
|
|
14
|
+
type: 'change';
|
|
15
|
+
timeStamp: number;
|
|
16
|
+
target: HtmlInputElement;
|
|
17
|
+
};
|
|
18
|
+
export type RadioMenuChangeEventHandler<T> = EventCallback<RadioMenuChangeEvent<T>>;
|
|
19
|
+
export type RadioMenuProps<T extends NonNil> = {
|
|
20
|
+
options: RadioMenuOption<T>[];
|
|
21
|
+
value?: T | null;
|
|
22
|
+
className?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Value comparison function. Defaults to {@linkcode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is|Object.is}
|
|
25
|
+
*/
|
|
26
|
+
valueEquals?: (a: T, b: T) => boolean;
|
|
27
|
+
onChange?: RadioMenuChangeEventHandler<T>;
|
|
28
|
+
name?: string;
|
|
29
|
+
};
|
|
30
|
+
export declare function RadioMenu<T extends NonNil>(menu: RadioMenuProps<T>): JSX.Element;
|
|
@@ -2,7 +2,13 @@
|
|
|
2
2
|
import { HtmlTextAreaElement, OverrideProps, VoidFn } from '../types/utility';
|
|
3
3
|
export type TextAreaRef = {
|
|
4
4
|
element: HtmlTextAreaElement;
|
|
5
|
-
|
|
5
|
+
adjustHeight: VoidFn;
|
|
6
6
|
};
|
|
7
|
-
export type TextAreaProps = OverrideProps<'textarea', {
|
|
8
|
-
|
|
7
|
+
export type TextAreaProps = OverrideProps<'textarea', {
|
|
8
|
+
/** Initial/minimum height. "0" or "auto" are good choices. Defaults to "auto" */
|
|
9
|
+
initialHeight?: string;
|
|
10
|
+
}>;
|
|
11
|
+
export declare const TextArea: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").DetailedHTMLProps<import("react").TextareaHTMLAttributes<HTMLTextAreaElement>, HTMLTextAreaElement>, "ref">, "initialHeight"> & {
|
|
12
|
+
/** Initial/minimum height. "0" or "auto" are good choices. Defaults to "auto" */
|
|
13
|
+
initialHeight?: string;
|
|
14
|
+
} & import("react").RefAttributes<TextAreaRef>>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function cast<T>(val: any): asserts val is T;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mpen/react-basic-inputs",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"packageManager": "yarn@3.5.0",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -54,5 +54,8 @@
|
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"react": ">=17 <19",
|
|
56
56
|
"react-dom": ">=17 <19"
|
|
57
|
+
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"classcat": "^5.0.4"
|
|
57
60
|
}
|
|
58
61
|
}
|