cx 26.4.1 → 26.4.3

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/widgets.css CHANGED
@@ -4319,6 +4319,7 @@ th.cxe-calendar-display {
4319
4319
  display: block;
4320
4320
  left: -10000px;
4321
4321
  top: -10000px;
4322
+ --cx-scss-dropdown-arrow-offset: var(--cx-js-dropdown-arrow-offset, 16px);
4322
4323
  color: #373a3c;
4323
4324
  background-color: white;
4324
4325
  border-radius: 0;
@@ -4406,18 +4407,18 @@ th.cxe-calendar-display {
4406
4407
 
4407
4408
  .cxs-place-right-down > .cxe-dropdown-arrow-fill,
4408
4409
  .cxs-place-left-down > .cxe-dropdown-arrow-fill {
4409
- top: 16px;
4410
+ top: var(--cx-scss-dropdown-arrow-offset);
4410
4411
  }
4411
4412
 
4412
4413
  .cxs-place-right-down > .cxe-dropdown-arrow-border,
4413
4414
  .cxs-place-left-down > .cxe-dropdown-arrow-border {
4414
- top: 16px;
4415
+ top: var(--cx-scss-dropdown-arrow-offset);
4415
4416
  }
4416
4417
 
4417
4418
  .cxs-place-right-up > .cxe-dropdown-arrow-fill, .cxs-place-right-up > .cxe-dropdown-arrow-border,
4418
4419
  .cxs-place-left-up > .cxe-dropdown-arrow-fill,
4419
4420
  .cxs-place-left-up > .cxe-dropdown-arrow-border {
4420
- top: calc(100% + -16px);
4421
+ top: calc(100% - var(--cx-scss-dropdown-arrow-offset));
4421
4422
  }
4422
4423
 
4423
4424
  .cxs-place-up > .cxe-dropdown-arrow-fill,
@@ -4459,17 +4460,17 @@ th.cxe-calendar-display {
4459
4460
  .cxs-place-down-right > .cxe-dropdown-arrow-fill, .cxs-place-down-right > .cxe-dropdown-arrow-border,
4460
4461
  .cxs-place-up-right > .cxe-dropdown-arrow-fill,
4461
4462
  .cxs-place-up-right > .cxe-dropdown-arrow-border {
4462
- left: 16px;
4463
+ left: var(--cx-scss-dropdown-arrow-offset);
4463
4464
  }
4464
4465
 
4465
4466
  .cxs-place-down-left > .cxe-dropdown-arrow-fill,
4466
4467
  .cxs-place-up-left > .cxe-dropdown-arrow-fill {
4467
- left: calc(100% + -16px);
4468
+ left: calc(100% - var(--cx-scss-dropdown-arrow-offset));
4468
4469
  }
4469
4470
 
4470
4471
  .cxs-place-down-left > .cxe-dropdown-arrow-border,
4471
4472
  .cxs-place-up-left > .cxe-dropdown-arrow-border {
4472
- left: calc(100% + -16px);
4473
+ left: calc(100% - var(--cx-scss-dropdown-arrow-offset));
4473
4474
  }
4474
4475
 
4475
4476
  .cxb-window {
@@ -4732,18 +4733,18 @@ th.cxe-calendar-display {
4732
4733
 
4733
4734
  .cxs-place-right-down > .cxe-tooltip-arrow-fill,
4734
4735
  .cxs-place-left-down > .cxe-tooltip-arrow-fill {
4735
- top: 16px;
4736
+ top: var(--cx-scss-dropdown-arrow-offset);
4736
4737
  }
4737
4738
 
4738
4739
  .cxs-place-right-down > .cxe-tooltip-arrow-border,
4739
4740
  .cxs-place-left-down > .cxe-tooltip-arrow-border {
4740
- top: 16px;
4741
+ top: var(--cx-scss-dropdown-arrow-offset);
4741
4742
  }
4742
4743
 
4743
4744
  .cxs-place-right-up > .cxe-tooltip-arrow-fill, .cxs-place-right-up > .cxe-tooltip-arrow-border,
4744
4745
  .cxs-place-left-up > .cxe-tooltip-arrow-fill,
4745
4746
  .cxs-place-left-up > .cxe-tooltip-arrow-border {
4746
- top: calc(100% + -16px);
4747
+ top: calc(100% - var(--cx-scss-dropdown-arrow-offset));
4747
4748
  }
4748
4749
 
4749
4750
  .cxs-place-up > .cxe-tooltip-arrow-fill,
@@ -4785,17 +4786,17 @@ th.cxe-calendar-display {
4785
4786
  .cxs-place-down-right > .cxe-tooltip-arrow-fill, .cxs-place-down-right > .cxe-tooltip-arrow-border,
4786
4787
  .cxs-place-up-right > .cxe-tooltip-arrow-fill,
4787
4788
  .cxs-place-up-right > .cxe-tooltip-arrow-border {
4788
- left: 16px;
4789
+ left: var(--cx-scss-dropdown-arrow-offset);
4789
4790
  }
4790
4791
 
4791
4792
  .cxs-place-down-left > .cxe-tooltip-arrow-fill,
4792
4793
  .cxs-place-up-left > .cxe-tooltip-arrow-fill {
4793
- left: calc(100% + -16px);
4794
+ left: calc(100% - var(--cx-scss-dropdown-arrow-offset));
4794
4795
  }
4795
4796
 
4796
4797
  .cxs-place-down-left > .cxe-tooltip-arrow-border,
4797
4798
  .cxs-place-up-left > .cxe-tooltip-arrow-border {
4798
- left: calc(100% + -16px);
4799
+ left: calc(100% - var(--cx-scss-dropdown-arrow-offset));
4799
4800
  }
4800
4801
 
4801
4802
  @keyframes cx-toast-enter-animation {
package/dist/widgets.js CHANGED
@@ -2765,13 +2765,17 @@ class OverlayBase extends ContainerBase {
2765
2765
  },
2766
2766
  key,
2767
2767
  );
2768
- return jsx(OverlayComponent, {
2769
- beaconEl: null,
2770
- instance: instance,
2771
- subscribeToBeforeDismiss: context.options.subscribeToBeforeDismiss,
2772
- parentEl: context.options.parentEl,
2773
- children: this.renderContents(context, instance),
2774
- });
2768
+ return jsx(
2769
+ OverlayComponent,
2770
+ {
2771
+ beaconEl: null,
2772
+ instance: instance,
2773
+ subscribeToBeforeDismiss: context.options.subscribeToBeforeDismiss,
2774
+ parentEl: context.options.parentEl,
2775
+ children: this.renderContents(context, instance),
2776
+ },
2777
+ key,
2778
+ );
2775
2779
  }
2776
2780
  renderContents(context, instance) {
2777
2781
  return this.renderChildren(context, instance);
@@ -3377,6 +3381,15 @@ class DropdownBase extends OverlayBase {
3377
3381
  instance.parentPositionChangeEvent = context.parentPositionChangeEvent;
3378
3382
  super.initInstance(context, instance);
3379
3383
  }
3384
+ prepareData(context, instance) {
3385
+ super.prepareData(context, instance);
3386
+ if (this.arrowOffset != null) {
3387
+ instance.data.style = {
3388
+ ...instance.data.style,
3389
+ "--cx-js-dropdown-arrow-offset": `${this.arrowOffset}px`,
3390
+ };
3391
+ }
3392
+ }
3380
3393
  explore(context, instance) {
3381
3394
  context.push("lastDropdown", instance);
3382
3395
  super.explore(context, instance);
@@ -3464,7 +3477,8 @@ class DropdownBase extends OverlayBase {
3464
3477
  if (this.matchMaxWidth) style.maxWidth = `${parentBounds.right - parentBounds.left}px`;
3465
3478
  var contentSize = this.measureNaturalDropdownSize(instance, component);
3466
3479
  var placement = this.findOptimalPlacement(contentSize, parentBounds, data.placement, component.lastPlacement);
3467
- this.applyPositioningPlacementStyles(style, placement, contentSize, parentBounds, el, false);
3480
+ var arrowAdjust = this.getArrowAdjust(component);
3481
+ this.applyPositioningPlacementStyles(style, placement, contentSize, parentBounds, el, false, arrowAdjust);
3468
3482
  component.setCustomStyle(style);
3469
3483
  this.setDirectionClass(component, placement);
3470
3484
  if (this.constrain) {
@@ -3472,7 +3486,7 @@ class DropdownBase extends OverlayBase {
3472
3486
  let newContentSize = this.measureNaturalDropdownSize(instance, component);
3473
3487
  if (newContentSize.width != contentSize.width || newContentSize.height != contentSize.height) {
3474
3488
  let newStyle = {};
3475
- this.applyPositioningPlacementStyles(newStyle, placement, newContentSize, parentBounds, el, true);
3489
+ this.applyPositioningPlacementStyles(newStyle, placement, newContentSize, parentBounds, el, true, arrowAdjust);
3476
3490
  component.setCustomStyle(newStyle);
3477
3491
  }
3478
3492
  }
@@ -3491,7 +3505,17 @@ class DropdownBase extends OverlayBase {
3491
3505
  );
3492
3506
  instance.positionChangeSubscribers.notify();
3493
3507
  }
3494
- applyFixedPositioningPlacementStyles(style, placement, contentSize, rel, el, noAuto) {
3508
+ getArrowAdjust(component) {
3509
+ if (!this.alignArrow || !this.arrow) return 0;
3510
+ if (component.cachedArrowOffset != null) return component.cachedArrowOffset;
3511
+ if (!component.el) return 0;
3512
+ let raw = getComputedStyle(component.el).getPropertyValue("--cx-scss-dropdown-arrow-offset").trim();
3513
+ let parsed = parseFloat(raw);
3514
+ if (!isFinite(parsed)) return 0;
3515
+ component.cachedArrowOffset = parsed;
3516
+ return parsed;
3517
+ }
3518
+ applyFixedPositioningPlacementStyles(style, placement, contentSize, rel, el, noAuto, arrowAdjust = 0) {
3495
3519
  let viewport = getViewportRect(this.screenPadding);
3496
3520
  style.position = "fixed";
3497
3521
  if (placement.startsWith("down")) {
@@ -3515,10 +3539,10 @@ class DropdownBase extends OverlayBase {
3515
3539
  break;
3516
3540
  case "down-right":
3517
3541
  style.right = "auto";
3518
- style.left = `${rel.left}px`;
3542
+ style.left = `${rel.left - arrowAdjust}px`;
3519
3543
  break;
3520
3544
  case "down-left":
3521
- style.right = `${document.documentElement.offsetWidth - rel.right}px`;
3545
+ style.right = `${document.documentElement.offsetWidth - rel.right - arrowAdjust}px`;
3522
3546
  style.left = "auto";
3523
3547
  break;
3524
3548
  case "up":
@@ -3528,10 +3552,10 @@ class DropdownBase extends OverlayBase {
3528
3552
  break;
3529
3553
  case "up-right":
3530
3554
  style.right = "auto";
3531
- style.left = `${rel.left}px`;
3555
+ style.left = `${rel.left - arrowAdjust}px`;
3532
3556
  break;
3533
3557
  case "up-left":
3534
- style.right = `${document.documentElement.offsetWidth - rel.right}px`;
3558
+ style.right = `${document.documentElement.offsetWidth - rel.right - arrowAdjust}px`;
3535
3559
  style.left = "auto";
3536
3560
  break;
3537
3561
  case "right":
@@ -3542,7 +3566,7 @@ class DropdownBase extends OverlayBase {
3542
3566
  style.left = `${rel.right + this.offset}px`;
3543
3567
  break;
3544
3568
  case "right-down":
3545
- style.top = `${rel.top}px`;
3569
+ style.top = `${rel.top - arrowAdjust}px`;
3546
3570
  style.right = "auto";
3547
3571
  style.bottom = "auto";
3548
3572
  style.left = `${rel.right + this.offset}px`;
@@ -3550,7 +3574,7 @@ class DropdownBase extends OverlayBase {
3550
3574
  case "right-up":
3551
3575
  style.top = "auto";
3552
3576
  style.right = "auto";
3553
- style.bottom = `${document.documentElement.offsetHeight - rel.bottom}px`;
3577
+ style.bottom = `${document.documentElement.offsetHeight - rel.bottom - arrowAdjust}px`;
3554
3578
  style.left = `${rel.right + this.offset}px`;
3555
3579
  break;
3556
3580
  case "left":
@@ -3561,7 +3585,7 @@ class DropdownBase extends OverlayBase {
3561
3585
  style.left = "auto";
3562
3586
  break;
3563
3587
  case "left-down":
3564
- style.top = `${rel.top}px`;
3588
+ style.top = `${rel.top - arrowAdjust}px`;
3565
3589
  style.right = `${document.documentElement.offsetWidth - rel.left + this.offset}px`;
3566
3590
  style.bottom = "auto";
3567
3591
  style.left = "auto";
@@ -3569,7 +3593,7 @@ class DropdownBase extends OverlayBase {
3569
3593
  case "left-up":
3570
3594
  style.top = "auto";
3571
3595
  style.right = `${document.documentElement.offsetWidth - rel.left + this.offset}px`;
3572
- style.bottom = `${document.documentElement.offsetHeight - rel.bottom}px`;
3596
+ style.bottom = `${document.documentElement.offsetHeight - rel.bottom - arrowAdjust}px`;
3573
3597
  style.left = "auto";
3574
3598
  break;
3575
3599
  case "screen-center":
@@ -3582,7 +3606,7 @@ class DropdownBase extends OverlayBase {
3582
3606
  break;
3583
3607
  }
3584
3608
  }
3585
- applyAbsolutePositioningPlacementStyles(style, placement, contentSize, rel, el, noAuto) {
3609
+ applyAbsolutePositioningPlacementStyles(style, placement, contentSize, rel, el, noAuto, arrowAdjust = 0) {
3586
3610
  var viewport = getViewportRect(this.screenPadding);
3587
3611
  style.position = "absolute";
3588
3612
  if (placement.startsWith("down")) {
@@ -3608,10 +3632,10 @@ class DropdownBase extends OverlayBase {
3608
3632
  break;
3609
3633
  case "down-right":
3610
3634
  style.right = "auto";
3611
- style.left = `0`;
3635
+ style.left = `${-arrowAdjust}px`;
3612
3636
  break;
3613
3637
  case "down-left":
3614
- style.right = `0`;
3638
+ style.right = `${-arrowAdjust}px`;
3615
3639
  style.left = "auto";
3616
3640
  break;
3617
3641
  case "up":
@@ -3621,10 +3645,10 @@ class DropdownBase extends OverlayBase {
3621
3645
  break;
3622
3646
  case "up-right":
3623
3647
  style.right = "auto";
3624
- style.left = `0`;
3648
+ style.left = `${-arrowAdjust}px`;
3625
3649
  break;
3626
3650
  case "up-left":
3627
- style.right = `0`;
3651
+ style.right = `${-arrowAdjust}px`;
3628
3652
  style.left = "auto";
3629
3653
  break;
3630
3654
  case "right":
@@ -3635,7 +3659,7 @@ class DropdownBase extends OverlayBase {
3635
3659
  style.left = `${rel.right - rel.left + this.offset}px`;
3636
3660
  break;
3637
3661
  case "right-down":
3638
- style.top = `0`;
3662
+ style.top = `${-arrowAdjust}px`;
3639
3663
  style.right = "auto";
3640
3664
  style.bottom = "auto";
3641
3665
  style.left = `${rel.right - rel.left + this.offset}px`;
@@ -3643,7 +3667,7 @@ class DropdownBase extends OverlayBase {
3643
3667
  case "right-up":
3644
3668
  style.top = "auto";
3645
3669
  style.right = "auto";
3646
- style.bottom = `0`;
3670
+ style.bottom = `${-arrowAdjust}px`;
3647
3671
  style.left = `${rel.right - rel.left + this.offset}px`;
3648
3672
  break;
3649
3673
  case "left":
@@ -3654,7 +3678,7 @@ class DropdownBase extends OverlayBase {
3654
3678
  style.left = "auto";
3655
3679
  break;
3656
3680
  case "left-down":
3657
- style.top = `0`;
3681
+ style.top = `${-arrowAdjust}px`;
3658
3682
  style.right = `${rel.right - rel.left + this.offset}px`;
3659
3683
  style.bottom = "auto";
3660
3684
  style.left = "auto";
@@ -3662,23 +3686,48 @@ class DropdownBase extends OverlayBase {
3662
3686
  case "left-up":
3663
3687
  style.top = "auto";
3664
3688
  style.right = `${rel.right - rel.left + this.offset}px`;
3665
- style.bottom = `0`;
3689
+ style.bottom = `${-arrowAdjust}px`;
3666
3690
  style.left = "auto";
3667
3691
  break;
3668
3692
  }
3669
3693
  }
3670
- applyPositioningPlacementStyles(style, placement, contentSize, parentBounds, el, noAuto) {
3694
+ applyPositioningPlacementStyles(style, placement, contentSize, parentBounds, el, noAuto, arrowAdjust = 0) {
3671
3695
  switch (this.positioning) {
3672
3696
  case "absolute":
3673
- this.applyAbsolutePositioningPlacementStyles(style, placement, contentSize, parentBounds, el, noAuto);
3697
+ this.applyAbsolutePositioningPlacementStyles(
3698
+ style,
3699
+ placement,
3700
+ contentSize,
3701
+ parentBounds,
3702
+ el,
3703
+ noAuto,
3704
+ arrowAdjust,
3705
+ );
3674
3706
  break;
3675
3707
  case "auto":
3676
3708
  if (isTouchDevice())
3677
- this.applyAbsolutePositioningPlacementStyles(style, placement, contentSize, parentBounds, el, noAuto);
3678
- else this.applyFixedPositioningPlacementStyles(style, placement, contentSize, parentBounds, el, noAuto);
3709
+ this.applyAbsolutePositioningPlacementStyles(
3710
+ style,
3711
+ placement,
3712
+ contentSize,
3713
+ parentBounds,
3714
+ el,
3715
+ noAuto,
3716
+ arrowAdjust,
3717
+ );
3718
+ else
3719
+ this.applyFixedPositioningPlacementStyles(
3720
+ style,
3721
+ placement,
3722
+ contentSize,
3723
+ parentBounds,
3724
+ el,
3725
+ noAuto,
3726
+ arrowAdjust,
3727
+ );
3679
3728
  break;
3680
3729
  default:
3681
- this.applyFixedPositioningPlacementStyles(style, placement, contentSize, parentBounds, el, noAuto);
3730
+ this.applyFixedPositioningPlacementStyles(style, placement, contentSize, parentBounds, el, noAuto, arrowAdjust);
3682
3731
  break;
3683
3732
  }
3684
3733
  }
@@ -3861,6 +3910,7 @@ DropdownBase.prototype.constrain = false;
3861
3910
  DropdownBase.prototype.positioning = "fixed";
3862
3911
  DropdownBase.prototype.touchFriendly = false;
3863
3912
  DropdownBase.prototype.arrow = false;
3913
+ DropdownBase.prototype.alignArrow = false;
3864
3914
  DropdownBase.prototype.pad = false;
3865
3915
  DropdownBase.prototype.elementExplode = 0;
3866
3916
  DropdownBase.prototype.closeOnScrollDistance = 50;
@@ -4590,6 +4640,7 @@ ContextMenu.prototype.offset = 0;
4590
4640
  ContextMenu.prototype.autoFocus = true;
4591
4641
  ContextMenu.prototype.autoFocusFirstChild = false;
4592
4642
  ContextMenu.prototype.focusable = true;
4643
+ ContextMenu.prototype.alignArrow = true;
4593
4644
  Localization.registerPrototype("cx/widgets/ContextMenu", ContextMenu);
4594
4645
  const openContextMenu = (e, content, storeOrInstance, options) => {
4595
4646
  e.preventDefault();
@@ -4788,38 +4839,6 @@ function hasQueryTokens(tokens) {
4788
4839
  }
4789
4840
  return false;
4790
4841
  }
4791
- /**
4792
- * Extract query parameter definitions from tokens
4793
- */
4794
- function extractQueryParams(tokens, optional = false) {
4795
- const params = [];
4796
- for (let i = 0; i < tokens.length; i++) {
4797
- const token = tokens[i];
4798
- if (token.type === "param") {
4799
- // Look back for the key name (e.g., 'page=' before ':page')
4800
- let key = token.value; // Default to param name
4801
- if (i > 0) {
4802
- const prevToken = tokens[i - 1];
4803
- if (prevToken.type === "static" && prevToken.value.endsWith("=")) {
4804
- // Extract key from "key="
4805
- const match = prevToken.value.match(/([^=&?]+)=$/);
4806
- if (match) {
4807
- key = match[1];
4808
- }
4809
- }
4810
- }
4811
- params.push({
4812
- name: token.value,
4813
- key,
4814
- optional,
4815
- });
4816
- } else if (token.type === "optional" && token.children) {
4817
- // Recursively extract from optional segments
4818
- params.push(...extractQueryParams(token.children, true));
4819
- }
4820
- }
4821
- return params;
4822
- }
4823
4842
  /**
4824
4843
  * Split tokens into path tokens and query tokens
4825
4844
  */
@@ -4858,7 +4877,7 @@ function escapeRegex(str) {
4858
4877
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4859
4878
  }
4860
4879
  /**
4861
- * Build a regex pattern from path tokens only (no query handling)
4880
+ * Build a regex pattern from path tokens
4862
4881
  */
4863
4882
  function buildPathRegex(tokens) {
4864
4883
  let pattern = "";
@@ -4869,17 +4888,14 @@ function buildPathRegex(tokens) {
4869
4888
  pattern += escapeRegex(token.value);
4870
4889
  break;
4871
4890
  case "param":
4872
- // Named parameter matches one path segment (no slashes)
4873
4891
  pattern += "([^/]+)";
4874
4892
  paramNames.push(token.value);
4875
4893
  break;
4876
4894
  case "splat":
4877
- // Splat matches any characters including slashes (non-greedy within constraints)
4878
4895
  pattern += "(.+?)";
4879
4896
  paramNames.push(token.value);
4880
4897
  break;
4881
4898
  case "optional":
4882
- // Optional segment - recursively build and wrap in optional group
4883
4899
  if (token.children) {
4884
4900
  const { pattern: childPattern, paramNames: childNames } = buildPathRegex(token.children);
4885
4901
  pattern += `(?:${childPattern})?`;
@@ -4893,13 +4909,104 @@ function buildPathRegex(tokens) {
4893
4909
  paramNames,
4894
4910
  };
4895
4911
  }
4912
+ /**
4913
+ * Build a regex pattern from query tokens.
4914
+ * Handles both key-value patterns (a=:a&b=:b) and structural patterns (auth(*splat)).
4915
+ * Strips leading ? and & query separators from optional children.
4916
+ */
4917
+ function buildQueryRegex(tokens) {
4918
+ let pattern = "";
4919
+ const paramNames = [];
4920
+ for (const token of tokens) {
4921
+ switch (token.type) {
4922
+ case "static":
4923
+ pattern += escapeRegex(token.value);
4924
+ break;
4925
+ case "param":
4926
+ pattern += "([^&]+)";
4927
+ paramNames.push(token.value);
4928
+ break;
4929
+ case "splat":
4930
+ pattern += "(.+?)";
4931
+ paramNames.push(token.value);
4932
+ break;
4933
+ case "optional":
4934
+ if (token.children) {
4935
+ // Strip leading query separators (? and &) from optional children
4936
+ let children = token.children;
4937
+ while (children.length > 0 && children[0].type === "querySeparator") {
4938
+ children = children.slice(1);
4939
+ }
4940
+ const { pattern: childPattern, paramNames: childNames } = buildQueryRegex(children);
4941
+ pattern += `(?:${childPattern})?`;
4942
+ paramNames.push(...childNames);
4943
+ }
4944
+ break;
4945
+ case "querySeparator":
4946
+ if (token.value === "&") {
4947
+ pattern += "&";
4948
+ }
4949
+ break;
4950
+ }
4951
+ }
4952
+ return {
4953
+ pattern,
4954
+ paramNames,
4955
+ };
4956
+ }
4957
+ function extractQueryDefs(tokens, optional = false) {
4958
+ const defs = [];
4959
+ for (let i = 0; i < tokens.length; i++) {
4960
+ const token = tokens[i];
4961
+ if (token.type === "param") {
4962
+ let key = token.value;
4963
+ if (i > 0) {
4964
+ const prevToken = tokens[i - 1];
4965
+ if (prevToken.type === "static" && prevToken.value.endsWith("=")) {
4966
+ const match = prevToken.value.match(/([^=&?]+)=$/);
4967
+ if (match) key = match[1];
4968
+ }
4969
+ }
4970
+ defs.push({
4971
+ key,
4972
+ paramName: token.value,
4973
+ optional,
4974
+ });
4975
+ } else if (token.type === "static") {
4976
+ // Extract fixed key=value pairs (e.g., 'type=bar')
4977
+ for (const part of token.value.split("&")) {
4978
+ const clean = part.replace(/^[?&]/, "");
4979
+ const eqIdx = clean.indexOf("=");
4980
+ if (eqIdx > 0 && clean.length > eqIdx + 1) {
4981
+ defs.push({
4982
+ key: clean.slice(0, eqIdx),
4983
+ fixedValue: clean.slice(eqIdx + 1),
4984
+ optional,
4985
+ });
4986
+ }
4987
+ }
4988
+ } else if (token.type === "optional" && token.children) {
4989
+ defs.push(...extractQueryDefs(token.children, true));
4990
+ }
4991
+ }
4992
+ return defs;
4993
+ }
4994
+ /**
4995
+ * Check if tokens contain splat parameters (recursively)
4996
+ */
4997
+ function hasSplats(tokens) {
4998
+ for (const token of tokens) {
4999
+ if (token.type === "splat") return true;
5000
+ if (token.type === "optional" && token.children && hasSplats(token.children)) return true;
5001
+ }
5002
+ return false;
5003
+ }
4896
5004
  /**
4897
5005
  * Parse a query string into key-value pairs
4898
5006
  */
4899
5007
  function parseQueryString(queryString) {
4900
5008
  const params = new Map();
4901
5009
  if (!queryString) return params;
4902
- // Remove leading ? if present
4903
5010
  const qs = queryString.startsWith("?") ? queryString.slice(1) : queryString;
4904
5011
  if (!qs) return params;
4905
5012
  for (const pair of qs.split("&")) {
@@ -4969,8 +5076,10 @@ class RouteParser {
4969
5076
  tokens;
4970
5077
  pathRegex;
4971
5078
  pathParamNames;
4972
- queryParamDefs;
4973
- hasQueryInSpec;
5079
+ // Query matching: use regex for structural patterns (splats), map-based for key-value patterns
5080
+ queryRegex;
5081
+ queryRegexParamNames;
5082
+ queryDefs;
4974
5083
  constructor(spec) {
4975
5084
  if (!spec) {
4976
5085
  throw new Error("spec is required");
@@ -4983,9 +5092,20 @@ class RouteParser {
4983
5092
  const { pattern, paramNames } = buildPathRegex(pathTokens);
4984
5093
  this.pathRegex = new RegExp(`^${pattern}(?:\\?.*)?$`);
4985
5094
  this.pathParamNames = paramNames;
4986
- // Extract query parameter definitions
4987
- this.queryParamDefs = extractQueryParams(queryTokens);
4988
- this.hasQueryInSpec = queryTokens.length > 0;
5095
+ // Query matching setup
5096
+ if (queryTokens.length > 0 && hasSplats(queryTokens)) {
5097
+ // Structural query: use regex
5098
+ const qTokens = queryTokens[0]?.type === "querySeparator" ? queryTokens.slice(1) : queryTokens;
5099
+ const { pattern: qPattern, paramNames: qParamNames } = buildQueryRegex(qTokens);
5100
+ this.queryRegex = new RegExp(`^${qPattern}$`);
5101
+ this.queryRegexParamNames = qParamNames;
5102
+ this.queryDefs = [];
5103
+ } else {
5104
+ // Key-value query: use map-based matching (order-independent)
5105
+ this.queryRegex = null;
5106
+ this.queryRegexParamNames = [];
5107
+ this.queryDefs = extractQueryDefs(queryTokens);
5108
+ }
4989
5109
  }
4990
5110
  /**
4991
5111
  * Match a path against this route
@@ -4996,7 +5116,6 @@ class RouteParser {
4996
5116
  const queryIndex = path.indexOf("?");
4997
5117
  const pathname = queryIndex >= 0 ? path.slice(0, queryIndex) : path;
4998
5118
  const queryString = queryIndex >= 0 ? path.slice(queryIndex + 1) : "";
4999
- // Match only the pathname part (not query string) to avoid capturing ? in params
5000
5119
  const match = this.pathRegex.exec(pathname);
5001
5120
  if (!match) {
5002
5121
  return false;
@@ -5006,18 +5125,29 @@ class RouteParser {
5006
5125
  for (let i = 0; i < this.pathParamNames.length; i++) {
5007
5126
  params[this.pathParamNames[i]] = match[i + 1];
5008
5127
  }
5009
- // If route has query params in spec, match them from URL's query string
5010
- if (this.hasQueryInSpec) {
5128
+ // Structural query matching (splats)
5129
+ if (this.queryRegex) {
5130
+ const qMatch = this.queryRegex.exec(queryString);
5131
+ if (!qMatch) return false;
5132
+ for (let i = 0; i < this.queryRegexParamNames.length; i++) {
5133
+ params[this.queryRegexParamNames[i]] = qMatch[i + 1];
5134
+ }
5135
+ }
5136
+ // Key-value query matching (order-independent)
5137
+ if (this.queryDefs.length > 0) {
5011
5138
  const urlQueryParams = parseQueryString(queryString);
5012
- for (const paramDef of this.queryParamDefs) {
5013
- const value = urlQueryParams.get(paramDef.key);
5014
- if (value !== undefined) {
5015
- params[paramDef.name] = value;
5016
- } else if (paramDef.optional) {
5017
- params[paramDef.name] = undefined;
5139
+ for (const def of this.queryDefs) {
5140
+ if (def.fixedValue !== undefined) {
5141
+ if (urlQueryParams.get(def.key) !== def.fixedValue && !def.optional) return false;
5018
5142
  } else {
5019
- // Required query param not found
5020
- return false;
5143
+ const value = urlQueryParams.get(def.key);
5144
+ if (value !== undefined) {
5145
+ params[def.paramName] = value;
5146
+ } else if (def.optional) {
5147
+ params[def.paramName] = undefined;
5148
+ } else {
5149
+ return false;
5150
+ }
5021
5151
  }
5022
5152
  }
5023
5153
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cx",
3
- "version": "26.4.1",
3
+ "version": "26.4.3",
4
4
  "description": "Advanced JavaScript UI framework for admin and dashboard applications with ready to use grid, form and chart components.",
5
5
  "exports": {
6
6
  "./data": {
@@ -35,39 +35,45 @@ export function getAvailableShapes(): string[] {
35
35
  }
36
36
 
37
37
  export function circle(cx: number, cy: number, size: number, props?: Config, options?: Config): React.ReactElement {
38
- return <circle {...props} cx={cx} cy={cy} r={size / 2} />;
38
+ const { key, ...rest } = props ?? {};
39
+ return <circle key={key} {...rest} cx={cx} cy={cy} r={size / 2} />;
39
40
  }
40
41
  registerShape("circle", circle);
41
42
 
42
43
  export function square(cx: number, cy: number, size: number, props?: Config, options?: Config): React.ReactElement {
43
44
  size *= 0.9;
44
- return <rect {...props} x={cx - size / 2} y={cy - size / 2} width={size} height={size} />;
45
+ const { key, ...rest } = props ?? {};
46
+ return <rect key={key} {...rest} x={cx - size / 2} y={cy - size / 2} width={size} height={size} />;
45
47
  }
46
48
  registerShape("square", square);
47
49
  registerShape("rect", square);
48
50
 
49
51
  export function bar(cx: number, cy: number, size: number, props?: Config, options?: Config): React.ReactElement {
50
52
  size *= 0.9;
51
- return <rect {...props} x={cx - size / 2} y={cy - size / 4} width={size} height={size / 2} />;
53
+ const { key, ...rest } = props ?? {};
54
+ return <rect key={key} {...rest} x={cx - size / 2} y={cy - size / 4} width={size} height={size / 2} />;
52
55
  }
53
56
  registerShape("bar", bar);
54
57
 
55
58
  export function column(cx: number, cy: number, size: number, props?: Config, options?: Config): React.ReactElement {
56
59
  size *= 0.9;
57
- return <rect {...props} x={cx - size / 4} y={cy - size / 2} width={size / 2} height={size} />;
60
+ const { key, ...rest } = props ?? {};
61
+ return <rect key={key} {...rest} x={cx - size / 4} y={cy - size / 2} width={size / 2} height={size} />;
58
62
  }
59
63
  registerShape("column", column);
60
64
 
61
65
  export function line(cx: number, cy: number, size: number, props?: Config, options?: Config): React.ReactElement {
62
66
  size *= 0.9;
63
- return <line {...props} x1={cx - size / 2} y1={cy} x2={cx + size / 2} y2={cy} />;
67
+ const { key, ...rest } = props ?? {};
68
+ return <line key={key} {...rest} x1={cx - size / 2} y1={cy} x2={cx + size / 2} y2={cy} />;
64
69
  }
65
70
  registerShape("line", line);
66
71
  registerShape("hline", line);
67
72
 
68
73
  export function vline(cx: number, cy: number, size: number, props?: Config, options?: Config): React.ReactElement {
69
74
  size *= 0.9;
70
- return <line {...props} x1={cx} y1={cy - size / 2} x2={cx} y2={cy + size / 2} />;
75
+ const { key, ...rest } = props ?? {};
76
+ return <line key={key} {...rest} x1={cx} y1={cy - size / 2} x2={cx} y2={cy + size / 2} />;
71
77
  }
72
78
  registerShape("vline", vline);
73
79
 
@@ -80,7 +86,8 @@ export function triangle(cx: number, cy: number, size: number, props?: Config, o
80
86
  d += `L ${cx + (cos * size) / 2} ${cy + (sin * size) / 2} `;
81
87
  d += `L ${cx - (cos * size) / 2} ${cy + (sin * size) / 2} `;
82
88
  d += `Z`;
83
- return <path {...props} d={d} />;
89
+ const { key, ...rest } = props ?? {};
90
+ return <path key={key} {...rest} d={d} />;
84
91
  }
85
92
 
86
93
  registerShape("triangle", triangle);