@underverse-ui/underverse 1.0.92 → 1.0.93

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "package": "@underverse-ui/underverse",
3
- "version": "1.0.92",
3
+ "version": "1.0.93",
4
4
  "sourceEntry": "src/index.ts",
5
5
  "totalExports": 225,
6
6
  "exports": [
package/dist/index.cjs CHANGED
@@ -9754,7 +9754,7 @@ function WheelColumn({
9754
9754
  ref: scrollRef,
9755
9755
  className: cn(
9756
9756
  "h-full overflow-y-auto overscroll-contain snap-y snap-mandatory",
9757
- "select-none cursor-grab active:cursor-grabbing",
9757
+ "select-none",
9758
9758
  "focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background rounded-xl",
9759
9759
  "[scrollbar-width:none] [-ms-overflow-style:none]",
9760
9760
  "[&::-webkit-scrollbar]:hidden"
@@ -9765,15 +9765,7 @@ function WheelColumn({
9765
9765
  tabIndex: focused ? 0 : -1,
9766
9766
  onKeyDown: (e) => onKeyDown(e, column),
9767
9767
  onFocus: () => setFocusedColumn(column),
9768
- onScroll: () => {
9769
- if (draggingRef.current) return;
9770
- if (inertialRef.current) return;
9771
- handleScroll();
9772
- },
9773
- onPointerDown,
9774
- onPointerMove,
9775
- onPointerUp: (e) => endDrag(e.pointerId),
9776
- onPointerCancel: (e) => endDrag(e.pointerId),
9768
+ onScroll: handleScroll,
9777
9769
  children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { children: extendedItems.map((n, index) => {
9778
9770
  const dist = Math.abs(index - currentVirtual);
9779
9771
  const distForVisual = Math.min(dist, 2);
@@ -11236,7 +11228,7 @@ function WheelColumn2({
11236
11228
  ref: scrollRef,
11237
11229
  className: cn(
11238
11230
  "h-full overflow-y-auto overscroll-contain snap-y snap-mandatory",
11239
- "select-none cursor-grab active:cursor-grabbing",
11231
+ "select-none",
11240
11232
  "focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background rounded-xl",
11241
11233
  "[scrollbar-width:none] [-ms-overflow-style:none]",
11242
11234
  "[&::-webkit-scrollbar]:hidden"
@@ -11247,15 +11239,7 @@ function WheelColumn2({
11247
11239
  tabIndex: focused ? 0 : -1,
11248
11240
  onKeyDown: (e) => onKeyDown(e, column),
11249
11241
  onFocus: () => setFocusedColumn(column),
11250
- onScroll: () => {
11251
- if (draggingRef.current) return;
11252
- if (inertialRef.current) return;
11253
- handleScroll();
11254
- },
11255
- onPointerDown,
11256
- onPointerMove,
11257
- onPointerUp: (e) => endDrag(e.pointerId),
11258
- onPointerCancel: (e) => endDrag(e.pointerId),
11242
+ onScroll: handleScroll,
11259
11243
  children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { children: extendedItems.map((n, index) => {
11260
11244
  const dist = Math.abs(index - currentVirtual);
11261
11245
  const distForVisual = Math.min(dist, 2);
@@ -11330,6 +11314,14 @@ function formatTime2({ h, m, s, p }, fmt, includeSeconds) {
11330
11314
  const base2 = `${pad(h)}:${pad(m)}`;
11331
11315
  return includeSeconds ? `${base2}:${pad(s)}` : base2;
11332
11316
  }
11317
+ function isCompleteTimeInput(input, fmt, includeSeconds) {
11318
+ const trimmed = input.trim().toUpperCase();
11319
+ if (!trimmed) return false;
11320
+ if (fmt === "12") {
11321
+ return includeSeconds ? /^\d{1,2}:\d{2}:\d{2}\s?(AM|PM)$/.test(trimmed) : /^\d{1,2}:\d{2}\s?(AM|PM)$/.test(trimmed);
11322
+ }
11323
+ return includeSeconds ? /^\d{1,2}:\d{2}:\d{2}$/.test(trimmed) : /^\d{1,2}:\d{2}$/.test(trimmed);
11324
+ }
11333
11325
  var PRESETS = {
11334
11326
  morning: { h: 9, m: 0, s: 0, icon: import_lucide_react18.Coffee, label: "Morning", color: "from-amber-400 to-orange-400" },
11335
11327
  afternoon: { h: 14, m: 0, s: 0, icon: import_lucide_react18.Sun, label: "Afternoon", color: "from-yellow-400 to-amber-400" },
@@ -11371,24 +11363,38 @@ function TimePicker({
11371
11363
  ...rest
11372
11364
  }) {
11373
11365
  const tv = useSmartTranslations("ValidationInput");
11366
+ const autoId = React31.useId();
11374
11367
  const isControlled = value !== void 0;
11375
11368
  const now = /* @__PURE__ */ new Date();
11376
11369
  const initial = parseTime(isControlled ? value : defaultValue, format, includeSeconds) || (format === "12" ? { h: now.getHours() % 12 || 12, m: now.getMinutes(), s: now.getSeconds(), p: now.getHours() >= 12 ? "PM" : "AM" } : { h: now.getHours(), m: now.getMinutes(), s: now.getSeconds() });
11377
11370
  const [open, setOpen] = React31.useState(false);
11378
11371
  const [parts, setParts] = React31.useState(initial);
11379
- const [manualInput, setManualInput] = React31.useState("");
11372
+ const [manualInput, setManualInput] = React31.useState(formatTime2(initial, format, includeSeconds));
11373
+ const [isDirectEditing, setIsDirectEditing] = React31.useState(false);
11380
11374
  const [focusedColumn, setFocusedColumn] = React31.useState(null);
11381
11375
  const [localRequiredError, setLocalRequiredError] = React31.useState();
11382
11376
  const [hasCommittedValue, setHasCommittedValue] = React31.useState(Boolean(isControlled ? value : defaultValue));
11383
11377
  const hourScrollRef = React31.useRef(null);
11384
11378
  const minuteScrollRef = React31.useRef(null);
11385
11379
  const secondScrollRef = React31.useRef(null);
11380
+ const periodRef = React31.useRef(null);
11381
+ const directEditInputRef = React31.useRef(null);
11382
+ const triggerId = `time-picker-trigger-${autoId}`;
11383
+ const labelId = label ? `time-picker-label-${autoId}` : void 0;
11386
11384
  React31.useEffect(() => {
11387
11385
  if (isControlled) {
11388
11386
  const parsed = parseTime(value, format, includeSeconds);
11389
11387
  if (parsed) setParts(parsed);
11390
11388
  }
11391
11389
  }, [value, isControlled, format, includeSeconds]);
11390
+ React31.useEffect(() => {
11391
+ setManualInput(formatTime2(parts, format, includeSeconds));
11392
+ }, [format, includeSeconds, parts]);
11393
+ React31.useEffect(() => {
11394
+ if (!isDirectEditing) return;
11395
+ directEditInputRef.current?.focus();
11396
+ directEditInputRef.current?.select();
11397
+ }, [isDirectEditing]);
11392
11398
  React31.useEffect(() => {
11393
11399
  if (isControlled) {
11394
11400
  setHasCommittedValue(Boolean(value));
@@ -11479,6 +11485,15 @@ function TimePicker({
11479
11485
  setLocalRequiredError(void 0);
11480
11486
  }
11481
11487
  }, [disabled, hasCommittedValue, required]);
11488
+ const focusColumn = React31.useCallback((column) => {
11489
+ if (!column) return;
11490
+ const target = column === "hour" ? hourScrollRef.current : column === "minute" ? minuteScrollRef.current : column === "second" ? secondScrollRef.current : periodRef.current;
11491
+ target?.focus({ preventScroll: true });
11492
+ }, []);
11493
+ React31.useEffect(() => {
11494
+ if (variant !== "inline" && !open) return;
11495
+ focusColumn(focusedColumn);
11496
+ }, [focusColumn, focusedColumn, open, variant]);
11482
11497
  const handleKeyDown2 = (e, column) => {
11483
11498
  if (!["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Home", "End", "PageUp", "PageDown"].includes(e.key)) return;
11484
11499
  e.preventDefault();
@@ -11522,6 +11537,7 @@ function TimePicker({
11522
11537
  }
11523
11538
  tryUpdate(newParts);
11524
11539
  };
11540
+ const display = formatTime2(parts, format, includeSeconds);
11525
11541
  const setNow = () => {
11526
11542
  const now2 = /* @__PURE__ */ new Date();
11527
11543
  const h = now2.getHours();
@@ -11547,6 +11563,7 @@ function TimePicker({
11547
11563
  };
11548
11564
  const handleManualInput = (input) => {
11549
11565
  setManualInput(input);
11566
+ if (!isCompleteTimeInput(input, format, includeSeconds)) return;
11550
11567
  const parsed = parseTime(input, format, includeSeconds);
11551
11568
  if (parsed) {
11552
11569
  const timeStr = formatTime2(parsed, format, includeSeconds);
@@ -11555,6 +11572,53 @@ function TimePicker({
11555
11572
  }
11556
11573
  }
11557
11574
  };
11575
+ const commitManualInput = React31.useCallback(
11576
+ (input) => {
11577
+ const trimmed = input.trim();
11578
+ if (!trimmed) {
11579
+ setManualInput(display);
11580
+ return false;
11581
+ }
11582
+ if (!isCompleteTimeInput(trimmed, format, includeSeconds)) {
11583
+ setManualInput(display);
11584
+ return false;
11585
+ }
11586
+ const parsed = parseTime(trimmed, format, includeSeconds);
11587
+ if (!parsed) {
11588
+ setManualInput(display);
11589
+ return false;
11590
+ }
11591
+ const timeStr = formatTime2(parsed, format, includeSeconds);
11592
+ if (!isTimeInRange(timeStr) || isTimeDisabled(timeStr)) {
11593
+ setManualInput(display);
11594
+ return false;
11595
+ }
11596
+ const updated = tryUpdate(parsed);
11597
+ if (!updated) {
11598
+ setManualInput(display);
11599
+ return false;
11600
+ }
11601
+ setManualInput(timeStr);
11602
+ return true;
11603
+ },
11604
+ [display, format, includeSeconds, isTimeDisabled, isTimeInRange, tryUpdate]
11605
+ );
11606
+ const startDirectEdit = React31.useCallback(() => {
11607
+ if (disabled) return;
11608
+ setManualInput(display);
11609
+ setIsDirectEditing(true);
11610
+ }, [disabled, display]);
11611
+ const stopDirectEdit = React31.useCallback(
11612
+ (mode) => {
11613
+ if (mode === "commit") {
11614
+ commitManualInput(manualInput);
11615
+ } else {
11616
+ setManualInput(display);
11617
+ }
11618
+ setIsDirectEditing(false);
11619
+ },
11620
+ [commitManualInput, display, manualInput]
11621
+ );
11558
11622
  const handleCustomPreset = (time) => {
11559
11623
  const parsed = parseTime(time, format, includeSeconds);
11560
11624
  if (parsed) {
@@ -11622,13 +11686,14 @@ function TimePicker({
11622
11686
  const shouldMatchTriggerWidth = matchTriggerWidth ?? variant !== "compact";
11623
11687
  const compactPanel = variant === "compact";
11624
11688
  const effectiveError = error ?? localRequiredError;
11625
- const display = formatTime2(parts, format, includeSeconds);
11626
11689
  const trigger = variant === "inline" ? null : /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
11627
11690
  "button",
11628
11691
  {
11692
+ id: triggerId,
11629
11693
  type: "button",
11630
11694
  disabled,
11631
11695
  "aria-label": "Select time",
11696
+ "aria-labelledby": labelId,
11632
11697
  "aria-haspopup": "dialog",
11633
11698
  "aria-expanded": open,
11634
11699
  "aria-required": required,
@@ -11665,10 +11730,10 @@ function TimePicker({
11665
11730
  {
11666
11731
  className: cn(
11667
11732
  "truncate font-medium transition-colors duration-200",
11668
- !value && !defaultValue && "text-muted-foreground",
11669
- value || defaultValue ? "text-foreground" : ""
11733
+ !hasCommittedValue && "text-muted-foreground",
11734
+ hasCommittedValue ? "text-foreground" : ""
11670
11735
  ),
11671
- children: value || defaultValue ? display : placeholder
11736
+ children: hasCommittedValue ? display : placeholder
11672
11737
  }
11673
11738
  )
11674
11739
  ] }),
@@ -11688,13 +11753,51 @@ function TimePicker({
11688
11753
  tryUpdate(next);
11689
11754
  };
11690
11755
  const timePickerContent = /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: cn(panelSz.stackGap, compactPanel && "space-y-2.5"), children: [
11691
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: cn("flex items-center justify-center py-1", compactPanel && "py-0.5"), children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: cn(panelSz.timeText, "font-bold tabular-nums tracking-wide text-foreground underline underline-offset-8 decoration-primary/60"), children: display }) }),
11756
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: cn("flex items-center justify-center py-1", compactPanel && "py-0.5"), children: isDirectEditing ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
11757
+ "input",
11758
+ {
11759
+ ref: directEditInputRef,
11760
+ type: "text",
11761
+ value: manualInput,
11762
+ onChange: (e) => setManualInput(e.target.value),
11763
+ onBlur: () => stopDirectEdit("commit"),
11764
+ onKeyDown: (e) => {
11765
+ if (e.key === "Enter") {
11766
+ e.preventDefault();
11767
+ stopDirectEdit("commit");
11768
+ }
11769
+ if (e.key === "Escape") {
11770
+ e.preventDefault();
11771
+ stopDirectEdit("cancel");
11772
+ }
11773
+ },
11774
+ "aria-label": "Edit time value",
11775
+ className: cn(
11776
+ panelSz.timeText,
11777
+ "min-w-0 w-36 bg-transparent border-b border-primary/60 text-center font-bold tabular-nums tracking-wide text-foreground",
11778
+ "focus:outline-none focus:border-primary"
11779
+ )
11780
+ }
11781
+ ) : /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
11782
+ "button",
11783
+ {
11784
+ type: "button",
11785
+ onClick: startDirectEdit,
11786
+ className: cn(
11787
+ panelSz.timeText,
11788
+ "font-bold tabular-nums tracking-wide text-foreground underline underline-offset-8 decoration-primary/60",
11789
+ "rounded-md px-2 transition-colors hover:text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/50"
11790
+ ),
11791
+ "aria-label": "Edit selected time",
11792
+ children: display
11793
+ }
11794
+ ) }),
11692
11795
  allowManualInput && /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "relative", children: [
11693
11796
  /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
11694
11797
  Input_default,
11695
11798
  {
11696
11799
  placeholder: format === "12" ? "02:30 PM" : "14:30",
11697
- value: manualInput || display,
11800
+ value: manualInput,
11698
11801
  onChange: (e) => handleManualInput(e.target.value),
11699
11802
  size: panelSz.inputSize,
11700
11803
  variant: "outlined",
@@ -11844,9 +11947,11 @@ function TimePicker({
11844
11947
  /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
11845
11948
  "div",
11846
11949
  {
11950
+ ref: periodRef,
11847
11951
  className: cn("flex flex-col p-1 rounded-xl bg-muted/30", panelSz.periodGap),
11848
11952
  role: "radiogroup",
11849
11953
  "aria-label": "Select AM or PM",
11954
+ "aria-labelledby": labelId,
11850
11955
  tabIndex: focusedColumn === "period" ? 0 : -1,
11851
11956
  onKeyDown: (e) => handleKeyDown2(e, "period"),
11852
11957
  onFocus: () => setFocusedColumn("period"),
@@ -11942,10 +12047,17 @@ function TimePicker({
11942
12047
  ] });
11943
12048
  if (variant === "inline") {
11944
12049
  return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "w-fit max-w-full", ...rest, children: [
11945
- label && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: "flex items-center justify-between mb-3", children: /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("label", { className: cn(sz.label, "font-semibold", disabled ? "text-muted-foreground" : "text-foreground", effectiveError && "text-destructive"), children: [
11946
- label,
11947
- required && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "text-destructive ml-1", children: "*" })
11948
- ] }) }),
12050
+ label && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: "flex items-center justify-between mb-3", children: /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
12051
+ "label",
12052
+ {
12053
+ id: labelId,
12054
+ className: cn(sz.label, "font-semibold", disabled ? "text-muted-foreground" : "text-foreground", effectiveError && "text-destructive"),
12055
+ children: [
12056
+ label,
12057
+ required && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "text-destructive ml-1", children: "*" })
12058
+ ]
12059
+ }
12060
+ ) }),
11949
12061
  /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
11950
12062
  "input",
11951
12063
  {
@@ -11982,6 +12094,8 @@ function TimePicker({
11982
12094
  label && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: "flex items-center justify-between mb-2", children: /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
11983
12095
  "label",
11984
12096
  {
12097
+ id: labelId,
12098
+ htmlFor: triggerId,
11985
12099
  className: cn(
11986
12100
  sz.label,
11987
12101
  "font-semibold",