Skip to content

Commit

Permalink
Merge pull request #21 from beforesemicolon/simplified-and-improved-t…
Browse files Browse the repository at this point in the history
…racking-and-context-system

Simplified and improved tracking and context system
  • Loading branch information
ECorreia45 committed Feb 5, 2022
2 parents 1e00886 + 462d4bb commit 1495813
Show file tree
Hide file tree
Showing 62 changed files with 1,739 additions and 1,113 deletions.
1 change: 1 addition & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ coverage
examples
jest.config.js
.github
**.js.map
12 changes: 6 additions & 6 deletions dist/cwco.min.js

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions dist/cwco.min.js.map

Large diffs are not rendered by default.

1,023 changes: 607 additions & 416 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "cwco",
"version": "1.4.8",
"description": "Contextful Web Component Library",
"description": "Powerful and Fast Web Component Library with a Simple API",
"main": "dist/index.js",
"scripts": {
"test": "jest",
Expand Down Expand Up @@ -32,11 +32,11 @@
"devDependencies": {
"@types/jest": "^27.0.3",
"@types/jsdom": "^16.2.13",
"esbuild": "^0.14.2",
"esbuild": "^0.14.18",
"jest": "^27.4.4",
"jest-environment-jsdom": "^27.4.4",
"nodemon": "^2.0.13",
"ts-jest": "^27.1.1",
"typescript": "^4.5.3"
"typescript": "^4.5.5"
}
}
9 changes: 4 additions & 5 deletions src/client.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
// cwco, copyright (c) by Elson Correia
// cwco, copyright (c) by Elson Correia / Before Semicolon
// Distributed under an MIT license: https://github.com/beforesemicolon/cwco/blob/master/LICENSE

import {WebComponent} from './web-component';
import {Directive} from './directive';
import {ContextProviderComponent} from './context-provider-component';
import {WebComponent} from './core/web-component';
import {Directive} from './core/directive';
import {ContextProviderComponent} from './core/context-provider-component';
import {html} from "./utils/html";

// @ts-ignore
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {ContextProviderComponent} from "./context-provider-component";
import {WebComponent} from "./web-component";
import {ShadowRootModeExtended} from "./enums/ShadowRootModeExtended.enum";
import {ShadowRootModeExtended} from "../enums/ShadowRootModeExtended.enum";

describe('ContextProviderComponent', () => {
describe('slot', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {ShadowRootModeExtended} from "./enums/ShadowRootModeExtended.enum";
import {ShadowRootModeExtended} from "../enums/ShadowRootModeExtended.enum";
import {WebComponent} from "./web-component";
import {CWCO} from "./cwco";
import {CWCO} from "../cwco";

/**
* a special WebComponent that handles slot tag differently allowing for render template right into HTML files
Expand Down
6 changes: 3 additions & 3 deletions src/directive.ts → src/core/directive.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {directiveRegistry} from "./directives/registry";
import {directiveRegistry} from "../directives/registry";
import {$} from "./metadata";
import {defineNodeContextMetadata} from "./utils/define-node-context-metadata";
import {CWCO} from "./cwco";
import {defineNodeContextMetadata} from "../tracker/utils/define-node-context-metadata";
import {CWCO} from "../cwco";

export class Directive implements CWCO.Directive {
constructor(component: CWCO.WebComponent) {
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import {proxify} from './proxify';
import boolAttr from './boolean-attributes.json';
import {directives} from "../directives";
import {jsonParse} from "./json-parse";
import {proxify} from '../../utils/proxify';
import boolAttr from '../boolean-attributes.json';
import {directives} from "../../directives";
import {jsonParse} from "../../utils/json-parse";
import {$} from "../metadata";
import {CWCO} from "../cwco";
import {isPrimitive} from "./is-primitive";
import {CWCO} from "../../cwco";
import {isPrimitive} from "../../utils/is-primitive";

export function setComponentPropertiesFromObservedAttributes(
comp: CWCO.WebComponent,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {turnCamelToKebabCasing} from "./turn-camel-to-kebab-casing";
import {directives} from "../directives";
import {proxify} from "./proxify";
import {CWCO} from "../cwco";
import {isPrimitive} from "./is-primitive";
import {turnCamelToKebabCasing} from "../../utils/turn-camel-to-kebab-casing";
import {directives} from "../../directives";
import {proxify} from "../../utils/proxify";
import {CWCO} from "../../cwco";
import {isPrimitive} from "../../utils/is-primitive";

export function setupComponentPropertiesForAutoUpdate(comp: CWCO.WebComponent, onUpdate: CWCO.onUpdateCallback): string[] {
const properties: string[] = [];
Expand Down
111 changes: 87 additions & 24 deletions src/web-component.spec.ts → src/core/web-component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {WebComponent} from './web-component';
import {ShadowRootModeExtended} from "./enums/ShadowRootModeExtended.enum";
import {ShadowRootModeExtended} from "../enums/ShadowRootModeExtended.enum";

describe('WebComponent', () => {

Expand Down Expand Up @@ -800,27 +800,27 @@ describe('WebComponent', () => {
s.data = 12;

expect(updateSpy).toHaveBeenCalledWith(12);
updateSpy.mockClear()

s.data = '{"x": 12}';

expect(updateSpy).toHaveBeenCalledWith({x: 12});
updateSpy.mockClear()

s.data = {x: 12};

expect(updateSpy).toHaveBeenCalledWith({x: 12});
updateSpy.mockClear()

s.data = new Set([12]);

expect(updateSpy).toHaveBeenCalledWith(expect.any(Set));
updateSpy.mockClear()

s.data = () => 12

expect(updateSpy).toHaveBeenCalledWith(expect.any(Function));
updateSpy.mockClear()
// updateSpy.mockClear()
//
// s.data = '{"x": 12}';
//
// expect(updateSpy).toHaveBeenCalledWith({x: 12});
// updateSpy.mockClear()
//
// s.data = {x: 12};
//
// expect(updateSpy).toHaveBeenCalledWith({x: 12});
// updateSpy.mockClear()
//
// s.data = new Set([12]);
//
// expect(updateSpy).toHaveBeenCalledWith(expect.any(Set));
// updateSpy.mockClear()
//
// s.data = () => 12
//
// expect(updateSpy).toHaveBeenCalledWith(expect.any(Function));
// updateSpy.mockClear()
});
})

Expand Down Expand Up @@ -906,7 +906,7 @@ describe('WebComponent', () => {
title: 'Updated Text App'
});

expect(target?.root?.innerHTML).toBe('Text App');
expect(target?.root?.innerHTML).toBe('');

// should update the DOM to grab new context and data
app.root?.appendChild(target);
Expand Down Expand Up @@ -1629,7 +1629,70 @@ describe('WebComponent', () => {
expect(s.root?.innerHTML).toBe('<p>2</p><p>4</p><p>6</p><p>2</p><p>4</p><p>6</p>')
});

it.todo('should handle nested component repeats')
it('should handle nested component repeats', () => {
class SmallR extends WebComponent {
static observedAttributes = ['value'];

get template() {
return '{value}';
}
}

class RepeatF extends WebComponent {
static observedAttributes = ['obj'];
obj = {}

get template() {
return '<div name="{attr.name}" repeat="(obj.observedAttrs || []) as attr">' +
'<small-r if="attr.type === 2" value="{attr.value}"></small-r>' +
'</div>';
}
}

class ContainerP extends WebComponent {
val = {
observedAttrs: [
{name: 'A', type: 2, value: 'a'},
{name: 'B', type: 1, value: 'b'},
{name: 'C', type: 2, value: 'c'},
{name: 'D', type: 2, value: 'd'},
{name: 'E', type: 1, value: 'e'},
]
};

get template() {
return '<repeat-f obj="{val}"></repeat-f>';
}
}

SmallR.register();
RepeatF.register();
ContainerP.register();

const p = new ContainerP();

document.body.appendChild(p);

const r = p.root?.children[0] as WebComponent

expect(r.root?.innerHTML).toBe(
'<div name="A"><small-r value="a"></small-r></div>' +
'<div name="B"><!-- if: false --></div>' +
'<div name="C"><small-r value="c"></small-r></div>' +
'<div name="D"><small-r value="d"></small-r></div>' +
'<div name="E"><!-- if: false --></div>');

p.val = {
observedAttrs: [
{name: 'C', type: 2, value: 'c'},
{name: 'E', type: 1, value: 'e'},
]
};

expect(r.root?.innerHTML).toBe(
'<div name="C"><small-r value="c"></small-r></div>' +
'<div name="E"><!-- if: false --></div>')
})
});

describe('should allow mix of directives', () => {
Expand Down
79 changes: 37 additions & 42 deletions src/web-component.ts → src/core/web-component.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
// simply importing directive here will automatically register them and make them available for
// anything later on
import './directives';
import booleanAttr from './utils/boolean-attributes.json';
import '../directives';
import booleanAttr from './boolean-attributes.json';
import {$} from "./metadata";
import {parse} from './utils/parse';
import {parse} from '../parser/parse';
import {setComponentPropertiesFromObservedAttributes} from './utils/set-component-properties-from-observed-attributes';
import {setupComponentPropertiesForAutoUpdate} from './utils/setup-component-properties-for-auto-update';
import {turnCamelToKebabCasing} from './utils/turn-camel-to-kebab-casing';
import {turnKebabToCamelCasing} from './utils/turn-kebab-to-camel-casing';
import {turnCamelToKebabCasing} from '../utils/turn-camel-to-kebab-casing';
import {turnKebabToCamelCasing} from '../utils/turn-kebab-to-camel-casing';
import {getStyleString} from './utils/get-style-string';
import {ShadowRootModeExtended} from "./enums/ShadowRootModeExtended.enum";
import {trackNode} from "./utils/track-node";
import {jsonParse} from "./utils/json-parse";
import {defineNodeContextMetadata} from "./utils/define-node-context-metadata";
import {resolveHtmlEntities} from "./utils/resolve-html-entities";
import {CWCO} from "./cwco";
import {ShadowRootModeExtended} from "../enums/ShadowRootModeExtended.enum";
import {jsonParse} from "../utils/json-parse";
import {resolveHtmlEntities} from "../utils/resolve-html-entities";
import {CWCO} from "../cwco";
import {NodeTrack} from "../tracker/node-track";
import {trackNodeTree} from "../tracker/track-node-tree";

/**
* a extension on the native web component API to simplify and automate most of the pain points
Expand All @@ -34,18 +34,15 @@ export class WebComponent extends HTMLElement implements CWCO.WebComponent {

let {mode, observedAttributes, delegatesFocus} = this.constructor as CWCO.WebComponentConstructor;

if (!$.has(this)) {
$.set(this, {})
}

const selfTrack = new NodeTrack(this, this)
const meta = $.get(this);

meta.root = this;
meta.mounted = false;
meta.parsed = false;
meta.clearAttr = false;
meta.externalNodes = []; // nodes moved outside of the component that needs to be updated on ctx change
meta.tracks = new Map();
meta.selfTrack = selfTrack;
meta.externalNodes = []; // nodes moved outside the component that needs to be updated on ctx change
meta.unsubscribeCtx = () => {};
meta.attrPropsMap = observedAttributes.reduce((map, attr) => ({
...map,
Expand Down Expand Up @@ -181,37 +178,37 @@ export class WebComponent extends HTMLElement implements CWCO.WebComponent {
}

updateContext(ctx: CWCO.ObjectLiteral) {
$.get(this).updateContext(ctx);
const {oldCtx, newCtx} = $.get(this).updateContext(ctx);

$.get(this).externalNodes.forEach((el: HTMLElement) => {
$.get(el).updateContext(ctx);
$.get(this).selfTrack.childNodeTracks.forEach((t: NodeTrack) => {
t.updateNode(true);
})

this.onUpdate('$context', oldCtx, newCtx);
}

connectedCallback() {
defineNodeContextMetadata(this);
const {initialContext, observedAttributes, tagName, mode} = this.constructor as CWCO.WebComponentConstructor;
const {parsed, tracks, root, attrPropsMap} = $.get(this);
const {parsed, selfTrack, root, attrPropsMap} = $.get(this);

if (Object.keys(initialContext).length) {
$.get(this).updateContext(initialContext);
}

const onPropUpdate = (prop: string, oldValue: any, newValue: any, update = true) => {
const onPropUpdate = (prop: string, oldValue: any, newValue: any) => {
if (this.mounted) {
if (update) {
try {
this.forceUpdate();
this.onUpdate(prop, oldValue, newValue);
} catch(e) {
this.onError(e as ErrorEvent);
}
this.onUpdate(prop, oldValue, newValue);
} else if(this.parsed) {
this.onError(new Error(`[Possibly a memory leak]: Cannot set property "${prop}" on unmounted component.`));
}
};

try {
$.get(this).unsubscribeCtx = $.get(this).subscribe((newContext: CWCO.ObjectLiteral) => {
onPropUpdate('$context', newContext, newContext, false);
})

$.get(this).mounted = true;

Expand Down Expand Up @@ -253,8 +250,12 @@ export class WebComponent extends HTMLElement implements CWCO.WebComponent {
if (this.customSlot) {
this.innerHTML = '';
}

trackNode(contentNode, this, {tracks});

trackNodeTree(contentNode, selfTrack, this);

selfTrack.childNodeTracks.forEach((t: NodeTrack) => {
t.updateNode();
})

if (mode === 'none') {
[
Expand Down Expand Up @@ -331,18 +332,12 @@ export class WebComponent extends HTMLElement implements CWCO.WebComponent {
* updates any already tracked node with current component data including context and node level data.
*/
forceUpdate() {
if (this.mounted) {
cancelAnimationFrame($.get(this).updateFrame);
$.get(this).updateFrame = requestAnimationFrame(() => {
$.get(this).tracks.forEach((t: CWCO.NodeTrack) => {
t.updateNode();
});
});

return true;
}

return false;
cancelAnimationFrame($.get(this).updateFrame);
$.get(this).updateFrame = requestAnimationFrame(() => {
$.get(this).selfTrack.childNodeTracks.forEach((t: NodeTrack) => {
t.updateNode();
})
});
}

adoptedCallback() {
Expand Down

0 comments on commit 1495813

Please sign in to comment.