@particle-academy/react-fancy 4.5.0 → 4.7.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
@@ -2104,6 +2104,17 @@ function applyTone(char, tone) {
2104
2104
  // src/components/Icon/icon-config.ts
2105
2105
  var registry = /* @__PURE__ */ new Map();
2106
2106
  var defaultSetName = "lucide";
2107
+ var addenda = [];
2108
+ function registerIconAddendum(set) {
2109
+ if (!addenda.includes(set)) addenda.push(set);
2110
+ }
2111
+ function resolveFromAddenda(name) {
2112
+ for (const set of addenda) {
2113
+ const hit = set.resolve(name);
2114
+ if (hit) return hit;
2115
+ }
2116
+ return null;
2117
+ }
2107
2118
  function kebabToPascal(str) {
2108
2119
  return str.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
2109
2120
  }
@@ -2167,6 +2178,8 @@ function resolveIcon(name, setName) {
2167
2178
  const set = registry.get(resolvedSet);
2168
2179
  const registered = set ? set.resolve(name) : null;
2169
2180
  if (registered) return registered;
2181
+ const fromAddenda = resolveFromAddenda(name);
2182
+ if (fromAddenda) return fromAddenda;
2170
2183
  if (resolvedSet === "lucide") return resolveFromLucide(name);
2171
2184
  return null;
2172
2185
  }
@@ -3091,19 +3104,33 @@ function DisplayValue({
3091
3104
  leading,
3092
3105
  trailing,
3093
3106
  empty = "\u2014",
3107
+ interactive,
3108
+ onActivate,
3094
3109
  className
3095
3110
  }) {
3096
3111
  const empties = isEmpty(children);
3112
+ const onKeyDown = interactive ? (e) => {
3113
+ if (e.key === "Enter" || e.key === " ") {
3114
+ e.preventDefault();
3115
+ onActivate?.();
3116
+ }
3117
+ } : void 0;
3097
3118
  return /* @__PURE__ */ jsxs(
3098
3119
  "div",
3099
3120
  {
3100
3121
  "data-react-fancy-display": "",
3101
3122
  "data-mode": "view",
3123
+ role: interactive ? "button" : void 0,
3124
+ tabIndex: interactive ? 0 : void 0,
3125
+ title: interactive ? "Click to edit" : void 0,
3126
+ onClick: interactive ? onActivate : void 0,
3127
+ onKeyDown,
3102
3128
  className: cn(
3103
3129
  "flex w-full items-center gap-2 text-zinc-900 dark:text-zinc-100",
3104
3130
  inputSizeClasses[size],
3105
3131
  // Strip the editable box look — keep only the size/padding rhythm.
3106
3132
  "border-0 bg-transparent px-0",
3133
+ 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
3134
  empties && "text-zinc-400 dark:text-zinc-500",
3108
3135
  className
3109
3136
  ),
@@ -3115,6 +3142,16 @@ function DisplayValue({
3115
3142
  }
3116
3143
  );
3117
3144
  }
3145
+ function useInlineEdit(mode, disabled) {
3146
+ const [editing, setEditing] = useState(false);
3147
+ const interactive = mode === "view" && !disabled;
3148
+ const showControl = mode !== "view" || interactive && editing;
3149
+ const enterEdit = useCallback(() => {
3150
+ if (interactive) setEditing(true);
3151
+ }, [interactive]);
3152
+ const exitEdit = useCallback(() => setEditing(false), []);
3153
+ return { showControl, interactive, enterEdit, exitEdit };
3154
+ }
3118
3155
  var Input = forwardRef(
3119
3156
  ({
3120
3157
  type = "text",
@@ -3141,7 +3178,18 @@ var Input = forwardRef(
3141
3178
  const autoId = useId();
3142
3179
  const inputId = id ?? autoId;
3143
3180
  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(
3181
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
3182
+ const input = !showControl ? /* @__PURE__ */ jsx(
3183
+ DisplayValue,
3184
+ {
3185
+ size,
3186
+ interactive,
3187
+ onActivate: enterEdit,
3188
+ leading: leading ?? prefix,
3189
+ trailing: trailing ?? suffix,
3190
+ children: type === "password" ? props.value ? "\u2022\u2022\u2022\u2022\u2022\u2022" : "" : props.value
3191
+ }
3192
+ ) : /* @__PURE__ */ jsx(
3145
3193
  InputWrapper,
3146
3194
  {
3147
3195
  prefix,
@@ -3173,7 +3221,12 @@ var Input = forwardRef(
3173
3221
  onChange?.(e);
3174
3222
  onValueChange?.(e.target.value);
3175
3223
  },
3176
- ...props
3224
+ ...props,
3225
+ autoFocus: interactive || props.autoFocus,
3226
+ onBlur: (e) => {
3227
+ props.onBlur?.(e);
3228
+ if (interactive) exitEdit();
3229
+ }
3177
3230
  }
3178
3231
  ),
3179
3232
  trailing && /* @__PURE__ */ jsx("span", { className: "pointer-events-none absolute right-3 text-zinc-400 dark:text-zinc-500", children: trailing })
@@ -3227,6 +3280,7 @@ var Textarea = forwardRef(
3227
3280
  const textareaId = id ?? autoId;
3228
3281
  const internalRef = useRef(null);
3229
3282
  const resolvedMode = useFieldMode(mode);
3283
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
3230
3284
  useEffect(() => {
3231
3285
  const el = internalRef.current;
3232
3286
  if (!autoResize || !el) return;
@@ -3236,7 +3290,16 @@ var Textarea = forwardRef(
3236
3290
  const maxHeight = maxRows ? maxRows * lineHeight : Infinity;
3237
3291
  el.style.height = `${Math.min(Math.max(el.scrollHeight, minHeight), maxHeight)}px`;
3238
3292
  }, [autoResize, minRows, maxRows, value, defaultValue]);
3239
- const textarea = resolvedMode === "view" ? /* @__PURE__ */ jsx(DisplayValue, { size, className: "whitespace-pre-wrap", children: value ?? defaultValue }) : /* @__PURE__ */ jsx(
3293
+ const textarea = !showControl ? /* @__PURE__ */ jsx(
3294
+ DisplayValue,
3295
+ {
3296
+ size,
3297
+ className: "whitespace-pre-wrap",
3298
+ interactive,
3299
+ onActivate: enterEdit,
3300
+ children: value ?? defaultValue
3301
+ }
3302
+ ) : /* @__PURE__ */ jsx(
3240
3303
  InputWrapper,
3241
3304
  {
3242
3305
  prefix,
@@ -3275,7 +3338,12 @@ var Textarea = forwardRef(
3275
3338
  onChange?.(e);
3276
3339
  onValueChange?.(e.target.value);
3277
3340
  },
3278
- ...props
3341
+ ...props,
3342
+ autoFocus: interactive || props.autoFocus,
3343
+ onBlur: (e) => {
3344
+ props.onBlur?.(e);
3345
+ if (interactive) exitEdit();
3346
+ }
3279
3347
  }
3280
3348
  )
3281
3349
  }
@@ -3518,10 +3586,21 @@ var NativeSelect = forwardRef(
3518
3586
  const autoId = useId();
3519
3587
  const selectId = id ?? autoId;
3520
3588
  const resolvedMode = useFieldMode(mode);
3521
- if (resolvedMode === "view") {
3589
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
3590
+ if (!showControl) {
3522
3591
  const current = value ?? defaultValue;
3523
3592
  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 });
3593
+ const display = /* @__PURE__ */ jsx(
3594
+ DisplayValue,
3595
+ {
3596
+ size,
3597
+ leading: prefix,
3598
+ trailing: suffix,
3599
+ interactive,
3600
+ onActivate: enterEdit,
3601
+ children: opt?.label ?? current
3602
+ }
3603
+ );
3525
3604
  if (label || error || description) {
3526
3605
  return /* @__PURE__ */ jsx(
3527
3606
  Field,
@@ -3572,6 +3651,11 @@ var NativeSelect = forwardRef(
3572
3651
  },
3573
3652
  ...props,
3574
3653
  ...isControlled ? { value } : { defaultValue: resolvedDefault },
3654
+ autoFocus: interactive || props.autoFocus,
3655
+ onBlur: (e) => {
3656
+ props.onBlur?.(e);
3657
+ if (interactive) exitEdit();
3658
+ },
3575
3659
  children: [
3576
3660
  placeholder && /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: placeholder }),
3577
3661
  list.map(
@@ -3633,6 +3717,7 @@ var ListboxSelect = forwardRef(
3633
3717
  const autoId = useId();
3634
3718
  const selectId = id ?? autoId;
3635
3719
  const resolvedMode = useFieldMode(mode);
3720
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
3636
3721
  const textInputEnabled = searchable || creatable;
3637
3722
  const [open, setOpen] = useState(false);
3638
3723
  const [search2, setSearch] = useState("");
@@ -3759,11 +3844,11 @@ var ListboxSelect = forwardRef(
3759
3844
  }
3760
3845
  }
3761
3846
  };
3762
- if (resolvedMode === "view") {
3847
+ if (!showControl) {
3763
3848
  const labels = multiple ? currentMulti.map(
3764
3849
  (v) => resolvedOptions.find((o) => o.value === v)?.label ?? v
3765
3850
  ) : currentSingle ? [resolvedOptions.find((o) => o.value === currentSingle)?.label ?? currentSingle] : [];
3766
- const display = /* @__PURE__ */ jsx(DisplayValue, { size, children: labels.join(", ") });
3851
+ const display = /* @__PURE__ */ jsx(DisplayValue, { size, interactive, onActivate: enterEdit, children: labels.join(", ") });
3767
3852
  if (label || error || description) {
3768
3853
  return /* @__PURE__ */ jsx(
3769
3854
  Field,
@@ -3787,6 +3872,7 @@ var ListboxSelect = forwardRef(
3787
3872
  type: "button",
3788
3873
  id: selectId,
3789
3874
  disabled,
3875
+ autoFocus: interactive,
3790
3876
  onClick: () => setOpen((o) => !o),
3791
3877
  onKeyDown: handleKeyDown,
3792
3878
  role: "combobox",
@@ -3908,10 +3994,21 @@ var ListboxSelect = forwardRef(
3908
3994
  ]
3909
3995
  }
3910
3996
  ) });
3911
- const content = /* @__PURE__ */ jsxs("div", { className: "relative", children: [
3912
- trigger,
3913
- dropdown
3914
- ] });
3997
+ const content = /* @__PURE__ */ jsxs(
3998
+ "div",
3999
+ {
4000
+ className: "relative",
4001
+ onBlur: (e) => {
4002
+ if (interactive && !open && !e.currentTarget.contains(e.relatedTarget)) {
4003
+ exitEdit();
4004
+ }
4005
+ },
4006
+ children: [
4007
+ trigger,
4008
+ dropdown
4009
+ ]
4010
+ }
4011
+ );
3915
4012
  if (label || error || description) {
3916
4013
  return /* @__PURE__ */ jsx(
3917
4014
  Field,
@@ -3962,6 +4059,7 @@ var Checkbox = forwardRef(
3962
4059
  const checkboxId = id ?? autoId;
3963
4060
  const internalRef = useRef(null);
3964
4061
  const resolvedMode = useFieldMode(mode);
4062
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
3965
4063
  const [checked, setChecked] = useControllableState(
3966
4064
  controlledChecked,
3967
4065
  defaultChecked,
@@ -3982,16 +4080,27 @@ var Checkbox = forwardRef(
3982
4080
  }[size];
3983
4081
  const glyph = indeterminate ? "\u2014" : checked ? "\u2713" : "\u2715";
3984
4082
  return /* @__PURE__ */ jsxs("div", { "data-react-fancy-checkbox": "", className: cn("flex items-start gap-2", className), children: [
3985
- resolvedMode === "view" ? /* @__PURE__ */ jsx(
4083
+ !showControl ? /* @__PURE__ */ jsx(
3986
4084
  "span",
3987
4085
  {
3988
4086
  "data-react-fancy-display": "",
3989
4087
  "data-mode": "view",
4088
+ role: interactive ? "button" : void 0,
4089
+ tabIndex: interactive ? 0 : void 0,
4090
+ title: interactive ? "Click to edit" : void 0,
4091
+ onClick: interactive ? enterEdit : void 0,
4092
+ onKeyDown: interactive ? (e) => {
4093
+ if (e.key === "Enter" || e.key === " ") {
4094
+ e.preventDefault();
4095
+ enterEdit();
4096
+ }
4097
+ } : void 0,
3990
4098
  className: cn(
3991
4099
  "flex shrink-0 items-center justify-center text-zinc-700 dark:text-zinc-200",
3992
- sizeClasses6
4100
+ sizeClasses6,
4101
+ 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
4102
  ),
3994
- "aria-hidden": "true",
4103
+ "aria-hidden": interactive ? void 0 : "true",
3995
4104
  children: glyph
3996
4105
  }
3997
4106
  ) : /* @__PURE__ */ jsx("div", { className: "relative flex items-center", children: /* @__PURE__ */ jsx(
@@ -4011,6 +4120,10 @@ var Checkbox = forwardRef(
4011
4120
  disabled,
4012
4121
  required,
4013
4122
  checked,
4123
+ autoFocus: interactive,
4124
+ onBlur: () => {
4125
+ if (interactive) exitEdit();
4126
+ },
4014
4127
  onChange: (e) => setChecked(e.target.checked),
4015
4128
  className: cn(
4016
4129
  sizeClasses6,
@@ -4061,6 +4174,7 @@ function CheckboxGroup({
4061
4174
  }) {
4062
4175
  const groupId = useId();
4063
4176
  const resolvedMode = useFieldMode(mode);
4177
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
4064
4178
  const [value, setValue] = useControllableState(
4065
4179
  controlledValue,
4066
4180
  defaultValue,
@@ -4077,10 +4191,13 @@ function CheckboxGroup({
4077
4191
  lg: "h-5 w-5",
4078
4192
  xl: "h-6 w-6"
4079
4193
  }[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(
4194
+ 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
4195
  "div",
4082
4196
  {
4083
4197
  "data-react-fancy-checkbox-group": "",
4198
+ onBlur: (e) => {
4199
+ if (interactive && !e.currentTarget.contains(e.relatedTarget)) exitEdit();
4200
+ },
4084
4201
  className: cn(
4085
4202
  "flex gap-3",
4086
4203
  orientation === "vertical" ? "flex-col" : "flex-row flex-wrap",
@@ -4100,6 +4217,7 @@ function CheckboxGroup({
4100
4217
  value: String(resolved.value),
4101
4218
  checked: isChecked,
4102
4219
  disabled: disabled || resolved.disabled,
4220
+ autoFocus: interactive && index === 0,
4103
4221
  onChange: () => handleToggle(resolved.value),
4104
4222
  className: cn(
4105
4223
  sizeClasses6,
@@ -4163,6 +4281,7 @@ function RadioGroup({
4163
4281
  const groupId = useId();
4164
4282
  const radioName = name ?? groupId;
4165
4283
  const resolvedMode = useFieldMode(mode);
4284
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
4166
4285
  const [value, setValue] = useControllableState(
4167
4286
  controlledValue,
4168
4287
  defaultValue,
@@ -4175,11 +4294,15 @@ function RadioGroup({
4175
4294
  lg: "h-5 w-5",
4176
4295
  xl: "h-6 w-6"
4177
4296
  }[size];
4178
- const content = resolvedMode === "view" ? /* @__PURE__ */ jsx(DisplayValue, { size, children: list.map(resolveOption).find((o) => o.value === value)?.label }) : /* @__PURE__ */ jsx(
4297
+ const selectedIndex = list.map(resolveOption).findIndex((o) => o.value === value);
4298
+ const content = !showControl ? /* @__PURE__ */ jsx(DisplayValue, { size, interactive, onActivate: enterEdit, children: list.map(resolveOption).find((o) => o.value === value)?.label }) : /* @__PURE__ */ jsx(
4179
4299
  "div",
4180
4300
  {
4181
4301
  "data-react-fancy-radio-group": "",
4182
4302
  role: "radiogroup",
4303
+ onBlur: (e) => {
4304
+ if (interactive && !e.currentTarget.contains(e.relatedTarget)) exitEdit();
4305
+ },
4183
4306
  className: cn(
4184
4307
  "flex gap-3",
4185
4308
  orientation === "vertical" ? "flex-col" : "flex-row flex-wrap",
@@ -4189,6 +4312,7 @@ function RadioGroup({
4189
4312
  const resolved = resolveOption(option);
4190
4313
  const optionId = `${groupId}-${index}`;
4191
4314
  const isSelected = value === resolved.value;
4315
+ const shouldAutoFocus = interactive && (selectedIndex === -1 ? index === 0 : isSelected);
4192
4316
  return /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
4193
4317
  /* @__PURE__ */ jsx(
4194
4318
  "input",
@@ -4199,6 +4323,7 @@ function RadioGroup({
4199
4323
  value: String(resolved.value),
4200
4324
  checked: isSelected,
4201
4325
  disabled: disabled || resolved.disabled,
4326
+ autoFocus: shouldAutoFocus,
4202
4327
  onChange: () => setValue(resolved.value),
4203
4328
  className: cn(
4204
4329
  sizeClasses6,
@@ -4289,6 +4414,7 @@ var Switch = forwardRef(
4289
4414
  const autoId = useId();
4290
4415
  const switchId = id ?? autoId;
4291
4416
  const resolvedMode = useFieldMode(mode);
4417
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
4292
4418
  const [checked, setChecked] = useControllableState(
4293
4419
  controlledChecked,
4294
4420
  defaultChecked,
@@ -4316,12 +4442,25 @@ var Switch = forwardRef(
4316
4442
  xl: "translate-x-6"
4317
4443
  }[size];
4318
4444
  return /* @__PURE__ */ jsxs("div", { "data-react-fancy-switch": "", className: cn("flex items-start gap-2", className), children: [
4319
- resolvedMode === "view" ? /* @__PURE__ */ jsx(
4445
+ !showControl ? /* @__PURE__ */ jsx(
4320
4446
  "span",
4321
4447
  {
4322
4448
  "data-react-fancy-display": "",
4323
4449
  "data-mode": "view",
4324
- className: "text-sm font-medium text-zinc-700 dark:text-zinc-200",
4450
+ role: interactive ? "button" : void 0,
4451
+ tabIndex: interactive ? 0 : void 0,
4452
+ title: interactive ? "Click to edit" : void 0,
4453
+ onClick: interactive ? enterEdit : void 0,
4454
+ onKeyDown: interactive ? (e) => {
4455
+ if (e.key === "Enter" || e.key === " ") {
4456
+ e.preventDefault();
4457
+ enterEdit();
4458
+ }
4459
+ } : void 0,
4460
+ className: cn(
4461
+ "text-sm font-medium text-zinc-700 dark:text-zinc-200",
4462
+ 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"
4463
+ ),
4325
4464
  children: checked ? "On" : "Off"
4326
4465
  }
4327
4466
  ) : /* @__PURE__ */ jsx(
@@ -4333,6 +4472,10 @@ var Switch = forwardRef(
4333
4472
  role: "switch",
4334
4473
  "aria-checked": checked,
4335
4474
  disabled,
4475
+ autoFocus: interactive,
4476
+ onBlur: () => {
4477
+ if (interactive) exitEdit();
4478
+ },
4336
4479
  onClick: () => setChecked(!checked),
4337
4480
  className: cn(
4338
4481
  "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 +4586,22 @@ var SingleSlider = forwardRef(
4443
4586
  }, ref) => {
4444
4587
  const singleProps = rest;
4445
4588
  const resolvedMode = useFieldMode(mode);
4589
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
4446
4590
  const [value, setValue] = useControllableState(
4447
4591
  singleProps.value,
4448
4592
  singleProps.defaultValue ?? min,
4449
4593
  singleProps.onValueChange
4450
4594
  );
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: [
4595
+ const slider = !showControl ? /* @__PURE__ */ jsx(
4596
+ DisplayValue,
4597
+ {
4598
+ size,
4599
+ className,
4600
+ interactive,
4601
+ onActivate: enterEdit,
4602
+ children: `${prefix ?? ""}${value}${suffix ?? ""}`
4603
+ }
4604
+ ) : /* @__PURE__ */ jsxs("div", { "data-react-fancy-slider": "", className: cn("flex flex-col gap-1", className), children: [
4452
4605
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
4453
4606
  /* @__PURE__ */ jsx(
4454
4607
  "input",
@@ -4462,6 +4615,10 @@ var SingleSlider = forwardRef(
4462
4615
  step,
4463
4616
  value,
4464
4617
  disabled,
4618
+ autoFocus: interactive,
4619
+ onBlur: () => {
4620
+ if (interactive) exitEdit();
4621
+ },
4465
4622
  onChange: (e) => setValue(Number(e.target.value)),
4466
4623
  className: cn(
4467
4624
  "w-full cursor-pointer accent-blue-600 disabled:cursor-not-allowed disabled:opacity-50",
@@ -4515,6 +4672,7 @@ var RangeSlider = forwardRef(
4515
4672
  }, ref) => {
4516
4673
  const rangeProps = rest;
4517
4674
  const resolvedMode = useFieldMode(mode);
4675
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
4518
4676
  const [value, setValue] = useControllableState(
4519
4677
  rangeProps.value,
4520
4678
  rangeProps.defaultValue ?? [min, max],
@@ -4528,61 +4686,81 @@ var RangeSlider = forwardRef(
4528
4686
  };
4529
4687
  const leftPercent = (value[0] - min) / (max - min) * 100;
4530
4688
  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
- ] });
4689
+ const slider = !showControl ? /* @__PURE__ */ jsx(
4690
+ DisplayValue,
4691
+ {
4692
+ size,
4693
+ className,
4694
+ interactive,
4695
+ onActivate: enterEdit,
4696
+ children: `${prefix ?? ""}${value[0]}${suffix ?? ""}\u2013${prefix ?? ""}${value[1]}${suffix ?? ""}`
4697
+ }
4698
+ ) : /* @__PURE__ */ jsxs(
4699
+ "div",
4700
+ {
4701
+ "data-react-fancy-slider": "",
4702
+ className: cn("flex flex-col gap-1", className),
4703
+ onBlur: (e) => {
4704
+ if (interactive && !e.currentTarget.contains(e.relatedTarget)) exitEdit();
4705
+ },
4706
+ children: [
4707
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
4708
+ /* @__PURE__ */ jsxs("div", { className: "relative w-full", children: [
4709
+ /* @__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" }),
4710
+ /* @__PURE__ */ jsx(
4711
+ "div",
4712
+ {
4713
+ className: "pointer-events-none absolute top-1/2 h-1.5 -translate-y-1/2 rounded-full bg-blue-500",
4714
+ style: { left: `${leftPercent}%`, width: `${rightPercent - leftPercent}%` }
4715
+ }
4716
+ ),
4717
+ /* @__PURE__ */ jsx(
4718
+ "input",
4719
+ {
4720
+ ref,
4721
+ type: "range",
4722
+ min,
4723
+ max,
4724
+ step,
4725
+ value: value[0],
4726
+ disabled,
4727
+ autoFocus: interactive,
4728
+ onChange: (e) => handleMin(Number(e.target.value)),
4729
+ className: cn(
4730
+ "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",
4731
+ dirtyRingClasses(dirty)
4732
+ )
4733
+ }
4734
+ ),
4735
+ /* @__PURE__ */ jsx(
4736
+ "input",
4737
+ {
4738
+ type: "range",
4739
+ min,
4740
+ max,
4741
+ step,
4742
+ value: value[1],
4743
+ disabled,
4744
+ onChange: (e) => handleMax(Number(e.target.value)),
4745
+ 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"
4746
+ }
4747
+ ),
4748
+ /* @__PURE__ */ jsx("div", { className: "h-6" })
4749
+ ] }),
4750
+ 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: [
4751
+ prefix,
4752
+ value[0],
4753
+ suffix,
4754
+ "\u2013",
4755
+ prefix,
4756
+ value[1],
4757
+ suffix
4758
+ ] })
4759
+ ] }),
4760
+ marks && marks.length > 0 && /* @__PURE__ */ jsx(SliderMarks, { marks, min, max })
4761
+ ]
4762
+ }
4763
+ );
4586
4764
  if (label || error || description) {
4587
4765
  return /* @__PURE__ */ jsx(Field, { label, description, error, required, htmlFor: id, size, children: slider });
4588
4766
  }
@@ -4636,6 +4814,7 @@ function MultiSwitch({
4636
4814
  const resolvedOptions = list.map(resolveOption);
4637
4815
  const fallback = defaultValue ?? resolvedOptions[0]?.value;
4638
4816
  const resolvedMode = useFieldMode(mode);
4817
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
4639
4818
  const [value, setValue] = useControllableState(controlledValue, fallback, onValueChange);
4640
4819
  const containerRef = useRef(null);
4641
4820
  const itemRefs = useRef([]);
@@ -4660,13 +4839,16 @@ function MultiSwitch({
4660
4839
  useEffect(() => {
4661
4840
  updateIndicator();
4662
4841
  }, [updateIndicator]);
4663
- const control = resolvedMode === "view" ? /* @__PURE__ */ jsx(DisplayValue, { size, children: resolvedOptions.find((o) => o.value === value)?.label }) : /* @__PURE__ */ jsxs(
4842
+ const control = !showControl ? /* @__PURE__ */ jsx(DisplayValue, { size, interactive, onActivate: enterEdit, children: resolvedOptions.find((o) => o.value === value)?.label }) : /* @__PURE__ */ jsxs(
4664
4843
  "div",
4665
4844
  {
4666
4845
  ref: containerRef,
4667
4846
  "data-react-fancy-multi-switch": "",
4668
4847
  role: "radiogroup",
4669
4848
  id,
4849
+ onBlur: (e) => {
4850
+ if (interactive && !e.currentTarget.contains(e.relatedTarget)) exitEdit();
4851
+ },
4670
4852
  className: cn(
4671
4853
  "relative inline-flex rounded-lg border border-zinc-300 bg-zinc-100 dark:border-zinc-700 dark:bg-zinc-800",
4672
4854
  dirty && "ring-2 ring-amber-400/50",
@@ -4694,6 +4876,7 @@ function MultiSwitch({
4694
4876
  role: "radio",
4695
4877
  "aria-checked": isSelected,
4696
4878
  disabled: disabled || option.disabled,
4879
+ autoFocus: interactive && (selectedIndex === -1 ? index === 0 : isSelected),
4697
4880
  onClick: () => {
4698
4881
  if (linear) {
4699
4882
  const nextIndex = (selectedIndex + 1) % resolvedOptions.length;
@@ -4813,12 +4996,22 @@ var SingleDatePicker = forwardRef(
4813
4996
  }, ref) => {
4814
4997
  const singleProps = rest;
4815
4998
  const resolvedMode = useFieldMode(mode);
4999
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
4816
5000
  const [value, setValue] = useControllableState(
4817
5001
  singleProps.value,
4818
5002
  singleProps.defaultValue ?? "",
4819
5003
  singleProps.onValueChange
4820
5004
  );
4821
- const input = resolvedMode === "view" ? /* @__PURE__ */ jsx(DisplayValue, { size, className, children: formatDateValue(value, includeTime) }) : /* @__PURE__ */ jsx(
5005
+ const input = !showControl ? /* @__PURE__ */ jsx(
5006
+ DisplayValue,
5007
+ {
5008
+ size,
5009
+ className,
5010
+ interactive,
5011
+ onActivate: enterEdit,
5012
+ children: formatDateValue(value, includeTime)
5013
+ }
5014
+ ) : /* @__PURE__ */ jsx(
4822
5015
  "input",
4823
5016
  {
4824
5017
  "data-react-fancy-date-picker": "",
@@ -4832,7 +5025,11 @@ var SingleDatePicker = forwardRef(
4832
5025
  disabled,
4833
5026
  required,
4834
5027
  onChange: (e) => setValue(e.target.value),
4835
- className: cn(inputClasses, className)
5028
+ className: cn(inputClasses, className),
5029
+ autoFocus: interactive,
5030
+ onBlur: () => {
5031
+ if (interactive) exitEdit();
5032
+ }
4836
5033
  }
4837
5034
  );
4838
5035
  if (label || error || description) {
@@ -4874,43 +5071,64 @@ var RangeDatePicker = forwardRef(
4874
5071
  }, ref) => {
4875
5072
  const rangeProps = rest;
4876
5073
  const resolvedMode = useFieldMode(mode);
5074
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
4877
5075
  const [value, setValue] = useControllableState(
4878
5076
  rangeProps.value,
4879
5077
  rangeProps.defaultValue ?? ["", ""],
4880
5078
  rangeProps.onValueChange
4881
5079
  );
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
- ] });
5080
+ const input = !showControl ? /* @__PURE__ */ jsx(
5081
+ DisplayValue,
5082
+ {
5083
+ size,
5084
+ className,
5085
+ interactive,
5086
+ onActivate: enterEdit,
5087
+ children: value[0] || value[1] ? `${formatDateValue(value[0], includeTime)} \u2013 ${formatDateValue(value[1], includeTime)}` : ""
5088
+ }
5089
+ ) : /* @__PURE__ */ jsxs(
5090
+ "div",
5091
+ {
5092
+ "data-react-fancy-date-picker": "",
5093
+ className: cn("flex items-center gap-2", className),
5094
+ onBlur: (e) => {
5095
+ if (interactive && !e.currentTarget.contains(e.relatedTarget)) exitEdit();
5096
+ },
5097
+ children: [
5098
+ /* @__PURE__ */ jsx(
5099
+ "input",
5100
+ {
5101
+ ref,
5102
+ id,
5103
+ type: inputType,
5104
+ name: name ? `${name}_start` : void 0,
5105
+ min,
5106
+ max: value[1] || max,
5107
+ value: value[0],
5108
+ disabled,
5109
+ required,
5110
+ onChange: (e) => setValue([e.target.value, value[1]]),
5111
+ className: inputClasses,
5112
+ autoFocus: interactive
5113
+ }
5114
+ ),
5115
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-zinc-500 dark:text-zinc-400", children: "to" }),
5116
+ /* @__PURE__ */ jsx(
5117
+ "input",
5118
+ {
5119
+ type: inputType,
5120
+ name: name ? `${name}_end` : void 0,
5121
+ min: value[0] || min,
5122
+ max,
5123
+ value: value[1],
5124
+ disabled,
5125
+ onChange: (e) => setValue([value[0], e.target.value]),
5126
+ className: inputClasses
5127
+ }
5128
+ )
5129
+ ]
5130
+ }
5131
+ );
4914
5132
  if (label || error || description) {
4915
5133
  return /* @__PURE__ */ jsx(
4916
5134
  Field,
@@ -5284,6 +5502,7 @@ var ColorPicker = forwardRef(
5284
5502
  onChange
5285
5503
  );
5286
5504
  const resolvedMode = useFieldMode(mode);
5505
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
5287
5506
  const inputRef = useRef(null);
5288
5507
  const datalistId = useId();
5289
5508
  const handleChange = (e) => {
@@ -5294,14 +5513,28 @@ var ColorPicker = forwardRef(
5294
5513
  inputRef.current?.click();
5295
5514
  }
5296
5515
  };
5297
- if (resolvedMode === "view") {
5516
+ if (!showControl) {
5298
5517
  return /* @__PURE__ */ jsxs(
5299
5518
  "div",
5300
5519
  {
5301
5520
  ref,
5302
5521
  "data-react-fancy-color-picker": "",
5303
5522
  "data-mode": "view",
5304
- className: cn("inline-flex items-center gap-2", className),
5523
+ role: interactive ? "button" : void 0,
5524
+ tabIndex: interactive ? 0 : void 0,
5525
+ title: interactive ? "Click to edit" : void 0,
5526
+ onClick: interactive ? enterEdit : void 0,
5527
+ onKeyDown: interactive ? (e) => {
5528
+ if (e.key === "Enter" || e.key === " ") {
5529
+ e.preventDefault();
5530
+ enterEdit();
5531
+ }
5532
+ } : void 0,
5533
+ className: cn(
5534
+ "inline-flex items-center gap-2",
5535
+ interactive && "cursor-pointer rounded-md outline-none transition-opacity hover:opacity-80 focus-visible:ring-2 focus-visible:ring-blue-500/40",
5536
+ className
5537
+ ),
5305
5538
  children: [
5306
5539
  /* @__PURE__ */ jsx(
5307
5540
  "span",
@@ -5336,12 +5569,16 @@ var ColorPicker = forwardRef(
5336
5569
  ref,
5337
5570
  "data-react-fancy-color-picker": "",
5338
5571
  className: cn("inline-flex items-center gap-2", className),
5572
+ onBlur: (e) => {
5573
+ if (interactive && !e.currentTarget.contains(e.relatedTarget)) exitEdit();
5574
+ },
5339
5575
  children: [
5340
5576
  /* @__PURE__ */ jsx(
5341
5577
  "button",
5342
5578
  {
5343
5579
  type: "button",
5344
5580
  disabled,
5581
+ autoFocus: interactive,
5345
5582
  onClick: handleSwatchClick,
5346
5583
  className: cn(
5347
5584
  "relative shrink-0 rounded-full transition-shadow",
@@ -9085,6 +9322,7 @@ var Autocomplete = forwardRef(
9085
9322
  onChange
9086
9323
  );
9087
9324
  const resolvedMode = useFieldMode(mode);
9325
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
9088
9326
  const [query, setQuery] = useState(value);
9089
9327
  const [open, setOpen] = useState(false);
9090
9328
  const [activeIndex, setActiveIndex] = useState(-1);
@@ -9137,7 +9375,7 @@ var Autocomplete = forwardRef(
9137
9375
  if (item && !item.disabled) select(item.value);
9138
9376
  }
9139
9377
  };
9140
- if (resolvedMode === "view") {
9378
+ if (!showControl) {
9141
9379
  const matched = options.find((o) => o.value === value);
9142
9380
  return /* @__PURE__ */ jsx(
9143
9381
  "div",
@@ -9145,72 +9383,97 @@ var Autocomplete = forwardRef(
9145
9383
  "data-react-fancy-autocomplete": "",
9146
9384
  "data-mode": "view",
9147
9385
  ref: wrapperRef,
9386
+ role: interactive ? "button" : void 0,
9387
+ tabIndex: interactive ? 0 : void 0,
9388
+ title: interactive ? "Click to edit" : void 0,
9389
+ onClick: interactive ? enterEdit : void 0,
9390
+ onKeyDown: interactive ? (e) => {
9391
+ if (e.key === "Enter" || e.key === " ") {
9392
+ e.preventDefault();
9393
+ enterEdit();
9394
+ }
9395
+ } : void 0,
9148
9396
  className: cn(
9149
9397
  "text-sm text-zinc-900 dark:text-zinc-100",
9150
9398
  !value && "text-zinc-400 dark:text-zinc-500",
9399
+ 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
9400
  className
9152
9401
  ),
9153
9402
  children: matched?.label ?? value ?? "\u2014"
9154
9403
  }
9155
9404
  );
9156
9405
  }
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",
9406
+ return /* @__PURE__ */ jsxs(
9407
+ "div",
9408
+ {
9409
+ "data-react-fancy-autocomplete": "",
9410
+ ref: wrapperRef,
9411
+ className: cn("relative", className),
9412
+ onBlur: (e) => {
9413
+ if (interactive && !open && !e.currentTarget.contains(e.relatedTarget)) {
9414
+ exitEdit();
9415
+ }
9416
+ },
9417
+ children: [
9418
+ /* @__PURE__ */ jsx(
9419
+ "input",
9196
9420
  {
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
- ] });
9421
+ ref: (node) => {
9422
+ anchorRef.current = node;
9423
+ if (typeof ref === "function") ref(node);
9424
+ else if (ref) ref.current = node;
9425
+ },
9426
+ type: "text",
9427
+ value: query,
9428
+ onChange: (e) => {
9429
+ setQuery(e.target.value);
9430
+ setOpen(true);
9431
+ setActiveIndex(-1);
9432
+ },
9433
+ onFocus: () => setOpen(true),
9434
+ onKeyDown: handleKeyDown,
9435
+ placeholder,
9436
+ disabled,
9437
+ autoFocus: interactive,
9438
+ role: "combobox",
9439
+ "aria-expanded": open,
9440
+ "aria-autocomplete": "list",
9441
+ 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"
9442
+ }
9443
+ ),
9444
+ open && /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(
9445
+ "div",
9446
+ {
9447
+ ref: listRef,
9448
+ role: "listbox",
9449
+ 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",
9450
+ style: {
9451
+ left: position.x,
9452
+ top: position.y,
9453
+ width: anchorRef.current?.offsetWidth
9454
+ },
9455
+ 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(
9456
+ "button",
9457
+ {
9458
+ type: "button",
9459
+ role: "option",
9460
+ "aria-selected": i === activeIndex,
9461
+ disabled: option.disabled,
9462
+ onClick: () => select(option.value),
9463
+ className: cn(
9464
+ "flex w-full items-center rounded-lg px-3 py-2 text-left text-sm text-zinc-700 transition-colors dark:text-zinc-200",
9465
+ i === activeIndex ? "bg-zinc-100 dark:bg-zinc-800" : "hover:bg-zinc-50 dark:hover:bg-zinc-800/50",
9466
+ option.disabled && "cursor-not-allowed opacity-50"
9467
+ ),
9468
+ children: /* @__PURE__ */ jsx(HighlightMatch, { text: option.label, query })
9469
+ },
9470
+ option.value
9471
+ ))
9472
+ }
9473
+ ) })
9474
+ ]
9475
+ }
9476
+ );
9214
9477
  }
9215
9478
  );
9216
9479
  function HighlightMatch({ text, query }) {
@@ -9335,6 +9598,7 @@ var OtpInput = forwardRef(
9335
9598
  onChange
9336
9599
  );
9337
9600
  const resolvedMode = useFieldMode(mode);
9601
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
9338
9602
  const inputsRef = useRef([]);
9339
9603
  const focusInput = useCallback(
9340
9604
  (index) => {
@@ -9388,42 +9652,64 @@ var OtpInput = forwardRef(
9388
9652
  },
9389
9653
  [length, setValue, focusInput]
9390
9654
  );
9391
- if (resolvedMode === "view") {
9655
+ if (!showControl) {
9392
9656
  return /* @__PURE__ */ jsx(
9393
9657
  "div",
9394
9658
  {
9395
9659
  "data-react-fancy-otp-input": "",
9396
9660
  "data-mode": "view",
9397
9661
  ref,
9662
+ role: interactive ? "button" : void 0,
9663
+ tabIndex: interactive ? 0 : void 0,
9664
+ title: interactive ? "Click to edit" : void 0,
9665
+ onClick: interactive ? enterEdit : void 0,
9666
+ onKeyDown: interactive ? (e) => {
9667
+ if (e.key === "Enter" || e.key === " ") {
9668
+ e.preventDefault();
9669
+ enterEdit();
9670
+ }
9671
+ } : void 0,
9398
9672
  className: cn(
9399
9673
  "font-mono text-lg tracking-[0.4em] text-zinc-900 dark:text-zinc-100",
9674
+ interactive && "cursor-pointer rounded-md outline-none transition-opacity hover:opacity-80 focus-visible:ring-2 focus-visible:ring-blue-500/40",
9400
9675
  className
9401
9676
  ),
9402
9677
  children: value || "\u2014"
9403
9678
  }
9404
9679
  );
9405
9680
  }
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",
9681
+ return /* @__PURE__ */ jsx(
9682
+ "div",
9408
9683
  {
9409
- ref: (el) => {
9410
- inputsRef.current[i] = el;
9684
+ "data-react-fancy-otp-input": "",
9685
+ ref,
9686
+ className: cn("flex gap-2", className),
9687
+ onBlur: (e) => {
9688
+ if (interactive && !e.currentTarget.contains(e.relatedTarget)) exitEdit();
9411
9689
  },
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
- )) });
9690
+ children: Array.from({ length }, (_, i) => /* @__PURE__ */ jsx(
9691
+ "input",
9692
+ {
9693
+ ref: (el) => {
9694
+ inputsRef.current[i] = el;
9695
+ },
9696
+ type: "text",
9697
+ inputMode: "numeric",
9698
+ maxLength: 1,
9699
+ value: value[i] ?? "",
9700
+ onChange: (e) => handleChange(i, e.target.value),
9701
+ onKeyDown: (e) => handleKeyDown(i, e),
9702
+ onPaste: handlePaste,
9703
+ onFocus: (e) => e.target.select(),
9704
+ disabled,
9705
+ autoFocus: (autoFocus || interactive) && i === 0,
9706
+ 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",
9707
+ "aria-label": `Digit ${i + 1}`
9708
+ },
9709
+ i
9710
+ ))
9711
+ }
9712
+ );
9427
9713
  }
9428
9714
  );
9429
9715
  OtpInput.displayName = "OtpInput";
@@ -9626,10 +9912,11 @@ var TimePicker = forwardRef(
9626
9912
  onChange
9627
9913
  );
9628
9914
  const resolvedMode = useFieldMode(mode);
9915
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
9629
9916
  const { hours: h24, minutes } = parseTime(value);
9630
9917
  const isPM = h24 >= 12;
9631
9918
  const displayHour = format === "12h" ? h24 % 12 || 12 : h24;
9632
- if (resolvedMode === "view") {
9919
+ if (!showControl) {
9633
9920
  const formatted = format === "12h" ? `${pad(displayHour)}:${pad(minutes)} ${isPM ? "PM" : "AM"}` : `${pad(displayHour)}:${pad(minutes)}`;
9634
9921
  return /* @__PURE__ */ jsx(
9635
9922
  "div",
@@ -9637,8 +9924,19 @@ var TimePicker = forwardRef(
9637
9924
  "data-react-fancy-time-picker": "",
9638
9925
  "data-mode": "view",
9639
9926
  ref,
9927
+ role: interactive ? "button" : void 0,
9928
+ tabIndex: interactive ? 0 : void 0,
9929
+ title: interactive ? "Click to edit" : void 0,
9930
+ onClick: interactive ? enterEdit : void 0,
9931
+ onKeyDown: interactive ? (e) => {
9932
+ if (e.key === "Enter" || e.key === " ") {
9933
+ e.preventDefault();
9934
+ enterEdit();
9935
+ }
9936
+ } : void 0,
9640
9937
  className: cn(
9641
9938
  "text-sm font-medium tabular-nums text-zinc-900 dark:text-zinc-100",
9939
+ 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
9940
  className
9643
9941
  ),
9644
9942
  children: formatted
@@ -9675,6 +9973,9 @@ var TimePicker = forwardRef(
9675
9973
  {
9676
9974
  "data-react-fancy-time-picker": "",
9677
9975
  ref,
9976
+ onBlur: (e) => {
9977
+ if (interactive && !e.currentTarget.contains(e.relatedTarget)) exitEdit();
9978
+ },
9678
9979
  className: cn(
9679
9980
  "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
9981
  disabled && "opacity-50",
@@ -9682,7 +9983,7 @@ var TimePicker = forwardRef(
9682
9983
  ),
9683
9984
  children: [
9684
9985
  /* @__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 }) }),
9986
+ /* @__PURE__ */ jsx("button", { type: "button", autoFocus: interactive, onClick: () => changeHour(1), disabled, className: spinBtnClass, "aria-label": "Increase hour", children: /* @__PURE__ */ jsx(ChevronUp, { size: 14 }) }),
9686
9987
  /* @__PURE__ */ jsx("div", { className: displayClass, children: pad(displayHour) }),
9687
9988
  /* @__PURE__ */ jsx("button", { type: "button", onClick: () => changeHour(-1), disabled, className: spinBtnClass, "aria-label": "Decrease hour", children: /* @__PURE__ */ jsx(ChevronDown, { size: 14 }) })
9688
9989
  ] }),
@@ -13964,6 +14265,6 @@ function caretRect(ta, start, end) {
13964
14265
  return { x, y };
13965
14266
  }
13966
14267
 
13967
- export { Accordion, AccordionPanel, AccordionPanelContent, AccordionPanelSection, AccordionPanelTrigger, Action, Autocomplete, Avatar, Badge, Brand, Breadcrumbs, Button, Calendar, Callout, Card, Carousel, Chart, ChatDrawer, Checkbox, CheckboxGroup, ColorPicker, Command, Composer, ContentRenderer, ContextMenu, DatePicker, DisplayValue, Dropdown, EMOJI_CATEGORY_ORDER, EMOJI_DATA, EMOJI_ENTRIES, Editor, Emoji, EmojiSelect, FauxClient, Field, FieldModeContext, FileUpload, Form, FormProvider, Heading, Icon, Input, InputTag, Kanban, MagicWand, Menu2 as Menu, MobileMenu, Modal, MoodMeter, MultiSwitch, Navbar, OtpInput, Pagination, Pillbox, Popover, Portal, Profile, Progress, PromptInput, RadioGroup, ReasonTag, SKIN_TONES, Select, Separator, Sidebar, Skeleton, Slider, StickyNote, Switch, Table, Tabs, Text, Textarea, TimeGrid, TimePicker, Timeline, Toast, Tooltip, TreeNav, applyTone, cn, configureIcons, contentEditableAdapter, controlledAdapter, find, hasSkinTones, inputAdapter, registerExtension, registerExtensions, registerIconSet, registerIcons, resolve, sanitizeHref, sanitizeHtml, search, skinTones, textareaAdapter, useAccordion, useAccordionPanel, useAccordionSection, useAnimation, useCarousel, useCommand, useContextMenu, useControllableState, useDropdown, useEditor, useEscapeKey, useFieldMode, useFileUpload, useFloatingPosition, useFocusTrap, useId12 as useId, useKanban, useMenu, useMobileMenu, useModal, useNavbar, useNodeRegistry, useOutsideClick, usePanZoom, usePopover, useSidebar, useTabs, useToast, useTreeNav };
14268
+ export { Accordion, AccordionPanel, AccordionPanelContent, AccordionPanelSection, AccordionPanelTrigger, Action, Autocomplete, Avatar, Badge, Brand, Breadcrumbs, Button, Calendar, Callout, Card, Carousel, Chart, ChatDrawer, Checkbox, CheckboxGroup, ColorPicker, Command, Composer, ContentRenderer, ContextMenu, DatePicker, DisplayValue, Dropdown, EMOJI_CATEGORY_ORDER, EMOJI_DATA, EMOJI_ENTRIES, Editor, Emoji, EmojiSelect, FauxClient, Field, FieldModeContext, FileUpload, Form, FormProvider, Heading, Icon, Input, InputTag, Kanban, MagicWand, Menu2 as Menu, MobileMenu, Modal, MoodMeter, MultiSwitch, Navbar, OtpInput, Pagination, Pillbox, Popover, Portal, Profile, Progress, PromptInput, RadioGroup, ReasonTag, SKIN_TONES, Select, Separator, Sidebar, Skeleton, Slider, StickyNote, Switch, Table, Tabs, Text, Textarea, TimeGrid, TimePicker, Timeline, Toast, Tooltip, TreeNav, applyTone, cn, configureIcons, contentEditableAdapter, controlledAdapter, find, hasSkinTones, inputAdapter, registerExtension, registerExtensions, registerIconAddendum, registerIconSet, registerIcons, resolve, sanitizeHref, sanitizeHtml, search, skinTones, textareaAdapter, useAccordion, useAccordionPanel, useAccordionSection, useAnimation, useCarousel, useCommand, useContextMenu, useControllableState, useDropdown, useEditor, useEscapeKey, useFieldMode, useFileUpload, useFloatingPosition, useFocusTrap, useId12 as useId, useKanban, useMenu, useMobileMenu, useModal, useNavbar, useNodeRegistry, useOutsideClick, usePanZoom, usePopover, useSidebar, useTabs, useToast, useTreeNav };
13968
14269
  //# sourceMappingURL=index.js.map
13969
14270
  //# sourceMappingURL=index.js.map