@underverse-ui/underverse 0.2.104 → 0.2.105

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.cjs CHANGED
@@ -7082,6 +7082,7 @@ var React25 = __toESM(require("react"), 1);
7082
7082
  var import_lucide_react16 = require("lucide-react");
7083
7083
  var import_jsx_runtime31 = require("react/jsx-runtime");
7084
7084
  var pad = (n) => n.toString().padStart(2, "0");
7085
+ var clamp3 = (n, min, max) => Math.min(max, Math.max(min, n));
7085
7086
  var WHEEL_ITEM_HEIGHT = {
7086
7087
  sm: 30,
7087
7088
  md: 34,
@@ -7096,6 +7097,7 @@ function WheelColumn({
7096
7097
  onSelect,
7097
7098
  scrollRef,
7098
7099
  itemHeight,
7100
+ size,
7099
7101
  animate,
7100
7102
  focused,
7101
7103
  setFocusedColumn,
@@ -7104,18 +7106,72 @@ function WheelColumn({
7104
7106
  const height = itemHeight * WHEEL_VISIBLE_ITEMS;
7105
7107
  const paddingY = (height - itemHeight) / 2;
7106
7108
  const rafRef = React25.useRef(0);
7107
- const lastIndexRef = React25.useRef(null);
7109
+ const lastVirtualIndexRef = React25.useRef(null);
7108
7110
  const wheelDeltaRef = React25.useRef(0);
7109
7111
  const scrollEndTimeoutRef = React25.useRef(null);
7110
7112
  const suppressScrollSelectUntilRef = React25.useRef(0);
7113
+ const loop = true;
7114
+ const ui = React25.useMemo(() => {
7115
+ if (size === "sm") {
7116
+ return {
7117
+ columnWidth: "min-w-[64px] max-w-[84px]",
7118
+ label: "text-[9px] mb-2",
7119
+ selectedText: "text-base",
7120
+ unselectedText: "text-sm",
7121
+ fadeHeight: "h-10"
7122
+ };
7123
+ }
7124
+ if (size === "lg") {
7125
+ return {
7126
+ columnWidth: "min-w-[80px] max-w-[110px]",
7127
+ label: "text-[11px] mb-3",
7128
+ selectedText: "text-xl",
7129
+ unselectedText: "text-lg",
7130
+ fadeHeight: "h-14"
7131
+ };
7132
+ }
7133
+ return {
7134
+ columnWidth: "min-w-[70px] max-w-[90px]",
7135
+ label: "text-[10px] mb-3",
7136
+ selectedText: "text-lg",
7137
+ unselectedText: "text-base",
7138
+ fadeHeight: "h-12"
7139
+ };
7140
+ }, [size]);
7141
+ const baseOffset = React25.useMemo(() => loop ? items.length : 0, [items.length, loop]);
7142
+ const extendedItems = React25.useMemo(() => loop ? [...items, ...items, ...items] : items, [items, loop]);
7143
+ const getNearestVirtualIndex = React25.useCallback(
7144
+ (realIndex, fromVirtual) => {
7145
+ const len = items.length;
7146
+ if (len <= 0) return 0;
7147
+ if (!loop) return clamp3(realIndex, 0, Math.max(0, len - 1));
7148
+ const candidates = [realIndex, realIndex + len, realIndex + 2 * len];
7149
+ let best = candidates[0];
7150
+ let bestDist = Math.abs(best - fromVirtual);
7151
+ for (const c of candidates) {
7152
+ const dist = Math.abs(c - fromVirtual);
7153
+ if (dist < bestDist) {
7154
+ best = c;
7155
+ bestDist = dist;
7156
+ }
7157
+ }
7158
+ return best;
7159
+ },
7160
+ [items.length, loop]
7161
+ );
7111
7162
  React25.useEffect(() => {
7112
7163
  const el = scrollRef.current;
7113
7164
  if (!el) return;
7114
- const nextTop = Math.max(0, valueIndex) * itemHeight;
7115
- if (Math.abs(el.scrollTop - nextTop) > 1) {
7116
- el.scrollTo({ top: nextTop, behavior: animate ? "smooth" : "auto" });
7117
- }
7118
- lastIndexRef.current = valueIndex;
7165
+ const maxVirtual = Math.max(0, extendedItems.length - 1);
7166
+ const currentVirtual2 = clamp3(Math.round(el.scrollTop / itemHeight), 0, maxVirtual);
7167
+ const desiredVirtual = loop ? getNearestVirtualIndex(valueIndex, currentVirtual2) : valueIndex;
7168
+ const nextTop = desiredVirtual * itemHeight;
7169
+ const delta = Math.abs(el.scrollTop - nextTop);
7170
+ if (delta > 1) {
7171
+ const behavior = animate && delta <= itemHeight * 1.5 ? "smooth" : "auto";
7172
+ el.scrollTo({ top: nextTop, behavior });
7173
+ }
7174
+ lastVirtualIndexRef.current = desiredVirtual;
7119
7175
  return () => {
7120
7176
  if (scrollEndTimeoutRef.current != null) {
7121
7177
  window.clearTimeout(scrollEndTimeoutRef.current);
@@ -7123,7 +7179,7 @@ function WheelColumn({
7123
7179
  }
7124
7180
  cancelAnimationFrame(rafRef.current);
7125
7181
  };
7126
- }, [animate, itemHeight, scrollRef, valueIndex]);
7182
+ }, [animate, extendedItems.length, getNearestVirtualIndex, itemHeight, loop, scrollRef, valueIndex]);
7127
7183
  React25.useEffect(() => {
7128
7184
  const el = scrollRef.current;
7129
7185
  if (!el) return;
@@ -7155,19 +7211,29 @@ function WheelColumn({
7155
7211
  step = Math.sign(wheelDeltaRef.current);
7156
7212
  wheelDeltaRef.current = 0;
7157
7213
  }
7158
- const fromIndex = lastIndexRef.current ?? valueIndex;
7159
- const nextIndex = Math.max(0, Math.min(items.length - 1, fromIndex + step));
7160
- if (nextIndex === fromIndex) return;
7214
+ if (items.length <= 0) return;
7215
+ const fromVirtual = lastVirtualIndexRef.current ?? (loop ? baseOffset + valueIndex : valueIndex);
7216
+ let nextVirtual = fromVirtual + step;
7217
+ if (!loop) {
7218
+ nextVirtual = clamp3(nextVirtual, 0, Math.max(0, items.length - 1));
7219
+ } else {
7220
+ const len = items.length;
7221
+ const maxVirtual = Math.max(0, len * 3 - 1);
7222
+ if (nextVirtual < 0) nextVirtual += len;
7223
+ if (nextVirtual > maxVirtual) nextVirtual -= len;
7224
+ }
7225
+ if (nextVirtual === fromVirtual) return;
7161
7226
  lastStepAtRef.current = Date.now();
7162
7227
  lastStepSignRef.current = step;
7163
- lastIndexRef.current = nextIndex;
7228
+ lastVirtualIndexRef.current = nextVirtual;
7164
7229
  suppressScrollSelectUntilRef.current = Date.now() + 350;
7165
- el.scrollTo({ top: nextIndex * itemHeight, behavior: animate ? "smooth" : "auto" });
7166
- onSelect(items[nextIndex]);
7230
+ el.scrollTo({ top: nextVirtual * itemHeight, behavior: animate ? "smooth" : "auto" });
7231
+ const realIndex = (nextVirtual % items.length + items.length) % items.length;
7232
+ onSelect(items[realIndex]);
7167
7233
  };
7168
7234
  el.addEventListener("wheel", onWheel, { passive: false });
7169
7235
  return () => el.removeEventListener("wheel", onWheel);
7170
- }, [animate, column, itemHeight, items, onSelect, scrollRef, setFocusedColumn, valueIndex]);
7236
+ }, [animate, baseOffset, column, itemHeight, items, loop, onSelect, scrollRef, setFocusedColumn, valueIndex]);
7171
7237
  const handleScroll = () => {
7172
7238
  const el = scrollRef.current;
7173
7239
  if (!el) return;
@@ -7178,17 +7244,38 @@ function WheelColumn({
7178
7244
  window.clearTimeout(scrollEndTimeoutRef.current);
7179
7245
  }
7180
7246
  scrollEndTimeoutRef.current = window.setTimeout(() => {
7181
- const idx = Math.max(0, Math.min(items.length - 1, Math.round(el.scrollTop / itemHeight)));
7182
- if (lastIndexRef.current === idx) return;
7183
- lastIndexRef.current = idx;
7247
+ if (items.length <= 0) return;
7248
+ const len = items.length;
7249
+ const maxVirtual = Math.max(0, extendedItems.length - 1);
7250
+ const idxVirtual = clamp3(Math.round(el.scrollTop / itemHeight), 0, maxVirtual);
7251
+ const realIndex = (idxVirtual % len + len) % len;
7252
+ const snappedVirtual = loop ? getNearestVirtualIndex(realIndex, idxVirtual) : realIndex;
7253
+ if (lastVirtualIndexRef.current !== snappedVirtual) lastVirtualIndexRef.current = snappedVirtual;
7184
7254
  suppressScrollSelectUntilRef.current = Date.now() + 350;
7185
- el.scrollTo({ top: idx * itemHeight, behavior: animate ? "smooth" : "auto" });
7186
- onSelect(items[idx]);
7255
+ el.scrollTo({ top: snappedVirtual * itemHeight, behavior: animate ? "smooth" : "auto" });
7256
+ onSelect(items[realIndex]);
7257
+ if (loop) {
7258
+ const min = len;
7259
+ const max = len * 2 - 1;
7260
+ let centered = snappedVirtual;
7261
+ if (centered < min) centered += len;
7262
+ if (centered > max) centered -= len;
7263
+ if (centered !== snappedVirtual) {
7264
+ lastVirtualIndexRef.current = centered;
7265
+ el.scrollTo({ top: centered * itemHeight, behavior: "auto" });
7266
+ }
7267
+ }
7187
7268
  }, 120);
7188
7269
  });
7189
7270
  };
7190
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "flex-1 min-w-[70px] max-w-[90px]", children: [
7191
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "text-[10px] font-bold uppercase tracking-wider text-muted-foreground/70 mb-3 text-center", children: labelText }),
7271
+ const currentVirtual = React25.useMemo(() => {
7272
+ if (!loop || items.length <= 0) return valueIndex;
7273
+ const fallback = baseOffset + valueIndex;
7274
+ const from = lastVirtualIndexRef.current ?? fallback;
7275
+ return getNearestVirtualIndex(valueIndex, from);
7276
+ }, [baseOffset, getNearestVirtualIndex, items.length, loop, valueIndex]);
7277
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: cn("flex-1", ui.columnWidth), children: [
7278
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: cn(ui.label, "font-bold uppercase tracking-wider text-muted-foreground/70 text-center"), children: labelText }),
7192
7279
  /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "relative rounded-xl bg-muted/30 overflow-hidden", style: { height }, children: [
7193
7280
  /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
7194
7281
  "div",
@@ -7197,8 +7284,8 @@ function WheelColumn({
7197
7284
  style: { height: itemHeight }
7198
7285
  }
7199
7286
  ),
7200
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "pointer-events-none absolute inset-x-0 top-0 h-12 bg-linear-to-b from-background/95 via-background/60 to-transparent z-10" }),
7201
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "pointer-events-none absolute inset-x-0 bottom-0 h-12 bg-linear-to-t from-background/95 via-background/60 to-transparent z-10" }),
7287
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: cn("pointer-events-none absolute inset-x-0 top-0 bg-linear-to-b from-background/95 via-background/60 to-transparent z-10", ui.fadeHeight) }),
7288
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: cn("pointer-events-none absolute inset-x-0 bottom-0 bg-linear-to-t from-background/95 via-background/60 to-transparent z-10", ui.fadeHeight) }),
7202
7289
  /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
7203
7290
  "div",
7204
7291
  {
@@ -7215,11 +7302,11 @@ function WheelColumn({
7215
7302
  onKeyDown: (e) => onKeyDown(e, column),
7216
7303
  onFocus: () => setFocusedColumn(column),
7217
7304
  onScroll: handleScroll,
7218
- children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { children: items.map((n, index) => {
7219
- const dist = Math.abs(index - valueIndex);
7305
+ children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { children: extendedItems.map((n, index) => {
7306
+ const dist = Math.abs(index - currentVirtual);
7220
7307
  const scale = 1 - Math.min(dist * 0.12, 0.36);
7221
7308
  const opacity = 1 - Math.min(dist * 0.22, 0.75);
7222
- const isSelected = index === valueIndex;
7309
+ const isSelected = index === currentVirtual;
7223
7310
  return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
7224
7311
  "button",
7225
7312
  {
@@ -7228,7 +7315,7 @@ function WheelColumn({
7228
7315
  "aria-selected": isSelected,
7229
7316
  className: cn(
7230
7317
  "w-full snap-center flex items-center justify-center rounded-lg transition-all duration-200 font-bold tabular-nums",
7231
- isSelected ? "text-primary text-lg" : "text-muted-foreground hover:text-foreground/70"
7318
+ isSelected ? cn("text-primary", ui.selectedText) : cn("text-muted-foreground hover:text-foreground/70", ui.unselectedText)
7232
7319
  ),
7233
7320
  style: {
7234
7321
  height: itemHeight,
@@ -7237,13 +7324,16 @@ function WheelColumn({
7237
7324
  },
7238
7325
  onClick: () => {
7239
7326
  const el = scrollRef.current;
7327
+ if (!el) return;
7240
7328
  suppressScrollSelectUntilRef.current = Date.now() + 350;
7241
- el?.scrollTo({ top: index * itemHeight, behavior: animate ? "smooth" : "auto" });
7242
- onSelect(n);
7329
+ lastVirtualIndexRef.current = index;
7330
+ el.scrollTo({ top: index * itemHeight, behavior: animate ? "smooth" : "auto" });
7331
+ const realIndex = (index % items.length + items.length) % items.length;
7332
+ onSelect(items[realIndex]);
7243
7333
  },
7244
7334
  children: pad(n)
7245
7335
  },
7246
- n
7336
+ `${column}_${index}`
7247
7337
  );
7248
7338
  }) })
7249
7339
  }
@@ -7303,6 +7393,7 @@ function TimePicker({
7303
7393
  secondStep = 1,
7304
7394
  clearable = true,
7305
7395
  variant = "default",
7396
+ matchTriggerWidth = true,
7306
7397
  showNow = false,
7307
7398
  showPresets = false,
7308
7399
  allowManualInput = false,
@@ -7473,12 +7564,60 @@ function TimePicker({
7473
7564
  const hours = format === "24" ? Array.from({ length: 24 }, (_, i) => i) : Array.from({ length: 12 }, (_, i) => i + 1);
7474
7565
  const minutes = Array.from({ length: Math.ceil(60 / minuteStep) }, (_, i) => Math.min(59, i * minuteStep));
7475
7566
  const seconds = Array.from({ length: Math.ceil(60 / secondStep) }, (_, i) => Math.min(59, i * secondStep));
7567
+ const panelSizeClasses = {
7568
+ sm: {
7569
+ contentPadding: "p-4",
7570
+ stackGap: "space-y-3",
7571
+ timeText: "text-xl",
7572
+ inputSize: "sm",
7573
+ icon: "w-3 h-3",
7574
+ separatorPad: "pt-6",
7575
+ presetText: "text-[11px]",
7576
+ presetPadding: "px-3 py-2",
7577
+ actionText: "text-[11px]",
7578
+ actionPadding: "px-3 py-2",
7579
+ periodLabel: "text-[9px] mb-2",
7580
+ periodGap: "gap-1.5",
7581
+ periodButton: "px-3 py-2 text-xs"
7582
+ },
7583
+ md: {
7584
+ contentPadding: "p-5",
7585
+ stackGap: "space-y-4",
7586
+ timeText: "text-2xl",
7587
+ inputSize: "sm",
7588
+ icon: "w-3.5 h-3.5",
7589
+ separatorPad: "pt-8",
7590
+ presetText: "text-xs",
7591
+ presetPadding: "px-3 py-2.5",
7592
+ actionText: "text-xs",
7593
+ actionPadding: "px-4 py-2.5",
7594
+ periodLabel: "text-[10px] mb-3",
7595
+ periodGap: "gap-2",
7596
+ periodButton: "px-4 py-3 text-sm"
7597
+ },
7598
+ lg: {
7599
+ contentPadding: "p-6",
7600
+ stackGap: "space-y-5",
7601
+ timeText: "text-3xl",
7602
+ inputSize: "md",
7603
+ icon: "w-4 h-4",
7604
+ separatorPad: "pt-9",
7605
+ presetText: "text-sm",
7606
+ presetPadding: "px-4 py-3",
7607
+ actionText: "text-sm",
7608
+ actionPadding: "px-5 py-3",
7609
+ periodLabel: "text-[11px] mb-3",
7610
+ periodGap: "gap-2",
7611
+ periodButton: "px-5 py-3.5 text-base"
7612
+ }
7613
+ };
7476
7614
  const sizeClasses2 = {
7477
7615
  sm: { label: "text-xs", height: "h-8", padding: "px-2.5 py-1.5", text: "text-xs", icon: "w-3.5 h-3.5" },
7478
7616
  md: { label: "text-sm", height: "h-10", padding: "px-3 py-2", text: "text-sm", icon: "w-4 h-4" },
7479
7617
  lg: { label: "text-base", height: "h-12", padding: "px-4 py-3", text: "text-base", icon: "w-5 h-5" }
7480
7618
  };
7481
7619
  const sz = sizeClasses2[size];
7620
+ const panelSz = panelSizeClasses[size];
7482
7621
  const display = formatTime(parts, format, includeSeconds);
7483
7622
  const trigger = variant === "inline" ? null : /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
7484
7623
  "button",
@@ -7509,8 +7648,8 @@ function TimePicker({
7509
7648
  "div",
7510
7649
  {
7511
7650
  className: cn(
7512
- "flex items-center justify-center rounded-md p-1.5 transition-all duration-300",
7513
- error ? "bg-destructive/10 text-destructive" : success ? "bg-success/10 text-success" : open ? "bg-primary/15 text-primary" : "bg-muted/50 text-muted-foreground group-hover:bg-primary/10 group-hover:text-primary"
7651
+ "flex items-center justify-center transition-colors duration-300",
7652
+ error ? "text-destructive" : success ? "text-success" : open ? "text-primary" : "text-muted-foreground group-hover:text-primary"
7514
7653
  ),
7515
7654
  children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react16.Clock, { className: cn(sz.icon, "transition-transform duration-300", open && "rotate-12") })
7516
7655
  }
@@ -7543,8 +7682,8 @@ function TimePicker({
7543
7682
  setParts(next);
7544
7683
  emit(next);
7545
7684
  };
7546
- const timePickerContent = /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "space-y-4", children: [
7547
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "flex items-center justify-center py-2 px-3 rounded-xl bg-linear-to-r from-primary/10 via-primary/5 to-primary/10 border border-primary/20", children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "text-2xl font-bold tabular-nums tracking-wide text-foreground", children: display }) }),
7685
+ const timePickerContent = /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: panelSz.stackGap, children: [
7686
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "flex items-center justify-center py-2 px-3 rounded-xl bg-linear-to-r from-primary/10 via-primary/5 to-primary/10 border border-primary/20", children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: cn(panelSz.timeText, "font-bold tabular-nums tracking-wide text-foreground"), children: display }) }),
7548
7687
  allowManualInput && /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "relative", children: [
7549
7688
  /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
7550
7689
  Input_default,
@@ -7552,12 +7691,12 @@ function TimePicker({
7552
7691
  placeholder: format === "12" ? "02:30 PM" : "14:30",
7553
7692
  value: manualInput || display,
7554
7693
  onChange: (e) => handleManualInput(e.target.value),
7555
- size: "sm",
7694
+ size: panelSz.inputSize,
7556
7695
  variant: "outlined",
7557
7696
  className: "pl-9"
7558
7697
  }
7559
7698
  ),
7560
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react16.Clock, { className: "absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" })
7699
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react16.Clock, { className: cn("absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground", panelSz.icon) })
7561
7700
  ] }),
7562
7701
  showPresets && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "grid grid-cols-2 gap-2", children: Object.keys(PRESETS).map((preset) => {
7563
7702
  const { icon: Icon, label: label2, color } = PRESETS[preset];
@@ -7566,7 +7705,9 @@ function TimePicker({
7566
7705
  {
7567
7706
  type: "button",
7568
7707
  className: cn(
7569
- "group relative px-3 py-2.5 text-xs font-medium rounded-xl border border-border/50 overflow-hidden",
7708
+ "group relative font-medium rounded-xl border border-border/50 overflow-hidden",
7709
+ panelSz.presetPadding,
7710
+ panelSz.presetText,
7570
7711
  "bg-linear-to-br from-background to-muted/30",
7571
7712
  "hover:border-primary/40 hover:shadow-md transition-all duration-300",
7572
7713
  animate && "hover:scale-[1.02] active:scale-[0.98]"
@@ -7582,7 +7723,7 @@ function TimePicker({
7582
7723
  }
7583
7724
  ),
7584
7725
  /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "relative flex items-center gap-2", children: [
7585
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(Icon, { className: "w-3.5 h-3.5 text-muted-foreground group-hover:text-primary transition-colors" }),
7726
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(Icon, { className: cn("text-muted-foreground group-hover:text-primary transition-colors", panelSz.icon) }),
7586
7727
  /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "text-foreground/80 group-hover:text-foreground transition-colors", children: label2 })
7587
7728
  ] })
7588
7729
  ]
@@ -7595,7 +7736,9 @@ function TimePicker({
7595
7736
  {
7596
7737
  type: "button",
7597
7738
  className: cn(
7598
- "px-3 py-2.5 text-xs font-medium rounded-xl border border-border/50",
7739
+ "font-medium rounded-xl border border-border/50",
7740
+ panelSz.presetPadding,
7741
+ panelSz.presetText,
7599
7742
  "bg-linear-to-br from-background to-muted/30",
7600
7743
  "hover:border-primary/40 hover:bg-primary/5 hover:shadow-md transition-all duration-300",
7601
7744
  animate && "hover:scale-[1.02] active:scale-[0.98]"
@@ -7620,6 +7763,7 @@ function TimePicker({
7620
7763
  onSelect: setHourFromDisplay,
7621
7764
  scrollRef: hourScrollRef,
7622
7765
  itemHeight,
7766
+ size,
7623
7767
  animate,
7624
7768
  focused: focusedColumn === "hour",
7625
7769
  setFocusedColumn: (col) => setFocusedColumn(col),
@@ -7627,7 +7771,7 @@ function TimePicker({
7627
7771
  }
7628
7772
  );
7629
7773
  })(),
7630
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "flex flex-col items-center justify-center pt-8", children: /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "flex flex-col gap-2", children: [
7774
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: cn("flex flex-col items-center justify-center", panelSz.separatorPad), children: /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "flex flex-col gap-2", children: [
7631
7775
  /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "w-1.5 h-1.5 rounded-full bg-primary/60" }),
7632
7776
  /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "w-1.5 h-1.5 rounded-full bg-primary/60" })
7633
7777
  ] }) }),
@@ -7647,6 +7791,7 @@ function TimePicker({
7647
7791
  },
7648
7792
  scrollRef: minuteScrollRef,
7649
7793
  itemHeight,
7794
+ size,
7650
7795
  animate,
7651
7796
  focused: focusedColumn === "minute",
7652
7797
  setFocusedColumn: (col) => setFocusedColumn(col),
@@ -7655,7 +7800,7 @@ function TimePicker({
7655
7800
  );
7656
7801
  })(),
7657
7802
  includeSeconds && /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_jsx_runtime31.Fragment, { children: [
7658
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "flex flex-col items-center justify-center pt-8", children: /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "flex flex-col gap-2", children: [
7803
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: cn("flex flex-col items-center justify-center", panelSz.separatorPad), children: /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "flex flex-col gap-2", children: [
7659
7804
  /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "w-1.5 h-1.5 rounded-full bg-primary/60" }),
7660
7805
  /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "w-1.5 h-1.5 rounded-full bg-primary/60" })
7661
7806
  ] }) }),
@@ -7675,6 +7820,7 @@ function TimePicker({
7675
7820
  },
7676
7821
  scrollRef: secondScrollRef,
7677
7822
  itemHeight,
7823
+ size,
7678
7824
  animate,
7679
7825
  focused: focusedColumn === "second",
7680
7826
  setFocusedColumn: (col) => setFocusedColumn(col),
@@ -7683,52 +7829,62 @@ function TimePicker({
7683
7829
  );
7684
7830
  })()
7685
7831
  ] }),
7686
- format === "12" && /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "flex-1 min-w-[70px] max-w-[90px]", children: [
7687
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "text-[10px] font-bold uppercase tracking-wider text-muted-foreground/70 mb-3 text-center", children: "Period" }),
7688
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
7689
- "div",
7690
- {
7691
- className: "flex flex-col gap-2 p-1 rounded-xl bg-muted/30",
7692
- role: "radiogroup",
7693
- "aria-label": "Select AM or PM",
7694
- tabIndex: focusedColumn === "period" ? 0 : -1,
7695
- onKeyDown: (e) => handleKeyDown(e, "period"),
7696
- onFocus: () => setFocusedColumn("period"),
7697
- children: ["AM", "PM"].map((p) => {
7698
- const isSelected = parts.p === p;
7699
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
7700
- "button",
7701
- {
7702
- type: "button",
7703
- role: "radio",
7704
- "aria-checked": isSelected,
7705
- className: cn(
7706
- "relative px-4 py-3 rounded-lg transition-all duration-300 text-sm font-bold overflow-hidden",
7707
- "focus-visible:ring-2 focus-visible:ring-primary/50 focus-visible:ring-offset-1",
7708
- isSelected && "bg-linear-to-r from-primary to-primary/80 text-primary-foreground shadow-lg shadow-primary/25",
7709
- !isSelected && "text-muted-foreground hover:text-foreground hover:bg-muted/50",
7710
- animate && "hover:scale-[1.02] active:scale-[0.98]"
7711
- ),
7712
- onClick: () => {
7713
- const pVal = p;
7714
- let hour = parts.h;
7715
- if (pVal === "AM" && hour >= 12) hour -= 12;
7716
- if (pVal === "PM" && hour < 12) hour += 12;
7717
- const next = { ...parts, p: pVal, h: hour };
7718
- setParts(next);
7719
- emit(next);
7720
- },
7721
- children: [
7722
- isSelected && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "absolute inset-0 bg-linear-to-tr from-white/20 to-transparent" }),
7723
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "relative", children: p })
7724
- ]
7725
- },
7726
- p
7727
- );
7728
- })
7729
- }
7730
- )
7731
- ] })
7832
+ format === "12" && /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
7833
+ "div",
7834
+ {
7835
+ className: cn(
7836
+ "flex-1",
7837
+ size === "sm" ? "min-w-[64px] max-w-[84px]" : size === "lg" ? "min-w-[80px] max-w-[110px]" : "min-w-[70px] max-w-[90px]"
7838
+ ),
7839
+ children: [
7840
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: cn(panelSz.periodLabel, "font-bold uppercase tracking-wider text-muted-foreground/70 text-center"), children: "Period" }),
7841
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
7842
+ "div",
7843
+ {
7844
+ className: cn("flex flex-col p-1 rounded-xl bg-muted/30", panelSz.periodGap),
7845
+ role: "radiogroup",
7846
+ "aria-label": "Select AM or PM",
7847
+ tabIndex: focusedColumn === "period" ? 0 : -1,
7848
+ onKeyDown: (e) => handleKeyDown(e, "period"),
7849
+ onFocus: () => setFocusedColumn("period"),
7850
+ children: ["AM", "PM"].map((p) => {
7851
+ const isSelected = parts.p === p;
7852
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
7853
+ "button",
7854
+ {
7855
+ type: "button",
7856
+ role: "radio",
7857
+ "aria-checked": isSelected,
7858
+ className: cn(
7859
+ "relative rounded-lg transition-all duration-300 font-bold overflow-hidden",
7860
+ panelSz.periodButton,
7861
+ "focus-visible:ring-2 focus-visible:ring-primary/50 focus-visible:ring-offset-1",
7862
+ isSelected && "bg-linear-to-r from-primary to-primary/80 text-primary-foreground shadow-lg shadow-primary/25",
7863
+ !isSelected && "text-muted-foreground hover:text-foreground hover:bg-muted/50",
7864
+ animate && "hover:scale-[1.02] active:scale-[0.98]"
7865
+ ),
7866
+ onClick: () => {
7867
+ const pVal = p;
7868
+ let hour = parts.h;
7869
+ if (pVal === "AM" && hour >= 12) hour -= 12;
7870
+ if (pVal === "PM" && hour < 12) hour += 12;
7871
+ const next = { ...parts, p: pVal, h: hour };
7872
+ setParts(next);
7873
+ emit(next);
7874
+ },
7875
+ children: [
7876
+ isSelected && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "absolute inset-0 bg-linear-to-tr from-white/20 to-transparent" }),
7877
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "relative", children: p })
7878
+ ]
7879
+ },
7880
+ p
7881
+ );
7882
+ })
7883
+ }
7884
+ )
7885
+ ]
7886
+ }
7887
+ )
7732
7888
  ] }),
7733
7889
  (showNow || clearable) && /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "flex items-center gap-2 pt-3 border-t border-border/50", children: [
7734
7890
  showNow && /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
@@ -7736,7 +7892,9 @@ function TimePicker({
7736
7892
  {
7737
7893
  type: "button",
7738
7894
  className: cn(
7739
- "flex-1 px-4 py-2.5 text-xs font-semibold rounded-xl",
7895
+ "flex-1 font-semibold rounded-xl",
7896
+ panelSz.actionPadding,
7897
+ panelSz.actionText,
7740
7898
  "bg-linear-to-r from-primary/10 to-primary/5 border border-primary/30",
7741
7899
  "text-primary hover:from-primary/20 hover:to-primary/10 hover:border-primary/50",
7742
7900
  "transition-all duration-300 flex items-center justify-center gap-2",
@@ -7748,7 +7906,7 @@ function TimePicker({
7748
7906
  },
7749
7907
  "aria-label": "Set current time",
7750
7908
  children: [
7751
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react16.Clock, { className: "w-3.5 h-3.5" }),
7909
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react16.Clock, { className: panelSz.icon }),
7752
7910
  "Now"
7753
7911
  ]
7754
7912
  }
@@ -7758,7 +7916,9 @@ function TimePicker({
7758
7916
  {
7759
7917
  type: "button",
7760
7918
  className: cn(
7761
- "flex-1 px-4 py-2.5 text-xs font-semibold rounded-xl",
7919
+ "flex-1 font-semibold rounded-xl",
7920
+ panelSz.actionPadding,
7921
+ panelSz.actionText,
7762
7922
  "bg-linear-to-r from-destructive/10 to-destructive/5 border border-destructive/30",
7763
7923
  "text-destructive hover:from-destructive/20 hover:to-destructive/10 hover:border-destructive/50",
7764
7924
  "transition-all duration-300 flex items-center justify-center gap-2",
@@ -7771,7 +7931,7 @@ function TimePicker({
7771
7931
  },
7772
7932
  "aria-label": "Clear selected time",
7773
7933
  children: [
7774
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react16.X, { className: "w-3.5 h-3.5" }),
7934
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react16.X, { className: panelSz.icon }),
7775
7935
  "Clear"
7776
7936
  ]
7777
7937
  }
@@ -7784,7 +7944,7 @@ function TimePicker({
7784
7944
  label,
7785
7945
  required && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "text-destructive ml-1", children: "*" })
7786
7946
  ] }) }),
7787
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: cn("p-5 rounded-2xl border border-border/60 bg-card/95 backdrop-blur-sm shadow-xl", className), children: timePickerContent })
7947
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: cn(panelSz.contentPadding, "rounded-2xl border border-border/60 bg-card/95 backdrop-blur-sm shadow-xl", className), children: timePickerContent })
7788
7948
  ] });
7789
7949
  }
7790
7950
  return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "w-full", ...rest, children: [
@@ -7811,9 +7971,11 @@ function TimePicker({
7811
7971
  open,
7812
7972
  onOpenChange: handleOpenChange,
7813
7973
  placement: "bottom-start",
7814
- contentWidth,
7974
+ matchTriggerWidth,
7975
+ contentWidth: matchTriggerWidth ? void 0 : contentWidth,
7815
7976
  contentClassName: cn(
7816
- "p-5 rounded-2xl border bg-popover/95 backdrop-blur-xl shadow-2xl",
7977
+ panelSz.contentPadding,
7978
+ "rounded-2xl border bg-popover/95 backdrop-blur-xl shadow-2xl",
7817
7979
  error && "border-destructive/40",
7818
7980
  success && "border-success/40",
7819
7981
  !error && !success && "border-border/60",
@@ -8165,7 +8327,7 @@ function getZonedWeekday(date, timeZone) {
8165
8327
  }
8166
8328
 
8167
8329
  // ../../components/ui/CalendarTimeline/layout.ts
8168
- function clamp3(n, min, max) {
8330
+ function clamp4(n, min, max) {
8169
8331
  return Math.max(min, Math.min(max, n));
8170
8332
  }
8171
8333
  function binarySearchFirstGE(arr, target) {
@@ -8304,7 +8466,7 @@ function useVirtualVariableRows(args) {
8304
8466
  const endPos = Math.max(0, Math.min(scrollTop + viewportHeight, total));
8305
8467
  let startIndex = Math.max(0, lowerBound(prefix, startPos) - 1);
8306
8468
  let endIndex = Math.min(itemCount, lowerBound(prefix, endPos) + overscan);
8307
- startIndex = clamp3(startIndex - overscan, 0, itemCount);
8469
+ startIndex = clamp4(startIndex - overscan, 0, itemCount);
8308
8470
  const topSpacer = prefix[startIndex] ?? 0;
8309
8471
  const bottomSpacer = total - (prefix[endIndex] ?? total);
8310
8472
  return { startIndex, endIndex, topSpacer, bottomSpacer, totalHeight: total };
@@ -8456,11 +8618,11 @@ function computeSlotStarts(args) {
8456
8618
  const step = Math.max(5, Math.min(240, Math.trunc(dayTimeStepMinutes)));
8457
8619
  const stepMs = step * 6e4;
8458
8620
  const hours = workHours ?? { startHour: 8, endHour: 17 };
8459
- const boundedStartHour = clamp3(Math.trunc(hours.startHour), 0, 23);
8460
- const boundedEndHour = clamp3(Math.trunc(hours.endHour), 1, 24);
8621
+ const boundedStartHour = clamp4(Math.trunc(hours.startHour), 0, 23);
8622
+ const boundedEndHour = clamp4(Math.trunc(hours.endHour), 1, 24);
8461
8623
  const isWork = dayRangeMode === "work";
8462
8624
  const start2 = isWork ? zonedDateAtTime(baseDayStart, timeZone, { hour: boundedStartHour }) : start;
8463
- const end2 = isWork ? boundedEndHour === 24 ? addZonedDays(baseDayStart, 1, timeZone) : zonedDateAtTime(baseDayStart, timeZone, { hour: clamp3(boundedEndHour, 0, 23) }) : addZonedDays(start, 1, timeZone);
8625
+ const end2 = isWork ? boundedEndHour === 24 ? addZonedDays(baseDayStart, 1, timeZone) : zonedDateAtTime(baseDayStart, timeZone, { hour: clamp4(boundedEndHour, 0, 23) }) : addZonedDays(start, 1, timeZone);
8464
8626
  const end3 = end2.getTime() > start2.getTime() ? end2 : addZonedDays(start2, 1, timeZone);
8465
8627
  const slotStarts2 = [];
8466
8628
  for (let cur2 = start2.getTime(), guard2 = 0; cur2 < end3.getTime() && guard2++ < 2e3; cur2 += stepMs) {
@@ -8489,8 +8651,8 @@ function normalizeEvents(args) {
8489
8651
  const ns = view === "day" ? start : startOfZonedDay(start, timeZone);
8490
8652
  let ne = view === "day" ? end : startOfZonedDay(end, timeZone);
8491
8653
  if (ne.getTime() <= ns.getTime()) ne = view === "day" ? new Date(ns.getTime() + 6e4) : addZonedDays(ns, 1, timeZone);
8492
- const cs = new Date(clamp3(ns.getTime(), rangeStart, rangeEnd));
8493
- const ce = new Date(clamp3(ne.getTime(), rangeStart, rangeEnd));
8654
+ const cs = new Date(clamp4(ns.getTime(), rangeStart, rangeEnd));
8655
+ const ce = new Date(clamp4(ne.getTime(), rangeStart, rangeEnd));
8494
8656
  if (ce.getTime() <= rangeStart || cs.getTime() >= rangeEnd) return null;
8495
8657
  return { ...e, _start: cs, _end: ce };
8496
8658
  }).filter(Boolean);
@@ -8852,12 +9014,12 @@ function useDayHeaderMarks(args) {
8852
9014
  for (const ev of normalizedEvents) {
8853
9015
  const startIdx = binarySearchLastLE(slotStarts, ev._start);
8854
9016
  const endIdxRaw = binarySearchFirstGE(slotStarts, ev._end);
8855
- const endIdx = clamp3(endIdxRaw, 0, n - 1);
9017
+ const endIdx = clamp4(endIdxRaw, 0, n - 1);
8856
9018
  if (startIdx >= 0 && startIdx < n) showTime[startIdx] = true;
8857
9019
  if (endIdx >= 0 && endIdx < n) showTime[endIdx] = true;
8858
9020
  const span = endIdx - startIdx;
8859
9021
  if (span >= 3) {
8860
- const mid = clamp3(Math.floor((startIdx + endIdx) / 2), 0, n - 1);
9022
+ const mid = clamp4(Math.floor((startIdx + endIdx) / 2), 0, n - 1);
8861
9023
  if (!showTime[mid]) showEllipsis[mid] = true;
8862
9024
  }
8863
9025
  }
@@ -8896,7 +9058,7 @@ function useSlotMetrics(args) {
8896
9058
  lefts2[0] = 0;
8897
9059
  for (let i = 0; i < n; i++) lefts2[i + 1] = lefts2[i] + widths[i];
8898
9060
  const gridWidth2 = lefts2[n] ?? 0;
8899
- const xToSlotIdx2 = (x) => clamp3(Math.floor(x / Math.max(1, fixedSlotWidth)), 0, Math.max(0, n - 1));
9061
+ const xToSlotIdx2 = (x) => clamp4(Math.floor(x / Math.max(1, fixedSlotWidth)), 0, Math.max(0, n - 1));
8900
9062
  return { slotWidths: widths, slotLefts: lefts2, slotHasEvent: null, gridWidth: gridWidth2, xToSlotIdx: xToSlotIdx2 };
8901
9063
  }
8902
9064
  const cfg = typeof adaptiveCfg === "object" ? adaptiveCfg : {};
@@ -8922,7 +9084,7 @@ function useSlotMetrics(args) {
8922
9084
  };
8923
9085
  if (dayAnchorCompression) {
8924
9086
  const hasEvent2 = dayHeaderMarks.anchor.slice(0, n);
8925
- const compressedEmptySlotWidth = clamp3(emptySlotWidth, 12, 20);
9087
+ const compressedEmptySlotWidth = clamp4(emptySlotWidth, 12, 20);
8926
9088
  for (let i = 0; i < n; i++) widths[i] = hasEvent2[i] ? fixedSlotWidth : compressedEmptySlotWidth;
8927
9089
  maybeFillToContainer(hasEvent2);
8928
9090
  const lefts2 = new Array(n + 1);
@@ -8930,7 +9092,7 @@ function useSlotMetrics(args) {
8930
9092
  for (let i = 0; i < n; i++) lefts2[i + 1] = lefts2[i] + widths[i];
8931
9093
  const gridWidth2 = lefts2[n] ?? 0;
8932
9094
  const xToSlotIdx2 = (x) => {
8933
- const xc = clamp3(x, 0, Math.max(0, gridWidth2 - 1e-3));
9095
+ const xc = clamp4(x, 0, Math.max(0, gridWidth2 - 1e-3));
8934
9096
  let lo = 0;
8935
9097
  let hi = n - 1;
8936
9098
  while (lo <= hi) {
@@ -8941,14 +9103,14 @@ function useSlotMetrics(args) {
8941
9103
  else if (xc >= right) lo = mid + 1;
8942
9104
  else return mid;
8943
9105
  }
8944
- return clamp3(lo, 0, Math.max(0, n - 1));
9106
+ return clamp4(lo, 0, Math.max(0, n - 1));
8945
9107
  };
8946
9108
  return { slotWidths: widths, slotLefts: lefts2, slotHasEvent: hasEvent2, gridWidth: gridWidth2, xToSlotIdx: xToSlotIdx2 };
8947
9109
  }
8948
9110
  const diff = new Array(n + 1).fill(0);
8949
9111
  for (const ev of normalizedEvents) {
8950
9112
  const startIdx = binarySearchLastLE(slotStarts, ev._start);
8951
- const endIdx = clamp3(binarySearchFirstGE(slotStarts, ev._end), startIdx + 1, n);
9113
+ const endIdx = clamp4(binarySearchFirstGE(slotStarts, ev._end), startIdx + 1, n);
8952
9114
  diff[startIdx] = (diff[startIdx] ?? 0) + 1;
8953
9115
  diff[endIdx] = (diff[endIdx] ?? 0) - 1;
8954
9116
  }
@@ -8966,7 +9128,7 @@ function useSlotMetrics(args) {
8966
9128
  lefts2[0] = 0;
8967
9129
  for (let i = 0; i < n; i++) lefts2[i + 1] = lefts2[i] + widths[i];
8968
9130
  const gridWidth2 = lefts2[n] ?? 0;
8969
- const xToSlotIdx2 = (x) => clamp3(Math.floor(x / Math.max(1, fixedSlotWidth)), 0, Math.max(0, n - 1));
9131
+ const xToSlotIdx2 = (x) => clamp4(Math.floor(x / Math.max(1, fixedSlotWidth)), 0, Math.max(0, n - 1));
8970
9132
  return { slotWidths: widths, slotLefts: lefts2, slotHasEvent: null, gridWidth: gridWidth2, xToSlotIdx: xToSlotIdx2 };
8971
9133
  }
8972
9134
  const emptyCount = n - eventCount;
@@ -8976,7 +9138,7 @@ function useSlotMetrics(args) {
8976
9138
  const remaining = Math.max(0, targetTotal - emptyCount * emptySlotWidth);
8977
9139
  const raw = remaining / Math.max(1, eventCount);
8978
9140
  const maxEventSlotWidth = cfg.maxEventSlotWidth ?? fixedSlotWidth * 2.5;
8979
- eventSlotWidth = clamp3(raw, fixedSlotWidth, Math.max(fixedSlotWidth, maxEventSlotWidth));
9141
+ eventSlotWidth = clamp4(raw, fixedSlotWidth, Math.max(fixedSlotWidth, maxEventSlotWidth));
8980
9142
  }
8981
9143
  for (let i = 0; i < n; i++) widths[i] = hasEvent[i] ? eventSlotWidth : emptySlotWidth;
8982
9144
  maybeFillToContainer(hasEvent);
@@ -8985,7 +9147,7 @@ function useSlotMetrics(args) {
8985
9147
  for (let i = 0; i < n; i++) lefts[i + 1] = lefts[i] + widths[i];
8986
9148
  const gridWidth = lefts[n] ?? 0;
8987
9149
  const xToSlotIdx = (x) => {
8988
- const xc = clamp3(x, 0, Math.max(0, gridWidth - 1e-3));
9150
+ const xc = clamp4(x, 0, Math.max(0, gridWidth - 1e-3));
8989
9151
  let lo = 0;
8990
9152
  let hi = n - 1;
8991
9153
  while (lo <= hi) {
@@ -8996,7 +9158,7 @@ function useSlotMetrics(args) {
8996
9158
  else if (xc >= right) lo = mid + 1;
8997
9159
  else return mid;
8998
9160
  }
8999
- return clamp3(lo, 0, Math.max(0, n - 1));
9161
+ return clamp4(lo, 0, Math.max(0, n - 1));
9000
9162
  };
9001
9163
  return { slotWidths: widths, slotLefts: lefts, slotHasEvent: hasEvent, gridWidth, xToSlotIdx };
9002
9164
  }, [
@@ -9023,7 +9185,7 @@ function useLayoutsByResource(args) {
9023
9185
  const s = isPreview ? preview.start : ev._start;
9024
9186
  const e = isPreview ? preview.end : ev._end;
9025
9187
  const startIdx = binarySearchLastLE(slotStarts, s);
9026
- const endIdx = clamp3(binarySearchFirstGE(slotStarts, e), startIdx + 1, slotsLength);
9188
+ const endIdx = clamp4(binarySearchFirstGE(slotStarts, e), startIdx + 1, slotsLength);
9027
9189
  return { ev: { ...ev, _start: s, _end: e }, startIdx, endIdx };
9028
9190
  });
9029
9191
  const { packed, laneCount } = intervalPack(mapped);
@@ -9087,8 +9249,8 @@ function useVisibleSlotRange(args) {
9087
9249
  const endPos = Math.max(0, scrollLeft + viewportWidth);
9088
9250
  let startIdx = Math.max(0, lowerBound2(slotLefts, startPos) - 1);
9089
9251
  let endIdx = Math.min(slotCount, lowerBound2(slotLefts, endPos) + 1);
9090
- startIdx = clamp3(startIdx - overscan, 0, slotCount);
9091
- endIdx = clamp3(endIdx + overscan, 0, slotCount);
9252
+ startIdx = clamp4(startIdx - overscan, 0, slotCount);
9253
+ endIdx = clamp4(endIdx + overscan, 0, slotCount);
9092
9254
  if (endIdx <= startIdx) endIdx = Math.min(slotCount, startIdx + 1);
9093
9255
  return { startIdx, endIdx };
9094
9256
  }, [enabled, overscan, scrollLeft, slotCount, slotLefts, viewportWidth]);
@@ -9411,7 +9573,7 @@ function CalendarTimeline({
9411
9573
  for (const [resourceId, list] of eventsByResource.entries()) {
9412
9574
  const mapped = list.map((ev) => {
9413
9575
  const startIdx = binarySearchLastLE(slotStarts, ev._start);
9414
- const endIdx = clamp3(binarySearchFirstGE(slotStarts, ev._end), startIdx + 1, slots.length);
9576
+ const endIdx = clamp4(binarySearchFirstGE(slotStarts, ev._end), startIdx + 1, slots.length);
9415
9577
  return { startIdx, endIdx };
9416
9578
  });
9417
9579
  const { laneCount } = intervalPack(mapped);
@@ -9434,7 +9596,7 @@ function CalendarTimeline({
9434
9596
  );
9435
9597
  const setRowHeightForResource = React31.useCallback(
9436
9598
  (resourceId, height) => {
9437
- const clamped = clamp3(Math.round(height), rowMin, rowMax);
9599
+ const clamped = clamp4(Math.round(height), rowMin, rowMax);
9438
9600
  onRowHeightChange?.(clamped);
9439
9601
  if (isControlledRowHeights) {
9440
9602
  const next = { ...activeRowHeights ?? {}, [resourceId]: clamped };
@@ -9473,7 +9635,7 @@ function CalendarTimeline({
9473
9635
  const resizeRef = React31.useRef(null);
9474
9636
  const setResourceColumnWidth = React31.useCallback(
9475
9637
  (next) => {
9476
- const clamped = clamp3(Math.round(next), colMin, colMax);
9638
+ const clamped = clamp4(Math.round(next), colMin, colMax);
9477
9639
  if (!isControlledResourceColumnWidth) setInternalResourceColumnWidth(clamped);
9478
9640
  onResourceColumnWidthChange?.(clamped);
9479
9641
  },
@@ -9596,10 +9758,10 @@ function CalendarTimeline({
9596
9758
  if (slots.length > 0) {
9597
9759
  if (activeView === "day") {
9598
9760
  const inRange = resolvedNow.getTime() >= range.start.getTime() && resolvedNow.getTime() < range.end.getTime();
9599
- startIdx = clamp3(inRange ? binarySearchFirstGE(slotStarts, resolvedNow) : 0, 0, slots.length - 1);
9761
+ startIdx = clamp4(inRange ? binarySearchFirstGE(slotStarts, resolvedNow) : 0, 0, slots.length - 1);
9600
9762
  } else {
9601
9763
  const dayStart = startOfZonedDay(activeDate, resolvedTimeZone);
9602
- startIdx = clamp3(binarySearchLastLE(slotStarts, dayStart), 0, slots.length - 1);
9764
+ startIdx = clamp4(binarySearchLastLE(slotStarts, dayStart), 0, slots.length - 1);
9603
9765
  }
9604
9766
  }
9605
9767
  setCreateStartIdx(startIdx);
@@ -9636,7 +9798,7 @@ function CalendarTimeline({
9636
9798
  const commitCreate = React31.useCallback(() => {
9637
9799
  if (!onCreateEvent) return;
9638
9800
  if (!createResourceId) return;
9639
- const start = slotStarts[clamp3(createStartIdx, 0, Math.max(0, slotStarts.length - 1))];
9801
+ const start = slotStarts[clamp4(createStartIdx, 0, Math.max(0, slotStarts.length - 1))];
9640
9802
  if (!start) return;
9641
9803
  const endBoundary = createEndIdx >= slotStarts.length ? range.end : slotStarts[createEndIdx];
9642
9804
  if (!endBoundary) return;
@@ -9666,8 +9828,8 @@ function CalendarTimeline({
9666
9828
  const body = bodyRef.current;
9667
9829
  if (!body) return null;
9668
9830
  const bodyRect = body.getBoundingClientRect();
9669
- const probeX = clamp3(clientX, bodyRect.left + 1, bodyRect.right - 1);
9670
- const probeY = clamp3(clientY, bodyRect.top + 1, bodyRect.bottom - 1);
9831
+ const probeX = clamp4(clientX, bodyRect.left + 1, bodyRect.right - 1);
9832
+ const probeY = clamp4(clientY, bodyRect.top + 1, bodyRect.bottom - 1);
9671
9833
  const el = document.elementFromPoint(probeX, probeY);
9672
9834
  const x = probeX - bodyRect.left + body.scrollLeft;
9673
9835
  const epsilon = opts?.biasLeft ? 0.01 : 0;
@@ -9681,7 +9843,7 @@ function CalendarTimeline({
9681
9843
  );
9682
9844
  const slotToDate = React31.useCallback(
9683
9845
  (slotIdx) => {
9684
- const start = slotStarts[clamp3(slotIdx, 0, slotStarts.length - 1)];
9846
+ const start = slotStarts[clamp4(slotIdx, 0, slotStarts.length - 1)];
9685
9847
  if (activeView === "day") {
9686
9848
  const stepMs = Math.trunc(Math.max(5, Math.min(240, Math.trunc(dayTimeStepMinutes))) * 6e4);
9687
9849
  return { start, end: new Date(start.getTime() + stepMs) };
@@ -9722,12 +9884,12 @@ function CalendarTimeline({
9722
9884
  return;
9723
9885
  }
9724
9886
  if (drag.mode === "resize-start") {
9725
- const nextStart = new Date(clamp3(targetSlotStart.getTime(), range.start.getTime(), drag.originEnd.getTime() - 6e4));
9887
+ const nextStart = new Date(clamp4(targetSlotStart.getTime(), range.start.getTime(), drag.originEnd.getTime() - 6e4));
9726
9888
  setPreview({ eventId: drag.eventId, resourceId: drag.resourceId, start: nextStart, end: drag.originEnd });
9727
9889
  return;
9728
9890
  }
9729
9891
  if (drag.mode === "resize-end") {
9730
- const nextEnd = new Date(clamp3(targetSlotStart.getTime(), drag.originStart.getTime() + 6e4, range.end.getTime()));
9892
+ const nextEnd = new Date(clamp4(targetSlotStart.getTime(), drag.originStart.getTime() + 6e4, range.end.getTime()));
9731
9893
  setPreview({ eventId: drag.eventId, resourceId: drag.resourceId, start: drag.originStart, end: nextEnd });
9732
9894
  return;
9733
9895
  }
@@ -9744,7 +9906,7 @@ function CalendarTimeline({
9744
9906
  }
9745
9907
  const maxScrollLeft = Math.max(0, body.scrollWidth - body.clientWidth);
9746
9908
  const prevLeft = body.scrollLeft;
9747
- const nextLeft = clamp3(prevLeft + st.dir * st.speed, 0, maxScrollLeft);
9909
+ const nextLeft = clamp4(prevLeft + st.dir * st.speed, 0, maxScrollLeft);
9748
9910
  if (nextLeft === prevLeft) {
9749
9911
  stopAutoScroll();
9750
9912
  return;
@@ -9764,12 +9926,12 @@ function CalendarTimeline({
9764
9926
  if (clientX < rect.left + edge) {
9765
9927
  dir = -1;
9766
9928
  const dist = clientX - rect.left;
9767
- const t2 = clamp3(1 - dist / edge, 0, 1);
9929
+ const t2 = clamp4(1 - dist / edge, 0, 1);
9768
9930
  speed = 8 + t2 * 28;
9769
9931
  } else if (clientX > rect.right - edge) {
9770
9932
  dir = 1;
9771
9933
  const dist = rect.right - clientX;
9772
- const t2 = clamp3(1 - dist / edge, 0, 1);
9934
+ const t2 = clamp4(1 - dist / edge, 0, 1);
9773
9935
  speed = 8 + t2 * 28;
9774
9936
  }
9775
9937
  autoScrollStateRef.current.lastClientX = clientX;
@@ -9880,7 +10042,7 @@ function CalendarTimeline({
9880
10042
  return;
9881
10043
  }
9882
10044
  const rect = rowEl.getBoundingClientRect();
9883
- const y = clamp3(e.clientY - rect.top, 0, rect.height);
10045
+ const y = clamp4(e.clientY - rect.top, 0, rect.height);
9884
10046
  if (!hoverCell || hoverCell.resourceId !== ctx.resourceId || hoverCell.slotIdx !== ctx.slotIdx || Math.abs(hoverCell.y - y) > 0.5) {
9885
10047
  setHoverCell({ resourceId: ctx.resourceId, slotIdx: ctx.slotIdx, y });
9886
10048
  }
@@ -10173,8 +10335,8 @@ function CalendarTimeline({
10173
10335
  const resource = resourceById.get(ev.resourceId);
10174
10336
  const tooltipTitle = ev.title || ev.id;
10175
10337
  const shouldCompact = activeView === "day" && dayEventStyle === "compact";
10176
- const defaultMaxVisual = clamp3(Math.round(fixedSlotWidth * 1.2), 160, 360);
10177
- const maxVisual = clamp3(Math.round(dayEventMaxWidth ?? defaultMaxVisual), 80, 1200);
10338
+ const defaultMaxVisual = clamp4(Math.round(fixedSlotWidth * 1.2), 160, 360);
10339
+ const maxVisual = clamp4(Math.round(dayEventMaxWidth ?? defaultMaxVisual), 80, 1200);
10178
10340
  const visualWidth = shouldCompact ? Math.min(width, maxVisual) : width;
10179
10341
  const isClipped = shouldCompact && width > visualWidth + 1;
10180
10342
  const block = /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
@@ -10264,7 +10426,7 @@ function CalendarTimeline({
10264
10426
  }),
10265
10427
  preview && preview.resourceId === r.id && !preview.eventId ? (() => {
10266
10428
  const startIdx = binarySearchLastLE(slotStarts, preview.start);
10267
- const endIdx = clamp3(binarySearchFirstGE(slotStarts, preview.end), startIdx + 1, slots.length);
10429
+ const endIdx = clamp4(binarySearchFirstGE(slotStarts, preview.end), startIdx + 1, slots.length);
10268
10430
  const left = slotLefts[startIdx] ?? 0;
10269
10431
  const width = Math.max(1, (slotLefts[endIdx] ?? 0) - (slotLefts[startIdx] ?? 0));
10270
10432
  return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
@@ -10287,7 +10449,7 @@ function CalendarTimeline({
10287
10449
  ),
10288
10450
  style: {
10289
10451
  left: (slotLefts[hoverCell.slotIdx] ?? 0) + (slotWidths[hoverCell.slotIdx] ?? fixedSlotWidth) / 2 - 10,
10290
- top: clamp3(Math.round(hoverCell.y - 10), 6, Math.max(6, h - 26))
10452
+ top: clamp4(Math.round(hoverCell.y - 10), 6, Math.max(6, h - 26))
10291
10453
  },
10292
10454
  "aria-hidden": true,
10293
10455
  children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_lucide_react21.Plus, { className: "h-3.5 w-3.5 text-muted-foreground" })
@@ -13523,7 +13685,7 @@ var Timeline_default = Timeline;
13523
13685
  var React42 = __toESM(require("react"), 1);
13524
13686
  var import_lucide_react29 = require("lucide-react");
13525
13687
  var import_jsx_runtime50 = require("react/jsx-runtime");
13526
- var clamp4 = (n, min, max) => Math.max(min, Math.min(max, n));
13688
+ var clamp5 = (n, min, max) => Math.max(min, Math.min(max, n));
13527
13689
  function hexToRgb(hex) {
13528
13690
  const str = hex.replace(/^#/, "").trim();
13529
13691
  if (str.length === 3) {
@@ -13541,7 +13703,7 @@ function hexToRgb(hex) {
13541
13703
  return null;
13542
13704
  }
13543
13705
  function rgbToHex(r, g, b) {
13544
- return `#${[r, g, b].map((v) => clamp4(Math.round(v), 0, 255).toString(16).padStart(2, "0")).join("")}`;
13706
+ return `#${[r, g, b].map((v) => clamp5(Math.round(v), 0, 255).toString(16).padStart(2, "0")).join("")}`;
13545
13707
  }
13546
13708
  function rgbToHsl(r, g, b) {
13547
13709
  r /= 255;
@@ -13602,10 +13764,10 @@ function parseAnyColor(input) {
13602
13764
  }
13603
13765
  const rgbaMatch = s.match(/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})(?:\s*,\s*(\d*\.?\d+))?\s*\)/i);
13604
13766
  if (rgbaMatch) {
13605
- const r = clamp4(parseInt(rgbaMatch[1], 10), 0, 255);
13606
- const g = clamp4(parseInt(rgbaMatch[2], 10), 0, 255);
13607
- const b = clamp4(parseInt(rgbaMatch[3], 10), 0, 255);
13608
- const a = rgbaMatch[4] != null ? clamp4(parseFloat(rgbaMatch[4]), 0, 1) : 1;
13767
+ const r = clamp5(parseInt(rgbaMatch[1], 10), 0, 255);
13768
+ const g = clamp5(parseInt(rgbaMatch[2], 10), 0, 255);
13769
+ const b = clamp5(parseInt(rgbaMatch[3], 10), 0, 255);
13770
+ const a = rgbaMatch[4] != null ? clamp5(parseFloat(rgbaMatch[4]), 0, 1) : 1;
13609
13771
  return { r, g, b, a };
13610
13772
  }
13611
13773
  const hslaMatch = s.match(/hsla?\(\s*(\d{1,3}(?:\.\d+)?)\s*,?\s*(\d{1,3}(?:\.\d+)?)%?\s*,?\s*(\d{1,3}(?:\.\d+)?)%?(?:\s*,?\s*(\d*\.?\d+))?\s*\)/i);
@@ -13613,7 +13775,7 @@ function parseAnyColor(input) {
13613
13775
  const h = parseFloat(hslaMatch[1]);
13614
13776
  const sl = parseFloat(hslaMatch[2]);
13615
13777
  const l = parseFloat(hslaMatch[3]);
13616
- const a = hslaMatch[4] != null ? clamp4(parseFloat(hslaMatch[4]), 0, 1) : 1;
13778
+ const a = hslaMatch[4] != null ? clamp5(parseFloat(hslaMatch[4]), 0, 1) : 1;
13617
13779
  const rgb = hslToRgb(h, sl, l);
13618
13780
  return { ...rgb, a };
13619
13781
  }
@@ -13626,12 +13788,12 @@ function formatOutput({ r, g, b, a }, withAlpha, format) {
13626
13788
  if (format === "hsl" || format === "hsla") {
13627
13789
  const hsl = rgbToHsl(r, g, b);
13628
13790
  if (format === "hsla" || withAlpha) {
13629
- return `hsla(${Math.round(hsl.h)}, ${Math.round(hsl.s)}%, ${Math.round(hsl.l)}%, ${clamp4(a, 0, 1)})`;
13791
+ return `hsla(${Math.round(hsl.h)}, ${Math.round(hsl.s)}%, ${Math.round(hsl.l)}%, ${clamp5(a, 0, 1)})`;
13630
13792
  }
13631
13793
  return `hsl(${Math.round(hsl.h)}, ${Math.round(hsl.s)}%, ${Math.round(hsl.l)}%)`;
13632
13794
  }
13633
13795
  if (withAlpha || a !== 1) {
13634
- return `rgba(${clamp4(r, 0, 255)}, ${clamp4(g, 0, 255)}, ${clamp4(b, 0, 255)}, ${clamp4(a, 0, 1)})`;
13796
+ return `rgba(${clamp5(r, 0, 255)}, ${clamp5(g, 0, 255)}, ${clamp5(b, 0, 255)}, ${clamp5(a, 0, 1)})`;
13635
13797
  }
13636
13798
  return rgbToHex(r, g, b);
13637
13799
  }
@@ -13765,7 +13927,7 @@ function ColorPicker({
13765
13927
  emit(next);
13766
13928
  };
13767
13929
  const setAlpha = (aPct) => {
13768
- const a = clamp4(aPct / 100, 0, 1);
13930
+ const a = clamp5(aPct / 100, 0, 1);
13769
13931
  const next = { ...rgba, a };
13770
13932
  setRgba(next);
13771
13933
  emit(next);
@@ -18426,7 +18588,7 @@ function toNullableNumber(value) {
18426
18588
  }
18427
18589
  return null;
18428
18590
  }
18429
- function clamp5(value, min, max) {
18591
+ function clamp6(value, min, max) {
18430
18592
  return Math.min(max, Math.max(min, value));
18431
18593
  }
18432
18594
  function ResizableImageNodeView(props) {
@@ -18488,18 +18650,18 @@ function ResizableImageNodeView(props) {
18488
18650
  let nextH = drag.startH;
18489
18651
  if (event.ctrlKey) {
18490
18652
  if (Math.abs(dx) >= Math.abs(dy)) {
18491
- nextW = clamp5(drag.startW + dx, MIN_IMAGE_SIZE_PX, drag.maxW);
18492
- nextH = clamp5(nextW / drag.aspect, MIN_IMAGE_SIZE_PX, Number.POSITIVE_INFINITY);
18653
+ nextW = clamp6(drag.startW + dx, MIN_IMAGE_SIZE_PX, drag.maxW);
18654
+ nextH = clamp6(nextW / drag.aspect, MIN_IMAGE_SIZE_PX, Number.POSITIVE_INFINITY);
18493
18655
  } else {
18494
- nextH = clamp5(drag.startH + dy, MIN_IMAGE_SIZE_PX, Number.POSITIVE_INFINITY);
18495
- nextW = clamp5(nextH * drag.aspect, MIN_IMAGE_SIZE_PX, drag.maxW);
18656
+ nextH = clamp6(drag.startH + dy, MIN_IMAGE_SIZE_PX, Number.POSITIVE_INFINITY);
18657
+ nextW = clamp6(nextH * drag.aspect, MIN_IMAGE_SIZE_PX, drag.maxW);
18496
18658
  }
18497
18659
  } else {
18498
18660
  if (!drag.axis && (Math.abs(dx) > AXIS_LOCK_THRESHOLD_PX || Math.abs(dy) > AXIS_LOCK_THRESHOLD_PX)) {
18499
18661
  drag.axis = Math.abs(dx) >= Math.abs(dy) ? "x" : "y";
18500
18662
  }
18501
- if (drag.axis === "x") nextW = clamp5(drag.startW + dx, MIN_IMAGE_SIZE_PX, drag.maxW);
18502
- if (drag.axis === "y") nextH = clamp5(drag.startH + dy, MIN_IMAGE_SIZE_PX, Number.POSITIVE_INFINITY);
18663
+ if (drag.axis === "x") nextW = clamp6(drag.startW + dx, MIN_IMAGE_SIZE_PX, drag.maxW);
18664
+ if (drag.axis === "y") nextH = clamp6(drag.startH + dy, MIN_IMAGE_SIZE_PX, Number.POSITIVE_INFINITY);
18503
18665
  }
18504
18666
  drag.lastW = nextW;
18505
18667
  drag.lastH = nextH;