@particle-academy/react-fancy 4.5.0 → 4.6.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.js CHANGED
@@ -3091,19 +3091,33 @@ function DisplayValue({
3091
3091
  leading,
3092
3092
  trailing,
3093
3093
  empty = "\u2014",
3094
+ interactive,
3095
+ onActivate,
3094
3096
  className
3095
3097
  }) {
3096
3098
  const empties = isEmpty(children);
3099
+ const onKeyDown = interactive ? (e) => {
3100
+ if (e.key === "Enter" || e.key === " ") {
3101
+ e.preventDefault();
3102
+ onActivate?.();
3103
+ }
3104
+ } : void 0;
3097
3105
  return /* @__PURE__ */ jsxs(
3098
3106
  "div",
3099
3107
  {
3100
3108
  "data-react-fancy-display": "",
3101
3109
  "data-mode": "view",
3110
+ role: interactive ? "button" : void 0,
3111
+ tabIndex: interactive ? 0 : void 0,
3112
+ title: interactive ? "Click to edit" : void 0,
3113
+ onClick: interactive ? onActivate : void 0,
3114
+ onKeyDown,
3102
3115
  className: cn(
3103
3116
  "flex w-full items-center gap-2 text-zinc-900 dark:text-zinc-100",
3104
3117
  inputSizeClasses[size],
3105
3118
  // Strip the editable box look — keep only the size/padding rhythm.
3106
3119
  "border-0 bg-transparent px-0",
3120
+ interactive && "-mx-2 cursor-text rounded-md px-2 outline-none transition-colors hover:bg-zinc-100 focus-visible:ring-2 focus-visible:ring-blue-500/40 dark:hover:bg-zinc-800",
3107
3121
  empties && "text-zinc-400 dark:text-zinc-500",
3108
3122
  className
3109
3123
  ),
@@ -3115,6 +3129,16 @@ function DisplayValue({
3115
3129
  }
3116
3130
  );
3117
3131
  }
3132
+ function useInlineEdit(mode, disabled) {
3133
+ const [editing, setEditing] = useState(false);
3134
+ const interactive = mode === "view" && !disabled;
3135
+ const showControl = mode !== "view" || interactive && editing;
3136
+ const enterEdit = useCallback(() => {
3137
+ if (interactive) setEditing(true);
3138
+ }, [interactive]);
3139
+ const exitEdit = useCallback(() => setEditing(false), []);
3140
+ return { showControl, interactive, enterEdit, exitEdit };
3141
+ }
3118
3142
  var Input = forwardRef(
3119
3143
  ({
3120
3144
  type = "text",
@@ -3141,7 +3165,18 @@ var Input = forwardRef(
3141
3165
  const autoId = useId();
3142
3166
  const inputId = id ?? autoId;
3143
3167
  const resolvedMode = useFieldMode(mode);
3144
- const input = resolvedMode === "view" ? /* @__PURE__ */ jsx(DisplayValue, { size, leading: leading ?? prefix, trailing: trailing ?? suffix, children: type === "password" ? props.value ? "\u2022\u2022\u2022\u2022\u2022\u2022" : "" : props.value }) : /* @__PURE__ */ jsx(
3168
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
3169
+ const input = !showControl ? /* @__PURE__ */ jsx(
3170
+ DisplayValue,
3171
+ {
3172
+ size,
3173
+ interactive,
3174
+ onActivate: enterEdit,
3175
+ leading: leading ?? prefix,
3176
+ trailing: trailing ?? suffix,
3177
+ children: type === "password" ? props.value ? "\u2022\u2022\u2022\u2022\u2022\u2022" : "" : props.value
3178
+ }
3179
+ ) : /* @__PURE__ */ jsx(
3145
3180
  InputWrapper,
3146
3181
  {
3147
3182
  prefix,
@@ -3173,7 +3208,12 @@ var Input = forwardRef(
3173
3208
  onChange?.(e);
3174
3209
  onValueChange?.(e.target.value);
3175
3210
  },
3176
- ...props
3211
+ ...props,
3212
+ autoFocus: interactive || props.autoFocus,
3213
+ onBlur: (e) => {
3214
+ props.onBlur?.(e);
3215
+ if (interactive) exitEdit();
3216
+ }
3177
3217
  }
3178
3218
  ),
3179
3219
  trailing && /* @__PURE__ */ jsx("span", { className: "pointer-events-none absolute right-3 text-zinc-400 dark:text-zinc-500", children: trailing })
@@ -3227,6 +3267,7 @@ var Textarea = forwardRef(
3227
3267
  const textareaId = id ?? autoId;
3228
3268
  const internalRef = useRef(null);
3229
3269
  const resolvedMode = useFieldMode(mode);
3270
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
3230
3271
  useEffect(() => {
3231
3272
  const el = internalRef.current;
3232
3273
  if (!autoResize || !el) return;
@@ -3236,7 +3277,16 @@ var Textarea = forwardRef(
3236
3277
  const maxHeight = maxRows ? maxRows * lineHeight : Infinity;
3237
3278
  el.style.height = `${Math.min(Math.max(el.scrollHeight, minHeight), maxHeight)}px`;
3238
3279
  }, [autoResize, minRows, maxRows, value, defaultValue]);
3239
- const textarea = resolvedMode === "view" ? /* @__PURE__ */ jsx(DisplayValue, { size, className: "whitespace-pre-wrap", children: value ?? defaultValue }) : /* @__PURE__ */ jsx(
3280
+ const textarea = !showControl ? /* @__PURE__ */ jsx(
3281
+ DisplayValue,
3282
+ {
3283
+ size,
3284
+ className: "whitespace-pre-wrap",
3285
+ interactive,
3286
+ onActivate: enterEdit,
3287
+ children: value ?? defaultValue
3288
+ }
3289
+ ) : /* @__PURE__ */ jsx(
3240
3290
  InputWrapper,
3241
3291
  {
3242
3292
  prefix,
@@ -3275,7 +3325,12 @@ var Textarea = forwardRef(
3275
3325
  onChange?.(e);
3276
3326
  onValueChange?.(e.target.value);
3277
3327
  },
3278
- ...props
3328
+ ...props,
3329
+ autoFocus: interactive || props.autoFocus,
3330
+ onBlur: (e) => {
3331
+ props.onBlur?.(e);
3332
+ if (interactive) exitEdit();
3333
+ }
3279
3334
  }
3280
3335
  )
3281
3336
  }
@@ -3518,10 +3573,21 @@ var NativeSelect = forwardRef(
3518
3573
  const autoId = useId();
3519
3574
  const selectId = id ?? autoId;
3520
3575
  const resolvedMode = useFieldMode(mode);
3521
- if (resolvedMode === "view") {
3576
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
3577
+ if (!showControl) {
3522
3578
  const current = value ?? defaultValue;
3523
3579
  const opt = flattenOptions(list).map((o) => resolveOption(o)).find((o) => o.value === current);
3524
- const display = /* @__PURE__ */ jsx(DisplayValue, { size, leading: prefix, trailing: suffix, children: opt?.label ?? current });
3580
+ const display = /* @__PURE__ */ jsx(
3581
+ DisplayValue,
3582
+ {
3583
+ size,
3584
+ leading: prefix,
3585
+ trailing: suffix,
3586
+ interactive,
3587
+ onActivate: enterEdit,
3588
+ children: opt?.label ?? current
3589
+ }
3590
+ );
3525
3591
  if (label || error || description) {
3526
3592
  return /* @__PURE__ */ jsx(
3527
3593
  Field,
@@ -3572,6 +3638,11 @@ var NativeSelect = forwardRef(
3572
3638
  },
3573
3639
  ...props,
3574
3640
  ...isControlled ? { value } : { defaultValue: resolvedDefault },
3641
+ autoFocus: interactive || props.autoFocus,
3642
+ onBlur: (e) => {
3643
+ props.onBlur?.(e);
3644
+ if (interactive) exitEdit();
3645
+ },
3575
3646
  children: [
3576
3647
  placeholder && /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: placeholder }),
3577
3648
  list.map(
@@ -3633,6 +3704,7 @@ var ListboxSelect = forwardRef(
3633
3704
  const autoId = useId();
3634
3705
  const selectId = id ?? autoId;
3635
3706
  const resolvedMode = useFieldMode(mode);
3707
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
3636
3708
  const textInputEnabled = searchable || creatable;
3637
3709
  const [open, setOpen] = useState(false);
3638
3710
  const [search2, setSearch] = useState("");
@@ -3759,11 +3831,11 @@ var ListboxSelect = forwardRef(
3759
3831
  }
3760
3832
  }
3761
3833
  };
3762
- if (resolvedMode === "view") {
3834
+ if (!showControl) {
3763
3835
  const labels = multiple ? currentMulti.map(
3764
3836
  (v) => resolvedOptions.find((o) => o.value === v)?.label ?? v
3765
3837
  ) : currentSingle ? [resolvedOptions.find((o) => o.value === currentSingle)?.label ?? currentSingle] : [];
3766
- const display = /* @__PURE__ */ jsx(DisplayValue, { size, children: labels.join(", ") });
3838
+ const display = /* @__PURE__ */ jsx(DisplayValue, { size, interactive, onActivate: enterEdit, children: labels.join(", ") });
3767
3839
  if (label || error || description) {
3768
3840
  return /* @__PURE__ */ jsx(
3769
3841
  Field,
@@ -3787,6 +3859,7 @@ var ListboxSelect = forwardRef(
3787
3859
  type: "button",
3788
3860
  id: selectId,
3789
3861
  disabled,
3862
+ autoFocus: interactive,
3790
3863
  onClick: () => setOpen((o) => !o),
3791
3864
  onKeyDown: handleKeyDown,
3792
3865
  role: "combobox",
@@ -3908,10 +3981,21 @@ var ListboxSelect = forwardRef(
3908
3981
  ]
3909
3982
  }
3910
3983
  ) });
3911
- const content = /* @__PURE__ */ jsxs("div", { className: "relative", children: [
3912
- trigger,
3913
- dropdown
3914
- ] });
3984
+ const content = /* @__PURE__ */ jsxs(
3985
+ "div",
3986
+ {
3987
+ className: "relative",
3988
+ onBlur: (e) => {
3989
+ if (interactive && !open && !e.currentTarget.contains(e.relatedTarget)) {
3990
+ exitEdit();
3991
+ }
3992
+ },
3993
+ children: [
3994
+ trigger,
3995
+ dropdown
3996
+ ]
3997
+ }
3998
+ );
3915
3999
  if (label || error || description) {
3916
4000
  return /* @__PURE__ */ jsx(
3917
4001
  Field,
@@ -3962,6 +4046,7 @@ var Checkbox = forwardRef(
3962
4046
  const checkboxId = id ?? autoId;
3963
4047
  const internalRef = useRef(null);
3964
4048
  const resolvedMode = useFieldMode(mode);
4049
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
3965
4050
  const [checked, setChecked] = useControllableState(
3966
4051
  controlledChecked,
3967
4052
  defaultChecked,
@@ -3982,16 +4067,27 @@ var Checkbox = forwardRef(
3982
4067
  }[size];
3983
4068
  const glyph = indeterminate ? "\u2014" : checked ? "\u2713" : "\u2715";
3984
4069
  return /* @__PURE__ */ jsxs("div", { "data-react-fancy-checkbox": "", className: cn("flex items-start gap-2", className), children: [
3985
- resolvedMode === "view" ? /* @__PURE__ */ jsx(
4070
+ !showControl ? /* @__PURE__ */ jsx(
3986
4071
  "span",
3987
4072
  {
3988
4073
  "data-react-fancy-display": "",
3989
4074
  "data-mode": "view",
4075
+ role: interactive ? "button" : void 0,
4076
+ tabIndex: interactive ? 0 : void 0,
4077
+ title: interactive ? "Click to edit" : void 0,
4078
+ onClick: interactive ? enterEdit : void 0,
4079
+ onKeyDown: interactive ? (e) => {
4080
+ if (e.key === "Enter" || e.key === " ") {
4081
+ e.preventDefault();
4082
+ enterEdit();
4083
+ }
4084
+ } : void 0,
3990
4085
  className: cn(
3991
4086
  "flex shrink-0 items-center justify-center text-zinc-700 dark:text-zinc-200",
3992
- sizeClasses6
4087
+ sizeClasses6,
4088
+ interactive && "cursor-pointer rounded outline-none transition-colors hover:text-zinc-900 focus-visible:ring-2 focus-visible:ring-blue-500/40 dark:hover:text-white"
3993
4089
  ),
3994
- "aria-hidden": "true",
4090
+ "aria-hidden": interactive ? void 0 : "true",
3995
4091
  children: glyph
3996
4092
  }
3997
4093
  ) : /* @__PURE__ */ jsx("div", { className: "relative flex items-center", children: /* @__PURE__ */ jsx(
@@ -4011,6 +4107,10 @@ var Checkbox = forwardRef(
4011
4107
  disabled,
4012
4108
  required,
4013
4109
  checked,
4110
+ autoFocus: interactive,
4111
+ onBlur: () => {
4112
+ if (interactive) exitEdit();
4113
+ },
4014
4114
  onChange: (e) => setChecked(e.target.checked),
4015
4115
  className: cn(
4016
4116
  sizeClasses6,
@@ -4061,6 +4161,7 @@ function CheckboxGroup({
4061
4161
  }) {
4062
4162
  const groupId = useId();
4063
4163
  const resolvedMode = useFieldMode(mode);
4164
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
4064
4165
  const [value, setValue] = useControllableState(
4065
4166
  controlledValue,
4066
4167
  defaultValue,
@@ -4077,10 +4178,13 @@ function CheckboxGroup({
4077
4178
  lg: "h-5 w-5",
4078
4179
  xl: "h-6 w-6"
4079
4180
  }[size];
4080
- const content = resolvedMode === "view" ? /* @__PURE__ */ jsx(DisplayValue, { size, children: list.map(resolveOption).filter((o) => value.includes(o.value)).map((o) => o.label).join(", ") }) : /* @__PURE__ */ jsx(
4181
+ const content = !showControl ? /* @__PURE__ */ jsx(DisplayValue, { size, interactive, onActivate: enterEdit, children: list.map(resolveOption).filter((o) => value.includes(o.value)).map((o) => o.label).join(", ") }) : /* @__PURE__ */ jsx(
4081
4182
  "div",
4082
4183
  {
4083
4184
  "data-react-fancy-checkbox-group": "",
4185
+ onBlur: (e) => {
4186
+ if (interactive && !e.currentTarget.contains(e.relatedTarget)) exitEdit();
4187
+ },
4084
4188
  className: cn(
4085
4189
  "flex gap-3",
4086
4190
  orientation === "vertical" ? "flex-col" : "flex-row flex-wrap",
@@ -4100,6 +4204,7 @@ function CheckboxGroup({
4100
4204
  value: String(resolved.value),
4101
4205
  checked: isChecked,
4102
4206
  disabled: disabled || resolved.disabled,
4207
+ autoFocus: interactive && index === 0,
4103
4208
  onChange: () => handleToggle(resolved.value),
4104
4209
  className: cn(
4105
4210
  sizeClasses6,
@@ -4163,6 +4268,7 @@ function RadioGroup({
4163
4268
  const groupId = useId();
4164
4269
  const radioName = name ?? groupId;
4165
4270
  const resolvedMode = useFieldMode(mode);
4271
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
4166
4272
  const [value, setValue] = useControllableState(
4167
4273
  controlledValue,
4168
4274
  defaultValue,
@@ -4175,11 +4281,15 @@ function RadioGroup({
4175
4281
  lg: "h-5 w-5",
4176
4282
  xl: "h-6 w-6"
4177
4283
  }[size];
4178
- const content = resolvedMode === "view" ? /* @__PURE__ */ jsx(DisplayValue, { size, children: list.map(resolveOption).find((o) => o.value === value)?.label }) : /* @__PURE__ */ jsx(
4284
+ const selectedIndex = list.map(resolveOption).findIndex((o) => o.value === value);
4285
+ const content = !showControl ? /* @__PURE__ */ jsx(DisplayValue, { size, interactive, onActivate: enterEdit, children: list.map(resolveOption).find((o) => o.value === value)?.label }) : /* @__PURE__ */ jsx(
4179
4286
  "div",
4180
4287
  {
4181
4288
  "data-react-fancy-radio-group": "",
4182
4289
  role: "radiogroup",
4290
+ onBlur: (e) => {
4291
+ if (interactive && !e.currentTarget.contains(e.relatedTarget)) exitEdit();
4292
+ },
4183
4293
  className: cn(
4184
4294
  "flex gap-3",
4185
4295
  orientation === "vertical" ? "flex-col" : "flex-row flex-wrap",
@@ -4189,6 +4299,7 @@ function RadioGroup({
4189
4299
  const resolved = resolveOption(option);
4190
4300
  const optionId = `${groupId}-${index}`;
4191
4301
  const isSelected = value === resolved.value;
4302
+ const shouldAutoFocus = interactive && (selectedIndex === -1 ? index === 0 : isSelected);
4192
4303
  return /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
4193
4304
  /* @__PURE__ */ jsx(
4194
4305
  "input",
@@ -4199,6 +4310,7 @@ function RadioGroup({
4199
4310
  value: String(resolved.value),
4200
4311
  checked: isSelected,
4201
4312
  disabled: disabled || resolved.disabled,
4313
+ autoFocus: shouldAutoFocus,
4202
4314
  onChange: () => setValue(resolved.value),
4203
4315
  className: cn(
4204
4316
  sizeClasses6,
@@ -4289,6 +4401,7 @@ var Switch = forwardRef(
4289
4401
  const autoId = useId();
4290
4402
  const switchId = id ?? autoId;
4291
4403
  const resolvedMode = useFieldMode(mode);
4404
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
4292
4405
  const [checked, setChecked] = useControllableState(
4293
4406
  controlledChecked,
4294
4407
  defaultChecked,
@@ -4316,12 +4429,25 @@ var Switch = forwardRef(
4316
4429
  xl: "translate-x-6"
4317
4430
  }[size];
4318
4431
  return /* @__PURE__ */ jsxs("div", { "data-react-fancy-switch": "", className: cn("flex items-start gap-2", className), children: [
4319
- resolvedMode === "view" ? /* @__PURE__ */ jsx(
4432
+ !showControl ? /* @__PURE__ */ jsx(
4320
4433
  "span",
4321
4434
  {
4322
4435
  "data-react-fancy-display": "",
4323
4436
  "data-mode": "view",
4324
- className: "text-sm font-medium text-zinc-700 dark:text-zinc-200",
4437
+ role: interactive ? "button" : void 0,
4438
+ tabIndex: interactive ? 0 : void 0,
4439
+ title: interactive ? "Click to edit" : void 0,
4440
+ onClick: interactive ? enterEdit : void 0,
4441
+ onKeyDown: interactive ? (e) => {
4442
+ if (e.key === "Enter" || e.key === " ") {
4443
+ e.preventDefault();
4444
+ enterEdit();
4445
+ }
4446
+ } : void 0,
4447
+ className: cn(
4448
+ "text-sm font-medium text-zinc-700 dark:text-zinc-200",
4449
+ interactive && "cursor-pointer rounded-md outline-none transition-colors hover:text-zinc-900 focus-visible:ring-2 focus-visible:ring-blue-500/40 dark:hover:text-white"
4450
+ ),
4325
4451
  children: checked ? "On" : "Off"
4326
4452
  }
4327
4453
  ) : /* @__PURE__ */ jsx(
@@ -4333,6 +4459,10 @@ var Switch = forwardRef(
4333
4459
  role: "switch",
4334
4460
  "aria-checked": checked,
4335
4461
  disabled,
4462
+ autoFocus: interactive,
4463
+ onBlur: () => {
4464
+ if (interactive) exitEdit();
4465
+ },
4336
4466
  onClick: () => setChecked(!checked),
4337
4467
  className: cn(
4338
4468
  "relative inline-flex shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/40 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
@@ -4443,12 +4573,22 @@ var SingleSlider = forwardRef(
4443
4573
  }, ref) => {
4444
4574
  const singleProps = rest;
4445
4575
  const resolvedMode = useFieldMode(mode);
4576
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
4446
4577
  const [value, setValue] = useControllableState(
4447
4578
  singleProps.value,
4448
4579
  singleProps.defaultValue ?? min,
4449
4580
  singleProps.onValueChange
4450
4581
  );
4451
- const slider = resolvedMode === "view" ? /* @__PURE__ */ jsx(DisplayValue, { size, className, children: `${prefix ?? ""}${value}${suffix ?? ""}` }) : /* @__PURE__ */ jsxs("div", { "data-react-fancy-slider": "", className: cn("flex flex-col gap-1", className), children: [
4582
+ const slider = !showControl ? /* @__PURE__ */ jsx(
4583
+ DisplayValue,
4584
+ {
4585
+ size,
4586
+ className,
4587
+ interactive,
4588
+ onActivate: enterEdit,
4589
+ children: `${prefix ?? ""}${value}${suffix ?? ""}`
4590
+ }
4591
+ ) : /* @__PURE__ */ jsxs("div", { "data-react-fancy-slider": "", className: cn("flex flex-col gap-1", className), children: [
4452
4592
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
4453
4593
  /* @__PURE__ */ jsx(
4454
4594
  "input",
@@ -4462,6 +4602,10 @@ var SingleSlider = forwardRef(
4462
4602
  step,
4463
4603
  value,
4464
4604
  disabled,
4605
+ autoFocus: interactive,
4606
+ onBlur: () => {
4607
+ if (interactive) exitEdit();
4608
+ },
4465
4609
  onChange: (e) => setValue(Number(e.target.value)),
4466
4610
  className: cn(
4467
4611
  "w-full cursor-pointer accent-blue-600 disabled:cursor-not-allowed disabled:opacity-50",
@@ -4515,6 +4659,7 @@ var RangeSlider = forwardRef(
4515
4659
  }, ref) => {
4516
4660
  const rangeProps = rest;
4517
4661
  const resolvedMode = useFieldMode(mode);
4662
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
4518
4663
  const [value, setValue] = useControllableState(
4519
4664
  rangeProps.value,
4520
4665
  rangeProps.defaultValue ?? [min, max],
@@ -4528,61 +4673,81 @@ var RangeSlider = forwardRef(
4528
4673
  };
4529
4674
  const leftPercent = (value[0] - min) / (max - min) * 100;
4530
4675
  const rightPercent = (value[1] - min) / (max - min) * 100;
4531
- const slider = resolvedMode === "view" ? /* @__PURE__ */ jsx(DisplayValue, { size, className, children: `${prefix ?? ""}${value[0]}${suffix ?? ""}\u2013${prefix ?? ""}${value[1]}${suffix ?? ""}` }) : /* @__PURE__ */ jsxs("div", { "data-react-fancy-slider": "", className: cn("flex flex-col gap-1", className), children: [
4532
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
4533
- /* @__PURE__ */ jsxs("div", { className: "relative w-full", children: [
4534
- /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute top-1/2 h-1.5 w-full -translate-y-1/2 rounded-full bg-zinc-200 dark:bg-zinc-700" }),
4535
- /* @__PURE__ */ jsx(
4536
- "div",
4537
- {
4538
- className: "pointer-events-none absolute top-1/2 h-1.5 -translate-y-1/2 rounded-full bg-blue-500",
4539
- style: { left: `${leftPercent}%`, width: `${rightPercent - leftPercent}%` }
4540
- }
4541
- ),
4542
- /* @__PURE__ */ jsx(
4543
- "input",
4544
- {
4545
- ref,
4546
- type: "range",
4547
- min,
4548
- max,
4549
- step,
4550
- value: value[0],
4551
- disabled,
4552
- onChange: (e) => handleMin(Number(e.target.value)),
4553
- className: cn(
4554
- "pointer-events-none absolute w-full cursor-pointer appearance-none bg-transparent [&::-webkit-slider-thumb]:pointer-events-auto [&::-webkit-slider-thumb]:h-4 [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-blue-600 [&::-webkit-slider-thumb]:shadow",
4555
- dirtyRingClasses(dirty)
4556
- )
4557
- }
4558
- ),
4559
- /* @__PURE__ */ jsx(
4560
- "input",
4561
- {
4562
- type: "range",
4563
- min,
4564
- max,
4565
- step,
4566
- value: value[1],
4567
- disabled,
4568
- onChange: (e) => handleMax(Number(e.target.value)),
4569
- className: "pointer-events-none absolute w-full cursor-pointer appearance-none bg-transparent [&::-webkit-slider-thumb]:pointer-events-auto [&::-webkit-slider-thumb]:h-4 [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-blue-600 [&::-webkit-slider-thumb]:shadow"
4570
- }
4571
- ),
4572
- /* @__PURE__ */ jsx("div", { className: "h-6" })
4573
- ] }),
4574
- showValue && /* @__PURE__ */ jsxs("span", { className: "min-w-[6ch] shrink-0 whitespace-nowrap text-right text-sm font-medium text-zinc-700 dark:text-zinc-300", children: [
4575
- prefix,
4576
- value[0],
4577
- suffix,
4578
- "\u2013",
4579
- prefix,
4580
- value[1],
4581
- suffix
4582
- ] })
4583
- ] }),
4584
- marks && marks.length > 0 && /* @__PURE__ */ jsx(SliderMarks, { marks, min, max })
4585
- ] });
4676
+ const slider = !showControl ? /* @__PURE__ */ jsx(
4677
+ DisplayValue,
4678
+ {
4679
+ size,
4680
+ className,
4681
+ interactive,
4682
+ onActivate: enterEdit,
4683
+ children: `${prefix ?? ""}${value[0]}${suffix ?? ""}\u2013${prefix ?? ""}${value[1]}${suffix ?? ""}`
4684
+ }
4685
+ ) : /* @__PURE__ */ jsxs(
4686
+ "div",
4687
+ {
4688
+ "data-react-fancy-slider": "",
4689
+ className: cn("flex flex-col gap-1", className),
4690
+ onBlur: (e) => {
4691
+ if (interactive && !e.currentTarget.contains(e.relatedTarget)) exitEdit();
4692
+ },
4693
+ children: [
4694
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
4695
+ /* @__PURE__ */ jsxs("div", { className: "relative w-full", children: [
4696
+ /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute top-1/2 h-1.5 w-full -translate-y-1/2 rounded-full bg-zinc-200 dark:bg-zinc-700" }),
4697
+ /* @__PURE__ */ jsx(
4698
+ "div",
4699
+ {
4700
+ className: "pointer-events-none absolute top-1/2 h-1.5 -translate-y-1/2 rounded-full bg-blue-500",
4701
+ style: { left: `${leftPercent}%`, width: `${rightPercent - leftPercent}%` }
4702
+ }
4703
+ ),
4704
+ /* @__PURE__ */ jsx(
4705
+ "input",
4706
+ {
4707
+ ref,
4708
+ type: "range",
4709
+ min,
4710
+ max,
4711
+ step,
4712
+ value: value[0],
4713
+ disabled,
4714
+ autoFocus: interactive,
4715
+ onChange: (e) => handleMin(Number(e.target.value)),
4716
+ className: cn(
4717
+ "pointer-events-none absolute w-full cursor-pointer appearance-none bg-transparent [&::-webkit-slider-thumb]:pointer-events-auto [&::-webkit-slider-thumb]:h-4 [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-blue-600 [&::-webkit-slider-thumb]:shadow",
4718
+ dirtyRingClasses(dirty)
4719
+ )
4720
+ }
4721
+ ),
4722
+ /* @__PURE__ */ jsx(
4723
+ "input",
4724
+ {
4725
+ type: "range",
4726
+ min,
4727
+ max,
4728
+ step,
4729
+ value: value[1],
4730
+ disabled,
4731
+ onChange: (e) => handleMax(Number(e.target.value)),
4732
+ className: "pointer-events-none absolute w-full cursor-pointer appearance-none bg-transparent [&::-webkit-slider-thumb]:pointer-events-auto [&::-webkit-slider-thumb]:h-4 [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-blue-600 [&::-webkit-slider-thumb]:shadow"
4733
+ }
4734
+ ),
4735
+ /* @__PURE__ */ jsx("div", { className: "h-6" })
4736
+ ] }),
4737
+ showValue && /* @__PURE__ */ jsxs("span", { className: "min-w-[6ch] shrink-0 whitespace-nowrap text-right text-sm font-medium text-zinc-700 dark:text-zinc-300", children: [
4738
+ prefix,
4739
+ value[0],
4740
+ suffix,
4741
+ "\u2013",
4742
+ prefix,
4743
+ value[1],
4744
+ suffix
4745
+ ] })
4746
+ ] }),
4747
+ marks && marks.length > 0 && /* @__PURE__ */ jsx(SliderMarks, { marks, min, max })
4748
+ ]
4749
+ }
4750
+ );
4586
4751
  if (label || error || description) {
4587
4752
  return /* @__PURE__ */ jsx(Field, { label, description, error, required, htmlFor: id, size, children: slider });
4588
4753
  }
@@ -4636,6 +4801,7 @@ function MultiSwitch({
4636
4801
  const resolvedOptions = list.map(resolveOption);
4637
4802
  const fallback = defaultValue ?? resolvedOptions[0]?.value;
4638
4803
  const resolvedMode = useFieldMode(mode);
4804
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
4639
4805
  const [value, setValue] = useControllableState(controlledValue, fallback, onValueChange);
4640
4806
  const containerRef = useRef(null);
4641
4807
  const itemRefs = useRef([]);
@@ -4660,13 +4826,16 @@ function MultiSwitch({
4660
4826
  useEffect(() => {
4661
4827
  updateIndicator();
4662
4828
  }, [updateIndicator]);
4663
- const control = resolvedMode === "view" ? /* @__PURE__ */ jsx(DisplayValue, { size, children: resolvedOptions.find((o) => o.value === value)?.label }) : /* @__PURE__ */ jsxs(
4829
+ const control = !showControl ? /* @__PURE__ */ jsx(DisplayValue, { size, interactive, onActivate: enterEdit, children: resolvedOptions.find((o) => o.value === value)?.label }) : /* @__PURE__ */ jsxs(
4664
4830
  "div",
4665
4831
  {
4666
4832
  ref: containerRef,
4667
4833
  "data-react-fancy-multi-switch": "",
4668
4834
  role: "radiogroup",
4669
4835
  id,
4836
+ onBlur: (e) => {
4837
+ if (interactive && !e.currentTarget.contains(e.relatedTarget)) exitEdit();
4838
+ },
4670
4839
  className: cn(
4671
4840
  "relative inline-flex rounded-lg border border-zinc-300 bg-zinc-100 dark:border-zinc-700 dark:bg-zinc-800",
4672
4841
  dirty && "ring-2 ring-amber-400/50",
@@ -4694,6 +4863,7 @@ function MultiSwitch({
4694
4863
  role: "radio",
4695
4864
  "aria-checked": isSelected,
4696
4865
  disabled: disabled || option.disabled,
4866
+ autoFocus: interactive && (selectedIndex === -1 ? index === 0 : isSelected),
4697
4867
  onClick: () => {
4698
4868
  if (linear) {
4699
4869
  const nextIndex = (selectedIndex + 1) % resolvedOptions.length;
@@ -4813,12 +4983,22 @@ var SingleDatePicker = forwardRef(
4813
4983
  }, ref) => {
4814
4984
  const singleProps = rest;
4815
4985
  const resolvedMode = useFieldMode(mode);
4986
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
4816
4987
  const [value, setValue] = useControllableState(
4817
4988
  singleProps.value,
4818
4989
  singleProps.defaultValue ?? "",
4819
4990
  singleProps.onValueChange
4820
4991
  );
4821
- const input = resolvedMode === "view" ? /* @__PURE__ */ jsx(DisplayValue, { size, className, children: formatDateValue(value, includeTime) }) : /* @__PURE__ */ jsx(
4992
+ const input = !showControl ? /* @__PURE__ */ jsx(
4993
+ DisplayValue,
4994
+ {
4995
+ size,
4996
+ className,
4997
+ interactive,
4998
+ onActivate: enterEdit,
4999
+ children: formatDateValue(value, includeTime)
5000
+ }
5001
+ ) : /* @__PURE__ */ jsx(
4822
5002
  "input",
4823
5003
  {
4824
5004
  "data-react-fancy-date-picker": "",
@@ -4832,7 +5012,11 @@ var SingleDatePicker = forwardRef(
4832
5012
  disabled,
4833
5013
  required,
4834
5014
  onChange: (e) => setValue(e.target.value),
4835
- className: cn(inputClasses, className)
5015
+ className: cn(inputClasses, className),
5016
+ autoFocus: interactive,
5017
+ onBlur: () => {
5018
+ if (interactive) exitEdit();
5019
+ }
4836
5020
  }
4837
5021
  );
4838
5022
  if (label || error || description) {
@@ -4874,43 +5058,64 @@ var RangeDatePicker = forwardRef(
4874
5058
  }, ref) => {
4875
5059
  const rangeProps = rest;
4876
5060
  const resolvedMode = useFieldMode(mode);
5061
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
4877
5062
  const [value, setValue] = useControllableState(
4878
5063
  rangeProps.value,
4879
5064
  rangeProps.defaultValue ?? ["", ""],
4880
5065
  rangeProps.onValueChange
4881
5066
  );
4882
- const input = resolvedMode === "view" ? /* @__PURE__ */ jsx(DisplayValue, { size, className, children: value[0] || value[1] ? `${formatDateValue(value[0], includeTime)} \u2013 ${formatDateValue(value[1], includeTime)}` : "" }) : /* @__PURE__ */ jsxs("div", { "data-react-fancy-date-picker": "", className: cn("flex items-center gap-2", className), children: [
4883
- /* @__PURE__ */ jsx(
4884
- "input",
4885
- {
4886
- ref,
4887
- id,
4888
- type: inputType,
4889
- name: name ? `${name}_start` : void 0,
4890
- min,
4891
- max: value[1] || max,
4892
- value: value[0],
4893
- disabled,
4894
- required,
4895
- onChange: (e) => setValue([e.target.value, value[1]]),
4896
- className: inputClasses
4897
- }
4898
- ),
4899
- /* @__PURE__ */ jsx("span", { className: "text-sm text-zinc-500 dark:text-zinc-400", children: "to" }),
4900
- /* @__PURE__ */ jsx(
4901
- "input",
4902
- {
4903
- type: inputType,
4904
- name: name ? `${name}_end` : void 0,
4905
- min: value[0] || min,
4906
- max,
4907
- value: value[1],
4908
- disabled,
4909
- onChange: (e) => setValue([value[0], e.target.value]),
4910
- className: inputClasses
4911
- }
4912
- )
4913
- ] });
5067
+ const input = !showControl ? /* @__PURE__ */ jsx(
5068
+ DisplayValue,
5069
+ {
5070
+ size,
5071
+ className,
5072
+ interactive,
5073
+ onActivate: enterEdit,
5074
+ children: value[0] || value[1] ? `${formatDateValue(value[0], includeTime)} \u2013 ${formatDateValue(value[1], includeTime)}` : ""
5075
+ }
5076
+ ) : /* @__PURE__ */ jsxs(
5077
+ "div",
5078
+ {
5079
+ "data-react-fancy-date-picker": "",
5080
+ className: cn("flex items-center gap-2", className),
5081
+ onBlur: (e) => {
5082
+ if (interactive && !e.currentTarget.contains(e.relatedTarget)) exitEdit();
5083
+ },
5084
+ children: [
5085
+ /* @__PURE__ */ jsx(
5086
+ "input",
5087
+ {
5088
+ ref,
5089
+ id,
5090
+ type: inputType,
5091
+ name: name ? `${name}_start` : void 0,
5092
+ min,
5093
+ max: value[1] || max,
5094
+ value: value[0],
5095
+ disabled,
5096
+ required,
5097
+ onChange: (e) => setValue([e.target.value, value[1]]),
5098
+ className: inputClasses,
5099
+ autoFocus: interactive
5100
+ }
5101
+ ),
5102
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-zinc-500 dark:text-zinc-400", children: "to" }),
5103
+ /* @__PURE__ */ jsx(
5104
+ "input",
5105
+ {
5106
+ type: inputType,
5107
+ name: name ? `${name}_end` : void 0,
5108
+ min: value[0] || min,
5109
+ max,
5110
+ value: value[1],
5111
+ disabled,
5112
+ onChange: (e) => setValue([value[0], e.target.value]),
5113
+ className: inputClasses
5114
+ }
5115
+ )
5116
+ ]
5117
+ }
5118
+ );
4914
5119
  if (label || error || description) {
4915
5120
  return /* @__PURE__ */ jsx(
4916
5121
  Field,
@@ -5284,6 +5489,7 @@ var ColorPicker = forwardRef(
5284
5489
  onChange
5285
5490
  );
5286
5491
  const resolvedMode = useFieldMode(mode);
5492
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
5287
5493
  const inputRef = useRef(null);
5288
5494
  const datalistId = useId();
5289
5495
  const handleChange = (e) => {
@@ -5294,14 +5500,28 @@ var ColorPicker = forwardRef(
5294
5500
  inputRef.current?.click();
5295
5501
  }
5296
5502
  };
5297
- if (resolvedMode === "view") {
5503
+ if (!showControl) {
5298
5504
  return /* @__PURE__ */ jsxs(
5299
5505
  "div",
5300
5506
  {
5301
5507
  ref,
5302
5508
  "data-react-fancy-color-picker": "",
5303
5509
  "data-mode": "view",
5304
- className: cn("inline-flex items-center gap-2", className),
5510
+ role: interactive ? "button" : void 0,
5511
+ tabIndex: interactive ? 0 : void 0,
5512
+ title: interactive ? "Click to edit" : void 0,
5513
+ onClick: interactive ? enterEdit : void 0,
5514
+ onKeyDown: interactive ? (e) => {
5515
+ if (e.key === "Enter" || e.key === " ") {
5516
+ e.preventDefault();
5517
+ enterEdit();
5518
+ }
5519
+ } : void 0,
5520
+ className: cn(
5521
+ "inline-flex items-center gap-2",
5522
+ interactive && "cursor-pointer rounded-md outline-none transition-opacity hover:opacity-80 focus-visible:ring-2 focus-visible:ring-blue-500/40",
5523
+ className
5524
+ ),
5305
5525
  children: [
5306
5526
  /* @__PURE__ */ jsx(
5307
5527
  "span",
@@ -5336,12 +5556,16 @@ var ColorPicker = forwardRef(
5336
5556
  ref,
5337
5557
  "data-react-fancy-color-picker": "",
5338
5558
  className: cn("inline-flex items-center gap-2", className),
5559
+ onBlur: (e) => {
5560
+ if (interactive && !e.currentTarget.contains(e.relatedTarget)) exitEdit();
5561
+ },
5339
5562
  children: [
5340
5563
  /* @__PURE__ */ jsx(
5341
5564
  "button",
5342
5565
  {
5343
5566
  type: "button",
5344
5567
  disabled,
5568
+ autoFocus: interactive,
5345
5569
  onClick: handleSwatchClick,
5346
5570
  className: cn(
5347
5571
  "relative shrink-0 rounded-full transition-shadow",
@@ -9085,6 +9309,7 @@ var Autocomplete = forwardRef(
9085
9309
  onChange
9086
9310
  );
9087
9311
  const resolvedMode = useFieldMode(mode);
9312
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
9088
9313
  const [query, setQuery] = useState(value);
9089
9314
  const [open, setOpen] = useState(false);
9090
9315
  const [activeIndex, setActiveIndex] = useState(-1);
@@ -9137,7 +9362,7 @@ var Autocomplete = forwardRef(
9137
9362
  if (item && !item.disabled) select(item.value);
9138
9363
  }
9139
9364
  };
9140
- if (resolvedMode === "view") {
9365
+ if (!showControl) {
9141
9366
  const matched = options.find((o) => o.value === value);
9142
9367
  return /* @__PURE__ */ jsx(
9143
9368
  "div",
@@ -9145,72 +9370,97 @@ var Autocomplete = forwardRef(
9145
9370
  "data-react-fancy-autocomplete": "",
9146
9371
  "data-mode": "view",
9147
9372
  ref: wrapperRef,
9373
+ role: interactive ? "button" : void 0,
9374
+ tabIndex: interactive ? 0 : void 0,
9375
+ title: interactive ? "Click to edit" : void 0,
9376
+ onClick: interactive ? enterEdit : void 0,
9377
+ onKeyDown: interactive ? (e) => {
9378
+ if (e.key === "Enter" || e.key === " ") {
9379
+ e.preventDefault();
9380
+ enterEdit();
9381
+ }
9382
+ } : void 0,
9148
9383
  className: cn(
9149
9384
  "text-sm text-zinc-900 dark:text-zinc-100",
9150
9385
  !value && "text-zinc-400 dark:text-zinc-500",
9386
+ interactive && "cursor-pointer rounded-md outline-none transition-colors hover:bg-zinc-100 focus-visible:ring-2 focus-visible:ring-blue-500/40 dark:hover:bg-zinc-800",
9151
9387
  className
9152
9388
  ),
9153
9389
  children: matched?.label ?? value ?? "\u2014"
9154
9390
  }
9155
9391
  );
9156
9392
  }
9157
- return /* @__PURE__ */ jsxs("div", { "data-react-fancy-autocomplete": "", ref: wrapperRef, className: cn("relative", className), children: [
9158
- /* @__PURE__ */ jsx(
9159
- "input",
9160
- {
9161
- ref: (node) => {
9162
- anchorRef.current = node;
9163
- if (typeof ref === "function") ref(node);
9164
- else if (ref) ref.current = node;
9165
- },
9166
- type: "text",
9167
- value: query,
9168
- onChange: (e) => {
9169
- setQuery(e.target.value);
9170
- setOpen(true);
9171
- setActiveIndex(-1);
9172
- },
9173
- onFocus: () => setOpen(true),
9174
- onKeyDown: handleKeyDown,
9175
- placeholder,
9176
- disabled,
9177
- role: "combobox",
9178
- "aria-expanded": open,
9179
- "aria-autocomplete": "list",
9180
- className: "w-full rounded-lg border border-zinc-200 bg-white px-3 py-2 text-sm text-zinc-900 placeholder:text-zinc-400 outline-none transition-[border-color,box-shadow] duration-150 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/40 dark:border-zinc-700 dark:bg-[#1e1e24] dark:text-zinc-100 dark:placeholder:text-zinc-500 dark:focus:border-blue-400 dark:focus:ring-blue-400/20"
9181
- }
9182
- ),
9183
- open && /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(
9184
- "div",
9185
- {
9186
- ref: listRef,
9187
- role: "listbox",
9188
- className: "fixed z-50 max-h-60 min-w-[8rem] overflow-y-auto rounded-xl border border-zinc-200 bg-white p-1 shadow-lg dark:border-zinc-700 dark:bg-zinc-900 dark:shadow-zinc-950/50 fancy-scale-in",
9189
- style: {
9190
- left: position.x,
9191
- top: position.y,
9192
- width: anchorRef.current?.offsetWidth
9193
- },
9194
- children: loading ? /* @__PURE__ */ jsx("div", { className: "px-3 py-2 text-sm text-zinc-400", children: "Loading..." }) : filtered.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-3 py-2 text-sm text-zinc-400", children: emptyMessage }) : filtered.map((option, i) => /* @__PURE__ */ jsx(
9195
- "button",
9393
+ return /* @__PURE__ */ jsxs(
9394
+ "div",
9395
+ {
9396
+ "data-react-fancy-autocomplete": "",
9397
+ ref: wrapperRef,
9398
+ className: cn("relative", className),
9399
+ onBlur: (e) => {
9400
+ if (interactive && !open && !e.currentTarget.contains(e.relatedTarget)) {
9401
+ exitEdit();
9402
+ }
9403
+ },
9404
+ children: [
9405
+ /* @__PURE__ */ jsx(
9406
+ "input",
9196
9407
  {
9197
- type: "button",
9198
- role: "option",
9199
- "aria-selected": i === activeIndex,
9200
- disabled: option.disabled,
9201
- onClick: () => select(option.value),
9202
- className: cn(
9203
- "flex w-full items-center rounded-lg px-3 py-2 text-left text-sm text-zinc-700 transition-colors dark:text-zinc-200",
9204
- i === activeIndex ? "bg-zinc-100 dark:bg-zinc-800" : "hover:bg-zinc-50 dark:hover:bg-zinc-800/50",
9205
- option.disabled && "cursor-not-allowed opacity-50"
9206
- ),
9207
- children: /* @__PURE__ */ jsx(HighlightMatch, { text: option.label, query })
9208
- },
9209
- option.value
9210
- ))
9211
- }
9212
- ) })
9213
- ] });
9408
+ ref: (node) => {
9409
+ anchorRef.current = node;
9410
+ if (typeof ref === "function") ref(node);
9411
+ else if (ref) ref.current = node;
9412
+ },
9413
+ type: "text",
9414
+ value: query,
9415
+ onChange: (e) => {
9416
+ setQuery(e.target.value);
9417
+ setOpen(true);
9418
+ setActiveIndex(-1);
9419
+ },
9420
+ onFocus: () => setOpen(true),
9421
+ onKeyDown: handleKeyDown,
9422
+ placeholder,
9423
+ disabled,
9424
+ autoFocus: interactive,
9425
+ role: "combobox",
9426
+ "aria-expanded": open,
9427
+ "aria-autocomplete": "list",
9428
+ className: "w-full rounded-lg border border-zinc-200 bg-white px-3 py-2 text-sm text-zinc-900 placeholder:text-zinc-400 outline-none transition-[border-color,box-shadow] duration-150 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/40 dark:border-zinc-700 dark:bg-[#1e1e24] dark:text-zinc-100 dark:placeholder:text-zinc-500 dark:focus:border-blue-400 dark:focus:ring-blue-400/20"
9429
+ }
9430
+ ),
9431
+ open && /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(
9432
+ "div",
9433
+ {
9434
+ ref: listRef,
9435
+ role: "listbox",
9436
+ className: "fixed z-50 max-h-60 min-w-[8rem] overflow-y-auto rounded-xl border border-zinc-200 bg-white p-1 shadow-lg dark:border-zinc-700 dark:bg-zinc-900 dark:shadow-zinc-950/50 fancy-scale-in",
9437
+ style: {
9438
+ left: position.x,
9439
+ top: position.y,
9440
+ width: anchorRef.current?.offsetWidth
9441
+ },
9442
+ children: loading ? /* @__PURE__ */ jsx("div", { className: "px-3 py-2 text-sm text-zinc-400", children: "Loading..." }) : filtered.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-3 py-2 text-sm text-zinc-400", children: emptyMessage }) : filtered.map((option, i) => /* @__PURE__ */ jsx(
9443
+ "button",
9444
+ {
9445
+ type: "button",
9446
+ role: "option",
9447
+ "aria-selected": i === activeIndex,
9448
+ disabled: option.disabled,
9449
+ onClick: () => select(option.value),
9450
+ className: cn(
9451
+ "flex w-full items-center rounded-lg px-3 py-2 text-left text-sm text-zinc-700 transition-colors dark:text-zinc-200",
9452
+ i === activeIndex ? "bg-zinc-100 dark:bg-zinc-800" : "hover:bg-zinc-50 dark:hover:bg-zinc-800/50",
9453
+ option.disabled && "cursor-not-allowed opacity-50"
9454
+ ),
9455
+ children: /* @__PURE__ */ jsx(HighlightMatch, { text: option.label, query })
9456
+ },
9457
+ option.value
9458
+ ))
9459
+ }
9460
+ ) })
9461
+ ]
9462
+ }
9463
+ );
9214
9464
  }
9215
9465
  );
9216
9466
  function HighlightMatch({ text, query }) {
@@ -9335,6 +9585,7 @@ var OtpInput = forwardRef(
9335
9585
  onChange
9336
9586
  );
9337
9587
  const resolvedMode = useFieldMode(mode);
9588
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
9338
9589
  const inputsRef = useRef([]);
9339
9590
  const focusInput = useCallback(
9340
9591
  (index) => {
@@ -9388,42 +9639,64 @@ var OtpInput = forwardRef(
9388
9639
  },
9389
9640
  [length, setValue, focusInput]
9390
9641
  );
9391
- if (resolvedMode === "view") {
9642
+ if (!showControl) {
9392
9643
  return /* @__PURE__ */ jsx(
9393
9644
  "div",
9394
9645
  {
9395
9646
  "data-react-fancy-otp-input": "",
9396
9647
  "data-mode": "view",
9397
9648
  ref,
9649
+ role: interactive ? "button" : void 0,
9650
+ tabIndex: interactive ? 0 : void 0,
9651
+ title: interactive ? "Click to edit" : void 0,
9652
+ onClick: interactive ? enterEdit : void 0,
9653
+ onKeyDown: interactive ? (e) => {
9654
+ if (e.key === "Enter" || e.key === " ") {
9655
+ e.preventDefault();
9656
+ enterEdit();
9657
+ }
9658
+ } : void 0,
9398
9659
  className: cn(
9399
9660
  "font-mono text-lg tracking-[0.4em] text-zinc-900 dark:text-zinc-100",
9661
+ interactive && "cursor-pointer rounded-md outline-none transition-opacity hover:opacity-80 focus-visible:ring-2 focus-visible:ring-blue-500/40",
9400
9662
  className
9401
9663
  ),
9402
9664
  children: value || "\u2014"
9403
9665
  }
9404
9666
  );
9405
9667
  }
9406
- return /* @__PURE__ */ jsx("div", { "data-react-fancy-otp-input": "", ref, className: cn("flex gap-2", className), children: Array.from({ length }, (_, i) => /* @__PURE__ */ jsx(
9407
- "input",
9668
+ return /* @__PURE__ */ jsx(
9669
+ "div",
9408
9670
  {
9409
- ref: (el) => {
9410
- inputsRef.current[i] = el;
9671
+ "data-react-fancy-otp-input": "",
9672
+ ref,
9673
+ className: cn("flex gap-2", className),
9674
+ onBlur: (e) => {
9675
+ if (interactive && !e.currentTarget.contains(e.relatedTarget)) exitEdit();
9411
9676
  },
9412
- type: "text",
9413
- inputMode: "numeric",
9414
- maxLength: 1,
9415
- value: value[i] ?? "",
9416
- onChange: (e) => handleChange(i, e.target.value),
9417
- onKeyDown: (e) => handleKeyDown(i, e),
9418
- onPaste: handlePaste,
9419
- onFocus: (e) => e.target.select(),
9420
- disabled,
9421
- autoFocus: autoFocus && i === 0,
9422
- className: "h-12 w-10 rounded-lg border border-zinc-200 bg-white text-center text-lg font-medium text-zinc-900 outline-none transition-[border-color,box-shadow] duration-150 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/40 dark:border-zinc-700 dark:bg-[#1e1e24] dark:text-zinc-100 dark:focus:border-blue-400 dark:focus:ring-blue-400/20",
9423
- "aria-label": `Digit ${i + 1}`
9424
- },
9425
- i
9426
- )) });
9677
+ children: Array.from({ length }, (_, i) => /* @__PURE__ */ jsx(
9678
+ "input",
9679
+ {
9680
+ ref: (el) => {
9681
+ inputsRef.current[i] = el;
9682
+ },
9683
+ type: "text",
9684
+ inputMode: "numeric",
9685
+ maxLength: 1,
9686
+ value: value[i] ?? "",
9687
+ onChange: (e) => handleChange(i, e.target.value),
9688
+ onKeyDown: (e) => handleKeyDown(i, e),
9689
+ onPaste: handlePaste,
9690
+ onFocus: (e) => e.target.select(),
9691
+ disabled,
9692
+ autoFocus: (autoFocus || interactive) && i === 0,
9693
+ className: "h-12 w-10 rounded-lg border border-zinc-200 bg-white text-center text-lg font-medium text-zinc-900 outline-none transition-[border-color,box-shadow] duration-150 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/40 dark:border-zinc-700 dark:bg-[#1e1e24] dark:text-zinc-100 dark:focus:border-blue-400 dark:focus:ring-blue-400/20",
9694
+ "aria-label": `Digit ${i + 1}`
9695
+ },
9696
+ i
9697
+ ))
9698
+ }
9699
+ );
9427
9700
  }
9428
9701
  );
9429
9702
  OtpInput.displayName = "OtpInput";
@@ -9626,10 +9899,11 @@ var TimePicker = forwardRef(
9626
9899
  onChange
9627
9900
  );
9628
9901
  const resolvedMode = useFieldMode(mode);
9902
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
9629
9903
  const { hours: h24, minutes } = parseTime(value);
9630
9904
  const isPM = h24 >= 12;
9631
9905
  const displayHour = format === "12h" ? h24 % 12 || 12 : h24;
9632
- if (resolvedMode === "view") {
9906
+ if (!showControl) {
9633
9907
  const formatted = format === "12h" ? `${pad(displayHour)}:${pad(minutes)} ${isPM ? "PM" : "AM"}` : `${pad(displayHour)}:${pad(minutes)}`;
9634
9908
  return /* @__PURE__ */ jsx(
9635
9909
  "div",
@@ -9637,8 +9911,19 @@ var TimePicker = forwardRef(
9637
9911
  "data-react-fancy-time-picker": "",
9638
9912
  "data-mode": "view",
9639
9913
  ref,
9914
+ role: interactive ? "button" : void 0,
9915
+ tabIndex: interactive ? 0 : void 0,
9916
+ title: interactive ? "Click to edit" : void 0,
9917
+ onClick: interactive ? enterEdit : void 0,
9918
+ onKeyDown: interactive ? (e) => {
9919
+ if (e.key === "Enter" || e.key === " ") {
9920
+ e.preventDefault();
9921
+ enterEdit();
9922
+ }
9923
+ } : void 0,
9640
9924
  className: cn(
9641
9925
  "text-sm font-medium tabular-nums text-zinc-900 dark:text-zinc-100",
9926
+ interactive && "cursor-pointer rounded-md outline-none transition-colors hover:bg-zinc-100 focus-visible:ring-2 focus-visible:ring-blue-500/40 dark:hover:bg-zinc-800",
9642
9927
  className
9643
9928
  ),
9644
9929
  children: formatted
@@ -9675,6 +9960,9 @@ var TimePicker = forwardRef(
9675
9960
  {
9676
9961
  "data-react-fancy-time-picker": "",
9677
9962
  ref,
9963
+ onBlur: (e) => {
9964
+ if (interactive && !e.currentTarget.contains(e.relatedTarget)) exitEdit();
9965
+ },
9678
9966
  className: cn(
9679
9967
  "inline-flex items-center gap-1 rounded-lg border border-zinc-200 bg-white p-2 dark:border-zinc-700 dark:bg-zinc-900",
9680
9968
  disabled && "opacity-50",
@@ -9682,7 +9970,7 @@ var TimePicker = forwardRef(
9682
9970
  ),
9683
9971
  children: [
9684
9972
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center", children: [
9685
- /* @__PURE__ */ jsx("button", { type: "button", onClick: () => changeHour(1), disabled, className: spinBtnClass, "aria-label": "Increase hour", children: /* @__PURE__ */ jsx(ChevronUp, { size: 14 }) }),
9973
+ /* @__PURE__ */ jsx("button", { type: "button", autoFocus: interactive, onClick: () => changeHour(1), disabled, className: spinBtnClass, "aria-label": "Increase hour", children: /* @__PURE__ */ jsx(ChevronUp, { size: 14 }) }),
9686
9974
  /* @__PURE__ */ jsx("div", { className: displayClass, children: pad(displayHour) }),
9687
9975
  /* @__PURE__ */ jsx("button", { type: "button", onClick: () => changeHour(-1), disabled, className: spinBtnClass, "aria-label": "Decrease hour", children: /* @__PURE__ */ jsx(ChevronDown, { size: 14 }) })
9688
9976
  ] }),