@almadar/ui 5.30.0 → 5.31.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.
@@ -3476,6 +3476,8 @@ var init_Input = __esm({
3476
3476
  className,
3477
3477
  inputType,
3478
3478
  type: htmlType,
3479
+ label,
3480
+ helperText,
3479
3481
  error,
3480
3482
  leftIcon,
3481
3483
  rightIcon,
@@ -3531,82 +3533,95 @@ var init_Input = __esm({
3531
3533
  onClear?.();
3532
3534
  }
3533
3535
  };
3536
+ const wrapField = (field) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full", children: [
3537
+ label && /* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-sm font-medium text-foreground mb-1", children: label }),
3538
+ field,
3539
+ (helperText || error) && /* @__PURE__ */ jsxRuntime.jsx("p", { className: cn("mt-1 text-xs", error ? "text-error" : "text-muted-foreground"), children: error ?? helperText })
3540
+ ] });
3534
3541
  if (type === "select") {
3535
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full", children: [
3536
- resolvedLeftIcon && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-muted-foreground", children: resolvedLeftIcon }),
3537
- /* @__PURE__ */ jsxRuntime.jsxs(
3538
- "select",
3542
+ return wrapField(
3543
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full", children: [
3544
+ resolvedLeftIcon && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-muted-foreground", children: resolvedLeftIcon }),
3545
+ /* @__PURE__ */ jsxRuntime.jsxs(
3546
+ "select",
3547
+ {
3548
+ ref,
3549
+ value,
3550
+ onChange: handleChange,
3551
+ className: cn(baseClassName, "appearance-none pr-10", className),
3552
+ ...props,
3553
+ children: [
3554
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: t("form.selectPlaceholder", { label: "" }) }),
3555
+ options?.map((opt) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: opt.value, children: opt.label }, opt.value))
3556
+ ]
3557
+ }
3558
+ ),
3559
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none text-muted-foreground", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "chevron-down", className: "h-icon-default w-icon-default" }) })
3560
+ ] })
3561
+ );
3562
+ }
3563
+ if (type === "textarea") {
3564
+ return wrapField(
3565
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative w-full", children: /* @__PURE__ */ jsxRuntime.jsx(
3566
+ "textarea",
3539
3567
  {
3540
3568
  ref,
3541
3569
  value,
3542
3570
  onChange: handleChange,
3543
- className: cn(baseClassName, "appearance-none pr-10", className),
3544
- ...props,
3545
- children: [
3546
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: t("form.selectPlaceholder", { label: "" }) }),
3547
- options?.map((opt) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: opt.value, children: opt.label }, opt.value))
3548
- ]
3571
+ rows: rows2,
3572
+ className: baseClassName,
3573
+ ...props
3549
3574
  }
3550
- ),
3551
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none text-muted-foreground", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "chevron-down", className: "h-icon-default w-icon-default" }) })
3552
- ] });
3553
- }
3554
- if (type === "textarea") {
3555
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative w-full", children: /* @__PURE__ */ jsxRuntime.jsx(
3556
- "textarea",
3557
- {
3558
- ref,
3559
- value,
3560
- onChange: handleChange,
3561
- rows: rows2,
3562
- className: baseClassName,
3563
- ...props
3564
- }
3565
- ) });
3575
+ ) })
3576
+ );
3566
3577
  }
3567
3578
  if (type === "checkbox") {
3568
- return /* @__PURE__ */ jsxRuntime.jsx(
3569
- "input",
3570
- {
3571
- ref,
3572
- type: "checkbox",
3573
- checked: props.checked,
3574
- onChange: handleChange,
3575
- className: cn(
3576
- "h-icon-default w-icon-default rounded-sm",
3577
- "border-border",
3578
- "text-primary focus:ring-ring",
3579
- "disabled:opacity-50 disabled:cursor-not-allowed",
3580
- className
3581
- ),
3582
- ...props
3583
- }
3579
+ return wrapField(
3580
+ /* @__PURE__ */ jsxRuntime.jsx(
3581
+ "input",
3582
+ {
3583
+ ref,
3584
+ type: "checkbox",
3585
+ checked: props.checked,
3586
+ onChange: handleChange,
3587
+ className: cn(
3588
+ "h-icon-default w-icon-default rounded-sm",
3589
+ "border-border",
3590
+ "text-primary focus:ring-ring",
3591
+ "disabled:opacity-50 disabled:cursor-not-allowed",
3592
+ className
3593
+ ),
3594
+ ...props
3595
+ }
3596
+ )
3584
3597
  );
3585
3598
  }
3586
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full", children: [
3587
- resolvedLeftIcon && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-muted-foreground", children: resolvedLeftIcon }),
3588
- /* @__PURE__ */ jsxRuntime.jsx(
3589
- "input",
3590
- {
3591
- ref,
3592
- type,
3593
- value,
3594
- onChange: handleChange,
3595
- className: baseClassName,
3596
- ...props
3597
- }
3598
- ),
3599
- showClearButton && /* @__PURE__ */ jsxRuntime.jsx(
3600
- "button",
3601
- {
3602
- type: "button",
3603
- onClick: handleClear,
3604
- className: "absolute inset-y-0 right-0 pr-3 flex items-center text-muted-foreground hover:text-foreground",
3605
- children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "x", className: "h-icon-default w-icon-default" })
3606
- }
3607
- ),
3608
- rightIcon && !showClearButton && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-y-0 right-0 pr-3 flex items-center text-muted-foreground", children: resolveIconNode(rightIcon, iconCls) })
3609
- ] });
3599
+ return wrapField(
3600
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full", children: [
3601
+ resolvedLeftIcon && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-muted-foreground", children: resolvedLeftIcon }),
3602
+ /* @__PURE__ */ jsxRuntime.jsx(
3603
+ "input",
3604
+ {
3605
+ ref,
3606
+ type,
3607
+ value,
3608
+ onChange: handleChange,
3609
+ className: baseClassName,
3610
+ ...props
3611
+ }
3612
+ ),
3613
+ showClearButton && /* @__PURE__ */ jsxRuntime.jsx(
3614
+ "button",
3615
+ {
3616
+ type: "button",
3617
+ onClick: handleClear,
3618
+ className: "absolute inset-y-0 right-0 pr-3 flex items-center text-muted-foreground hover:text-foreground",
3619
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "x", className: "h-icon-default w-icon-default" })
3620
+ }
3621
+ ),
3622
+ rightIcon && !showClearButton && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-y-0 right-0 pr-3 flex items-center text-muted-foreground", children: resolveIconNode(rightIcon, iconCls) })
3623
+ ] })
3624
+ );
3610
3625
  }
3611
3626
  );
3612
3627
  Input.displayName = "Input";
@@ -3677,6 +3692,190 @@ var init_Textarea = __esm({
3677
3692
  Textarea.displayName = "Textarea";
3678
3693
  }
3679
3694
  });
3695
+ function flatOptions(opts, groups) {
3696
+ const flat = opts ?? [];
3697
+ const grp = (groups ?? []).flatMap((g) => g.options);
3698
+ return [...flat, ...grp];
3699
+ }
3700
+ function NativeSelect({
3701
+ className,
3702
+ options,
3703
+ groups,
3704
+ placeholder,
3705
+ error,
3706
+ onChange,
3707
+ value,
3708
+ ...props
3709
+ }) {
3710
+ const eventBus = useEventBus();
3711
+ const handleChange = (e) => {
3712
+ if (typeof onChange === "string") {
3713
+ eventBus.emit(`UI:${onChange}`, { value: e.target.value });
3714
+ } else {
3715
+ onChange?.(e.target.value);
3716
+ }
3717
+ };
3718
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
3719
+ /* @__PURE__ */ jsxRuntime.jsxs(
3720
+ "select",
3721
+ {
3722
+ onChange: handleChange,
3723
+ value,
3724
+ className: cn(
3725
+ "block w-full border-[length:var(--border-width)] shadow-sm appearance-none",
3726
+ "px-3 py-2 pr-10 text-sm text-foreground font-medium",
3727
+ "bg-card",
3728
+ "focus:outline-none focus:ring-2 focus:ring-offset-0 focus:ring-ring",
3729
+ "disabled:bg-muted disabled:text-muted-foreground disabled:cursor-not-allowed",
3730
+ error ? "border-error focus:border-error" : "border-border focus:border-primary",
3731
+ className
3732
+ ),
3733
+ ...props,
3734
+ children: [
3735
+ placeholder && /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", disabled: true, children: placeholder }),
3736
+ options?.map((option) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: option.value, disabled: option.disabled, children: option.label }, option.value)),
3737
+ groups?.map((group) => /* @__PURE__ */ jsxRuntime.jsx("optgroup", { label: group.label, children: group.options.map((option) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: option.value, disabled: option.disabled, children: option.label }, option.value)) }, group.label))
3738
+ ]
3739
+ }
3740
+ ),
3741
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "chevron-down", className: "h-icon-default w-icon-default text-foreground" }) })
3742
+ ] });
3743
+ }
3744
+ function RichSelect({
3745
+ className,
3746
+ options,
3747
+ groups,
3748
+ placeholder,
3749
+ error,
3750
+ onChange,
3751
+ value,
3752
+ multiple,
3753
+ searchable,
3754
+ clearable,
3755
+ disabled
3756
+ }) {
3757
+ const eventBus = useEventBus();
3758
+ const [open, setOpen] = React80.useState(false);
3759
+ const [search, setSearch] = React80.useState("");
3760
+ const containerRef = React80.useRef(null);
3761
+ const selected = multiple ? Array.isArray(value) ? value : value ? [value] : [] : value ? [value] : [];
3762
+ const all = flatOptions(options, groups);
3763
+ const filtered = searchable && search ? all.filter((o) => o.label.toLowerCase().includes(search.toLowerCase())) : all;
3764
+ const toggle = (optValue) => {
3765
+ let next;
3766
+ if (multiple) {
3767
+ next = selected.includes(optValue) ? selected.filter((v) => v !== optValue) : [...selected, optValue];
3768
+ } else {
3769
+ next = optValue;
3770
+ setOpen(false);
3771
+ }
3772
+ if (typeof onChange === "string") {
3773
+ eventBus.emit(`UI:${onChange}`, { value: next });
3774
+ } else {
3775
+ onChange?.(next);
3776
+ }
3777
+ };
3778
+ const clear = (e) => {
3779
+ e.stopPropagation();
3780
+ const next = multiple ? [] : "";
3781
+ if (typeof onChange === "string") {
3782
+ eventBus.emit(`UI:${onChange}`, { value: next });
3783
+ } else {
3784
+ onChange?.(next);
3785
+ }
3786
+ };
3787
+ React80.useEffect(() => {
3788
+ const handler = (e) => {
3789
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
3790
+ setOpen(false);
3791
+ setSearch("");
3792
+ }
3793
+ };
3794
+ document.addEventListener("mousedown", handler);
3795
+ return () => document.removeEventListener("mousedown", handler);
3796
+ }, []);
3797
+ const displayLabel = selected.length === 0 ? placeholder ?? "" : multiple ? `${selected.length} selected` : all.find((o) => o.value === selected[0])?.label ?? selected[0];
3798
+ const hasValue = selected.length > 0;
3799
+ const renderOptions = (opts) => opts.map((opt) => /* @__PURE__ */ jsxRuntime.jsxs(
3800
+ "button",
3801
+ {
3802
+ type: "button",
3803
+ disabled: opt.disabled,
3804
+ onClick: () => !opt.disabled && toggle(opt.value),
3805
+ className: cn(
3806
+ "w-full flex items-center justify-between px-3 py-1.5 text-sm text-start",
3807
+ "hover:bg-muted transition-colors",
3808
+ "disabled:opacity-50 disabled:cursor-not-allowed",
3809
+ selected.includes(opt.value) && "text-primary font-medium"
3810
+ ),
3811
+ children: [
3812
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: opt.label }),
3813
+ selected.includes(opt.value) && /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "check", className: "h-icon-default w-icon-default" })
3814
+ ]
3815
+ },
3816
+ opt.value
3817
+ ));
3818
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: cn("relative w-full", className), children: [
3819
+ /* @__PURE__ */ jsxRuntime.jsx(
3820
+ "button",
3821
+ {
3822
+ type: "button",
3823
+ disabled,
3824
+ onClick: () => !disabled && setOpen((o) => !o),
3825
+ className: cn(
3826
+ "block w-full border-[length:var(--border-width)] shadow-sm",
3827
+ "px-3 py-2 pr-10 text-sm text-start font-medium",
3828
+ "bg-card rounded-sm",
3829
+ "focus:outline-none focus:ring-2 focus:ring-offset-0 focus:ring-ring",
3830
+ "disabled:bg-muted disabled:text-muted-foreground disabled:cursor-not-allowed",
3831
+ error ? "border-error focus:border-error" : "border-border focus:border-primary",
3832
+ !hasValue && "text-muted-foreground"
3833
+ ),
3834
+ children: displayLabel
3835
+ }
3836
+ ),
3837
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute inset-y-0 right-0 pr-3 flex items-center gap-1 pointer-events-none", children: [
3838
+ clearable && hasValue && /* @__PURE__ */ jsxRuntime.jsx(
3839
+ "button",
3840
+ {
3841
+ type: "button",
3842
+ onClick: clear,
3843
+ className: "pointer-events-auto text-muted-foreground hover:text-foreground",
3844
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "x", className: "h-icon-default w-icon-default" })
3845
+ }
3846
+ ),
3847
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "chevron-down", className: "h-icon-default w-icon-default text-foreground" })
3848
+ ] }),
3849
+ open && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn(
3850
+ "absolute z-50 mt-1 w-full",
3851
+ "bg-card border-[length:var(--border-width)] border-border",
3852
+ "rounded-sm shadow-elevation-popover py-1 max-h-60 overflow-y-auto"
3853
+ ), children: [
3854
+ searchable && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-2 pb-1 border-b border-border", children: /* @__PURE__ */ jsxRuntime.jsx(
3855
+ "input",
3856
+ {
3857
+ autoFocus: true,
3858
+ type: "text",
3859
+ value: search,
3860
+ onChange: (e) => setSearch(e.target.value),
3861
+ placeholder: "Search\u2026",
3862
+ className: cn(
3863
+ "w-full px-2 py-1 text-sm bg-transparent",
3864
+ "focus:outline-none text-foreground placeholder:text-muted-foreground"
3865
+ )
3866
+ }
3867
+ ) }),
3868
+ groups && groups.length > 0 ? groups.map((g) => {
3869
+ const groupFiltered = searchable && search ? g.options.filter((o) => o.label.toLowerCase().includes(search.toLowerCase())) : g.options;
3870
+ if (groupFiltered.length === 0) return null;
3871
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3872
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 py-1 text-xs font-semibold text-muted-foreground uppercase tracking-wide", children: g.label }),
3873
+ renderOptions(groupFiltered)
3874
+ ] }, g.label);
3875
+ }) : renderOptions(filtered)
3876
+ ] })
3877
+ ] });
3878
+ }
3680
3879
  var Select;
3681
3880
  var init_Select = __esm({
3682
3881
  "components/core/atoms/Select.tsx"() {
@@ -3684,47 +3883,12 @@ var init_Select = __esm({
3684
3883
  init_Icon();
3685
3884
  init_useEventBus();
3686
3885
  Select = React80__namespace.default.forwardRef(
3687
- ({ className, options, placeholder, error, onChange, ...props }, ref) => {
3688
- const eventBus = useEventBus();
3689
- const handleChange = (e) => {
3690
- if (typeof onChange === "string") {
3691
- eventBus.emit(`UI:${onChange}`, { value: e.target.value });
3692
- } else {
3693
- onChange?.(e);
3694
- }
3695
- };
3696
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
3697
- /* @__PURE__ */ jsxRuntime.jsxs(
3698
- "select",
3699
- {
3700
- ref,
3701
- onChange: handleChange,
3702
- className: cn(
3703
- "block w-full border-[length:var(--border-width)] shadow-sm appearance-none",
3704
- "px-3 py-2 pr-10 text-sm text-foreground font-medium",
3705
- "bg-card",
3706
- "focus:outline-none focus:ring-2 focus:ring-offset-0 focus:ring-ring",
3707
- "disabled:bg-muted disabled:text-muted-foreground disabled:cursor-not-allowed",
3708
- error ? "border-error focus:border-error" : "border-border focus:border-primary",
3709
- className
3710
- ),
3711
- ...props,
3712
- children: [
3713
- placeholder && /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", disabled: true, children: placeholder }),
3714
- options.map((option) => /* @__PURE__ */ jsxRuntime.jsx(
3715
- "option",
3716
- {
3717
- value: option.value,
3718
- disabled: option.disabled,
3719
- children: option.label
3720
- },
3721
- option.value
3722
- ))
3723
- ]
3724
- }
3725
- ),
3726
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "chevron-down", className: "h-icon-default w-icon-default text-foreground" }) })
3727
- ] });
3886
+ (props, _ref) => {
3887
+ const { multiple, searchable, clearable } = props;
3888
+ if (multiple || searchable || clearable) {
3889
+ return /* @__PURE__ */ jsxRuntime.jsx(RichSelect, { ...props });
3890
+ }
3891
+ return /* @__PURE__ */ jsxRuntime.jsx(NativeSelect, { ...props });
3728
3892
  }
3729
3893
  );
3730
3894
  Select.displayName = "Select";
@@ -3778,11 +3942,54 @@ var init_Checkbox = __esm({
3778
3942
  Checkbox.displayName = "Checkbox";
3779
3943
  }
3780
3944
  });
3945
+ var sizeStyles3, Spinner;
3946
+ var init_Spinner = __esm({
3947
+ "components/core/atoms/Spinner.tsx"() {
3948
+ init_cn();
3949
+ init_Icon();
3950
+ sizeStyles3 = {
3951
+ xs: "h-3 w-3",
3952
+ sm: "h-icon-default w-icon-default",
3953
+ md: "h-6 w-6",
3954
+ lg: "h-8 w-8"
3955
+ };
3956
+ Spinner = React80__namespace.default.forwardRef(
3957
+ ({ className, size = "md", overlay, ...props }, ref) => {
3958
+ if (overlay) {
3959
+ return /* @__PURE__ */ jsxRuntime.jsx(
3960
+ "div",
3961
+ {
3962
+ ref,
3963
+ className: cn(
3964
+ "absolute inset-0 z-10 flex items-center justify-center",
3965
+ "bg-background/60 backdrop-blur-sm",
3966
+ className
3967
+ ),
3968
+ ...props,
3969
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "loader", className: cn("animate-spin text-foreground", sizeStyles3[size]) })
3970
+ }
3971
+ );
3972
+ }
3973
+ return /* @__PURE__ */ jsxRuntime.jsx(
3974
+ "div",
3975
+ {
3976
+ ref,
3977
+ className: cn("text-foreground", className),
3978
+ ...props,
3979
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "loader", className: cn("animate-spin", sizeStyles3[size]) })
3980
+ }
3981
+ );
3982
+ }
3983
+ );
3984
+ Spinner.displayName = "Spinner";
3985
+ }
3986
+ });
3781
3987
  var variantStyles4, paddingStyles2, shadowStyles2, lookStyles2, Card, CardHeader, CardTitle, CardContent, CardBody, CardFooter;
3782
3988
  var init_Card = __esm({
3783
3989
  "components/core/atoms/Card.tsx"() {
3784
3990
  init_cn();
3785
3991
  init_useEventBus();
3992
+ init_Spinner();
3786
3993
  variantStyles4 = {
3787
3994
  default: [
3788
3995
  "bg-card",
@@ -3847,6 +4054,7 @@ var init_Card = __esm({
3847
4054
  look = "elevated",
3848
4055
  children,
3849
4056
  action,
4057
+ loading,
3850
4058
  onClick,
3851
4059
  ...props
3852
4060
  }, ref) => {
@@ -3860,7 +4068,7 @@ var init_Card = __esm({
3860
4068
  {
3861
4069
  ref,
3862
4070
  className: cn(
3863
- "rounded-container",
4071
+ "rounded-container relative",
3864
4072
  "transition-all duration-[var(--transition-normal)]",
3865
4073
  variantStyles4[variant],
3866
4074
  paddingStyles2[padding],
@@ -3871,6 +4079,7 @@ var init_Card = __esm({
3871
4079
  onClick: handleClick,
3872
4080
  ...props,
3873
4081
  children: [
4082
+ loading && /* @__PURE__ */ jsxRuntime.jsx(Spinner, { overlay: true, size: "md" }),
3874
4083
  (title || subtitle) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-4", children: [
3875
4084
  title && /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg text-card-foreground font-bold", children: title }),
3876
4085
  subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground mt-1", children: subtitle })
@@ -3912,7 +4121,7 @@ var init_Card = __esm({
3912
4121
  CardFooter.displayName = "CardFooter";
3913
4122
  }
3914
4123
  });
3915
- var variantStyles5, sizeStyles3, iconSizes, FilterPill;
4124
+ var variantStyles5, sizeStyles4, iconSizes, FilterPill;
3916
4125
  var init_FilterPill = __esm({
3917
4126
  "components/core/atoms/FilterPill.tsx"() {
3918
4127
  init_cn();
@@ -3946,7 +4155,7 @@ var init_FilterPill = __esm({
3946
4155
  "border-[length:var(--border-width-thin)] border-border"
3947
4156
  ].join(" ")
3948
4157
  };
3949
- sizeStyles3 = {
4158
+ sizeStyles4 = {
3950
4159
  sm: "px-2 py-0.5 text-xs",
3951
4160
  md: "px-2.5 py-1 text-sm",
3952
4161
  lg: "px-3 py-1.5 text-base"
@@ -3990,7 +4199,7 @@ var init_FilterPill = __esm({
3990
4199
  className: cn(
3991
4200
  "inline-flex items-center gap-1 font-bold rounded-pill",
3992
4201
  variantStyles5[variant],
3993
- sizeStyles3[size],
4202
+ sizeStyles4[size],
3994
4203
  (onClick || clickEvent) && "cursor-pointer",
3995
4204
  className
3996
4205
  ),
@@ -4022,33 +4231,6 @@ var init_FilterPill = __esm({
4022
4231
  FilterPill.displayName = "FilterPill";
4023
4232
  }
4024
4233
  });
4025
- var sizeStyles4, Spinner;
4026
- var init_Spinner = __esm({
4027
- "components/core/atoms/Spinner.tsx"() {
4028
- init_cn();
4029
- init_Icon();
4030
- sizeStyles4 = {
4031
- xs: "h-3 w-3",
4032
- sm: "h-icon-default w-icon-default",
4033
- md: "h-6 w-6",
4034
- lg: "h-8 w-8"
4035
- };
4036
- Spinner = React80__namespace.default.forwardRef(
4037
- ({ className, size = "md", ...props }, ref) => {
4038
- return /* @__PURE__ */ jsxRuntime.jsx(
4039
- "div",
4040
- {
4041
- ref,
4042
- className: cn("text-foreground", className),
4043
- ...props,
4044
- children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "loader", className: cn("animate-spin", sizeStyles4[size]) })
4045
- }
4046
- );
4047
- }
4048
- );
4049
- Spinner.displayName = "Spinner";
4050
- }
4051
- });
4052
4234
  function generateInitials(name) {
4053
4235
  const parts = name.trim().split(/\s+/);
4054
4236
  if (parts.length === 1) {
@@ -16515,30 +16697,30 @@ var init_BranchingLogicBuilder = __esm({
16515
16697
  if (!sourceQuestion?.optionValues) return [];
16516
16698
  return sourceQuestion.optionValues.map((v) => ({ value: v, label: v }));
16517
16699
  }, [sourceQuestion]);
16518
- const handleSource = (e) => {
16519
- onChange({ ...rule, sourceQuestionId: e.target.value });
16700
+ const handleSource = (v) => {
16701
+ onChange({ ...rule, sourceQuestionId: v });
16520
16702
  };
16521
- const handleOperator = (e) => {
16522
- const next = e.target.value;
16703
+ const handleOperator = (v) => {
16704
+ const next = v;
16523
16705
  const nextValue = next === "in" && !Array.isArray(rule.value) ? rule.value ? [rule.value] : [] : next !== "in" && Array.isArray(rule.value) ? rule.value[0] ?? "" : rule.value;
16524
16706
  onChange({ ...rule, operator: next, value: nextValue });
16525
16707
  };
16526
16708
  const handleScalarValue = (e) => {
16527
16709
  onChange({ ...rule, value: e.target.value });
16528
16710
  };
16529
- const handleAddChip = (e) => {
16530
- const v = e.target.value;
16531
- if (!v) return;
16711
+ const handleAddChip = (v) => {
16712
+ const val = v;
16713
+ if (!val) return;
16532
16714
  const current = Array.isArray(rule.value) ? rule.value : [];
16533
- if (current.includes(v)) return;
16534
- onChange({ ...rule, value: [...current, v] });
16715
+ if (current.includes(val)) return;
16716
+ onChange({ ...rule, value: [...current, val] });
16535
16717
  };
16536
16718
  const handleRemoveChip = (chip) => {
16537
16719
  const current = Array.isArray(rule.value) ? rule.value : [];
16538
16720
  onChange({ ...rule, value: current.filter((c) => c !== chip) });
16539
16721
  };
16540
- const handleTarget = (e) => {
16541
- onChange({ ...rule, targetQuestionId: e.target.value });
16722
+ const handleTarget = (v) => {
16723
+ onChange({ ...rule, targetQuestionId: v });
16542
16724
  };
16543
16725
  const isMulti = rule.operator === "in";
16544
16726
  const chips = Array.isArray(rule.value) ? rule.value : [];
@@ -16619,7 +16801,7 @@ var init_BranchingLogicBuilder = __esm({
16619
16801
  options: valueOptions,
16620
16802
  value: scalarValue,
16621
16803
  placeholder: t("branchingLogic.selectValue"),
16622
- onChange: (e) => onChange({ ...rule, value: e.target.value }),
16804
+ onChange: (v) => onChange({ ...rule, value: v }),
16623
16805
  disabled: readOnly
16624
16806
  }
16625
16807
  ) : /* @__PURE__ */ jsxRuntime.jsx(
@@ -19051,7 +19233,7 @@ var init_Pagination = __esm({
19051
19233
  Select,
19052
19234
  {
19053
19235
  value: String(pageSize),
19054
- onChange: (e) => handlePageSizeChange(Number(e.target.value)),
19236
+ onChange: (v) => handlePageSizeChange(Number(v)),
19055
19237
  options: pageSizeOptions.map((size) => ({
19056
19238
  value: String(size),
19057
19239
  label: String(size)
@@ -22077,7 +22259,9 @@ var init_Menu = __esm({
22077
22259
  trigger,
22078
22260
  items,
22079
22261
  position = "bottom-left",
22080
- className
22262
+ className,
22263
+ header,
22264
+ footer
22081
22265
  }) => {
22082
22266
  const eventBus = useEventBus();
22083
22267
  const { direction } = hooks.useTranslate();
@@ -22209,14 +22393,18 @@ var init_Menu = __esm({
22209
22393
  )
22210
22394
  ] }, itemId);
22211
22395
  });
22212
- const panel = isOpen && triggerRect ? /* @__PURE__ */ jsxRuntime.jsx(
22396
+ const panel = isOpen && triggerRect ? /* @__PURE__ */ jsxRuntime.jsxs(
22213
22397
  "div",
22214
22398
  {
22215
22399
  ref: menuRef,
22216
22400
  className: cn("fixed z-50", menuContainerStyles, className),
22217
22401
  style: computeMenuStyle(effectivePosition, triggerRect),
22218
22402
  role: "menu",
22219
- children: renderMenuItems(items)
22403
+ children: [
22404
+ header && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 py-2 border-b border-border", children: header }),
22405
+ renderMenuItems(items),
22406
+ footer && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 py-2 border-t border-border", children: footer })
22407
+ ]
22220
22408
  }
22221
22409
  ) : null;
22222
22410
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
@@ -23975,7 +24163,7 @@ var init_FilterGroup = __esm({
23975
24163
  Select,
23976
24164
  {
23977
24165
  value: selectedValues[filter.field] || "all",
23978
- onChange: (e) => handleFilterSelect(filter.field, e.target.value),
24166
+ onChange: (v) => handleFilterSelect(filter.field, v),
23979
24167
  options: [
23980
24168
  { value: "all", label: t("filterGroup.all") },
23981
24169
  ...filter.options?.map((opt) => ({
@@ -24058,7 +24246,7 @@ var init_FilterGroup = __esm({
24058
24246
  Select,
24059
24247
  {
24060
24248
  value: selectedValues[filter.field] || "all",
24061
- onChange: (e) => handleFilterSelect(filter.field, e.target.value),
24249
+ onChange: (v) => handleFilterSelect(filter.field, v),
24062
24250
  options: [
24063
24251
  { value: "all", label: t("filterGroup.allOf", { label: filter.label }) },
24064
24252
  ...filter.options?.map((opt) => ({
@@ -24176,7 +24364,7 @@ var init_FilterGroup = __esm({
24176
24364
  Select,
24177
24365
  {
24178
24366
  value: selectedValues[filter.field] || "all",
24179
- onChange: (e) => handleFilterSelect(filter.field, e.target.value),
24367
+ onChange: (v) => handleFilterSelect(filter.field, v),
24180
24368
  options: [
24181
24369
  { value: "all", label: t("filterGroup.all") },
24182
24370
  ...filter.options?.map((opt) => ({
@@ -28358,13 +28546,13 @@ var init_MapView = __esm({
28358
28546
  shadowSize: [41, 41]
28359
28547
  });
28360
28548
  L.Marker.prototype.options.icon = defaultIcon;
28361
- const { useEffect: useEffect71, useRef: useRef66, useCallback: useCallback112, useState: useState99 } = React80__namespace.default;
28549
+ const { useEffect: useEffect72, useRef: useRef67, useCallback: useCallback112, useState: useState100 } = React80__namespace.default;
28362
28550
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
28363
28551
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
28364
28552
  function MapUpdater({ centerLat, centerLng, zoom }) {
28365
28553
  const map = useMap();
28366
- const prevRef = useRef66({ centerLat, centerLng, zoom });
28367
- useEffect71(() => {
28554
+ const prevRef = useRef67({ centerLat, centerLng, zoom });
28555
+ useEffect72(() => {
28368
28556
  const prev = prevRef.current;
28369
28557
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
28370
28558
  map.setView([centerLat, centerLng], zoom);
@@ -28375,7 +28563,7 @@ var init_MapView = __esm({
28375
28563
  }
28376
28564
  function MapClickHandler({ onMapClick }) {
28377
28565
  const map = useMap();
28378
- useEffect71(() => {
28566
+ useEffect72(() => {
28379
28567
  if (!onMapClick) return;
28380
28568
  const handler = (e) => {
28381
28569
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -28403,7 +28591,7 @@ var init_MapView = __esm({
28403
28591
  showAttribution = true
28404
28592
  }) {
28405
28593
  const eventBus = useEventBus2();
28406
- const [clickedPosition, setClickedPosition] = useState99(null);
28594
+ const [clickedPosition, setClickedPosition] = useState100(null);
28407
28595
  const handleMapClick = useCallback112((lat, lng) => {
28408
28596
  if (showClickedPin) {
28409
28597
  setClickedPosition({ lat, lng });
@@ -33839,8 +34027,8 @@ var init_VersionDiff = __esm({
33839
34027
  return { added, removed };
33840
34028
  }, [diff]);
33841
34029
  const handleBeforeChange = React80.useCallback(
33842
- (e) => {
33843
- const id = e.target.value;
34030
+ (v) => {
34031
+ const id = v;
33844
34032
  setInternalBefore(id);
33845
34033
  onSelectBefore?.(id);
33846
34034
  if (selectBeforeEvent) eventBus.emit(`UI:${selectBeforeEvent}`, { id });
@@ -33848,8 +34036,8 @@ var init_VersionDiff = __esm({
33848
34036
  [onSelectBefore, selectBeforeEvent, eventBus]
33849
34037
  );
33850
34038
  const handleAfterChange = React80.useCallback(
33851
- (e) => {
33852
- const id = e.target.value;
34039
+ (v) => {
34040
+ const id = v;
33853
34041
  setInternalAfter(id);
33854
34042
  onSelectAfter?.(id);
33855
34043
  if (selectAfterEvent) eventBus.emit(`UI:${selectAfterEvent}`, { id });
@@ -38475,11 +38663,11 @@ function RuleEditor({
38475
38663
  className
38476
38664
  }) {
38477
38665
  const { t } = hooks.useTranslate();
38478
- const handleWhenChange = React80.useCallback((e) => {
38479
- onChange({ ...rule, whenEvent: e.target.value });
38666
+ const handleWhenChange = React80.useCallback((v) => {
38667
+ onChange({ ...rule, whenEvent: v });
38480
38668
  }, [rule, onChange]);
38481
- const handleThenChange = React80.useCallback((e) => {
38482
- onChange({ ...rule, thenAction: e.target.value });
38669
+ const handleThenChange = React80.useCallback((v) => {
38670
+ onChange({ ...rule, thenAction: v });
38483
38671
  }, [rule, onChange]);
38484
38672
  return /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: cn("items-center p-2 rounded-lg bg-muted/50 border border-border", className), gap: "sm", children: [
38485
38673
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-primary font-bold whitespace-nowrap", children: t("eventHandler.when") }),
@@ -39555,7 +39743,7 @@ var init_Form = __esm({
39555
39743
  ...commonProps,
39556
39744
  options,
39557
39745
  value: String(currentValue),
39558
- onChange: (e) => handleChange(fieldName, e.target.value),
39746
+ onChange: (v) => handleChange(fieldName, v),
39559
39747
  placeholder: field.placeholder || `Select ${label}...`
39560
39748
  }
39561
39749
  );
@@ -46323,18 +46511,32 @@ var init_WorldMapTemplate = __esm({
46323
46511
  }
46324
46512
  });
46325
46513
  function lazyThree(name, loader) {
46326
- const Lazy = React80__namespace.default.lazy(() => loader().then((m) => ({ default: m[name] })));
46514
+ const Lazy = React80__namespace.default.lazy(
46515
+ () => loader().then((m) => {
46516
+ const Resolved = m[name];
46517
+ if (!Resolved) {
46518
+ throw new Error(
46519
+ `[@almadar/ui] 3D component "${name}" was not found in the three subpath bundle.`
46520
+ );
46521
+ }
46522
+ return { default: Resolved };
46523
+ })
46524
+ );
46327
46525
  function ThreeWrapper(props) {
46328
46526
  return React80__namespace.default.createElement(
46329
- React80__namespace.default.Suspense,
46330
- { fallback: null },
46331
- React80__namespace.default.createElement(Lazy, props)
46527
+ ThreeBoundary,
46528
+ { name },
46529
+ React80__namespace.default.createElement(
46530
+ React80__namespace.default.Suspense,
46531
+ { fallback: null },
46532
+ React80__namespace.default.createElement(Lazy, props)
46533
+ )
46332
46534
  );
46333
46535
  }
46334
46536
  ThreeWrapper.displayName = `Lazy(${name})`;
46335
46537
  return ThreeWrapper;
46336
46538
  }
46337
- var FeatureRenderer, GameCanvas3D, GameCanvas3DBattleTemplate, GameCanvas3DCastleTemplate, GameCanvas3DWorldMapTemplate, COMPONENT_REGISTRY;
46539
+ var ThreeBoundary, FeatureRenderer, GameCanvas3D, GameCanvas3DBattleTemplate, GameCanvas3DCastleTemplate, GameCanvas3DWorldMapTemplate, COMPONENT_REGISTRY;
46338
46540
  var init_component_registry_generated = __esm({
46339
46541
  "components/core/organisms/component-registry.generated.ts"() {
46340
46542
  init_AboutPageTemplate();
@@ -46620,6 +46822,28 @@ var init_component_registry_generated = __esm({
46620
46822
  init_WorldMapBoard();
46621
46823
  init_WorldMapTemplate();
46622
46824
  init_XPBar();
46825
+ ThreeBoundary = class extends React80__namespace.default.Component {
46826
+ constructor() {
46827
+ super(...arguments);
46828
+ __publicField(this, "state", { failed: false });
46829
+ }
46830
+ static getDerivedStateFromError() {
46831
+ return { failed: true };
46832
+ }
46833
+ render() {
46834
+ if (this.state.failed) {
46835
+ return React80__namespace.default.createElement(
46836
+ "div",
46837
+ {
46838
+ "data-testid": "three-unavailable",
46839
+ style: { padding: 16, fontSize: 13, lineHeight: 1.5, opacity: 0.7 }
46840
+ },
46841
+ `3D pattern "${this.props.name}" requires three.js. Install the optional peers three + @react-three/fiber + @react-three/drei (matching the host React major) to render it.`
46842
+ );
46843
+ }
46844
+ return this.props.children;
46845
+ }
46846
+ };
46623
46847
  FeatureRenderer = lazyThree("FeatureRenderer", () => import('@almadar/ui/components/molecules/game/three'));
46624
46848
  GameCanvas3D = lazyThree("GameCanvas3D", () => import('@almadar/ui/components/molecules/game/three'));
46625
46849
  GameCanvas3DBattleTemplate = lazyThree("GameCanvas3DBattleTemplate", () => import('@almadar/ui/components/molecules/game/three'));