@mpen/react-basic-inputs 0.1.7 → 0.1.9

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 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 usedKeys = new Map;
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
- let fixedKey = defaultMakeKey(opt, idx);
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
@@ -259,8 +267,59 @@ const TextArea = React.forwardRef((function TextArea({onInput, style, initialHei
259
267
  });
260
268
  }));
261
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, labelClassName, inputClassName, textClassName, ...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
+ "aria-disabled": rest.disabled,
300
+ children: jsxRuntime.jsxs("label", {
301
+ className: labelClassName,
302
+ children: [ jsxRuntime.jsx("input", {
303
+ ...rest,
304
+ className: inputClassName,
305
+ value: idx,
306
+ onChange,
307
+ name,
308
+ type: "radio"
309
+ }), jsxRuntime.jsx("span", {
310
+ className: textClassName,
311
+ children: text
312
+ }) ]
313
+ })
314
+ }, fixedKey);
315
+ }))
316
+ });
317
+ }
318
+
262
319
  exports.Input = Input;
263
320
 
321
+ exports.RadioMenu = RadioMenu;
322
+
264
323
  exports.Select = Select;
265
324
 
266
325
  exports.TextArea = TextArea;
package/dist/bundle.d.ts CHANGED
@@ -2,3 +2,4 @@ export * from './components/Select';
2
2
  export * from './components/TextInput';
3
3
  export * from './components/Input';
4
4
  export * from './components/TextArea';
5
+ export * from './components/RadioMenu';
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 usedKeys = new Map;
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
- let fixedKey = defaultMakeKey(opt, idx);
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
@@ -257,4 +265,53 @@ const TextArea = forwardRef((function TextArea({onInput, style, initialHeight =
257
265
  });
258
266
  }));
259
267
 
260
- export { Input, Select, TextArea, TextInput };
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, labelClassName, inputClassName, textClassName, ...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
+ "aria-disabled": rest.disabled,
298
+ children: jsxs("label", {
299
+ className: labelClassName,
300
+ children: [ jsx("input", {
301
+ ...rest,
302
+ className: inputClassName,
303
+ value: idx,
304
+ onChange,
305
+ name,
306
+ type: "radio"
307
+ }), jsx("span", {
308
+ className: textClassName,
309
+ children: text
310
+ }) ]
311
+ })
312
+ }, fixedKey);
313
+ }))
314
+ });
315
+ }
316
+
317
+ export { Input, RadioMenu, Select, TextArea, TextInput };
@@ -0,0 +1,33 @@
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
+ labelClassName?: string;
11
+ inputClassName?: string;
12
+ textClassName?: string;
13
+ }, 'type' | 'children' | 'checked' | 'name' | 'className'>;
14
+ export type RadioMenuChangeEvent<T> = {
15
+ value: T;
16
+ index: number;
17
+ type: 'change';
18
+ timeStamp: number;
19
+ target: HtmlInputElement;
20
+ };
21
+ export type RadioMenuChangeEventHandler<T> = EventCallback<RadioMenuChangeEvent<T>>;
22
+ export type RadioMenuProps<T extends NonNil> = {
23
+ options: RadioMenuOption<T>[];
24
+ value?: T | null;
25
+ className?: string;
26
+ /**
27
+ * Value comparison function. Defaults to {@linkcode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is|Object.is}
28
+ */
29
+ valueEquals?: (a: T, b: T) => boolean;
30
+ onChange?: RadioMenuChangeEventHandler<T>;
31
+ name?: string;
32
+ };
33
+ export declare function RadioMenu<T extends NonNil>(menu: RadioMenuProps<T>): JSX.Element;
@@ -0,0 +1 @@
1
+ export declare function cast<T>(val: any): asserts val is T;
@@ -0,0 +1,8 @@
1
+ import { Key } from 'react';
2
+ /**
3
+ * Produces unique React Keys from an option.
4
+ */
5
+ export declare class KeyFixer {
6
+ usedKeys: Map<Key, number>;
7
+ fix(opt: any, idx: number): Key;
8
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mpen/react-basic-inputs",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
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
  }