Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature] style binding #120

Merged
merged 2 commits into from
May 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions plugins/converter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,10 @@ describe.each([
expect($t<ASTv1.MustacheStatement>(`{{true}}`)).toEqual(true);
expect($t<ASTv1.MustacheStatement>(`{{false}}`)).toEqual(false);
});
test('support string literals', () => {
expect($t<ASTv1.MustacheStatement>(`{{'true'}}`)).toEqual('"true"');
expect($t<ASTv1.MustacheStatement>(`{{'false'}}`)).toEqual('"false"');
});
test('support null literals', () => {
expect($t<ASTv1.MustacheStatement>(`{{null}}`)).toEqual(null);
});
Expand Down
22 changes: 20 additions & 2 deletions plugins/converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,8 @@ export function convert(
return node.path.value;
} else if (node.path.type === 'SubExpression') {
return `${wrap ? `$:() => ` : ''}${ToJSType(node.path)}`;
} else if (node.path.type === 'StringLiteral') {
return escapeString(node.path.value);
}
return null;
}
Expand Down Expand Up @@ -475,6 +477,22 @@ export function convert(
const children = resolvedChildren(element.children)
.map((el) => ToJSType(el))
.filter((el) => el !== null);

const rawStyleEvents = element.attributes.filter((attr) => {
return attr.name.startsWith('style.');
});
element.attributes = element.attributes.filter((attr) => {
return !rawStyleEvents.includes(attr);
});
const styleEvents = rawStyleEvents.map((attr) => {
const propertyName = attr.name.split('.').pop();
const value = attr.value.type === 'TextNode' ? escapeString(attr.value.chars) : ToJSType(attr.value);
const isPath = typeof value === 'string' ? value.includes('.') : false;
return [
EVENT_TYPE.ON_CREATED,
`$:function($v,$n){$n.style.setProperty('${propertyName}',$v);}.bind(null,${SYMBOLS.$_TO_VALUE}(${isPath?`$:()=>${value}`: value}))`,
];
});
const node = {
tag: element.tag,
selfClosing: element.selfClosing,
Expand All @@ -498,7 +516,7 @@ export function convert(
rawValue,
];
}),
events: element.modifiers
events: [...styleEvents,...element.modifiers
.map((mod) => {
if (mod.path.type !== 'PathExpression') {
return null;
Expand Down Expand Up @@ -540,7 +558,7 @@ export function convert(
];
}
})
.filter((el) => el !== null),
.filter((el) => el !== null)],
children: children,
};
if (children.length === 1 && typeof children[0] === 'string') {
Expand Down
1 change: 1 addition & 0 deletions plugins/symbols.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const SYMBOLS = {
DYNAMIC_COMPONENT: '$_dc',
$SLOTS_SYMBOL: '$SLOTS_SYMBOL',
$PROPS_SYMBOL: '$PROPS_SYMBOL',
$_TO_VALUE: '$_TO_VALUE',
$_GET_SLOTS: '$_GET_SLOTS',
$_GET_ARGS: '$_GET_ARGS',
$_GET_FW: '$_GET_FW',
Expand Down
28 changes: 16 additions & 12 deletions plugins/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,28 +158,32 @@ export function transform(
const hasFw = results.some((el) => el.includes('$fw'));
const hasSlots = results.some((el) => el.includes('$slots'));
const slotsResolution = `const $slots = ${SYMBOLS.$_GET_SLOTS}(this, arguments);`;
const maybeFw = `${hasFw ? `const $fw = ${SYMBOLS.$_GET_FW}(this, arguments);` : ''}`;
const maybeSlots = `${hasSlots ? slotsResolution : ''}`;
const declareRoots = `const roots = [${results.join(', ')}];`;
const declareReturn = `return ${SYMBOLS.FINALIZE_COMPONENT}(roots, ${finContext});`;

if (isTemplateTag) {
result = `function () {
${hasFw ? `const $fw = ${SYMBOLS.$_GET_FW}(this, arguments);` : ''}
${maybeFw}
${SYMBOLS.$_GET_ARGS}(this, arguments);
${hasSlots ? slotsResolution : ''}
const roots = [${results.join(', ')}];
return ${SYMBOLS.FINALIZE_COMPONENT}(roots, ${finContext});
${maybeSlots}
${declareRoots}
${declareReturn}
}`;
} else {
result = isClass
? `() => {
${hasSlots ? slotsResolution : ''}
${hasFw ? `const $fw = ${SYMBOLS.$_GET_FW}(this, arguments);` : ''}
const roots = [${results.join(', ')}];
return ${SYMBOLS.FINALIZE_COMPONENT}(roots, ${finContext});
${maybeSlots}
${maybeFw}
${declareRoots}
${declareReturn}
}`
: `(() => {
${hasSlots ? slotsResolution : ''}
${hasFw ? `const $fw = ${SYMBOLS.$_GET_FW}(this, arguments);` : ''}
const roots = [${results.join(', ')}];
return ${SYMBOLS.FINALIZE_COMPONENT}(roots, ${finContext});
${maybeSlots}
${maybeFw}
${declareRoots}
${declareReturn}
})()`;
}

Expand Down
31 changes: 26 additions & 5 deletions src/components/pages/PageOne.gts
Original file line number Diff line number Diff line change
@@ -1,11 +1,32 @@
import { Component } from '@lifeart/gxt';
import { Component, cell } from '@lifeart/gxt';
import { Smile } from './page-one/Smile';
import { Table } from './page-one/Table.gts';

export class PageOne extends Component {
<template>
function Controls() {
const color = cell('red');

const intervalId = setInterval(() => {
color.update(Math.random() > 0.5 ? 'red' : 'blue');
}, 1000);

function onDestroy() {
return () => {
console.log('destroying interval');
clearInterval(intervalId);
};
}

return <template>
<h1><q {{onDestroy}} style.background-color={{color}}>Compilers are the New
Frameworks</q>
- Tom Dale &copy;</h1>
</template>;
}

export function PageOne() {
return <template>
<div class='text-white p-3'>
<h1><q>Compilers are the New Frameworks</q> - Tom Dale &copy;</h1>
<Controls />
<br />

<div>Imagine a world where the robust, mature ecosystems of development
Expand Down Expand Up @@ -38,5 +59,5 @@ export class PageOne extends Component {
efficiency. Get ready to elevate your coding experience!</i>
<br /><br />
<a href='/pageTwo'>Go to page two <Smile /></a></div>
</template>
</template>;
}
2 changes: 1 addition & 1 deletion src/tests/integration/Button-test.gts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ module('Integration | Component | Button', function () {
<Button @onClick={{onClick}} data-test-button>DEMO</Button>
</template>,
);
click('[data-test-button]');
await click('[data-test-button]');
});

test('has default type', async function (assert) {
Expand Down
2 changes: 1 addition & 1 deletion src/tests/integration/fn-test.gts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ module('Integration | InternalHelper | fn', function () {
</template>,
);

click('[data-test-button]');
await click('[data-test-button]');
});
});
66 changes: 66 additions & 0 deletions src/tests/integration/functional-component-test.gts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { module, test } from 'qunit';
import { render, rerender } from '@lifeart/gxt/test-utils';
import { cell } from '@lifeart/gxt';

module('Integration | component | functional', function () {
test('should render text', async function (assert) {
function HelloWolrd() {
return <template>123</template>;
}
await render(<template><HelloWolrd /></template>);
assert.dom().hasText('123');
});
test('should render node', async function (assert) {
function HelloWolrd() {
return <template>
<div>123</div>
</template>;
}
await render(<template><HelloWolrd /></template>);
assert.dom('div').hasText('123');
});
test('support static args', async function (assert) {
function HelloWolrd() {
return <template>
<div>{{@name}}</div>
</template>;
}
await render(<template><HelloWolrd @name={{'123'}} /></template>);
assert.dom('div').hasText('123');
});
test('support static args from functional params', async function (assert) {
const HelloWolrd = ({ name }) => {
return <template>
<div>{{name}}</div>
</template>;
};
await render(<template><HelloWolrd @name={{'123'}} /></template>);
assert.dom('div').hasText('123');
});
test('support dynamic args from functional params', async function (assert) {
const value = cell('123');
const HelloWolrd = ({ name }) => {
return <template>
<div>{{name}}</div>
</template>;
};
await render(<template><HelloWolrd @name={{value}} /></template>);
assert.dom('div').hasText('123');
value.update('321');
await rerender();
assert.dom('div').hasText('321');
});
test('support dynamic args from functional params reference', async function (assert) {
const value = cell('123');
const HelloWolrd = (args) => {
return <template>
<div>{{args.name}}</div>
</template>;
};
await render(<template><HelloWolrd @name={{value.value}} /></template>);
assert.dom('div').hasText('123');
value.update('321');
await rerender();
assert.dom('div').hasText('321');
});
});
69 changes: 69 additions & 0 deletions src/tests/integration/style-test.gts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { module, test } from 'qunit';
import { render, rerender, click } from '@lifeart/gxt/test-utils';
import { cell, Component, tracked } from '@lifeart/gxt';

module('Integration | Interal | style', function () {
test('works with static style binding', async function (assert) {
await render(
<template>
<div style.color={{'green'}}>123</div>
</template>,
);
assert.dom('div').hasStyle({
color: 'rgb(0, 128, 0)',
});
});
test('works with dynamic binding', async function (assert) {
const color = cell('red');
await render(
<template>
<div style.color={{color}}>123</div>
</template>,
);
assert.dom('div').hasStyle({
color: 'rgb(255, 0, 0)',
});
color.update('blue');
await rerender();
assert.dom('div').hasStyle({
color: 'rgb(0, 0, 255)',
});
});
test('works with dynamic binding in class', async function (assert) {
class MyComponent {
@tracked color = 'red';
onClick = () => {
this.color = 'blue';
};
<template>
<div style.color={{this.color}}>123</div>
<button type='button' {{on 'click' this.onClick}}>change color</button>
</template>
}
await render(<template><MyComponent /></template>);
assert.dom('div').hasStyle({
color: 'rgb(255, 0, 0)',
});
await click('button');
assert.dom('div').hasStyle({
color: 'rgb(0, 0, 255)',
});
});
test('works with functions', async function (assert) {
const color = cell('red');
const getColor = () => color.value;
await render(
<template>
<div style.color={{getColor}}>123</div>
</template>,
);
assert.dom('div').hasStyle({
color: 'rgb(255, 0, 0)',
});
color.update('blue');
await rerender();
assert.dom('div').hasStyle({
color: 'rgb(0, 0, 255)',
});
});
});
3 changes: 2 additions & 1 deletion src/tests/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export async function rerender(timeout = 0) {
});
}

export function click(selector: string) {
export async function click(selector: string) {
const element = getDocument()
.getElementById('ember-testing')!
.querySelector(selector);
Expand All @@ -93,6 +93,7 @@ export function click(selector: string) {
cancelable: true,
});
element!.dispatchEvent(event);
await rerender();
}

export function step(message: string) {
Expand Down
15 changes: 14 additions & 1 deletion src/utils/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,16 @@ const $_className = 'className';
let unstableWrapperId: number = 0;
let ROOT: Component<any> | null = null;

export function $_TO_VALUE(reference: unknown) {
if (isPrimitive(reference)) {
return reference;
} else if (isTagLike(reference)) {
return reference;
} else {
return resolveRenderable(reference as Function);
}
}

export function $_componentHelper(params: any, hash: any) {
const componentFn = params.shift();

Expand Down Expand Up @@ -712,7 +722,10 @@ function _component(
}
}
// @ts-expect-error construct signature
const instance = new (comp as unknown as Component<any>)(args, fw);
let instance = comp.prototype === undefined ? comp(args, fw) : new (comp as unknown as Component<any>)(args, fw);
if (typeof instance === 'function') {
instance = new instance(args, fw);
}
// todo - fix typings here
if ($template in instance) {
const result = (
Expand Down
15 changes: 8 additions & 7 deletions src/utils/reactive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,14 +216,15 @@ export class MergedCell {
return this.fn();
}

let $tracker = tracker();
try {
currentTracker = tracker();
setTracker($tracker)
return this.fn();
} finally {
bindAllCellsToTag(currentTracker!, this);
this.isConst = currentTracker!.size === 0;
this.relatedCells = currentTracker;
currentTracker = null;
bindAllCellsToTag($tracker, this);
this.isConst = $tracker.size === 0;
this.relatedCells = $tracker;
setTracker(null)
}
}
}
Expand Down Expand Up @@ -328,9 +329,9 @@ export function cell<T>(value: T, debugName?: string) {

export function inNewTrackingFrame(callback: () => void) {
const existingTracker = currentTracker;
currentTracker = null;
setTracker(null);
callback();
currentTracker = existingTracker;
setTracker(existingTracker);
}

export function getTracker() {
Expand Down
Loading