chore: split two-way binding logic into another file
All checks were successful
Publish to npm / quality (push) Successful in 20s
Publish to npm / publish (push) Successful in 24s

This commit is contained in:
Astrian Zheng 2025-05-21 14:36:30 +10:00
parent 9ea14fc2b9
commit 15a63968ea
Signed by: Astrian
SSH Key Fingerprint: SHA256:rVnhx3DAKjujCwWE13aDl7uV6+9U1MvydLkNRXJrBiA
3 changed files with 47 additions and 34 deletions

View File

@ -8,7 +8,9 @@
"build": "tsc && rollup -c && npm run cleanup-intermediate", "build": "tsc && rollup -c && npm run cleanup-intermediate",
"prepare": "npm run build", "prepare": "npm run build",
"cleanup-intermediate": "rimraf dist/main.js dist/types", "cleanup-intermediate": "rimraf dist/main.js dist/types",
"quality-check": "biome ci ." "quality-check": "biome ci .",
"qc": "npm run quality-check",
"lint": "biome format . --write"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -156,7 +156,6 @@ export default (options: ComponentOptions) => {
setupArrowFunctionHandler: this._setupArrowFunctionHandler.bind(this), setupArrowFunctionHandler: this._setupArrowFunctionHandler.bind(this),
setupFunctionCallHandler: this._setupFunctionCallHandler.bind(this), setupFunctionCallHandler: this._setupFunctionCallHandler.bind(this),
setupExpressionHandler: this._setupExpressionHandler.bind(this), setupExpressionHandler: this._setupExpressionHandler.bind(this),
setupTwoWayBinding: this._setupTwoWayBinding.bind(this),
setupConditionRendering: this._setupConditionRendering.bind(this), setupConditionRendering: this._setupConditionRendering.bind(this),
setupListRendering: this._setupListRendering.bind(this), setupListRendering: this._setupListRendering.bind(this),
stateToElementsMap: this._stateToElementsMap, stateToElementsMap: this._stateToElementsMap,
@ -168,6 +167,7 @@ export default (options: ComponentOptions) => {
typeof (this as Record<string, unknown>)[name] === 'function' && typeof (this as Record<string, unknown>)[name] === 'function' &&
name !== 'constructor', name !== 'constructor',
), ),
stateListeners: this._statesListeners,
}) })
} }
@ -200,36 +200,6 @@ export default (options: ComponentOptions) => {
} }
} }
// Handle two-way data binding (%connect macro)
private _setupTwoWayBinding(element: Element, expr: string) {
// Get the initial value
const value = this._getNestedState(expr)
// Set the initial value
if (value !== undefined)
element.setAttribute('data-laterano-connect', String(value))
else
console.error(
`State \`${expr}\` not found in the component state. Although Laterano will try to work with it, it may has potentially unexpected behavior.`,
)
// Add event listener for input events
element.addEventListener('input', (event: Event) => {
const target = event.target as HTMLInputElement
const newValue = target.value
// Update the state
this.setState(expr, newValue)
})
// Add event listener for state changes
this._statesListeners[expr] = (newValue: unknown) => {
if (element instanceof HTMLInputElement)
element.value = newValue as string
else element.setAttribute('data-laterano-connect', String(newValue))
}
}
// Handle condition rendering (%if macro) // Handle condition rendering (%if macro)
private _setupConditionRendering(element: Element, expr: string) { private _setupConditionRendering(element: Element, expr: string) {
const placeholder = document.createComment(` %if: ${expr} `) const placeholder = document.createComment(` %if: ${expr} `)

View File

@ -24,7 +24,6 @@ export default function processTemplateMacros(
eventName: string, eventName: string,
handlerValue: string, handlerValue: string,
) => void ) => void
setupTwoWayBinding: (element: Element, expr: string) => void
setupConditionRendering: (element: Element, expr: string) => void setupConditionRendering: (element: Element, expr: string) => void
setupListRendering: (element: Element, expr: string) => void setupListRendering: (element: Element, expr: string) => void
stateToElementsMap: Record<string, Set<HTMLElement>> stateToElementsMap: Record<string, Set<HTMLElement>>
@ -34,6 +33,7 @@ export default function processTemplateMacros(
originalContent: string originalContent: string
}[] }[]
availableFuncs: string[] availableFuncs: string[]
stateListeners: Record<string, (newValue: unknown) => void>
}, },
) { ) {
/* /*
@ -194,7 +194,11 @@ export default function processTemplateMacros(
// Handle different types of macros // Handle different types of macros
if (macroName === 'connect') if (macroName === 'connect')
// Handle state connection: %connect="stateName" // Handle state connection: %connect="stateName"
options.setupTwoWayBinding(currentElementNode, expr) setupTwoWayBinding(currentElementNode, expr, {
getNestedState: context.getState.bind(context),
setState: context.setState.bind(context),
statesListeners: options.stateListeners,
})
else if (macroName === 'if') { else if (macroName === 'if') {
ifDirectivesToProcess.push({ element: currentElementNode, expr }) ifDirectivesToProcess.push({ element: currentElementNode, expr })
} else if (macroName === 'for') } else if (macroName === 'for')
@ -213,3 +217,40 @@ export default function processTemplateMacros(
options.setupConditionRendering(ifElement, expr) options.setupConditionRendering(ifElement, expr)
} }
} }
// Handle two-way data binding (%connect macro)
function setupTwoWayBinding(
element: Element,
expr: string,
ops: {
getNestedState: (path: string) => unknown
setState: (path: string, value: unknown) => void
statesListeners: Record<string, (newValue: unknown) => void>
},
) {
// Get the initial value
const value = ops.getNestedState(expr)
// Set the initial value
if (value !== undefined)
element.setAttribute('data-laterano-connect', String(value))
else
console.error(
`State \`${expr}\` not found in the component state. Although Laterano will try to work with it, it may has potentially unexpected behavior.`,
)
// Add event listener for input events
element.addEventListener('input', (event: Event) => {
const target = event.target as HTMLInputElement
const newValue = target.value
// Update the state
ops.setState(expr, newValue)
})
// Add event listener for state changes
ops.statesListeners[expr] = (newValue: unknown) => {
if (element instanceof HTMLInputElement) element.value = newValue as string
else element.setAttribute('data-laterano-connect', String(newValue))
}
}