@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/README.md CHANGED
@@ -6,9 +6,9 @@
6
6
  [![NPM Downloads](https://img.shields.io/npm/dm/@ryupold/vode)](https://www.npmjs.com/package/@ryupold/vode)
7
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
8
8
 
9
- A small web framework for a minimalistic development flow. Zero dependencies, no build step except for typescript compilation, and a simple virtual DOM implementation that is easy to understand and use. Autocompletion out of the box due to binding to `lib.dom.d.ts`.
9
+ A compact web framework for minimalist developers. Zero dependencies, no build step except for typescript compilation, and a simple virtual DOM implementation that is easy to understand and use. Autocompletion out of the box thanks to `lib.dom.d.ts`.
10
10
 
11
- It can be used to create single page applications or isolated components with complex state. The usage of arrays gives flexibility in composition and makes refactoring easy.
11
+ It brings a primitive building block to the table that gives flexibility in composition and makes refactoring easy. Usecases can be single page applications or isolated components with complex state.
12
12
 
13
13
  ## Usage
14
14
 
@@ -116,7 +116,7 @@ const state = createState({
116
116
 
117
117
  type State = typeof state;
118
118
 
119
- const appNode = document.getElementById('app');
119
+ const appNode = document.getElementById('app')!;
120
120
 
121
121
  app<State>(appNode, state,
122
122
  (s: State) => [DIV,
@@ -399,6 +399,17 @@ const ComponentEwww = (s) => {
399
399
 
400
400
  return [DIV, s.loading ? [PROGRESS] : s.title];
401
401
  }
402
+
403
+ // ✨ experimental view transitions support ✨
404
+ // patch with a render via view transition
405
+ s.patch([{}, (s) => {/*...*/}]); //all given patches will be part of a view transition
406
+
407
+ // empty array patches command to skip the current view transition
408
+ // and set the queued animated patches until now as current state with a sync patch
409
+ s.patch([]);
410
+
411
+ // skip current view transition and start this view transition instead
412
+ s.patch([[], { loading: true }]);
402
413
  ```
403
414
 
404
415
  ### memoization
@@ -452,6 +463,7 @@ import { tag, props, children, mergeClass, hydrate } from '@ryupold/vode';
452
463
  // Merge class props intelligently
453
464
  mergeClass('foo', ['baz', 'bar']); // -> 'foo bar baz'
454
465
  mergeClass(['foo'], { bar: true, baz: false }); // -> 'foo bar'
466
+ mergeClass({zig: true, zag: false}, 'foo', ['baz', 'bar']); // -> 'zig foo bar baz'
455
467
 
456
468
  const myVode = [DIV, { class: 'foo' }, [SPAN, 'hello'], [STRONG, 'world']];
457
469
 
@@ -473,6 +485,30 @@ Like the other events they can be patches too.
473
485
  > is actually created/removed which might not always be the case during
474
486
  > rendering, as only a diff of the virtual DOM is applied.
475
487
 
488
+ ### SVG & MathML
489
+ SVG and MathML elements are supported but need the namespace defined in properties.
490
+
491
+ ```typescript
492
+ import { SVG, CIRCLE } from '@ryupold/vode';
493
+
494
+ const CompSVG = (s) =>
495
+ [SVG, { xmlns: 'http://www.w3.org/2000/svg', width: 100, height: 100 },
496
+ [CIRCLE, { cx: 50, cy: 50, r: 40, stroke: 'green', 'stroke-width': 4, fill: 'yellow' }]
497
+ ];
498
+ ```
499
+
500
+ ```typescript
501
+ import { MATH, MSUP, MI, MN } from '@ryupold/vode';
502
+
503
+ const CompMathML = (s) =>
504
+ [MATH, { xmlns: 'http://www.w3.org/1998/Math/MathML' },
505
+ [MSUP,
506
+ [MI, 'x'],
507
+ [MN, '2']
508
+ ]
509
+ ];
510
+ ```
511
+
476
512
  ### advanced usage
477
513
 
478
514
  #### isolated state
@@ -504,6 +540,24 @@ The memo with empty dependency array prevents further render calls from the oute
504
540
  so rendering of the subtree inside is controlled by the inner app.
505
541
  Take note of the fact that the top-level element of the inner app refers to the surrounding element and will change its state accordingly.
506
542
 
543
+ #### view transitions
544
+ The library has experimental support for [View Transitions API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API).
545
+ You can pass an array of patches to the `patch` function where each patch will be applied with the next available view transition.
546
+
547
+ Patching an empty array `[]` will skip the current view transition and set the queued animated patches until now as current state with a sync patch.
548
+ > Keep in mind that view transitions are not supported in all browsers yet and only one active transition can happen at a time. This feature may change significantly in the future, so do not rely on it heavily.
549
+
550
+ Scheduling behaviour can in theory be overridden with `containerNode._vode.asyncRenderer`.
551
+
552
+ ```js
553
+ // or globally disable view transitions for the vode framework
554
+ import { globals } from '@ryupold/vode';
555
+ globals.startViewTransition = null;
556
+
557
+ // set to disable view transitions for specific vode-app
558
+ containerNode._vode.asyncRenderer = null;
559
+ ```
560
+
507
561
  ### performance
508
562
 
509
563
  There are some metrics available on the appNode.
package/dist/vode.js CHANGED
@@ -61,6 +61,7 @@ var V = (() => {
61
61
  DIV: () => DIV,
62
62
  DL: () => DL,
63
63
  DT: () => DT,
64
+ DelegateStateContext: () => DelegateStateContext,
64
65
  ELLIPSE: () => ELLIPSE,
65
66
  EM: () => EM,
66
67
  EMBED: () => EMBED,
@@ -115,6 +116,7 @@ var V = (() => {
115
116
  INPUT: () => INPUT,
116
117
  INS: () => INS,
117
118
  KBD: () => KBD,
119
+ KeyStateContext: () => KeyStateContext,
118
120
  LABEL: () => LABEL,
119
121
  LEGEND: () => LEGEND,
120
122
  LI: () => LI,
@@ -182,6 +184,7 @@ var V = (() => {
182
184
  S: () => S,
183
185
  SAMP: () => SAMP,
184
186
  SCRIPT: () => SCRIPT,
187
+ SEARCH: () => SEARCH,
185
188
  SECTION: () => SECTION,
186
189
  SELECT: () => SELECT,
187
190
  SEMANTICS: () => SEMANTICS,
@@ -217,6 +220,7 @@ var V = (() => {
217
220
  U: () => U,
218
221
  UL: () => UL,
219
222
  USE: () => USE,
223
+ VAR: () => VAR,
220
224
  VIDEO: () => VIDEO,
221
225
  VIEW: () => VIEW,
222
226
  WBR: () => WBR,
@@ -227,6 +231,7 @@ var V = (() => {
227
231
  childrenStart: () => childrenStart,
228
232
  createPatch: () => createPatch,
229
233
  createState: () => createState,
234
+ globals: () => globals,
230
235
  hydrate: () => hydrate,
231
236
  memo: () => memo,
232
237
  mergeClass: () => mergeClass,
@@ -236,6 +241,11 @@ var V = (() => {
236
241
  });
237
242
 
238
243
  // src/vode.ts
244
+ var globals = {
245
+ currentViewTransition: void 0,
246
+ requestAnimationFrame: !!window.requestAnimationFrame ? window.requestAnimationFrame.bind(window) : ((cb) => cb()),
247
+ startViewTransition: !!document.startViewTransition ? document.startViewTransition.bind(document) : null
248
+ };
239
249
  function vode(tag2, props2, ...children2) {
240
250
  if (!tag2) throw new Error("first argument to vode() must be a tag name or a vode");
241
251
  if (Array.isArray(tag2)) return tag2;
@@ -247,12 +257,16 @@ var V = (() => {
247
257
  if (!state || typeof state !== "object") throw new Error("second argument to app() must be a state object");
248
258
  if (typeof dom !== "function") throw new Error("third argument to app() must be a function that returns a vode");
249
259
  const _vode = {};
250
- _vode.stats = { lastRenderTime: 0, renderCount: 0, liveEffectCount: 0, patchCount: 0, renderPatchCount: 0 };
260
+ _vode.syncRenderer = globals.requestAnimationFrame;
261
+ _vode.asyncRenderer = globals.startViewTransition;
262
+ _vode.qSync = null;
263
+ _vode.qAsync = null;
264
+ _vode.stats = { lastSyncRenderTime: 0, lastAsyncRenderTime: 0, syncRenderCount: 0, asyncRenderCount: 0, liveEffectCount: 0, patchCount: 0, syncRenderPatchCount: 0, asyncRenderPatchCount: 0 };
251
265
  Object.defineProperty(state, "patch", {
252
266
  enumerable: false,
253
267
  configurable: true,
254
268
  writable: false,
255
- value: async (action) => {
269
+ value: async (action, isAsync) => {
256
270
  if (!action || typeof action !== "function" && typeof action !== "object") return;
257
271
  _vode.stats.patchCount++;
258
272
  if (action?.next) {
@@ -263,13 +277,13 @@ var V = (() => {
263
277
  while (v.done === false) {
264
278
  _vode.stats.liveEffectCount++;
265
279
  try {
266
- _vode.patch(v.value);
280
+ _vode.patch(v.value, isAsync);
267
281
  v = await generator.next();
268
282
  } finally {
269
283
  _vode.stats.liveEffectCount--;
270
284
  }
271
285
  }
272
- _vode.patch(v.value);
286
+ _vode.patch(v.value, isAsync);
273
287
  } finally {
274
288
  _vode.stats.liveEffectCount--;
275
289
  }
@@ -277,57 +291,91 @@ var V = (() => {
277
291
  _vode.stats.liveEffectCount++;
278
292
  try {
279
293
  const nextState = await action;
280
- _vode.patch(nextState);
294
+ _vode.patch(nextState, isAsync);
281
295
  } finally {
282
296
  _vode.stats.liveEffectCount--;
283
297
  }
284
298
  } else if (Array.isArray(action)) {
285
- if (typeof action[0] === "function") {
286
- if (action.length > 1)
287
- _vode.patch(action[0](_vode.state, ...action.slice(1)));
288
- else _vode.patch(action[0](_vode.state));
299
+ if (action.length > 0) {
300
+ for (const p of action) {
301
+ _vode.patch(p, !document.hidden && !!_vode.asyncRenderer);
302
+ }
289
303
  } else {
290
- _vode.stats.patchCount--;
304
+ _vode.qSync = mergeState(_vode.qSync || {}, _vode.qAsync, false);
305
+ _vode.qAsync = null;
306
+ globals.currentViewTransition?.skipTransition();
307
+ _vode.stats.syncRenderPatchCount++;
308
+ _vode.renderSync();
291
309
  }
292
310
  } else if (typeof action === "function") {
293
- _vode.patch(action(_vode.state));
311
+ _vode.patch(action(_vode.state), isAsync);
294
312
  } else {
295
- _vode.stats.renderPatchCount++;
296
- _vode.q = mergeState(_vode.q || {}, action, false);
297
- if (!_vode.isRendering) _vode.render();
313
+ if (isAsync) {
314
+ _vode.stats.asyncRenderPatchCount++;
315
+ _vode.qAsync = mergeState(_vode.qAsync || {}, action, false);
316
+ await _vode.renderAsync();
317
+ } else {
318
+ _vode.stats.syncRenderPatchCount++;
319
+ _vode.qSync = mergeState(_vode.qSync || {}, action, false);
320
+ _vode.renderSync();
321
+ }
298
322
  }
299
323
  }
300
324
  });
301
- Object.defineProperty(_vode, "render", {
325
+ function renderDom(isAsync) {
326
+ const sw = Date.now();
327
+ const vom = dom(_vode.state);
328
+ _vode.vode = render(_vode.state, _vode.patch, container.parentElement, 0, _vode.vode, vom);
329
+ if (container.tagName.toUpperCase() !== vom[0].toUpperCase()) {
330
+ container = _vode.vode.node;
331
+ container._vode = _vode;
332
+ }
333
+ if (!isAsync) {
334
+ _vode.stats.lastSyncRenderTime = Date.now() - sw;
335
+ _vode.stats.syncRenderCount++;
336
+ _vode.isRendering = false;
337
+ if (_vode.qSync) _vode.renderSync();
338
+ }
339
+ }
340
+ const sr = renderDom.bind(null, false);
341
+ const ar = renderDom.bind(null, true);
342
+ Object.defineProperty(_vode, "renderSync", {
302
343
  enumerable: false,
303
344
  configurable: true,
304
345
  writable: false,
305
- value: () => requestAnimationFrame(() => {
306
- if (_vode.isRendering || !_vode.q) return;
346
+ value: () => {
347
+ if (_vode.isRendering || !_vode.qSync) return;
307
348
  _vode.isRendering = true;
349
+ _vode.state = mergeState(_vode.state, _vode.qSync, true);
350
+ _vode.qSync = null;
351
+ _vode.syncRenderer(sr);
352
+ }
353
+ });
354
+ Object.defineProperty(_vode, "renderAsync", {
355
+ enumerable: false,
356
+ configurable: true,
357
+ writable: false,
358
+ value: async () => {
359
+ if (_vode.isAnimating || !_vode.qAsync) return;
360
+ await globals.currentViewTransition?.updateCallbackDone;
361
+ if (_vode.isAnimating || !_vode.qAsync || document.hidden) return;
362
+ _vode.isAnimating = true;
308
363
  const sw = Date.now();
309
364
  try {
310
- _vode.state = mergeState(_vode.state, _vode.q, true);
311
- _vode.q = null;
312
- const vom = dom(_vode.state);
313
- _vode.vode = render(_vode.state, _vode.patch, container.parentElement, 0, _vode.vode, vom);
314
- if (container.tagName.toUpperCase() !== vom[0].toUpperCase()) {
315
- container = _vode.vode.node;
316
- container._vode = _vode;
317
- }
365
+ _vode.state = mergeState(_vode.state, _vode.qAsync, true);
366
+ _vode.qAsync = null;
367
+ globals.currentViewTransition = _vode.asyncRenderer(ar);
368
+ await globals.currentViewTransition?.updateCallbackDone;
318
369
  } finally {
319
- _vode.isRendering = false;
320
- _vode.stats.renderCount++;
321
- _vode.stats.lastRenderTime = Date.now() - sw;
322
- if (_vode.q) {
323
- _vode.render();
324
- }
370
+ _vode.stats.lastAsyncRenderTime = Date.now() - sw;
371
+ _vode.stats.asyncRenderCount++;
372
+ _vode.isAnimating = false;
325
373
  }
326
- })
374
+ if (_vode.qAsync) _vode.renderAsync();
375
+ }
327
376
  });
328
377
  _vode.patch = state.patch;
329
378
  _vode.state = state;
330
- _vode.q = null;
331
379
  const root = container;
332
380
  root._vode = _vode;
333
381
  _vode.vode = render(
@@ -402,47 +450,6 @@ var V = (() => {
402
450
  }
403
451
  return void 0;
404
452
  }
405
- function mergeClass(a, b) {
406
- if (!a) return b;
407
- if (!b) return a;
408
- if (typeof a === "string" && typeof b === "string") {
409
- const aSplit = a.split(" ");
410
- const bSplit = b.split(" ");
411
- const classSet = /* @__PURE__ */ new Set([...aSplit, ...bSplit]);
412
- return Array.from(classSet).join(" ").trim();
413
- } else if (typeof a === "string" && Array.isArray(b)) {
414
- const classSet = /* @__PURE__ */ new Set([...b, ...a.split(" ")]);
415
- return Array.from(classSet).join(" ").trim();
416
- } else if (Array.isArray(a) && typeof b === "string") {
417
- const classSet = /* @__PURE__ */ new Set([...a, ...b.split(" ")]);
418
- return Array.from(classSet).join(" ").trim();
419
- } else if (Array.isArray(a) && Array.isArray(b)) {
420
- const classSet = /* @__PURE__ */ new Set([...a, ...b]);
421
- return Array.from(classSet).join(" ").trim();
422
- } else if (typeof a === "string" && typeof b === "object") {
423
- return { [a]: true, ...b };
424
- } else if (typeof a === "object" && typeof b === "string") {
425
- return { ...a, [b]: true };
426
- } else if (typeof a === "object" && typeof b === "object") {
427
- return { ...a, ...b };
428
- } else if (typeof a === "object" && Array.isArray(b)) {
429
- const aa = { ...a };
430
- for (const item of b) {
431
- aa[item] = true;
432
- }
433
- return aa;
434
- } else if (Array.isArray(a) && typeof b === "object") {
435
- const aa = {};
436
- for (const item of a) {
437
- aa[item] = true;
438
- }
439
- for (const bKey of Object.keys(b)) {
440
- aa[bKey] = b[bKey];
441
- }
442
- return aa;
443
- }
444
- throw new Error(`cannot merge classes of ${a} (${typeof a}) and ${b} (${typeof b})`);
445
- }
446
453
  function children(vode2) {
447
454
  const start = childrenStart(vode2);
448
455
  if (start > 0) {
@@ -490,7 +497,7 @@ var V = (() => {
490
497
  }
491
498
  return target;
492
499
  }
493
- function render(state, patch, parent, childIndex, oldVode, newVode, svg) {
500
+ function render(state, patch, parent, childIndex, oldVode, newVode, xmlns) {
494
501
  newVode = remember(state, newVode, oldVode);
495
502
  const isNoVode = !newVode || typeof newVode === "number" || typeof newVode === "boolean";
496
503
  if (newVode === oldVode || !oldVode && isNoVode) {
@@ -534,15 +541,15 @@ var V = (() => {
534
541
  return text;
535
542
  }
536
543
  if (isNode && (!oldNode || oldIsText || oldVode[0] !== newVode[0])) {
537
- svg = svg || newVode[0] === "svg";
538
- const newNode = svg ? document.createElementNS("http://www.w3.org/2000/svg", newVode[0]) : document.createElement(newVode[0]);
539
- newVode.node = newNode;
540
544
  const newvode = newVode;
541
545
  if (1 in newvode) {
542
546
  newvode[1] = remember(state, newvode[1], void 0);
543
547
  }
544
548
  const properties = props(newVode);
545
- patchProperties(patch, newNode, void 0, properties, svg);
549
+ xmlns = properties?.xmlns || xmlns;
550
+ const newNode = xmlns ? document.createElementNS(xmlns, newVode[0]) : document.createElement(newVode[0]);
551
+ newVode.node = newNode;
552
+ patchProperties(state, patch, newNode, void 0, properties);
546
553
  if (oldNode) {
547
554
  oldNode.onUnmount && patch(oldNode.onUnmount(oldNode));
548
555
  oldNode.replaceWith(newNode);
@@ -557,7 +564,7 @@ var V = (() => {
557
564
  if (newChildren) {
558
565
  for (let i = 0; i < newChildren.length; i++) {
559
566
  const child2 = newChildren[i];
560
- const attached = render(state, patch, newNode, i, void 0, child2, svg);
567
+ const attached = render(state, patch, newNode, i, void 0, child2, xmlns);
561
568
  newVode[properties ? i + 2 : i + 1] = attached;
562
569
  }
563
570
  }
@@ -565,7 +572,6 @@ var V = (() => {
565
572
  return newVode;
566
573
  }
567
574
  if (!oldIsText && isNode && oldVode[0] === newVode[0]) {
568
- svg = svg || newVode[0] === "svg";
569
575
  newVode.node = oldNode;
570
576
  const newvode = newVode;
571
577
  const oldvode = oldVode;
@@ -575,12 +581,12 @@ var V = (() => {
575
581
  newvode[1] = remember(state, newvode[1], oldvode[1]);
576
582
  if (prev !== newvode[1]) {
577
583
  const properties = props(newVode);
578
- patchProperties(patch, oldNode, props(oldVode), properties, svg);
584
+ patchProperties(state, patch, oldNode, props(oldVode), properties);
579
585
  hasProps = !!properties;
580
586
  }
581
587
  } else {
582
588
  const properties = props(newVode);
583
- patchProperties(patch, oldNode, props(oldVode), properties, svg);
589
+ patchProperties(state, patch, oldNode, props(oldVode), properties);
584
590
  hasProps = !!properties;
585
591
  }
586
592
  const newKids = children(newVode);
@@ -589,7 +595,7 @@ var V = (() => {
589
595
  for (let i = 0; i < newKids.length; i++) {
590
596
  const child2 = newKids[i];
591
597
  const oldChild = oldKids && oldKids[i];
592
- const attached = render(state, patch, oldNode, i, oldChild, child2, svg);
598
+ const attached = render(state, patch, oldNode, i, oldChild, child2, xmlns);
593
599
  if (attached) {
594
600
  newVode[hasProps ? i + 2 : i + 1] = attached;
595
601
  }
@@ -645,15 +651,15 @@ var V = (() => {
645
651
  return c;
646
652
  }
647
653
  }
648
- function patchProperties(patch, node, oldProps, newProps, isSvg) {
654
+ function patchProperties(s, patch, node, oldProps, newProps) {
649
655
  if (!newProps && !oldProps) return;
650
656
  if (oldProps) {
651
657
  for (const key in oldProps) {
652
658
  const oldValue = oldProps[key];
653
659
  const newValue = newProps?.[key];
654
660
  if (oldValue !== newValue) {
655
- if (newProps) newProps[key] = patchProperty(patch, node, key, oldValue, newValue, isSvg);
656
- else patchProperty(patch, node, key, oldValue, void 0, isSvg);
661
+ if (newProps) newProps[key] = patchProperty(s, patch, node, key, oldValue, newValue);
662
+ else patchProperty(s, patch, node, key, oldValue, void 0);
657
663
  }
658
664
  }
659
665
  }
@@ -661,17 +667,17 @@ var V = (() => {
661
667
  for (const key in newProps) {
662
668
  if (!(key in oldProps)) {
663
669
  const newValue = newProps[key];
664
- newProps[key] = patchProperty(patch, node, key, void 0, newValue, isSvg);
670
+ newProps[key] = patchProperty(s, patch, node, key, void 0, newValue);
665
671
  }
666
672
  }
667
673
  } else if (newProps) {
668
674
  for (const key in newProps) {
669
675
  const newValue = newProps[key];
670
- newProps[key] = patchProperty(patch, node, key, void 0, newValue, isSvg);
676
+ newProps[key] = patchProperty(s, patch, node, key, void 0, newValue);
671
677
  }
672
678
  }
673
679
  }
674
- function patchProperty(patch, node, key, oldValue, newValue, isSvg) {
680
+ function patchProperty(s, patch, node, key, oldValue, newValue) {
675
681
  if (key === "style") {
676
682
  if (!newValue) {
677
683
  node.style.cssText = "";
@@ -691,35 +697,17 @@ var V = (() => {
691
697
  }
692
698
  }
693
699
  } else if (key === "class") {
694
- if (isSvg) {
695
- if (newValue) {
696
- const newClass = classString(newValue);
697
- node.classList.value = newClass;
698
- } else {
699
- node.classList.value = "";
700
- }
700
+ if (newValue) {
701
+ node.setAttribute("class", classString(newValue));
701
702
  } else {
702
- if (newValue) {
703
- const newClass = classString(newValue);
704
- node.className = newClass;
705
- } else {
706
- node.className = "";
707
- }
703
+ node.removeAttribute("class");
708
704
  }
709
705
  } else if (key[0] === "o" && key[1] === "n") {
710
706
  if (newValue) {
711
707
  let eventHandler = null;
712
708
  if (typeof newValue === "function") {
713
709
  const action = newValue;
714
- eventHandler = (evt) => patch([action, evt]);
715
- } else if (Array.isArray(newValue)) {
716
- const arr = newValue;
717
- const action = newValue[0];
718
- if (arr.length > 1) {
719
- eventHandler = () => patch([action, ...arr.slice(1)]);
720
- } else {
721
- eventHandler = (evt) => patch([action, evt]);
722
- }
710
+ eventHandler = (evt) => patch(action(s, evt));
723
711
  } else if (typeof newValue === "object") {
724
712
  eventHandler = () => patch(newValue);
725
713
  }
@@ -829,6 +817,7 @@ var V = (() => {
829
817
  var S = "s";
830
818
  var SAMP = "samp";
831
819
  var SCRIPT = "script";
820
+ var SEARCH = "search";
832
821
  var SECTION = "section";
833
822
  var SELECT = "select";
834
823
  var SLOT = "slot";
@@ -854,6 +843,7 @@ var V = (() => {
854
843
  var TRACK = "track";
855
844
  var U = "u";
856
845
  var UL = "ul";
846
+ var VAR = "var";
857
847
  var VIDEO = "video";
858
848
  var WBR = "wbr";
859
849
  var ANIMATE = "animate";
@@ -945,5 +935,136 @@ var V = (() => {
945
935
  var MUNDER = "munder";
946
936
  var MUNDEROVER = "munderover";
947
937
  var SEMANTICS = "semantics";
938
+
939
+ // src/merge-class.ts
940
+ function mergeClass(...classes) {
941
+ if (!classes || classes.length === 0) return null;
942
+ if (classes.length === 1) return classes[0];
943
+ let finalClass = classes[0];
944
+ for (let index = 1; index < classes.length; index++) {
945
+ const a = finalClass, b = classes[index];
946
+ if (!a) {
947
+ finalClass = b;
948
+ } else if (!b) {
949
+ continue;
950
+ } else if (typeof a === "string" && typeof b === "string") {
951
+ const aSplit = a.split(" ");
952
+ const bSplit = b.split(" ");
953
+ const classSet = /* @__PURE__ */ new Set([...aSplit, ...bSplit]);
954
+ finalClass = Array.from(classSet).join(" ").trim();
955
+ } else if (typeof a === "string" && Array.isArray(b)) {
956
+ const classSet = /* @__PURE__ */ new Set([...b, ...a.split(" ")]);
957
+ finalClass = Array.from(classSet).join(" ").trim();
958
+ } else if (Array.isArray(a) && typeof b === "string") {
959
+ const classSet = /* @__PURE__ */ new Set([...a, ...b.split(" ")]);
960
+ finalClass = Array.from(classSet).join(" ").trim();
961
+ } else if (Array.isArray(a) && Array.isArray(b)) {
962
+ const classSet = /* @__PURE__ */ new Set([...a, ...b]);
963
+ finalClass = Array.from(classSet).join(" ").trim();
964
+ } else if (typeof a === "string" && typeof b === "object") {
965
+ finalClass = { [a]: true, ...b };
966
+ } else if (typeof a === "object" && typeof b === "string") {
967
+ finalClass = { ...a, [b]: true };
968
+ } else if (typeof a === "object" && typeof b === "object") {
969
+ finalClass = { ...a, ...b };
970
+ } else if (typeof a === "object" && Array.isArray(b)) {
971
+ const aa = { ...a };
972
+ for (const item of b) {
973
+ aa[item] = true;
974
+ }
975
+ finalClass = aa;
976
+ } else if (Array.isArray(a) && typeof b === "object") {
977
+ const aa = {};
978
+ for (const item of a) {
979
+ aa[item] = true;
980
+ }
981
+ for (const bKey of Object.keys(b)) {
982
+ aa[bKey] = b[bKey];
983
+ }
984
+ finalClass = aa;
985
+ } else throw new Error(`cannot merge classes of ${a} (${typeof a}) and ${b} (${typeof b})`);
986
+ }
987
+ return finalClass;
988
+ }
989
+
990
+ // src/state-context.ts
991
+ var KeyStateContext = class {
992
+ constructor(state, path) {
993
+ this.state = state;
994
+ this.path = path;
995
+ this.keys = path.split(".");
996
+ }
997
+ keys;
998
+ get() {
999
+ const keys = this.keys;
1000
+ let raw = this.state ? this.state[keys[0]] : void 0;
1001
+ for (let i = 1; i < keys.length && !!raw; i++) {
1002
+ raw = raw[keys[i]];
1003
+ }
1004
+ return raw;
1005
+ }
1006
+ put(value) {
1007
+ this.putDeep(value, this.state);
1008
+ }
1009
+ patch(value) {
1010
+ if (Array.isArray(value)) {
1011
+ const animation = [];
1012
+ for (const v of value) {
1013
+ animation.push(this.createPatch(v));
1014
+ }
1015
+ this.state.patch(animation);
1016
+ }
1017
+ this.state.patch(this.createPatch(value));
1018
+ }
1019
+ /**
1020
+ * Creates a render-patch for the parent state by setting a nested sub-state value while creating necessary structure.
1021
+ *
1022
+ * @example
1023
+ * ```typescript
1024
+ * const ctx = new StateContext(state, 'user.profile.settings');
1025
+ * const patch = ctx.createPatch({ theme: 'light' });
1026
+ * // patch is { user: { profile: { settings: { theme: 'light' } } } }
1027
+ * ```
1028
+ *
1029
+ * @param value
1030
+ * @returns {{key-path}:{...: value}} render-patch for the parent state
1031
+ */
1032
+ createPatch(value) {
1033
+ const renderPatch = {};
1034
+ this.putDeep(value, renderPatch);
1035
+ return renderPatch;
1036
+ }
1037
+ putDeep(value, target) {
1038
+ const keys = this.keys;
1039
+ if (keys.length > 1) {
1040
+ let i = 0;
1041
+ let raw = target[keys[i]];
1042
+ if (typeof raw !== "object" || raw === null) {
1043
+ target[keys[i]] = raw = {};
1044
+ }
1045
+ for (i = 1; i < keys.length - 1; i++) {
1046
+ const p = raw;
1047
+ raw = raw[keys[i]];
1048
+ if (typeof raw !== "object" || raw === null) {
1049
+ p[keys[i]] = raw = {};
1050
+ }
1051
+ }
1052
+ raw[keys[i]] = value;
1053
+ } else {
1054
+ if (typeof target[keys[0]] === "object" && typeof value === "object")
1055
+ Object.assign(target[keys[0]], value);
1056
+ else
1057
+ target[keys[0]] = value;
1058
+ }
1059
+ }
1060
+ };
1061
+ var DelegateStateContext = class {
1062
+ constructor(state, get, put, patch) {
1063
+ this.state = state;
1064
+ this.get = get;
1065
+ this.put = put;
1066
+ this.patch = patch;
1067
+ }
1068
+ };
948
1069
  return __toCommonJS(index_exports);
949
1070
  })();