@equinor/apollo-utils 0.1.1 → 0.1.3-beta.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/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @equinor/apollo-utils
2
2
 
3
+ ## 0.1.3
4
+
5
+ ### Patch Changes
6
+
7
+ - e1354f0: Move edit cells to apollo-utils
8
+ - Updated dependencies [e1354f0]
9
+ - @equinor/apollo-components@3.1.2
10
+
11
+ ## 0.1.2
12
+
13
+ ### Patch Changes
14
+
15
+ - 7fa24ff: Move cells to @equinor/apollo-utils and extract common functionality into separate package
16
+ - Updated dependencies [07e344a]
17
+ - Updated dependencies [7fa24ff]
18
+ - apollo-common@0.1.2
19
+
3
20
  ## 0.1.1
4
21
 
5
22
  ### Patch Changes
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@equinor/apollo-utils",
3
3
  "main": "src/index.ts",
4
4
  "types": "src/index.ts",
5
- "version": "0.1.1",
5
+ "version": "0.1.3-beta.0",
6
6
  "license": "MIT",
7
7
  "scripts": {
8
8
  "build": "tsup src/index.ts --format esm,cjs --dts --external react",
@@ -22,7 +22,14 @@
22
22
  "typescript": "^4.9.5",
23
23
  "zod": "^3.20.6"
24
24
  },
25
+ "peerDependencies": {
26
+ "react": "^18.2.0",
27
+ "react-dom": "^18.2.0",
28
+ "react-hook-form": "^7.43.8",
29
+ "@equinor/apollo-components": "^3.1.0"
30
+ },
25
31
  "devDependencies": {
32
+ "@equinor/apollo-components": "*",
26
33
  "@testing-library/react": "^13.4.0",
27
34
  "@testing-library/react-hooks": "^8.0.1",
28
35
  "@vitest/ui": "^0.28.5",
@@ -31,6 +38,7 @@
31
38
  "jsdom": "^21.1.0",
32
39
  "react": "^18.2.0",
33
40
  "react-dom": "^18.2.0",
41
+ "react-hook-form": "^7.43.8",
34
42
  "vitest": "^0.28.5"
35
43
  }
36
44
  }
@@ -0,0 +1,34 @@
1
+ import { Checkbox, EdsProvider } from '@equinor/eds-core-react'
2
+ import { CellContext } from '@tanstack/react-table'
3
+ import { Controller, useFormContext } from 'react-hook-form'
4
+ import { FormMeta, useEditMode } from '../form-meta'
5
+
6
+ export function EditableCheckboxCell<T extends FormMeta>(context: CellContext<T, boolean>) {
7
+ const editMode = useEditMode()
8
+ const { control } = useFormContext()
9
+
10
+ if (!editMode)
11
+ return (
12
+ <EdsProvider density="compact">
13
+ <Checkbox
14
+ enterKeyHint="next"
15
+ aria-label="readonly"
16
+ readOnly={true}
17
+ checked={context.getValue()}
18
+ disabled={true}
19
+ />
20
+ </EdsProvider>
21
+ )
22
+
23
+ return (
24
+ <Controller
25
+ control={control}
26
+ name={context.column.id}
27
+ render={({ field: { value, ...field } }) => (
28
+ <EdsProvider density="compact">
29
+ <Checkbox enterKeyHint="send" aria-label="editable" checked={value} {...field} />
30
+ </EdsProvider>
31
+ )}
32
+ />
33
+ )
34
+ }
@@ -0,0 +1,80 @@
1
+ import { TypographyCustom } from '@equinor/apollo-components'
2
+ import { TextField } from '@equinor/eds-core-react'
3
+ import { CellContext } from '@tanstack/react-table'
4
+ import { ChangeEvent, useMemo } from 'react'
5
+ import { Controller, useFormContext } from 'react-hook-form'
6
+ import styled from 'styled-components'
7
+ import { FormMeta, useEditMode } from '../form-meta'
8
+ import { getHelperTextProps } from './utils'
9
+
10
+ export interface EditableDateCellProps<T extends FormMeta> extends CellContext<T, unknown> {
11
+ dateStringFormatter?: (date: string) => string
12
+ }
13
+
14
+ export function EditableDateCell<T extends FormMeta>(props: EditableDateCellProps<T>) {
15
+ const { dateStringFormatter, ...context } = props
16
+ const rawValue = context.getValue<string>()
17
+
18
+ const editMode = useEditMode()
19
+ const { control } = useFormContext()
20
+
21
+ const formattedValue = useMemo(
22
+ () => dateStringFormatter?.(rawValue) ?? rawValue,
23
+ [rawValue, dateStringFormatter]
24
+ )
25
+
26
+ if (!editMode) return <TypographyCustom truncate>{formattedValue}</TypographyCustom>
27
+
28
+ return (
29
+ <Controller
30
+ control={control}
31
+ name={context.column.id}
32
+ render={({ field: { value, onChange, ...field }, fieldState: { error } }) => (
33
+ <InlineTextField
34
+ id={context.column.id}
35
+ type="date"
36
+ autoComplete="none"
37
+ value={value ? parseISODate(value) : ''}
38
+ onChange={(e: ChangeEvent<HTMLInputElement>) =>
39
+ onChange(e.target.value ? parseISODate(e.target.value) : '')
40
+ }
41
+ {...getHelperTextProps({ error })}
42
+ {...field}
43
+ />
44
+ )}
45
+ />
46
+ )
47
+ }
48
+
49
+ /**
50
+ * Formats a date string into `yyyy-mm-dd` format.
51
+ */
52
+ function parseISODate(dateString: string) {
53
+ const date = new Date(dateString)
54
+ const offset = date.getTimezoneOffset()
55
+ const dateWithoutOffset = new Date(date.getTime() - offset * 60 * 1000)
56
+ return dateWithoutOffset.toISOString().split('T')[0]
57
+ }
58
+
59
+ const InlineTextField = styled(TextField)`
60
+ /*
61
+ TODO: Improve input based on feedback from UX
62
+ & > div {
63
+ background: transparent;
64
+ margin: 0 -0.5rem;
65
+ padding: 0 0.5rem;
66
+ box-shadow: none;
67
+ width: auto;
68
+ }
69
+
70
+ input {
71
+ padding: 0;
72
+ letter-spacing: 0;
73
+ font-weight: 400;
74
+ color: inherit;
75
+
76
+ ::placeholder {
77
+ color: red;
78
+ }
79
+ } */
80
+ `
@@ -0,0 +1,68 @@
1
+ import { TypographyCustom } from '@equinor/apollo-components'
2
+ import { Autocomplete } from '@equinor/eds-core-react'
3
+ import { CellContext } from '@tanstack/react-table'
4
+ import { Controller, useFormContext } from 'react-hook-form'
5
+ import styled from 'styled-components'
6
+ import { FormMeta, useEditMode } from '../form-meta'
7
+
8
+ export interface Option {
9
+ label: string
10
+ value: string
11
+ }
12
+
13
+ export interface EditableDropdownCellProps<T extends FormMeta> extends CellContext<T, unknown> {
14
+ /**
15
+ * `Option.value` is used internally to get and update selection state. `Option.label` is *only* for visual purposes.
16
+ */
17
+ options: Option[]
18
+ }
19
+
20
+ function buildEmptyOption(): Option {
21
+ return { label: '', value: '' }
22
+ }
23
+
24
+ export function EditableDropdownCell<T extends FormMeta>(props: EditableDropdownCellProps<T>) {
25
+ const { options, ...context } = props
26
+ const editMode = useEditMode()
27
+ const { control } = useFormContext()
28
+ if (!editMode) return <TypographyCustom truncate>{context.getValue() as any}</TypographyCustom>
29
+
30
+ return (
31
+ <Controller
32
+ control={control}
33
+ name={context.column.id}
34
+ render={({ field: { value, onChange, ...field } }) => {
35
+ const selectedOption = options.find((option) => option.value === value) ?? buildEmptyOption
36
+ return (
37
+ <AutocompleteCustom
38
+ label=""
39
+ // Casting is due to stying the Autocomplete, plain EDS Autocomplete works
40
+ // Fixed when workaround is not needed anymore
41
+ selectedOptions={selectedOption && ([selectedOption] as Option[])}
42
+ options={options}
43
+ optionLabel={(option) => (option as Option)?.label ?? ''}
44
+ aria-required
45
+ hideClearButton
46
+ aria-autocomplete="none"
47
+ onOptionsChange={(changes) => {
48
+ const [change] = changes.selectedItems
49
+ onChange((change as Option)?.value)
50
+ }}
51
+ {...field}
52
+ />
53
+ )
54
+ }}
55
+ />
56
+ )
57
+ }
58
+
59
+ // Requested in https://github.com/equinor/design-system/issues/2804
60
+ export const AutocompleteCustom = styled(Autocomplete)`
61
+ input[type='text'] {
62
+ overflow: hidden;
63
+ white-space: nowrap;
64
+ text-overflow: ellipsis;
65
+ padding-right: ${({ hideClearButton }) =>
66
+ hideClearButton ? `calc(8px + (24px * 1))` : `calc(8px + (24px * 2))`};
67
+ }
68
+ `
@@ -0,0 +1,57 @@
1
+ import { TypographyCustom } from '@equinor/apollo-components'
2
+ import { TextField } from '@equinor/eds-core-react'
3
+ import { CellContext } from '@tanstack/react-table'
4
+ import { ChangeEvent } from 'react'
5
+ import { Controller, useFormContext } from 'react-hook-form'
6
+ import styled from 'styled-components'
7
+ import { FormMeta, useEditMode } from '../form-meta'
8
+ import { getHelperTextProps } from './utils'
9
+
10
+ export function EditableNumberCell<T extends FormMeta>(context: CellContext<T, number>) {
11
+ const editMode = useEditMode()
12
+ const { control } = useFormContext()
13
+
14
+ if (!editMode) return <TypographyCustom truncate>{context.getValue()}</TypographyCustom>
15
+
16
+ return (
17
+ <Controller
18
+ control={control}
19
+ name={context.column.id}
20
+ render={({ field: { onChange, ...field }, fieldState: { error } }) => (
21
+ <>
22
+ <InlineTextField
23
+ id={context.column.id}
24
+ type="number"
25
+ autoComplete="none"
26
+ onChange={(e: ChangeEvent<HTMLInputElement>) => onChange(e.target.valueAsNumber)}
27
+ {...field}
28
+ {...getHelperTextProps({ error })}
29
+ />
30
+ </>
31
+ )}
32
+ />
33
+ )
34
+ }
35
+
36
+ const InlineTextField = styled(TextField)`
37
+ /*
38
+ TODO: Improve input based on feedback from UX
39
+ & > div {
40
+ background: transparent;
41
+ margin: 0 -0.5rem;
42
+ padding: 0 0.5rem;
43
+ box-shadow: none;
44
+ width: auto;
45
+ }
46
+
47
+ input {
48
+ padding: 0;
49
+ letter-spacing: 0;
50
+ font-weight: 400;
51
+ color: inherit;
52
+
53
+ ::placeholder {
54
+ color: red;
55
+ }
56
+ } */
57
+ `
@@ -0,0 +1,126 @@
1
+ import { PopoverCell, stopPropagation } from '@equinor/apollo-components'
2
+ import { Button, Dialog as EDS, Icon, TextField } from '@equinor/eds-core-react'
3
+ import { CellContext } from '@tanstack/react-table'
4
+ import { ChangeEvent, useState } from 'react'
5
+ import { Controller, useFormContext } from 'react-hook-form'
6
+ import styled from 'styled-components'
7
+ import { FormMeta, useEditMode } from '../form-meta'
8
+ import { getHelperTextProps } from './utils'
9
+
10
+ interface EdtiableTextAreaProps<T extends FormMeta> extends CellContext<T, string> {
11
+ title: string
12
+ }
13
+
14
+ export function EditableTextAreaCell<T extends FormMeta>(props: EdtiableTextAreaProps<T>) {
15
+ const { title, ...context } = props
16
+
17
+ const [textareaValue, setTextareaValue] = useState<string>(context.getValue())
18
+ const [open, setOpen] = useState(false)
19
+ const editMode = useEditMode()
20
+ const { control } = useFormContext()
21
+
22
+ const name = context.column.id
23
+
24
+ if (!editMode)
25
+ return <PopoverCell id={name + 'popover'} value={context.getValue()} title={title} />
26
+
27
+ const openDialog = () => setOpen(true)
28
+ const closeDialog = () => setOpen(false)
29
+
30
+ return (
31
+ <Controller
32
+ control={control}
33
+ name={name}
34
+ render={({ field: { onChange, ref, ...field }, fieldState: { error } }) => (
35
+ <>
36
+ {/* Inline input */}
37
+ <div
38
+ style={{
39
+ display: 'flex',
40
+ alignItems: 'center',
41
+ position: 'relative',
42
+ }}
43
+ >
44
+ <StyledTextField
45
+ id={field.name}
46
+ onChange={onChange}
47
+ ref={ref}
48
+ {...field}
49
+ {...getHelperTextProps({ error })}
50
+ />
51
+ <IconButton variant="ghost_icon" onClick={stopPropagation(openDialog)}>
52
+ <Icon name="arrow_up" size={24} style={{ transform: 'rotateZ(45deg)' }} />
53
+ </IconButton>
54
+ </div>
55
+
56
+ {/* Dialog */}
57
+ <EDS
58
+ open={open}
59
+ onClose={() => {
60
+ closeDialog()
61
+ onChange(textareaValue)
62
+ }}
63
+ isDismissable
64
+ style={{ width: 'min(50rem, calc(100vw - 4rem))' }}
65
+ >
66
+ <EDS.Header>
67
+ <EDS.Title>{title}</EDS.Title>
68
+ </EDS.Header>
69
+ <EDS.Content>
70
+ <TextField
71
+ style={{
72
+ maxWidth: '100%',
73
+ marginTop: '1rem',
74
+ }}
75
+ id={field.name + 'modal'}
76
+ multiline
77
+ rows={8}
78
+ name={field.name + 'modal'}
79
+ value={textareaValue}
80
+ onChange={(e: ChangeEvent<HTMLInputElement> | ChangeEvent<HTMLTextAreaElement>) => {
81
+ setTextareaValue(e.target.value)
82
+ }}
83
+ />
84
+ </EDS.Content>
85
+ <EDS.Actions style={{ display: 'flex', gap: '1rem' }}>
86
+ <Button
87
+ onClick={() => {
88
+ closeDialog()
89
+ onChange(textareaValue)
90
+ }}
91
+ >
92
+ Submit
93
+ </Button>
94
+ <Button
95
+ variant="ghost"
96
+ onClick={() => {
97
+ closeDialog()
98
+ setTextareaValue(context.getValue())
99
+ }}
100
+ >
101
+ Cancel
102
+ </Button>
103
+ </EDS.Actions>
104
+ </EDS>
105
+ </>
106
+ )}
107
+ />
108
+ )
109
+ }
110
+
111
+ const StyledTextField = styled(TextField)`
112
+ & input {
113
+ padding-right: 40px;
114
+ letter-spacing: 0;
115
+ overflow: hidden;
116
+ white-space: nowrap;
117
+ text-overflow: ellipsis;
118
+ }
119
+ `
120
+
121
+ const IconButton = styled(Button)`
122
+ position: absolute;
123
+ height: 32px;
124
+ width: 32px;
125
+ right: 4px;
126
+ `
@@ -0,0 +1,53 @@
1
+ import { TypographyCustom } from '@equinor/apollo-components'
2
+ import { TextField } from '@equinor/eds-core-react'
3
+ import { CellContext } from '@tanstack/react-table'
4
+ import { Controller, FieldPath, useFormContext } from 'react-hook-form'
5
+ import styled from 'styled-components'
6
+ import { FormMeta, useEditMode } from '../form-meta'
7
+ import { getHelperTextProps } from './utils'
8
+
9
+ export function EditableTextFieldCell<T extends FormMeta>(context: CellContext<T, unknown>) {
10
+ const { control } = useFormContext<T>()
11
+ const editMode = useEditMode()
12
+
13
+ if (!editMode) return <TypographyCustom truncate>{context.getValue() as any}</TypographyCustom>
14
+
15
+ return (
16
+ <Controller
17
+ control={control}
18
+ name={context.column.id as FieldPath<T>}
19
+ render={({ field: { value, ...field }, fieldState: { error } }) => (
20
+ <InlineTextField
21
+ id={context.column.id}
22
+ autoComplete="none"
23
+ value={String(value)}
24
+ {...field}
25
+ {...getHelperTextProps({ error })}
26
+ />
27
+ )}
28
+ />
29
+ )
30
+ }
31
+
32
+ const InlineTextField = styled(TextField)`
33
+ /*
34
+ TODO: Improve input based on feedback from UX
35
+ & > div {
36
+ background: transparent;
37
+ margin: 0 -0.5rem;
38
+ padding: 0 0.5rem;
39
+ box-shadow: none;
40
+ width: auto;
41
+ }
42
+
43
+ input {
44
+ padding: 0;
45
+ letter-spacing: 0;
46
+ font-weight: 400;
47
+ color: inherit;
48
+
49
+ ::placeholder {
50
+ color: red;
51
+ }
52
+ } */
53
+ `
@@ -0,0 +1,6 @@
1
+ export * from './EditableCheckboxCell'
2
+ export * from './EditableDateCell'
3
+ export * from './EditableDropdownCell'
4
+ export * from './EditableNumberCell'
5
+ export * from './EditableTextAreaCell'
6
+ export * from './EditableTextFieldCell'
@@ -0,0 +1,40 @@
1
+ import { Icon } from '@equinor/eds-core-react'
2
+ import { Variants } from '@equinor/eds-core-react/dist/types/components/types'
3
+ import { error_filled, warning_filled } from '@equinor/eds-icons'
4
+
5
+ interface GetHelperTextPropsProps {
6
+ error?: { message?: string }
7
+ warning?: { message: string }
8
+ helperText?: string
9
+ }
10
+
11
+ interface GetHelperTextProps {
12
+ variant?: Variants
13
+ helperText?: string
14
+ helperIcon: JSX.Element | null
15
+ }
16
+
17
+ export function getHelperTextProps({
18
+ error,
19
+ warning,
20
+ helperText,
21
+ }: GetHelperTextPropsProps): GetHelperTextProps {
22
+ if (error)
23
+ return {
24
+ variant: 'error',
25
+ helperText: error.message,
26
+ helperIcon: <Icon data={error_filled} size={16} />,
27
+ }
28
+
29
+ if (warning)
30
+ return {
31
+ variant: 'warning',
32
+ helperText: warning.message,
33
+ helperIcon: <Icon data={warning_filled} size={16} />,
34
+ }
35
+
36
+ return {
37
+ helperText,
38
+ helperIcon: null,
39
+ }
40
+ }
@@ -0,0 +1,2 @@
1
+ export * from './types'
2
+ export * from './utils'
@@ -0,0 +1,7 @@
1
+ export type FormMeta = {
2
+ _isNew?: boolean;
3
+ _editMode?: boolean;
4
+ _hasRemoteChange?: boolean;
5
+ };
6
+
7
+ export type WithoutFormMeta<T extends FormMeta> = Omit<T, keyof FormMeta>;
@@ -0,0 +1,66 @@
1
+ import { omit } from 'lodash'
2
+ import { useFormContext } from 'react-hook-form'
3
+ import { FormMeta } from './types'
4
+
5
+ // This file contains methods related to the FormMeta type
6
+
7
+ const formMeta: FormMeta = {
8
+ _editMode: false,
9
+ _isNew: false,
10
+ _hasRemoteChange: false,
11
+ }
12
+
13
+ /**
14
+ * Subscribes to the `_editMode` field in a `react-hook-form` context.
15
+ *
16
+ * @returns edit mode value
17
+ */
18
+ export function useEditMode() {
19
+ const { watch } = useFormContext<FormMeta>()
20
+ return watch('_editMode') ?? false
21
+ }
22
+
23
+ /**
24
+ * Subscribes to the `_hasRemoteChange` field in a `react-hook-form` context.
25
+ *
26
+ * @returns edit mode value
27
+ */
28
+ export function useHasRemoteChange() {
29
+ const { watch } = useFormContext<FormMeta>()
30
+ return watch('_hasRemoteChange') ?? false
31
+ }
32
+
33
+ /**
34
+ * @returns function getting is new meta
35
+ */
36
+ export function useGetIsNew() {
37
+ const { getValues } = useFormContext<FormMeta>()
38
+ return () => getValues('_isNew') ?? false
39
+ }
40
+
41
+ export function useSetFormMeta<T extends FormMeta>() {
42
+ const { setValue } = useFormContext<T>()
43
+ return (newValues: Partial<T>) =>
44
+ objectKeys(newValues).forEach((key) => newValues[key] && setValue(key, newValues[key]))
45
+ }
46
+
47
+ /**
48
+ * ```
49
+ * Object.keys()
50
+ * ```
51
+ * With better typing. Same uses.
52
+ *
53
+ * @param obj
54
+ * @returns `Object.keys(obj)`
55
+ */
56
+ function objectKeys<Obj>(obj: any): (keyof Obj)[] {
57
+ return Object.keys(obj) as (keyof Obj)[]
58
+ }
59
+
60
+ export function removeFormMeta<T extends FormMeta>(withFormMeta: T): Omit<T, keyof FormMeta> {
61
+ return omit(withFormMeta, Object.keys(formMeta)) as Omit<T, keyof FormMeta>
62
+ }
63
+
64
+ export function addFormMeta<T>(withoutFormMeta: T): T & FormMeta {
65
+ return { ...formMeta, ...withoutFormMeta }
66
+ }
package/src/index.ts CHANGED
@@ -1,2 +1,4 @@
1
+ export * from './edit-cells'
2
+ export * from './form-meta'
1
3
  export * from './jotai-form'
2
4
  export * from './zod-validation'
@@ -1,14 +0,0 @@
1
- $ tsup src/index.ts --format esm,cjs --dts --external react
2
- CLI Building entry: src/index.ts
3
- CLI Using tsconfig: tsconfig.json
4
- CLI tsup v6.6.3
5
- CLI Target: es6
6
- ESM Build start
7
- CJS Build start
8
- ESM dist/index.mjs 3.48 KB
9
- ESM ⚡️ Build success in 85ms
10
- CJS dist/index.js 4.62 KB
11
- CJS ⚡️ Build success in 86ms
12
- DTS Build start
13
- DTS ⚡️ Build success in 4462ms
14
- DTS dist/index.d.ts 2.00 KB
package/dist/index.d.ts DELETED
@@ -1,46 +0,0 @@
1
- import { PrimitiveAtom } from 'jotai';
2
- import { AtomFamily } from 'jotai/vanilla/utils/atomFamily';
3
- import { z } from 'zod';
4
-
5
- type ValidationErrorMap<T> = Map<keyof T, {
6
- message: string;
7
- code: string;
8
- }>;
9
-
10
- declare function createValidator<S extends z.ZodTypeAny>(schema: S): {
11
- validate: <E extends z.TypeOf<S>>(entity: E) => ValidationErrorMap<E> | undefined;
12
- validateAsync: <E_1 extends z.TypeOf<S>>(entity: z.infer<typeof schema>) => Promise<ValidationErrorMap<E_1> | undefined>;
13
- getSchema(): S;
14
- };
15
-
16
- type FormState<T> = {
17
- status: 'editing' | 'pending';
18
- values: T;
19
- errors?: ValidationErrorMap<T>;
20
- isValid?: boolean;
21
- };
22
- type FormFamilyParam = {
23
- id: number | string;
24
- } & Record<string, unknown>;
25
-
26
- declare function createFormFamily<E extends Record<string, unknown>>(): AtomFamily<FormFamilyParam, PrimitiveAtom<FormState<E> | undefined> & {
27
- init: FormState<E> | undefined;
28
- }>;
29
- type FormFamily<T> = AtomFamily<FormFamilyParam, PrimitiveAtom<FormState<T> | undefined>>;
30
- type FormValidator = ReturnType<typeof createValidator>;
31
- type UseFormFamilyUtilsOptions = {
32
- validator?: FormValidator;
33
- };
34
- declare function useFormFamilyUtils<T>(family: FormFamily<T>, options?: UseFormFamilyUtilsOptions): {
35
- useFormStateAtom: (param: FormFamilyParam) => [FormState<T> | undefined, (args_0: FormState<T> | ((prev: FormState<T> | undefined) => FormState<T> | undefined) | undefined) => void];
36
- useFormState: (param: FormFamilyParam) => FormState<T> | undefined;
37
- useUpdateFormMutation: (param: FormFamilyParam) => (update: Partial<T>) => void;
38
- useFormMutations(param: FormFamilyParam): {
39
- update: (update: Partial<T>) => void;
40
- initializeForm: (entity: T) => void;
41
- resetForm: () => void;
42
- };
43
- };
44
- declare function useFormFamilyMutation<T>(family: FormFamily<T>, param: FormFamilyParam, validator?: FormValidator): (update: Partial<T>) => void;
45
-
46
- export { FormFamilyParam, FormState, ValidationErrorMap, createFormFamily, createValidator, useFormFamilyMutation, useFormFamilyUtils };
package/dist/index.js DELETED
@@ -1,147 +0,0 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __getOwnPropSymbols = Object.getOwnPropertySymbols;
6
- var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- var __propIsEnum = Object.prototype.propertyIsEnumerable;
8
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
9
- var __spreadValues = (a, b) => {
10
- for (var prop in b || (b = {}))
11
- if (__hasOwnProp.call(b, prop))
12
- __defNormalProp(a, prop, b[prop]);
13
- if (__getOwnPropSymbols)
14
- for (var prop of __getOwnPropSymbols(b)) {
15
- if (__propIsEnum.call(b, prop))
16
- __defNormalProp(a, prop, b[prop]);
17
- }
18
- return a;
19
- };
20
- var __export = (target, all) => {
21
- for (var name in all)
22
- __defProp(target, name, { get: all[name], enumerable: true });
23
- };
24
- var __copyProps = (to, from, except, desc) => {
25
- if (from && typeof from === "object" || typeof from === "function") {
26
- for (let key of __getOwnPropNames(from))
27
- if (!__hasOwnProp.call(to, key) && key !== except)
28
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
29
- }
30
- return to;
31
- };
32
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
33
- var __async = (__this, __arguments, generator) => {
34
- return new Promise((resolve, reject) => {
35
- var fulfilled = (value) => {
36
- try {
37
- step(generator.next(value));
38
- } catch (e) {
39
- reject(e);
40
- }
41
- };
42
- var rejected = (value) => {
43
- try {
44
- step(generator.throw(value));
45
- } catch (e) {
46
- reject(e);
47
- }
48
- };
49
- var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
50
- step((generator = generator.apply(__this, __arguments)).next());
51
- });
52
- };
53
-
54
- // src/index.ts
55
- var src_exports = {};
56
- __export(src_exports, {
57
- createFormFamily: () => createFormFamily,
58
- createValidator: () => createValidator,
59
- useFormFamilyMutation: () => useFormFamilyMutation,
60
- useFormFamilyUtils: () => useFormFamilyUtils
61
- });
62
- module.exports = __toCommonJS(src_exports);
63
-
64
- // src/jotai-form/formUtils.ts
65
- var import_jotai = require("jotai");
66
- var import_utils = require("jotai/utils");
67
- function createFormFamily() {
68
- return (0, import_utils.atomFamily)(
69
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
70
- (_param) => (0, import_jotai.atom)(void 0),
71
- (a, b) => a.id === b.id
72
- );
73
- }
74
- function useFormFamilyUtils(family, options = {}) {
75
- return {
76
- useFormStateAtom: (param) => (0, import_jotai.useAtom)(family(param)),
77
- useFormState: (param) => (0, import_jotai.useAtomValue)(family(param)),
78
- useUpdateFormMutation: (param) => {
79
- return useFormFamilyMutation(family, param, options.validator);
80
- },
81
- useFormMutations(param) {
82
- const mutateAtom = (0, import_jotai.useSetAtom)(family(param));
83
- return {
84
- update: useFormFamilyMutation(family, param, options.validator),
85
- initializeForm: (entity) => mutateAtom({
86
- status: "editing",
87
- values: entity,
88
- isValid: true
89
- }),
90
- resetForm: () => mutateAtom(void 0)
91
- };
92
- }
93
- };
94
- }
95
- function useFormFamilyMutation(family, param, validator) {
96
- const mutate = (0, import_jotai.useSetAtom)(family(param));
97
- return (update) => {
98
- return mutate((previous) => {
99
- if (!previous)
100
- return;
101
- const updatedValues = __spreadValues(__spreadValues({}, previous.values), update);
102
- const errors = validator == null ? void 0 : validator.validate(updatedValues);
103
- return {
104
- status: "editing",
105
- values: updatedValues,
106
- errors,
107
- isValid: !errors
108
- };
109
- });
110
- };
111
- }
112
-
113
- // src/zod-validation/utils.ts
114
- function createValidator(schema) {
115
- return {
116
- validate: (entity) => {
117
- const validation = schema.safeParse(entity);
118
- if (validation.success)
119
- return void 0;
120
- return prepareErrors(validation);
121
- },
122
- validateAsync: (entity) => __async(this, null, function* () {
123
- const validation = yield schema.safeParseAsync(entity);
124
- if (validation.success)
125
- return void 0;
126
- return prepareErrors(validation);
127
- }),
128
- getSchema() {
129
- return schema;
130
- }
131
- };
132
- }
133
- function prepareErrors(errorValidation) {
134
- return new Map(
135
- errorValidation.error.errors.map((error) => [
136
- error.path[0],
137
- { message: error.message, code: error.code }
138
- ])
139
- );
140
- }
141
- // Annotate the CommonJS export names for ESM import in node:
142
- 0 && (module.exports = {
143
- createFormFamily,
144
- createValidator,
145
- useFormFamilyMutation,
146
- useFormFamilyUtils
147
- });
package/dist/index.mjs DELETED
@@ -1,120 +0,0 @@
1
- var __defProp = Object.defineProperty;
2
- var __getOwnPropSymbols = Object.getOwnPropertySymbols;
3
- var __hasOwnProp = Object.prototype.hasOwnProperty;
4
- var __propIsEnum = Object.prototype.propertyIsEnumerable;
5
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
6
- var __spreadValues = (a, b) => {
7
- for (var prop in b || (b = {}))
8
- if (__hasOwnProp.call(b, prop))
9
- __defNormalProp(a, prop, b[prop]);
10
- if (__getOwnPropSymbols)
11
- for (var prop of __getOwnPropSymbols(b)) {
12
- if (__propIsEnum.call(b, prop))
13
- __defNormalProp(a, prop, b[prop]);
14
- }
15
- return a;
16
- };
17
- var __async = (__this, __arguments, generator) => {
18
- return new Promise((resolve, reject) => {
19
- var fulfilled = (value) => {
20
- try {
21
- step(generator.next(value));
22
- } catch (e) {
23
- reject(e);
24
- }
25
- };
26
- var rejected = (value) => {
27
- try {
28
- step(generator.throw(value));
29
- } catch (e) {
30
- reject(e);
31
- }
32
- };
33
- var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
34
- step((generator = generator.apply(__this, __arguments)).next());
35
- });
36
- };
37
-
38
- // src/jotai-form/formUtils.ts
39
- import { atom, useAtom, useAtomValue, useSetAtom } from "jotai";
40
- import { atomFamily } from "jotai/utils";
41
- function createFormFamily() {
42
- return atomFamily(
43
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
44
- (_param) => atom(void 0),
45
- (a, b) => a.id === b.id
46
- );
47
- }
48
- function useFormFamilyUtils(family, options = {}) {
49
- return {
50
- useFormStateAtom: (param) => useAtom(family(param)),
51
- useFormState: (param) => useAtomValue(family(param)),
52
- useUpdateFormMutation: (param) => {
53
- return useFormFamilyMutation(family, param, options.validator);
54
- },
55
- useFormMutations(param) {
56
- const mutateAtom = useSetAtom(family(param));
57
- return {
58
- update: useFormFamilyMutation(family, param, options.validator),
59
- initializeForm: (entity) => mutateAtom({
60
- status: "editing",
61
- values: entity,
62
- isValid: true
63
- }),
64
- resetForm: () => mutateAtom(void 0)
65
- };
66
- }
67
- };
68
- }
69
- function useFormFamilyMutation(family, param, validator) {
70
- const mutate = useSetAtom(family(param));
71
- return (update) => {
72
- return mutate((previous) => {
73
- if (!previous)
74
- return;
75
- const updatedValues = __spreadValues(__spreadValues({}, previous.values), update);
76
- const errors = validator == null ? void 0 : validator.validate(updatedValues);
77
- return {
78
- status: "editing",
79
- values: updatedValues,
80
- errors,
81
- isValid: !errors
82
- };
83
- });
84
- };
85
- }
86
-
87
- // src/zod-validation/utils.ts
88
- function createValidator(schema) {
89
- return {
90
- validate: (entity) => {
91
- const validation = schema.safeParse(entity);
92
- if (validation.success)
93
- return void 0;
94
- return prepareErrors(validation);
95
- },
96
- validateAsync: (entity) => __async(this, null, function* () {
97
- const validation = yield schema.safeParseAsync(entity);
98
- if (validation.success)
99
- return void 0;
100
- return prepareErrors(validation);
101
- }),
102
- getSchema() {
103
- return schema;
104
- }
105
- };
106
- }
107
- function prepareErrors(errorValidation) {
108
- return new Map(
109
- errorValidation.error.errors.map((error) => [
110
- error.path[0],
111
- { message: error.message, code: error.code }
112
- ])
113
- );
114
- }
115
- export {
116
- createFormFamily,
117
- createValidator,
118
- useFormFamilyMutation,
119
- useFormFamilyUtils
120
- };