From e43a4899c5b5a6ca4783809c1b189408c4441f88 Mon Sep 17 00:00:00 2001 From: Alex Kanunnikov Date: Fri, 3 May 2024 20:44:36 +0300 Subject: [PATCH 1/2] style binding --- plugins/converter.test.ts | 4 ++ plugins/converter.ts | 23 ++++++- plugins/symbols.ts | 1 + plugins/test.ts | 28 ++++---- src/components/pages/PageOne.gts | 31 +++++++-- src/tests/integration/Button-test.gts | 2 +- src/tests/integration/fn-test.gts | 2 +- .../integration/functional-component-test.gts | 66 ++++++++++++++++++ src/tests/integration/style-test.gts | 69 +++++++++++++++++++ src/tests/utils.ts | 3 +- src/utils/dom.ts | 15 +++- src/utils/reactive.ts | 15 ++-- 12 files changed, 229 insertions(+), 30 deletions(-) create mode 100644 src/tests/integration/functional-component-test.gts create mode 100644 src/tests/integration/style-test.gts diff --git a/plugins/converter.test.ts b/plugins/converter.test.ts index 3579b51a..a1b6f335 100644 --- a/plugins/converter.test.ts +++ b/plugins/converter.test.ts @@ -494,6 +494,10 @@ describe.each([ expect($t(`{{true}}`)).toEqual(true); expect($t(`{{false}}`)).toEqual(false); }); + test('support string literals', () => { + expect($t(`{{'true'}}`)).toEqual('"true"'); + expect($t(`{{'false'}}`)).toEqual('"false"'); + }); test('support null literals', () => { expect($t(`{{null}}`)).toEqual(null); }); diff --git a/plugins/converter.ts b/plugins/converter.ts index 9530a220..5d5f1f12 100644 --- a/plugins/converter.ts +++ b/plugins/converter.ts @@ -233,6 +233,9 @@ export function convert( } else if (node.path.type === 'SubExpression') { return `${wrap ? `$:() => ` : ''}${ToJSType(node.path)}`; } + if (node.path.type === 'StringLiteral') { + return escapeString(node.path.value); + } return null; } // replacing builtin helpers @@ -475,6 +478,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, @@ -498,7 +517,7 @@ export function convert( rawValue, ]; }), - events: element.modifiers + events: [...styleEvents,...element.modifiers .map((mod) => { if (mod.path.type !== 'PathExpression') { return null; @@ -540,7 +559,7 @@ export function convert( ]; } }) - .filter((el) => el !== null), + .filter((el) => el !== null)], children: children, }; if (children.length === 1 && typeof children[0] === 'string') { diff --git a/plugins/symbols.ts b/plugins/symbols.ts index cd2e0134..56da6aaf 100644 --- a/plugins/symbols.ts +++ b/plugins/symbols.ts @@ -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', diff --git a/plugins/test.ts b/plugins/test.ts index 53e27b37..a3899836 100644 --- a/plugins/test.ts +++ b/plugins/test.ts @@ -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} })()`; } diff --git a/src/components/pages/PageOne.gts b/src/components/pages/PageOne.gts index ea20beb6..3ac26338 100644 --- a/src/components/pages/PageOne.gts +++ b/src/components/pages/PageOne.gts @@ -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 { - ; } diff --git a/src/tests/integration/Button-test.gts b/src/tests/integration/Button-test.gts index 2704eb04..f097d43c 100644 --- a/src/tests/integration/Button-test.gts +++ b/src/tests/integration/Button-test.gts @@ -24,7 +24,7 @@ module('Integration | Component | Button', function () { , ); - click('[data-test-button]'); + await click('[data-test-button]'); }); test('has default type', async function (assert) { diff --git a/src/tests/integration/fn-test.gts b/src/tests/integration/fn-test.gts index d5397253..22657acf 100644 --- a/src/tests/integration/fn-test.gts +++ b/src/tests/integration/fn-test.gts @@ -18,6 +18,6 @@ module('Integration | InternalHelper | fn', function () { , ); - click('[data-test-button]'); + await click('[data-test-button]'); }); }); diff --git a/src/tests/integration/functional-component-test.gts b/src/tests/integration/functional-component-test.gts new file mode 100644 index 00000000..4215f058 --- /dev/null +++ b/src/tests/integration/functional-component-test.gts @@ -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 ; + } + await render(); + assert.dom().hasText('123'); + }); + test('should render node', async function (assert) { + function HelloWolrd() { + return ; + } + await render(); + assert.dom('div').hasText('123'); + }); + test('support static args', async function (assert) { + function HelloWolrd() { + return ; + } + await render(); + assert.dom('div').hasText('123'); + }); + test('support static args from functional params', async function (assert) { + const HelloWolrd = ({ name }) => { + return ; + }; + await render(); + assert.dom('div').hasText('123'); + }); + test('support dynamic args from functional params', async function (assert) { + const value = cell('123'); + const HelloWolrd = ({ name }) => { + return ; + }; + await render(); + 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 ; + }; + await render(); + assert.dom('div').hasText('123'); + value.update('321'); + await rerender(); + assert.dom('div').hasText('321'); + }); +}); diff --git a/src/tests/integration/style-test.gts b/src/tests/integration/style-test.gts new file mode 100644 index 00000000..b3000218 --- /dev/null +++ b/src/tests/integration/style-test.gts @@ -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( + , + ); + assert.dom('div').hasStyle({ + color: 'rgb(0, 128, 0)', + }); + }); + test('works with dynamic binding', async function (assert) { + const color = cell('red'); + await render( + , + ); + 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'; + }; + + } + await render(); + 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( + , + ); + assert.dom('div').hasStyle({ + color: 'rgb(255, 0, 0)', + }); + color.update('blue'); + await rerender(); + assert.dom('div').hasStyle({ + color: 'rgb(0, 0, 255)', + }); + }); +}); diff --git a/src/tests/utils.ts b/src/tests/utils.ts index 9ee7a28e..a2806b6f 100644 --- a/src/tests/utils.ts +++ b/src/tests/utils.ts @@ -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); @@ -93,6 +93,7 @@ export function click(selector: string) { cancelable: true, }); element!.dispatchEvent(event); + await rerender(); } export function step(message: string) { diff --git a/src/utils/dom.ts b/src/utils/dom.ts index 93aa5b13..cc7a7283 100644 --- a/src/utils/dom.ts +++ b/src/utils/dom.ts @@ -81,6 +81,16 @@ const $_className = 'className'; let unstableWrapperId: number = 0; let ROOT: Component | 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(); @@ -712,7 +722,10 @@ function _component( } } // @ts-expect-error construct signature - const instance = new (comp as unknown as Component)(args, fw); + let instance = comp.prototype === undefined ? comp(args, fw) : new (comp as unknown as Component)(args, fw); + if (typeof instance === 'function') { + instance = new instance(args, fw); + } // todo - fix typings here if ($template in instance) { const result = ( diff --git a/src/utils/reactive.ts b/src/utils/reactive.ts index f2a7cebd..0bde51e8 100644 --- a/src/utils/reactive.ts +++ b/src/utils/reactive.ts @@ -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) } } } @@ -328,9 +329,9 @@ export function cell(value: T, debugName?: string) { export function inNewTrackingFrame(callback: () => void) { const existingTracker = currentTracker; - currentTracker = null; + setTracker(null); callback(); - currentTracker = existingTracker; + setTracker(existingTracker); } export function getTracker() { From bcd6293c02986ffd02abe224b4ff1c9d390abb3d Mon Sep 17 00:00:00 2001 From: Alex Kanunnikov Date: Sat, 4 May 2024 18:20:53 +0300 Subject: [PATCH 2/2] + --- plugins/converter.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/converter.ts b/plugins/converter.ts index 5d5f1f12..82013960 100644 --- a/plugins/converter.ts +++ b/plugins/converter.ts @@ -232,8 +232,7 @@ export function convert( return node.path.value; } else if (node.path.type === 'SubExpression') { return `${wrap ? `$:() => ` : ''}${ToJSType(node.path)}`; - } - if (node.path.type === 'StringLiteral') { + } else if (node.path.type === 'StringLiteral') { return escapeString(node.path.value); } return null;