mayoiga 0.1.8 → 0.2.0

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/src/component.tsx DELETED
@@ -1,23 +0,0 @@
1
- import React from 'react';
2
-
3
- import type {SliceProps, FieldProps, StateRestriction, Controller} from './index';
4
-
5
- type ControlledComponentProps<State extends StateRestriction, Props> = Props & {
6
- controller: Controller<State>;
7
- };
8
-
9
- export function Slicer<State extends StateRestriction, R extends ReadonlyArray<unknown>>(
10
- props: ControlledComponentProps<State, SliceProps<State, R>>,
11
- ) {
12
- const {controller, ...restProps} = props;
13
- const {Slicer: Delegate} = controller.components;
14
- return <Delegate {...restProps} />;
15
- }
16
-
17
- export function Field<State extends StateRestriction, Name extends keyof State>(
18
- props: ControlledComponentProps<State, FieldProps<State, Name>>,
19
- ) {
20
- const {controller, ...restProps} = props;
21
- const {Field: Delegate} = controller.components;
22
- return <Delegate {...restProps} />;
23
- }
@@ -1,241 +0,0 @@
1
- import {render, screen, waitFor} from '@testing-library/react';
2
- import userEvent from '@testing-library/user-event';
3
-
4
- import React from 'react';
5
-
6
- import {test, expect} from 'vitest';
7
-
8
- import * as zod from 'zod';
9
-
10
- import {createFormHook, useFormSlice} from './index';
11
-
12
- import type {Controller} from './index';
13
-
14
- const testSchema = zod.object({
15
- name: zod.string(),
16
- description: zod.string(),
17
- age: zod
18
- .string()
19
- .transform((val) => Number(val))
20
- .refine((val) => (Number.isNaN(val) ? 0 : val)),
21
- marked: zod.boolean(),
22
- });
23
-
24
- // every fields are optional for test
25
- type FormStateBeforeValidation = {
26
- name: string;
27
- description: string;
28
- age: string;
29
- marked: boolean;
30
- };
31
-
32
- const initialState = {name: '', description: '', age: '', marked: false};
33
- const createTestHook = () => createFormHook(initialState as FormStateBeforeValidation, testSchema);
34
-
35
- const AgeInputComponent = (props: {controller: Controller<FormStateBeforeValidation>}) => {
36
- const {controller} = props;
37
- const [[age, errorMessage], {handleChange}] = useFormSlice(controller, (s) => [s.value.age, s.errors.age] as const);
38
- return (
39
- <div>
40
- <input
41
- data-testid="age-input"
42
- value={age}
43
- name="age"
44
- onChange={(e) => {
45
- handleChange('age' as const, e.currentTarget.value);
46
- }}
47
- />
48
- <span data-testid="age-error">{errorMessage}</span>
49
- </div>
50
- );
51
- };
52
-
53
- // afterEach(cleanup);
54
-
55
- test('useSelector can select the fields', async () => {
56
- const TestFormHook = createTestHook();
57
-
58
- const Top = () => {
59
- return (
60
- <div>
61
- <AgeInputComponent controller={TestFormHook.controller} />
62
- </div>
63
- );
64
- };
65
-
66
- render(<Top />);
67
-
68
- const INPUT_AGE = '4';
69
-
70
- // fireEvent.change(screen.getByTestId('age-input') , {target: {value: INPUT_AGE}})
71
- // calling `userEvent` in here will cause race condition...
72
- await userEvent.type(screen.getByTestId('age-input'), INPUT_AGE);
73
- // TestFormHook.actions.handleBulkChange(prev => ({...prev, name: 'test', description: 'desc'}));
74
-
75
- await waitFor(() => {
76
- const ageInput: HTMLInputElement = screen.getByTestId('age-input');
77
- expect(ageInput.value).toBe(INPUT_AGE);
78
- });
79
- });
80
-
81
- test('zod parse value before submit', async () => {
82
- const TestFormHook = createTestHook();
83
-
84
- const INPUT_AGE = '9';
85
- const PARSED_RESULT = Number(INPUT_AGE);
86
-
87
- const handleSubmitTester = TestFormHook.handleSubmit((e) => (result) => {
88
- if (result.success) {
89
- expect(result.data.age).toBe(PARSED_RESULT);
90
- } else {
91
- expect('should not called').toBe(result.error);
92
- }
93
- return Promise.resolve('returns value');
94
- });
95
-
96
- const Top = () => {
97
- return (
98
- <div>
99
- <AgeInputComponent controller={TestFormHook} />
100
- <button
101
- data-testid="submit-button"
102
- onClick={async (e) => {
103
- const returnedValue = await handleSubmitTester(e);
104
- expect(returnedValue).toBe('returns value');
105
- }}
106
- />
107
- </div>
108
- );
109
- };
110
-
111
- render(<Top />);
112
-
113
- const ageInput: HTMLInputElement = screen.getByTestId('age-input');
114
- await userEvent.type(ageInput, INPUT_AGE);
115
-
116
- await waitFor(() => {
117
- const ageInput: HTMLInputElement = screen.getByTestId('age-input');
118
- expect(ageInput.value).toBe(INPUT_AGE);
119
- });
120
-
121
- const submitButton = screen.getByTestId('submit-button');
122
- await userEvent.click(submitButton);
123
-
124
- await waitFor(() => {
125
- console.log('waiting');
126
- TestFormHook.api.actions.peek((s) => {
127
- expect(s.value.age).toBe(INPUT_AGE);
128
- expect(s.errors.age).toBeNull();
129
- });
130
- });
131
- });
132
-
133
- test('able to handle parse error on submit', async () => {
134
- const TestFormHook = createTestHook();
135
-
136
- const INPUT_AGE = 'aaaa';
137
- const EXPECTED_ISSUE = {
138
- code: 'custom',
139
- message: 'Invalid input',
140
- path: ['age'],
141
- };
142
-
143
- const handleSubmitTester = TestFormHook.handleSubmit((e) => (result) => {
144
- if (result.success) {
145
- expect('should not succeed').toBe(false);
146
- } else {
147
- expect(result.success).toBe(false);
148
- expect(result.error).toStrictEqual([EXPECTED_ISSUE]);
149
- }
150
- return Promise.resolve('returns value');
151
- });
152
-
153
- const Top = () => {
154
- return (
155
- <div>
156
- <AgeInputComponent controller={TestFormHook} />
157
- <button
158
- data-testid="submit-button"
159
- onClick={async (e) => {
160
- const returnedValue = await handleSubmitTester(e);
161
- expect(returnedValue).toBe('returns value');
162
- }}
163
- />
164
- </div>
165
- );
166
- };
167
-
168
- render(<Top />);
169
-
170
- const ageInput: HTMLInputElement = screen.getByTestId('age-input');
171
- await userEvent.type(ageInput, INPUT_AGE);
172
-
173
- await waitFor(() => {
174
- const ageInput: HTMLInputElement = screen.getByTestId('age-input');
175
- expect(ageInput.value).toBe(INPUT_AGE);
176
- });
177
-
178
- const submitButton = screen.getByTestId('submit-button');
179
- await userEvent.click(submitButton);
180
-
181
- await waitFor(() => {
182
- console.log('waiting');
183
- TestFormHook.api.actions.peek((s) => {
184
- expect(s.value.age).toBe(INPUT_AGE);
185
- expect(s.errors.age).toBe(EXPECTED_ISSUE.message);
186
- });
187
- });
188
- });
189
-
190
- test('User is able to re-initialize the form state', async () => {
191
- const TestFormHook = createTestHook();
192
-
193
- const Top = () => {
194
- return (
195
- <div>
196
- <AgeInputComponent controller={TestFormHook.controller} />
197
- </div>
198
- );
199
- };
200
-
201
- render(<Top />);
202
-
203
- const ageInput: HTMLInputElement = screen.getByTestId('age-input');
204
- await userEvent.type(ageInput, '9999');
205
-
206
- expect(TestFormHook.api.getState().isDirty).toBeTruthy();
207
- TestFormHook.actions.initializeForm({}, {cleanup: true});
208
- expect(TestFormHook.api.getState().isDirty).toBeFalsy();
209
- });
210
-
211
- test('User is able to push server side validation', async () => {
212
- const TestFormHook = createTestHook();
213
-
214
- const Top = () => {
215
- return (
216
- <div>
217
- <AgeInputComponent controller={TestFormHook.controller} />
218
- </div>
219
- );
220
- };
221
-
222
- render(<Top />);
223
-
224
- const ageInput: HTMLInputElement = screen.getByTestId('age-input');
225
- await userEvent.type(ageInput, '9999');
226
-
227
- TestFormHook.actions.pushFormErrors((_s) => ({age: 'server side error'}));
228
- const state1 = TestFormHook.api.getState();
229
- expect(state1.isDirty).toBeTruthy();
230
- expect(state1.isValid).toBeFalsy();
231
- expect(state1.errors.age).toEqual('server side error');
232
-
233
- await userEvent.type(ageInput, 'hgoehoge');
234
-
235
- // confirm empty field won't overwrite any messages
236
- TestFormHook.actions.pushFormErrors((_s) => ({}));
237
- const state2 = TestFormHook.api.getState();
238
- expect(state2.isDirty).toBeTruthy();
239
- expect(state2.isValid).toBeFalsy();
240
- expect(state2.errors.age).toEqual('Invalid input');
241
- });
package/src/index.ts DELETED
@@ -1,373 +0,0 @@
1
- import {createStore} from 'nozuchi';
2
- import {createElement, Fragment, useEffect, useMemo} from 'react';
3
-
4
- import {isChangeEvent} from './utils';
5
-
6
- import type {Subscriber} from 'nozuchi';
7
- import type {BaseSyntheticEvent, ChangeEvent, ReactNode, ReactElement} from 'react';
8
-
9
- import type {infer as zodInfer, SafeParseReturnType, ZodIssue, ZodType} from 'zod';
10
-
11
- export type StateRestriction = Record<string, any>;
12
-
13
- type FormStatus = {
14
- isDirty: boolean;
15
- isValid: boolean;
16
- };
17
-
18
- type FormErrors<State extends StateRestriction> = {
19
- [k in keyof State]: string | null;
20
- };
21
-
22
- export type FullFormState<State extends StateRestriction> = FormStatus & {value: State; errors: FormErrors<State>};
23
-
24
- export type HandleChangeAction<State extends StateRestriction, R = void> = <Name extends keyof State>(name: Name, value: State[Name]) => R;
25
-
26
- export type FieldProps<State extends StateRestriction, Name extends keyof State> = {
27
- name: Name;
28
- children: (
29
- tool: {
30
- name: Name;
31
- value: State[Name];
32
- onChange(this: void, name: Name, value: State[Name]): void;
33
- onChange(this: void, e: ChangeEvent<HTMLElement>): void;
34
- },
35
- value: State[Name],
36
- errorMessage: string | null,
37
- ) => ReactNode;
38
-
39
- /**
40
- * Mayoiga Field will memoize the result of renderProps for performance.
41
- * If your renderProps depends on external variables, you should apply those on deps
42
- */
43
- deps?: ReadonlyArray<unknown>;
44
- };
45
-
46
- export type SliceProps<State extends StateRestriction, Selected extends ReadonlyArray<unknown>> = {
47
- selector: (s: FullFormState<State>) => Selected;
48
- children: (tool: {handleChange: HandleChangeAction<State>}, ...value: Readonly<Selected>) => ReactNode;
49
- /**
50
- * Mayoiga Slicer will memoize the result of renderProps for performance.
51
- * If your renderProps depends on external variables, you should apply those on deps
52
- *
53
- * ```typescript
54
- * <Slicer selector={s => [s.value.selected]} deps={[selection]}>{(tools, selected: string) => <AwesomeSelection value={selected} options={selection} />}</Slicer>
55
- * ```
56
- */
57
- deps?: ReadonlyArray<unknown>;
58
- };
59
-
60
- type FormControllerBehavior<StateBeforeValidation extends StateRestriction> = {
61
- /** For testing */
62
- peek: (
63
- eyeball: (prev: FullFormState<StateBeforeValidation>) => void,
64
- ) => (prev: FullFormState<StateBeforeValidation>) => FullFormState<StateBeforeValidation>;
65
-
66
- reset: () => () => FullFormState<StateBeforeValidation>;
67
-
68
- /**
69
- * Initialize form state without validation.
70
- *
71
- * @param initialVal value to initialize form state
72
- * @param opts.cleanup if true, form state is become pristine (which means, `form.isDirty` is going to be false) .
73
- */
74
- initializeForm: (
75
- initialVal: Partial<StateBeforeValidation>,
76
- opts?: {
77
- cleanup?: true;
78
- },
79
- ) => (prev: FullFormState<StateBeforeValidation>) => FullFormState<StateBeforeValidation>;
80
-
81
- /**
82
- * Handle issue that zod reported
83
- */
84
- handleIssues: (issues: ReadonlyArray<ZodIssue>) => (prev: FullFormState<StateBeforeValidation>) => FullFormState<StateBeforeValidation>;
85
-
86
- /**
87
- * You can handle server side or other external error via this action.
88
- */
89
- pushFormErrors: (
90
- validator: (state: StateBeforeValidation) => Partial<FormErrors<StateBeforeValidation>>,
91
- ) => (prev: FullFormState<StateBeforeValidation>) => FullFormState<StateBeforeValidation>;
92
-
93
- reduceFormErrors: (
94
- reducer: (prev: FormErrors<StateBeforeValidation>) => FormErrors<StateBeforeValidation>,
95
- ) => (prev: FullFormState<StateBeforeValidation>) => FullFormState<StateBeforeValidation>;
96
-
97
- /**
98
- */
99
- handleChange: HandleChangeAction<
100
- StateBeforeValidation,
101
- (prev: FullFormState<StateBeforeValidation>) => FullFormState<StateBeforeValidation>
102
- >;
103
-
104
- reduceFormState: (
105
- reducer: (prevValue: StateBeforeValidation) => StateBeforeValidation,
106
- ) => (prev: FullFormState<StateBeforeValidation>) => FullFormState<StateBeforeValidation>;
107
- handleBulkChange: (
108
- setter: (prev: StateBeforeValidation) => Partial<StateBeforeValidation>,
109
- ) => (prev: FullFormState<StateBeforeValidation>) => FullFormState<StateBeforeValidation>;
110
- };
111
-
112
- const initialFormState = Object.freeze({isDirty: false, isValid: false});
113
-
114
- function createFormStore<StateBeforeValidation extends StateRestriction, Schema extends ZodType<any, any, any>>(
115
- initialState: StateBeforeValidation,
116
- schema: Schema,
117
- ): Subscriber<FullFormState<StateBeforeValidation>, FormControllerBehavior<StateBeforeValidation>> {
118
- type FullState = FullFormState<StateBeforeValidation>;
119
- type Err = FormErrors<StateBeforeValidation>;
120
- type StateKeys = keyof StateBeforeValidation;
121
-
122
- const initialErrors = Object.freeze(
123
- Object.keys(initialState).reduce(
124
- (buf, k) => {
125
- buf[k as StateKeys] = null;
126
- return buf;
127
- },
128
- {} as any as Err,
129
- ),
130
- );
131
-
132
- const fullInitialState: FullState = Object.freeze({
133
- ...initialFormState,
134
- value: {...initialState},
135
- errors: {...initialErrors},
136
- });
137
-
138
- const withValidation = (prev: FullState, newValue: StateBeforeValidation) => {
139
- const result = schema.safeParse(newValue);
140
- if (result.success) {
141
- return {...prev, isDirty: true, isValid: true, value: newValue, errors: initialErrors};
142
- }
143
- const newErrors = result.error.issues.reduce(
144
- (buf, iss) => {
145
- // TODO: should support nested value?
146
- const shallowPath = iss.path[0];
147
- if (shallowPath === undefined) {
148
- return buf;
149
- }
150
- buf[shallowPath as any as StateKeys] = iss.message;
151
- return buf;
152
- },
153
- {} as any as Err,
154
- );
155
-
156
- return {...prev, isDirty: true, isValid: false, errors: newErrors, value: newValue};
157
- };
158
-
159
- return createStore(fullInitialState, {
160
- peek: (eyeball) => (prev) => {
161
- eyeball(prev);
162
- return prev;
163
- },
164
-
165
- reset: () => () => fullInitialState,
166
- initializeForm: (initialVal, opts) => (prev) => ({
167
- ...prev,
168
- value: {...prev.value, ...initialVal},
169
- errors: {...initialErrors},
170
- isDirty: opts?.cleanup === true ? false : prev.isDirty,
171
- }),
172
- handleIssues: (issues) => (prev) => {
173
- // FIXME: handle duplication
174
- const newErrors = issues.reduce(
175
- (buf, iss) => {
176
- const shallowPath = iss.path[0];
177
- if (shallowPath === undefined) {
178
- return buf;
179
- }
180
- buf[shallowPath as any as StateKeys] = iss.message;
181
- return buf;
182
- },
183
- {} as any as Err,
184
- );
185
-
186
- return {...prev, isDirty: true, isValid: false, errors: newErrors};
187
- },
188
- pushFormErrors: (validator) => (prev) => {
189
- const pushedErrors = validator(prev.value);
190
- const allErrors = {...prev.errors, ...pushedErrors};
191
- return {...prev, isValid: Object.keys(allErrors).length === 0, errors: allErrors};
192
- },
193
- reduceFormErrors: (reducer) => (prev) => {
194
- return {...prev, errors: reducer(prev.errors)};
195
- },
196
- handleChange: (name, value) => (prev) => {
197
- if (name in prev.value === false) {
198
- return prev;
199
- }
200
- const newValue = {...prev.value, [name]: value};
201
- return withValidation(prev, newValue);
202
- },
203
- reduceFormState: (reducer) => (prev) => {
204
- const reducedState = reducer(prev.value);
205
- return withValidation(prev, reducedState);
206
- },
207
- handleBulkChange: (setter) => (prev) => {
208
- const mergedNewState = {...prev.value, ...setter(prev.value)};
209
- return withValidation(prev, mergedNewState);
210
- },
211
- });
212
- }
213
-
214
- type ActionsCanBePublic<State extends StateRestriction> = {
215
- reset: VoidFunction;
216
-
217
- /**
218
- * Initialize form state without validation.
219
- *
220
- * @param initialVal value to initialize form state
221
- * @param opts.cleanup if true, form state is become pristine (which means, `form.isDirty` is going to be false) .
222
- */
223
- initializeForm: (
224
- initial: Partial<State>,
225
- opts?: {
226
- cleanup?: true;
227
- },
228
- ) => void;
229
- pushFormErrors: (validator: (state: State) => Partial<FormErrors<State>>) => void;
230
- handleChange: HandleChangeAction<State>;
231
- handleBulkChange: (setter: (prev: State) => Partial<State>) => void;
232
- };
233
-
234
- /**
235
- * Restricted type interface of the form store
236
- * TODO: more restriction
237
- */
238
- export type Controller<State extends StateRestriction> = {
239
- useInitialize: (initialValue: Partial<State>) => void;
240
- useSelector: <R>(selector: (state: FullFormState<State>) => R, isEqual?: (prev: R, current: R) => boolean) => R;
241
-
242
- actions: ActionsCanBePublic<State>;
243
- components: {
244
- Field: <Name extends keyof State>(props: FieldProps<State, Name>) => ReactElement<any, any> | null;
245
- Slicer: <Selected extends ReadonlyArray<unknown>>(props: SliceProps<State, Selected>) => ReactElement<any, any> | null;
246
- };
247
- };
248
-
249
- type FormHook<State extends StateRestriction, Schema extends ZodType<any, any, any>> = {
250
- controller: Controller<State>;
251
-
252
- /**
253
- * @param handler handling logic
254
- * @returns created handler is always return Promise, because of the schema validation run it asynchronously.
255
- */
256
- handleSubmit: <R>(
257
- handler: (
258
- e: BaseSyntheticEvent,
259
- ) => (val: {success: true; data: zodInfer<Schema>; error: undefined} | {success: false; error: ReadonlyArray<ZodIssue>}) => R,
260
- ) => (e: BaseSyntheticEvent) => Promise<R>;
261
-
262
- api: Subscriber<FullFormState<State>, FormControllerBehavior<State>>;
263
- } & Controller<State>;
264
-
265
- /**
266
- * @typeParam InitialState should be shallow
267
- */
268
- export function createFormHook<StateBeforeValidation extends StateRestriction, Schema extends ZodType<any, any, any>>(
269
- initialState: StateBeforeValidation,
270
- schema: Schema,
271
- ): FormHook<StateBeforeValidation, Schema> {
272
- const store = createFormStore(initialState, schema);
273
-
274
- const formHook = {
275
- useInitialize: (initialValue: Partial<StateBeforeValidation>) => {
276
- useEffect(() => {
277
- store.actions.initializeForm(initialValue);
278
- }, []);
279
- },
280
- useSelector: store.useSelector,
281
- handleSubmit: <R>(
282
- handler: (
283
- e: BaseSyntheticEvent,
284
- ) => (val: {success: true; data: zodInfer<Schema>; error: undefined} | {success: false; error: ReadonlyArray<ZodIssue>}) => R,
285
- ) => {
286
- return (e: BaseSyntheticEvent) => {
287
- const eventHandled = handler(e);
288
- const value = store.getState().value;
289
- return schema.safeParseAsync(value).then((result: SafeParseReturnType<StateBeforeValidation, zodInfer<typeof schema>>) => {
290
- // FIXME: hook formを捨ててzodのバージョンをあげればこの辺に型をつけられる
291
- if (result.success) {
292
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
293
- return eventHandled({success: true, data: result.data, error: undefined});
294
- }
295
-
296
- const handled: any = eventHandled({success: false, error: result.error.issues});
297
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
298
- if (typeof handled['then'] === 'function') {
299
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
300
- return handled.then((x: any) => {
301
- store.actions.handleIssues(result.error.issues);
302
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
303
- return x;
304
- }) as R;
305
- } else {
306
- store.actions.handleIssues(result.error.issues);
307
- return handled as R;
308
- }
309
- });
310
- };
311
- },
312
-
313
- components: {
314
- Slicer<R extends ReadonlyArray<unknown>>(props: SliceProps<StateBeforeValidation, R>) {
315
- const {selector, children, deps} = props;
316
- const slicedValues = formHook.useSelector(selector);
317
- return useMemo(
318
- () => {
319
- const renderContent = children({handleChange: formHook.actions.handleChange}, ...slicedValues);
320
- return createElement(Fragment, {}, renderContent);
321
- },
322
- deps ? [...slicedValues, ...deps] : slicedValues,
323
- );
324
- },
325
- Field<Name extends keyof StateBeforeValidation>(props: FieldProps<StateBeforeValidation, Name>) {
326
- const {name, children, deps} = props;
327
- const [value, errMsg] = store.useSelector((s) => [s.value[name], s.errors[name]] as const);
328
- return useMemo(
329
- () => {
330
- const renderContent = children(
331
- {
332
- name,
333
- value,
334
- onChange: (name_or_event: unknown, value_or_none?: unknown) => {
335
- if (typeof name_or_event === 'string') {
336
- // assumes name is valid if it's string
337
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
338
- return store.actions.handleChange(name_or_event as Name, value_or_none as any);
339
- }
340
- if (isChangeEvent(name_or_event)) {
341
- const target: any = name_or_event.currentTarget || name_or_event.target;
342
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
343
- return store.actions.handleChange(target.name, target.value);
344
- }
345
- throw new Error('`handleChange` handles unexpected formed object.');
346
- },
347
- },
348
- value,
349
- errMsg,
350
- );
351
- return createElement(Fragment, {}, renderContent);
352
- },
353
- deps ? [value, errMsg, ...deps] : [value, errMsg],
354
- );
355
- },
356
- },
357
- actions: store.actions,
358
- };
359
-
360
- return {
361
- controller: formHook,
362
- api: store,
363
- ...formHook,
364
- };
365
- }
366
-
367
- export function useFormSlice<State extends StateRestriction, Sliced>(
368
- formController: Controller<State>,
369
- selector: (s: FullFormState<State>) => Sliced,
370
- ) {
371
- const sliced = formController.useSelector((s) => selector(s));
372
- return [sliced, formController.actions] as const;
373
- }
package/src/utils.ts DELETED
@@ -1,8 +0,0 @@
1
- import type {ChangeEvent} from 'react';
2
-
3
- export function isChangeEvent<T>(x: any): x is ChangeEvent<T> {
4
- if (typeof x !== 'object') {
5
- return false;
6
- }
7
- return 'target' in x;
8
- }
package/tsconfig.json DELETED
@@ -1,36 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "esnext",
4
- "module": "esnext",
5
- "declaration": true,
6
- "jsx": "react",
7
- "moduleResolution": "node",
8
- // TODO: skip while vitest is broken
9
- "skipLibCheck": true,
10
-
11
- "strict": true,
12
-
13
- "noUnusedLocals": true,
14
- "noUnusedParameters": false,
15
- "noImplicitReturns": true,
16
- "noFallthroughCasesInSwitch": true,
17
- "noUncheckedIndexedAccess": true,
18
- "useUnknownInCatchVariables": true,
19
- "exactOptionalPropertyTypes": true,
20
-
21
- "forceConsistentCasingInFileNames": true,
22
- "removeComments": false,
23
-
24
- "allowSyntheticDefaultImports": true,
25
- "esModuleInterop": true,
26
- "noErrorTruncation": true,
27
-
28
- "lib": ["dom", "esnext"],
29
-
30
- "rootDir": "./src",
31
- "outDir": "./dist"
32
- },
33
- "include": [
34
- "./src/**/*"
35
- ],
36
- }
package/vite.config.js DELETED
@@ -1,10 +0,0 @@
1
- import {defineConfig} from 'vitest/config';
2
-
3
- export default defineConfig({
4
- test: {
5
- globals: true,
6
- // environment: 'happy-dom', DO NOT USE IT
7
- environment: 'jsdom',
8
- setupFiles: ['./setup/vite-global-setup.js'],
9
- },
10
- })