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