Skip to content

Commit

Permalink
feat: Vue Devtools (#116)
Browse files Browse the repository at this point in the history
* feat: add dev-tools package with initial configuration and dependencies

* feat: implement dev-tools package with core functionalities and types

* refactor: move helper functions to a new helpers.ts file

* feat: enhance dev-tools with field type resolution and improved tagging

* feat: integrate checkbox support in dev-tools with registration

* feat: add radio field support to dev-tools with registration

* fix: update import paths for dev-tools to use wildcard syntax

* refactor: simplify dev-tools registration by removing formId parameter

* chore: update package name

* refactor: simplify the API

* refactor: use the new api calls and add devtools as dep

* fix: eliminate circular dependency

* feat: hook all fields to devtools

* fix: remove standalone tag

* chore: test playground forms with no forms

* fix: ensure fields appear on mount

* fix: added klona and typefest as deps for devtools

* chore: ensure klona is external to devtools

* chore(playground): remove build failures

* chore: match fw version

* chore: added changeset

* fix: only register fields on mounted

* fix: delay registeration logic

* feat: branding and form actions

* feat: support filtering

* feat: render path state

* fix: make sure devtools is a dependency

---------

Co-authored-by: Abdelrahman Awad <[email protected]>
  • Loading branch information
s8n11c and logaretm authored Feb 22, 2025
1 parent b2b5297 commit d34984c
Show file tree
Hide file tree
Showing 36 changed files with 1,151 additions and 238 deletions.
6 changes: 6 additions & 0 deletions .changeset/poor-carpets-fail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@formwerk/devtools': patch
'@formwerk/core': patch
---

feat: initial devtools support
3 changes: 2 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@
"@standard-schema/spec": "1.0.0",
"@standard-schema/utils": "^0.3.0",
"klona": "^2.0.6",
"type-fest": "^4.34.1"
"type-fest": "^4.34.1",
"@formwerk/devtools": "workspace:*"
},
"peerDependencies": {
"vue": ">=3.5.0"
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export * from './usePicker';
export * from './types';
export * from './config';
export * from './useForm';
export * from './useFormField';
export * from './useFormGroup';
export * from './useFormRepeater';
export * from './validation';
Expand Down
8 changes: 8 additions & 0 deletions packages/core/src/useCalendar/useCalendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { exposeField, FormField, useFormField } from '../useFormField';
import { useInputValidity } from '../validation';
import { fromDateToCalendarZonedDateTime, useTemporalStore } from '../useDateTimeField/useTemporalStore';
import { PickerContextKey } from '../usePicker';
import { registerField } from '@formwerk/devtools';

export interface CalendarProps {
/**
Expand Down Expand Up @@ -366,6 +367,13 @@ export function useCalendar(_props: Reactivify<CalendarProps, 'field' | 'schema'
);
});

if (__DEV__) {
// If it is its own field, we should register it with devtools.
if (!props.field) {
registerField(field, 'Calendar');
}
}

return exposeField(
{
/**
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/useCheckbox/useCheckbox.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { computed, inject, nextTick, Ref, ref, toValue } from 'vue';
import { registerField } from '@formwerk/devtools';
import { hasKeyCode, isEqual, isInputElement, normalizeProps, useUniqId, withRefCapture } from '../utils/common';
import {
AriaLabelableProps,
Expand Down Expand Up @@ -274,6 +275,10 @@ export function useCheckbox<TValue = string>(

const isGrouped = !!group;

if (__DEV__) {
registerField(field, 'Checkbox');
}

return exposeField(
{
/**
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/useComboBox/useComboBox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { useErrorMessage } from '../a11y/useErrorMessage';
import { useInputValidity } from '../validation';
import { FilterFn } from '../collections';
import { useControlButtonProps } from '../helpers/useControlButtonProps';
import { registerField } from '@formwerk/devtools';

export interface ComboBoxProps<TOption, TValue = TOption> {
/**
Expand Down Expand Up @@ -369,6 +370,10 @@ export function useComboBox<TOption, TValue = TOption>(
watch(inputValue, debounce(filter.debounceMs, updateHiddenState));
}

if (__DEV__) {
registerField(field, 'ComboBox');
}

return exposeField(
{
/**
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/useCustomField/useCustomField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { exposeField, useFormField } from '../useFormField';
import { createDescribedByProps, normalizeProps, propsToValues, useUniqId, withRefCapture } from '../utils/common';
import { useLabel, useErrorMessage } from '../a11y';
import { useInputValidity } from '../validation';
import { registerField } from '@formwerk/devtools';

export interface CustomFieldProps<TValue = unknown> {
/**
Expand Down Expand Up @@ -97,6 +98,10 @@ export function useCustomField<TValue = unknown>(
),
);

if (__DEV__) {
registerField(field, 'Custom');
}

return exposeField(
{
/**
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/useDateTimeField/useDateTimeField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { fromDateToCalendarZonedDateTime, useTemporalStore } from './useTemporal
import { ZonedDateTime, Calendar } from '@internationalized/date';
import { useInputValidity } from '../validation';
import { createDisabledContext } from '../helpers/createDisabledContext';
import { registerField } from '@formwerk/devtools';

export interface DateTimeFieldProps {
/**
Expand Down Expand Up @@ -182,6 +183,10 @@ export function useDateTimeField(_props: Reactivify<DateTimeFieldProps, 'schema'
);
});

if (__DEV__) {
registerField(field, 'Date');
}

return exposeField(
{
/**
Expand Down
18 changes: 16 additions & 2 deletions packages/core/src/useForm/useForm.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { InjectionKey, MaybeRefOrGetter, onMounted, provide, reactive, readonly, Ref, ref } from 'vue';
import type { StandardSchemaV1 } from '@standard-schema/spec';
import { registerForm } from '@formwerk/devtools';
import { cloneDeep, useUniqId } from '../utils/common';
import {
FormObject,
Expand Down Expand Up @@ -284,8 +285,21 @@ export function useForm<
formProps,
};

return {
const form = {
...baseReturns,
...actions,
} as typeof baseReturns & FormActions<TInput, TOutput>;
};

if (__DEV__) {
// using any until we can figure out how to type this properly
// eslint-disable-next-line @typescript-eslint/no-explicit-any
registerForm(form as any);
}

return form as typeof baseReturns & FormActions<TInput, TOutput>;
}

/**
* Just a utility type helper to get the return type of the useForm composable.
*/
export type FormReturns = ReturnType<typeof useForm>;
3 changes: 2 additions & 1 deletion packages/core/src/useFormField/useFormField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export type FormField<TValue> = {
setTouched: (touched: boolean) => void;
setErrors: (messages: Arrayable<string>) => void;
displayError: () => string | undefined;
form?: FormContext | null;
};

export function useFormField<TValue = unknown>(opts?: Partial<FormFieldOptions<TValue>>): FormField<TValue> {
Expand Down Expand Up @@ -200,7 +201,7 @@ export function useFormField<TValue = unknown>(opts?: Partial<FormFieldOptions<T
form.setFieldDisabled(path, disabled);
});

return field;
return { ...field, form };
}

function useFieldValidity(getPath: Getter<string | undefined>, isDisabled: Ref<boolean>, form?: FormContext | null) {
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/useHiddenField/useHiddenField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Reactivify } from '../types';
import { exposeField, useFormField } from '../useFormField';
import { normalizeProps } from '../utils/common';
import { useInputValidity } from '../validation';
import { registerField } from '@formwerk/devtools';

export interface HiddenFieldProps<TValue = unknown> {
/**
Expand Down Expand Up @@ -41,5 +42,9 @@ export function useHiddenField<TValue = unknown>(_props: Reactivify<HiddenFieldP
},
);

if (__DEV__) {
registerField(field, 'Hidden');
}

return exposeField({}, field);
}
5 changes: 5 additions & 0 deletions packages/core/src/useNumberField/useNumberField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { useLocale } from '../i18n';
import { exposeField, useFormField } from '../useFormField';
import { FieldTypePrefixes } from '../constants';
import { useEventListener } from '../helpers/useEventListener';
import { registerField } from '@formwerk/devtools';

export interface NumberInputDOMAttributes {
name?: string;
Expand Down Expand Up @@ -298,6 +299,10 @@ export function useNumberField(
{ disabled: () => isDisabled.value || toValue(props.disableWheel), passive: true },
);

if (__DEV__) {
registerField(field, 'Number');
}

return exposeField(
{
/**
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/useRadio/useRadioGroup.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { InjectionKey, toValue, computed, onBeforeUnmount, reactive, provide, ref } from 'vue';
import { registerField } from '@formwerk/devtools';
import { useInputValidity } from '../validation/useInputValidity';
import { useLabel, useErrorMessage } from '../a11y';
import {
Expand Down Expand Up @@ -268,6 +269,10 @@ export function useRadioGroup<TValue = string>(_props: Reactivify<RadioGroupProp

provide(RadioGroupKey, context);

if (__DEV__) {
registerField(field, 'Radio');
}

return exposeField(
{
/**
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/useSearchField/useSearchField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { useLabel, useErrorMessage } from '../a11y';
import { useFormField, exposeField } from '../useFormField';
import { FieldTypePrefixes } from '../constants';
import { createDisabledContext } from '../helpers/createDisabledContext';
import { registerField } from '@formwerk/devtools';

export interface SearchInputDOMAttributes extends TextInputBaseAttributes {
type?: 'search';
Expand Down Expand Up @@ -227,6 +228,10 @@ export function useSearchField(
),
);

if (__DEV__) {
registerField(field, 'Search');
}

return exposeField(
{
/**
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/useSelect/useSelect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { useInputValidity } from '../validation';
import { useListBox } from '../useListBox';
import { useLabel, useErrorMessage } from '../a11y';
import { FieldTypePrefixes } from '../constants';
import { registerField } from '@formwerk/devtools';

export interface SelectProps<TOption, TValue = TOption> {
/**
Expand Down Expand Up @@ -273,6 +274,10 @@ export function useSelect<TOption, TValue = TOption>(_props: Reactivify<SelectPr
);
});

if (__DEV__) {
registerField(field, 'Select');
}

return exposeField(
{
/**
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/useSlider/useSlider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { useLocale } from '../i18n';
import { useFormField, exposeField } from '../useFormField';
import { FieldTypePrefixes } from '../constants';
import { useInputValidity } from '../validation';
import { registerField } from '@formwerk/devtools';

export interface SliderProps<TValue = number> {
/**
Expand Down Expand Up @@ -479,6 +480,10 @@ export function useSlider<TValue>(_props: Reactivify<SliderProps<TValue>, 'schem
});
}

if (__DEV__) {
registerField(field, 'Slider');
}

return exposeField(
{
/**
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/useSwitch/useSwitch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { useLabel, useErrorMessage } from '../a11y';
import { useFormField, exposeField } from '../useFormField';
import { FieldTypePrefixes } from '../constants';
import { useInputValidity } from '../validation';
import { registerField } from '@formwerk/devtools';

export interface SwitchDomInputProps
extends InputBaseAttributes,
Expand Down Expand Up @@ -235,6 +236,10 @@ export function useSwitch<TValue = boolean>(
isPressed.value = force ?? !isPressed.value;
}

if (__DEV__) {
registerField(field, 'Switch');
}

return exposeField(
{
/**
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/useTextField/useTextField.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Ref, computed, shallowRef, toValue } from 'vue';
import { registerField } from '@formwerk/devtools';
import { createDescribedByProps, normalizeProps, propsToValues, useUniqId, withRefCapture } from '../utils/common';
import {
AriaDescribableProps,
Expand Down Expand Up @@ -177,6 +178,10 @@ export function useTextField(
);
});

if (__DEV__) {
registerField(field, 'Text');
}

return exposeField(
{
/**
Expand Down
43 changes: 43 additions & 0 deletions packages/devtools/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"name": "@formwerk/devtools",
"version": "0.8.1",
"description": "Formwerk's Vue devtools plugin",
"author": "Abdelrahman Awad <[email protected]>",
"license": "MIT",
"funding": "https://github.com/sponsors/logaretm",
"module": "dist/devtools.mjs",
"unpkg": "dist/devtools.iife.js",
"jsdelivr": "dist/devtools.iife.js",
"main": "dist/index.mjs",
"types": "dist/devtools.d.ts",
"type": "module",
"exports": {
".": {
"import": "./dist/devtools.mjs",
"require": "./dist/devtools.cjs",
"types": "./dist/devtools.d.ts"
},
"./dist/index.d.ts": "./dist/devtools.d.ts"
},
"sideEffects": false,
"keywords": [
"VueJS",
"Vue",
"validation",
"validator",
"inputs",
"form"
],
"files": [
"dist/*.js",
"dist/*.mjs",
"dist/*.cjs",
"dist/*.d.ts"
],
"dependencies": {
"klona": "^2.0.6",
"type-fest": "^4.34.1",
"@vue/devtools-api": "^7.7.2",
"@vue/devtools-kit": "^7.7.2"
}
}
20 changes: 20 additions & 0 deletions packages/devtools/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export function getPluginColors() {
return {
error: 0xbd4b4b,
success: 0x06d77b,
unknown: 0x54436b,
white: 0xffffff,
black: 0x000000,
blue: 0x035397,
purple: 0xb980f0,
orange: 0xf5a962,
gray: 0xbbbfca,
};
}
export function getInspectorId() {
return 'formwerk-inspector';
}

export function getRootFormId() {
return '~none';
}
Loading

0 comments on commit d34984c

Please sign in to comment.