@rebasepro/ui 0.23.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
@@ -3093,7 +3093,7 @@ var BooleanSwitch = React.forwardRef(function BooleanSwitch({ value, allowIndete
3093
3093
  else onValueChange?.(null);
3094
3094
  else onValueChange?.(!value);
3095
3095
  },
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", 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),
3097
3097
  ...props,
3098
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")]
3099
3099
  });
@@ -3104,7 +3104,7 @@ var BooleanSwitch = React.forwardRef(function BooleanSwitch({ value, allowIndete
3104
3104
  * Simple boolean switch.
3105
3105
  *
3106
3106
  */
3107
- 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 }) {
3108
3108
  const ref = React.useRef(null);
3109
3109
  const refInput = React.useRef(null);
3110
3110
  const switchLabelId = React.useId();
@@ -3112,18 +3112,13 @@ var BooleanSwitchWithLabel = function BooleanSwitchWithLabel({ value, position =
3112
3112
  const onFocus = () => setFocused(true);
3113
3113
  const onBlur = () => setFocused(false);
3114
3114
  React.useEffect(() => {
3115
- if (autoFocus) {}
3115
+ if (autoFocus) refInput.current?.focus();
3116
3116
  }, []);
3117
3117
  const focus = document.activeElement === refInput?.current || document.activeElement === ref?.current;
3118
3118
  return /* @__PURE__ */ jsxs("div", {
3119
3119
  ref,
3120
3120
  onFocus,
3121
3121
  onBlur,
3122
- role: "switch",
3123
- "aria-checked": props.allowIndeterminate && (value === null || value === void 0) ? "mixed" : !!value,
3124
- "aria-disabled": disabled || void 0,
3125
- "aria-labelledby": label ? switchLabelId : void 0,
3126
- tabIndex: -1,
3127
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),
3128
3123
  onClick: disabled ? void 0 : (e) => {
3129
3124
  if (props.allowIndeterminate) {
@@ -3137,7 +3132,9 @@ var BooleanSwitchWithLabel = function BooleanSwitchWithLabel({ value, position =
3137
3132
  value,
3138
3133
  ref: refInput,
3139
3134
  size,
3140
- 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),
3141
3138
  disabled,
3142
3139
  ...props
3143
3140
  }), /* @__PURE__ */ jsx("div", {
@@ -3154,9 +3151,9 @@ var ButtonInner = React.memo(React.forwardRef(({ children, className, variant =
3154
3151
  const buttonClasses = cls({
3155
3152
  "w-full": fullWidth,
3156
3153
  "w-fit": !fullWidth,
3157
- "border border-primary bg-primary focus:ring-primary text-white hover:text-white hover:brightness-105": variant === "filled" && color === "primary" && !disabled,
3158
- "border border-secondary bg-secondary focus:ring-secondary text-white hover:text-white hover:brightness-105": variant === "filled" && color === "secondary" && !disabled,
3159
- "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,
3160
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,
3161
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,
3162
3159
  "border border-transparent text-primary hover:text-primary hover:bg-surface-hover": variant === "text" && color === "primary" && !disabled,
@@ -3736,12 +3733,74 @@ function Chip({ children, colorScheme, error, outlined, variant = "tinted", onCl
3736
3733
  });
3737
3734
  }
3738
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
3739
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
+ }
3740
3798
  var Tooltip = ({ open, defaultOpen, side = "bottom", delayDuration = 200, sideOffset, align, onOpenChange, title, tooltipClassName, tooltipStyle, children, asChild = false, container, className, style }) => {
3741
3799
  useInjectStyles("Tooltip", styles$2);
3742
3800
  const contextContainer = usePortalContainer();
3743
3801
  const finalContainer = container ?? contextContainer ?? void 0;
3744
3802
  if (!title) return /* @__PURE__ */ jsx(Fragment, { children });
3803
+ children = nameFromTitle(children, title);
3745
3804
  const trigger = asChild ? /* @__PURE__ */ jsx(TooltipPrimitive.Trigger, {
3746
3805
  asChild: true,
3747
3806
  children
@@ -3921,51 +3980,6 @@ function ColorPicker({ value, onChange, size = "medium", allowClear = true, disa
3921
3980
  });
3922
3981
  }
3923
3982
  //#endregion
3924
- //#region src/components/IconButton.tsx
3925
- var buttonClasses = "hover:scale-[1.04] active:scale-95 transition-transform";
3926
- 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";
3927
- var colorClasses$1 = "text-surface-accent-500 visited:text-surface-accent-500 dark:text-surface-accent-300 dark:visited:text-surface-accent-300";
3928
- var sizeClasses$1 = {
3929
- medium: "w-10 !h-10 min-w-10 min-h-10 p-2",
3930
- small: "w-8 !h-8 min-w-8 min-h-8 p-2",
3931
- smallest: "w-7 !h-7 min-w-7 min-h-7 p-2",
3932
- large: "w-12 !h-12 min-w-12 min-h-12 p-2"
3933
- };
3934
- var shapeClasses = {
3935
- circular: "rounded-full",
3936
- square: "rounded-md"
3937
- };
3938
- /** Icon size used when the caller doesn't set one on the child. */
3939
- var defaultIconSize = {
3940
- smallest: 16,
3941
- small: 18,
3942
- medium: 20,
3943
- large: 24
3944
- };
3945
- var IconButtonInner = ({ children, className, size = "medium", variant = "ghost", shape = "circular", disabled, toggled, component, onClick, ...props }, ref) => {
3946
- const bgClasses = variant === "ghost" ? "bg-transparent hover:bg-surface-hover" : "bg-surface-raised hover:bg-surface-raised-hover";
3947
- const Component = component || "button";
3948
- const isNativeButton = Component === "button";
3949
- const sizedChildren = React.Children.map(children, (child) => {
3950
- if (!React.isValidElement(child)) return child;
3951
- if (child.props.size !== void 0) return child;
3952
- return React.cloneElement(child, { size: defaultIconSize[size] });
3953
- });
3954
- return /* @__PURE__ */ jsx(Component, {
3955
- type: isNativeButton ? "button" : void 0,
3956
- role: isNativeButton ? void 0 : "button",
3957
- ref,
3958
- disabled: isNativeButton ? disabled : void 0,
3959
- "aria-disabled": disabled || void 0,
3960
- tabIndex: disabled ? -1 : void 0,
3961
- onClick: disabled ? void 0 : onClick,
3962
- ...props,
3963
- 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),
3964
- children: sizedChildren
3965
- });
3966
- };
3967
- var IconButton = React.forwardRef(IconButtonInner);
3968
- //#endregion
3969
3983
  //#region src/components/InputLabel.tsx
3970
3984
  var defaultClasses = {
3971
3985
  root: "origin-left transition-transform block whitespace-nowrap overflow-hidden text-ellipsis max-w-full",
@@ -4003,6 +4017,7 @@ var DateTimeField = ({ value, label, onChange, disabled, clearable, mode = "date
4003
4017
  useInjectStyles("DateTimeField", inputStyles);
4004
4018
  const handleClear = (e) => {
4005
4019
  e.preventDefault();
4020
+ if (disabled) return;
4006
4021
  setInternalValue("");
4007
4022
  setIsTyping(false);
4008
4023
  onChange?.(null);
@@ -4144,15 +4159,21 @@ var DateTimeField = ({ value, label, onChange, disabled, clearable, mode = "date
4144
4159
  }),
4145
4160
  pickerButton && /* @__PURE__ */ jsx(IconButton, {
4146
4161
  size: adornmentSize,
4162
+ "aria-label": "Open calendar",
4163
+ disabled,
4147
4164
  onClick: (e) => {
4148
4165
  e.stopPropagation();
4149
- inputRef.current?.showPicker();
4166
+ try {
4167
+ inputRef.current?.showPicker();
4168
+ } catch {}
4150
4169
  },
4151
4170
  className: "absolute right-3 top-1/2 transform -translate-y-1/2 text-surface-accent-500!",
4152
4171
  children: /* @__PURE__ */ jsx(CalendarIcon$1, {})
4153
4172
  }),
4154
4173
  clearable && value && /* @__PURE__ */ jsx(IconButton, {
4155
4174
  size: adornmentSize,
4175
+ "aria-label": "Clear date",
4176
+ disabled,
4156
4177
  onClick: handleClear,
4157
4178
  className: "absolute right-14 top-1/2 transform -translate-y-1/2 text-surface-accent-400 ",
4158
4179
  children: /* @__PURE__ */ jsx(XIcon$1, {})
@@ -4247,6 +4268,82 @@ function useRestoreInterruptedFocus(container) {
4247
4268
  }, [container]);
4248
4269
  }
4249
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
4250
4347
  //#region src/components/Dialog.tsx
4251
4348
  var widthClasses = {
4252
4349
  xs: "max-w-xs w-xs",
@@ -4262,11 +4359,20 @@ var widthClasses = {
4262
4359
  "7xl": "max-w-7xl w-7xl",
4263
4360
  full: "max-w-full w-full"
4264
4361
  };
4265
- 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 }) => {
4266
4363
  const [displayed, setDisplayed] = useState(false);
4267
4364
  const [popupHost, setPopupHost] = useState(null);
4268
4365
  const [contentEl, setContentEl] = useState(null);
4269
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
+ });
4270
4376
  const contextContainer = usePortalContainer();
4271
4377
  const finalContainer = portalContainer ?? contextContainer ?? void 0;
4272
4378
  useEffect(() => {
@@ -4287,23 +4393,29 @@ var Dialog = ({ open, onOpenChange, children, className, containerClassName, ful
4287
4393
  children: /* @__PURE__ */ jsx(DialogPrimitive.Portal, {
4288
4394
  container: finalContainer,
4289
4395
  children: /* @__PURE__ */ jsxs("div", {
4290
- 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),
4291
4398
  children: [/* @__PURE__ */ jsx(DialogPrimitive.Overlay, {
4292
- 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"),
4293
4401
  style: { pointerEvents: displayed ? "auto" : "none" }
4294
4402
  }), /* @__PURE__ */ jsxs(DialogPrimitive.Content, {
4295
4403
  ref: setContentEl,
4296
4404
  onEscapeKeyDown,
4297
- onOpenAutoFocus: (e) => {
4298
- if (disableInitialFocus) e.preventDefault();
4299
- 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();
4300
4412
  },
4301
- onPointerDownOutside,
4413
+ onFocusOutside: (e) => e.preventDefault(),
4302
4414
  onInteractOutside,
4303
4415
  "aria-describedby": ariaDescribedby,
4304
- 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),
4305
4417
  children: [/* @__PURE__ */ jsx("div", {
4306
- 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),
4307
4419
  children: /* @__PURE__ */ jsx(PortalContainerProvider, {
4308
4420
  container: popupHost,
4309
4421
  children
@@ -4409,6 +4521,12 @@ function ExpandablePanel({ title, children, invisible = false, expanded, onExpan
4409
4521
  role: "button",
4410
4522
  tabIndex: 0,
4411
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
+ },
4412
4530
  children: [title, /* @__PURE__ */ jsx(ChevronDownIcon$1, { className: cls("transition", open ? "rotate-180" : "") })]
4413
4531
  })
4414
4532
  }), /* @__PURE__ */ jsx(Collapsible.Content, {
@@ -4517,12 +4635,11 @@ var Label = React$1.forwardRef(({ className, border, onClick, ...props }, ref) =
4517
4635
  Label.displayName = LabelPrimitive.Root.displayName;
4518
4636
  //#endregion
4519
4637
  //#region src/components/LoadingButton.tsx
4520
- function LoadingButton({ children, loading, disabled, onClick, startIcon, ...props }) {
4638
+ function LoadingButton({ children, loading, startIcon, ...props }) {
4639
+ const buttonProps = props;
4521
4640
  return /* @__PURE__ */ jsxs(Button, {
4522
- disabled: loading || disabled,
4523
- onClick,
4524
- component: props.component,
4525
- ...props,
4641
+ ...buttonProps,
4642
+ disabled: loading || buttonProps.disabled,
4526
4643
  children: [
4527
4644
  loading && /* @__PURE__ */ jsx(CircularProgress, { size: "smallest" }),
4528
4645
  !loading && startIcon,
@@ -4787,7 +4904,7 @@ function MultiSelectTrigger({ anchorRef, children }) {
4787
4904
  });
4788
4905
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(PopoverPrimitive.Anchor, { virtualRef: anchorRef }), children] });
4789
4906
  }
4790
- var MultiSelect = React$1.forwardRef(({ value, size = "large", label, error, onValueChange, invisible, chevron = true, anchorRef, disabled, placeholder, modalPopover = true, includeClear = true, includeSelectAll = true, useChips = true, className, inputClassName, inputRef, children, renderValues, open, onOpenChange, portalContainer, "aria-label": ariaLabel }, ref) => {
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) => {
4791
4908
  const [isMounted, setIsMounted] = useState(false);
4792
4909
  const [isPopoverOpen, setIsPopoverOpen] = useState(open ?? false);
4793
4910
  const [selectedValues, setSelectedValues] = useState(value ?? []);
@@ -4860,6 +4977,7 @@ var MultiSelect = React$1.forwardRef(({ value, size = "large", label, error, onV
4860
4977
  fieldValue: selectedValues,
4861
4978
  onItemClick
4862
4979
  }), [selectedValues, onItemClick]);
4980
+ const bare = triggerContent !== void 0;
4863
4981
  return /* @__PURE__ */ jsxs(MultiSelectContext.Provider, {
4864
4982
  value: contextValue,
4865
4983
  children: [typeof label === "string" ? /* @__PURE__ */ jsx(SelectInputLabel, {
@@ -4876,7 +4994,7 @@ var MultiSelect = React$1.forwardRef(({ value, size = "large", label, error, onV
4876
4994
  disabled,
4877
4995
  "aria-label": ariaLabel ?? (typeof label === "string" ? label : void 0),
4878
4996
  onClick: handleTogglePopover,
4879
- 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 ? {
4880
4998
  smallest: "min-h-[28px]",
4881
4999
  small: "min-h-[32px]",
4882
5000
  medium: "min-h-[40px]",
@@ -4893,8 +5011,8 @@ var MultiSelect = React$1.forwardRef(({ value, size = "large", label, error, onV
4893
5011
  }, {
4894
5012
  "px-2": size === "small" || size === "smallest",
4895
5013
  "px-4": size === "medium" || size === "large"
4896
- }, "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),
4897
- 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", {
4898
5016
  className: "flex justify-between items-center w-full",
4899
5017
  children: [/* @__PURE__ */ jsxs("div", {
4900
5018
  className: "flex flex-wrap items-center gap-1.5 text-start",
@@ -5211,7 +5329,7 @@ function SearchBar({ onClick, onTextSearch, placeholder = "Search", expandable =
5211
5329
  }
5212
5330
  //#endregion
5213
5331
  //#region src/components/Select.tsx
5214
- 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, 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) => {
5215
5333
  const [openInternal, setOpenInternal] = useState(open ?? false);
5216
5334
  useEffect(() => {
5217
5335
  setOpenInternal(open ?? false);
@@ -5263,6 +5381,17 @@ var Select = forwardRef(({ inputRef, open, name, fullWidth = false, id, onOpenCh
5263
5381
  stringValue,
5264
5382
  value
5265
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
+ };
5266
5395
  return /* @__PURE__ */ jsxs(SelectPrimitive.Root, {
5267
5396
  name,
5268
5397
  value: stringValue,
@@ -5280,11 +5409,7 @@ var Select = forwardRef(({ inputRef, open, name, fullWidth = false, id, onOpenCh
5280
5409
  children: label
5281
5410
  }) : label,
5282
5411
  /* @__PURE__ */ jsxs("div", {
5283
- className: cls("select-none rounded-lg text-sm", invisible ? fieldBackgroundInvisibleMixin : fieldBackgroundMixin, disabled ? fieldBackgroundDisabledMixin : invisible ? "" : fieldBackgroundHoverMixin, "relative flex items-center", className, {
5284
- "min-h-[28px]": size === "smallest",
5285
- "min-h-[32px]": size === "small",
5286
- "min-h-[40px]": size === "medium",
5287
- "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, {
5288
5413
  "w-fit": !fullWidth,
5289
5414
  "w-full": fullWidth
5290
5415
  }),
@@ -5295,36 +5420,33 @@ var Select = forwardRef(({ inputRef, open, name, fullWidth = false, id, onOpenCh
5295
5420
  type: "button",
5296
5421
  "aria-label": ariaLabel ?? (typeof label === "string" ? label : typeof renderValue === "string" ? renderValue : "Select an option"),
5297
5422
  "aria-invalid": error || void 0,
5298
- className: cls("h-full", padding ? {
5423
+ className: cls("h-full", padding && !bare ? {
5299
5424
  "px-4": size === "large",
5300
5425
  "px-3": size === "medium",
5301
5426
  "px-2": size === "small" || size === "smallest"
5302
- } : "", "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", {
5303
- "min-h-[28px]": size === "smallest",
5304
- "min-h-[32px]": size === "small",
5305
- "min-h-[40px]": size === "medium",
5306
- "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", {
5307
5434
  "w-full": fullWidth,
5308
5435
  "w-fit": !fullWidth
5309
5436
  }, inputClassName),
5310
5437
  children: [/* @__PURE__ */ jsx("div", {
5311
5438
  ref,
5312
- className: cls("flex-grow max-w-full flex flex-row gap-2 items-center", "overflow-visible", {
5313
- "min-h-[28px]": size === "smallest",
5314
- "min-h-[32px]": size === "small",
5315
- "min-h-[40px]": size === "medium",
5316
- "min-h-[48px]": size === "large"
5317
- }),
5439
+ className: cls("flex-grow max-w-full flex flex-row gap-2 items-center", "overflow-visible", !bare && controlHeight),
5318
5440
  children: /* @__PURE__ */ jsx(SelectPrimitive.Value, {
5319
5441
  onClick: (e) => {
5320
5442
  e.preventDefault();
5321
5443
  e.stopPropagation();
5322
5444
  },
5323
- placeholder: emptyItemDisplay ?? placeholder,
5445
+ placeholder: bareContent ?? emptyItemDisplay ?? placeholder,
5324
5446
  className: "w-full",
5325
- children: hasValue && value !== void 0 && renderValue ? renderValue(value) : displayChildren || placeholder
5447
+ children: bareContent ?? (hasValue && value !== void 0 && renderValue ? renderValue(value) : displayChildren || placeholder)
5326
5448
  })
5327
- }), chevron && /* @__PURE__ */ jsx(SelectPrimitive.Icon, {
5449
+ }), chevron && !bare && /* @__PURE__ */ jsx(SelectPrimitive.Icon, {
5328
5450
  asChild: true,
5329
5451
  children: /* @__PURE__ */ jsx(ChevronDownIcon$1, {
5330
5452
  size: size === "large" ? iconSize.medium : iconSize.small,
@@ -5426,10 +5548,16 @@ var Slider = React$1.forwardRef(({ className, size = "regular", ...props }, ref)
5426
5548
  Slider.displayName = "Slider";
5427
5549
  //#endregion
5428
5550
  //#region src/components/Sheet.tsx
5429
- 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 }) => {
5430
5552
  const [displayed, setDisplayed] = useState(false);
5431
5553
  const [contentEl, setContentEl] = useState(null);
5432
5554
  useRestoreInterruptedFocus(contentEl);
5555
+ const focus = useModalFocus({
5556
+ open,
5557
+ initialFocus,
5558
+ onOpenAutoFocus,
5559
+ onCloseAutoFocus
5560
+ });
5433
5561
  const contextContainer = usePortalContainer();
5434
5562
  const finalContainer = portalContainer ?? contextContainer ?? void 0;
5435
5563
  useEffect(() => {
@@ -5462,7 +5590,8 @@ var Sheet = ({ children, side = "right", title, modal = true, includeBackgroundO
5462
5590
  }), /* @__PURE__ */ jsxs(DialogPrimitive.Content, {
5463
5591
  ...props,
5464
5592
  ref: setContentEl,
5465
- onOpenAutoFocus: (event) => event.preventDefault(),
5593
+ onOpenAutoFocus: focus.onOpenAutoFocus,
5594
+ onCloseAutoFocus: focus.onCloseAutoFocus,
5466
5595
  onPointerDownOutside,
5467
5596
  onInteractOutside,
5468
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),
@@ -6023,42 +6152,48 @@ Badge.displayName = "Badge";
6023
6152
  //#endregion
6024
6153
  //#region src/components/DebouncedTextField.tsx
6025
6154
  function DebouncedTextField(props) {
6026
- const [internalValue, setInternalValue] = useState(props.value ?? "");
6027
- const lastSentValueRef = useRef(props.value ?? "");
6155
+ const { value, onChange, name, onKeyDown, onBlur } = props;
6156
+ const [internalValue, setInternalValue] = useState(value ?? "");
6157
+ const lastSentValueRef = useRef(value ?? "");
6028
6158
  const timerRef = useRef(void 0);
6029
6159
  useEffect(() => {
6030
- const externalValue = props.value ?? "";
6160
+ const externalValue = value ?? "";
6031
6161
  if (externalValue !== lastSentValueRef.current) {
6032
6162
  setInternalValue(externalValue);
6033
6163
  lastSentValueRef.current = externalValue;
6034
6164
  }
6035
- }, [props.value]);
6165
+ }, [value]);
6166
+ const pendingFlushRef = useRef(void 0);
6036
6167
  useEffect(() => {
6037
6168
  return () => {
6038
- if (timerRef.current) clearTimeout(timerRef.current);
6169
+ if (timerRef.current) {
6170
+ clearTimeout(timerRef.current);
6171
+ timerRef.current = void 0;
6172
+ pendingFlushRef.current?.();
6173
+ }
6039
6174
  };
6040
6175
  }, []);
6041
- const flushChange = useCallback((value, event) => {
6176
+ const flushChange = useCallback((next, event) => {
6042
6177
  if (timerRef.current) {
6043
6178
  clearTimeout(timerRef.current);
6044
6179
  timerRef.current = void 0;
6045
6180
  }
6046
- if (value !== props.value && props.onChange) {
6047
- lastSentValueRef.current = value;
6181
+ if (next !== value && onChange) {
6182
+ lastSentValueRef.current = next;
6048
6183
  const e = {
6049
6184
  ...event,
6050
6185
  target: {
6051
6186
  ...event?.target,
6052
- value,
6053
- name: props.name
6187
+ value: next,
6188
+ name
6054
6189
  }
6055
6190
  };
6056
- props.onChange(e);
6191
+ onChange(e);
6057
6192
  }
6058
6193
  }, [
6059
- props.value,
6060
- props.onChange,
6061
- props.name
6194
+ value,
6195
+ onChange,
6196
+ name
6062
6197
  ]);
6063
6198
  const internalOnChange = useCallback((event) => {
6064
6199
  const newValue = event.target.value;
@@ -6071,21 +6206,31 @@ function DebouncedTextField(props) {
6071
6206
  name: event?.target?.name
6072
6207
  }
6073
6208
  };
6209
+ pendingFlushRef.current = () => flushChange(newValue, eventCopy);
6074
6210
  timerRef.current = setTimeout(() => {
6075
6211
  flushChange(newValue, eventCopy);
6076
6212
  }, 150);
6077
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
+ ]);
6078
6222
  const internalOnBlur = useCallback((event) => {
6079
6223
  flushChange(internalValue, event);
6080
- props.onBlur?.(event);
6224
+ onBlur?.(event);
6081
6225
  }, [
6082
6226
  internalValue,
6083
6227
  flushChange,
6084
- props.onBlur
6228
+ onBlur
6085
6229
  ]);
6086
6230
  return /* @__PURE__ */ jsx(TextField, {
6087
6231
  ...props,
6088
6232
  onChange: internalOnChange,
6233
+ onKeyDown: internalOnKeyDown,
6089
6234
  onBlur: internalOnBlur,
6090
6235
  value: internalValue
6091
6236
  });
@@ -7104,14 +7249,11 @@ function VirtualTableNumberInput(props) {
7104
7249
  if (inputRef.current && focused) inputRef.current.focus({ preventScroll: true });
7105
7250
  }, [focused, inputRef]);
7106
7251
  const regexp = /^-?[0-9]+[,.]?[0-9]*$/;
7107
- return /* @__PURE__ */ jsx(TextField, {
7108
- inputRef,
7109
- invisible: true,
7252
+ return /* @__PURE__ */ jsx("input", {
7253
+ ref: inputRef,
7110
7254
  disabled,
7111
- size: "small",
7112
- className: "w-full",
7113
- inputClassName: cls("p-0 m-0 bg-transparent border-none outline-hidden font-normal leading-normal text-unset", focusedDisabled),
7114
- inputStyle: { textAlign: align },
7255
+ className: cls("w-full p-0 m-0 bg-transparent border-none outline-none font-mono tabular-nums", focusedDisabled),
7256
+ style: { textAlign: align },
7115
7257
  value: internalValue ?? "",
7116
7258
  onChange: (evt) => {
7117
7259
  const newValue = evt.target.value.replace(",", ".");
@@ -7276,6 +7418,76 @@ function VirtualTableSelect(props) {
7276
7418
  });
7277
7419
  }
7278
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
7279
7491
  //#region src/views/ListView.tsx
7280
7492
  function getEstimatedRowHeight(size) {
7281
7493
  switch (size) {
@@ -7289,16 +7501,7 @@ function getEstimatedRowHeight(size) {
7289
7501
  }
7290
7502
  var OVERSCAN_COUNT = 8;
7291
7503
  var LOAD_MORE_THRESHOLD = 400;
7292
- function getScrollParent$1(element) {
7293
- let parent = element?.parentElement ?? null;
7294
- while (parent) {
7295
- const style = getComputedStyle(parent);
7296
- if (style.overflowY === "auto" || style.overflowY === "scroll" || style.overflow === "auto" || style.overflow === "scroll") return parent;
7297
- parent = parent.parentElement;
7298
- }
7299
- return document.documentElement;
7300
- }
7301
- 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 }) {
7302
7505
  const containerRef = useRef(null);
7303
7506
  const rowsRef = useRef(null);
7304
7507
  const isLoadingMore = useRef(false);
@@ -7330,7 +7533,7 @@ function ListView({ data, dataLoading = false, noMoreToLoad = false, dataLoading
7330
7533
  useEffect(() => {
7331
7534
  const el = containerRef.current;
7332
7535
  if (!el) return;
7333
- const scrollEl = getScrollParent$1(el);
7536
+ const scrollEl = getScrollParent(el);
7334
7537
  if (!scrollEl) return;
7335
7538
  let rafId = null;
7336
7539
  const update = () => {
@@ -7345,19 +7548,25 @@ function ListView({ data, dataLoading = false, noMoreToLoad = false, dataLoading
7345
7548
  setItemCount?.((ic ?? ps) + ps);
7346
7549
  }
7347
7550
  };
7348
- const onScroll = () => {
7551
+ const onScrollEvent = () => {
7349
7552
  if (rafId === null) rafId = requestAnimationFrame(update);
7350
7553
  };
7351
- scrollEl.addEventListener("scroll", onScroll, { passive: true });
7554
+ scrollEl.addEventListener("scroll", onScrollEvent, { passive: true });
7352
7555
  const ro = new ResizeObserver(() => update());
7353
7556
  ro.observe(scrollEl);
7354
7557
  update();
7355
7558
  return () => {
7356
- scrollEl.removeEventListener("scroll", onScroll);
7559
+ scrollEl.removeEventListener("scroll", onScrollEvent);
7357
7560
  ro.disconnect();
7358
7561
  if (rafId !== null) cancelAnimationFrame(rafId);
7359
7562
  };
7360
7563
  }, [setItemCount]);
7564
+ useScrollParentRestoration({
7565
+ containerRef,
7566
+ initialScroll,
7567
+ onScroll,
7568
+ dataLength: data.length
7569
+ });
7361
7570
  const totalHeight = data.length * estimatedRowHeight;
7362
7571
  const startIndex = Math.max(0, Math.floor(effectiveScrollTop / estimatedRowHeight) - OVERSCAN_COUNT);
7363
7572
  const endIndex = Math.min(data.length, Math.ceil((effectiveScrollTop + viewportHeight) / estimatedRowHeight) + OVERSCAN_COUNT);
@@ -7484,19 +7693,8 @@ function getGridColumnsClass(size) {
7484
7693
  default: return "grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6";
7485
7694
  }
7486
7695
  }
7487
- function getScrollParent(element) {
7488
- if (!element) return null;
7489
- let parent = element.parentElement;
7490
- while (parent) {
7491
- const overflowY = window.getComputedStyle(parent).overflowY;
7492
- if (overflowY === "auto" || overflowY === "scroll") return parent;
7493
- parent = parent.parentElement;
7494
- }
7495
- return document.documentElement;
7496
- }
7497
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 }) {
7498
7697
  const containerRef = useRef(null);
7499
- const hasRestoredScroll = useRef(false);
7500
7698
  const isLoadingMore = useRef(false);
7501
7699
  const paginationStateRef = useRef({
7502
7700
  paginationEnabled,
@@ -7547,42 +7745,12 @@ function CardView({ data, dataLoading = false, noMoreToLoad = false, dataLoading
7547
7745
  if (rafId !== null) cancelAnimationFrame(rafId);
7548
7746
  };
7549
7747
  }, [setItemCount]);
7550
- useEffect(() => {
7551
- if (!containerRef.current || !initialScroll || hasRestoredScroll.current || data.length === 0) return;
7552
- const scrollEl = getScrollParent(containerRef.current);
7553
- if (!scrollEl) return;
7554
- let attempts = 0;
7555
- const maxAttempts = 5;
7556
- const tryRestore = () => {
7557
- if (scrollEl.scrollHeight >= initialScroll || attempts >= maxAttempts) {
7558
- scrollEl.scrollTop = initialScroll;
7559
- hasRestoredScroll.current = true;
7560
- } else {
7561
- attempts++;
7562
- requestAnimationFrame(tryRestore);
7563
- }
7564
- };
7565
- requestAnimationFrame(tryRestore);
7566
- }, [initialScroll, data.length]);
7567
- const lastScrollOffset = useRef(0);
7568
- useEffect(() => {
7569
- const el = containerRef.current;
7570
- if (!el || !onScroll) return;
7571
- const scrollEl = getScrollParent(el);
7572
- if (!scrollEl) return;
7573
- const handleScroll = () => {
7574
- const currentOffset = scrollEl.scrollTop;
7575
- const direction = currentOffset > lastScrollOffset.current ? "forward" : "backward";
7576
- lastScrollOffset.current = currentOffset;
7577
- onScroll({
7578
- scrollDirection: direction,
7579
- scrollOffset: currentOffset,
7580
- scrollUpdateWasRequested: false
7581
- });
7582
- };
7583
- scrollEl.addEventListener("scroll", handleScroll, { passive: true });
7584
- return () => scrollEl.removeEventListener("scroll", handleScroll);
7585
- }, [onScroll]);
7748
+ useScrollParentRestoration({
7749
+ containerRef,
7750
+ initialScroll,
7751
+ onScroll,
7752
+ dataLength: data.length
7753
+ });
7586
7754
  const getItemId = useCallback((item) => {
7587
7755
  if (item && typeof item === "object" && "id" in item) {
7588
7756
  const id = item.id;
@@ -9357,6 +9525,40 @@ function CollectionView({ dataController, properties, propertiesOrder, displayed
9357
9525
  });
9358
9526
  }
9359
9527
  //#endregion
9360
- 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 };
9361
9563
 
9362
9564
  //# sourceMappingURL=index.es.js.map