@ryupold/vode 1.1.9 → 1.3.0

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.
package/dist/vode.mjs CHANGED
@@ -1,4 +1,9 @@
1
1
  // src/vode.ts
2
+ var globals = {
3
+ currentViewTransition: undefined,
4
+ requestAnimationFrame: window.requestAnimationFrame ? window.requestAnimationFrame.bind(window) : (cb) => cb(),
5
+ startViewTransition: document.startViewTransition ? document.startViewTransition.bind(document) : null
6
+ };
2
7
  function vode(tag, props, ...children) {
3
8
  if (!tag)
4
9
  throw new Error("first argument to vode() must be a tag name or a vode");
@@ -17,12 +22,16 @@ function app(container, state, dom, ...initialPatches) {
17
22
  if (typeof dom !== "function")
18
23
  throw new Error("third argument to app() must be a function that returns a vode");
19
24
  const _vode = {};
20
- _vode.stats = { lastRenderTime: 0, renderCount: 0, liveEffectCount: 0, patchCount: 0, renderPatchCount: 0 };
25
+ _vode.syncRenderer = globals.requestAnimationFrame;
26
+ _vode.asyncRenderer = globals.startViewTransition;
27
+ _vode.qSync = null;
28
+ _vode.qAsync = null;
29
+ _vode.stats = { lastSyncRenderTime: 0, lastAsyncRenderTime: 0, syncRenderCount: 0, asyncRenderCount: 0, liveEffectCount: 0, patchCount: 0, syncRenderPatchCount: 0, asyncRenderPatchCount: 0 };
21
30
  Object.defineProperty(state, "patch", {
22
31
  enumerable: false,
23
32
  configurable: true,
24
33
  writable: false,
25
- value: async (action) => {
34
+ value: async (action, isAsync) => {
26
35
  if (!action || typeof action !== "function" && typeof action !== "object")
27
36
  return;
28
37
  _vode.stats.patchCount++;
@@ -34,13 +43,13 @@ function app(container, state, dom, ...initialPatches) {
34
43
  while (v.done === false) {
35
44
  _vode.stats.liveEffectCount++;
36
45
  try {
37
- _vode.patch(v.value);
46
+ _vode.patch(v.value, isAsync);
38
47
  v = await generator.next();
39
48
  } finally {
40
49
  _vode.stats.liveEffectCount--;
41
50
  }
42
51
  }
43
- _vode.patch(v.value);
52
+ _vode.patch(v.value, isAsync);
44
53
  } finally {
45
54
  _vode.stats.liveEffectCount--;
46
55
  }
@@ -48,60 +57,96 @@ function app(container, state, dom, ...initialPatches) {
48
57
  _vode.stats.liveEffectCount++;
49
58
  try {
50
59
  const nextState = await action;
51
- _vode.patch(nextState);
60
+ _vode.patch(nextState, isAsync);
52
61
  } finally {
53
62
  _vode.stats.liveEffectCount--;
54
63
  }
55
64
  } else if (Array.isArray(action)) {
56
- if (typeof action[0] === "function") {
57
- if (action.length > 1)
58
- _vode.patch(action[0](_vode.state, ...action.slice(1)));
59
- else
60
- _vode.patch(action[0](_vode.state));
65
+ if (action.length > 0) {
66
+ for (const p of action) {
67
+ _vode.patch(p, !document.hidden && !!_vode.asyncRenderer);
68
+ }
61
69
  } else {
62
- _vode.stats.patchCount--;
70
+ _vode.qSync = mergeState(_vode.qSync || {}, _vode.qAsync, false);
71
+ _vode.qAsync = null;
72
+ globals.currentViewTransition?.skipTransition();
73
+ _vode.stats.syncRenderPatchCount++;
74
+ _vode.renderSync();
63
75
  }
64
76
  } else if (typeof action === "function") {
65
- _vode.patch(action(_vode.state));
77
+ _vode.patch(action(_vode.state), isAsync);
66
78
  } else {
67
- _vode.stats.renderPatchCount++;
68
- _vode.q = mergeState(_vode.q || {}, action, false);
69
- if (!_vode.isRendering)
70
- _vode.render();
79
+ if (isAsync) {
80
+ _vode.stats.asyncRenderPatchCount++;
81
+ _vode.qAsync = mergeState(_vode.qAsync || {}, action, false);
82
+ await _vode.renderAsync();
83
+ } else {
84
+ _vode.stats.syncRenderPatchCount++;
85
+ _vode.qSync = mergeState(_vode.qSync || {}, action, false);
86
+ _vode.renderSync();
87
+ }
71
88
  }
72
89
  }
73
90
  });
74
- Object.defineProperty(_vode, "render", {
91
+ function renderDom(isAsync) {
92
+ const sw = Date.now();
93
+ const vom = dom(_vode.state);
94
+ _vode.vode = render(_vode.state, _vode.patch, container.parentElement, 0, _vode.vode, vom);
95
+ if (container.tagName.toUpperCase() !== vom[0].toUpperCase()) {
96
+ container = _vode.vode.node;
97
+ container._vode = _vode;
98
+ }
99
+ if (!isAsync) {
100
+ _vode.stats.lastSyncRenderTime = Date.now() - sw;
101
+ _vode.stats.syncRenderCount++;
102
+ _vode.isRendering = false;
103
+ if (_vode.qSync)
104
+ _vode.renderSync();
105
+ }
106
+ }
107
+ const sr = renderDom.bind(null, false);
108
+ const ar = renderDom.bind(null, true);
109
+ Object.defineProperty(_vode, "renderSync", {
75
110
  enumerable: false,
76
111
  configurable: true,
77
112
  writable: false,
78
- value: () => requestAnimationFrame(() => {
79
- if (_vode.isRendering || !_vode.q)
113
+ value: () => {
114
+ if (_vode.isRendering || !_vode.qSync)
80
115
  return;
81
116
  _vode.isRendering = true;
117
+ _vode.state = mergeState(_vode.state, _vode.qSync, true);
118
+ _vode.qSync = null;
119
+ _vode.syncRenderer(sr);
120
+ }
121
+ });
122
+ Object.defineProperty(_vode, "renderAsync", {
123
+ enumerable: false,
124
+ configurable: true,
125
+ writable: false,
126
+ value: async () => {
127
+ if (_vode.isAnimating || !_vode.qAsync)
128
+ return;
129
+ await globals.currentViewTransition?.updateCallbackDone;
130
+ if (_vode.isAnimating || !_vode.qAsync || document.hidden)
131
+ return;
132
+ _vode.isAnimating = true;
82
133
  const sw = Date.now();
83
134
  try {
84
- _vode.state = mergeState(_vode.state, _vode.q, true);
85
- _vode.q = null;
86
- const vom = dom(_vode.state);
87
- _vode.vode = render(_vode.state, _vode.patch, container.parentElement, 0, _vode.vode, vom);
88
- if (container.tagName.toUpperCase() !== vom[0].toUpperCase()) {
89
- container = _vode.vode.node;
90
- container._vode = _vode;
91
- }
135
+ _vode.state = mergeState(_vode.state, _vode.qAsync, true);
136
+ _vode.qAsync = null;
137
+ globals.currentViewTransition = _vode.asyncRenderer(ar);
138
+ await globals.currentViewTransition?.updateCallbackDone;
92
139
  } finally {
93
- _vode.isRendering = false;
94
- _vode.stats.renderCount++;
95
- _vode.stats.lastRenderTime = Date.now() - sw;
96
- if (_vode.q) {
97
- _vode.render();
98
- }
140
+ _vode.stats.lastAsyncRenderTime = Date.now() - sw;
141
+ _vode.stats.asyncRenderCount++;
142
+ _vode.isAnimating = false;
99
143
  }
100
- })
144
+ if (_vode.qAsync)
145
+ _vode.renderAsync();
146
+ }
101
147
  });
102
148
  _vode.patch = state.patch;
103
149
  _vode.state = state;
104
- _vode.q = null;
105
150
  const root = container;
106
151
  root._vode = _vode;
107
152
  _vode.vode = render(state, _vode.patch, container.parentElement, Array.from(container.parentElement.children).indexOf(container), hydrate(container, true), dom(state));
@@ -175,49 +220,6 @@ function props(vode2) {
175
220
  }
176
221
  return;
177
222
  }
178
- function mergeClass(a, b) {
179
- if (!a)
180
- return b;
181
- if (!b)
182
- return a;
183
- if (typeof a === "string" && typeof b === "string") {
184
- const aSplit = a.split(" ");
185
- const bSplit = b.split(" ");
186
- const classSet = new Set([...aSplit, ...bSplit]);
187
- return Array.from(classSet).join(" ").trim();
188
- } else if (typeof a === "string" && Array.isArray(b)) {
189
- const classSet = new Set([...b, ...a.split(" ")]);
190
- return Array.from(classSet).join(" ").trim();
191
- } else if (Array.isArray(a) && typeof b === "string") {
192
- const classSet = new Set([...a, ...b.split(" ")]);
193
- return Array.from(classSet).join(" ").trim();
194
- } else if (Array.isArray(a) && Array.isArray(b)) {
195
- const classSet = new Set([...a, ...b]);
196
- return Array.from(classSet).join(" ").trim();
197
- } else if (typeof a === "string" && typeof b === "object") {
198
- return { [a]: true, ...b };
199
- } else if (typeof a === "object" && typeof b === "string") {
200
- return { ...a, [b]: true };
201
- } else if (typeof a === "object" && typeof b === "object") {
202
- return { ...a, ...b };
203
- } else if (typeof a === "object" && Array.isArray(b)) {
204
- const aa = { ...a };
205
- for (const item of b) {
206
- aa[item] = true;
207
- }
208
- return aa;
209
- } else if (Array.isArray(a) && typeof b === "object") {
210
- const aa = {};
211
- for (const item of a) {
212
- aa[item] = true;
213
- }
214
- for (const bKey of Object.keys(b)) {
215
- aa[bKey] = b[bKey];
216
- }
217
- return aa;
218
- }
219
- throw new Error(`cannot merge classes of ${a} (${typeof a}) and ${b} (${typeof b})`);
220
- }
221
223
  function children(vode2) {
222
224
  const start = childrenStart(vode2);
223
225
  if (start > 0) {
@@ -269,7 +271,7 @@ function mergeState(target, source, allowDeletion) {
269
271
  }
270
272
  return target;
271
273
  }
272
- function render(state, patch, parent, childIndex, oldVode, newVode, svg) {
274
+ function render(state, patch, parent, childIndex, oldVode, newVode, xmlns) {
273
275
  newVode = remember(state, newVode, oldVode);
274
276
  const isNoVode = !newVode || typeof newVode === "number" || typeof newVode === "boolean";
275
277
  if (newVode === oldVode || !oldVode && isNoVode) {
@@ -313,15 +315,15 @@ function render(state, patch, parent, childIndex, oldVode, newVode, svg) {
313
315
  return text;
314
316
  }
315
317
  if (isNode && (!oldNode || oldIsText || oldVode[0] !== newVode[0])) {
316
- svg = svg || newVode[0] === "svg";
317
- const newNode = svg ? document.createElementNS("http://www.w3.org/2000/svg", newVode[0]) : document.createElement(newVode[0]);
318
- newVode.node = newNode;
319
318
  const newvode = newVode;
320
319
  if (1 in newvode) {
321
320
  newvode[1] = remember(state, newvode[1], undefined);
322
321
  }
323
322
  const properties = props(newVode);
324
- patchProperties(patch, newNode, undefined, properties, svg);
323
+ xmlns = properties?.xmlns || xmlns;
324
+ const newNode = xmlns ? document.createElementNS(xmlns, newVode[0]) : document.createElement(newVode[0]);
325
+ newVode.node = newNode;
326
+ patchProperties(state, patch, newNode, undefined, properties);
325
327
  if (oldNode) {
326
328
  oldNode.onUnmount && patch(oldNode.onUnmount(oldNode));
327
329
  oldNode.replaceWith(newNode);
@@ -336,7 +338,7 @@ function render(state, patch, parent, childIndex, oldVode, newVode, svg) {
336
338
  if (newChildren) {
337
339
  for (let i = 0;i < newChildren.length; i++) {
338
340
  const child2 = newChildren[i];
339
- const attached = render(state, patch, newNode, i, undefined, child2, svg);
341
+ const attached = render(state, patch, newNode, i, undefined, child2, xmlns);
340
342
  newVode[properties ? i + 2 : i + 1] = attached;
341
343
  }
342
344
  }
@@ -344,7 +346,6 @@ function render(state, patch, parent, childIndex, oldVode, newVode, svg) {
344
346
  return newVode;
345
347
  }
346
348
  if (!oldIsText && isNode && oldVode[0] === newVode[0]) {
347
- svg = svg || newVode[0] === "svg";
348
349
  newVode.node = oldNode;
349
350
  const newvode = newVode;
350
351
  const oldvode = oldVode;
@@ -354,12 +355,12 @@ function render(state, patch, parent, childIndex, oldVode, newVode, svg) {
354
355
  newvode[1] = remember(state, newvode[1], oldvode[1]);
355
356
  if (prev !== newvode[1]) {
356
357
  const properties = props(newVode);
357
- patchProperties(patch, oldNode, props(oldVode), properties, svg);
358
+ patchProperties(state, patch, oldNode, props(oldVode), properties);
358
359
  hasProps = !!properties;
359
360
  }
360
361
  } else {
361
362
  const properties = props(newVode);
362
- patchProperties(patch, oldNode, props(oldVode), properties, svg);
363
+ patchProperties(state, patch, oldNode, props(oldVode), properties);
363
364
  hasProps = !!properties;
364
365
  }
365
366
  const newKids = children(newVode);
@@ -368,7 +369,7 @@ function render(state, patch, parent, childIndex, oldVode, newVode, svg) {
368
369
  for (let i = 0;i < newKids.length; i++) {
369
370
  const child2 = newKids[i];
370
371
  const oldChild = oldKids && oldKids[i];
371
- const attached = render(state, patch, oldNode, i, oldChild, child2, svg);
372
+ const attached = render(state, patch, oldNode, i, oldChild, child2, xmlns);
372
373
  if (attached) {
373
374
  newVode[hasProps ? i + 2 : i + 1] = attached;
374
375
  }
@@ -425,7 +426,7 @@ function unwrap(c, s) {
425
426
  return c;
426
427
  }
427
428
  }
428
- function patchProperties(patch, node, oldProps, newProps, isSvg) {
429
+ function patchProperties(s, patch, node, oldProps, newProps) {
429
430
  if (!newProps && !oldProps)
430
431
  return;
431
432
  if (oldProps) {
@@ -434,9 +435,9 @@ function patchProperties(patch, node, oldProps, newProps, isSvg) {
434
435
  const newValue = newProps?.[key];
435
436
  if (oldValue !== newValue) {
436
437
  if (newProps)
437
- newProps[key] = patchProperty(patch, node, key, oldValue, newValue, isSvg);
438
+ newProps[key] = patchProperty(s, patch, node, key, oldValue, newValue);
438
439
  else
439
- patchProperty(patch, node, key, oldValue, undefined, isSvg);
440
+ patchProperty(s, patch, node, key, oldValue, undefined);
440
441
  }
441
442
  }
442
443
  }
@@ -444,17 +445,17 @@ function patchProperties(patch, node, oldProps, newProps, isSvg) {
444
445
  for (const key in newProps) {
445
446
  if (!(key in oldProps)) {
446
447
  const newValue = newProps[key];
447
- newProps[key] = patchProperty(patch, node, key, undefined, newValue, isSvg);
448
+ newProps[key] = patchProperty(s, patch, node, key, undefined, newValue);
448
449
  }
449
450
  }
450
451
  } else if (newProps) {
451
452
  for (const key in newProps) {
452
453
  const newValue = newProps[key];
453
- newProps[key] = patchProperty(patch, node, key, undefined, newValue, isSvg);
454
+ newProps[key] = patchProperty(s, patch, node, key, undefined, newValue);
454
455
  }
455
456
  }
456
457
  }
457
- function patchProperty(patch, node, key, oldValue, newValue, isSvg) {
458
+ function patchProperty(s, patch, node, key, oldValue, newValue) {
458
459
  if (key === "style") {
459
460
  if (!newValue) {
460
461
  node.style.cssText = "";
@@ -475,35 +476,17 @@ function patchProperty(patch, node, key, oldValue, newValue, isSvg) {
475
476
  }
476
477
  }
477
478
  } else if (key === "class") {
478
- if (isSvg) {
479
- if (newValue) {
480
- const newClass = classString(newValue);
481
- node.classList.value = newClass;
482
- } else {
483
- node.classList.value = "";
484
- }
479
+ if (newValue) {
480
+ node.setAttribute("class", classString(newValue));
485
481
  } else {
486
- if (newValue) {
487
- const newClass = classString(newValue);
488
- node.className = newClass;
489
- } else {
490
- node.className = "";
491
- }
482
+ node.removeAttribute("class");
492
483
  }
493
484
  } else if (key[0] === "o" && key[1] === "n") {
494
485
  if (newValue) {
495
486
  let eventHandler = null;
496
487
  if (typeof newValue === "function") {
497
488
  const action = newValue;
498
- eventHandler = (evt) => patch([action, evt]);
499
- } else if (Array.isArray(newValue)) {
500
- const arr = newValue;
501
- const action = newValue[0];
502
- if (arr.length > 1) {
503
- eventHandler = () => patch([action, ...arr.slice(1)]);
504
- } else {
505
- eventHandler = (evt) => patch([action, evt]);
506
- }
489
+ eventHandler = (evt) => patch(action(s, evt));
507
490
  } else if (typeof newValue === "object") {
508
491
  eventHandler = () => patch(newValue);
509
492
  }
@@ -612,6 +595,7 @@ var RUBY = "ruby";
612
595
  var S = "s";
613
596
  var SAMP = "samp";
614
597
  var SCRIPT = "script";
598
+ var SEARCH = "search";
615
599
  var SECTION = "section";
616
600
  var SELECT = "select";
617
601
  var SLOT = "slot";
@@ -637,6 +621,7 @@ var TR = "tr";
637
621
  var TRACK = "track";
638
622
  var U = "u";
639
623
  var UL = "ul";
624
+ var VAR = "var";
640
625
  var VIDEO = "video";
641
626
  var WBR = "wbr";
642
627
  var ANIMATE = "animate";
@@ -728,6 +713,132 @@ var MTR = "mtr";
728
713
  var MUNDER = "munder";
729
714
  var MUNDEROVER = "munderover";
730
715
  var SEMANTICS = "semantics";
716
+ // src/merge-class.ts
717
+ function mergeClass(...classes) {
718
+ if (!classes || classes.length === 0)
719
+ return null;
720
+ if (classes.length === 1)
721
+ return classes[0];
722
+ let finalClass = classes[0];
723
+ for (let index = 1;index < classes.length; index++) {
724
+ const a = finalClass, b = classes[index];
725
+ if (!a) {
726
+ finalClass = b;
727
+ } else if (!b) {
728
+ continue;
729
+ } else if (typeof a === "string" && typeof b === "string") {
730
+ const aSplit = a.split(" ");
731
+ const bSplit = b.split(" ");
732
+ const classSet = new Set([...aSplit, ...bSplit]);
733
+ finalClass = Array.from(classSet).join(" ").trim();
734
+ } else if (typeof a === "string" && Array.isArray(b)) {
735
+ const classSet = new Set([...b, ...a.split(" ")]);
736
+ finalClass = Array.from(classSet).join(" ").trim();
737
+ } else if (Array.isArray(a) && typeof b === "string") {
738
+ const classSet = new Set([...a, ...b.split(" ")]);
739
+ finalClass = Array.from(classSet).join(" ").trim();
740
+ } else if (Array.isArray(a) && Array.isArray(b)) {
741
+ const classSet = new Set([...a, ...b]);
742
+ finalClass = Array.from(classSet).join(" ").trim();
743
+ } else if (typeof a === "string" && typeof b === "object") {
744
+ finalClass = { [a]: true, ...b };
745
+ } else if (typeof a === "object" && typeof b === "string") {
746
+ finalClass = { ...a, [b]: true };
747
+ } else if (typeof a === "object" && typeof b === "object") {
748
+ finalClass = { ...a, ...b };
749
+ } else if (typeof a === "object" && Array.isArray(b)) {
750
+ const aa = { ...a };
751
+ for (const item of b) {
752
+ aa[item] = true;
753
+ }
754
+ finalClass = aa;
755
+ } else if (Array.isArray(a) && typeof b === "object") {
756
+ const aa = {};
757
+ for (const item of a) {
758
+ aa[item] = true;
759
+ }
760
+ for (const bKey of Object.keys(b)) {
761
+ aa[bKey] = b[bKey];
762
+ }
763
+ finalClass = aa;
764
+ } else
765
+ throw new Error(`cannot merge classes of ${a} (${typeof a}) and ${b} (${typeof b})`);
766
+ }
767
+ return finalClass;
768
+ }
769
+ // src/state-context.ts
770
+ class KeyStateContext {
771
+ state;
772
+ path;
773
+ keys;
774
+ constructor(state, path) {
775
+ this.state = state;
776
+ this.path = path;
777
+ this.keys = path.split(".");
778
+ }
779
+ get() {
780
+ const keys = this.keys;
781
+ let raw = this.state ? this.state[keys[0]] : undefined;
782
+ for (let i = 1;i < keys.length && !!raw; i++) {
783
+ raw = raw[keys[i]];
784
+ }
785
+ return raw;
786
+ }
787
+ put(value) {
788
+ this.putDeep(value, this.state);
789
+ }
790
+ patch(value) {
791
+ if (Array.isArray(value)) {
792
+ const animation = [];
793
+ for (const v of value) {
794
+ animation.push(this.createPatch(v));
795
+ }
796
+ this.state.patch(animation);
797
+ }
798
+ this.state.patch(this.createPatch(value));
799
+ }
800
+ createPatch(value) {
801
+ const renderPatch = {};
802
+ this.putDeep(value, renderPatch);
803
+ return renderPatch;
804
+ }
805
+ putDeep(value, target) {
806
+ const keys = this.keys;
807
+ if (keys.length > 1) {
808
+ let i = 0;
809
+ let raw = target[keys[i]];
810
+ if (typeof raw !== "object" || raw === null) {
811
+ target[keys[i]] = raw = {};
812
+ }
813
+ for (i = 1;i < keys.length - 1; i++) {
814
+ const p = raw;
815
+ raw = raw[keys[i]];
816
+ if (typeof raw !== "object" || raw === null) {
817
+ p[keys[i]] = raw = {};
818
+ }
819
+ }
820
+ raw[keys[i]] = value;
821
+ } else {
822
+ if (typeof target[keys[0]] === "object" && typeof value === "object")
823
+ Object.assign(target[keys[0]], value);
824
+ else
825
+ target[keys[0]] = value;
826
+ }
827
+ }
828
+ }
829
+
830
+ class DelegateStateContext {
831
+ state;
832
+ get;
833
+ put;
834
+ patch;
835
+ constructor(state, get, put, patch) {
836
+ this.state = state;
837
+ this.get = get;
838
+ this.put = put;
839
+ this.patch = patch;
840
+ }
841
+ }
731
842
  export {
732
843
  vode,
733
844
  tag,
@@ -735,6 +846,7 @@ export {
735
846
  mergeClass,
736
847
  memo,
737
848
  hydrate,
849
+ globals,
738
850
  createState,
739
851
  createPatch,
740
852
  childrenStart,
@@ -745,6 +857,7 @@ export {
745
857
  WBR,
746
858
  VIEW,
747
859
  VIDEO,
860
+ VAR,
748
861
  USE,
749
862
  UL,
750
863
  U,
@@ -780,6 +893,7 @@ export {
780
893
  SEMANTICS,
781
894
  SELECT,
782
895
  SECTION,
896
+ SEARCH,
783
897
  SCRIPT,
784
898
  SAMP,
785
899
  S,
@@ -847,6 +961,7 @@ export {
847
961
  LI,
848
962
  LEGEND,
849
963
  LABEL,
964
+ KeyStateContext,
850
965
  KBD,
851
966
  INS,
852
967
  INPUT,
@@ -901,6 +1016,7 @@ export {
901
1016
  EMBED,
902
1017
  EM,
903
1018
  ELLIPSE,
1019
+ DelegateStateContext,
904
1020
  DT,
905
1021
  DL,
906
1022
  DIV,
package/index.ts CHANGED
@@ -1,2 +1,6 @@
1
1
  export * from "./src/vode.js";
2
- export * from "./src/vode-tags.js";
2
+
3
+ // utilities
4
+ export * from "./src/vode-tags.js";
5
+ export * from "./src/merge-class.js";
6
+ export * from "./src/state-context.js";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ryupold/vode",
3
- "version": "1.1.9",
4
- "description": "Small web framework for minimal websites",
3
+ "version": "1.3.0",
4
+ "description": "a minimalist web framework",
5
5
  "author": "Michael Scherbakow (ryupold)",
6
6
  "license": "MIT",
7
7
  "icon": "icon.webp",
@@ -33,8 +33,8 @@
33
33
  "watch": "tsc -b -w"
34
34
  },
35
35
  "devDependencies": {
36
- "bun": "1.2.23",
37
- "esbuild": "0.25.10",
36
+ "bun": "1.3.1",
37
+ "esbuild": "0.25.11",
38
38
  "typescript": "5.9.3"
39
39
  }
40
40
  }
@@ -0,0 +1,62 @@
1
+ import { ClassProp } from "./vode.js";
2
+
3
+ /** merge `ClassProp`s regardless of structure */
4
+ export function mergeClass(...classes: ClassProp[]): ClassProp {
5
+ if ((!classes || classes.length === 0)) return null;
6
+ if (classes.length === 1) return classes[0];
7
+
8
+ let finalClass: ClassProp = classes[0];
9
+ for (let index = 1; index < classes.length; index++) {
10
+ const a = finalClass, b = classes[index];
11
+ if (!a) {
12
+ finalClass = b;
13
+ }
14
+ else if (!b) {
15
+ continue;
16
+ }
17
+ else if (typeof a === "string" && typeof b === "string") {
18
+ const aSplit = a.split(" ");
19
+ const bSplit = b.split(" ");
20
+ const classSet = new Set([...aSplit, ...bSplit]);
21
+ finalClass = Array.from(classSet).join(" ").trim();
22
+ }
23
+ else if (typeof a === "string" && Array.isArray(b)) {
24
+ const classSet = new Set([...b, ...a.split(" ")]);
25
+ finalClass = Array.from(classSet).join(" ").trim();
26
+ }
27
+ else if (Array.isArray(a) && typeof b === "string") {
28
+ const classSet = new Set([...a, ...b.split(" ")]);
29
+ finalClass = Array.from(classSet).join(" ").trim();
30
+ }
31
+ else if (Array.isArray(a) && Array.isArray(b)) {
32
+ const classSet = new Set([...a, ...b]);
33
+ finalClass = Array.from(classSet).join(" ").trim();
34
+ }
35
+ else if (typeof a === "string" && typeof b === "object") {
36
+ finalClass = { [a]: true, ...b };
37
+ }
38
+ else if (typeof a === "object" && typeof b === "string") {
39
+ finalClass = { ...a, [b]: true };
40
+ }
41
+ else if (typeof a === "object" && typeof b === "object") {
42
+ finalClass = { ...a, ...b };
43
+ } else if (typeof a === "object" && Array.isArray(b)) {
44
+ const aa = { ...a };
45
+ for (const item of b as string[]) {
46
+ (<Record<string, boolean | null | undefined>>aa)[item] = true;
47
+ }
48
+ finalClass = aa;
49
+ } else if (Array.isArray(a) && typeof b === "object") {
50
+ const aa: Record<string, any> = {};
51
+ for (const item of a as string[]) {
52
+ aa[item] = true;
53
+ }
54
+ for (const bKey of Object.keys(b!)) {
55
+ aa[bKey] = (<Record<string, boolean | null | undefined>>b)[bKey];
56
+ }
57
+ finalClass = aa;
58
+ }
59
+ else throw new Error(`cannot merge classes of ${a} (${typeof a}) and ${b} (${typeof b})`);
60
+ }
61
+ return finalClass;
62
+ }