@rebasepro/ui 0.22.0 → 0.24.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/index.es.js CHANGED
@@ -3008,9 +3008,10 @@ var Alert = ({ children, onDismiss, color = "info", size = "medium", action, out
3008
3008
  //#endregion
3009
3009
  //#region src/components/Avatar.tsx
3010
3010
  var AvatarInner = ({ src, alt, children, className, style, outerClassName, hover = true, ...props }, ref) => {
3011
- const [isImageError, setIsImageError] = useState(false);
3011
+ const [failedSrc, setFailedSrc] = useState(void 0);
3012
+ const isImageError = src !== void 0 && failedSrc === src;
3012
3013
  const handleImageError = () => {
3013
- setIsImageError(true);
3014
+ setFailedSrc(src);
3014
3015
  };
3015
3016
  return /* @__PURE__ */ jsx("button", {
3016
3017
  ref,
@@ -3092,7 +3093,7 @@ var BooleanSwitch = React.forwardRef(function BooleanSwitch({ value, allowIndete
3092
3093
  else onValueChange?.(null);
3093
3094
  else onValueChange?.(!value);
3094
3095
  },
3095
- className: cls(SWITCH_GEOMETRY[size].track, "outline-none outline-hidden rounded-full relative shadow-sm", disabled ? value ? "bg-primary/40 ring-1 ring-transparent" : "bg-surface-field ring-1 ring-hairline" : value ? "bg-primary ring-1 ring-primary" : "bg-surface-active ring-1 ring-hairline-strong", className),
3096
+ className: cls(SWITCH_GEOMETRY[size].track, "outline-none outline-hidden rounded-full relative shadow-sm", disabled ? value ? "bg-primary/40 ring-1 ring-transparent" : "bg-surface-field ring-1 ring-hairline" : value ? "bg-primary ring-1 ring-primary" : "bg-surface-active ring-1 ring-hairline-strong", "focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-surface-card", className),
3096
3097
  ...props,
3097
3098
  children: [allowIndeterminate && (value === null || value === void 0) && /* @__PURE__ */ jsx("div", { className: cls("block rounded-full transition-transform duration-100 ease-out transform will-change-auto shadow-sm", "bg-surface-400", SWITCH_GEOMETRY[size].bar, SWITCH_GEOMETRY[size].barX) }, "knob"), !(allowIndeterminate && (value === null || value === void 0)) && /* @__PURE__ */ jsx("div", { className: cls("block rounded-full transition-transform duration-100 ease-out transform will-change-auto shadow-sm", disabled ? "bg-surface-300 dark:bg-surface-600" : value ? "bg-white shadow" : "bg-surface-500 dark:bg-surface-400", SWITCH_GEOMETRY[size].knob, value ? SWITCH_GEOMETRY[size].on : SWITCH_GEOMETRY[size].off) }, "knob")]
3098
3099
  });
@@ -3103,7 +3104,7 @@ var BooleanSwitch = React.forwardRef(function BooleanSwitch({ value, allowIndete
3103
3104
  * Simple boolean switch.
3104
3105
  *
3105
3106
  */
3106
- var BooleanSwitchWithLabel = function BooleanSwitchWithLabel({ value, position = "end", size = "medium", invisible, onValueChange, error, label, autoFocus, disabled, className, fullWidth = true, inputClassName, ...props }) {
3107
+ var BooleanSwitchWithLabel = function BooleanSwitchWithLabel({ value, position = "end", size = "medium", invisible, onValueChange, error, label, autoFocus, disabled, className, fullWidth = true, inputClassName, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, ...props }) {
3107
3108
  const ref = React.useRef(null);
3108
3109
  const refInput = React.useRef(null);
3109
3110
  const switchLabelId = React.useId();
@@ -3111,18 +3112,13 @@ var BooleanSwitchWithLabel = function BooleanSwitchWithLabel({ value, position =
3111
3112
  const onFocus = () => setFocused(true);
3112
3113
  const onBlur = () => setFocused(false);
3113
3114
  React.useEffect(() => {
3114
- if (autoFocus) {}
3115
+ if (autoFocus) refInput.current?.focus();
3115
3116
  }, []);
3116
3117
  const focus = document.activeElement === refInput?.current || document.activeElement === ref?.current;
3117
3118
  return /* @__PURE__ */ jsxs("div", {
3118
3119
  ref,
3119
3120
  onFocus,
3120
3121
  onBlur,
3121
- role: "switch",
3122
- "aria-checked": props.allowIndeterminate && (value === null || value === void 0) ? "mixed" : !!value,
3123
- "aria-disabled": disabled || void 0,
3124
- "aria-labelledby": label ? switchLabelId : void 0,
3125
- tabIndex: -1,
3126
3122
  className: cls(!invisible && "bg-surface-field border border-hairline", !invisible && (disabled ? "bg-surface-field/60" : "transition-colors duration-150 hover:bg-surface-field-hover"), disabled ? "cursor-default" : "cursor-pointer", "rounded-lg max-w-full justify-between box-border relative inline-flex items-center", !invisible && focus && !disabled ? focusedClasses : "", error ? "text-red-500 dark:text-red-600" : focus && !disabled ? "text-primary" : !disabled ? "text-text-primary dark:text-text-primary-dark" : "text-text-secondary dark:text-text-secondary-dark", controlHeightMixin[size], size === "small" || size === "smallest" ? "pl-2" : "pl-4", size === "small" || size === "smallest" ? "pr-4" : "pr-6", position === "end" ? "flex-row-reverse" : "flex-row", fullWidth ? "w-full" : "", className),
3127
3123
  onClick: disabled ? void 0 : (e) => {
3128
3124
  if (props.allowIndeterminate) {
@@ -3136,7 +3132,9 @@ var BooleanSwitchWithLabel = function BooleanSwitchWithLabel({ value, position =
3136
3132
  value,
3137
3133
  ref: refInput,
3138
3134
  size,
3139
- className: cls(invisible && focus ? focusedClasses : "", inputClassName),
3135
+ "aria-labelledby": ariaLabelledBy ?? (label ? switchLabelId : void 0),
3136
+ "aria-label": ariaLabel,
3137
+ className: cls(invisible ? focus ? focusedClasses : "" : focusedDisabled, inputClassName),
3140
3138
  disabled,
3141
3139
  ...props
3142
3140
  }), /* @__PURE__ */ jsx("div", {
@@ -3153,9 +3151,9 @@ var ButtonInner = React.memo(React.forwardRef(({ children, className, variant =
3153
3151
  const buttonClasses = cls({
3154
3152
  "w-full": fullWidth,
3155
3153
  "w-fit": !fullWidth,
3156
- "border border-primary bg-primary focus:ring-primary text-white hover:text-white hover:brightness-105": variant === "filled" && color === "primary" && !disabled,
3157
- "border border-secondary bg-secondary focus:ring-secondary text-white hover:text-white hover:brightness-105": variant === "filled" && color === "secondary" && !disabled,
3158
- "border border-red-600 bg-red-600 hover:bg-red-700 focus:ring-red-600 text-white hover:text-white": variant === "filled" && color === "error" && !disabled,
3154
+ "border border-primary bg-primary focus-visible:ring-white/90 text-white hover:text-white hover:brightness-105": variant === "filled" && color === "primary" && !disabled,
3155
+ "border border-secondary bg-secondary focus-visible:ring-white/90 text-white hover:text-white hover:brightness-105": variant === "filled" && color === "secondary" && !disabled,
3156
+ "border border-red-600 bg-red-600 hover:bg-red-700 focus-visible:ring-white/90 text-white hover:text-white": variant === "filled" && color === "error" && !disabled,
3159
3157
  "border border-hairline-strong bg-surface-raised hover:bg-surface-raised-hover text-text-primary hover:text-text-primary dark:text-text-primary-dark hover:dark:text-text-primary-dark": variant === "filled" && color === "text" && !disabled,
3160
3158
  "border border-transparent bg-surface-raised hover:bg-surface-raised-hover text-text-primary hover:text-text-primary dark:text-text-primary-dark hover:dark:text-text-primary-dark": variant === "filled" && color === "neutral" && !disabled,
3161
3159
  "border border-transparent text-primary hover:text-primary hover:bg-surface-hover": variant === "text" && color === "primary" && !disabled,
@@ -3735,12 +3733,74 @@ function Chip({ children, colorScheme, error, outlined, variant = "tinted", onCl
3735
3733
  });
3736
3734
  }
3737
3735
  //#endregion
3736
+ //#region src/components/IconButton.tsx
3737
+ var buttonClasses = "hover:scale-[1.04] active:scale-95 transition-transform";
3738
+ var baseClasses = "inline-flex items-center justify-center text-sm font-medium focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/50 transition-colors ease-in-out duration-150 [&>svg]:shrink-0";
3739
+ var colorClasses$1 = "text-surface-accent-500 visited:text-surface-accent-500 dark:text-surface-accent-300 dark:visited:text-surface-accent-300";
3740
+ var sizeClasses$1 = {
3741
+ medium: "w-10 !h-10 min-w-10 min-h-10 p-2",
3742
+ small: "w-8 !h-8 min-w-8 min-h-8 p-2",
3743
+ smallest: "w-7 !h-7 min-w-7 min-h-7 p-2",
3744
+ large: "w-12 !h-12 min-w-12 min-h-12 p-2"
3745
+ };
3746
+ var shapeClasses = {
3747
+ circular: "rounded-full",
3748
+ square: "rounded-md"
3749
+ };
3750
+ /** Icon size used when the caller doesn't set one on the child. */
3751
+ var defaultIconSize = {
3752
+ smallest: 16,
3753
+ small: 18,
3754
+ medium: 20,
3755
+ large: 24
3756
+ };
3757
+ var IconButtonInner = ({ children, className, size = "medium", variant = "ghost", shape = "circular", disabled, toggled, component, onClick, ...props }, ref) => {
3758
+ const bgClasses = variant === "ghost" ? "bg-transparent hover:bg-surface-hover" : "bg-surface-raised hover:bg-surface-raised-hover";
3759
+ const Component = component || "button";
3760
+ const isNativeButton = Component === "button";
3761
+ const sizedChildren = React.Children.map(children, (child) => {
3762
+ if (!React.isValidElement(child)) return child;
3763
+ if (child.props.size !== void 0) return child;
3764
+ return React.cloneElement(child, { size: defaultIconSize[size] });
3765
+ });
3766
+ return /* @__PURE__ */ jsx(Component, {
3767
+ type: isNativeButton ? "button" : void 0,
3768
+ role: isNativeButton ? void 0 : "button",
3769
+ ref,
3770
+ disabled: isNativeButton ? disabled : void 0,
3771
+ "aria-disabled": disabled || void 0,
3772
+ tabIndex: disabled ? -1 : void 0,
3773
+ onClick: disabled ? void 0 : onClick,
3774
+ ...props,
3775
+ className: cls(disabled ? "opacity-50 pointer-events-none" : "cursor-pointer", toggled ? "outline outline-2 outline-primary" : "", "text-inherit dark:text-inherit", colorClasses$1, bgClasses, baseClasses, buttonClasses, shapeClasses[shape], sizeClasses$1[size], className),
3776
+ children: sizedChildren
3777
+ });
3778
+ };
3779
+ var IconButton = React.forwardRef(IconButtonInner);
3780
+ //#endregion
3738
3781
  //#region src/components/Tooltip.tsx
3782
+ /**
3783
+ * A Radix tooltip only describes its trigger (`aria-describedby`, and only
3784
+ * while open); it never names it. Wrapped around an icon-only button with no
3785
+ * name of its own, the title was the one word that said what the button does,
3786
+ * and a screen reader announced "button". So a string title names an
3787
+ * icon-only button (no text, no `aria-label`/`aria-labelledby`) it wraps.
3788
+ */
3789
+ function nameFromTitle(children, title) {
3790
+ if (typeof title !== "string" || !React.isValidElement(children)) return children;
3791
+ const type = children.type;
3792
+ if (type !== IconButton && type !== "button" && type !== "a") return children;
3793
+ const props = children.props;
3794
+ if (props["aria-label"] || props["aria-labelledby"]) return children;
3795
+ if (React.Children.toArray(props.children).some((child) => typeof child === "string" || typeof child === "number")) return children;
3796
+ return React.cloneElement(children, { "aria-label": title });
3797
+ }
3739
3798
  var Tooltip = ({ open, defaultOpen, side = "bottom", delayDuration = 200, sideOffset, align, onOpenChange, title, tooltipClassName, tooltipStyle, children, asChild = false, container, className, style }) => {
3740
3799
  useInjectStyles("Tooltip", styles$2);
3741
3800
  const contextContainer = usePortalContainer();
3742
3801
  const finalContainer = container ?? contextContainer ?? void 0;
3743
3802
  if (!title) return /* @__PURE__ */ jsx(Fragment, { children });
3803
+ children = nameFromTitle(children, title);
3744
3804
  const trigger = asChild ? /* @__PURE__ */ jsx(TooltipPrimitive.Trigger, {
3745
3805
  asChild: true,
3746
3806
  children
@@ -3920,49 +3980,6 @@ function ColorPicker({ value, onChange, size = "medium", allowClear = true, disa
3920
3980
  });
3921
3981
  }
3922
3982
  //#endregion
3923
- //#region src/components/IconButton.tsx
3924
- var buttonClasses = "hover:scale-[1.04] active:scale-95 transition-transform";
3925
- var baseClasses = "inline-flex items-center justify-center text-sm font-medium focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/50 transition-colors ease-in-out duration-150 [&>svg]:shrink-0";
3926
- var colorClasses$1 = "text-surface-accent-500 visited:text-surface-accent-500 dark:text-surface-accent-300 dark:visited:text-surface-accent-300";
3927
- var sizeClasses$1 = {
3928
- medium: "w-10 !h-10 min-w-10 min-h-10 p-2",
3929
- small: "w-8 !h-8 min-w-8 min-h-8 p-2",
3930
- smallest: "w-7 !h-7 min-w-7 min-h-7 p-2",
3931
- large: "w-12 !h-12 min-w-12 min-h-12 p-2"
3932
- };
3933
- var shapeClasses = {
3934
- circular: "rounded-full",
3935
- square: "rounded-md"
3936
- };
3937
- /** Icon size used when the caller doesn't set one on the child. */
3938
- var defaultIconSize = {
3939
- smallest: 16,
3940
- small: 18,
3941
- medium: 20,
3942
- large: 24
3943
- };
3944
- var IconButtonInner = ({ children, className, size = "medium", variant = "ghost", shape = "circular", disabled, toggled, component, ...props }, ref) => {
3945
- const bgClasses = variant === "ghost" ? "bg-transparent hover:bg-surface-hover" : "bg-surface-raised hover:bg-surface-raised-hover";
3946
- const Component = component || "button";
3947
- const isNativeButton = Component === "button";
3948
- const sizedChildren = React.Children.map(children, (child) => {
3949
- if (!React.isValidElement(child)) return child;
3950
- if (child.props.size !== void 0) return child;
3951
- return React.cloneElement(child, { size: defaultIconSize[size] });
3952
- });
3953
- return /* @__PURE__ */ jsx(Component, {
3954
- type: isNativeButton ? "button" : void 0,
3955
- role: isNativeButton ? void 0 : "button",
3956
- ref,
3957
- "aria-disabled": disabled || void 0,
3958
- tabIndex: disabled ? -1 : void 0,
3959
- ...props,
3960
- className: cls(disabled ? "opacity-50 pointer-events-none" : "cursor-pointer", toggled ? "outline outline-2 outline-primary" : "", "text-inherit dark:text-inherit", colorClasses$1, bgClasses, baseClasses, buttonClasses, shapeClasses[shape], sizeClasses$1[size], className),
3961
- children: sizedChildren
3962
- });
3963
- };
3964
- var IconButton = React.forwardRef(IconButtonInner);
3965
- //#endregion
3966
3983
  //#region src/components/InputLabel.tsx
3967
3984
  var defaultClasses = {
3968
3985
  root: "origin-left transition-transform block whitespace-nowrap overflow-hidden text-ellipsis max-w-full",
@@ -3983,8 +4000,13 @@ var InputLabel = React$1.forwardRef(function InputLabel(inProps, ref) {
3983
4000
  });
3984
4001
  //#endregion
3985
4002
  //#region src/components/DateTimeField.tsx
3986
- var DateTimeField = ({ value, label, onChange, disabled, clearable, mode = "date", error, size = "large", className, style, inputClassName, invisible, timezone, "aria-label": ariaLabel }) => {
4003
+ var DateTimeField = ({ value, label, onChange, disabled, clearable, mode = "date", error, size = "large", className, style, inputClassName, invisible, pickerButton = true, inputRef: inputRefProp, timezone, "aria-label": ariaLabel }) => {
3987
4004
  const inputRef = useRef(null);
4005
+ const setInputRef = useCallback((node) => {
4006
+ inputRef.current = node;
4007
+ if (typeof inputRefProp === "function") inputRefProp(node);
4008
+ else if (inputRefProp) inputRefProp.current = node;
4009
+ }, [inputRefProp]);
3988
4010
  const [focused, setFocused] = useState(false);
3989
4011
  const inputId = useId();
3990
4012
  const labelId = `${inputId}-label`;
@@ -3995,6 +4017,7 @@ var DateTimeField = ({ value, label, onChange, disabled, clearable, mode = "date
3995
4017
  useInjectStyles("DateTimeField", inputStyles);
3996
4018
  const handleClear = (e) => {
3997
4019
  e.preventDefault();
4020
+ if (disabled) return;
3998
4021
  setInternalValue("");
3999
4022
  setIsTyping(false);
4000
4023
  onChange?.(null);
@@ -4061,11 +4084,19 @@ var DateTimeField = ({ value, label, onChange, disabled, clearable, mode = "date
4061
4084
  [hours, minutes] = timePart.split(":").map(Number);
4062
4085
  }
4063
4086
  let resultDate;
4064
- if (!timezone) resultDate = new Date(year, month - 1, day, hours, minutes);
4065
- else {
4066
- const refUtcDate = new Date(Date.UTC(year, month - 1, day, hours, minutes));
4067
- const offsetMinutes = getTimezoneOffsetMinutes(refUtcDate, timezone);
4068
- resultDate = /* @__PURE__ */ new Date(Date.UTC(year, month - 1, day, hours, minutes) - offsetMinutes * 6e4);
4087
+ if (!timezone) {
4088
+ resultDate = /* @__PURE__ */ new Date(0);
4089
+ resultDate.setFullYear(year, month - 1, day);
4090
+ resultDate.setHours(hours, minutes, 0, 0);
4091
+ } else {
4092
+ const wallClock = /* @__PURE__ */ new Date(0);
4093
+ wallClock.setUTCFullYear(year, month - 1, day);
4094
+ wallClock.setUTCHours(hours, minutes, 0, 0);
4095
+ const wallClockAsUtc = wallClock.getTime();
4096
+ const firstGuess = getTimezoneOffsetMinutes(new Date(wallClockAsUtc), timezone);
4097
+ const offsetMinutes = getTimezoneOffsetMinutes(/* @__PURE__ */ new Date(wallClockAsUtc - firstGuess * 6e4), timezone);
4098
+ resultDate = /* @__PURE__ */ new Date(wallClockAsUtc - offsetMinutes * 6e4);
4099
+ if (getTimezoneOffsetMinutes(resultDate, timezone) !== offsetMinutes) resultDate = /* @__PURE__ */ new Date(wallClockAsUtc - firstGuess * 6e4);
4069
4100
  }
4070
4101
  if (isNaN(resultDate.getTime())) throw new Error("Invalid date");
4071
4102
  onChange?.(resultDate);
@@ -4104,7 +4135,7 @@ var DateTimeField = ({ value, label, onChange, disabled, clearable, mode = "date
4104
4135
  children: label
4105
4136
  }),
4106
4137
  /* @__PURE__ */ jsx("input", {
4107
- ref: inputRef,
4138
+ ref: setInputRef,
4108
4139
  id: inputId,
4109
4140
  "aria-labelledby": label ? labelId : void 0,
4110
4141
  "aria-label": label ? void 0 : ariaLabel,
@@ -4114,7 +4145,7 @@ var DateTimeField = ({ value, label, onChange, disabled, clearable, mode = "date
4114
4145
  onFocus: handleFocus,
4115
4146
  onBlur: handleBlur,
4116
4147
  disabled,
4117
- className: cls("w-full outline-hidden bg-transparent leading-normal text-sm px-3", clearable ? "pr-14" : "pr-12", "rounded-lg", {
4148
+ className: cls("w-full outline-hidden bg-transparent leading-normal text-sm px-3", pickerButton ? clearable ? "pr-14" : "pr-12" : clearable ? "pr-12" : "", "rounded-lg", {
4118
4149
  "min-h-[28px]": size === "smallest",
4119
4150
  "min-h-[32px]": size === "small",
4120
4151
  "min-h-[40px]": size === "medium",
@@ -4126,17 +4157,23 @@ var DateTimeField = ({ value, label, onChange, disabled, clearable, mode = "date
4126
4157
  large: "py-3"
4127
4158
  }[size], inputClassName, disabled && "border border-transparent outline-hidden opacity-50 dark:opacity-50 text-surface-accent-600 dark:text-surface-accent-500")
4128
4159
  }),
4129
- /* @__PURE__ */ jsx(IconButton, {
4160
+ pickerButton && /* @__PURE__ */ jsx(IconButton, {
4130
4161
  size: adornmentSize,
4162
+ "aria-label": "Open calendar",
4163
+ disabled,
4131
4164
  onClick: (e) => {
4132
4165
  e.stopPropagation();
4133
- inputRef.current?.showPicker();
4166
+ try {
4167
+ inputRef.current?.showPicker();
4168
+ } catch {}
4134
4169
  },
4135
4170
  className: "absolute right-3 top-1/2 transform -translate-y-1/2 text-surface-accent-500!",
4136
4171
  children: /* @__PURE__ */ jsx(CalendarIcon$1, {})
4137
4172
  }),
4138
4173
  clearable && value && /* @__PURE__ */ jsx(IconButton, {
4139
4174
  size: adornmentSize,
4175
+ "aria-label": "Clear date",
4176
+ disabled,
4140
4177
  onClick: handleClear,
4141
4178
  className: "absolute right-14 top-1/2 transform -translate-y-1/2 text-surface-accent-400 ",
4142
4179
  children: /* @__PURE__ */ jsx(XIcon$1, {})
@@ -4231,6 +4268,82 @@ function useRestoreInterruptedFocus(container) {
4231
4268
  }, [container]);
4232
4269
  }
4233
4270
  //#endregion
4271
+ //#region src/hooks/useModalFocus.ts
4272
+ function currentFocus() {
4273
+ if (typeof document === "undefined") return null;
4274
+ const active = document.activeElement;
4275
+ if (!(active instanceof HTMLElement) || active === document.body) return null;
4276
+ const layer = active.closest("[role=menu], [role=listbox]");
4277
+ if (layer instanceof HTMLElement && layer.id && layer !== active) {
4278
+ const trigger = Array.from(document.querySelectorAll("[aria-controls]")).find((el) => el.getAttribute("aria-controls") === layer.id);
4279
+ if (trigger instanceof HTMLElement && !layer.contains(trigger)) return trigger;
4280
+ }
4281
+ return active;
4282
+ }
4283
+ /**
4284
+ * Where focus goes when a kit modal (`Dialog`, `Sheet`) opens and closes.
4285
+ *
4286
+ * Both used to cancel Radix's open-focus and do nothing else, so focus stayed
4287
+ * on the trigger behind the modal — which Radix had just hidden from assistive
4288
+ * technology with `aria-hidden`. A screen reader announced nothing, and Enter
4289
+ * pressed again fired the trigger a second time underneath the open modal.
4290
+ *
4291
+ * On open, focus moves into the modal: to `initialFocus` when the caller names
4292
+ * an element, otherwise to the modal's own container (it carries
4293
+ * `tabIndex=-1`). The container rather than the first field, because the first
4294
+ * field on a phone raises the keyboard over a form the user has not started,
4295
+ * and because the container is what a screen reader announces by its title.
4296
+ * The first Tab then lands on the first control.
4297
+ *
4298
+ * On close, focus goes back to whatever had it when the modal opened. Radix
4299
+ * only returns it to a `Dialog.Trigger`, and the kit's modals are controlled —
4300
+ * there is no trigger — so focus used to fall to `<body>` and the next Tab
4301
+ * started from the top of the page.
4302
+ *
4303
+ * The origin is read while rendering the open edge, not from the open-focus
4304
+ * event: a field with `autoFocus` inside the modal has taken focus by the time
4305
+ * any effect runs, and Radix then skips the event altogether. When the origin
4306
+ * is an item in a menu, the menu's trigger is recorded instead: the item is
4307
+ * gone a moment later, and the menu does not take focus back to its trigger
4308
+ * once the modal has it.
4309
+ *
4310
+ * Internal to the kit's modals — not exported.
4311
+ */
4312
+ function useModalFocus({ open, initialFocus, focusContainer = true, onOpenAutoFocus, onCloseAutoFocus }) {
4313
+ const [origin, setOrigin] = useState(() => open ? currentFocus() : null);
4314
+ const [wasOpen, setWasOpen] = useState(open);
4315
+ if (open !== wasOpen) {
4316
+ setWasOpen(open);
4317
+ if (open) setOrigin(currentFocus());
4318
+ }
4319
+ return {
4320
+ onOpenAutoFocus: useCallback((event) => {
4321
+ onOpenAutoFocus?.(event);
4322
+ if (event.defaultPrevented) return;
4323
+ const target = initialFocus?.current;
4324
+ if (target) {
4325
+ event.preventDefault();
4326
+ target.focus({ preventScroll: true });
4327
+ return;
4328
+ }
4329
+ if (!focusContainer) return;
4330
+ event.preventDefault();
4331
+ const container = event.currentTarget;
4332
+ if (container instanceof HTMLElement) container.focus({ preventScroll: true });
4333
+ }, [
4334
+ initialFocus,
4335
+ focusContainer,
4336
+ onOpenAutoFocus
4337
+ ]),
4338
+ onCloseAutoFocus: useCallback((event) => {
4339
+ onCloseAutoFocus?.(event);
4340
+ if (event.defaultPrevented) return;
4341
+ event.preventDefault();
4342
+ if (origin?.isConnected) origin.focus({ preventScroll: true });
4343
+ }, [origin, onCloseAutoFocus])
4344
+ };
4345
+ }
4346
+ //#endregion
4234
4347
  //#region src/components/Dialog.tsx
4235
4348
  var widthClasses = {
4236
4349
  xs: "max-w-xs w-xs",
@@ -4246,11 +4359,20 @@ var widthClasses = {
4246
4359
  "7xl": "max-w-7xl w-7xl",
4247
4360
  full: "max-w-full w-full"
4248
4361
  };
4249
- var Dialog = ({ open, onOpenChange, children, className, containerClassName, fullWidth = true, fullHeight, fullScreen, scrollable = true, maxWidth = "lg", modal = true, onOpenAutoFocus, onEscapeKeyDown, onPointerDownOutside, onInteractOutside, disableInitialFocus = true, portalContainer, "aria-describedby": ariaDescribedby }) => {
4362
+ var Dialog = ({ open, onOpenChange, children, className, containerClassName, fullWidth = true, fullHeight, fullScreen, scrollable = true, maxWidth = "lg", modal = true, onOpenAutoFocus, onCloseAutoFocus, onEscapeKeyDown, onPointerDownOutside, onInteractOutside, dismissOnBackdrop = false, initialFocus, disableInitialFocus = true, portalContainer, "aria-describedby": ariaDescribedby }) => {
4250
4363
  const [displayed, setDisplayed] = useState(false);
4251
4364
  const [popupHost, setPopupHost] = useState(null);
4252
4365
  const [contentEl, setContentEl] = useState(null);
4253
4366
  useRestoreInterruptedFocus(contentEl);
4367
+ const [overlayEl, setOverlayEl] = useState(null);
4368
+ const [layerEl, setLayerEl] = useState(null);
4369
+ const focus = useModalFocus({
4370
+ open: Boolean(open),
4371
+ initialFocus,
4372
+ focusContainer: disableInitialFocus,
4373
+ onOpenAutoFocus,
4374
+ onCloseAutoFocus
4375
+ });
4254
4376
  const contextContainer = usePortalContainer();
4255
4377
  const finalContainer = portalContainer ?? contextContainer ?? void 0;
4256
4378
  useEffect(() => {
@@ -4271,23 +4393,29 @@ var Dialog = ({ open, onOpenChange, children, className, containerClassName, ful
4271
4393
  children: /* @__PURE__ */ jsx(DialogPrimitive.Portal, {
4272
4394
  container: finalContainer,
4273
4395
  children: /* @__PURE__ */ jsxs("div", {
4274
- className: cls("fixed inset-0 z-50", containerClassName),
4396
+ ref: setLayerEl,
4397
+ className: cls("fixed inset-0 z-50 flex justify-center items-center", containerClassName),
4275
4398
  children: [/* @__PURE__ */ jsx(DialogPrimitive.Overlay, {
4276
- className: cls("fixed inset-0 transition-opacity ease-in-out duration-200 bg-black/50 dark:bg-black/60 backdrop-blur-sm", displayed && open ? "opacity-100" : "opacity-0", "z-50 fixed top-0 left-0 w-full h-full flex justify-center items-center"),
4399
+ ref: setOverlayEl,
4400
+ className: cls("fixed inset-0 transition-opacity ease-in-out duration-200 bg-black/50 dark:bg-black/60 backdrop-blur-sm", displayed && open ? "opacity-100" : "opacity-0", "z-50 fixed top-0 left-0 w-full h-full"),
4277
4401
  style: { pointerEvents: displayed ? "auto" : "none" }
4278
4402
  }), /* @__PURE__ */ jsxs(DialogPrimitive.Content, {
4279
4403
  ref: setContentEl,
4280
4404
  onEscapeKeyDown,
4281
- onOpenAutoFocus: (e) => {
4282
- if (disableInitialFocus) e.preventDefault();
4283
- onOpenAutoFocus?.(e);
4405
+ onOpenAutoFocus: focus.onOpenAutoFocus,
4406
+ onCloseAutoFocus: focus.onCloseAutoFocus,
4407
+ onPointerDownOutside: (e) => {
4408
+ onPointerDownOutside?.(e);
4409
+ if (e.defaultPrevented) return;
4410
+ const target = e.target;
4411
+ if (!dismissOnBackdrop || !(target === overlayEl || target === layerEl)) e.preventDefault();
4284
4412
  },
4285
- onPointerDownOutside,
4413
+ onFocusOutside: (e) => e.preventDefault(),
4286
4414
  onInteractOutside,
4287
4415
  "aria-describedby": ariaDescribedby,
4288
- className: cls("relative h-full outline-none flex justify-center items-center z-60 opacity-100 transition-all duration-200 ease-in-out"),
4416
+ className: cls("relative z-60 outline-none", focusedDisabled, fullWidth && !fullScreen ? "w-11/12" : void 0, fullHeight && !fullScreen ? "h-[90vh]" : void 0, fullScreen ? "h-screen w-screen" : void 0, maxWidth && !fullScreen ? widthClasses[maxWidth] : void 0),
4289
4417
  children: [/* @__PURE__ */ jsx("div", {
4290
- className: cls(paperMixin, "rounded-2xl", "z-60", "relative", "overflow-hidden", "outline-none focus:outline-none", fullWidth && !fullScreen ? "w-11/12" : void 0, fullHeight && !fullScreen ? "h-full" : void 0, "text-text-primary dark:text-text-primary-dark", "justify-center items-center", fullScreen ? "h-screen w-screen" : "max-h-[90vh] shadow-lg", "ease-in-out duration-200", scrollable && "overflow-y-auto", displayed && open ? "opacity-100 scale-100" : "opacity-0 scale-[0.97]", maxWidth && !fullScreen ? widthClasses[maxWidth] : void 0, className),
4418
+ className: cls(paperMixin, "rounded-2xl", "z-60", "relative", "w-full", "overflow-hidden", "outline-none focus:outline-none", fullHeight || fullScreen ? "h-full" : void 0, "text-text-primary dark:text-text-primary-dark", "justify-center items-center", fullScreen ? void 0 : "max-h-[90vh] shadow-lg", "ease-in-out duration-200", scrollable && "overflow-y-auto", displayed && open ? "opacity-100 scale-100" : "opacity-0 scale-[0.97]", className),
4291
4419
  children: /* @__PURE__ */ jsx(PortalContainerProvider, {
4292
4420
  container: popupHost,
4293
4421
  children
@@ -4393,6 +4521,12 @@ function ExpandablePanel({ title, children, invisible = false, expanded, onExpan
4393
4521
  role: "button",
4394
4522
  tabIndex: 0,
4395
4523
  "aria-expanded": open,
4524
+ onKeyDown: (event) => {
4525
+ if (event.target !== event.currentTarget) return;
4526
+ if (event.key !== "Enter" && event.key !== " ") return;
4527
+ event.preventDefault();
4528
+ event.currentTarget.click();
4529
+ },
4396
4530
  children: [title, /* @__PURE__ */ jsx(ChevronDownIcon$1, { className: cls("transition", open ? "rotate-180" : "") })]
4397
4531
  })
4398
4532
  }), /* @__PURE__ */ jsx(Collapsible.Content, {
@@ -4501,12 +4635,11 @@ var Label = React$1.forwardRef(({ className, border, onClick, ...props }, ref) =
4501
4635
  Label.displayName = LabelPrimitive.Root.displayName;
4502
4636
  //#endregion
4503
4637
  //#region src/components/LoadingButton.tsx
4504
- function LoadingButton({ children, loading, disabled, onClick, startIcon, ...props }) {
4638
+ function LoadingButton({ children, loading, startIcon, ...props }) {
4639
+ const buttonProps = props;
4505
4640
  return /* @__PURE__ */ jsxs(Button, {
4506
- disabled: loading || disabled,
4507
- onClick,
4508
- component: props.component,
4509
- ...props,
4641
+ ...buttonProps,
4642
+ disabled: loading || buttonProps.disabled,
4510
4643
  children: [
4511
4644
  loading && /* @__PURE__ */ jsx(CircularProgress, { size: "smallest" }),
4512
4645
  !loading && startIcon,
@@ -4757,7 +4890,21 @@ function SelectInputLabel({ children, error }) {
4757
4890
  //#endregion
4758
4891
  //#region src/components/MultiSelect.tsx
4759
4892
  var MultiSelectContext = React$1.createContext(null);
4760
- var MultiSelect = React$1.forwardRef(({ value, size = "large", label, error, onValueChange, invisible, disabled, placeholder, modalPopover = true, includeClear = true, includeSelectAll = true, useChips = true, className, inputClassName, inputRef, children, renderValues, open, onOpenChange, portalContainer, "aria-label": ariaLabel }, ref) => {
4893
+ /**
4894
+ * The popover's trigger, or — given an anchor — a plain child beside a
4895
+ * `Popover.Anchor` on that element. Not both: Radix's trigger registers itself
4896
+ * as the anchor through a ref callback, which under StrictMode runs again
4897
+ * after the custom anchor's effect and wins, leaving the list anchored to a
4898
+ * trigger the custom anchor then unmounts. The button toggles the list itself.
4899
+ */
4900
+ function MultiSelectTrigger({ anchorRef, children }) {
4901
+ if (!anchorRef) return /* @__PURE__ */ jsx(PopoverPrimitive.Trigger, {
4902
+ asChild: true,
4903
+ children
4904
+ });
4905
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(PopoverPrimitive.Anchor, { virtualRef: anchorRef }), children] });
4906
+ }
4907
+ var MultiSelect = React$1.forwardRef(({ value, size = "large", label, error, onValueChange, invisible, chevron = true, anchorRef, triggerContent, disabled, placeholder, modalPopover = true, includeClear = true, includeSelectAll = true, useChips = true, className, inputClassName, inputRef, children, renderValues, open, onOpenChange, portalContainer, "aria-label": ariaLabel }, ref) => {
4761
4908
  const [isMounted, setIsMounted] = useState(false);
4762
4909
  const [isPopoverOpen, setIsPopoverOpen] = useState(open ?? false);
4763
4910
  const [selectedValues, setSelectedValues] = useState(value ?? []);
@@ -4788,9 +4935,10 @@ var MultiSelect = React$1.forwardRef(({ value, size = "large", label, error, onV
4788
4935
  setSelectedValues(value ?? []);
4789
4936
  }, [value]);
4790
4937
  const updateValues = React$1.useCallback((values) => {
4938
+ if (disabled) return;
4791
4939
  setSelectedValues(values);
4792
4940
  onValueChange?.(values);
4793
- }, [onValueChange]);
4941
+ }, [onValueChange, disabled]);
4794
4942
  const onItemClick = React$1.useCallback((newValue) => {
4795
4943
  let newSelectedValues;
4796
4944
  if (selectedValues.some((v) => String(v) === String(newValue))) newSelectedValues = selectedValues.filter((v) => String(v) !== String(newValue));
@@ -4829,6 +4977,7 @@ var MultiSelect = React$1.forwardRef(({ value, size = "large", label, error, onV
4829
4977
  fieldValue: selectedValues,
4830
4978
  onItemClick
4831
4979
  }), [selectedValues, onItemClick]);
4980
+ const bare = triggerContent !== void 0;
4832
4981
  return /* @__PURE__ */ jsxs(MultiSelectContext.Provider, {
4833
4982
  value: contextValue,
4834
4983
  children: [typeof label === "string" ? /* @__PURE__ */ jsx(SelectInputLabel, {
@@ -4838,13 +4987,14 @@ var MultiSelect = React$1.forwardRef(({ value, size = "large", label, error, onV
4838
4987
  open: isMounted && isPopoverOpen,
4839
4988
  onOpenChange: onPopoverOpenChange,
4840
4989
  modal: modalPopover,
4841
- children: [/* @__PURE__ */ jsx(PopoverPrimitive.Trigger, {
4842
- asChild: true,
4990
+ children: [/* @__PURE__ */ jsx(MultiSelectTrigger, {
4991
+ anchorRef,
4843
4992
  children: /* @__PURE__ */ jsx("button", {
4844
4993
  ref: inputRef ?? ref,
4994
+ disabled,
4845
4995
  "aria-label": ariaLabel ?? (typeof label === "string" ? label : void 0),
4846
4996
  onClick: handleTogglePopover,
4847
- className: cls(invisible ? {
4997
+ className: bare ? cls("relative flex items-center w-full text-start select-none outline-none focus:outline-none focus-visible:outline-none", className, inputClassName) : cls(invisible ? {
4848
4998
  smallest: "min-h-[28px]",
4849
4999
  small: "min-h-[32px]",
4850
5000
  medium: "min-h-[40px]",
@@ -4861,8 +5011,8 @@ var MultiSelect = React$1.forwardRef(({ value, size = "large", label, error, onV
4861
5011
  }, {
4862
5012
  "px-2": size === "small" || size === "smallest",
4863
5013
  "px-4": size === "medium" || size === "large"
4864
- }, "select-none rounded-lg text-sm", "focus:ring-0 focus-visible:ring-0 outline-none focus:outline-none focus-visible:outline-none", invisible ? fieldBackgroundInvisibleMixin : fieldBackgroundMixin, disabled ? fieldBackgroundDisabledMixin : invisible ? "" : fieldBackgroundHoverMixin, "relative flex items-center", className, inputClassName),
4865
- children: selectedValues.length > 0 ? /* @__PURE__ */ jsxs("div", {
5014
+ }, "select-none rounded-lg text-sm", "outline-none focus:outline-none focus-visible:outline-none", invisible ? fieldBackgroundInvisibleMixin : fieldBackgroundMixin, disabled ? fieldBackgroundDisabledMixin : invisible ? "" : fieldBackgroundHoverMixin, "relative flex items-center", className, inputClassName),
5015
+ children: bare ? triggerContent : selectedValues.length > 0 ? /* @__PURE__ */ jsxs("div", {
4866
5016
  className: "flex justify-between items-center w-full",
4867
5017
  children: [/* @__PURE__ */ jsxs("div", {
4868
5018
  className: "flex flex-wrap items-center gap-1.5 text-start",
@@ -4890,7 +5040,7 @@ var MultiSelect = React$1.forwardRef(({ value, size = "large", label, error, onV
4890
5040
  event.stopPropagation();
4891
5041
  handleClear();
4892
5042
  }
4893
- }), /* @__PURE__ */ jsx("div", {
5043
+ }), chevron && /* @__PURE__ */ jsx("div", {
4894
5044
  className: cls("px-2 h-full flex items-center"),
4895
5045
  children: /* @__PURE__ */ jsx(ChevronDownIcon$1, {
4896
5046
  size: size === "large" ? iconSize.medium : iconSize.small,
@@ -4903,7 +5053,7 @@ var MultiSelect = React$1.forwardRef(({ value, size = "large", label, error, onV
4903
5053
  children: [/* @__PURE__ */ jsx("span", {
4904
5054
  className: "text-sm",
4905
5055
  children: placeholder
4906
- }), /* @__PURE__ */ jsx("div", {
5056
+ }), chevron && /* @__PURE__ */ jsx("div", {
4907
5057
  className: cls("px-2 h-full flex items-center"),
4908
5058
  children: /* @__PURE__ */ jsx(ChevronDownIcon$1, {
4909
5059
  size: size === "large" ? iconSize.medium : iconSize.small,
@@ -5179,7 +5329,7 @@ function SearchBar({ onClick, onTextSearch, placeholder = "Search", expandable =
5179
5329
  }
5180
5330
  //#endregion
5181
5331
  //#region src/components/Select.tsx
5182
- var Select = forwardRef(({ inputRef, open, name, fullWidth = false, id, onOpenChange, value, onChange, onValueChange, className, inputClassName, viewportClassName, placeholder, renderValue, label, size = "large", error, disabled, padding = true, position = "item-aligned", endAdornment, invisible, children, dataType = "string", portalContainer: manualContainer, "aria-label": ariaLabel, ...props }, ref) => {
5332
+ var Select = forwardRef(({ inputRef, open, name, fullWidth = false, id, onOpenChange, value, onChange, onValueChange, className, inputClassName, viewportClassName, placeholder, renderValue, label, size = "large", error, disabled, padding = true, position = "item-aligned", endAdornment, invisible, chevron = true, triggerContent, children, dataType = "string", portalContainer: manualContainer, "aria-label": ariaLabel, ...props }, ref) => {
5183
5333
  const [openInternal, setOpenInternal] = useState(open ?? false);
5184
5334
  useEffect(() => {
5185
5335
  setOpenInternal(open ?? false);
@@ -5220,6 +5370,28 @@ var Select = forwardRef(({ inputRef, open, name, fullWidth = false, id, onOpenCh
5220
5370
  renderValue,
5221
5371
  value
5222
5372
  ]);
5373
+ const emptyItemDisplay = useMemo(() => {
5374
+ if (stringValue !== "") return null;
5375
+ const emptyItem = Children.toArray(children).find((child) => React.isValidElement(child) && String(child.props.value) === "");
5376
+ if (!emptyItem) return null;
5377
+ return value !== void 0 && renderValue ? renderValue(value) : emptyItem.props.children;
5378
+ }, [
5379
+ children,
5380
+ renderValue,
5381
+ stringValue,
5382
+ value
5383
+ ]);
5384
+ const bare = triggerContent !== void 0;
5385
+ const bareContent = bare ? /* @__PURE__ */ jsx("div", {
5386
+ className: "flex w-full",
5387
+ children: triggerContent
5388
+ }) : void 0;
5389
+ const controlHeight = {
5390
+ "min-h-[28px]": size === "smallest",
5391
+ "min-h-[32px]": size === "small",
5392
+ "min-h-[40px]": size === "medium",
5393
+ "min-h-[48px]": size === "large"
5394
+ };
5223
5395
  return /* @__PURE__ */ jsxs(SelectPrimitive.Root, {
5224
5396
  name,
5225
5397
  value: stringValue,
@@ -5237,11 +5409,7 @@ var Select = forwardRef(({ inputRef, open, name, fullWidth = false, id, onOpenCh
5237
5409
  children: label
5238
5410
  }) : label,
5239
5411
  /* @__PURE__ */ jsxs("div", {
5240
- className: cls("select-none rounded-lg text-sm", invisible ? fieldBackgroundInvisibleMixin : fieldBackgroundMixin, disabled ? fieldBackgroundDisabledMixin : invisible ? "" : fieldBackgroundHoverMixin, "relative flex items-center", className, {
5241
- "min-h-[28px]": size === "smallest",
5242
- "min-h-[32px]": size === "small",
5243
- "min-h-[40px]": size === "medium",
5244
- "min-h-[48px]": size === "large",
5412
+ className: cls("select-none", !bare && "rounded-lg text-sm", !bare && (invisible ? "bg-transparent" : "bg-surface-field border border-hairline"), !bare && (disabled ? "bg-surface-field/60" : invisible ? "" : "transition-colors duration-150 hover:bg-surface-field-hover"), "relative flex items-center", className, !bare && controlHeight, {
5245
5413
  "w-fit": !fullWidth,
5246
5414
  "w-full": fullWidth
5247
5415
  }),
@@ -5252,36 +5420,33 @@ var Select = forwardRef(({ inputRef, open, name, fullWidth = false, id, onOpenCh
5252
5420
  type: "button",
5253
5421
  "aria-label": ariaLabel ?? (typeof label === "string" ? label : typeof renderValue === "string" ? renderValue : "Select an option"),
5254
5422
  "aria-invalid": error || void 0,
5255
- className: cls("h-full", padding ? {
5423
+ className: cls("h-full", padding && !bare ? {
5256
5424
  "px-4": size === "large",
5257
5425
  "px-3": size === "medium",
5258
5426
  "px-2": size === "small" || size === "smallest"
5259
- } : "", "outline-hidden focus:outline-hidden", "outline-none focus:outline-none", "select-none rounded-lg text-sm", error ? "text-red-500 dark:text-red-600" : "focus:text-text-primary dark:focus:text-text-primary-dark", error ? "border border-red-500 dark:border-red-600" : "", disabled ? "text-surface-accent-600 dark:text-surface-accent-400" : "text-surface-accent-800 dark:text-white", "relative flex flex-row items-center", {
5260
- "min-h-[28px]": size === "smallest",
5261
- "min-h-[32px]": size === "small",
5262
- "min-h-[40px]": size === "medium",
5263
- "min-h-[48px]": size === "large",
5427
+ } : "", "outline-hidden focus:outline-hidden", "outline-none focus:outline-none", "select-none", !bare && [
5428
+ "rounded-lg text-sm",
5429
+ error ? "text-red-500 dark:text-red-600" : "focus:text-text-primary dark:focus:text-text-primary-dark",
5430
+ error ? "border border-red-500 dark:border-red-600" : "",
5431
+ disabled ? "text-surface-accent-600 dark:text-surface-accent-400" : "text-surface-accent-800 dark:text-white",
5432
+ controlHeight
5433
+ ], "relative flex flex-row items-center", {
5264
5434
  "w-full": fullWidth,
5265
5435
  "w-fit": !fullWidth
5266
5436
  }, inputClassName),
5267
5437
  children: [/* @__PURE__ */ jsx("div", {
5268
5438
  ref,
5269
- className: cls("flex-grow max-w-full flex flex-row gap-2 items-center", "overflow-visible", {
5270
- "min-h-[28px]": size === "smallest",
5271
- "min-h-[32px]": size === "small",
5272
- "min-h-[40px]": size === "medium",
5273
- "min-h-[48px]": size === "large"
5274
- }),
5439
+ className: cls("flex-grow max-w-full flex flex-row gap-2 items-center", "overflow-visible", !bare && controlHeight),
5275
5440
  children: /* @__PURE__ */ jsx(SelectPrimitive.Value, {
5276
5441
  onClick: (e) => {
5277
5442
  e.preventDefault();
5278
5443
  e.stopPropagation();
5279
5444
  },
5280
- placeholder,
5445
+ placeholder: bareContent ?? emptyItemDisplay ?? placeholder,
5281
5446
  className: "w-full",
5282
- children: hasValue && value !== void 0 && renderValue ? renderValue(value) : displayChildren || placeholder
5447
+ children: bareContent ?? (hasValue && value !== void 0 && renderValue ? renderValue(value) : displayChildren || placeholder)
5283
5448
  })
5284
- }), /* @__PURE__ */ jsx(SelectPrimitive.Icon, {
5449
+ }), chevron && !bare && /* @__PURE__ */ jsx(SelectPrimitive.Icon, {
5285
5450
  asChild: true,
5286
5451
  children: /* @__PURE__ */ jsx(ChevronDownIcon$1, {
5287
5452
  size: size === "large" ? iconSize.medium : iconSize.small,
@@ -5371,7 +5536,7 @@ var Slider = React$1.forwardRef(({ className, size = "regular", ...props }, ref)
5371
5536
  "bg-primary": !props.disabled,
5372
5537
  "bg-surface-raised-hover": props.disabled
5373
5538
  }) })
5374
- }), (props.value ?? [0]).map((_, index) => /* @__PURE__ */ jsx(SliderThumb, {
5539
+ }), (props.value ?? props.defaultValue ?? [0]).map((_, index) => /* @__PURE__ */ jsx(SliderThumb, {
5375
5540
  index,
5376
5541
  props,
5377
5542
  hovered,
@@ -5383,10 +5548,16 @@ var Slider = React$1.forwardRef(({ className, size = "regular", ...props }, ref)
5383
5548
  Slider.displayName = "Slider";
5384
5549
  //#endregion
5385
5550
  //#region src/components/Sheet.tsx
5386
- var Sheet = ({ children, side = "right", title, modal = true, includeBackgroundOverlay = true, open, onOpenChange, onPointerDownOutside, onInteractOutside, transparent, className, style, overlayClassName, overlayZIndex = "z-50", overlayStyle, portalContainer, ...props }) => {
5551
+ var Sheet = ({ children, side = "right", title, modal = true, includeBackgroundOverlay = true, open, onOpenChange, onPointerDownOutside, onInteractOutside, initialFocus, onOpenAutoFocus, onCloseAutoFocus, transparent, className, style, overlayClassName, overlayZIndex = "z-50", overlayStyle, portalContainer, ...props }) => {
5387
5552
  const [displayed, setDisplayed] = useState(false);
5388
5553
  const [contentEl, setContentEl] = useState(null);
5389
5554
  useRestoreInterruptedFocus(contentEl);
5555
+ const focus = useModalFocus({
5556
+ open,
5557
+ initialFocus,
5558
+ onOpenAutoFocus,
5559
+ onCloseAutoFocus
5560
+ });
5390
5561
  const contextContainer = usePortalContainer();
5391
5562
  const finalContainer = portalContainer ?? contextContainer ?? void 0;
5392
5563
  useEffect(() => {
@@ -5419,7 +5590,8 @@ var Sheet = ({ children, side = "right", title, modal = true, includeBackgroundO
5419
5590
  }), /* @__PURE__ */ jsxs(DialogPrimitive.Content, {
5420
5591
  ...props,
5421
5592
  ref: setContentEl,
5422
- onOpenAutoFocus: (event) => event.preventDefault(),
5593
+ onOpenAutoFocus: focus.onOpenAutoFocus,
5594
+ onCloseAutoFocus: focus.onCloseAutoFocus,
5423
5595
  onPointerDownOutside,
5424
5596
  onInteractOutside,
5425
5597
  className: cls("outline-none", borderClass[side], defaultBorderMixin, "text-surface-accent-900 dark:text-white", "fixed transform z-50 transition-[transform,opacity] ease-in-out", !displayed ? "duration-150" : "duration-100", "outline-none focus:outline-none focus-visible:outline-none focus-visible:ring-0 focus:ring-0", transparent ? "" : "shadow-md bg-surface-card", side === "top" || side === "bottom" ? "w-full" : "h-full", side === "left" || side === "top" ? "left-0 top-0" : "right-0 bottom-0", displayed && open ? "opacity-100" : "opacity-50", !displayed || !open ? transformValue[side] : "", className),
@@ -5605,7 +5777,7 @@ function setRef(ref, value) {
5605
5777
  }
5606
5778
  //#endregion
5607
5779
  //#region src/components/TextField.tsx
5608
- var TextField = forwardRef(({ value, onChange, label, type = "text", multiline = false, invisible, maxRows, minRows, disabled, error, endAdornment, autoFocus, placeholder, size = "large", className, style, inputClassName, inputStyle, inputRef: inputRefProp, ...inputProps }, ref) => {
5780
+ var TextField = forwardRef(({ value, onChange, onFocus, onBlur, label, type = "text", multiline = false, invisible, maxRows, minRows, disabled, error, endAdornment, autoFocus, placeholder, size = "large", className, style, inputClassName, inputStyle, inputRef: inputRefProp, ...inputProps }, ref) => {
5609
5781
  const fallbackRef = useRef(null);
5610
5782
  const inputRef = inputRefProp ?? fallbackRef;
5611
5783
  const autoId = useId();
@@ -5613,21 +5785,22 @@ var TextField = forwardRef(({ value, onChange, label, type = "text", multiline =
5613
5785
  const labelId = `${inputId}-label`;
5614
5786
  const [focused, setFocused] = React.useState(false);
5615
5787
  const hasValue = value !== void 0 && value !== null && value !== "";
5788
+ const handleFocus = (event) => {
5789
+ setFocused(true);
5790
+ onFocus?.(event);
5791
+ };
5792
+ const handleBlur = (event) => {
5793
+ setFocused(false);
5794
+ onBlur?.(event);
5795
+ };
5616
5796
  useEffect(() => {
5617
5797
  const element = inputRef && "current" in inputRef ? inputRef.current : null;
5618
5798
  if (element && document.activeElement === element) setFocused(true);
5619
5799
  }, []);
5620
- useEffect(() => {
5621
- if (type !== "number") return;
5622
- const handleWheel = (event) => {
5623
- if (event.target instanceof HTMLElement) event.target.blur();
5624
- };
5625
- const element = "current" in inputRef ? inputRef.current : inputRef;
5626
- element?.addEventListener("wheel", handleWheel);
5627
- return () => {
5628
- element?.removeEventListener("wheel", handleWheel);
5629
- };
5630
- }, [inputRef, type]);
5800
+ const handleWheel = (event) => {
5801
+ if (type === "number") event.currentTarget.blur();
5802
+ inputProps.onWheel?.(event);
5803
+ };
5631
5804
  const input = multiline ? /* @__PURE__ */ jsx("textarea", {
5632
5805
  ...inputProps,
5633
5806
  ref: inputRef,
@@ -5635,13 +5808,14 @@ var TextField = forwardRef(({ value, onChange, label, type = "text", multiline =
5635
5808
  "aria-labelledby": label ? labelId : inputProps["aria-labelledby"],
5636
5809
  "aria-invalid": error || void 0,
5637
5810
  "aria-disabled": disabled || void 0,
5811
+ disabled,
5638
5812
  placeholder: focused || hasValue || !label ? placeholder : void 0,
5639
5813
  autoFocus,
5640
5814
  rows: typeof minRows === "string" ? parseInt(minRows) : minRows ?? 3,
5641
5815
  value: value ?? "",
5642
5816
  onChange,
5643
- onFocus: () => setFocused(true),
5644
- onBlur: () => setFocused(false),
5817
+ onFocus: handleFocus,
5818
+ onBlur: handleBlur,
5645
5819
  style: inputStyle,
5646
5820
  className: cls(invisible ? focusedInvisibleMixin : "", "rounded-lg resize-none w-full outline-none text-sm bg-transparent min-h-[64px] px-3", label ? "pt-8 pb-2" : "py-2", disabled && "outline-none opacity-50 text-surface-accent-600 dark:text-surface-accent-500", inputClassName)
5647
5821
  }) : /* @__PURE__ */ jsx("input", {
@@ -5666,8 +5840,9 @@ var TextField = forwardRef(({ value, onChange, label, type = "text", multiline =
5666
5840
  }[size] : size === "smallest" ? "py-0.5" : size === "small" ? "py-1" : "py-2", endAdornment ? "pr-12" : "pr-3", disabled && "outline-none opacity-65 dark:opacity-60 text-surface-accent-800 dark:text-white", inputClassName),
5667
5841
  placeholder: focused || hasValue || !label ? placeholder : void 0,
5668
5842
  autoFocus,
5669
- onFocus: () => setFocused(true),
5670
- onBlur: () => setFocused(false),
5843
+ onFocus: handleFocus,
5844
+ onBlur: handleBlur,
5845
+ onWheel: handleWheel,
5671
5846
  type,
5672
5847
  value: type === "number" && Number.isNaN(value) ? "" : value ?? "",
5673
5848
  onChange
@@ -5977,42 +6152,48 @@ Badge.displayName = "Badge";
5977
6152
  //#endregion
5978
6153
  //#region src/components/DebouncedTextField.tsx
5979
6154
  function DebouncedTextField(props) {
5980
- const [internalValue, setInternalValue] = useState(props.value ?? "");
5981
- const lastSentValueRef = useRef(props.value ?? "");
6155
+ const { value, onChange, name, onKeyDown, onBlur } = props;
6156
+ const [internalValue, setInternalValue] = useState(value ?? "");
6157
+ const lastSentValueRef = useRef(value ?? "");
5982
6158
  const timerRef = useRef(void 0);
5983
6159
  useEffect(() => {
5984
- const externalValue = props.value ?? "";
6160
+ const externalValue = value ?? "";
5985
6161
  if (externalValue !== lastSentValueRef.current) {
5986
6162
  setInternalValue(externalValue);
5987
6163
  lastSentValueRef.current = externalValue;
5988
6164
  }
5989
- }, [props.value]);
6165
+ }, [value]);
6166
+ const pendingFlushRef = useRef(void 0);
5990
6167
  useEffect(() => {
5991
6168
  return () => {
5992
- if (timerRef.current) clearTimeout(timerRef.current);
6169
+ if (timerRef.current) {
6170
+ clearTimeout(timerRef.current);
6171
+ timerRef.current = void 0;
6172
+ pendingFlushRef.current?.();
6173
+ }
5993
6174
  };
5994
6175
  }, []);
5995
- const flushChange = useCallback((value, event) => {
6176
+ const flushChange = useCallback((next, event) => {
5996
6177
  if (timerRef.current) {
5997
6178
  clearTimeout(timerRef.current);
5998
6179
  timerRef.current = void 0;
5999
6180
  }
6000
- if (value !== props.value && props.onChange) {
6001
- lastSentValueRef.current = value;
6181
+ if (next !== value && onChange) {
6182
+ lastSentValueRef.current = next;
6002
6183
  const e = {
6003
6184
  ...event,
6004
6185
  target: {
6005
6186
  ...event?.target,
6006
- value,
6007
- name: props.name
6187
+ value: next,
6188
+ name
6008
6189
  }
6009
6190
  };
6010
- props.onChange(e);
6191
+ onChange(e);
6011
6192
  }
6012
6193
  }, [
6013
- props.value,
6014
- props.onChange,
6015
- props.name
6194
+ value,
6195
+ onChange,
6196
+ name
6016
6197
  ]);
6017
6198
  const internalOnChange = useCallback((event) => {
6018
6199
  const newValue = event.target.value;
@@ -6025,21 +6206,31 @@ function DebouncedTextField(props) {
6025
6206
  name: event?.target?.name
6026
6207
  }
6027
6208
  };
6209
+ pendingFlushRef.current = () => flushChange(newValue, eventCopy);
6028
6210
  timerRef.current = setTimeout(() => {
6029
6211
  flushChange(newValue, eventCopy);
6030
6212
  }, 150);
6031
6213
  }, [flushChange]);
6214
+ const internalOnKeyDown = useCallback((event) => {
6215
+ if (event.key === "Enter" && timerRef.current) flushChange(internalValue);
6216
+ onKeyDown?.(event);
6217
+ }, [
6218
+ internalValue,
6219
+ flushChange,
6220
+ onKeyDown
6221
+ ]);
6032
6222
  const internalOnBlur = useCallback((event) => {
6033
6223
  flushChange(internalValue, event);
6034
- props.onBlur?.(event);
6224
+ onBlur?.(event);
6035
6225
  }, [
6036
6226
  internalValue,
6037
6227
  flushChange,
6038
- props.onBlur
6228
+ onBlur
6039
6229
  ]);
6040
6230
  return /* @__PURE__ */ jsx(TextField, {
6041
6231
  ...props,
6042
6232
  onChange: internalOnChange,
6233
+ onKeyDown: internalOnKeyDown,
6043
6234
  onBlur: internalOnBlur,
6044
6235
  value: internalValue
6045
6236
  });
@@ -7035,12 +7226,12 @@ function VirtualTableInput(props) {
7035
7226
  //#endregion
7036
7227
  //#region src/components/VirtualTable/fields/VirtualTableNumberInput.tsx
7037
7228
  function VirtualTableNumberInput(props) {
7038
- const { align = "left", value, updateValue, focused } = props;
7039
- const propStringValue = value && typeof value === "number" ? value.toString() : "";
7229
+ const { align = "left", value, updateValue, focused, disabled } = props;
7230
+ const propStringValue = typeof value === "number" ? value.toString() : "";
7040
7231
  const [internalValue, setInternalValue] = useState(propStringValue);
7041
7232
  const prevValue = useRef(value);
7042
7233
  useEffect(() => {
7043
- if (prevValue.current !== value && String(value) !== internalValue) setInternalValue(value ? value.toString() : null);
7234
+ if (prevValue.current !== value && String(value) !== internalValue) setInternalValue(value !== void 0 && value !== null ? value.toString() : null);
7044
7235
  prevValue.current = value;
7045
7236
  }, [value]);
7046
7237
  useDebouncedCallback(internalValue, React.useCallback(() => {
@@ -7058,13 +7249,11 @@ function VirtualTableNumberInput(props) {
7058
7249
  if (inputRef.current && focused) inputRef.current.focus({ preventScroll: true });
7059
7250
  }, [focused, inputRef]);
7060
7251
  const regexp = /^-?[0-9]+[,.]?[0-9]*$/;
7061
- return /* @__PURE__ */ jsx(TextField, {
7062
- inputRef,
7063
- invisible: true,
7064
- size: "small",
7065
- className: "w-full",
7066
- inputClassName: cls("p-0 m-0 bg-transparent border-none outline-hidden font-normal leading-normal text-unset", focusedDisabled),
7067
- inputStyle: { textAlign: align },
7252
+ return /* @__PURE__ */ jsx("input", {
7253
+ ref: inputRef,
7254
+ disabled,
7255
+ className: cls("w-full p-0 m-0 bg-transparent border-none outline-none font-mono tabular-nums", focusedDisabled),
7256
+ style: { textAlign: align },
7068
7257
  value: internalValue ?? "",
7069
7258
  onChange: (evt) => {
7070
7259
  const newValue = evt.target.value.replace(",", ".");
@@ -7076,7 +7265,7 @@ function VirtualTableNumberInput(props) {
7076
7265
  //#endregion
7077
7266
  //#region src/components/VirtualTable/fields/VirtualTableSwitch.tsx
7078
7267
  function VirtualTableSwitch(props) {
7079
- const { internalValue, updateValue, focused } = props;
7268
+ const { internalValue, updateValue, focused, disabled } = props;
7080
7269
  const ref = React.useRef(null);
7081
7270
  useEffect(() => {
7082
7271
  if (ref.current && focused) ref.current.focus({ preventScroll: true });
@@ -7085,26 +7274,71 @@ function VirtualTableSwitch(props) {
7085
7274
  ref,
7086
7275
  size: "small",
7087
7276
  value: Boolean(internalValue),
7277
+ disabled,
7088
7278
  onValueChange: updateValue
7089
7279
  });
7090
7280
  }
7091
7281
  //#endregion
7092
7282
  //#region src/components/VirtualTable/fields/VirtualTableDateField.tsx
7093
7283
  function VirtualTableDateField(props) {
7094
- const { name, disabled, error, mode, timezone, internalValue, updateValue, small = false, locale } = props;
7095
- return /* @__PURE__ */ jsx(DateTimeField, {
7096
- value: internalValue ?? void 0,
7097
- onChange: (dateValue) => updateValue(dateValue ?? null),
7098
- disabled,
7099
- error: Boolean(error),
7100
- "aria-label": name,
7101
- invisible: true,
7102
- size: small ? "small" : "medium",
7103
- inputClassName: cls("w-full h-full", focusedDisabled),
7104
- className: cls("w-full h-full", focusedDisabled),
7105
- mode,
7106
- timezone,
7107
- locale
7284
+ const { name, disabled, error, mode, timezone, internalValue, updateValue, small = false, locale, pickerButton = true, inputRef } = props;
7285
+ const [focused, setFocused] = useState(false);
7286
+ const focusedRef = useRef(false);
7287
+ const [draft, setDraft] = useState(null);
7288
+ const draftRef = useRef(null);
7289
+ const timerRef = useRef(void 0);
7290
+ const updateValueRef = useRef(updateValue);
7291
+ useEffect(() => {
7292
+ updateValueRef.current = updateValue;
7293
+ }, [updateValue]);
7294
+ const commit = useCallback(() => {
7295
+ clearTimeout(timerRef.current);
7296
+ const pending = draftRef.current;
7297
+ if (!pending) return;
7298
+ draftRef.current = null;
7299
+ setDraft(null);
7300
+ updateValueRef.current(pending.value);
7301
+ }, []);
7302
+ const onDateChange = (dateValue) => {
7303
+ const pending = { value: dateValue ?? null };
7304
+ draftRef.current = pending;
7305
+ setDraft(pending);
7306
+ clearTimeout(timerRef.current);
7307
+ if (!focusedRef.current) commit();
7308
+ else if (!pending.value || pending.value.getFullYear() >= 1e3) timerRef.current = setTimeout(commit, 400);
7309
+ };
7310
+ useEffect(() => () => {
7311
+ clearTimeout(timerRef.current);
7312
+ if (draftRef.current) updateValueRef.current(draftRef.current.value);
7313
+ }, []);
7314
+ const shownValue = draft ? draft.value : internalValue;
7315
+ return /* @__PURE__ */ jsx("div", {
7316
+ className: "w-full h-full flex items-center",
7317
+ onFocus: () => {
7318
+ focusedRef.current = true;
7319
+ setFocused(true);
7320
+ },
7321
+ onBlur: () => {
7322
+ focusedRef.current = false;
7323
+ setFocused(false);
7324
+ commit();
7325
+ },
7326
+ children: /* @__PURE__ */ jsx(DateTimeField, {
7327
+ value: shownValue ?? void 0,
7328
+ onChange: onDateChange,
7329
+ disabled,
7330
+ error: Boolean(error),
7331
+ "aria-label": name,
7332
+ invisible: true,
7333
+ pickerButton,
7334
+ inputRef,
7335
+ size: small ? "small" : "medium",
7336
+ inputClassName: cls("w-full h-full", focusedDisabled, !pickerButton && "px-0", !shownValue && !focused && "text-transparent"),
7337
+ className: cls("w-full h-full", focusedDisabled),
7338
+ mode,
7339
+ timezone,
7340
+ locale
7341
+ })
7108
7342
  });
7109
7343
  }
7110
7344
  //#endregion
@@ -7184,6 +7418,76 @@ function VirtualTableSelect(props) {
7184
7418
  });
7185
7419
  }
7186
7420
  //#endregion
7421
+ //#region src/views/useScrollParentRestoration.ts
7422
+ /**
7423
+ * The nearest ancestor that scrolls vertically. The list and card views do not
7424
+ * scroll themselves: the page around them does, so that is the element whose
7425
+ * offset is worth keeping.
7426
+ */
7427
+ function getScrollParent(element) {
7428
+ if (!element) return null;
7429
+ let parent = element.parentElement;
7430
+ while (parent) {
7431
+ const overflowY = window.getComputedStyle(parent).overflowY;
7432
+ if (overflowY === "auto" || overflowY === "scroll") return parent;
7433
+ parent = parent.parentElement;
7434
+ }
7435
+ return document.documentElement;
7436
+ }
7437
+ /**
7438
+ * Report the scroll parent's offset through `onScroll`, and put it back at
7439
+ * `initialScroll` once there are rows to scroll over.
7440
+ *
7441
+ * Opening a record full screen unmounts the collection view, so coming back
7442
+ * mounts a fresh one. The caller keeps the offset (and the rows it was over)
7443
+ * outside the component; this is what hands it over and applies it again.
7444
+ */
7445
+ function useScrollParentRestoration({ containerRef, initialScroll, onScroll, dataLength }) {
7446
+ const restoreTo = useRef(initialScroll);
7447
+ useEffect(() => {
7448
+ const target = restoreTo.current;
7449
+ if (!containerRef.current || !target || dataLength === 0) return;
7450
+ const scrollEl = getScrollParent(containerRef.current);
7451
+ if (!scrollEl) return;
7452
+ let attempts = 0;
7453
+ const maxAttempts = 5;
7454
+ let rafId = null;
7455
+ const tryRestore = () => {
7456
+ rafId = null;
7457
+ if (scrollEl.scrollHeight >= target || attempts >= maxAttempts) {
7458
+ scrollEl.scrollTop = target;
7459
+ restoreTo.current = void 0;
7460
+ } else {
7461
+ attempts++;
7462
+ rafId = requestAnimationFrame(tryRestore);
7463
+ }
7464
+ };
7465
+ rafId = requestAnimationFrame(tryRestore);
7466
+ return () => {
7467
+ if (rafId !== null) cancelAnimationFrame(rafId);
7468
+ };
7469
+ }, [containerRef, dataLength]);
7470
+ const lastScrollOffset = useRef(0);
7471
+ useEffect(() => {
7472
+ const el = containerRef.current;
7473
+ if (!el || !onScroll) return;
7474
+ const scrollEl = getScrollParent(el);
7475
+ if (!scrollEl) return;
7476
+ const handleScroll = () => {
7477
+ const currentOffset = scrollEl.scrollTop;
7478
+ const direction = currentOffset > lastScrollOffset.current ? "forward" : "backward";
7479
+ lastScrollOffset.current = currentOffset;
7480
+ onScroll({
7481
+ scrollDirection: direction,
7482
+ scrollOffset: currentOffset,
7483
+ scrollUpdateWasRequested: false
7484
+ });
7485
+ };
7486
+ scrollEl.addEventListener("scroll", handleScroll, { passive: true });
7487
+ return () => scrollEl.removeEventListener("scroll", handleScroll);
7488
+ }, [containerRef, onScroll]);
7489
+ }
7490
+ //#endregion
7187
7491
  //#region src/views/ListView.tsx
7188
7492
  function getEstimatedRowHeight(size) {
7189
7493
  switch (size) {
@@ -7197,16 +7501,7 @@ function getEstimatedRowHeight(size) {
7197
7501
  }
7198
7502
  var OVERSCAN_COUNT = 8;
7199
7503
  var LOAD_MORE_THRESHOLD = 400;
7200
- function getScrollParent$1(element) {
7201
- let parent = element?.parentElement ?? null;
7202
- while (parent) {
7203
- const style = getComputedStyle(parent);
7204
- if (style.overflowY === "auto" || style.overflowY === "scroll" || style.overflow === "auto" || style.overflow === "scroll") return parent;
7205
- parent = parent.parentElement;
7206
- }
7207
- return document.documentElement;
7208
- }
7209
- function ListView({ data, dataLoading = false, noMoreToLoad = false, dataLoadingError, itemCount, setItemCount, pageSize = 50, paginationEnabled = true, onItemClick, selectedIds, highlightedIds, selectionEnabled = true, onSelectionChange, emptyComponent, size = "m", selectedEntityId, header, renderRow }) {
7504
+ function ListView({ data, dataLoading = false, noMoreToLoad = false, dataLoadingError, itemCount, setItemCount, pageSize = 50, paginationEnabled = true, onItemClick, selectedIds, highlightedIds, selectionEnabled = true, onSelectionChange, emptyComponent, onScroll, initialScroll, size = "m", selectedEntityId, header, renderRow }) {
7210
7505
  const containerRef = useRef(null);
7211
7506
  const rowsRef = useRef(null);
7212
7507
  const isLoadingMore = useRef(false);
@@ -7238,7 +7533,7 @@ function ListView({ data, dataLoading = false, noMoreToLoad = false, dataLoading
7238
7533
  useEffect(() => {
7239
7534
  const el = containerRef.current;
7240
7535
  if (!el) return;
7241
- const scrollEl = getScrollParent$1(el);
7536
+ const scrollEl = getScrollParent(el);
7242
7537
  if (!scrollEl) return;
7243
7538
  let rafId = null;
7244
7539
  const update = () => {
@@ -7253,19 +7548,25 @@ function ListView({ data, dataLoading = false, noMoreToLoad = false, dataLoading
7253
7548
  setItemCount?.((ic ?? ps) + ps);
7254
7549
  }
7255
7550
  };
7256
- const onScroll = () => {
7551
+ const onScrollEvent = () => {
7257
7552
  if (rafId === null) rafId = requestAnimationFrame(update);
7258
7553
  };
7259
- scrollEl.addEventListener("scroll", onScroll, { passive: true });
7554
+ scrollEl.addEventListener("scroll", onScrollEvent, { passive: true });
7260
7555
  const ro = new ResizeObserver(() => update());
7261
7556
  ro.observe(scrollEl);
7262
7557
  update();
7263
7558
  return () => {
7264
- scrollEl.removeEventListener("scroll", onScroll);
7559
+ scrollEl.removeEventListener("scroll", onScrollEvent);
7265
7560
  ro.disconnect();
7266
7561
  if (rafId !== null) cancelAnimationFrame(rafId);
7267
7562
  };
7268
7563
  }, [setItemCount]);
7564
+ useScrollParentRestoration({
7565
+ containerRef,
7566
+ initialScroll,
7567
+ onScroll,
7568
+ dataLength: data.length
7569
+ });
7269
7570
  const totalHeight = data.length * estimatedRowHeight;
7270
7571
  const startIndex = Math.max(0, Math.floor(effectiveScrollTop / estimatedRowHeight) - OVERSCAN_COUNT);
7271
7572
  const endIndex = Math.min(data.length, Math.ceil((effectiveScrollTop + viewportHeight) / estimatedRowHeight) + OVERSCAN_COUNT);
@@ -7392,19 +7693,8 @@ function getGridColumnsClass(size) {
7392
7693
  default: return "grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6";
7393
7694
  }
7394
7695
  }
7395
- function getScrollParent(element) {
7396
- if (!element) return null;
7397
- let parent = element.parentElement;
7398
- while (parent) {
7399
- const overflowY = window.getComputedStyle(parent).overflowY;
7400
- if (overflowY === "auto" || overflowY === "scroll") return parent;
7401
- parent = parent.parentElement;
7402
- }
7403
- return document.documentElement;
7404
- }
7405
7696
  function CardView({ data, dataLoading = false, noMoreToLoad = false, dataLoadingError, itemCount, setItemCount, pageSize = 50, paginationEnabled = true, onItemClick, selectedIds, highlightedIds, selectionEnabled = true, onSelectionChange, onScroll, initialScroll, size = "m", renderCard, emptyComponent }) {
7406
7697
  const containerRef = useRef(null);
7407
- const hasRestoredScroll = useRef(false);
7408
7698
  const isLoadingMore = useRef(false);
7409
7699
  const paginationStateRef = useRef({
7410
7700
  paginationEnabled,
@@ -7455,42 +7745,12 @@ function CardView({ data, dataLoading = false, noMoreToLoad = false, dataLoading
7455
7745
  if (rafId !== null) cancelAnimationFrame(rafId);
7456
7746
  };
7457
7747
  }, [setItemCount]);
7458
- useEffect(() => {
7459
- if (!containerRef.current || !initialScroll || hasRestoredScroll.current || data.length === 0) return;
7460
- const scrollEl = getScrollParent(containerRef.current);
7461
- if (!scrollEl) return;
7462
- let attempts = 0;
7463
- const maxAttempts = 5;
7464
- const tryRestore = () => {
7465
- if (scrollEl.scrollHeight >= initialScroll || attempts >= maxAttempts) {
7466
- scrollEl.scrollTop = initialScroll;
7467
- hasRestoredScroll.current = true;
7468
- } else {
7469
- attempts++;
7470
- requestAnimationFrame(tryRestore);
7471
- }
7472
- };
7473
- requestAnimationFrame(tryRestore);
7474
- }, [initialScroll, data.length]);
7475
- const lastScrollOffset = useRef(0);
7476
- useEffect(() => {
7477
- const el = containerRef.current;
7478
- if (!el || !onScroll) return;
7479
- const scrollEl = getScrollParent(el);
7480
- if (!scrollEl) return;
7481
- const handleScroll = () => {
7482
- const currentOffset = scrollEl.scrollTop;
7483
- const direction = currentOffset > lastScrollOffset.current ? "forward" : "backward";
7484
- lastScrollOffset.current = currentOffset;
7485
- onScroll({
7486
- scrollDirection: direction,
7487
- scrollOffset: currentOffset,
7488
- scrollUpdateWasRequested: false
7489
- });
7490
- };
7491
- scrollEl.addEventListener("scroll", handleScroll, { passive: true });
7492
- return () => scrollEl.removeEventListener("scroll", handleScroll);
7493
- }, [onScroll]);
7748
+ useScrollParentRestoration({
7749
+ containerRef,
7750
+ initialScroll,
7751
+ onScroll,
7752
+ dataLength: data.length
7753
+ });
7494
7754
  const getItemId = useCallback((item) => {
7495
7755
  if (item && typeof item === "object" && "id" in item) {
7496
7756
  const id = item.id;
@@ -9265,6 +9525,40 @@ function CollectionView({ dataController, properties, propertiesOrder, displayed
9265
9525
  });
9266
9526
  }
9267
9527
  //#endregion
9268
- export { Alert, AlertCircleIcon, AlertTriangleIcon, AlignLeftIcon, AppWindow, ArrowDownIcon, ArrowDownToLineIcon, ArrowLeftIcon, ArrowRightFromLineIcon, ArrowRightIcon, ArrowRightLeftIcon, ArrowRightToLineIcon, ArrowUpDownIcon, ArrowUpIcon, ArrowUpToLineIcon, Autocomplete, AutocompleteItem, Avatar, Badge, BoldIcon, BookOpenIcon, BooleanSwitch, BooleanSwitchWithLabel, Button, CHIP_COLORS, CHIP_HUES, CHIP_SEED_KEYS, CONTROL_HEIGHT, CalendarIcon, Card, CardView, CenteredView, CheckCircle2Icon, CheckCircleIcon, CheckIcon, CheckSquareIcon, Checkbox, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronUpIcon, ChevronsLeftIcon, ChevronsRightIcon, ChevronsUpDownIcon, Chip, CircleDotIcon, CircleIcon, CircleUserIcon, CircularProgress, CircularProgressCenter, CodeIcon, Collapse, CollectionView, ColorPicker, ColumnsIcon, Container, CopyIcon, DatabaseIcon, DateTimeField, DebouncedTextField, Dialog, DialogActions, DialogContent, DialogTitle, DollarSignIcon, DownloadIcon, ErrorBoundary, ExpandablePanel, ExternalLinkIcon, EyeIcon, EyeOffIcon, FileIcon, FileSearchIcon, FileTextIcon, FileUpload, FilterChip, FilterIcon, FilterXIcon, FlagIcon, FolderIcon, FolderKanbanIcon, FolderPlusIcon, FolderUpIcon, FunctionSquareIcon, GitBranchIcon, GitHubIcon, GlobeIcon, HandleIcon, HashIcon, Heading1Icon, Heading2Icon, Heading3Icon, HelpCircleIcon, HistoryIcon, HomeIcon, IconButton, ImageIcon, ImageOffIcon, InfoIcon, InfoLabel, InputLabel, ItalicIcon, KanbanIcon, KanbanView, KeyIcon, KeyRoundIcon, Label, LanguagesIcon, LayoutGridIcon, Link2Icon, LinkIcon, ListIcon, ListOrderedIcon, ListPlusIcon, ListTodoIcon, ListView, LoaderIcon, LoadingButton, LockIcon, LogOutIcon, LucideIconByName, MailIcon, Markdown, Maximize2Icon, Menu, MenuIcon, MenuItem, Menubar, MenubarCheckboxItem, MenubarContent, MenubarItem, MenubarItemIndicator, MenubarMenu, MenubarPortal, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarSubTriggerIndicator, MenubarTrigger, MessageCircleIcon, MinusCircleIcon, MinusIcon, MoonIcon, MoreVerticalIcon, MultiSelect, MultiSelectContext, MultiSelectItem, Music2Icon, PanelLeftCloseIcon, PanelLeftIcon, PanelLeftOpenIcon, Paper, PauseIcon, PenLineIcon, PencilIcon, PhoneIcon, PinIcon, PlayIcon, PlusIcon, Popover, PopoverPrimitive, Portal, PortalContainerProvider, QuoteIcon, RadioGroup, RadioGroupItem, RefreshCcwIcon, RefreshCwIcon, RepeatIcon, ResizablePanels, Rows3Icon, SaveIcon, SearchBar, SearchIcon, Select, SelectGroup, SelectItem, SendIcon, Separator, SettingsIcon, Sheet, ShieldIcon, ShoppingCartIcon, Skeleton, Slider, SlidersHorizontalIcon, Slot, SquareIcon, StarIcon, StickyNoteIcon, StrikethroughIcon, SunIcon, SunMoonIcon, Tab, Table, TableBody, TableCell, TableHeader, TableIcon, TableRow, Tabs, TagIcon, TerminalIcon, TextField, TextIcon, TextareaAutosize, ToggleButtonGroup, Tooltip, Trash2Icon, TrendingUpIcon, TypeIcon, Typography, UnderlineIcon, UndoIcon, Unlink2Icon, UploadCloudIcon, UploadIcon, UserCheckIcon, UserIcon, UserPlus, UsersIcon, VideoIcon, VirtualTable, VirtualTableDateField, VirtualTableInput, VirtualTableNumberInput, VirtualTableSelect, VirtualTableSelectionProvider, VirtualTableSwitch, VoteIcon, Wand2Icon, WrenchIcon, XCircleIcon, XIcon, accentTextMixin, cardClickableMixin, cardMixin, cardSelectedMixin, cls, codeSurfaceMixin, colorClassesMapping, controlHeightMixin, controlPaddingMixin, coolIconKeys, createVirtualTableSelectionStore, debounce, defaultBorderMixin, fieldBackgroundDisabledMixin, fieldBackgroundHoverMixin, fieldBackgroundInvisibleMixin, fieldBackgroundMixin, focusedClasses, focusedDisabled, focusedInvisibleMixin, getColorSchemeForKey, getColorSchemeForSeed, getLoadedLucideIcons, iconKeys, iconSize, isChunkLoadError, lazyChunk, loadChunk, loadLucideIcons, lucideIcons, paperMixin, resolveLucideIcon, surfaceActiveMixin, surfaceHoverMixin, useAutoComplete, useDebounceCallback, useDebounceValue, useDebouncedCallback, useInjectStyles, useIsDarkMode, useLucideIcons, useOutsideAlerter, usePortalContainer, useVirtualTableCellSelected, useVirtualTableSelection };
9528
+ //#region src/util/keys.ts
9529
+ /**
9530
+ * Whether a key event has already been handled by something above you — the
9531
+ * one question every global key handler (a `keydown` listener on `window` or
9532
+ * `document`) must ask before acting.
9533
+ *
9534
+ * The rule: **a handler that acts on a key calls `event.preventDefault()`;
9535
+ * a global handler that finds the event already handled does nothing.** Every
9536
+ * kit overlay follows it for free — `Dialog`, `Sheet`, `Popover`, `Menu`,
9537
+ * `Select`, `MultiSelect`, `Tooltip` are Radix layers, and a Radix layer calls
9538
+ * `preventDefault()` on the Escape it consumes to close itself. So Escape
9539
+ * pressed in an open dropdown closes the dropdown, and the record panel's
9540
+ * Escape handler underneath sees it handled and leaves the record open.
9541
+ *
9542
+ * Two conditions make it hold:
9543
+ *
9544
+ * - **Listen in the bubble phase.** A Radix layer listens on `document` in
9545
+ * the capture phase, so it has run before any bubble listener — on the
9546
+ * target, on `document` or on `window` — whatever order they were added in.
9547
+ * A capture listener added before the layer opened runs *before* it and
9548
+ * cannot see its claim. Only a layer of your own, one that sits above
9549
+ * everything else while it is open, belongs in the capture phase, and it
9550
+ * must call `preventDefault()` when it acts.
9551
+ * - **Do not ask the DOM instead.** `document.querySelector('[role="dialog"]')`
9552
+ * misses every layer that is not a dialog (a menu is `role="menu"`, a select
9553
+ * list `role="listbox"`), and the list of roles is never finished.
9554
+ *
9555
+ * An IME composition counts as handled too: Escape while composing Japanese or
9556
+ * Chinese text cancels the composition, and must not close a panel.
9557
+ */
9558
+ function isKeyHandled(event) {
9559
+ return event.defaultPrevented || event.isComposing;
9560
+ }
9561
+ //#endregion
9562
+ export { Alert, AlertCircleIcon, AlertTriangleIcon, AlignLeftIcon, AppWindow, ArrowDownIcon, ArrowDownToLineIcon, ArrowLeftIcon, ArrowRightFromLineIcon, ArrowRightIcon, ArrowRightLeftIcon, ArrowRightToLineIcon, ArrowUpDownIcon, ArrowUpIcon, ArrowUpToLineIcon, Autocomplete, AutocompleteItem, Avatar, Badge, BoldIcon, BookOpenIcon, BooleanSwitch, BooleanSwitchWithLabel, Button, CHIP_COLORS, CHIP_HUES, CHIP_SEED_KEYS, CONTROL_HEIGHT, CalendarIcon, Card, CardView, CenteredView, CheckCircle2Icon, CheckCircleIcon, CheckIcon, CheckSquareIcon, Checkbox, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronUpIcon, ChevronsLeftIcon, ChevronsRightIcon, ChevronsUpDownIcon, Chip, CircleDotIcon, CircleIcon, CircleUserIcon, CircularProgress, CircularProgressCenter, CodeIcon, Collapse, CollectionView, ColorPicker, ColumnsIcon, Container, CopyIcon, DatabaseIcon, DateTimeField, DebouncedTextField, Dialog, DialogActions, DialogContent, DialogTitle, DollarSignIcon, DownloadIcon, ErrorBoundary, ExpandablePanel, ExternalLinkIcon, EyeIcon, EyeOffIcon, FileIcon, FileSearchIcon, FileTextIcon, FileUpload, FilterChip, FilterIcon, FilterXIcon, FlagIcon, FolderIcon, FolderKanbanIcon, FolderPlusIcon, FolderUpIcon, FunctionSquareIcon, GitBranchIcon, GitHubIcon, GlobeIcon, HandleIcon, HashIcon, Heading1Icon, Heading2Icon, Heading3Icon, HelpCircleIcon, HistoryIcon, HomeIcon, IconButton, ImageIcon, ImageOffIcon, InfoIcon, InfoLabel, InputLabel, ItalicIcon, KanbanIcon, KanbanView, KeyIcon, KeyRoundIcon, Label, LanguagesIcon, LayoutGridIcon, Link2Icon, LinkIcon, ListIcon, ListOrderedIcon, ListPlusIcon, ListTodoIcon, ListView, LoaderIcon, LoadingButton, LockIcon, LogOutIcon, LucideIconByName, MailIcon, Markdown, Maximize2Icon, Menu, MenuIcon, MenuItem, Menubar, MenubarCheckboxItem, MenubarContent, MenubarItem, MenubarItemIndicator, MenubarMenu, MenubarPortal, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarSubTriggerIndicator, MenubarTrigger, MessageCircleIcon, MinusCircleIcon, MinusIcon, MoonIcon, MoreVerticalIcon, MultiSelect, MultiSelectContext, MultiSelectItem, Music2Icon, PanelLeftCloseIcon, PanelLeftIcon, PanelLeftOpenIcon, Paper, PauseIcon, PenLineIcon, PencilIcon, PhoneIcon, PinIcon, PlayIcon, PlusIcon, Popover, PopoverPrimitive, Portal, PortalContainerProvider, QuoteIcon, RadioGroup, RadioGroupItem, RefreshCcwIcon, RefreshCwIcon, RepeatIcon, ResizablePanels, Rows3Icon, SaveIcon, SearchBar, SearchIcon, Select, SelectGroup, SelectItem, SendIcon, Separator, SettingsIcon, Sheet, ShieldIcon, ShoppingCartIcon, Skeleton, Slider, SlidersHorizontalIcon, Slot, SquareIcon, StarIcon, StickyNoteIcon, StrikethroughIcon, SunIcon, SunMoonIcon, Tab, Table, TableBody, TableCell, TableHeader, TableIcon, TableRow, Tabs, TagIcon, TerminalIcon, TextField, TextIcon, TextareaAutosize, ToggleButtonGroup, Tooltip, Trash2Icon, TrendingUpIcon, TypeIcon, Typography, UnderlineIcon, UndoIcon, Unlink2Icon, UploadCloudIcon, UploadIcon, UserCheckIcon, UserIcon, UserPlus, UsersIcon, VideoIcon, VirtualTable, VirtualTableDateField, VirtualTableInput, VirtualTableNumberInput, VirtualTableSelect, VirtualTableSelectionProvider, VirtualTableSwitch, VoteIcon, Wand2Icon, WrenchIcon, XCircleIcon, XIcon, accentTextMixin, cardClickableMixin, cardMixin, cardSelectedMixin, cls, codeSurfaceMixin, colorClassesMapping, controlHeightMixin, controlPaddingMixin, coolIconKeys, createVirtualTableSelectionStore, debounce, defaultBorderMixin, fieldBackgroundDisabledMixin, fieldBackgroundHoverMixin, fieldBackgroundInvisibleMixin, fieldBackgroundMixin, focusedClasses, focusedDisabled, focusedInvisibleMixin, getColorSchemeForKey, getColorSchemeForSeed, getLoadedLucideIcons, iconKeys, iconSize, isChunkLoadError, isKeyHandled, lazyChunk, loadChunk, loadLucideIcons, lucideIcons, paperMixin, resolveLucideIcon, surfaceActiveMixin, surfaceHoverMixin, useAutoComplete, useDebounceCallback, useDebounceValue, useDebouncedCallback, useInjectStyles, useIsDarkMode, useLucideIcons, useOutsideAlerter, usePortalContainer, useVirtualTableCellSelected, useVirtualTableSelection };
9269
9563
 
9270
9564
  //# sourceMappingURL=index.es.js.map