Skip to content

Commit

Permalink
Merge pull request #15 from daleal/master
Browse files Browse the repository at this point in the history
Release 0.3.0
  • Loading branch information
daleal authored Apr 27, 2022
2 parents b680d91 + 03205bc commit 0cceed3
Show file tree
Hide file tree
Showing 14 changed files with 203 additions and 52 deletions.
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
<p align="center">
<a href="https://github.com/daleal/loveform">
<img src="https://loveform.daleal.dev/assets/images/loveform-300x300.png">
</a>
</p>

<h1 align="center">Loveform</h1>

<p align="center">
<em>
The Vue form assembly tool that <strong>won't break your heart</strong> 💔.
The Vue form assembly tool that <strong>won't break your heart</strong> 💔
</em>
</p>

Expand Down Expand Up @@ -92,6 +98,16 @@ const submit = async () => {

Each validation corresponds to a function that receives the value in the input and returns `true` (if the value of the input is correct) or an error string. Each validation will be run _sequentially_ from the first validation of the array to the last one, and the first validation to fail will be displayed as the error.

## Available Components

The available components are listed below:

- `LForm`: The form wrapper that validates inputs when trying to submit.
- `LInput`: A validatable `input` component.
- `LTextarea`: A validatable `textarea` component.
- `LCheckbox`: A `checkbox` type `input` component that plays nicely with the `LCheckboxGroup` component.
- `LCheckboxGroup`: A validatable group of `LCheckbox` components.

## Development

### Testing locally
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
{
"name": "loveform",
"version": "0.2.0",
"version": "0.3.0",
"description": "The Vue form assembly tool that won't break your heart 💔",
"homepage": "https://loveform.daleal.dev",
"repository": "https://github.com/daleal/loveform",
"bugs": "https://github.com/daleal/loveform/issues",
"license": "MIT",
"author": {
"name": "Daniel Leal",
Expand Down
41 changes: 41 additions & 0 deletions src/components/LCheckbox/LCheckbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { defineComponent, ref } from 'vue';
import { useCheckbox } from '@/composables/checkbox';
import { useRender } from '@/utils/render';

export const LCheckbox = defineComponent({
name: 'LCheckbox',
inheritAttrs: false,
props: {
modelValue: {
type: Boolean,
default: () => false,
},
},
emits: {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
'update:modelValue': (value: boolean) => true,
},
setup(props, { attrs, emit }) {
const content = ref(props.modelValue);

useCheckbox(content);

const onInput = (event: Event) => {
content.value = (event.target as HTMLInputElement).checked;
emit('update:modelValue', (event.target as HTMLInputElement).checked);
};

useRender(() => (
<>
<input
value={content.value}
onInput={onInput}
type="checkbox"
{ ...attrs }
/>
</>
));
},
});

export type LCheckbox = InstanceType<typeof LCheckbox>
1 change: 1 addition & 0 deletions src/components/LCheckbox/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { LCheckbox } from './LCheckbox';
35 changes: 35 additions & 0 deletions src/components/LCheckboxGroup/LCheckboxGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { computed, defineComponent } from 'vue';
import { createCheckboxGroup } from '@/composables/checkboxGroup';
import { useValidation, makeValidationProps } from '@/composables/validation';
import { useRender } from '@/utils/render';

export const LCheckboxGroup = defineComponent({
name: 'LCheckboxGroup',
inheritAttrs: false,
props: {
...makeValidationProps<Array<boolean>>(),
},
setup(props, { slots }) {
const checkboxGroup = createCheckboxGroup();

const items = computed(() => checkboxGroup.items.value.map((content) => content.value));

const validation = useValidation<Array<boolean>>(props, items);

useRender(() => (
<>
<>
{ slots.default?.() }
</>
{ validation.renderError.value && <p>{ validation.error.value }</p> }
</>
));

return {
valid: validation.valid,
error: validation.error,
};
},
});

export type LCheckboxGroup = InstanceType<typeof LCheckboxGroup>;
1 change: 1 addition & 0 deletions src/components/LCheckboxGroup/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { LCheckboxGroup } from './LCheckboxGroup';
26 changes: 15 additions & 11 deletions src/components/LInput/LInput.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,38 @@
import { defineComponent } from 'vue';
import {
useValidation,
makeValidationEmits,
makeValidationProps,
UPDATE_MODEL_VALUE,
} from '@/composables/validation';
import { defineComponent, ref } from 'vue';
import { useValidation, makeValidationProps } from '@/composables/validation';
import { useRender } from '@/utils/render';

export const LInput = defineComponent({
name: 'LInput',
inheritAttrs: false,
props: {
modelValue: {
type: String,
default: () => '',
},
...makeValidationProps<string>(),
},
emits: {
...makeValidationEmits<string>(),
// eslint-disable-next-line @typescript-eslint/no-unused-vars
'update:modelValue': (value: string) => true,
},
setup(props, { attrs, emit }) {
const validation = useValidation<string>(props);
const content = ref(props.modelValue);

const validation = useValidation<string>(props, content);

const onInput = (event: Event) => {
emit(UPDATE_MODEL_VALUE, (event.target as HTMLInputElement).value);
content.value = (event.target as HTMLInputElement).value;
emit('update:modelValue', (event.target as HTMLInputElement).value);
};

useRender(() => (
<>
<input
value={props.modelValue}
value={content.value}
onInput={onInput}
onBlur={validation.startValidating}
type="text"
{ ...attrs }
/>
{ validation.renderError.value && <p>{ validation.error.value }</p> }
Expand Down
25 changes: 14 additions & 11 deletions src/components/LTextarea/LTextarea.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,35 @@
import { defineComponent } from 'vue';
import {
useValidation,
makeValidationEmits,
makeValidationProps,
UPDATE_MODEL_VALUE,
} from '@/composables/validation';
import { defineComponent, ref } from 'vue';
import { useValidation, makeValidationProps } from '@/composables/validation';
import { useRender } from '@/utils/render';

export const LTextarea = defineComponent({
name: 'LTextarea',
inheritAttrs: false,
props: {
modelValue: {
type: String,
default: () => '',
},
...makeValidationProps<string>(),
},
emits: {
...makeValidationEmits<string>(),
// eslint-disable-next-line @typescript-eslint/no-unused-vars
'update:modelValue': (value: string) => true,
},
setup(props, { attrs, emit }) {
const validation = useValidation<string>(props);
const content = ref(props.modelValue);

const validation = useValidation<string>(props, content);

const onInput = (event: Event) => {
emit(UPDATE_MODEL_VALUE, (event.target as HTMLInputElement).value);
content.value = (event.target as HTMLInputElement).value;
emit('update:modelValue', (event.target as HTMLInputElement).value);
};

useRender(() => (
<>
<textarea
value={props.modelValue}
value={content.value}
onInput={onInput}
onBlur={validation.startValidating}
{ ...attrs }
Expand Down
2 changes: 2 additions & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export * from './LCheckbox';
export * from './LCheckboxGroup';
export * from './LForm';
export * from './LInput';
export * from './LTextarea';
19 changes: 19 additions & 0 deletions src/composables/checkbox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { onBeforeMount, onBeforeUnmount } from 'vue';
import { useCheckboxGroup } from '@/composables/checkboxGroup';
import { getUniqueId } from '@/utils/uniqueId';

// Types
import type { Ref } from 'vue';

export const useCheckbox = (content: Ref<boolean>) => {
const checkboxGroup = useCheckboxGroup();
const uid = getUniqueId();

onBeforeMount(() => {
checkboxGroup?.register(uid, content);
});

onBeforeUnmount(() => {
checkboxGroup?.unregister(uid);
});
};
44 changes: 44 additions & 0 deletions src/composables/checkboxGroup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import {
computed, inject, provide, shallowRef,
} from 'vue';

// Types
import type { InjectionKey, Ref } from 'vue';

type IdType = number;

interface Checkbox {
id: IdType,
value: Ref<boolean>,
}

export interface CheckboxGroupProvide {
register: (
id: IdType,
value: Ref<boolean>,
) => void,
unregister: (
id: IdType,
) => void,
}

export const CheckboxGroupKey: InjectionKey<CheckboxGroupProvide> = Symbol.for('loveform:checkbox-group');

export const createCheckboxGroup = () => {
const items = shallowRef<Array<Checkbox>>([]);

const values = computed(() => items.value.map((checkbox) => checkbox.value));

provide(CheckboxGroupKey, {
register: (id: IdType, value: Ref<boolean>) => {
items.value = [...items.value, { id, value }];
},
unregister: (id: IdType) => {
items.value = items.value.filter((item) => item.id !== id);
},
});

return { items: values };
};

export const useCheckboxGroup = () => inject(CheckboxGroupKey, null);
15 changes: 5 additions & 10 deletions src/composables/form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ import {
} from 'vue';

// Types
import type {
ComputedRef, InjectionKey, PropType, Ref,
} from 'vue';
import type { InjectionKey, PropType, Ref } from 'vue';

export const SUBMIT = 'submit';
export const SUBMIT_PROP = 'onSubmit';
Expand All @@ -25,13 +23,13 @@ export interface FormProps {
export interface FormProvide {
register: (
id: IdType,
valid: ComputedRef<boolean>,
valid: Ref<boolean>,
) => void,
unregister: (
id: IdType,
) => void,
hideErrors: boolean,
valid: ComputedRef<boolean>,
valid: Ref<boolean>,
}

export const FormKey: InjectionKey<FormProvide> = Symbol.for('loveform:form');
Expand Down Expand Up @@ -62,11 +60,8 @@ export const createForm = (props: FormProps) => {
});

provide(FormKey, {
register: (id: IdType, valid: ComputedRef) => {
items.value.push({
id,
valid,
});
register: (id: IdType, valid: Ref<boolean>) => {
items.value = [...items.value, { id, valid }];
},
unregister: (id: IdType) => {
items.value = items.value.filter((item) => item.id !== id);
Expand Down
Loading

0 comments on commit 0cceed3

Please sign in to comment.