@particle-academy/react-fancy 4.4.7 → 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
@@ -3079,6 +3079,68 @@ function InputWrapper({
3079
3079
  hasInsideSuffix && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pointer-events-none absolute right-3 z-10 text-zinc-400 dark:text-zinc-500", children: suffix })
3080
3080
  ] });
3081
3081
  }
3082
+ var FieldModeContext = react.createContext(null);
3083
+ function useFieldMode(explicit) {
3084
+ const ctx = react.useContext(FieldModeContext);
3085
+ return explicit ?? ctx?.mode ?? "edit";
3086
+ }
3087
+ function isEmpty(value) {
3088
+ return value === null || value === void 0 || value === "";
3089
+ }
3090
+ function DisplayValue({
3091
+ children,
3092
+ size = "md",
3093
+ leading,
3094
+ trailing,
3095
+ empty = "\u2014",
3096
+ interactive,
3097
+ onActivate,
3098
+ className
3099
+ }) {
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;
3107
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3108
+ "div",
3109
+ {
3110
+ "data-react-fancy-display": "",
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,
3117
+ className: cn(
3118
+ "flex w-full items-center gap-2 text-zinc-900 dark:text-zinc-100",
3119
+ inputSizeClasses[size],
3120
+ // Strip the editable box look — keep only the size/padding rhythm.
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",
3123
+ empties && "text-zinc-400 dark:text-zinc-500",
3124
+ className
3125
+ ),
3126
+ children: [
3127
+ leading && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-zinc-400 dark:text-zinc-500", children: leading }),
3128
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 truncate", children: empties ? empty : children }),
3129
+ trailing && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-zinc-400 dark:text-zinc-500", children: trailing })
3130
+ ]
3131
+ }
3132
+ );
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
+ }
3082
3144
  var Input = react.forwardRef(
3083
3145
  ({
3084
3146
  type = "text",
@@ -3097,13 +3159,26 @@ var Input = react.forwardRef(
3097
3159
  suffix,
3098
3160
  prefixPosition,
3099
3161
  suffixPosition,
3162
+ mode,
3100
3163
  onValueChange,
3101
3164
  onChange,
3102
3165
  ...props
3103
3166
  }, ref) => {
3104
3167
  const autoId = react.useId();
3105
3168
  const inputId = id ?? autoId;
3106
- const input = /* @__PURE__ */ jsxRuntime.jsx(
3169
+ const resolvedMode = useFieldMode(mode);
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(
3107
3182
  InputWrapper,
3108
3183
  {
3109
3184
  prefix,
@@ -3135,7 +3210,12 @@ var Input = react.forwardRef(
3135
3210
  onChange?.(e);
3136
3211
  onValueChange?.(e.target.value);
3137
3212
  },
3138
- ...props
3213
+ ...props,
3214
+ autoFocus: interactive || props.autoFocus,
3215
+ onBlur: (e) => {
3216
+ props.onBlur?.(e);
3217
+ if (interactive) exitEdit();
3218
+ }
3139
3219
  }
3140
3220
  ),
3141
3221
  trailing && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pointer-events-none absolute right-3 text-zinc-400 dark:text-zinc-500", children: trailing })
@@ -3178,6 +3258,7 @@ var Textarea = react.forwardRef(
3178
3258
  suffix,
3179
3259
  prefixPosition: _prefixPosition,
3180
3260
  suffixPosition: _suffixPosition,
3261
+ mode,
3181
3262
  onValueChange,
3182
3263
  onChange,
3183
3264
  value,
@@ -3187,6 +3268,8 @@ var Textarea = react.forwardRef(
3187
3268
  const autoId = react.useId();
3188
3269
  const textareaId = id ?? autoId;
3189
3270
  const internalRef = react.useRef(null);
3271
+ const resolvedMode = useFieldMode(mode);
3272
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
3190
3273
  react.useEffect(() => {
3191
3274
  const el = internalRef.current;
3192
3275
  if (!autoResize || !el) return;
@@ -3196,7 +3279,16 @@ var Textarea = react.forwardRef(
3196
3279
  const maxHeight = maxRows ? maxRows * lineHeight : Infinity;
3197
3280
  el.style.height = `${Math.min(Math.max(el.scrollHeight, minHeight), maxHeight)}px`;
3198
3281
  }, [autoResize, minRows, maxRows, value, defaultValue]);
3199
- const textarea = /* @__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(
3200
3292
  InputWrapper,
3201
3293
  {
3202
3294
  prefix,
@@ -3235,7 +3327,12 @@ var Textarea = react.forwardRef(
3235
3327
  onChange?.(e);
3236
3328
  onValueChange?.(e.target.value);
3237
3329
  },
3238
- ...props
3330
+ ...props,
3331
+ autoFocus: interactive || props.autoFocus,
3332
+ onBlur: (e) => {
3333
+ props.onBlur?.(e);
3334
+ if (interactive) exitEdit();
3335
+ }
3239
3336
  }
3240
3337
  )
3241
3338
  }
@@ -3468,6 +3565,7 @@ var NativeSelect = react.forwardRef(
3468
3565
  suffix,
3469
3566
  prefixPosition,
3470
3567
  suffixPosition,
3568
+ mode,
3471
3569
  onValueChange,
3472
3570
  onChange,
3473
3571
  value,
@@ -3476,6 +3574,38 @@ var NativeSelect = react.forwardRef(
3476
3574
  }, ref) => {
3477
3575
  const autoId = react.useId();
3478
3576
  const selectId = id ?? autoId;
3577
+ const resolvedMode = useFieldMode(mode);
3578
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
3579
+ if (!showControl) {
3580
+ const current = value ?? defaultValue;
3581
+ const opt = flattenOptions(list).map((o) => resolveOption(o)).find((o) => o.value === 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
+ );
3593
+ if (label || error || description) {
3594
+ return /* @__PURE__ */ jsxRuntime.jsx(
3595
+ Field,
3596
+ {
3597
+ label,
3598
+ description,
3599
+ error,
3600
+ required,
3601
+ htmlFor: selectId,
3602
+ size,
3603
+ children: display
3604
+ }
3605
+ );
3606
+ }
3607
+ return display;
3608
+ }
3479
3609
  const isControlled = value !== void 0;
3480
3610
  const resolvedDefault = !isControlled && defaultValue === void 0 && placeholder ? "" : defaultValue;
3481
3611
  const select = /* @__PURE__ */ jsxRuntime.jsx(
@@ -3510,6 +3640,11 @@ var NativeSelect = react.forwardRef(
3510
3640
  },
3511
3641
  ...props,
3512
3642
  ...isControlled ? { value } : { defaultValue: resolvedDefault },
3643
+ autoFocus: interactive || props.autoFocus,
3644
+ onBlur: (e) => {
3645
+ props.onBlur?.(e);
3646
+ if (interactive) exitEdit();
3647
+ },
3513
3648
  children: [
3514
3649
  placeholder && /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", disabled: true, children: placeholder }),
3515
3650
  list.map(
@@ -3564,11 +3699,14 @@ var ListboxSelect = react.forwardRef(
3564
3699
  createLabel = "Create",
3565
3700
  selectedSuffix = "selected",
3566
3701
  indicator = "check",
3702
+ mode,
3567
3703
  value: controlledSingleValue,
3568
3704
  defaultValue: defaultSingleValue
3569
3705
  }, _ref) => {
3570
3706
  const autoId = react.useId();
3571
3707
  const selectId = id ?? autoId;
3708
+ const resolvedMode = useFieldMode(mode);
3709
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
3572
3710
  const textInputEnabled = searchable || creatable;
3573
3711
  const [open, setOpen] = react.useState(false);
3574
3712
  const [search2, setSearch] = react.useState("");
@@ -3695,6 +3833,27 @@ var ListboxSelect = react.forwardRef(
3695
3833
  }
3696
3834
  }
3697
3835
  };
3836
+ if (!showControl) {
3837
+ const labels = multiple ? currentMulti.map(
3838
+ (v) => resolvedOptions.find((o) => o.value === v)?.label ?? v
3839
+ ) : currentSingle ? [resolvedOptions.find((o) => o.value === currentSingle)?.label ?? currentSingle] : [];
3840
+ const display = /* @__PURE__ */ jsxRuntime.jsx(DisplayValue, { size, interactive, onActivate: enterEdit, children: labels.join(", ") });
3841
+ if (label || error || description) {
3842
+ return /* @__PURE__ */ jsxRuntime.jsx(
3843
+ Field,
3844
+ {
3845
+ label,
3846
+ description,
3847
+ error,
3848
+ required,
3849
+ htmlFor: selectId,
3850
+ size,
3851
+ children: display
3852
+ }
3853
+ );
3854
+ }
3855
+ return display;
3856
+ }
3698
3857
  const trigger = /* @__PURE__ */ jsxRuntime.jsxs(
3699
3858
  "button",
3700
3859
  {
@@ -3702,6 +3861,7 @@ var ListboxSelect = react.forwardRef(
3702
3861
  type: "button",
3703
3862
  id: selectId,
3704
3863
  disabled,
3864
+ autoFocus: interactive,
3705
3865
  onClick: () => setOpen((o) => !o),
3706
3866
  onKeyDown: handleKeyDown,
3707
3867
  role: "combobox",
@@ -3823,10 +3983,21 @@ var ListboxSelect = react.forwardRef(
3823
3983
  ]
3824
3984
  }
3825
3985
  ) });
3826
- const content = /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
3827
- trigger,
3828
- dropdown
3829
- ] });
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
+ );
3830
4001
  if (label || error || description) {
3831
4002
  return /* @__PURE__ */ jsxRuntime.jsx(
3832
4003
  Field,
@@ -3870,11 +4041,14 @@ var Checkbox = react.forwardRef(
3870
4041
  checked: controlledChecked,
3871
4042
  defaultChecked = false,
3872
4043
  onCheckedChange,
3873
- indeterminate
4044
+ indeterminate,
4045
+ mode
3874
4046
  }, ref) => {
3875
4047
  const autoId = react.useId();
3876
4048
  const checkboxId = id ?? autoId;
3877
4049
  const internalRef = react.useRef(null);
4050
+ const resolvedMode = useFieldMode(mode);
4051
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
3878
4052
  const [checked, setChecked] = useControllableState(
3879
4053
  controlledChecked,
3880
4054
  defaultChecked,
@@ -3893,8 +4067,32 @@ var Checkbox = react.forwardRef(
3893
4067
  lg: "h-5 w-5",
3894
4068
  xl: "h-6 w-6"
3895
4069
  }[size];
4070
+ const glyph = indeterminate ? "\u2014" : checked ? "\u2713" : "\u2715";
3896
4071
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-react-fancy-checkbox": "", className: cn("flex items-start gap-2", className), children: [
3897
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative flex items-center", children: /* @__PURE__ */ jsxRuntime.jsx(
4072
+ !showControl ? /* @__PURE__ */ jsxRuntime.jsx(
4073
+ "span",
4074
+ {
4075
+ "data-react-fancy-display": "",
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,
4087
+ className: cn(
4088
+ "flex shrink-0 items-center justify-center text-zinc-700 dark:text-zinc-200",
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"
4091
+ ),
4092
+ "aria-hidden": interactive ? void 0 : "true",
4093
+ children: glyph
4094
+ }
4095
+ ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative flex items-center", children: /* @__PURE__ */ jsxRuntime.jsx(
3898
4096
  "input",
3899
4097
  {
3900
4098
  ref: (node) => {
@@ -3911,6 +4109,10 @@ var Checkbox = react.forwardRef(
3911
4109
  disabled,
3912
4110
  required,
3913
4111
  checked,
4112
+ autoFocus: interactive,
4113
+ onBlur: () => {
4114
+ if (interactive) exitEdit();
4115
+ },
3914
4116
  onChange: (e) => setChecked(e.target.checked),
3915
4117
  className: cn(
3916
4118
  sizeClasses6,
@@ -3956,9 +4158,12 @@ function CheckboxGroup({
3956
4158
  value: controlledValue,
3957
4159
  defaultValue = [],
3958
4160
  onValueChange,
3959
- orientation = "vertical"
4161
+ orientation = "vertical",
4162
+ mode
3960
4163
  }) {
3961
4164
  const groupId = react.useId();
4165
+ const resolvedMode = useFieldMode(mode);
4166
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
3962
4167
  const [value, setValue] = useControllableState(
3963
4168
  controlledValue,
3964
4169
  defaultValue,
@@ -3975,10 +4180,13 @@ function CheckboxGroup({
3975
4180
  lg: "h-5 w-5",
3976
4181
  xl: "h-6 w-6"
3977
4182
  }[size];
3978
- const content = /* @__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(
3979
4184
  "div",
3980
4185
  {
3981
4186
  "data-react-fancy-checkbox-group": "",
4187
+ onBlur: (e) => {
4188
+ if (interactive && !e.currentTarget.contains(e.relatedTarget)) exitEdit();
4189
+ },
3982
4190
  className: cn(
3983
4191
  "flex gap-3",
3984
4192
  orientation === "vertical" ? "flex-col" : "flex-row flex-wrap",
@@ -3998,6 +4206,7 @@ function CheckboxGroup({
3998
4206
  value: String(resolved.value),
3999
4207
  checked: isChecked,
4000
4208
  disabled: disabled || resolved.disabled,
4209
+ autoFocus: interactive && index === 0,
4001
4210
  onChange: () => handleToggle(resolved.value),
4002
4211
  className: cn(
4003
4212
  sizeClasses6,
@@ -4055,10 +4264,13 @@ function RadioGroup({
4055
4264
  value: controlledValue,
4056
4265
  defaultValue,
4057
4266
  onValueChange,
4058
- orientation = "vertical"
4267
+ orientation = "vertical",
4268
+ mode
4059
4269
  }) {
4060
4270
  const groupId = react.useId();
4061
4271
  const radioName = name ?? groupId;
4272
+ const resolvedMode = useFieldMode(mode);
4273
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
4062
4274
  const [value, setValue] = useControllableState(
4063
4275
  controlledValue,
4064
4276
  defaultValue,
@@ -4071,11 +4283,15 @@ function RadioGroup({
4071
4283
  lg: "h-5 w-5",
4072
4284
  xl: "h-6 w-6"
4073
4285
  }[size];
4074
- const content = /* @__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(
4075
4288
  "div",
4076
4289
  {
4077
4290
  "data-react-fancy-radio-group": "",
4078
4291
  role: "radiogroup",
4292
+ onBlur: (e) => {
4293
+ if (interactive && !e.currentTarget.contains(e.relatedTarget)) exitEdit();
4294
+ },
4079
4295
  className: cn(
4080
4296
  "flex gap-3",
4081
4297
  orientation === "vertical" ? "flex-col" : "flex-row flex-wrap",
@@ -4085,6 +4301,7 @@ function RadioGroup({
4085
4301
  const resolved = resolveOption(option);
4086
4302
  const optionId = `${groupId}-${index}`;
4087
4303
  const isSelected = value === resolved.value;
4304
+ const shouldAutoFocus = interactive && (selectedIndex === -1 ? index === 0 : isSelected);
4088
4305
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2", children: [
4089
4306
  /* @__PURE__ */ jsxRuntime.jsx(
4090
4307
  "input",
@@ -4095,6 +4312,7 @@ function RadioGroup({
4095
4312
  value: String(resolved.value),
4096
4313
  checked: isSelected,
4097
4314
  disabled: disabled || resolved.disabled,
4315
+ autoFocus: shouldAutoFocus,
4098
4316
  onChange: () => setValue(resolved.value),
4099
4317
  className: cn(
4100
4318
  sizeClasses6,
@@ -4179,10 +4397,13 @@ var Switch = react.forwardRef(
4179
4397
  checked: controlledChecked,
4180
4398
  defaultChecked = false,
4181
4399
  onCheckedChange,
4182
- color = "blue"
4400
+ color = "blue",
4401
+ mode
4183
4402
  }, ref) => {
4184
4403
  const autoId = react.useId();
4185
4404
  const switchId = id ?? autoId;
4405
+ const resolvedMode = useFieldMode(mode);
4406
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
4186
4407
  const [checked, setChecked] = useControllableState(
4187
4408
  controlledChecked,
4188
4409
  defaultChecked,
@@ -4210,7 +4431,28 @@ var Switch = react.forwardRef(
4210
4431
  xl: "translate-x-6"
4211
4432
  }[size];
4212
4433
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-react-fancy-switch": "", className: cn("flex items-start gap-2", className), children: [
4213
- /* @__PURE__ */ jsxRuntime.jsx(
4434
+ !showControl ? /* @__PURE__ */ jsxRuntime.jsx(
4435
+ "span",
4436
+ {
4437
+ "data-react-fancy-display": "",
4438
+ "data-mode": "view",
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
+ ),
4453
+ children: checked ? "On" : "Off"
4454
+ }
4455
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
4214
4456
  "button",
4215
4457
  {
4216
4458
  ref,
@@ -4219,6 +4461,10 @@ var Switch = react.forwardRef(
4219
4461
  role: "switch",
4220
4462
  "aria-checked": checked,
4221
4463
  disabled,
4464
+ autoFocus: interactive,
4465
+ onBlur: () => {
4466
+ if (interactive) exitEdit();
4467
+ },
4222
4468
  onClick: () => setChecked(!checked),
4223
4469
  className: cn(
4224
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",
@@ -4324,15 +4570,27 @@ var SingleSlider = react.forwardRef(
4324
4570
  marks,
4325
4571
  prefix,
4326
4572
  suffix,
4573
+ mode,
4327
4574
  ...rest
4328
4575
  }, ref) => {
4329
4576
  const singleProps = rest;
4577
+ const resolvedMode = useFieldMode(mode);
4578
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
4330
4579
  const [value, setValue] = useControllableState(
4331
4580
  singleProps.value,
4332
4581
  singleProps.defaultValue ?? min,
4333
4582
  singleProps.onValueChange
4334
4583
  );
4335
- const slider = /* @__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: [
4336
4594
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
4337
4595
  /* @__PURE__ */ jsxRuntime.jsx(
4338
4596
  "input",
@@ -4346,6 +4604,10 @@ var SingleSlider = react.forwardRef(
4346
4604
  step,
4347
4605
  value,
4348
4606
  disabled,
4607
+ autoFocus: interactive,
4608
+ onBlur: () => {
4609
+ if (interactive) exitEdit();
4610
+ },
4349
4611
  onChange: (e) => setValue(Number(e.target.value)),
4350
4612
  className: cn(
4351
4613
  "w-full cursor-pointer accent-blue-600 disabled:cursor-not-allowed disabled:opacity-50",
@@ -4394,9 +4656,12 @@ var RangeSlider = react.forwardRef(
4394
4656
  marks,
4395
4657
  prefix,
4396
4658
  suffix,
4659
+ mode,
4397
4660
  ...rest
4398
4661
  }, ref) => {
4399
4662
  const rangeProps = rest;
4663
+ const resolvedMode = useFieldMode(mode);
4664
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
4400
4665
  const [value, setValue] = useControllableState(
4401
4666
  rangeProps.value,
4402
4667
  rangeProps.defaultValue ?? [min, max],
@@ -4410,61 +4675,81 @@ var RangeSlider = react.forwardRef(
4410
4675
  };
4411
4676
  const leftPercent = (value[0] - min) / (max - min) * 100;
4412
4677
  const rightPercent = (value[1] - min) / (max - min) * 100;
4413
- const slider = /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-react-fancy-slider": "", className: cn("flex flex-col gap-1", className), children: [
4414
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
4415
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full", children: [
4416
- /* @__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" }),
4417
- /* @__PURE__ */ jsxRuntime.jsx(
4418
- "div",
4419
- {
4420
- className: "pointer-events-none absolute top-1/2 h-1.5 -translate-y-1/2 rounded-full bg-blue-500",
4421
- style: { left: `${leftPercent}%`, width: `${rightPercent - leftPercent}%` }
4422
- }
4423
- ),
4424
- /* @__PURE__ */ jsxRuntime.jsx(
4425
- "input",
4426
- {
4427
- ref,
4428
- type: "range",
4429
- min,
4430
- max,
4431
- step,
4432
- value: value[0],
4433
- disabled,
4434
- onChange: (e) => handleMin(Number(e.target.value)),
4435
- className: cn(
4436
- "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",
4437
- dirtyRingClasses(dirty)
4438
- )
4439
- }
4440
- ),
4441
- /* @__PURE__ */ jsxRuntime.jsx(
4442
- "input",
4443
- {
4444
- type: "range",
4445
- min,
4446
- max,
4447
- step,
4448
- value: value[1],
4449
- disabled,
4450
- onChange: (e) => handleMax(Number(e.target.value)),
4451
- 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"
4452
- }
4453
- ),
4454
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-6" })
4455
- ] }),
4456
- 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: [
4457
- prefix,
4458
- value[0],
4459
- suffix,
4460
- "\u2013",
4461
- prefix,
4462
- value[1],
4463
- suffix
4464
- ] })
4465
- ] }),
4466
- marks && marks.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(SliderMarks, { marks, min, max })
4467
- ] });
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
+ );
4468
4753
  if (label || error || description) {
4469
4754
  return /* @__PURE__ */ jsxRuntime.jsx(Field, { label, description, error, required, htmlFor: id, size, children: slider });
4470
4755
  }
@@ -4512,10 +4797,13 @@ function MultiSwitch({
4512
4797
  value: controlledValue,
4513
4798
  defaultValue,
4514
4799
  onValueChange,
4515
- linear
4800
+ linear,
4801
+ mode
4516
4802
  }) {
4517
4803
  const resolvedOptions = list.map(resolveOption);
4518
4804
  const fallback = defaultValue ?? resolvedOptions[0]?.value;
4805
+ const resolvedMode = useFieldMode(mode);
4806
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
4519
4807
  const [value, setValue] = useControllableState(controlledValue, fallback, onValueChange);
4520
4808
  const containerRef = react.useRef(null);
4521
4809
  const itemRefs = react.useRef([]);
@@ -4540,13 +4828,16 @@ function MultiSwitch({
4540
4828
  react.useEffect(() => {
4541
4829
  updateIndicator();
4542
4830
  }, [updateIndicator]);
4543
- const control = /* @__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(
4544
4832
  "div",
4545
4833
  {
4546
4834
  ref: containerRef,
4547
4835
  "data-react-fancy-multi-switch": "",
4548
4836
  role: "radiogroup",
4549
4837
  id,
4838
+ onBlur: (e) => {
4839
+ if (interactive && !e.currentTarget.contains(e.relatedTarget)) exitEdit();
4840
+ },
4550
4841
  className: cn(
4551
4842
  "relative inline-flex rounded-lg border border-zinc-300 bg-zinc-100 dark:border-zinc-700 dark:bg-zinc-800",
4552
4843
  dirty && "ring-2 ring-amber-400/50",
@@ -4574,6 +4865,7 @@ function MultiSwitch({
4574
4865
  role: "radio",
4575
4866
  "aria-checked": isSelected,
4576
4867
  disabled: disabled || option.disabled,
4868
+ autoFocus: interactive && (selectedIndex === -1 ? index === 0 : isSelected),
4577
4869
  onClick: () => {
4578
4870
  if (linear) {
4579
4871
  const nextIndex = (selectedIndex + 1) % resolvedOptions.length;
@@ -4603,6 +4895,22 @@ function MultiSwitch({
4603
4895
  return control;
4604
4896
  }
4605
4897
  MultiSwitch.displayName = "MultiSwitch";
4898
+ function formatDateValue(iso, includeTime) {
4899
+ if (!iso) return "";
4900
+ const date = new Date(iso);
4901
+ if (Number.isNaN(date.getTime())) return iso;
4902
+ return includeTime ? date.toLocaleString(void 0, {
4903
+ year: "numeric",
4904
+ month: "short",
4905
+ day: "numeric",
4906
+ hour: "2-digit",
4907
+ minute: "2-digit"
4908
+ }) : date.toLocaleDateString(void 0, {
4909
+ year: "numeric",
4910
+ month: "short",
4911
+ day: "numeric"
4912
+ });
4913
+ }
4606
4914
  var DatePicker = react.forwardRef(
4607
4915
  (props, ref) => {
4608
4916
  const {
@@ -4669,17 +4977,30 @@ var SingleDatePicker = react.forwardRef(
4669
4977
  name,
4670
4978
  min,
4671
4979
  max,
4980
+ includeTime,
4981
+ mode,
4672
4982
  inputType,
4673
4983
  inputClasses,
4674
4984
  ...rest
4675
4985
  }, ref) => {
4676
4986
  const singleProps = rest;
4987
+ const resolvedMode = useFieldMode(mode);
4988
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
4677
4989
  const [value, setValue] = useControllableState(
4678
4990
  singleProps.value,
4679
4991
  singleProps.defaultValue ?? "",
4680
4992
  singleProps.onValueChange
4681
4993
  );
4682
- const input = /* @__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(
4683
5004
  "input",
4684
5005
  {
4685
5006
  "data-react-fancy-date-picker": "",
@@ -4693,7 +5014,11 @@ var SingleDatePicker = react.forwardRef(
4693
5014
  disabled,
4694
5015
  required,
4695
5016
  onChange: (e) => setValue(e.target.value),
4696
- className: cn(inputClasses, className)
5017
+ className: cn(inputClasses, className),
5018
+ autoFocus: interactive,
5019
+ onBlur: () => {
5020
+ if (interactive) exitEdit();
5021
+ }
4697
5022
  }
4698
5023
  );
4699
5024
  if (label || error || description) {
@@ -4727,48 +5052,72 @@ var RangeDatePicker = react.forwardRef(
4727
5052
  name,
4728
5053
  min,
4729
5054
  max,
5055
+ includeTime,
5056
+ mode,
4730
5057
  inputType,
4731
5058
  inputClasses,
4732
5059
  ...rest
4733
5060
  }, ref) => {
4734
5061
  const rangeProps = rest;
5062
+ const resolvedMode = useFieldMode(mode);
5063
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
4735
5064
  const [value, setValue] = useControllableState(
4736
5065
  rangeProps.value,
4737
5066
  rangeProps.defaultValue ?? ["", ""],
4738
5067
  rangeProps.onValueChange
4739
5068
  );
4740
- const input = /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-react-fancy-date-picker": "", className: cn("flex items-center gap-2", className), children: [
4741
- /* @__PURE__ */ jsxRuntime.jsx(
4742
- "input",
4743
- {
4744
- ref,
4745
- id,
4746
- type: inputType,
4747
- name: name ? `${name}_start` : void 0,
4748
- min,
4749
- max: value[1] || max,
4750
- value: value[0],
4751
- disabled,
4752
- required,
4753
- onChange: (e) => setValue([e.target.value, value[1]]),
4754
- className: inputClasses
4755
- }
4756
- ),
4757
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-zinc-500 dark:text-zinc-400", children: "to" }),
4758
- /* @__PURE__ */ jsxRuntime.jsx(
4759
- "input",
4760
- {
4761
- type: inputType,
4762
- name: name ? `${name}_end` : void 0,
4763
- min: value[0] || min,
4764
- max,
4765
- value: value[1],
4766
- disabled,
4767
- onChange: (e) => setValue([value[0], e.target.value]),
4768
- className: inputClasses
4769
- }
4770
- )
4771
- ] });
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
+ );
4772
5121
  if (label || error || description) {
4773
5122
  return /* @__PURE__ */ jsxRuntime.jsx(
4774
5123
  Field,
@@ -4787,6 +5136,13 @@ var RangeDatePicker = react.forwardRef(
4787
5136
  }
4788
5137
  );
4789
5138
  RangeDatePicker.displayName = "RangeDatePicker";
5139
+ function FormProvider({ mode = "edit", children }) {
5140
+ const value = react.useMemo(() => ({ mode }), [mode]);
5141
+ return /* @__PURE__ */ jsxRuntime.jsx(FieldModeContext.Provider, { value, children });
5142
+ }
5143
+ function Form({ mode, children, ...formProps }) {
5144
+ return /* @__PURE__ */ jsxRuntime.jsx(FormProvider, { mode, children: /* @__PURE__ */ jsxRuntime.jsx("form", { "data-react-fancy-form": "", ...formProps, children }) });
5145
+ }
4790
5146
  var CarouselContext = react.createContext(null);
4791
5147
  CarouselContext.displayName = "CarouselContext";
4792
5148
  function useCarousel() {
@@ -5126,13 +5482,16 @@ var ColorPicker = react.forwardRef(
5126
5482
  size = "md",
5127
5483
  variant = "outline",
5128
5484
  disabled = false,
5129
- className
5485
+ className,
5486
+ mode
5130
5487
  }, ref) => {
5131
5488
  const [color, setColor] = useControllableState(
5132
5489
  value,
5133
5490
  defaultValue,
5134
5491
  onChange
5135
5492
  );
5493
+ const resolvedMode = useFieldMode(mode);
5494
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
5136
5495
  const inputRef = react.useRef(null);
5137
5496
  const datalistId = react.useId();
5138
5497
  const handleChange = (e) => {
@@ -5143,18 +5502,72 @@ var ColorPicker = react.forwardRef(
5143
5502
  inputRef.current?.click();
5144
5503
  }
5145
5504
  };
5505
+ if (!showControl) {
5506
+ return /* @__PURE__ */ jsxRuntime.jsxs(
5507
+ "div",
5508
+ {
5509
+ ref,
5510
+ "data-react-fancy-color-picker": "",
5511
+ "data-mode": "view",
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
+ ),
5527
+ children: [
5528
+ /* @__PURE__ */ jsxRuntime.jsx(
5529
+ "span",
5530
+ {
5531
+ className: cn(
5532
+ "shrink-0 rounded-full",
5533
+ SWATCH_SIZES[size],
5534
+ variant === "outline" && "ring-1 ring-zinc-300 dark:ring-zinc-600"
5535
+ ),
5536
+ style: { backgroundColor: color },
5537
+ "aria-label": `Color: ${color}`
5538
+ }
5539
+ ),
5540
+ /* @__PURE__ */ jsxRuntime.jsx(
5541
+ "span",
5542
+ {
5543
+ className: cn(
5544
+ "select-all font-mono uppercase",
5545
+ TEXT_SIZES[size],
5546
+ "text-zinc-700 dark:text-zinc-300"
5547
+ ),
5548
+ children: color.toUpperCase()
5549
+ }
5550
+ )
5551
+ ]
5552
+ }
5553
+ );
5554
+ }
5146
5555
  return /* @__PURE__ */ jsxRuntime.jsxs(
5147
5556
  "div",
5148
5557
  {
5149
5558
  ref,
5150
5559
  "data-react-fancy-color-picker": "",
5151
5560
  className: cn("inline-flex items-center gap-2", className),
5561
+ onBlur: (e) => {
5562
+ if (interactive && !e.currentTarget.contains(e.relatedTarget)) exitEdit();
5563
+ },
5152
5564
  children: [
5153
5565
  /* @__PURE__ */ jsxRuntime.jsx(
5154
5566
  "button",
5155
5567
  {
5156
5568
  type: "button",
5157
5569
  disabled,
5570
+ autoFocus: interactive,
5158
5571
  onClick: handleSwatchClick,
5159
5572
  className: cn(
5160
5573
  "relative shrink-0 rounded-full transition-shadow",
@@ -6032,6 +6445,14 @@ var Badge = react.forwardRef(
6032
6445
  }
6033
6446
  );
6034
6447
  Badge.displayName = "Badge";
6448
+ var glowColors = {
6449
+ on: "#a855f7",
6450
+ // violet — neutral
6451
+ xp: "#22c55e",
6452
+ // green
6453
+ achievement: "#f59e0b"
6454
+ // amber
6455
+ };
6035
6456
  var containerSizeClasses = {
6036
6457
  xs: "h-6 w-6",
6037
6458
  sm: "h-8 w-8",
@@ -6066,8 +6487,10 @@ var Avatar = react.forwardRef(
6066
6487
  fallback,
6067
6488
  size = "md",
6068
6489
  status,
6490
+ glow,
6069
6491
  className
6070
6492
  }, ref) => {
6493
+ const glowKey = glow === true ? "on" : glow || null;
6071
6494
  return /* @__PURE__ */ jsxRuntime.jsxs(
6072
6495
  "div",
6073
6496
  {
@@ -6080,6 +6503,15 @@ var Avatar = react.forwardRef(
6080
6503
  className
6081
6504
  ),
6082
6505
  children: [
6506
+ glowKey && /* @__PURE__ */ jsxRuntime.jsx(
6507
+ "span",
6508
+ {
6509
+ "aria-hidden": true,
6510
+ "data-react-fancy-avatar-glow": glowKey,
6511
+ className: "fancy-avatar-glow",
6512
+ style: { "--fancy-glow": glowColors[glowKey] }
6513
+ }
6514
+ ),
6083
6515
  src ? /* @__PURE__ */ jsxRuntime.jsx(
6084
6516
  "img",
6085
6517
  {
@@ -8870,13 +9302,16 @@ var Autocomplete = react.forwardRef(
8870
9302
  loading = false,
8871
9303
  emptyMessage = "No results found.",
8872
9304
  disabled = false,
8873
- className
9305
+ className,
9306
+ mode
8874
9307
  }, ref) {
8875
9308
  const [value, setValue] = useControllableState(
8876
9309
  controlledValue,
8877
9310
  defaultValue,
8878
9311
  onChange
8879
9312
  );
9313
+ const resolvedMode = useFieldMode(mode);
9314
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
8880
9315
  const [query, setQuery] = react.useState(value);
8881
9316
  const [open, setOpen] = react.useState(false);
8882
9317
  const [activeIndex, setActiveIndex] = react.useState(-1);
@@ -8929,63 +9364,105 @@ var Autocomplete = react.forwardRef(
8929
9364
  if (item && !item.disabled) select(item.value);
8930
9365
  }
8931
9366
  };
8932
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-react-fancy-autocomplete": "", ref: wrapperRef, className: cn("relative", className), children: [
8933
- /* @__PURE__ */ jsxRuntime.jsx(
8934
- "input",
8935
- {
8936
- ref: (node) => {
8937
- anchorRef.current = node;
8938
- if (typeof ref === "function") ref(node);
8939
- else if (ref) ref.current = node;
8940
- },
8941
- type: "text",
8942
- value: query,
8943
- onChange: (e) => {
8944
- setQuery(e.target.value);
8945
- setOpen(true);
8946
- setActiveIndex(-1);
8947
- },
8948
- onFocus: () => setOpen(true),
8949
- onKeyDown: handleKeyDown,
8950
- placeholder,
8951
- disabled,
8952
- role: "combobox",
8953
- "aria-expanded": open,
8954
- "aria-autocomplete": "list",
8955
- 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"
8956
- }
8957
- ),
8958
- open && /* @__PURE__ */ jsxRuntime.jsx(Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(
9367
+ if (!showControl) {
9368
+ const matched = options.find((o) => o.value === value);
9369
+ return /* @__PURE__ */ jsxRuntime.jsx(
8959
9370
  "div",
8960
9371
  {
8961
- ref: listRef,
8962
- role: "listbox",
8963
- 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",
8964
- style: {
8965
- left: position.x,
8966
- top: position.y,
8967
- width: anchorRef.current?.offsetWidth
8968
- },
8969
- 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(
8970
- "button",
8971
- {
8972
- type: "button",
8973
- role: "option",
8974
- "aria-selected": i === activeIndex,
8975
- disabled: option.disabled,
8976
- onClick: () => select(option.value),
8977
- className: cn(
8978
- "flex w-full items-center rounded-lg px-3 py-2 text-left text-sm text-zinc-700 transition-colors dark:text-zinc-200",
8979
- i === activeIndex ? "bg-zinc-100 dark:bg-zinc-800" : "hover:bg-zinc-50 dark:hover:bg-zinc-800/50",
8980
- option.disabled && "cursor-not-allowed opacity-50"
8981
- ),
8982
- children: /* @__PURE__ */ jsxRuntime.jsx(HighlightMatch, { text: option.label, query })
8983
- },
8984
- option.value
8985
- ))
9372
+ "data-react-fancy-autocomplete": "",
9373
+ "data-mode": "view",
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,
9385
+ className: cn(
9386
+ "text-sm text-zinc-900 dark:text-zinc-100",
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",
9389
+ className
9390
+ ),
9391
+ children: matched?.label ?? value ?? "\u2014"
8986
9392
  }
8987
- ) })
8988
- ] });
9393
+ );
9394
+ }
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",
9409
+ {
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
+ );
8989
9466
  }
8990
9467
  );
8991
9468
  function HighlightMatch({ text, query }) {
@@ -9101,13 +9578,16 @@ var OtpInput = react.forwardRef(
9101
9578
  onChange,
9102
9579
  disabled = false,
9103
9580
  autoFocus = false,
9104
- className
9581
+ className,
9582
+ mode
9105
9583
  }, ref) {
9106
9584
  const [value, setValue] = useControllableState(
9107
9585
  controlledValue,
9108
9586
  "",
9109
9587
  onChange
9110
9588
  );
9589
+ const resolvedMode = useFieldMode(mode);
9590
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
9111
9591
  const inputsRef = react.useRef([]);
9112
9592
  const focusInput = react.useCallback(
9113
9593
  (index) => {
@@ -9161,27 +9641,64 @@ var OtpInput = react.forwardRef(
9161
9641
  },
9162
9642
  [length, setValue, focusInput]
9163
9643
  );
9164
- 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(
9165
- "input",
9644
+ if (!showControl) {
9645
+ return /* @__PURE__ */ jsxRuntime.jsx(
9646
+ "div",
9647
+ {
9648
+ "data-react-fancy-otp-input": "",
9649
+ "data-mode": "view",
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,
9661
+ className: cn(
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",
9664
+ className
9665
+ ),
9666
+ children: value || "\u2014"
9667
+ }
9668
+ );
9669
+ }
9670
+ return /* @__PURE__ */ jsxRuntime.jsx(
9671
+ "div",
9166
9672
  {
9167
- ref: (el) => {
9168
- 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();
9169
9678
  },
9170
- type: "text",
9171
- inputMode: "numeric",
9172
- maxLength: 1,
9173
- value: value[i] ?? "",
9174
- onChange: (e) => handleChange(i, e.target.value),
9175
- onKeyDown: (e) => handleKeyDown(i, e),
9176
- onPaste: handlePaste,
9177
- onFocus: (e) => e.target.select(),
9178
- disabled,
9179
- autoFocus: autoFocus && i === 0,
9180
- 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",
9181
- "aria-label": `Digit ${i + 1}`
9182
- },
9183
- i
9184
- )) });
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
+ );
9185
9702
  }
9186
9703
  );
9187
9704
  OtpInput.displayName = "OtpInput";
@@ -9375,16 +9892,46 @@ var TimePicker = react.forwardRef(
9375
9892
  format = "12h",
9376
9893
  minuteStep = 1,
9377
9894
  disabled = false,
9378
- className
9895
+ className,
9896
+ mode
9379
9897
  }, ref) {
9380
9898
  const [value, setValue] = useControllableState(
9381
9899
  controlledValue,
9382
9900
  defaultValue,
9383
9901
  onChange
9384
9902
  );
9903
+ const resolvedMode = useFieldMode(mode);
9904
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
9385
9905
  const { hours: h24, minutes } = parseTime(value);
9386
9906
  const isPM = h24 >= 12;
9387
9907
  const displayHour = format === "12h" ? h24 % 12 || 12 : h24;
9908
+ if (!showControl) {
9909
+ const formatted = format === "12h" ? `${pad(displayHour)}:${pad(minutes)} ${isPM ? "PM" : "AM"}` : `${pad(displayHour)}:${pad(minutes)}`;
9910
+ return /* @__PURE__ */ jsxRuntime.jsx(
9911
+ "div",
9912
+ {
9913
+ "data-react-fancy-time-picker": "",
9914
+ "data-mode": "view",
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,
9926
+ className: cn(
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",
9929
+ className
9930
+ ),
9931
+ children: formatted
9932
+ }
9933
+ );
9934
+ }
9388
9935
  const updateTime = react.useCallback(
9389
9936
  (hours, mins) => {
9390
9937
  setValue(`${pad(hours)}:${pad(mins)}`);
@@ -9415,6 +9962,9 @@ var TimePicker = react.forwardRef(
9415
9962
  {
9416
9963
  "data-react-fancy-time-picker": "",
9417
9964
  ref,
9965
+ onBlur: (e) => {
9966
+ if (interactive && !e.currentTarget.contains(e.relatedTarget)) exitEdit();
9967
+ },
9418
9968
  className: cn(
9419
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",
9420
9970
  disabled && "opacity-50",
@@ -9422,7 +9972,7 @@ var TimePicker = react.forwardRef(
9422
9972
  ),
9423
9973
  children: [
9424
9974
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center", children: [
9425
- /* @__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 }) }),
9426
9976
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: displayClass, children: pad(displayHour) }),
9427
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 }) })
9428
9978
  ] }),
@@ -13730,6 +14280,7 @@ exports.Composer = Composer;
13730
14280
  exports.ContentRenderer = ContentRenderer;
13731
14281
  exports.ContextMenu = ContextMenu;
13732
14282
  exports.DatePicker = DatePicker;
14283
+ exports.DisplayValue = DisplayValue;
13733
14284
  exports.Dropdown = Dropdown;
13734
14285
  exports.EMOJI_CATEGORY_ORDER = EMOJI_CATEGORY_ORDER;
13735
14286
  exports.EMOJI_DATA = EMOJI_DATA;
@@ -13739,7 +14290,10 @@ exports.Emoji = Emoji;
13739
14290
  exports.EmojiSelect = EmojiSelect;
13740
14291
  exports.FauxClient = FauxClient;
13741
14292
  exports.Field = Field;
14293
+ exports.FieldModeContext = FieldModeContext;
13742
14294
  exports.FileUpload = FileUpload;
14295
+ exports.Form = Form;
14296
+ exports.FormProvider = FormProvider;
13743
14297
  exports.Heading = Heading;
13744
14298
  exports.Icon = Icon;
13745
14299
  exports.Input = Input;
@@ -13809,6 +14363,7 @@ exports.useControllableState = useControllableState;
13809
14363
  exports.useDropdown = useDropdown;
13810
14364
  exports.useEditor = useEditor;
13811
14365
  exports.useEscapeKey = useEscapeKey;
14366
+ exports.useFieldMode = useFieldMode;
13812
14367
  exports.useFileUpload = useFileUpload;
13813
14368
  exports.useFloatingPosition = useFloatingPosition;
13814
14369
  exports.useFocusTrap = useFocusTrap;