rask-ui 0.2.6 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -27,13 +27,29 @@ export function createState(state) {
27
27
  return getProxy(state);
28
28
  }
29
29
  const proxyCache = new WeakMap();
30
+ const PROXY_MARKER = Symbol('isProxy');
30
31
  function getProxy(value) {
32
+ // Check if already a proxy to avoid double-wrapping
33
+ if (PROXY_MARKER in value) {
34
+ return value;
35
+ }
31
36
  if (proxyCache.has(value)) {
32
37
  return proxyCache.get(value);
33
38
  }
34
39
  const signals = {};
35
40
  const proxy = new Proxy(value, {
41
+ has(target, key) {
42
+ // Support the "in" operator check for PROXY_MARKER
43
+ if (key === PROXY_MARKER) {
44
+ return true;
45
+ }
46
+ return Reflect.has(target, key);
47
+ },
36
48
  get(target, key) {
49
+ // Mark this as a proxy to prevent double-wrapping
50
+ if (key === PROXY_MARKER) {
51
+ return true;
52
+ }
37
53
  const value = Reflect.get(target, key);
38
54
  if (typeof key === "symbol" || typeof value === "function") {
39
55
  return value;
@@ -31,7 +31,7 @@ export declare abstract class AbstractVNode {
31
31
  patchChildren(newChildren: VNode[]): {
32
32
  children: VNode[];
33
33
  hasChangedStructure: boolean;
34
- operations: PatchOperation[];
34
+ operations?: PatchOperation[];
35
35
  };
36
36
  applyPatchOperations(target: HTMLElement, operations: PatchOperation[]): void;
37
37
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"AbstractVNode.d.ts","sourceRoot":"","sources":["../../src/vdom/AbstractVNode.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAEhC,MAAM,MAAM,cAAc,GACtB;IACE,IAAI,EAAE,KAAK,CAAC;IACZ,GAAG,EAAE,IAAI,GAAG,IAAI,EAAE,CAAC;CACpB,GACD;IACE,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,EAAE,IAAI,GAAG,IAAI,EAAE,CAAC;IACtB,MAAM,EAAE,IAAI,GAAG,IAAI,EAAE,CAAC;CACvB,GACD;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,GAAG,EAAE,IAAI,GAAG,IAAI,EAAE,CAAC;CACpB,CAAC;AAEN,8BAAsB,aAAa;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,KAAK,CAAC;IACf,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,GAAG,CAAC,EAAE,IAAI,CAAC;IACX,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC;IACnB,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,GAAG,IAAI,GAAG,IAAI,EAAE;IAC7C,QAAQ,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,GAAG,IAAI;IACpC,QAAQ,CAAC,OAAO,IAAI,IAAI;IACxB,QAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,cAAc,EAAE,GAAG,IAAI;IACtD,SAAS,CAAC,cAAc;IAOxB;;OAEG;IACH,WAAW,IAAI,IAAI,EAAE;IAWrB,gBAAgB,IAAI,WAAW;IAiB/B,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,GAAG,OAAO;IAoB3D,aAAa,CAAC,WAAW,EAAE,KAAK,EAAE,GAAG;QACnC,QAAQ,EAAE,KAAK,EAAE,CAAC;QAClB,mBAAmB,EAAE,OAAO,CAAC;QAC7B,UAAU,EAAE,cAAc,EAAE,CAAC;KAC9B;IAkJD,oBAAoB,CAAC,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE;IAqCtE;;;;OAIG;IACH,SAAS,CAAC,eAAe;CA6B1B"}
1
+ {"version":3,"file":"AbstractVNode.d.ts","sourceRoot":"","sources":["../../src/vdom/AbstractVNode.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAEhC,MAAM,MAAM,cAAc,GACtB;IACE,IAAI,EAAE,KAAK,CAAC;IACZ,GAAG,EAAE,IAAI,GAAG,IAAI,EAAE,CAAC;CACpB,GACD;IACE,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,EAAE,IAAI,GAAG,IAAI,EAAE,CAAC;IACtB,MAAM,EAAE,IAAI,GAAG,IAAI,EAAE,CAAC;CACvB,GACD;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,GAAG,EAAE,IAAI,GAAG,IAAI,EAAE,CAAC;CACpB,CAAC;AAEN,8BAAsB,aAAa;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,KAAK,CAAC;IACf,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,GAAG,CAAC,EAAE,IAAI,CAAC;IACX,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC;IACnB,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,GAAG,IAAI,GAAG,IAAI,EAAE;IAC7C,QAAQ,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,GAAG,IAAI;IACpC,QAAQ,CAAC,OAAO,IAAI,IAAI;IACxB,QAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,cAAc,EAAE,GAAG,IAAI;IACtD,SAAS,CAAC,cAAc;IAOxB;;OAEG;IACH,WAAW,IAAI,IAAI,EAAE;IAmBrB,gBAAgB,IAAI,WAAW;IAiB/B,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,GAAG,OAAO;IAoB3D,aAAa,CAAC,WAAW,EAAE,KAAK,EAAE,GAAG;QACnC,QAAQ,EAAE,KAAK,EAAE,CAAC;QAClB,mBAAmB,EAAE,OAAO,CAAC;QAC7B,UAAU,CAAC,EAAE,cAAc,EAAE,CAAC;KAC/B;IAqJD,oBAAoB,CAAC,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE;IAqCtE;;;;OAIG;IACH,SAAS,CAAC,eAAe;CA6B1B"}
@@ -21,7 +21,15 @@ export class AbstractVNode {
21
21
  if (!this.children) {
22
22
  throw new Error("This VNode has no element or children");
23
23
  }
24
- return this.children.map((child) => child.getElements()).flat();
24
+ // Optimized: avoid intermediate arrays from map+flat
25
+ const result = [];
26
+ for (let i = 0; i < this.children.length; i++) {
27
+ const childElms = this.children[i].getElements();
28
+ for (let j = 0; j < childElms.length; j++) {
29
+ result.push(childElms[j]);
30
+ }
31
+ }
32
+ return result;
25
33
  }
26
34
  getParentElement() {
27
35
  let parent = this.parent;
@@ -55,13 +63,15 @@ export class AbstractVNode {
55
63
  }
56
64
  patchChildren(newChildren) {
57
65
  const prevChildren = this.children;
66
+ if (newChildren.length === 0 && prevChildren.length === 0) {
67
+ return { children: [], hasChangedStructure: false };
68
+ }
58
69
  // When there are only new children, we just mount them
59
- if (newChildren && prevChildren.length === 0) {
70
+ if (prevChildren.length === 0) {
60
71
  newChildren.forEach((child) => child.mount(this));
61
72
  return {
62
73
  children: newChildren,
63
74
  hasChangedStructure: true,
64
- operations: [],
65
75
  };
66
76
  }
67
77
  // If we want to remove all children, we just unmount the previous ones
@@ -1 +1 @@
1
- {"version":3,"file":"ComponentVNode.d.ts","sourceRoot":"","sources":["../../src/vdom/ComponentVNode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,QAAQ,EAAU,MAAM,gBAAgB,CAAC;AACtE,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGhE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAGvC,MAAM,MAAM,cAAc,GACtB,KAAK,GACL,MAAM,GACN,IAAI,GACJ,MAAM,GACN,SAAS,GACT,OAAO,CAAC;AACZ,MAAM,MAAM,iBAAiB,GAAG,cAAc,GAAG,cAAc,EAAE,CAAC;AAElE;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,KAAK,IACjC,CAAC,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,iBAAiB,CAAC,GACvC,CAAC,MAAM,MAAM,iBAAiB,CAAC,CAAC;AAEpC,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,CAAC,EAAE,KAAK,CAAC;IACf,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACtC,QAAQ,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;IAC5B,UAAU,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;IAC9B,QAAQ,EAAE,QAAQ,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;IACf,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;CACnC,CAAC;AAKF,wBAAgB,mBAAmB,sBAYlC;AAED,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,IAAI,QAYrC;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,IAAI,QAYvC;AAED,qBAAa,cAAe,SAAQ,aAAa;IAC/C,SAAS,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC;IAC1B,KAAK,EAAE,KAAK,CAAC;IAEb,QAAQ,EAAE,KAAK,EAAE,CAAM;IACvB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;gBAE3B,SAAS,EAAE,SAAS,CAAC,GAAG,CAAC,EACzB,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,KAAK,EAAE,EACjB,GAAG,CAAC,EAAE,MAAM;IAWd,QAAQ,CAAC,UAAU,CAAC,EAAE,cAAc,EAAE,GAAG,IAAI;IAG7C,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,GAAG,IAAI,EAAE;IA0I7B,KAAK,CAAC,OAAO,EAAE,cAAc;IAW7B,OAAO;CAcR"}
1
+ {"version":3,"file":"ComponentVNode.d.ts","sourceRoot":"","sources":["../../src/vdom/ComponentVNode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,QAAQ,EAAU,MAAM,gBAAgB,CAAC;AACtE,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGhE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAIvC,MAAM,MAAM,cAAc,GACtB,KAAK,GACL,MAAM,GACN,IAAI,GACJ,MAAM,GACN,SAAS,GACT,OAAO,CAAC;AACZ,MAAM,MAAM,iBAAiB,GAAG,cAAc,GAAG,cAAc,EAAE,CAAC;AAElE;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,KAAK,IACjC,CAAC,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,iBAAiB,CAAC,GACvC,CAAC,MAAM,MAAM,iBAAiB,CAAC,CAAC;AAEpC,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,CAAC,EAAE,KAAK,CAAC;IACf,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACtC,QAAQ,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;IAC5B,UAAU,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;IAC9B,QAAQ,EAAE,QAAQ,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;IACf,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;CACnC,CAAC;AAKF,wBAAgB,mBAAmB,sBAYlC;AAED,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,IAAI,QAYrC;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,IAAI,QAYvC;AAED,qBAAa,cAAe,SAAQ,aAAa;IAC/C,SAAS,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC;IAC1B,KAAK,EAAE,KAAK,CAAC;IAEb,QAAQ,EAAE,KAAK,EAAE,CAAM;IACvB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;gBAE3B,SAAS,EAAE,SAAS,CAAC,GAAG,CAAC,EACzB,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,KAAK,EAAE,EACjB,GAAG,CAAC,EAAE,MAAM;IAWd,QAAQ,CAAC,UAAU,CAAC,EAAE,cAAc,EAAE,GAAG,IAAI;IAG7C,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,GAAG,IAAI,EAAE;IA6I7B,KAAK,CAAC,OAAO,EAAE,cAAc;IAW7B,OAAO;CAcR"}
@@ -3,6 +3,7 @@ import { AbstractVNode } from "./AbstractVNode";
3
3
  import { FragmentVNode } from "./FragmentVNode";
4
4
  import { RootVNode } from "./RootVNode";
5
5
  import { normalizeChildren } from "./utils";
6
+ import { flattenNodes } from "./dom-utils";
6
7
  import { currentRoot } from "./RootVNode";
7
8
  export function getCurrentComponent() {
8
9
  if (!currentRoot) {
@@ -99,11 +100,11 @@ export class ComponentVNode extends AbstractVNode {
99
100
  this.children = children;
100
101
  // So if a fragment is returned where we add new elements we can not safely
101
102
  // add them yet, check Fragment for a potential later optimization
102
- const hasAddOperation = operations.some((operation) => operation.type === "add");
103
+ const hasAddOperation = operations?.some((operation) => operation.type === "add");
103
104
  if (hasChangedStructure || hasAddOperation) {
104
105
  this.parent?.rerender();
105
106
  }
106
- else if (operations.length) {
107
+ else if (operations?.length) {
107
108
  this.parent?.rerender(operations);
108
109
  }
109
110
  this.root?.clearCurrent();
@@ -158,9 +159,12 @@ export class ComponentVNode extends AbstractVNode {
158
159
  this.children = executeRender();
159
160
  this.root?.popComponent();
160
161
  this.root?.clearCurrent();
161
- const childElements = this.children
162
- .map((child) => child.mount(this))
163
- .flat();
162
+ // Optimized: avoid intermediate arrays from map+flat
163
+ const childResults = [];
164
+ for (let i = 0; i < this.children.length; i++) {
165
+ childResults.push(this.children[i].mount(this));
166
+ }
167
+ const childElements = flattenNodes(childResults);
164
168
  // Queue onMount callbacks after children are mounted
165
169
  // This ensures refs and other child lifecycle hooks run before parent onMount
166
170
  instance.onMounts.forEach((cb) => {
@@ -1,13 +1,15 @@
1
1
  import { AbstractVNode, PatchOperation } from "./AbstractVNode";
2
- import { Props, VNode } from "./types";
2
+ import { Props, VNode, VFlags } from "./types";
3
3
  export declare class ElementVNode extends AbstractVNode {
4
4
  tag: string;
5
5
  props: Props;
6
6
  children: VNode[];
7
7
  key?: string;
8
+ flags: VFlags;
8
9
  private ref?;
9
10
  private eventListeners?;
10
11
  constructor(tag: string, { ref, ...props }: Props, children: VNode[], key?: string);
12
+ private computeFlags;
11
13
  rerender(operations?: PatchOperation[]): void;
12
14
  mount(parent?: VNode): Node;
13
15
  /**
@@ -19,6 +21,7 @@ export declare class ElementVNode extends AbstractVNode {
19
21
  unmount(): void;
20
22
  private setProp;
21
23
  private patchProps;
24
+ private patchStyle;
22
25
  private addEventListener;
23
26
  }
24
27
  //# sourceMappingURL=ElementVNode.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ElementVNode.d.ts","sourceRoot":"","sources":["../../src/vdom/ElementVNode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGhE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAUvC,qBAAa,YAAa,SAAQ,aAAa;IAC7C,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,KAAK,EAAE,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,GAAG,CAAC,CAA0D;IACtE,OAAO,CAAC,cAAc,CAAC,CAA6B;gBAElD,GAAG,EAAE,MAAM,EACX,EAAE,GAAG,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,EACxB,QAAQ,EAAE,KAAK,EAAE,EACjB,GAAG,CAAC,EAAE,MAAM;IASd,QAAQ,CAAC,UAAU,CAAC,EAAE,cAAc,EAAE,GAAG,IAAI;IAO7C,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,GAAG,IAAI;IAkC3B;;;;OAIG;IACH,KAAK,CAAC,OAAO,EAAE,YAAY;IAc3B,OAAO;IAYP,OAAO,CAAC,OAAO,CAoCb;IACF,OAAO,CAAC,UAAU;IAGlB,OAAO,CAAC,gBAAgB;CAgBzB"}
1
+ {"version":3,"file":"ElementVNode.d.ts","sourceRoot":"","sources":["../../src/vdom/ElementVNode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGhE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAU/C,qBAAa,YAAa,SAAQ,aAAa;IAC7C,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,KAAK,EAAE,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,GAAG,CAAC,CAA0D;IACtE,OAAO,CAAC,cAAc,CAAC,CAA6B;gBAElD,GAAG,EAAE,MAAM,EACX,EAAE,GAAG,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,EACxB,QAAQ,EAAE,KAAK,EAAE,EACjB,GAAG,CAAC,EAAE,MAAM;IAad,OAAO,CAAC,YAAY;IAwBpB,QAAQ,CAAC,UAAU,CAAC,EAAE,cAAc,EAAE,GAAG,IAAI;IAO7C,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,GAAG,IAAI;IAkC3B;;;;OAIG;IACH,KAAK,CAAC,OAAO,EAAE,YAAY;IAwB3B,OAAO;IAYP,OAAO,CAAC,OAAO,CAoCb;IACF,OAAO,CAAC,UAAU;IAsClB,OAAO,CAAC,UAAU;IAoClB,OAAO,CAAC,gBAAgB;CAgBzB"}
@@ -7,6 +7,7 @@ export class ElementVNode extends AbstractVNode {
7
7
  props;
8
8
  children;
9
9
  key;
10
+ flags;
10
11
  ref;
11
12
  eventListeners;
12
13
  constructor(tag, { ref, ...props }, children, key) {
@@ -16,6 +17,31 @@ export class ElementVNode extends AbstractVNode {
16
17
  this.children = children;
17
18
  this.key = key;
18
19
  this.ref = ref;
20
+ // Pre-compute flags for fast-path checks during patching
21
+ this.flags = this.computeFlags(props);
22
+ }
23
+ computeFlags(props) {
24
+ let flags = 0 /* VFlags.None */;
25
+ const propKeys = Object.keys(props);
26
+ if (propKeys.length > 0) {
27
+ flags |= 1 /* VFlags.HasProps */;
28
+ for (let i = 0; i < propKeys.length; i++) {
29
+ const key = propKeys[i];
30
+ if (key === "class" || key === "className") {
31
+ flags |= 2 /* VFlags.HasClass */;
32
+ }
33
+ else if (key === "style") {
34
+ flags |= 4 /* VFlags.HasStyle */;
35
+ }
36
+ else if (isEventProp(key)) {
37
+ flags |= 8 /* VFlags.HasEvents */;
38
+ }
39
+ else if (key.startsWith("data-") || key.startsWith("aria-")) {
40
+ flags |= 16 /* VFlags.HasDataAttrs */;
41
+ }
42
+ }
43
+ }
44
+ return flags;
19
45
  }
20
46
  rerender(operations) {
21
47
  if (operations) {
@@ -60,14 +86,21 @@ export class ElementVNode extends AbstractVNode {
60
86
  * - Patch the children
61
87
  */
62
88
  patch(newNode) {
63
- this.patchProps(newNode.props);
89
+ // Save old flags before updating
90
+ const oldFlags = this.flags;
91
+ // Only patch props if either old or new node has props
92
+ if ((oldFlags | newNode.flags) & 1 /* VFlags.HasProps */) {
93
+ this.patchProps(newNode.props, oldFlags, newNode.flags);
94
+ }
95
+ // Update flags and props after patching
96
+ this.flags = newNode.flags;
64
97
  this.props = newNode.props;
65
98
  const { children, hasChangedStructure, operations } = this.patchChildren(newNode.children);
66
99
  this.children = children;
67
100
  if (hasChangedStructure) {
68
101
  this.syncDOMChildren();
69
102
  }
70
- else {
103
+ else if (operations?.length) {
71
104
  this.applyPatchOperations(this.getHTMLElement(), operations);
72
105
  }
73
106
  }
@@ -110,8 +143,64 @@ export class ElementVNode extends AbstractVNode {
110
143
  }
111
144
  setElementProp(elm, prop, value);
112
145
  };
113
- patchProps(newProps) {
114
- diffObjectKeys(this.props, newProps, this.setProp);
146
+ patchProps(newProps, oldFlags, newFlags) {
147
+ // Early bailout for reference equality
148
+ if (this.props === newProps) {
149
+ return;
150
+ }
151
+ const oldProps = this.props;
152
+ const elm = this.getHTMLElement();
153
+ // Handle class separately for efficiency (check if either old or new has class)
154
+ if ((oldFlags | newFlags) & 2 /* VFlags.HasClass */) {
155
+ const oldClass = oldProps.class ?? oldProps.className;
156
+ const newClass = newProps.class ?? newProps.className;
157
+ if (oldClass !== newClass) {
158
+ setElementClass(elm, newClass);
159
+ }
160
+ }
161
+ // Handle style separately with per-property diffing (check if either old or new has style)
162
+ if ((oldFlags | newFlags) & 4 /* VFlags.HasStyle */) {
163
+ this.patchStyle(oldProps.style, newProps.style);
164
+ }
165
+ // Handle regular props (excluding class, className, style, children)
166
+ diffObjectKeys(oldProps, newProps, (key, value, oldValue) => {
167
+ // Skip props we've already handled
168
+ if (key === "class" ||
169
+ key === "className" ||
170
+ key === "style" ||
171
+ key === "children") {
172
+ return;
173
+ }
174
+ this.setProp(key, value);
175
+ });
176
+ }
177
+ patchStyle(oldStyle, newStyle) {
178
+ // Early bailout for reference equality
179
+ if (oldStyle === newStyle) {
180
+ return;
181
+ }
182
+ const elm = this.getHTMLElement();
183
+ // If either is a string, fall back to full replacement
184
+ if (typeof oldStyle === "string" || typeof newStyle === "string") {
185
+ setElementStyle(elm, newStyle);
186
+ return;
187
+ }
188
+ // Per-property style diffing for objects
189
+ const os = oldStyle || {};
190
+ const ns = newStyle || {};
191
+ // Remove old styles not in new
192
+ for (const key in os) {
193
+ if (!(key in ns)) {
194
+ elm.style[key] = "";
195
+ }
196
+ }
197
+ // Set new/changed styles
198
+ for (const key in ns) {
199
+ const newVal = ns[key];
200
+ if (newVal !== os[key]) {
201
+ elm.style[key] = newVal;
202
+ }
203
+ }
115
204
  }
116
205
  addEventListener(type, cb) {
117
206
  if (!this.eventListeners) {
@@ -1 +1 @@
1
- {"version":3,"file":"FragmentVNode.d.ts","sourceRoot":"","sources":["../../src/vdom/FragmentVNode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAKhE,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAEhC,eAAO,MAAM,QAAQ,eAAqB,CAAC;AAE3C,qBAAa,aAAc,SAAQ,aAAa;IAC9C,QAAQ,EAAE,KAAK,EAAE,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;gBAED,QAAQ,EAAE,KAAK,EAAE,EAAE,GAAG,CAAC,EAAE,MAAM;IAK3C,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,GAAG,IAAI,EAAE;IAW7B,QAAQ,CAAC,UAAU,CAAC,EAAE,cAAc,EAAE,GAAG,IAAI;IAG7C,KAAK,CAAC,OAAO,EAAE,aAAa;IAiB5B,OAAO;CAMR"}
1
+ {"version":3,"file":"FragmentVNode.d.ts","sourceRoot":"","sources":["../../src/vdom/FragmentVNode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAKhE,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAGhC,eAAO,MAAM,QAAQ,eAAqB,CAAC;AAE3C,qBAAa,aAAc,SAAQ,aAAa;IAC9C,QAAQ,EAAE,KAAK,EAAE,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;gBAED,QAAQ,EAAE,KAAK,EAAE,EAAE,GAAG,CAAC,EAAE,MAAM;IAK3C,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,GAAG,IAAI,EAAE;IAgB7B,QAAQ,CAAC,UAAU,CAAC,EAAE,cAAc,EAAE,GAAG,IAAI;IAG7C,KAAK,CAAC,OAAO,EAAE,aAAa;IAiB5B,OAAO;CAMR"}
@@ -1,5 +1,6 @@
1
1
  import { AbstractVNode } from "./AbstractVNode";
2
2
  import { RootVNode } from "./RootVNode";
3
+ import { flattenNodes } from "./dom-utils";
3
4
  export const Fragment = Symbol("Fragment");
4
5
  export class FragmentVNode extends AbstractVNode {
5
6
  children;
@@ -17,7 +18,12 @@ export class FragmentVNode extends AbstractVNode {
17
18
  else {
18
19
  this.root = parent?.root;
19
20
  }
20
- return this.children.map((child) => child.mount(this)).flat();
21
+ // Optimized: avoid intermediate arrays from map+flat
22
+ const childResults = [];
23
+ for (let i = 0; i < this.children.length; i++) {
24
+ childResults.push(this.children[i].mount(this));
25
+ }
26
+ return flattenNodes(childResults);
21
27
  }
22
28
  rerender(operations) {
23
29
  this.parent?.rerender(operations);
@@ -30,7 +36,7 @@ export class FragmentVNode extends AbstractVNode {
30
36
  // handled with some additional detection, changing it to insertBefore. This can be
31
37
  // done by passing this vnode up to the parent
32
38
  this.rerender(hasChangedStructure ||
33
- operations.some((operation) => operation.type === "add")
39
+ operations?.some((operation) => operation.type === "add")
34
40
  ? undefined
35
41
  : operations);
36
42
  }
@@ -1 +1 @@
1
- {"version":3,"file":"RootVNode.d.ts","sourceRoot":"","sources":["../../src/vdom/RootVNode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAChE,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAIrD,eAAO,IAAI,WAAW,EAAE,SAAS,GAAG,SAAS,CAAC;AAE9C,qBAAa,SAAU,SAAQ,aAAa;IAC1C,QAAQ,EAAE,KAAK,EAAE,CAAC;IAClB,cAAc,EAAE,iBAAiB,EAAE,CAAM;IACzC,OAAO,CAAC,cAAc,CAGpB;IACF,OAAO,CAAC,mBAAmB,CAAkB;IAC7C,OAAO,CAAC,gBAAgB,CAAyB;gBAErC,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,WAAW;IAMnD,UAAU,CAAC,EAAE,EAAE,MAAM,IAAI;IAIzB,YAAY,CAAC,EAAE,EAAE,MAAM,IAAI;IAG3B,aAAa,CAAC,EAAE,EAAE,MAAM,IAAI;IAa5B,cAAc;IAOd,aAAa,CAAC,QAAQ,EAAE,iBAAiB;IAIzC,YAAY;IAIZ,YAAY;IAIZ,YAAY;IAMZ,KAAK,IAAI,IAAI,GAAG,IAAI,EAAE;IAGtB,KAAK,IAAI,IAAI;IACb,QAAQ,CAAC,UAAU,CAAC,EAAE,cAAc,EAAE,GAAG,IAAI;IAS7C,OAAO,IAAI,IAAI;CAChB"}
1
+ {"version":3,"file":"RootVNode.d.ts","sourceRoot":"","sources":["../../src/vdom/RootVNode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAChE,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAKrD,eAAO,IAAI,WAAW,EAAE,SAAS,GAAG,SAAS,CAAC;AAE9C,qBAAa,SAAU,SAAQ,aAAa;IAC1C,QAAQ,EAAE,KAAK,EAAE,CAAC;IAClB,cAAc,EAAE,iBAAiB,EAAE,CAAM;IACzC,OAAO,CAAC,cAAc,CAGpB;IACF,OAAO,CAAC,mBAAmB,CAAkB;IAC7C,OAAO,CAAC,gBAAgB,CAAyB;gBAErC,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,WAAW;IAMnD,UAAU,CAAC,EAAE,EAAE,MAAM,IAAI;IAIzB,YAAY,CAAC,EAAE,EAAE,MAAM,IAAI;IAG3B,aAAa,CAAC,EAAE,EAAE,MAAM,IAAI;IAa5B,cAAc;IAOd,aAAa,CAAC,QAAQ,EAAE,iBAAiB;IAIzC,YAAY;IAIZ,YAAY;IAIZ,YAAY;IAMZ,KAAK,IAAI,IAAI,GAAG,IAAI,EAAE;IAStB,KAAK,IAAI,IAAI;IACb,QAAQ,CAAC,UAAU,CAAC,EAAE,cAAc,EAAE,GAAG,IAAI;IAS7C,OAAO,IAAI,IAAI;CAChB"}
@@ -1,4 +1,5 @@
1
1
  import { AbstractVNode } from "./AbstractVNode";
2
+ import { flattenNodes } from "./dom-utils";
2
3
  // Global reference to the currently executing root
3
4
  // Safe because JS is single-threaded - only one render executes at a time
4
5
  export let currentRoot;
@@ -56,7 +57,13 @@ export class RootVNode extends AbstractVNode {
56
57
  }
57
58
  }
58
59
  mount() {
59
- return this.children.map((childNode) => childNode.mount(this)).flat();
60
+ // Optimized: avoid intermediate arrays from map+flat
61
+ const childResults = [];
62
+ for (let i = 0; i < this.children.length; i++) {
63
+ childResults.push(this.children[i].mount(this));
64
+ }
65
+ const result = flattenNodes(childResults);
66
+ return result.length === 1 ? result[0] : result;
60
67
  }
61
68
  patch() { }
62
69
  rerender(operations) {
@@ -1,9 +1,14 @@
1
+ /**
2
+ * Efficiently flatten a Node or Node[] result without creating intermediate arrays.
3
+ * Used to optimize mount operations that return either Node or Node[].
4
+ */
5
+ export declare function flattenNodes(items: (Node | Node[])[]): Node[];
1
6
  export declare function replaceElementsOf(parent: HTMLElement, newChildren: Node | Node[]): void;
2
7
  export declare function elementsToFragment(elm: Node | Node[]): Node;
3
8
  export declare function removeElementRange(parent: Node, start: Node, end: Node): void;
4
9
  export declare function setElementProp(elm: HTMLElement, key: string, value: unknown): void;
5
10
  export declare function setElementAttr(elm: HTMLElement, key: string, value: string | null): void;
6
- export declare function setElementStyle(elm: HTMLElement, value: string | Record<string, unknown> | null): void;
11
+ export declare function setElementStyle(elm: HTMLElement, value?: string | Record<string, unknown> | null): void;
7
12
  export declare function isEventProp(name: string): boolean;
8
13
  export declare function setElementClass(elm: HTMLElement, value: string | Record<string, boolean> | null | undefined): void;
9
14
  //# sourceMappingURL=dom-utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dom-utils.d.ts","sourceRoot":"","sources":["../../src/vdom/dom-utils.ts"],"names":[],"mappings":"AAAA,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,WAAW,EACnB,WAAW,EAAE,IAAI,GAAG,IAAI,EAAE,QAO3B;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,IAAI,GAAG,IAAI,EAAE,GAAG,IAAI,CAc3D;AAGD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,QAQtE;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,QAE3E;AAED,wBAAgB,cAAc,CAC5B,GAAG,EAAE,WAAW,EAChB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,GAAG,IAAI,QAOrB;AAED,wBAAgB,eAAe,CAC7B,GAAG,EAAE,WAAW,EAChB,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,QAe/C;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED,wBAAgB,eAAe,CAC7B,GAAG,EAAE,WAAW,EAChB,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,QA0B3D"}
1
+ {"version":3,"file":"dom-utils.d.ts","sourceRoot":"","sources":["../../src/vdom/dom-utils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,CAAC,IAAI,GAAG,IAAI,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,CAa7D;AAED,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,WAAW,EACnB,WAAW,EAAE,IAAI,GAAG,IAAI,EAAE,QAO3B;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,IAAI,GAAG,IAAI,EAAE,GAAG,IAAI,CAc3D;AAGD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,QAQtE;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,QAE3E;AAED,wBAAgB,cAAc,CAC5B,GAAG,EAAE,WAAW,EAChB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,GAAG,IAAI,QAOrB;AAED,wBAAgB,eAAe,CAC7B,GAAG,EAAE,WAAW,EAChB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,QAehD;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED,wBAAgB,eAAe,CAC7B,GAAG,EAAE,WAAW,EAChB,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,QA0B3D"}
@@ -1,3 +1,22 @@
1
+ /**
2
+ * Efficiently flatten a Node or Node[] result without creating intermediate arrays.
3
+ * Used to optimize mount operations that return either Node or Node[].
4
+ */
5
+ export function flattenNodes(items) {
6
+ const result = [];
7
+ for (let i = 0; i < items.length; i++) {
8
+ const item = items[i];
9
+ if (Array.isArray(item)) {
10
+ for (let j = 0; j < item.length; j++) {
11
+ result.push(item[j]);
12
+ }
13
+ }
14
+ else {
15
+ result.push(item);
16
+ }
17
+ }
18
+ return result;
19
+ }
1
20
  export function replaceElementsOf(parent, newChildren) {
2
21
  if (Array.isArray(newChildren)) {
3
22
  parent.replaceChildren(...newChildren);
@@ -42,7 +61,7 @@ export function setElementAttr(elm, key, value) {
42
61
  }
43
62
  }
44
63
  export function setElementStyle(elm, value) {
45
- if (value === null) {
64
+ if (value === null || value === undefined) {
46
65
  elm.removeAttribute("style");
47
66
  return;
48
67
  }
@@ -5,4 +5,16 @@ import { RootVNode } from "./RootVNode";
5
5
  import { TextVNode } from "./TextVNode";
6
6
  export type VNode = ElementVNode | FragmentVNode | ComponentVNode | TextVNode | RootVNode;
7
7
  export type Props = Record<string, unknown>;
8
+ /**
9
+ * Bit flags to optimize VNode property checks.
10
+ * Pre-computed during VNode creation to avoid repeated conditional checks.
11
+ */
12
+ export declare const enum VFlags {
13
+ None = 0,
14
+ HasProps = 1,// Has any props at all
15
+ HasClass = 2,// Has class or className prop
16
+ HasStyle = 4,// Has style prop
17
+ HasEvents = 8,// Has event listeners (onXxx props)
18
+ HasDataAttrs = 16
19
+ }
8
20
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/vdom/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,MAAM,MAAM,KAAK,GACb,YAAY,GACZ,aAAa,GACb,cAAc,GACd,SAAS,GACT,SAAS,CAAC;AAEd,MAAM,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/vdom/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,MAAM,MAAM,KAAK,GACb,YAAY,GACZ,aAAa,GACb,cAAc,GACd,SAAS,GACT,SAAS,CAAC;AAEd,MAAM,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE5C;;;GAGG;AACH,0BAAkB,MAAM;IACtB,IAAI,IAAI;IACR,QAAQ,IAAS,CAAQ,uBAAuB;IAChD,QAAQ,IAAS,CAAQ,8BAA8B;IACvD,QAAQ,IAAS,CAAQ,iBAAiB;IAC1C,SAAS,IAAS,CAAO,oCAAoC;IAC7D,YAAY,KAAS;CACtB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rask-ui",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",