@servicetitan/anvil2-ext-common 0.3.20 → 0.5.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.
Files changed (48) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +27 -0
  3. package/dist/Suggestions-ChGGcGuO.js +47 -0
  4. package/dist/Suggestions-ChGGcGuO.js.map +1 -0
  5. package/dist/Suggestions.d.ts +1 -0
  6. package/dist/Suggestions.js +2 -0
  7. package/dist/Suggestions.js.map +1 -0
  8. package/dist/index.css +2915 -0
  9. package/dist/index.js +35857 -1
  10. package/dist/index.js.map +1 -1
  11. package/dist/src/components/Suggestions/Suggestions.d.ts +59 -0
  12. package/dist/src/components/Suggestions/index.d.ts +2 -0
  13. package/dist/src/components/index.d.ts +1 -0
  14. package/dist/src/form/components/Form.d.ts +15 -0
  15. package/dist/src/form/components/FormCheckboxGroup/FormCheckboxGroup.d.ts +13 -0
  16. package/dist/src/form/components/FormCheckboxGroup/index.d.ts +1 -0
  17. package/dist/src/form/components/FormDateFieldRange/FormDateFieldRange.d.ts +6 -0
  18. package/dist/src/form/components/FormDateFieldRange/index.d.ts +1 -0
  19. package/dist/src/form/components/FormDateFieldSingle/FormDateFieldSingle.d.ts +6 -0
  20. package/dist/src/form/components/FormDateFieldSingle/index.d.ts +1 -0
  21. package/dist/src/form/components/FormDateFieldYearless/FormDateFieldYearless.d.ts +6 -0
  22. package/dist/src/form/components/FormDateFieldYearless/index.d.ts +1 -0
  23. package/dist/src/form/components/FormDateFieldYearlessRange/FormDateFieldYearlessRange.d.ts +6 -0
  24. package/dist/src/form/components/FormDateFieldYearlessRange/index.d.ts +1 -0
  25. package/dist/src/form/components/FormMultiSelect/FormMultiSelect.d.ts +12 -0
  26. package/dist/src/form/components/FormMultiSelect/index.d.ts +1 -0
  27. package/dist/src/form/components/FormNumberField/FormNumberField.d.ts +6 -0
  28. package/dist/src/form/components/FormNumberField/index.d.ts +1 -0
  29. package/dist/src/form/components/FormRadioGroup/FormRadioGroup.d.ts +13 -0
  30. package/dist/src/form/components/FormRadioGroup/index.d.ts +1 -0
  31. package/dist/src/form/components/FormSelect/FormSelect.d.ts +11 -0
  32. package/dist/src/form/components/FormSelect/index.d.ts +1 -0
  33. package/dist/src/form/components/FormTextField/FormTextField.d.ts +6 -0
  34. package/dist/src/form/components/FormTextField/index.d.ts +1 -0
  35. package/dist/src/form/components/FormTextarea/FormTextarea.d.ts +6 -0
  36. package/dist/src/form/components/FormTextarea/index.d.ts +1 -0
  37. package/dist/src/form/components/FormTimeField/FormTimeField.d.ts +6 -0
  38. package/dist/src/form/components/FormTimeField/index.d.ts +1 -0
  39. package/dist/src/form/components/FormTreeSelect/FormTreeSelect.d.ts +12 -0
  40. package/dist/src/form/components/FormTreeSelect/index.d.ts +1 -0
  41. package/dist/src/form/components/index.d.ts +13 -0
  42. package/dist/src/form/index.d.ts +3 -0
  43. package/dist/src/form/state/A2InputFieldState.d.ts +54 -0
  44. package/dist/src/form/state/SelectableOptionsFieldState.d.ts +20 -0
  45. package/dist/src/form/state/index.d.ts +3 -0
  46. package/dist/src/form/state/types.d.ts +31 -0
  47. package/dist/src/index.d.ts +1 -0
  48. package/package.json +8 -2
@@ -0,0 +1,59 @@
1
+ import { ReactNode } from 'react';
2
+ type Handler = (...args: any[]) => void;
3
+ type Handlers = Record<string, Handler | undefined>;
4
+ export interface SuggestionsApi {
5
+ /**
6
+ * Props for a field that should be highlighted while it holds a suggested
7
+ * value. Returns `isHighlighted` plus copies of any handlers you pass, each
8
+ * wrapped to clear the highlight on the first user edit. Pass the handler
9
+ * under the field's own prop name (`onChange`, `onSelectedOptionChange`, ...).
10
+ *
11
+ * @example <TextField {...suggestable("city", { onChange: setCity })} />
12
+ */
13
+ suggestable: <O extends Handlers>(key: string, handlers?: O) => {
14
+ isHighlighted: boolean;
15
+ } & O;
16
+ /**
17
+ * Props for the field whose selection populates the others. Returns copies of
18
+ * the handlers you pass, each wrapped to first mark the suggestable fields as
19
+ * highlighted. Your handler still does the value population.
20
+ *
21
+ * @param keys which fields to highlight; defaults to every registered key.
22
+ * @example <TypeaheadTextField {...suggestor({ onSelectOption: apply })} />
23
+ */
24
+ suggestor: <O extends Handlers>(handlers?: O, keys?: string[]) => O;
25
+ /** Imperatively highlight fields (defaults to every registered key). */
26
+ markSuggested: (keys?: string[]) => void;
27
+ /** Imperatively clear a field's highlight. */
28
+ clearHighlight: (key: string) => void;
29
+ /** Whether a field is currently holding a suggested value. */
30
+ isHighlighted: (key: string) => boolean;
31
+ }
32
+ export interface SuggestionsProps {
33
+ children: (api: SuggestionsApi) => ReactNode;
34
+ }
35
+ /**
36
+ * Coordinates the "highlight fields populated by a suggestion until edited"
37
+ * behavior for a group of fields, without per-field state wiring.
38
+ *
39
+ * Relies on the fact that a controlled field's change handler fires only on
40
+ * user input, never when its `value` prop changes programmatically — so we
41
+ * track provenance per field rather than comparing values. A field stays
42
+ * un-highlighted after an edit even if the suggested value is re-entered,
43
+ * because typing still fires the handler.
44
+ *
45
+ * @example
46
+ * <Suggestions>
47
+ * {({ suggestor, suggestable }) => (
48
+ * <>
49
+ * <TypeaheadTextField
50
+ * {...suggestor({ onSelectOption: (opt) => applyAddress(opt.value) })}
51
+ * loadOptions={lookupAddresses}
52
+ * />
53
+ * <TextField {...suggestable("city", { onChange: handleCity })} />
54
+ * </>
55
+ * )}
56
+ * </Suggestions>
57
+ */
58
+ export declare function Suggestions({ children }: SuggestionsProps): import("react/jsx-runtime").JSX.Element;
59
+ export {};
@@ -0,0 +1,2 @@
1
+ export * from './Suggestions';
2
+ export { Suggestions as default } from './Suggestions';
@@ -1 +1,2 @@
1
1
  export * from './ExampleComponent';
2
+ export * from './Suggestions';
@@ -0,0 +1,15 @@
1
+ export declare const Form: {
2
+ TextField: ({ fieldState, disabled, ...props }: import('./FormTextField').FormTextFieldProps) => import("react/jsx-runtime").JSX.Element;
3
+ Textarea: ({ fieldState, disabled, ...props }: import('./FormTextarea').FormTextareaProps) => import("react/jsx-runtime").JSX.Element;
4
+ NumberField: ({ fieldState, disabled, ...props }: import('./FormNumberField').FormNumberFieldProps) => import("react/jsx-runtime").JSX.Element;
5
+ DateFieldSingle: ({ fieldState, disabled, ...props }: import('./FormDateFieldSingle').FormDateFieldSingleProps) => import("react/jsx-runtime").JSX.Element;
6
+ DateFieldRange: ({ fieldState, disabled, ...props }: import('./FormDateFieldRange').FormDateFieldRangeProps) => import("react/jsx-runtime").JSX.Element;
7
+ DateFieldYearless: ({ fieldState, disabled, ...props }: import('./FormDateFieldYearless').FormDateFieldYearlessProps) => import("react/jsx-runtime").JSX.Element;
8
+ DateFieldYearlessRange: ({ fieldState, disabled, ...props }: import('./FormDateFieldYearlessRange').FormDateFieldYearlessRangeProps) => import("react/jsx-runtime").JSX.Element;
9
+ TimeField: ({ fieldState, disabled, ...props }: import('./FormTimeField').FormTimeFieldProps) => import("react/jsx-runtime").JSX.Element;
10
+ Select: ({ fieldState, disabled, ...props }: import('./FormSelect').FormSelectProps) => import("react/jsx-runtime").JSX.Element;
11
+ MultiSelect: ({ fieldState, disabled, ...props }: import('./FormMultiSelect').FormMultiSelectProps) => import("react/jsx-runtime").JSX.Element;
12
+ TreeSelect: ({ fieldState, disabled, ...props }: import('./FormTreeSelect').FormTreeSelectProps) => import("react/jsx-runtime").JSX.Element;
13
+ RadioGroup: <T, G extends import('./FormRadioGroup').FormRadioGroupItem<T>>({ fieldState, items, disabled, ...props }: import('./FormRadioGroup').FormRadioGroupProps<T, G>) => import("react/jsx-runtime").JSX.Element;
14
+ CheckboxGroup: <T, G extends import('./FormCheckboxGroup').FormCheckboxGroupItem<T>>({ fieldState, items, disabled, ...props }: import('./FormCheckboxGroup').FormCheckboxGroupProps<T, G>) => import("react/jsx-runtime").JSX.Element;
15
+ };
@@ -0,0 +1,13 @@
1
+ import { ReactNode } from 'react';
2
+ import { CheckboxGroupProps } from '@servicetitan/anvil2';
3
+ import { SelectableOptionsArrayFieldState, OptionType } from '../../state';
4
+ export interface FormCheckboxGroupItem<T> extends OptionType<T> {
5
+ label: ReactNode;
6
+ }
7
+ export interface FormCheckboxGroupProps<T, G extends FormCheckboxGroupItem<T>> extends Omit<CheckboxGroupProps, "error" | "warning" | "children"> {
8
+ fieldState: SelectableOptionsArrayFieldState<T, G>;
9
+ items: G[];
10
+ }
11
+ declare function FormCheckboxGroupInner<T, G extends FormCheckboxGroupItem<T>>({ fieldState, items, disabled, ...props }: FormCheckboxGroupProps<T, G>): import("react/jsx-runtime").JSX.Element;
12
+ export declare const FormCheckboxGroup: typeof FormCheckboxGroupInner;
13
+ export {};
@@ -0,0 +1 @@
1
+ export * from './FormCheckboxGroup';
@@ -0,0 +1,6 @@
1
+ import { DateFieldRangeProps, DateFieldRangeValue } from '@servicetitan/anvil2';
2
+ import { A2InputFieldState } from '../../state';
3
+ export interface FormDateFieldRangeProps extends Omit<DateFieldRangeProps, "value" | "onChange" | "error" | "warning"> {
4
+ fieldState: A2InputFieldState<DateFieldRangeValue | null>;
5
+ }
6
+ export declare const FormDateFieldRange: ({ fieldState, disabled, ...props }: FormDateFieldRangeProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1 @@
1
+ export * from './FormDateFieldRange';
@@ -0,0 +1,6 @@
1
+ import { DateFieldSingleProps } from '@servicetitan/anvil2';
2
+ import { A2InputFieldState } from '../../state';
3
+ export interface FormDateFieldSingleProps extends Omit<DateFieldSingleProps, "value" | "onChange" | "error" | "warning"> {
4
+ fieldState: A2InputFieldState<string | null>;
5
+ }
6
+ export declare const FormDateFieldSingle: ({ fieldState, disabled, ...props }: FormDateFieldSingleProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1 @@
1
+ export * from './FormDateFieldSingle';
@@ -0,0 +1,6 @@
1
+ import { DateFieldYearlessProps, YearlessDate } from '@servicetitan/anvil2';
2
+ import { A2InputFieldState } from '../../state';
3
+ export interface FormDateFieldYearlessProps extends Omit<DateFieldYearlessProps, "value" | "onChange" | "error" | "warning"> {
4
+ fieldState: A2InputFieldState<YearlessDate | null>;
5
+ }
6
+ export declare const FormDateFieldYearless: ({ fieldState, disabled, ...props }: FormDateFieldYearlessProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1 @@
1
+ export * from './FormDateFieldYearless';
@@ -0,0 +1,6 @@
1
+ import { DateFieldYearlessRangeProps, DateFieldYearlessRangeValue } from '@servicetitan/anvil2';
2
+ import { A2InputFieldState } from '../../state';
3
+ export interface FormDateFieldYearlessRangeProps extends Omit<DateFieldYearlessRangeProps, "value" | "onChange" | "error" | "warning"> {
4
+ fieldState: A2InputFieldState<DateFieldYearlessRangeValue>;
5
+ }
6
+ export declare const FormDateFieldYearlessRange: ({ fieldState, disabled, ...props }: FormDateFieldYearlessRangeProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1 @@
1
+ export * from './FormDateFieldYearlessRange';
@@ -0,0 +1,12 @@
1
+ import { MultiSelectFieldOption, MultiSelectFieldProps } from '@servicetitan/anvil2/beta';
2
+ import { A2InputFieldState, DistributiveOmit } from '../../state';
3
+ /**
4
+ * FormMultiSelect wraps the beta MultiSelectField and binds it to an
5
+ * A2InputFieldState. Supports all loader modes (eager and all lazy variants)
6
+ * because the props type uses DistributiveOmit to preserve each union member's
7
+ * loader type.
8
+ */
9
+ export type FormMultiSelectProps = DistributiveOmit<MultiSelectFieldProps, "value" | "onSelectedOptionsChange" | "error" | "warning"> & {
10
+ fieldState: A2InputFieldState<MultiSelectFieldOption[]>;
11
+ };
12
+ export declare const FormMultiSelect: ({ fieldState, disabled, ...props }: FormMultiSelectProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1 @@
1
+ export * from './FormMultiSelect';
@@ -0,0 +1,6 @@
1
+ import { NumberFieldProps } from '@servicetitan/anvil2';
2
+ import { A2InputFieldState } from '../../state';
3
+ export interface FormNumberFieldProps extends Omit<NumberFieldProps, "value" | "onChange" | "error" | "warning"> {
4
+ fieldState: A2InputFieldState<number | null>;
5
+ }
6
+ export declare const FormNumberField: ({ fieldState, disabled, ...props }: FormNumberFieldProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1 @@
1
+ export * from './FormNumberField';
@@ -0,0 +1,13 @@
1
+ import { ReactNode } from 'react';
2
+ import { RadioGroupProps } from '@servicetitan/anvil2';
3
+ import { SelectableOptionsFieldState, OptionType } from '../../state';
4
+ export interface FormRadioGroupItem<T> extends OptionType<T> {
5
+ label: ReactNode;
6
+ }
7
+ export interface FormRadioGroupProps<T, G extends FormRadioGroupItem<T>> extends Omit<RadioGroupProps, "error" | "warning" | "children"> {
8
+ fieldState: SelectableOptionsFieldState<T, G>;
9
+ items: G[];
10
+ }
11
+ declare function FormRadioGroupInner<T, G extends FormRadioGroupItem<T>>({ fieldState, items, disabled, ...props }: FormRadioGroupProps<T, G>): import("react/jsx-runtime").JSX.Element;
12
+ export declare const FormRadioGroup: typeof FormRadioGroupInner;
13
+ export {};
@@ -0,0 +1 @@
1
+ export * from './FormRadioGroup';
@@ -0,0 +1,11 @@
1
+ import { SelectFieldOption, SelectFieldProps } from '@servicetitan/anvil2/beta';
2
+ import { A2InputFieldState, DistributiveOmit } from '../../state';
3
+ /**
4
+ * FormSelect wraps the beta SelectField and binds it to an A2InputFieldState.
5
+ * Supports all loader modes (eager and all lazy variants) because the props
6
+ * type uses DistributiveOmit to preserve each union member's loader type.
7
+ */
8
+ export type FormSelectProps = DistributiveOmit<SelectFieldProps, "value" | "onSelectedOptionChange" | "error" | "warning"> & {
9
+ fieldState: A2InputFieldState<SelectFieldOption | null>;
10
+ };
11
+ export declare const FormSelect: ({ fieldState, disabled, ...props }: FormSelectProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1 @@
1
+ export * from './FormSelect';
@@ -0,0 +1,6 @@
1
+ import { TextFieldProps } from '@servicetitan/anvil2';
2
+ import { A2InputFieldState } from '../../state';
3
+ export interface FormTextFieldProps extends Omit<TextFieldProps, "value" | "onChange" | "error" | "warning"> {
4
+ fieldState: A2InputFieldState<string>;
5
+ }
6
+ export declare const FormTextField: ({ fieldState, disabled, ...props }: FormTextFieldProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1 @@
1
+ export * from './FormTextField';
@@ -0,0 +1,6 @@
1
+ import { TextareaProps } from '@servicetitan/anvil2';
2
+ import { A2InputFieldState } from '../../state';
3
+ export interface FormTextareaProps extends Omit<TextareaProps, "value" | "onChange" | "error" | "warning"> {
4
+ fieldState: A2InputFieldState<string>;
5
+ }
6
+ export declare const FormTextarea: ({ fieldState, disabled, ...props }: FormTextareaProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1 @@
1
+ export * from './FormTextarea';
@@ -0,0 +1,6 @@
1
+ import { TimeFieldProps } from '@servicetitan/anvil2';
2
+ import { A2InputFieldState } from '../../state';
3
+ export interface FormTimeFieldProps extends Omit<TimeFieldProps, "value" | "onChange" | "error" | "warning"> {
4
+ fieldState: A2InputFieldState<string | null>;
5
+ }
6
+ export declare const FormTimeField: ({ fieldState, disabled, ...props }: FormTimeFieldProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1 @@
1
+ export * from './FormTimeField';
@@ -0,0 +1,12 @@
1
+ import { TreeSelectFieldValue, TreeSelectFieldProps } from '@servicetitan/anvil2/beta';
2
+ import { A2InputFieldState } from '../../state';
3
+ /**
4
+ * FormTreeSelect wraps the beta TreeSelectField and binds it to an
5
+ * A2InputFieldState. TreeSelectFieldProps is a flat (non-union) type so we can
6
+ * use Omit directly. The loadOptions signature is:
7
+ * (searchValue: string, parentNode?: TreeSelectFieldNode) => TreeSelectFieldNode[] | Promise<TreeSelectFieldNode[]>
8
+ */
9
+ export interface FormTreeSelectProps extends Omit<TreeSelectFieldProps, "value" | "onSelectedOptionsChange" | "error" | "warning"> {
10
+ fieldState: A2InputFieldState<TreeSelectFieldValue[]>;
11
+ }
12
+ export declare const FormTreeSelect: ({ fieldState, disabled, ...props }: FormTreeSelectProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1 @@
1
+ export * from './FormTreeSelect';
@@ -0,0 +1,13 @@
1
+ export * from './FormTextField';
2
+ export * from './FormTextarea';
3
+ export * from './FormNumberField';
4
+ export * from './FormDateFieldSingle';
5
+ export * from './FormDateFieldRange';
6
+ export * from './FormDateFieldYearless';
7
+ export * from './FormDateFieldYearlessRange';
8
+ export * from './FormTimeField';
9
+ export * from './FormSelect';
10
+ export * from './FormMultiSelect';
11
+ export * from './FormTreeSelect';
12
+ export * from './FormRadioGroup';
13
+ export * from './FormCheckboxGroup';
@@ -0,0 +1,3 @@
1
+ export * from './state';
2
+ export * from './components';
3
+ export * from './components/Form';
@@ -0,0 +1,54 @@
1
+ import { FieldState } from 'formstate';
2
+ import { ChangeEvent, SyntheticEvent } from 'react';
3
+ import { UpdateMode, Validator, WarningValidator } from './types';
4
+ /**
5
+ * Base field-state for Anvil2 form fields. Extends formstate's `FieldState` with
6
+ * non-blocking warnings, dynamic disablers, soft validation, hard-set helpers,
7
+ * an optional update-mode sub-field, and `@servicetitan/form`-compatible change
8
+ * handlers. Ported from the app's `A2InputFieldState` onto formstate directly.
9
+ */
10
+ export declare class A2InputFieldState<T> extends FieldState<T> {
11
+ warning: string | undefined;
12
+ disabled: boolean;
13
+ /** Optional sub-field tracking how the value should be applied. */
14
+ updateMode?: FieldState<UpdateMode>;
15
+ private _warnings;
16
+ private _disablers;
17
+ private additionalValidations;
18
+ private _onReset;
19
+ constructor(initValue: T);
20
+ /** `@servicetitan/form`-compatible handler: `(event, { value })`. */
21
+ onChangeHandler: (_event: SyntheticEvent<HTMLInputElement>, data: {
22
+ value: T;
23
+ }) => void;
24
+ /** `@servicetitan/form`-compatible native handler: reads `event.currentTarget.value`. */
25
+ onChangeNativeHandler: (event: ChangeEvent<HTMLInputElement>) => void;
26
+ onChange(value: T): void;
27
+ validate(): Promise<{
28
+ hasError: true;
29
+ } | {
30
+ hasError: false;
31
+ value: T;
32
+ }>;
33
+ /** Run disablers + warnings without committing a full validation. */
34
+ softValidate(): Promise<void>;
35
+ /** Register a handler that runs after the field is reset. */
36
+ onReset(handler: () => void): this;
37
+ reset(value?: T): void;
38
+ hardSetValue: (value: T) => void;
39
+ hardConfirmValue: () => void;
40
+ seedValue: (value: T) => void;
41
+ /** Add a validator once per key (guards against double registration). */
42
+ addValidator(validationKey: string, validator: Validator<T>): void;
43
+ disablers(...validators: WarningValidator<T>[]): void;
44
+ checkDisablers: () => void;
45
+ warnings(...validators: WarningValidator<T>[]): void;
46
+ checkWarnings: () => void;
47
+ enableUpdateMode(initUpdateMode?: UpdateMode): void;
48
+ disableUpdateMode(): void;
49
+ hasValueChanged: () => boolean;
50
+ hasUpdateModeValueChanged: () => boolean;
51
+ /** The field's initial value (public so consumers can compare against it). */
52
+ get initValue(): T;
53
+ private getInitValue;
54
+ }
@@ -0,0 +1,20 @@
1
+ import { A2InputFieldState } from './A2InputFieldState';
2
+ import { OptionType, OptionValidationRule } from './types';
3
+ export type SelectableOptionsConfig<T, G extends OptionType<T>> = {
4
+ validationRules?: OptionValidationRule<T, G>[];
5
+ };
6
+ export declare class SelectableOptionsFieldState<T, G extends OptionType<T>> extends A2InputFieldState<T> {
7
+ options: G[];
8
+ private validationTrackers;
9
+ private config?;
10
+ constructor(initValue: T, config?: SelectableOptionsConfig<T, G>);
11
+ setOptions(newOptions: G[]): void;
12
+ }
13
+ export declare class SelectableOptionsArrayFieldState<T, G extends OptionType<T>> extends A2InputFieldState<T[]> {
14
+ options: G[];
15
+ private validationTrackers;
16
+ private config?;
17
+ constructor(initValue: T[], config?: SelectableOptionsConfig<T, G>);
18
+ setOptions(newOptions: G[]): void;
19
+ hasValueChanged: () => boolean;
20
+ }
@@ -0,0 +1,3 @@
1
+ export * from './types';
2
+ export * from './A2InputFieldState';
3
+ export * from './SelectableOptionsFieldState';
@@ -0,0 +1,31 @@
1
+ import { Validator } from 'formstate';
2
+ /**
3
+ * How a value-update control should apply its value. Ported from the app's
4
+ * A2InputFieldState. Primarily useful for value-update controls.
5
+ */
6
+ export declare enum UpdateMode {
7
+ Keep = "Keep",
8
+ Replace = "Replace",
9
+ Append = "Append",
10
+ Remove = "Remove",
11
+ Clear = "Clear"
12
+ }
13
+ /** A validator that returns a message string (truthy) or a falsy value when OK. */
14
+ export type WarningValidator<T> = (value: T) => string | false | null | undefined;
15
+ /** Minimal selectable option shape used by the SelectableOptions field states. */
16
+ export type OptionType<T> = {
17
+ id: T;
18
+ disabled?: boolean;
19
+ };
20
+ /**
21
+ * Rule that flags a tracked option as invalid. When a selected value matches a
22
+ * tracked option, the field reports `errorCode` as its error.
23
+ */
24
+ export type OptionValidationRule<T, G extends OptionType<T>> = {
25
+ /** Return true if this option should be tracked as a validation trigger. */
26
+ shouldTrack: (option: G) => boolean;
27
+ /** The error code/message returned when a tracked value is selected. */
28
+ errorCode: string;
29
+ };
30
+ export type { Validator };
31
+ export type DistributiveOmit<T, K extends keyof never> = T extends unknown ? Omit<T, K> : never;
@@ -2,3 +2,4 @@ export * from './components';
2
2
  export * from './hooks';
3
3
  export * from './providers';
4
4
  export * from './types';
5
+ export * from './form';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@servicetitan/anvil2-ext-common",
3
- "version": "0.3.20",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "types": "./dist/src/index.d.ts",
6
6
  "main": "./dist/index.js",
@@ -31,16 +31,22 @@
31
31
  "tabbable": "^6.2.0",
32
32
  "tinycolor2": "^1.6.0",
33
33
  "uuid": "^14.0.0",
34
- "@servicetitan/anvil2": "3.0.9"
34
+ "@servicetitan/anvil2": "^3.2.0"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "@types/react": "^18 || ^19",
38
38
  "@types/react-dom": "^18 || ^19",
39
+ "formstate": "^2.1.0",
40
+ "mobx": "^6.9.0",
41
+ "mobx-react": "^9.2.0",
39
42
  "react": "^18 || ^19",
40
43
  "react-dom": "^18 || ^19"
41
44
  },
42
45
  "devDependencies": {
43
46
  "@chromatic-com/storybook": "^5.1.2",
47
+ "formstate": "2.1.0",
48
+ "mobx": "^6.9.0",
49
+ "mobx-react": "^9.2.0",
44
50
  "@storybook/addon-a11y": "10.3.5",
45
51
  "@storybook/addon-docs": "10.3.5",
46
52
  "@storybook/addon-links": "10.3.5",