Props
Guide
Perhaps the props Guide is also useful to you.
propType
This object is what's created by the propType
helpers and passed to your component for each prop you define. Besides the information in here, props are also typed based on this object, and the helpers make sure that the type
matches up with the deafult
and validator
. Together with that, the optional
and validator
can make type | undefined
.
The PropType
is the type of the propType
helper functions that you can use for your component props, and it exposes an initial type
(e.g. string
or func
) for you to work with.
export type PropTypeDefinition<T = any> = {
type:
| typeof Number
| typeof String
| typeof Boolean
| typeof Date
| typeof Array
| typeof Object
| typeof Function;
default?: T extends Primitive ? T : () => T;
validator?: Predicate<T>;
isOptional?: boolean;
missingValue?: boolean;
// eslint-disable-next-line @typescript-eslint/ban-types
shapeType?: Function;
sourceOptions?: {
target?: string;
type?: 'data' | 'json' | 'attr' | 'css' | 'text' | 'html' | 'form' | 'custom';
name?: string;
options?: {
cssPredicate?: Array<string>;
};
}
};
type PropType = {
string: PropTypeDefinition;
number: PropTypeDefinition;
boolean: PropTypeDefinition;
object: PropTypeDefinition;
array: PropTypeDefinition;
date: PropTypeDefinition;
func: PropTypeDefinition;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
THis is how you can use it in your component:
import { propType } from '@muban/muban';
defineComponent({
props: {
foo: propType.string
}
})
2
3
4
5
6
7
type
The objects below highlight what properties or functions you can call on each propType
object. Setting or calling them will result in setting one of the keys from the PropTypeDefinition
above.
All of them offer the same helpers, except func
since that one can only be passed from parent components. The only difference between the others is how they are typed.
propType.string;
{
type: String,
optional: {},
defaultValue(value: string) => {},
validate(predicate: Predicate<string>) => {},
sourceOptions: {},
}
2
3
4
5
6
7
8
9
propType.number;
{
type: Number,
optional: {},
defaultValue(value: number) => {},
validate(predicate: Predicate<number>) => {},
sourceOptions: {},
}
2
3
4
5
6
7
8
9
propType.boolean;
{
type: Boolean,
optional: {},
defaultValue(value: boolean) => {},
validate(predicate: Predicate<boolean>) => {},
sourceOptions: {},
}
2
3
4
5
6
7
8
9
propType.object;
{
type: Object,
optional: {},
defaultValue(value: Object) => {},
validate(predicate: Predicate<Object>) => {},
sourceOptions: {},
}
2
3
4
5
6
7
8
9
propType.array;
{
type: Array,
optional: {},
defaultValue(value: Array) => {},
validate(predicate: Predicate<Array>) => {},
sourceOptions: {},
}
2
3
4
5
6
7
8
9
propType.date;
{
type: Date,
optional: {},
defaultValue(value: Date) => {},
validate(predicate: Predicate<Date>) => {},
sourceOptions: {},
}
2
3
4
5
6
7
8
9
propType.func;
{
type: Function,
optional: {},
shape<T>() => {},
}
2
3
4
5
6
7
optional
When using optional
, you mark the PropTypeDefinition
object as optional (you don't have to pass a value), and also indicates that the value (when the prop is resolve) is missing.
optional;
{
...obj,
isOptional: true,
missingValue: true,
}
2
3
4
5
6
7
propType.string.optional;
Guide
Read more on the optional Guide page.
defaultValue
When using defaultValue
, you set this default value on the PropTypeDefinition
while also marking it as optional
(you don't have to pass a value), but since you have a default
set, it will never be missing a value (when resolved).
The passed defaultValue
must match the type of propType
you're working with.
defaultValue: <U extends ConstructorType<T['type']> | undefined>(
value: U extends Primitive ? U : () => U,
);
{
...obj,
isOptional: true,
default: value,
}
2
3
4
5
6
7
8
9
propType.string.defaultValue('foo');
Guide
Read more on the defaultValue Guide page.
validate
When using the validate
, you have to pass a Predicate
function that gets checked after converting the extracted value to the right type, or when passing a value from the parent component. If the predicate fails (returns false), an error will be thrown.
Furthermore, the predicate is also used to set a stricter type from the default string
or Object
, so you can make it a string literal or a union thereof, or define a complex object shape.
validate: <U extends ConstructorType<T['type']> | undefined>(predicate: Predicate<U>)
{
...obj,
validator: predicate,
}
2
3
4
5
6
export const isPositive: Predicate<number> = (value: unknown): value is number =>
typeof value === 'number' && (value === 0 ? Infinity / value : value) > 0
// value validation
propType.number.validate(isPositive);
// value validation + type
propType.object.validate(
shape({ foo: isNumber, bar: optional(isString)})
);
// is typed as
{
foo: number;
bar?: string;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Guide
Read more on the validate Guide page.
shape
The shape
helper only exists for function types, and allows you to define the shape of the function you expect from a parent component.
shape: <T extends Function>();
{
...obj,
shapeType: (true as unknown) as T,
}
2
3
4
5
6
propType.func.shape<(isExpanded: boolean) => void>()
Guide
Read more on the functions Guide page.
source
The source
helper allows you to explicitly define how and where from to extract data from your HTML besides the default behaviour.
declare function source(options: {
target?: string;
type?: 'data' | 'json' | 'attr' | 'css' | 'text' | 'html' | 'form' | 'custom';
name?: string;
options?: {
cssPredicate?: Array<string>;
};
})
2
3
4
5
6
7
8
target?: string
– The refName (those you configure as part of the component options) from which you want to extract this property. Defaults to the data-component element.type?: 'data' | 'json' | 'attr' | 'css' | 'text' | 'html' | 'form' | 'custom'
- The type source you want to extract. Defaults to thedata + json + css
source (css
only for boolean props).data
– Reads thedata-attribute
from your target element.json
– Reads the object key from a<script type="application/json">
JSON block that is located as a first child of your target element.attr
- Reads theattribute
from your target element.css
– When thepropType
is boolean, checks if the css class is set on the target element. When thepropType
isstring
, it sets the value to the first matching class on the target element that matches the configuredcssPredicate
option. When thepropType
is anArray
, it fills it with all the classes from the target element. When thepropType
is set toObject
, it creates keys for the classnames, and setstrue
to all values.text
– Reads thetextContents
from the target element.html
– Reads theinnerHTML
from the target element.form
– Reads thevalue
from the target element when targeting a form input andFormData
when targeting a form element
name?: string
– For thedata
,json
,attr
andcss
source types, by default it will use the propName for the name of the (data) attribute or class name. For situations where the name of the "source" is different from the propName you want to have in your component, you can set this value. For example, if you want to have 2 different props filled with the samedata-attribute
from different target elements.options?
– An options object to configure additional settings for some of the source types.cssPredicate?: Predicate<string>
– Sometimes you set one of a few possible css classes, and you want to know which one is set. When astring
propType is used in combination with thecss
source, you have to provide this Predicate to search for the class name you're interested in.
TBD future?
If you have an
Array
propType, it becomes possible to use anElementCollection
orComponentCollection
, and the extracted values from each element or component will be added to the array. Unfortunately all extracted values will be strings.
Guide
Read more on the source Guide page.
data-attribute
Default defineComponent({
name: 'my-component',
props: {
// by default, read the data-attribute from the data-component element
isActive: propType.boolean
},
})
2
3
4
5
6
7
<div data-component="my-component" data-is-active="true">Content</div>
<div data-component="my-component" data-active="true">Content</div>
<div data-component="my-component" data-isActive="true">Content</div>
2
3
<script type="application/json">
Default fallback to defineComponent({
name: 'my-component',
props: {
// if no data-attribute exits, fallback to the <script type="application/json"> tag
isActive: propType.boolean
},
})
2
3
4
5
6
7
<div data-component="my-component">
<script type="application/json">
{ "isActive": true }
</script>
Content
</div>
2
3
4
5
6
css
for boolean
props
Fallback to defineComponent({
name: 'my-component',
props: {
// if no data-attribute or JSON exits, fallback to `css` source
// this only works for boolean props
isActive: propType.boolean
},
})
2
3
4
5
6
7
8
<div data-component="my-component" class="is-active">Content</div>
<div data-component="my-component" class="isActive">Content</div>
2
css
on a different element
Use defineComponent({
name: 'my-component',
refs: {
// this is needed for the source-target
content: 'content',
contentMore: 'content-more',
},
props: {
// instead of the data-component element, use the content ref
// to get the property value from
isExpanded: propType.boolean.source({ target: 'content' }),
// if your propName is different from the source name, you should specify it
isMoreExpanded: propType.boolean.source(
{ target: 'content', name: 'is-expanded' }
),
},
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<div data-component="my-component">
<div class="is-expanded" data-ref="content">Content</div>
<div class="is-expanded" data-ref="content-more">More Content</div>
</div>
2
3
4
string
, Array
and Object
types for css
Use the defineComponent({
name: 'my-component',
refs: {
// this is needed for the source-target
item1: 'item1',
item2: 'item2',
item3: 'item3',
items: refCollection(parent => parent.querySelectorAll('.item')),
},
props: {
// set to 'recipe'
item1Type: propType.string.source({
target: 'item1',
type: 'css',
options: { cssPredicate: either('recipe', 'article') }
}),
// set to 'item-recipe'
item2Type: propType.string.source({
target: 'item2',
type: 'css',
options: { cssPredicate: either('item-recipe', 'item-article') }
}),
// set to 'item-recipe'
item2Type: propType.string.source({
target: 'item2',
type: 'css',
options: { cssPredicate: test(/^item-/) }
}),
// get all classNames from a single element
// set to either ['item', 'item-recipe']
itemTypes: propType.array.source({
target: 'item2',
type: 'css',
}),
// combine enum classes from a collection
// set to ['item-recipe', 'item-article']
// TODO: currently not supported
// itemTypes: propType.array.source({
// target: 'items',
// type: 'css',
// options: { cssPredicate: test(/^item-/) }
// }),
// get all classNames from a single element
// set to { item: true, 'item-recipe': true }
itemTypes: propType.object.source({
target: 'items1',
type: 'css',
}),
},
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<div data-component="my-component">
<div data-ref="item1" class="recipe" >Content</div>
<div data-ref="item2" class="item item-recipe" >More Content</div>
<div data-ref="item3" class="item item-article" >More Content</div>
</div>
2
3
4
5
6
attr
Use defineComponent({
name: 'my-component',
refs: {
// this is needed for the source-target
contentImage: 'content-image',
},
props: {
// grab the `src` attribute from the `contentImage` ref.
contentImageSource: propType.string.source(
{ target: 'contentImage', type: 'attr', name: 'src' },
),
// without a `name` it will use the propName (`src` in this case)
src: propType.string.source(
{ target: 'contentImage', type: 'attr' },
)
},
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<div data-component="my-component">
<img data-ref="content-image" src="./image.jpg"/>
</div>
2
3
text
and html
Use defineComponent({
name: 'my-component',
refs: {
// this is needed for the source-target
content: 'content',
status: 'status',
value: 'value',
},
props: {
// get the innerHTML from the content ref
// outputs "This <strong>is</strong> some <u>Content</u>"
content: propType.string.source(
{ target: 'content', type: 'html'},
),
// get the textContents from the status ref
// outputs "This is some Content"
textContent: propType.string.source(
{ target: 'status', type: 'text'},
),
// outputs "Success"
status: propType.string.source(
{ target: 'status', type: 'text'},
),
// conversions to Booleans, Numbers and Dates also works
// outputs "12.45" (as a number)
status: propType.number.source(
{ target: 'value', type: 'text'},
),
},
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<div data-component="my-component">
<p data-ref="content">
This <strong>is</strong> some <u>Content</u>
</p>
<span data-ref="status">Success</span>
<span data-ref="value">12.45</span>
</div>
2
3
4
5
6
7
form
Use defineComponent({
name: 'my-component',
refs: {
// this is needed for the source-target
form: 'form',
email: 'email',
phone: 'phone',
},
props: {
// get the FormData from the form ref
// outputs FormData {}
form: propType.object.source(
{ target: 'form', type: 'form', formData: true},
),
// get the value from the email ref
// outputs "user@company.com"
email: propType.string.source(
{ target: 'email', type: 'form'},
),
// Extract the 'email' property from the form ref FormData object
// outputs "user@company.com"
emailAddress: propType.string.source(
{ target: 'form', type: 'form', name: 'email'},
),
// conversions to Booleans, Numbers and Dates also work
// outputs "986868" (as a number)
phone: propType.number.source(
{ target: 'phone', type: 'form'},
),
},
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<div data-component="my-component">
<form data-ref="form">
<input type="text" data-ref="email" value="user@company.com"/>
<input type="text" data-ref="phone" value="986868"/>
</form>
</div>
2
3
4
5
6
custom
Use defineComponent({
name: 'my-component',
refs: {
// this is needed for the source-target
title: 'title',
},
props: {
// grab the length of the title component.
// outputs "32" (as a number)
characterCount: propType.number.source({
target: 'title',
type: 'custom',
options: {
customSource: (element: HTMLElement | Array<HTMLElement> | undefined) =>
(element as HTMLElement)!.innerHTML.length;
}
})
},
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div data-component="my-component">
<h1 data-ref="title">This title is 32 characters long</h1>
</div>
2
3