Skip to content

Commit

Permalink
mount(el, component, attributes) proposal
Browse files Browse the repository at this point in the history
  • Loading branch information
sebkolind committed Jun 4, 2024
1 parent bfa7b35 commit 2139352
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 30 deletions.
15 changes: 14 additions & 1 deletion src/__tests__/attributes.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { addAttribute } from '../attributes';
import { addAttribute, addAttributes } from '../attributes';
import { tags } from '../tags';

describe('attributes.ts', () => {
test('adds a simple attribute', () => {
Expand Down Expand Up @@ -42,4 +43,16 @@ describe('attributes.ts', () => {

expect(el.hasAttribute('mounted')).toBe(false);
});

test('addAttributes adds all attributes', () => {
const el = tags.div([]);

addAttributes(el, {
id: 'test',
foo: 'bar',
});

expect(el.getAttribute('id')).toBe('test');
expect(el.getAttribute('foo')).toBe('bar');
});
});
68 changes: 67 additions & 1 deletion src/__tests__/main.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { Component, mount, tags } from '../main';
import { fireEvent, getByRole, getByTestId } from '@testing-library/dom';
import { mount, tags, type Component } from '../main';

beforeEach(() => {
document.body.innerHTML = '';
});

describe('main', () => {
test('`null` element', () => {
Expand Down Expand Up @@ -34,4 +39,65 @@ describe('main', () => {
}),
).not.toThrow();
});

test('that attributes are set when using `mount` with attrs set', () => {
const target = document.createElement('div');
target.setAttribute('data-testid', 'test');
document.body.append(target);

const TestComponent: Component = {
view: () => tags.div('Hey, this is me!'),
};

mount(target, TestComponent, {
id: 'foo',
foo: 'bar',
});

const el = getByTestId(document.body, 'test');

expect(el.getAttribute('id')).toBe('foo');
expect(el.getAttribute('foo')).toBe('bar');
});

test('that attributes are set when using `mount` and dynamic mounting', () => {
const target = document.createElement('div');
target.setAttribute('data-testid', 'test');
document.body.append(target);

const modalTarget = document.createElement('div');
modalTarget.setAttribute('data-testid', 'modal');
document.body.append(modalTarget);

const Parent: Component = {
view: () =>
tags.button('Click me', {
onclick() {
mount(modalTarget, Modal, { modalId: 'foo' });
},
}),
};

const Modal: Component = {
view: () => tags.p('I am a modal'),
};

mount(target, Parent, {
id: 'foo',
foo: 'bar',
});

const el = getByTestId(document.body, 'test');

expect(el.getAttribute('id')).toBe('foo');
expect(el.getAttribute('foo')).toBe('bar');

const btn = getByRole(document.body, 'button');

fireEvent.click(btn);

const modal = getByTestId(document.body, 'modal');

expect(modal.getAttribute('modalId')).toBe('foo');
});
});
16 changes: 12 additions & 4 deletions src/attributes.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import { type TentNode, type Attrs, type TagAttrsValues } from './types';

function addAttributes<A extends Attrs>(el: TentNode<A>, attributes: A) {
for (const key in attributes) {
const value = attributes[key as string];

el.$tent.attributes[key as string] = value;

addAttribute(el, key, value);
}
}

function addAttribute<A extends Attrs>(
el: TentNode<A> | HTMLElement,
key: string,
value: TagAttrsValues,
) {
if (key === 'mounted') {
return;
}
if (key === 'mounted') return;

if (typeof value === 'boolean') {
if (value) {
Expand All @@ -28,4 +36,4 @@ function addAttribute<A extends Attrs>(
}
}

export { addAttribute };
export { addAttributes, addAttribute };
3 changes: 3 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import type { Children, Context, Component, TentNode, Attrs } from './types';
import { createTag, tags } from './tags';
import { walker } from './walker';
import { addAttributes } from './attributes';

function mount<S extends {} = {}, A extends Attrs = {}>(
element: HTMLElement | Element | TentNode<A> | null,
component: Component<S, A>,
attributes?: A,
) {
if (element == null) return;

Expand All @@ -14,6 +16,7 @@ function mount<S extends {} = {}, A extends Attrs = {}>(
const el = element as TentNode<A>;

el.$tent = { attributes: {} };
addAttributes(el, attributes);

const handler = {
get(obj: S, key: string) {
Expand Down
17 changes: 4 additions & 13 deletions src/tags.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,13 @@
import { addAttribute } from './attributes';
import { type Tags, type Context, type TentNode } from './types';
import { addAttributes } from './attributes';
import type { Tags, Context, TentNode } from './types';

function createTag(context: Context) {
const [tag, children, attributes] = context;

const el = document.createElement(tag) as TentNode;

el.$tent = {
attributes: {},
};

for (const key in attributes) {
const value = attributes[key];

el.$tent.attributes[key] = value;

addAttribute(el, key, value);
}
el.$tent = { attributes: {} };
addAttributes(el, attributes);

if (Array.isArray(children)) {
for (let i = 0; i < children.length; i++) {
Expand Down
18 changes: 7 additions & 11 deletions src/walker.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { addAttribute } from './attributes';
import { type Attrs, type TentNode } from './types';
import { addAttributes } from './attributes';
import type { Attrs, TentNode } from './types';

function walker<A extends Attrs>(oldNode: TentNode<A>, newNode: TentNode<A>) {
function walker(oldNode: TentNode<Attrs>, newNode: TentNode<Attrs>) {
if (oldNode.tagName !== newNode.tagName) {
oldNode.replaceWith(newNode);

return;
}

const nc = Array.from(newNode.childNodes, (n) => n as TentNode<A>);
const oc = Array.from(oldNode.childNodes, (n) => n as TentNode<A>);
const nc = Array.from(newNode.childNodes, (n) => n as TentNode);
const oc = Array.from(oldNode.childNodes, (n) => n as TentNode);

if (oldNode.nodeType === Node.TEXT_NODE) {
if (oldNode.nodeValue !== newNode.nodeValue) {
Expand All @@ -36,14 +36,10 @@ function walker<A extends Attrs>(oldNode: TentNode<A>, newNode: TentNode<A>) {
}

// Add attributes that are not present in the old node
const attrs = {
addAttributes(oldNode, {
...oldNode.$tent.attributes,
...newNode.$tent.attributes,
};

for (const key in attrs) {
addAttribute(oldNode, key, attrs[key]);
}
});

if (oc.length === 0 && nc.length === 0) return;

Expand Down

0 comments on commit 2139352

Please sign in to comment.