Skip to content

Commit 1f27223

Browse files
authored
Merge pull request #145 from plusnew/style
Style
2 parents 5926f21 + e56d1ad commit 1f27223

File tree

12 files changed

+249
-103
lines changed

12 files changed

+249
-103
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"exports": {
2222
".": "./src/index.ts",
2323
"./jsx-runtime": "./src/jsx-runtime.ts",
24+
"./jsx-dev-runtime": "./src/jsx-runtime.ts",
2425
"./babel-plugin-transform-jsx": "./src/babel-plugin-transform-jsx.cjs"
2526
},
2627
"homepage": "https://github.com/plusnew/webcomponent#readme",
@@ -43,4 +44,4 @@
4344
"prettier": "^3.3.3",
4445
"typescript": "^5.7.0"
4546
}
46-
}
47+
}

src/jsx-runtime.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { ShadowHostElement } from "./types";
22
import { PLUSNEW_ELEMENT_TYPE } from "./types";
33

44
export { Fragment } from "./types";
5-
export type { JSX } from "./types"
5+
export type { JSX } from "./types";
66

77
export function jsx(
88
type: string,

src/reconciler/component.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { type ShadowComponentElement, type ShadowElement, PLUSNEW_ELEMENT_TYPE} from "../types";
1+
import {
2+
type ShadowComponentElement,
3+
type ShadowElement,
4+
PLUSNEW_ELEMENT_TYPE,
5+
} from "../types";
26
import { reconcile, type Reconciler } from "./index";
37
import { ShadowCache } from "./utils";
48

@@ -27,18 +31,22 @@ export const componentReconcile: Reconciler = (opt) => {
2731
opt.shadowCache.remove();
2832

2933
opt.shadowCache.value = opt.shadowElement;
30-
opt.shadowCache.nestedShadows = [new ShadowCache(false)]
34+
opt.shadowCache.nestedShadows = [new ShadowCache(false)];
3135
}
32-
3336

34-
const result = (opt.shadowElement.type as any)({
35-
...opt.shadowElement.props,
36-
children: opt.shadowElement.children.map((child) => child())
37-
}, { shadowCache: opt.shadowCache, parentElement: opt.parentElement });
37+
const result = (opt.shadowElement.type as any)(
38+
{
39+
...opt.shadowElement.props,
40+
children: opt.shadowElement.children.map((child) => child()),
41+
},
42+
{ shadowCache: opt.shadowCache, parentElement: opt.parentElement },
43+
);
3844

3945
let nextSibling = reconcile({
40-
parentElement: (opt.shadowCache.node as ParentNode | null) ?? opt.parentElement,
41-
previousSibling: opt.shadowCache.node === null ? null : opt.previousSibling,
46+
parentElement:
47+
(opt.shadowCache.node as ParentNode | null) ?? opt.parentElement,
48+
previousSibling:
49+
opt.shadowCache.node === null ? null : opt.previousSibling,
4250
shadowCache: opt.shadowCache.nestedShadows[0],
4351
shadowElement: result,
4452
getParentOverwrite: opt.shadowCache.getParentOverwrite,

src/reconciler/fragment.ts

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,43 @@
1-
import { PLUSNEW_ELEMENT_TYPE, Fragment, type ShadowElement, type ShadowHostElement } from "../types";
1+
import {
2+
PLUSNEW_ELEMENT_TYPE,
3+
Fragment,
4+
type ShadowElement,
5+
type ShadowHostElement,
6+
} from "../types";
27
import type { Reconciler } from "./index";
38
import { arrayReconcileWithoutSorting } from "./utils";
49

5-
610
export function isFragmentElement(
7-
shadowElement: ShadowElement,
8-
): shadowElement is ShadowHostElement {
9-
return (
10-
typeof shadowElement === "object" &&
11-
"$$typeof" in shadowElement &&
12-
shadowElement.$$typeof === PLUSNEW_ELEMENT_TYPE &&
13-
shadowElement.type === Fragment
14-
);
15-
}
11+
shadowElement: ShadowElement,
12+
): shadowElement is ShadowHostElement {
13+
return (
14+
typeof shadowElement === "object" &&
15+
"$$typeof" in shadowElement &&
16+
shadowElement.$$typeof === PLUSNEW_ELEMENT_TYPE &&
17+
shadowElement.type === Fragment
18+
);
19+
}
1620

17-
1821
export const fragmentReconcile: Reconciler = (opt) => {
1922
// Check if new shadow is of type dom-element
2023
if (isFragmentElement(opt.shadowElement)) {
2124
// Check if old shadow is of same shadow-type
2225
if (isFragmentElement(opt.shadowCache.value) === false) {
23-
opt.shadowCache.remove();
26+
opt.shadowCache.remove();
2427

25-
opt.shadowCache.value = {
26-
$$typeof: PLUSNEW_ELEMENT_TYPE,
27-
type: Fragment,
28-
props: {},
29-
children: []
30-
}
28+
opt.shadowCache.value = {
29+
$$typeof: PLUSNEW_ELEMENT_TYPE,
30+
type: Fragment,
31+
props: {},
32+
children: [],
33+
};
3134
}
3235

3336
return arrayReconcileWithoutSorting({
3437
parentElement: opt.parentElement,
3538
previousSibling: opt.previousSibling,
3639
shadowCache: opt.shadowCache,
37-
shadowElement: opt.shadowElement.children.map(child => child()),
40+
shadowElement: opt.shadowElement.children.map((child) => child()),
3841
getParentOverwrite: opt.getParentOverwrite,
3942
});
4043
} else {

src/reconciler/host.ts

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ function isHostElement(
1717
return (
1818
typeof shadowElement === "object" &&
1919
"$$typeof" in shadowElement &&
20-
(typeof shadowElement.type === "string" || Element.isPrototypeOf(shadowElement.type))
20+
(typeof shadowElement.type === "string" ||
21+
Element.isPrototypeOf(shadowElement.type))
2122
);
2223
}
2324

@@ -38,7 +39,9 @@ export const hostReconcile: Reconciler = (opt) => {
3839
// create new element
3940
const element = untracked(() => {
4041
const shadowElement = opt.shadowElement as ShadowHostElement;
41-
return typeof shadowElement.type === "string" ? document.createElement(shadowElement.type) : new (shadowElement.type)();
42+
return typeof shadowElement.type === "string"
43+
? document.createElement(shadowElement.type)
44+
: new shadowElement.type();
4245
});
4346

4447
opt.shadowCache.node = element;
@@ -87,10 +90,14 @@ export const hostReconcile: Reconciler = (opt) => {
8790

8891
callback(evt, ...args);
8992

90-
if ((opt.shadowElement as ShadowHostElement).props.value !== newValue) {
93+
if (
94+
(opt.shadowElement as ShadowHostElement).props.value !==
95+
newValue
96+
) {
9197
evt.preventDefault();
92-
(evt.currentTarget as HTMLInputElement).value =
93-
(opt.shadowElement as ShadowHostElement).props.value;
98+
(evt.currentTarget as HTMLInputElement).value = (
99+
opt.shadowElement as ShadowHostElement
100+
).props.value;
94101
}
95102
};
96103
}
@@ -119,11 +126,24 @@ export const hostReconcile: Reconciler = (opt) => {
119126
shadowElement.props.value;
120127
}
121128
}
122-
: (opt.shadowElement).props[propKey],
129+
: opt.shadowElement.props[propKey],
123130
);
124131
} else {
125132
untracked(() => {
126-
(opt.shadowCache.node as any)[propKey] = (opt.shadowElement as ShadowHostElement).props[propKey];
133+
if (propKey === "style") {
134+
(opt.shadowCache.node as any).setAttribute(
135+
"style",
136+
Object.entries(
137+
(opt.shadowElement as ShadowHostElement).props[propKey],
138+
)
139+
.map(([key, value]) => `${key}: ${value}`)
140+
.join(";"),
141+
);
142+
} else {
143+
(opt.shadowCache.node as any)[propKey] = (
144+
opt.shadowElement as ShadowHostElement
145+
).props[propKey];
146+
}
127147
});
128148
}
129149

@@ -132,6 +152,17 @@ export const hostReconcile: Reconciler = (opt) => {
132152
}
133153
}
134154

155+
for (const propKey in (opt.shadowCache.value as ShadowHostElement).props) {
156+
if (propKey in opt.shadowElement.props === false) {
157+
untracked(() => {
158+
if (propKey === "style") {
159+
(opt.shadowCache.node as any).removeAttribute("style");
160+
}
161+
});
162+
delete (opt.shadowCache.value as ShadowHostElement).props[propKey];
163+
}
164+
}
165+
135166
// @TODO Remove unneded props
136167

137168
const previousActiveElement = active.parentElement;
@@ -157,7 +188,11 @@ export const hostReconcile: Reconciler = (opt) => {
157188
});
158189

159190
if (elementNeedsAppending) {
160-
append(opt.parentElement, opt.previousSibling, opt.shadowCache.node as Node);
191+
append(
192+
opt.parentElement,
193+
opt.previousSibling,
194+
opt.shadowCache.node as Node,
195+
);
161196
}
162197

163198
return opt.shadowCache.node;

src/reconciler/index.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ import { textReconcile } from "./text";
88
import type { ShadowCache } from "./utils";
99

1010
export type Reconciler = (opt: {
11-
parentElement: ParentNode,
12-
previousSibling: Node | null,
13-
shadowCache: ShadowCache,
14-
shadowElement: ShadowElement,
15-
getParentOverwrite: (() => Element) | null
11+
parentElement: ParentNode;
12+
previousSibling: Node | null;
13+
shadowCache: ShadowCache;
14+
shadowElement: ShadowElement;
15+
getParentOverwrite: (() => Element) | null;
1616
}) => Node | null | false;
1717

1818
export function reconcile(opt: Parameters<Reconciler>[0]): Node | null {

src/reconciler/utils.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { ShadowElement } from "../types";
22
import { reconcile } from "./index";
33

4-
export class ShadowCache {
4+
export class ShadowCache {
55
value: ShadowElement;
66
node: Node | null = null;
77
nestedShadows: ShadowCache[] = [];
@@ -13,7 +13,6 @@ export class ShadowCache {
1313
remove() {
1414
this.unmount();
1515

16-
1716
if (this.node === null) {
1817
for (const nestedShadow of this.nestedShadows) {
1918
nestedShadow.remove();
@@ -30,14 +29,14 @@ export class ShadowCache {
3029
nestedShadow.unmount();
3130
}
3231
}
33-
};
32+
}
3433

3534
export const arrayReconcileWithoutSorting = (opt: {
36-
parentElement: ParentNode,
37-
previousSibling: Node | null,
38-
shadowCache: ShadowCache,
39-
shadowElement: ShadowElement[],
40-
getParentOverwrite: (() => Element) | null
35+
parentElement: ParentNode;
36+
previousSibling: Node | null;
37+
shadowCache: ShadowCache;
38+
shadowElement: ShadowElement[];
39+
getParentOverwrite: (() => Element) | null;
4140
}) => {
4241
let lastAddedSibling = opt.previousSibling;
4342

src/types.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
export const PLUSNEW_ELEMENT_TYPE = Symbol("plusnew-element-type");
22

33
export function Fragment(props: { children: ShadowElement }) {
4-
return props.children
4+
return props.children;
55
}
66

77
// type Expect<T extends true> = T;
@@ -116,13 +116,13 @@ export type ShadowHostElement = {
116116

117117
export type ShadowComponentElement<T> = {
118118
$$typeof: typeof PLUSNEW_ELEMENT_TYPE;
119-
type: (props: T)=> ShadowElement;
119+
type: (props: T) => ShadowElement;
120120
props: T;
121121
children: (() => ShadowElement)[];
122122
};
123123

124124
export type ShadowFragmentElement = {
125-
$$typeof: typeof PLUSNEW_ELEMENT_TYPE
125+
$$typeof: typeof PLUSNEW_ELEMENT_TYPE;
126126
type: typeof Fragment;
127127
props: any;
128128
children: (() => ShadowElement)[];

src/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const ERROR_EVENT = "plusnewerror"
1+
const ERROR_EVENT = "plusnewerror";
22

33
export function dispatchError(element: Element, error: unknown) {
44
const result = element.dispatchEvent(

test/base.test.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,8 @@ describe("webcomponent", () => {
240240

241241
const component = container.childNodes[0] as HTMLElement;
242242
const nestedComponent = component.shadowRoot?.childNodes[0] as HTMLElement;
243-
const buttonElement = nestedComponent.shadowRoot?.childNodes[0] as ChildNode;
243+
const buttonElement = nestedComponent.shadowRoot
244+
?.childNodes[0] as ChildNode;
244245

245246
expect(component.tagName).to.equal("TEST-CONTAINER-RERENDER");
246247
expect(component.childNodes.length).to.equal(0);
@@ -250,7 +251,9 @@ describe("webcomponent", () => {
250251

251252
buttonElement.dispatchEvent(new Event("click"));
252253

253-
expect(nestedComponent.shadowRoot?.childNodes[0] === buttonElement).to.equal(true);
254+
expect(
255+
nestedComponent.shadowRoot?.childNodes[0] === buttonElement,
256+
).to.equal(true);
254257
expect(buttonElement.textContent).to.equal("1");
255258
expect(containerRenderCounter).to.equal(1);
256259
expect(nestedRenderCounter).to.equal(2);

0 commit comments

Comments
 (0)