Skip to content

Commit 502de8b

Browse files
author
arnoson
committed
chore: update readme
1 parent 3bdeb59 commit 502de8b

File tree

1 file changed

+140
-51
lines changed

1 file changed

+140
-51
lines changed

README.md

Lines changed: 140 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# ♻️ Very Simple Components
22

3-
A very simple way to attach javascript to the DOM. When even [petite-vue](https://github.com/vuejs/petite-vue) or [alpine.js](https://github.com/alpinejs/alpine/) would be too much.
3+
A very simple way to attach javascript/typescript to the DOM. When even [petite-vue](https://github.com/vuejs/petite-vue) or [alpine.js](https://github.com/alpinejs/alpine/) would be too much.
44

5-
💾 ~ 0.6kb (minify and gzip)
5+
💾 ~ 0.8kb (minify and gzip)
66

77
## Installation
88

@@ -15,13 +15,16 @@ npm i @very-simple/components
1515
```js
1616
// components/gallery.js
1717

18-
import { registerComponent, defineProps } from '@very-simple/components'
18+
import { registerComponent, defineOptions } from '@very-simple/components'
1919

20-
const props = defineProps({ loop: Boolean })
21-
registerComponent('gallery', ({ el, refs, refsAll }) => {
20+
const options = defineOptions({
21+
props: { loop: Boolean }
22+
})
23+
24+
registerComponent('gallery', options, ({ el, props, refs, refsAll }) => {
2225
// Props are read from el's dataset and automatically converted to the correct
2326
// type. Default values are also possible, see documentation.
24-
const { loop } = props(el)
27+
const { loop } = props
2528

2629
// Multiple HTML elements can have the same `ref` name. They will be
2730
// grouped in `refsAll` ...
@@ -76,13 +79,58 @@ registerComponent('gallery', ({ el, refs, refsAll }) => {
7679
### Register a Component
7780

7881
```ts
79-
registerComponent(name: string, component: Component)
82+
registerComponent('my-name', (ctx: Context) => {})
8083

81-
type Component = (payload: {
84+
type Context = {
85+
// The element the component is mounted to.
8286
el: HTMLElement
87+
88+
// The props of the component. If no prop types or default values are defined
89+
// this is just the element's dataset. Otherwise it will be a proxy around the
90+
// element's dataset that takes care of converting the props to the correct type.
91+
// See: #Props
92+
props: DOMStringMap | Proxy
93+
94+
// A Record of refs (elements with a `data-ref="name"` inside the component).
8395
refs: Record<string, HTMLElement | undefined>
96+
97+
// Similar to refs but can also contain multiple refs with the same name.
8498
refsAll: Record<string, HTMLElement[]>
85-
}) => any
99+
100+
// A fully typed `CustomEvent` constructor to dispatch type safe events.
101+
// See: #Events
102+
ComponentEvent: SimpleComponentEvent
103+
}
104+
```
105+
106+
### Register a Component with Options
107+
108+
By passing along `options`, you can add provide additional type hints and automatically parse props from the element's dataset to the correct type.
109+
110+
```ts
111+
const options = defineOptions({
112+
// Provide a type for the element (default will be HTMLElement)
113+
el: HTMLImageElement,
114+
115+
// Provide types and/or default values for props
116+
// See: #Props
117+
props: {
118+
loop: Boolean,
119+
count: 10
120+
},
121+
122+
// Provide types for some or all refs (by default all refs will be HTMLElement)
123+
refs: { click: HTMLButtonElement },
124+
125+
// Provide types for the elements events.
126+
// See: #Events
127+
events: {
128+
updateCount: Number,
129+
close: null
130+
}
131+
})
132+
133+
registerComponent('my-name', options, (ctx: Context) => {})
86134
```
87135

88136
### Mount a single Component
@@ -100,42 +148,39 @@ mountComponent(el: HTMLElement)
100148
mountComponent(root?: HTMLElement)
101149
```
102150

103-
### Define Props
151+
### Props
104152

105-
Define properties to automatically convert `el.dataset` properties to the
106-
correct type and enable autocompletion.
153+
`props`, passed to the component's setup function can read from / write to the component elements dataset. By default all values are strings (as is the normal behavior with an element's dataset). But by providing types and default values for props, these values will be automatically converted to the correct type!
107154

108155
```ts
109-
// You can either define a prop's type by proving a constructor ...
110-
defineProps({ answer: Number, enabled: Boolean })
156+
const options = defineOptions({
157+
props: { count: 0 }
158+
})
111159

112-
// ... or by providing a default value.
113-
defineProps({ answer: 42, enabled: true })
160+
registerComponent('my-component', options, ({ el, props }) => {
161+
// If the element hasn't `data-count` specified, this will output the default
162+
// value `0`.
163+
console.log(props.count) // => 0
114164

115-
// For objects and arrays the default value can be wrapped inside a function
116-
defineProps({ list: () => [1, 2, 3] })
165+
// If the element has `data-count="20"`, "20" will be automatically parsed
166+
// into a number and returned.
167+
console.log(props.count) // => 20
168+
})
117169
```
118170

119-
Example:
171+
This also works for complex data types:
120172

121173
```ts
122-
const props = defineProps({
123-
enabled: true,
124-
message: String,
125-
tags: () => ['default']
174+
const options = defineOptions({
175+
props: { todos: [] as string[] }
126176
})
127177

128-
registerComponent('my-component', ({ el }) => {
129-
const { enabled, message, tags } = props(el)
130-
})
131-
```
178+
// Lets say the html for the component looks like this:
179+
// <div data-simple-component="todo" data-todos='["mount components!", "enjoy"]'>
132180

133-
```html
134-
<div
135-
data-simple-component="my-component"
136-
data-message="Hello"
137-
data-tags='[ "very", "simple", "components" ]'
138-
></div>
181+
registerComponent('todo', options, ({ props }) => {
182+
console.log(props.todos[0]) // => 'mount components!'
183+
})
139184
```
140185

141186
### Ignore Elements
@@ -149,40 +194,46 @@ to mount:
149194
</div>
150195
```
151196

152-
### Expose Component Methods
197+
### Expose Component
153198

154-
Everything you return from the component function is available on the HTML
155-
element's `$component` property:
199+
The component's context and everything you return from the component's setup function is available on the HTML element.
156200

157201
```js
158-
registerComponent('my-component', () => {
159-
const sayHello = () => console.log('Hello :~)')
202+
registerComponent('my-component', ({ refs, refsAll, props }) => {
203+
const sayHello = () => console.log(props.message)
160204
return { sayHello }
161205
})
162206
```
163207

164208
```html
165-
<div data-simple-component="my-component" id="my-id"></div>
209+
<div data-simple-component="my-component" id="my-id" data-message="Hello :~)">
210+
<button data-ref="button">Click me!</button>
211+
</div>
166212
```
167213

168214
```js
169-
document.getElementById('my-id').$component.sayHello()
215+
const el = document.getElementById('my-id')
216+
el.$refs.button.innerText // => 'Click me!'
217+
el.$component.sayHello() // => 'Hello :~)'
218+
el.$props.message = 'Goodbye'
219+
el.$component.sayHello() // => 'Goodbye'
170220
```
171221

172222
With typescript you can also get autocompletion:
173223

174224
```ts
175225
// my-component.ts
176-
export default registerComponent('my-component', () => {
177-
const sayHello = () => console.log('Hello :~)')
226+
export default registerComponent('my-component', ({ props }) => {
227+
const sayHello = () => console.log(props.message)
178228
return { sayHello }
179229
})
180230

181231
// index.ts
182232
import MyComponent from './my-component.ts'
183233
import type { SimpleElement } from '@very-simple/components'
184234

185-
const el = document.getElementById<SimpleElement<typeof MyComponent>>('my-id')
235+
type MyComponentElement = SimpleElement<typeof MyComponent>
236+
const el = document.getElementById<MyComponentElement>('my-id')
186237

187238
el.$component.sayHello() // <- this gets autocompleted
188239
```
@@ -193,17 +244,55 @@ Refs are of type HTMLElement by default, but it can be useful to define a more
193244
specific type for some of them:
194245

195246
```ts
196-
import type { DefineRefs, DefineRefsAll } from '@very-simple/components'
247+
const options = defineOptions({
248+
refs: { img: HTMLImageElement, videos: HTMLVideoElement }
249+
})
250+
251+
registerComponent('my-component', options, ({ refs, refsAll }) => {
252+
const { container, img } = refs
253+
// container -> HTMLElement
254+
// img -> HTMLImageElement
197255

198-
registerComponent('my-component', ({ refs, refsAll }) => {
199-
const { slides, videos } = refsAll as DefineRefsAll<{
200-
videos: HTMLVideoElement[]
201-
}>
256+
const { slides, videos } = refsAll
202257
// slides -> HTMLElement[]
203258
// videos -> HTMLVideoElement[]
259+
})
260+
```
204261

205-
const { container, img } = refs as DefineRefs<{ img: HTMLImageElement }>
206-
// container -> HTMLElement
207-
// img -> HTMLImageElement
262+
### Events
263+
264+
Components try to stay as close to native APIs as possible. Therefore events are just [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent), but they can be fully typed:
265+
266+
```ts
267+
const options = defineOptions({
268+
events: { updateCounter: Number, close: null }
269+
})
270+
271+
registerComponent('my-component', options, ({ el, ComponentEvent }) => {
272+
// These will be autocompleted and generate type-errors if you forget, for
273+
// example, the value for the `updateCounter` event.
274+
el.dispatchEvent(new ComponentEvent('updateCounter', { detail: 10 }))
275+
el.dispatchEvent(new ComponentEvent('close'))
276+
277+
// `ComponentEvent` ist just the native `CustomEvent` but with types based
278+
// on your `options.events`.
208279
})
209280
```
281+
282+
This also works if you listen to a component's event from outside the component's setup fuction.
283+
284+
```ts
285+
// my-component.ts
286+
const options = defineOptions({ events: { updateCounter: Number } })
287+
export default registerComponent('my-component', options, () => {})
288+
289+
// index.ts
290+
import MyComponent from './my-component.ts'
291+
import type { SimpleElement } from '@very-simple/components'
292+
293+
type MyComponentElement = SimpleElement<typeof MyComponent>
294+
const el = document.getElementById<MyComponentElement>('my-id')
295+
296+
// This will be fully typed:
297+
el.addEventListener('updateCounter', ({ detail: count }) => console.log(count))
298+
```

0 commit comments

Comments
 (0)