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 + csssource (cssonly for boolean props).data– Reads thedata-attributefrom 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 theattributefrom your target element.css– When thepropTypeis boolean, checks if the css class is set on the target element. When thepropTypeisstring, it sets the value to the first matching class on the target element that matches the configuredcssPredicateoption. When thepropTypeis anArray, it fills it with all the classes from the target element. When thepropTypeis set toObject, it creates keys for the classnames, and setstrueto all values.text– Reads thetextContentsfrom the target element.html– Reads theinnerHTMLfrom the target element.form– Reads thevaluefrom the target element when targeting a form input andFormDatawhen targeting a form element
name?: string– For thedata,json,attrandcsssource 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-attributefrom 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 astringpropType is used in combination with thecsssource, you have to provide this Predicate to search for the class name you're interested in.
TBD future?
If you have an
ArraypropType, it becomes possible to use anElementCollectionorComponentCollection, 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.
Default data-attribute
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
Default fallback to <script type="application/json">
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
Fallback to css for boolean props
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
Use css on a different element
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
Use the string, Array and Object types for css
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
Use attr
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
Use text and html
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
Use form
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
Use custom
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