Skip to content

Commit

Permalink
fix-in-element-logic (#39)
Browse files Browse the repository at this point in the history
+

+

+

+

+
  • Loading branch information
lifeart authored Jan 17, 2024
1 parent 4a1ac17 commit d000adc
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 45 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

`GXT` is a cutting-edge, compilable runtime environment designed as `glimmer-vm` alternative, showcasing the power and flexibility of modern web component development. This repo includes a live example of how `GXT` can be used in real-world applications, providing developers with a practical and interactive experience. Explore our [sample](https://g-next.netlify.app/) at netlify.


## Benefits

- 🔥 Hot Module Replacement (Reloading)
Expand Down
16 changes: 10 additions & 6 deletions plugins/converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,12 +206,16 @@ export function convert(seenNodes: Set<ASTv1.Node>) {
const inverse = elseChildElements?.map((el) => ToJSType(el)) ?? null;

if (name === 'in-element') {
return `$:${SYMBOLS.$_inElement}(${ToJSType(
node.params[0],
)}, $:() => [${serializeChildren(
children as unknown as [string | HBSNode | HBSControlExpression],
'this',
)}], this)`;
return {
type: 'in-element',
isControl: true,
condition: ToJSType(node.params[0]) as string,
blockParams: [],
children: children,
inverse: [],
isSync: true,
key: '',
} as HBSControlExpression;
} else if (name === 'unless') {
return {
type: 'if',
Expand Down
12 changes: 9 additions & 3 deletions plugins/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { SYMBOLS } from './symbols';
import { flags } from './flags';

export type HBSControlExpression = {
type: 'each' | 'if';
type: 'each' | 'if' | 'in-element';
isControl: true;
condition: string;
blockParams: string[];
Expand Down Expand Up @@ -214,8 +214,14 @@ export function serializeNode(
}

const newCtxName = nextCtxName();

if (key === '@each') {
if (key === '@in-element') {
return `$:${
SYMBOLS.$_inElement
}(${arrayName}, $:(${newCtxName}) => [${serializeChildren(
childs as unknown as [string | HBSNode | HBSControlExpression],
newCtxName,
)}], ${ctxName})`;
} else if (key === '@each') {
if (paramNames.length === 1) {
// @todo - add compiler param to mark there is no index here
// likely we need to import $getIndex function and pass it as a param for each constructor
Expand Down
60 changes: 52 additions & 8 deletions src/tests/integration/in-element-test.gts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { module, test, skip } from 'qunit';
import { module, test } from 'qunit';
import { allSettled, render } from '@lifeart/gxt/test-utils';
import { getDocument } from '@/utils/dom-api';
import { cell } from '@/utils/reactive';
Expand All @@ -8,14 +8,19 @@ module('Integration | InternalComponent | in-elment', function () {
const elementRef = () => {
return getDocument().getElementById('42');
};
const isMainRootRendered = cell(false);
await render(
<template>
<div id='42'></div>
{{#in-element elementRef}}
<div data-test-in-element>t</div>
{{/in-element}}
{{#if isMainRootRendered}}
{{#in-element elementRef}}
<div data-test-in-element>t</div>
{{/in-element}}
{{/if}}
</template>,
);
isMainRootRendered.value = true;
await allSettled();
assert.dom('[id="42"]').hasText('t');
});
test('support strings as args and element as ref', async function (assert) {
Expand All @@ -42,23 +47,29 @@ module('Integration | InternalComponent | in-elment', function () {
);
assert.dom(elementRef.value).hasText('t');
});
skip('cell values remain reactive in in-element', async function (assert) {
test('cell values remain reactive in in-element', async function (assert) {
const elementRef = () => {
return getDocument().getElementById('42');
};
const sideNode = () => {
return getDocument().getElementById('43');
};
const isMainRootRendered = cell(false);
const value = cell('t');
await render(
<template>
<div id='42'></div>
{{#in-element elementRef}}
<div data-test-in-element>{{value}}</div>
{{/in-element}}
{{#if isMainRootRendered}}
{{#in-element elementRef}}
<div data-test-in-element>{{value}}</div>
{{/in-element}}
{{/if}}
<div id='43'>{{value}}</div>
</template>,
);

isMainRootRendered.value = true;
await allSettled();
assert
.dom(elementRef())
.hasText(value.value, 'values should be rendered inside in-element');
Expand All @@ -77,4 +88,37 @@ module('Integration | InternalComponent | in-elment', function () {
.dom(elementRef())
.hasText(value.value, 'values should be reactive inside in-element');
});
test('it works inside conditions', async function (assert) {
const elementRef = () => {
return getDocument().getElementById('42');
};
const isExpended = cell(false);
const value = cell('t');
await render(
<template>
<div id='42'></div>
{{#if isExpended}}
{{#in-element elementRef}}
<div data-test-in-element>{{value}}</div>
{{/in-element}}
{{/if}}
</template>,
);
assert
.dom('[data-test-in-element]')
.doesNotExist('should not render, because if is hidden');
assert.dom(elementRef()).exists();
isExpended.value = true;
await allSettled();
assert
.dom('[data-test-in-element]')
.exists('should render, because if is visible');
isExpended.value = false;

await allSettled();
await allSettled();
assert
.dom('[data-test-in-element]')
.doesNotExist('should not render, because if is hidden');
});
});
19 changes: 15 additions & 4 deletions src/utils/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,12 @@ export function addChild(
} else if (isTagLike(child)) {
api.append(element, cellToText(child, destructors), index);
} else if (isFn(child)) {
addChild(element, resolveRenderable(child), destructors, index);
addChild(
element,
resolveRenderable(child, `element.child[${index}]`),
destructors,
index,
);
} else {
// renderComponent case
api.append(element, child, index);
Expand Down Expand Up @@ -344,7 +349,10 @@ function _DOM(
} else {
const formulas = classNameModifiers.map((modifier) => {
if (isFn(modifier)) {
return formula(() => deepFnValue(modifier), 'functional modifier');
return formula(
() => deepFnValue(modifier),
'functional modifier for className',
);
} else {
return modifier;
}
Expand Down Expand Up @@ -393,10 +401,13 @@ export function $_inElement(
appendRef = elementRef;
}
const destructors: Destructors = [];
roots(this).forEach((child, index) => {
roots(ctx).forEach((child, index) => {
addChild(appendRef, child, destructors, index);
});
associateDestroyable(this, destructors);
destructors.push(() => {
appendRef.innerHTML = '';
});
associateDestroyable(ctx, destructors);
return $_fin([], {}, false, this);
} as unknown as Component<any>,
{},
Expand Down
4 changes: 2 additions & 2 deletions src/utils/if.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ export function ifCondition(
};
const originalCell = cell;
if (isFn(originalCell)) {
cell = formula(() => deepFnValue(originalCell));
cell = formula(() => deepFnValue(originalCell), 'if-condition-wrapper-fn');
} else if (isPrimitive(originalCell)) {
cell = formula(() => originalCell);
cell = formula(() => originalCell, 'if-condition-primitive-wrapper');
}
let runNumber = 0;
let throwedError: Error | null = null;
Expand Down
4 changes: 2 additions & 2 deletions src/utils/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,9 @@ class BasicListComponent<T extends { id: number }> {
if (!isTagLike(tag)) {
if (Array.isArray(tag)) {
console.warn('iterator for @each should be a cell');
tag = new Cell(tag);
tag = new Cell(tag, 'list tag');
} else if (isFn(originalTag)) {
tag = formula(() => deepFnValue(originalTag));
tag = formula(() => deepFnValue(originalTag), 'list tag');
}
}
this.tag = tag;
Expand Down
41 changes: 22 additions & 19 deletions src/utils/reactive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export const relatedTags: WeakMap<Cell, Set<MergedCell>> = new WeakMap();

export const DEBUG_MERGED_CELLS = new Set<MergedCell>();
export const DEBUG_CELLS = new Set<Cell>();
var currentTracker: Set<Cell> | null = null;
let _isRendering = false;

export function getCells() {
return Array.from(DEBUG_CELLS);
Expand Down Expand Up @@ -77,9 +79,6 @@ export function tracked(
// we have only 2 types of cells
export type AnyCell = Cell | MergedCell;

let currentTracker: Set<Cell> | null = null;
let _isRendering = false;

export function isRendering() {
return _isRendering;
}
Expand Down Expand Up @@ -159,7 +158,7 @@ function bindAllCellsToTag(cells: Set<Cell>, tag: MergedCell) {
export class MergedCell {
fn: Fn | Function;
declare toHTML: () => string;
isConst = false;
isConst: boolean = false;
isDestroyed = false;
[Symbol.toPrimitive]() {
return this.value;
Expand Down Expand Up @@ -190,23 +189,20 @@ export class MergedCell {
get value() {
if (this.isDestroyed) {
return;
} else if (this.isConst) {
}

if (this.isConst || !_isRendering || currentTracker !== null) {
return this.fn();
} else if (null === currentTracker && _isRendering) {
}

try {
currentTracker = tracker();
try {
return this.fn();
} finally {
if (currentTracker.size > 0) {
bindAllCellsToTag(currentTracker, this);
} else {
this.isConst = true;
}
this.relatedCells = currentTracker;
currentTracker = null;
}
} else {
return this.fn();
} finally {
bindAllCellsToTag(currentTracker!, this);
this.isConst = currentTracker!.size === 0;
this.relatedCells = currentTracker;
currentTracker = null;
}
}
}
Expand Down Expand Up @@ -288,7 +284,7 @@ export function deepFnValue(fn: Function | Fn) {
if (isFn(cell)) {
return deepFnValue(cell);
} else if (typeof cell === 'object' && cell !== null && isTagLike(cell)) {
return deepFnValue(() => cell.value);
return cell.value;
} else {
return cell;
}
Expand All @@ -297,3 +293,10 @@ export function deepFnValue(fn: Function | Fn) {
export function cell<T>(value: T, debugName?: string) {
return new Cell(value, debugName);
}

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

0 comments on commit d000adc

Please sign in to comment.