Skip to content

Commit bfc56f4

Browse files
committed
add state usage support for props
1 parent cf13e61 commit bfc56f4

File tree

7 files changed

+101
-60
lines changed

7 files changed

+101
-60
lines changed

src/compiler/generate-code.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export function clientTemplatesToJs(): string {
3333
mapMapToArray(getClientTemplates(), ([componentId, template]) => {
3434
code += wrapQuoteIfStartsWithNumber(componentId)
3535
code += ':'
36-
code += `'${template}'`
36+
code += `'${template.replace(/'/, '\\\'')}'`
3737
code += ','
3838
})
3939
code += '},'

src/compiler/template/check-props.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {filterObject, mapObject} from '../../utils/iterate-object'
22
import {isStateUsage} from '../../state/state-usage'
33
import {isState} from '../../state/state'
4+
import {stringifyDomTokenList, stringifyStyleObject} from '../../runtime/state-usage'
45

56
export function checkProps(props: { [propName: string]: any }) { // todo
67
const mapped = mapObject(props, ([propName, propValue]) => {
@@ -20,12 +21,9 @@ export function transformProps(props: { [propName: string]: any }) {
2021
propValue = propValue.use()
2122
if (!isStateUsage(propValue)) {
2223
if (['class', 'id'].includes(propName) && Array.isArray(propValue))
23-
propValue = propValue.filter(v => v).join(' ').trim()
24+
propValue = stringifyDomTokenList(propValue)
2425
if (propName === 'style' && typeof propValue === 'object')
25-
propValue = Object.entries(propValue)
26-
.filter(([name, value]) => value && value !== 0)
27-
.map(([name, value]) => `${name}:${value}`)
28-
.join(';')
26+
propValue = stringifyStyleObject(propValue)
2927
}
3028
return [propName.toLowerCase(), propValue]
3129
})

src/compiler/template/state-usage.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,6 @@ import StateUsage, {isStateUsage} from '../../state/state-usage'
44
import {getClientState} from '../../runtime'
55
import {stateIsListenedTo} from '../states-collector'
66

7-
export type ContextType<T extends 'child' | 'prop'> = {
8-
type: T,
9-
contextElement: VirtualElement['_id']['fullPath']
10-
prop: T extends 'prop' ? string : never,
11-
beforeChild: T extends 'child' ? number : never,
12-
makeString: string // (stateValue: string) => string
13-
}
14-
157
export type ProtoContextType<T extends 'child' | 'prop'> = {
168
type: T,
179
contextElement?: VirtualElement,
@@ -50,7 +42,7 @@ export function getStateUsagesAsCode() {
5042
code += `${stateUsagesName}.set('${key}', ${usage.transform.toString()});` + newLine
5143
let functionArray = '['
5244
for (const state of usage.states) {
53-
functionArray += `${getClientState.name}('${state.id}')`
45+
functionArray += `${getClientState.name}('${state.id}'),`
5446
if (!stateStateUsagesMap[state.id]) {
5547
stateStateUsagesMap[state.id] = []
5648
}
@@ -85,6 +77,14 @@ function stringifyContext(context: ContextType<any>): string {
8577
return contextString
8678
}
8779

80+
export type ContextType<T extends 'child' | 'prop'> = {
81+
type: T,
82+
contextElement: VirtualElement['_id']['fullPath']
83+
prop: T extends 'prop' ? string : never,
84+
beforeChild: T extends 'child' ? number : never,
85+
makeString: string // (stateValue: string) => string
86+
}
87+
8888
export function makeContext<T extends 'child' | 'prop'>(
8989
{type, contextElement, prop}: ProtoContextType<T>, stateUsage: StateUsage
9090
): ContextType<T> {
@@ -118,7 +118,11 @@ export function makeContext<T extends 'child' | 'prop'>(
118118
context.makeString = functionString
119119
} else {
120120
context.prop = prop
121-
// todo
121+
/*if (['style', 'class', 'id'].includes(prop)) {
122+
context.makeString = 'value => value'
123+
} else {
124+
context.makeString = 'value => String(value)'
125+
}*/
122126
}
123127
return context as ContextType<T>
124128
}

src/compiler/template/template-builder.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ export default class TemplateBuilder {
121121
element.props.ref.populate(element)
122122
}
123123
const props = checkProps(element.props)
124-
const stringifiedProps = stringifyProps(props)
124+
const stringifiedProps = stringifyProps(props, element)
125125
const [stringifiedChildren, serverChildren] = this.stringifyNodes(element.children.flat(), element)
126126
const wrappedChildren = stringifiedChildren.length === 1 ? stringifiedChildren : `[${stringifiedChildren.join('')}]`
127127
return [

src/runtime/client-state.ts

Lines changed: 2 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import {findElementByPath} from './dom'
21
import {HashType} from '../jsx/VirtualElement'
32
import {ContextType} from '../compiler/template/state-usage'
43
import {AbstractState} from './abstract-state'
54
import {getStateListenerCleanupMap} from './state-listener'
5+
import {updateStateUsages} from './state-usage'
66

77
function cloneStateValue<V>(value: V): V {
88
if (['string', 'number', 'boolean'].includes(typeof value) || [null, undefined].includes(value)) {
@@ -16,11 +16,6 @@ function cloneStateValue<V>(value: V): V {
1616
}
1717

1818
export type StateChangeHandlerType = (...args: ([any, (value: any) => void] | HTMLElement)[]) => (() => void) | void
19-
type ClientContextType<T extends 'child' | 'prop'> = ContextType<T> & { makeString: (value: string) => string }
20-
21-
declare const stateUsages: Map<HashType, (...values: any[]) => string>
22-
declare const stateUsagesParameters: Map<HashType, State[]>
23-
declare const stateUsagesContexts: Map<HashType, ClientContextType<any>[]>
2419

2520
export class State<V = any> extends AbstractState<V> {
2621
private _listeners: (StateChangeHandlerType)[] = []
@@ -53,36 +48,7 @@ export class State<V = any> extends AbstractState<V> {
5348
cleanup()
5449
})
5550
this._value = cloneStateValue(value)
56-
stateStateUsagesMap[this._id]?.forEach(stateUsageId => {
57-
const contexts = stateUsagesContexts.get(stateUsageId)
58-
contexts.forEach(context => {
59-
let transform
60-
if (stateUsages.has(stateUsageId)) {
61-
transform = stateUsages.get(stateUsageId)
62-
transform = stateUsages.get(stateUsageId)
63-
} else {
64-
transform = value => String(value)
65-
}
66-
const transformParameters = stateUsagesParameters.get(stateUsageId)
67-
const target = findElementByPath(context.contextElement)
68-
const newString = context.makeString(
69-
transform(
70-
...transformParameters.map(state => state.valueOf())
71-
)
72-
)
73-
if (context.type === 'child') {
74-
if (target.childElementCount == 0) {
75-
target.innerText = newString
76-
} else {
77-
target.children[context.beforeChild].previousSibling.replaceWith(
78-
document.createTextNode(newString)
79-
)
80-
}
81-
} else if (context.type === 'prop') {
82-
// todo
83-
}
84-
})
85-
})
51+
updateStateUsages(this._id)
8652
if (executeListeners) {
8753
this._listeners.forEach(listener => stateListenerCleanupMap.set(listener, listener()))
8854
}
@@ -102,7 +68,6 @@ export class State<V = any> extends AbstractState<V> {
10268
}
10369

10470
const states = new Map()
105-
declare const stateStateUsagesMap: { [key: HashType]: HashType[] }
10671

10772
export function getClientState(id: HashType, value?: any) {
10873
if (!states.has(id))

src/runtime/state-usage.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import {type HashType} from '../jsx/VirtualElement'
2+
import {type State} from './client-state'
3+
import {findElementByPath} from './dom'
4+
import {type ContextType} from '../compiler/template/state-usage'
5+
import {type StringifiableType} from '../utils/stringify'
6+
7+
export type ClientContextType<T extends 'child' | 'prop'> = ContextType<T> & { makeString: (value: string) => string }
8+
9+
declare const stateUsages: Map<HashType, (...values: any[]) => string>
10+
declare const stateStateUsagesMap: { [key: HashType]: HashType[] }
11+
declare const stateUsagesParameters: Map<HashType, State[]>
12+
declare const stateUsagesContexts: Map<HashType, ClientContextType<any>[]>
13+
14+
export function stringifyStyleObject(object: { [p: string]: StringifiableType }) {
15+
return Object.entries(object)
16+
.filter(([name, value]) => value && value !== 0)
17+
.map(([name, value]) => `${name}:${value}`)
18+
.join(';')
19+
}
20+
21+
export function stringifyDomTokenList(array: StringifiableType[]) {
22+
return array.filter(v => v).join(' ').trim()
23+
}
24+
25+
export function updateStateUsages(id: HashType) {
26+
stateStateUsagesMap[id]?.forEach(stateUsageId => {
27+
const contexts = stateUsagesContexts.get(stateUsageId)
28+
contexts.forEach(context => {
29+
let transform
30+
if (stateUsages.has(stateUsageId)) {
31+
transform = stateUsages.get(stateUsageId)
32+
transform = stateUsages.get(stateUsageId)
33+
} else {
34+
transform = value => String(value)
35+
}
36+
const transformParameters = stateUsagesParameters.get(stateUsageId)
37+
const target = findElementByPath(context.contextElement)
38+
const transformed = transform(
39+
...transformParameters.map(state => state.valueOf())
40+
)
41+
if (context.type === 'child') {
42+
const newString = context.makeString(transformed)
43+
if (target.childElementCount == 0) {
44+
target.innerText = newString
45+
} else {
46+
target.children[context.beforeChild].previousSibling.replaceWith(
47+
document.createTextNode(newString)
48+
)
49+
}
50+
} else if (context.type === 'prop') {
51+
let newString
52+
if (context.prop === 'style' && typeof transformed === 'object') {
53+
newString = stringifyStyleObject(transformed)
54+
} else if (['class', 'id'].includes(context.prop) && Array.isArray(transformed)) {
55+
newString = stringifyDomTokenList(transform)
56+
} else {
57+
newString = String(transform)
58+
}
59+
target.setAttribute(context.prop, newString)
60+
}
61+
})
62+
})
63+
}

src/utils/stringify.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import {isArray} from './array'
33
import {mapObject, mapObjectToArray} from './iterate-object'
44
import {isState} from '../state/state'
55
import {isStateUsage} from '../state/state-usage'
6+
import {includeStateUsage} from '../compiler/template/state-usage'
7+
import {type VirtualElementInterface} from '../jsx/cherry-soda'
8+
import {type VirtualElement} from '../jsx/VirtualElement'
69

710
interface HasToStringInterface {
811
toString: () => string
@@ -22,10 +25,18 @@ export default function stringifyValue(value: StringifiableType): string {
2225
return value.toString()
2326
}
2427

25-
export function stringifyProps(props: { [p: string]: any }) {
26-
return mapObjectToArray(props, ([key, value]) =>
27-
isState(value) || isStateUsage(value)
28-
? '#' + ((!isStateUsage(value) ? value.use() : value).$$stateId.serialize()) // todo
29-
: key + JSON.stringify(stringifyValue(value))
30-
)
28+
export function stringifyProps(props: { [p: string]: any }, contextElement?: VirtualElementInterface<any>) {
29+
return mapObjectToArray(props, ([key, value]) => {
30+
if (isState(value) || isStateUsage(value)) {
31+
const stateUsage = !isStateUsage(value) ? value.use() : value
32+
includeStateUsage(stateUsage, {
33+
type: 'prop',
34+
contextElement: contextElement as VirtualElement,
35+
prop: key
36+
})
37+
return '#' + stateUsage.$$stateId.serialize() // todo
38+
} else {
39+
return key + JSON.stringify(stringifyValue(value))
40+
}
41+
})
3142
}

0 commit comments

Comments
 (0)