Skip to content

Commit 8072694

Browse files
author
arnoson
committed
feat!: allow options to define refs and props
1 parent c96d9c5 commit 8072694

File tree

12 files changed

+190
-92
lines changed

12 files changed

+190
-92
lines changed

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
{
2-
"editor.formatOnSave": true
2+
"editor.formatOnSave": true,
3+
"editor.defaultFormatter": "esbenp.prettier-vscode"
34
}

index.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>Document</title>
8+
</head>
9+
<body>
10+
<div data-simple-component="test" data-message="hello"></div>
11+
<script type="module" src="./src/dev.ts"></script>
12+
</body>
13+
</html>

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"devDependencies": {
3535
"bumpp": "^8.2.1",
3636
"jsdom": "^20.0.0",
37+
"prettier": "^2.8.7",
3738
"shx": "^0.3.4",
3839
"tsup": "^6.0.1",
3940
"typescript": "^4.8.4",

pnpm-lock.yaml

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/dev.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { registerComponent, mountComponents } from '.'
2+
3+
const options = {
4+
props: { message: String, propWithDefault: 123 }
5+
}
6+
7+
registerComponent('test', options, ({ props }) => {
8+
console.log(props.propWithDefault)
9+
})
10+
11+
mountComponents()

src/index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
export { registerComponent } from './registerComponent'
1+
export { registerComponent, defineOptions } from './registerComponent'
22
export { mountComponent, mountComponents } from './mountComponent'
3-
export { defineProps } from './props'
43
export * from './types'

src/mountComponent.ts

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,47 @@
11
import { getComponent } from './registerComponent'
22
import { SimpleElement, SimpleRefs, SimpleRefsAll } from './types'
3+
import { isBuiltInTypeConstructor } from './utils'
34
import { walkComponent } from './walkComponent'
45

5-
const getRefsAll = (el: HTMLElement): SimpleRefsAll => {
6-
const refs: SimpleRefsAll = {}
6+
const parseProp = (value: any, type: string) =>
7+
type === 'string' ? String(value) : JSON.parse(value)
8+
9+
const stringifyProp = (value: any) =>
10+
typeof value === 'string' ? value : JSON.stringify(value)
11+
12+
const getDefaultProp = (definition: any) =>
13+
definition instanceof Function ? definition() : definition
14+
15+
const createPropsProxy = (
16+
el: HTMLElement,
17+
definitions: Record<string, any>
18+
) => {
19+
const get = (_: Object, key: string) => {
20+
const value = el.dataset[key]
21+
const definition = definitions[key]
22+
if (!definition) return value
23+
24+
const isConstructor = isBuiltInTypeConstructor(definition)
25+
const providesDefault = !isConstructor
26+
if (value === undefined && providesDefault)
27+
return getDefaultProp(definition)
28+
29+
const type = isConstructor
30+
? definition.prototype.constructor.name.toLowerCase()
31+
: typeof definition
32+
return parseProp(value, type)
33+
}
34+
35+
const set = (_: Object, key: string, value: unknown) => {
36+
el.dataset[key] = stringifyProp(value)
37+
return true
38+
}
39+
40+
return new Proxy({}, { get, set })
41+
}
42+
43+
const getRefsAll = (el: HTMLElement): SimpleRefsAll<any> => {
44+
const refs: SimpleRefsAll<any> = {}
745
walkComponent(el, el => {
846
const { ref } = el.dataset
947
if (ref) {
@@ -33,10 +71,17 @@ export const mountComponent = (el: HTMLElement, isChild = false) => {
3371

3472
const component = getComponent(el)
3573
if (component) {
36-
const simpleEl = el as SimpleElement<ReturnType<typeof component>>
37-
simpleEl.$component = component({ el, refs, refsAll }) || {}
74+
const simpleEl = el as SimpleElement<typeof component>
75+
const propsDefinitions = component.options.props
76+
77+
const props = propsDefinitions
78+
? createPropsProxy(el, propsDefinitions)
79+
: el.dataset
80+
81+
simpleEl.$props = props
3882
simpleEl.$refs = refs
3983
simpleEl.$refsAll = refsAll
84+
simpleEl.$component = component.setup({ el, refs, refsAll, props }) || {}
4085
}
4186
}
4287

src/props.ts

Lines changed: 0 additions & 57 deletions
This file was deleted.

src/registerComponent.ts

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,39 @@
1-
import { SimpleComponent } from './types'
1+
import {
2+
SimpleComponent,
3+
SimpleComponentOptions,
4+
SimpleComponentSetup
5+
} from './types'
26

37
export const components: Record<string, SimpleComponent<any>> = {}
48
export const getComponent = (el: HTMLElement) => {
59
const name = el.dataset.simpleComponent
610
return name ? components[name] : undefined
711
}
812

9-
export const registerComponent = <
10-
T extends HTMLElement,
11-
C extends SimpleComponent<T>
12-
>(
13-
name: string,
14-
component: C
15-
) => {
16-
if (typeof component !== 'function') {
13+
export const defineOptions = (options: SimpleComponentOptions) => options
14+
15+
export function registerComponent<
16+
Setup extends SimpleComponentSetup<Options>,
17+
Options extends SimpleComponentOptions = {}
18+
>(name: string, setup: Setup): SimpleComponent<any>
19+
20+
export function registerComponent<
21+
Setup extends SimpleComponentSetup<Options>,
22+
Options extends SimpleComponentOptions = {}
23+
>(name: string, options: Options, setup: Setup): SimpleComponent<any>
24+
25+
export function registerComponent<
26+
Setup extends SimpleComponentSetup<Options>,
27+
Options extends SimpleComponentOptions = {}
28+
>(name: string, arg1: Options | Setup, arg2?: Setup): SimpleComponent<Options> {
29+
const hasBothArgs = arg2 !== undefined
30+
const options = (hasBothArgs ? arg1 : {}) as Options
31+
const setup = (hasBothArgs ? arg2 : arg1) as Setup
32+
33+
if (typeof setup !== 'function')
1734
throw new Error(`Component ${name} is not a function.`)
18-
}
35+
36+
const component = { options, setup }
1937
components[name] = component
2038
return component
2139
}

src/types.ts

Lines changed: 65 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,75 @@
1-
export type SimpleRefs = Record<string, HTMLElement | undefined>
1+
type HTMLElementConstructor = typeof HTMLElement
22

3-
export type SimpleRefsAll = Record<string, HTMLElement[]>
3+
type HTMLElementType<T extends HTMLElementConstructor> = T['prototype']
44

5-
export type DefineRefs<T> = SimpleRefs & Partial<T>
5+
type HTMLElementsTypes<T extends Record<string, HTMLElementConstructor>> = {
6+
[K in keyof T]: HTMLElementType<T[K]>
7+
}
8+
9+
type BuiltInType = Number | String | Array<any> | Object | Boolean
610

7-
export type DefineRefsAll<T> = SimpleRefsAll & T
11+
type BuiltInTypeConstructor =
12+
| NumberConstructor
13+
| StringConstructor
14+
| ArrayConstructor
15+
| ObjectConstructor
16+
| BooleanConstructor
17+
18+
type PropTypes<Definitions extends SimpleComponentOptions['props']> = {
19+
[K in keyof Definitions]: Definitions[K] extends BuiltInTypeConstructor
20+
? ReturnType<Definitions[K]> | undefined
21+
: Definitions[K] extends () => any
22+
? ReturnType<Definitions[K]>
23+
: Definitions[K]
24+
}
825

9-
export type SimpleInstance<C extends SimpleComponent> = ReturnType<C>
26+
export type SimpleRefs = Record<string, HTMLElement>
1027

11-
export type SimpleElement<C extends SimpleComponent, T = HTMLElement> = T & {
12-
$component: SimpleInstance<C>
13-
$refs: SimpleRefs,
14-
$refsAll: SimpleRefsAll
28+
export type SimpleRefsAll<R extends SimpleRefs> = {
29+
[K in keyof R]: R[K][]
1530
}
1631

17-
export interface SimpleComponentPayload<T> {
18-
el: T
19-
refs: SimpleRefs
20-
refsAll: SimpleRefsAll
32+
export interface SimpleComponentOptions {
33+
el?: HTMLElementConstructor
34+
props?: Record<string, BuiltInType | BuiltInTypeConstructor>
35+
refs?: Record<string, HTMLElementConstructor>
36+
events?: Record<string, any>
37+
}
38+
39+
export type SimpleComponentPayload<
40+
Options extends SimpleComponentOptions,
41+
Refs extends SimpleRefs = HTMLElementsTypes<NonNullable<Options['refs']>>
42+
> = {
43+
el: Options['el'] extends HTMLElementConstructor
44+
? HTMLElementType<Options['el']>
45+
: HTMLElement
46+
47+
props: PropTypes<Options['props']>
48+
49+
refs: Record<string, HTMLElement | undefined> & Partial<Refs>
50+
51+
refsAll: Record<string, HTMLElement[]> & SimpleRefsAll<Refs>
2152
}
2253

23-
export type SimpleComponent<T = HTMLElement> = (
24-
payload: SimpleComponentPayload<T>
54+
export type SimpleComponentSetup<O extends SimpleComponentOptions> = (
55+
payload: SimpleComponentPayload<O>
2556
) => any
57+
58+
export type SimpleComponent<Options extends SimpleComponentOptions> = {
59+
setup: SimpleComponentSetup<Options>
60+
options: Options
61+
}
62+
63+
export type SimpleInstance<Component extends SimpleComponent<any>> = ReturnType<
64+
Component['setup']
65+
>
66+
67+
export type SimpleElement<
68+
Component extends SimpleComponent<any>,
69+
Options extends SimpleComponentOptions = Component['options'],
70+
Payload extends SimpleComponentPayload<any> = SimpleComponentPayload<Options>
71+
> = Payload['el'] & {
72+
$component: SimpleInstance<Component>
73+
$refs: Payload['refs']
74+
$refsAll: Payload['refsAll']
75+
}

0 commit comments

Comments
 (0)