@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.js CHANGED
@@ -3077,6 +3077,68 @@ function InputWrapper({
3077
3077
  hasInsideSuffix && /* @__PURE__ */ jsx("span", { className: "pointer-events-none absolute right-3 z-10 text-zinc-400 dark:text-zinc-500", children: suffix })
3078
3078
  ] });
3079
3079
  }
3080
+ var FieldModeContext = createContext(null);
3081
+ function useFieldMode(explicit) {
3082
+ const ctx = useContext(FieldModeContext);
3083
+ return explicit ?? ctx?.mode ?? "edit";
3084
+ }
3085
+ function isEmpty(value) {
3086
+ return value === null || value === void 0 || value === "";
3087
+ }
3088
+ function DisplayValue({
3089
+ children,
3090
+ size = "md",
3091
+ leading,
3092
+ trailing,
3093
+ empty = "\u2014",
3094
+ interactive,
3095
+ onActivate,
3096
+ className
3097
+ }) {
3098
+ const empties = isEmpty(children);
3099
+ const onKeyDown = interactive ? (e) => {
3100
+ if (e.key === "Enter" || e.key === " ") {
3101
+ e.preventDefault();
3102
+ onActivate?.();
3103
+ }
3104
+ } : void 0;
3105
+ return /* @__PURE__ */ jsxs(
3106
+ "div",
3107
+ {
3108
+ "data-react-fancy-display": "",
3109
+ "data-mode": "view",
3110
+ role: interactive ? "button" : void 0,
3111
+ tabIndex: interactive ? 0 : void 0,
3112
+ title: interactive ? "Click to edit" : void 0,
3113
+ onClick: interactive ? onActivate : void 0,
3114
+ onKeyDown,
3115
+ className: cn(
3116
+ "flex w-full items-center gap-2 text-zinc-900 dark:text-zinc-100",
3117
+ inputSizeClasses[size],
3118
+ // Strip the editable box look — keep only the size/padding rhythm.
3119
+ "border-0 bg-transparent px-0",
3120
+ interactive && "-mx-2 cursor-text rounded-md px-2 outline-none transition-colors hover:bg-zinc-100 focus-visible:ring-2 focus-visible:ring-blue-500/40 dark:hover:bg-zinc-800",
3121
+ empties && "text-zinc-400 dark:text-zinc-500",
3122
+ className
3123
+ ),
3124
+ children: [
3125
+ leading && /* @__PURE__ */ jsx("span", { className: "text-zinc-400 dark:text-zinc-500", children: leading }),
3126
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: empties ? empty : children }),
3127
+ trailing && /* @__PURE__ */ jsx("span", { className: "text-zinc-400 dark:text-zinc-500", children: trailing })
3128
+ ]
3129
+ }
3130
+ );
3131
+ }
3132
+ function useInlineEdit(mode, disabled) {
3133
+ const [editing, setEditing] = useState(false);
3134
+ const interactive = mode === "view" && !disabled;
3135
+ const showControl = mode !== "view" || interactive && editing;
3136
+ const enterEdit = useCallback(() => {
3137
+ if (interactive) setEditing(true);
3138
+ }, [interactive]);
3139
+ const exitEdit = useCallback(() => setEditing(false), []);
3140
+ return { showControl, interactive, enterEdit, exitEdit };
3141
+ }
3080
3142
  var Input = forwardRef(
3081
3143
  ({
3082
3144
  type = "text",
@@ -3095,13 +3157,26 @@ var Input = forwardRef(
3095
3157
  suffix,
3096
3158
  prefixPosition,
3097
3159
  suffixPosition,
3160
+ mode,
3098
3161
  onValueChange,
3099
3162
  onChange,
3100
3163
  ...props
3101
3164
  }, ref) => {
3102
3165
  const autoId = useId();
3103
3166
  const inputId = id ?? autoId;
3104
- const input = /* @__PURE__ */ jsx(
3167
+ const resolvedMode = useFieldMode(mode);
3168
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
3169
+ const input = !showControl ? /* @__PURE__ */ jsx(
3170
+ DisplayValue,
3171
+ {
3172
+ size,
3173
+ interactive,
3174
+ onActivate: enterEdit,
3175
+ leading: leading ?? prefix,
3176
+ trailing: trailing ?? suffix,
3177
+ children: type === "password" ? props.value ? "\u2022\u2022\u2022\u2022\u2022\u2022" : "" : props.value
3178
+ }
3179
+ ) : /* @__PURE__ */ jsx(
3105
3180
  InputWrapper,
3106
3181
  {
3107
3182
  prefix,
@@ -3133,7 +3208,12 @@ var Input = forwardRef(
3133
3208
  onChange?.(e);
3134
3209
  onValueChange?.(e.target.value);
3135
3210
  },
3136
- ...props
3211
+ ...props,
3212
+ autoFocus: interactive || props.autoFocus,
3213
+ onBlur: (e) => {
3214
+ props.onBlur?.(e);
3215
+ if (interactive) exitEdit();
3216
+ }
3137
3217
  }
3138
3218
  ),
3139
3219
  trailing && /* @__PURE__ */ jsx("span", { className: "pointer-events-none absolute right-3 text-zinc-400 dark:text-zinc-500", children: trailing })
@@ -3176,6 +3256,7 @@ var Textarea = forwardRef(
3176
3256
  suffix,
3177
3257
  prefixPosition: _prefixPosition,
3178
3258
  suffixPosition: _suffixPosition,
3259
+ mode,
3179
3260
  onValueChange,
3180
3261
  onChange,
3181
3262
  value,
@@ -3185,6 +3266,8 @@ var Textarea = forwardRef(
3185
3266
  const autoId = useId();
3186
3267
  const textareaId = id ?? autoId;
3187
3268
  const internalRef = useRef(null);
3269
+ const resolvedMode = useFieldMode(mode);
3270
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
3188
3271
  useEffect(() => {
3189
3272
  const el = internalRef.current;
3190
3273
  if (!autoResize || !el) return;
@@ -3194,7 +3277,16 @@ var Textarea = forwardRef(
3194
3277
  const maxHeight = maxRows ? maxRows * lineHeight : Infinity;
3195
3278
  el.style.height = `${Math.min(Math.max(el.scrollHeight, minHeight), maxHeight)}px`;
3196
3279
  }, [autoResize, minRows, maxRows, value, defaultValue]);
3197
- const textarea = /* @__PURE__ */ jsx(
3280
+ const textarea = !showControl ? /* @__PURE__ */ jsx(
3281
+ DisplayValue,
3282
+ {
3283
+ size,
3284
+ className: "whitespace-pre-wrap",
3285
+ interactive,
3286
+ onActivate: enterEdit,
3287
+ children: value ?? defaultValue
3288
+ }
3289
+ ) : /* @__PURE__ */ jsx(
3198
3290
  InputWrapper,
3199
3291
  {
3200
3292
  prefix,
@@ -3233,7 +3325,12 @@ var Textarea = forwardRef(
3233
3325
  onChange?.(e);
3234
3326
  onValueChange?.(e.target.value);
3235
3327
  },
3236
- ...props
3328
+ ...props,
3329
+ autoFocus: interactive || props.autoFocus,
3330
+ onBlur: (e) => {
3331
+ props.onBlur?.(e);
3332
+ if (interactive) exitEdit();
3333
+ }
3237
3334
  }
3238
3335
  )
3239
3336
  }
@@ -3466,6 +3563,7 @@ var NativeSelect = forwardRef(
3466
3563
  suffix,
3467
3564
  prefixPosition,
3468
3565
  suffixPosition,
3566
+ mode,
3469
3567
  onValueChange,
3470
3568
  onChange,
3471
3569
  value,
@@ -3474,6 +3572,38 @@ var NativeSelect = forwardRef(
3474
3572
  }, ref) => {
3475
3573
  const autoId = useId();
3476
3574
  const selectId = id ?? autoId;
3575
+ const resolvedMode = useFieldMode(mode);
3576
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
3577
+ if (!showControl) {
3578
+ const current = value ?? defaultValue;
3579
+ const opt = flattenOptions(list).map((o) => resolveOption(o)).find((o) => o.value === current);
3580
+ const display = /* @__PURE__ */ jsx(
3581
+ DisplayValue,
3582
+ {
3583
+ size,
3584
+ leading: prefix,
3585
+ trailing: suffix,
3586
+ interactive,
3587
+ onActivate: enterEdit,
3588
+ children: opt?.label ?? current
3589
+ }
3590
+ );
3591
+ if (label || error || description) {
3592
+ return /* @__PURE__ */ jsx(
3593
+ Field,
3594
+ {
3595
+ label,
3596
+ description,
3597
+ error,
3598
+ required,
3599
+ htmlFor: selectId,
3600
+ size,
3601
+ children: display
3602
+ }
3603
+ );
3604
+ }
3605
+ return display;
3606
+ }
3477
3607
  const isControlled = value !== void 0;
3478
3608
  const resolvedDefault = !isControlled && defaultValue === void 0 && placeholder ? "" : defaultValue;
3479
3609
  const select = /* @__PURE__ */ jsx(
@@ -3508,6 +3638,11 @@ var NativeSelect = forwardRef(
3508
3638
  },
3509
3639
  ...props,
3510
3640
  ...isControlled ? { value } : { defaultValue: resolvedDefault },
3641
+ autoFocus: interactive || props.autoFocus,
3642
+ onBlur: (e) => {
3643
+ props.onBlur?.(e);
3644
+ if (interactive) exitEdit();
3645
+ },
3511
3646
  children: [
3512
3647
  placeholder && /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: placeholder }),
3513
3648
  list.map(
@@ -3562,11 +3697,14 @@ var ListboxSelect = forwardRef(
3562
3697
  createLabel = "Create",
3563
3698
  selectedSuffix = "selected",
3564
3699
  indicator = "check",
3700
+ mode,
3565
3701
  value: controlledSingleValue,
3566
3702
  defaultValue: defaultSingleValue
3567
3703
  }, _ref) => {
3568
3704
  const autoId = useId();
3569
3705
  const selectId = id ?? autoId;
3706
+ const resolvedMode = useFieldMode(mode);
3707
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
3570
3708
  const textInputEnabled = searchable || creatable;
3571
3709
  const [open, setOpen] = useState(false);
3572
3710
  const [search2, setSearch] = useState("");
@@ -3693,6 +3831,27 @@ var ListboxSelect = forwardRef(
3693
3831
  }
3694
3832
  }
3695
3833
  };
3834
+ if (!showControl) {
3835
+ const labels = multiple ? currentMulti.map(
3836
+ (v) => resolvedOptions.find((o) => o.value === v)?.label ?? v
3837
+ ) : currentSingle ? [resolvedOptions.find((o) => o.value === currentSingle)?.label ?? currentSingle] : [];
3838
+ const display = /* @__PURE__ */ jsx(DisplayValue, { size, interactive, onActivate: enterEdit, children: labels.join(", ") });
3839
+ if (label || error || description) {
3840
+ return /* @__PURE__ */ jsx(
3841
+ Field,
3842
+ {
3843
+ label,
3844
+ description,
3845
+ error,
3846
+ required,
3847
+ htmlFor: selectId,
3848
+ size,
3849
+ children: display
3850
+ }
3851
+ );
3852
+ }
3853
+ return display;
3854
+ }
3696
3855
  const trigger = /* @__PURE__ */ jsxs(
3697
3856
  "button",
3698
3857
  {
@@ -3700,6 +3859,7 @@ var ListboxSelect = forwardRef(
3700
3859
  type: "button",
3701
3860
  id: selectId,
3702
3861
  disabled,
3862
+ autoFocus: interactive,
3703
3863
  onClick: () => setOpen((o) => !o),
3704
3864
  onKeyDown: handleKeyDown,
3705
3865
  role: "combobox",
@@ -3821,10 +3981,21 @@ var ListboxSelect = forwardRef(
3821
3981
  ]
3822
3982
  }
3823
3983
  ) });
3824
- const content = /* @__PURE__ */ jsxs("div", { className: "relative", children: [
3825
- trigger,
3826
- dropdown
3827
- ] });
3984
+ const content = /* @__PURE__ */ jsxs(
3985
+ "div",
3986
+ {
3987
+ className: "relative",
3988
+ onBlur: (e) => {
3989
+ if (interactive && !open && !e.currentTarget.contains(e.relatedTarget)) {
3990
+ exitEdit();
3991
+ }
3992
+ },
3993
+ children: [
3994
+ trigger,
3995
+ dropdown
3996
+ ]
3997
+ }
3998
+ );
3828
3999
  if (label || error || description) {
3829
4000
  return /* @__PURE__ */ jsx(
3830
4001
  Field,
@@ -3868,11 +4039,14 @@ var Checkbox = forwardRef(
3868
4039
  checked: controlledChecked,
3869
4040
  defaultChecked = false,
3870
4041
  onCheckedChange,
3871
- indeterminate
4042
+ indeterminate,
4043
+ mode
3872
4044
  }, ref) => {
3873
4045
  const autoId = useId();
3874
4046
  const checkboxId = id ?? autoId;
3875
4047
  const internalRef = useRef(null);
4048
+ const resolvedMode = useFieldMode(mode);
4049
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
3876
4050
  const [checked, setChecked] = useControllableState(
3877
4051
  controlledChecked,
3878
4052
  defaultChecked,
@@ -3891,8 +4065,32 @@ var Checkbox = forwardRef(
3891
4065
  lg: "h-5 w-5",
3892
4066
  xl: "h-6 w-6"
3893
4067
  }[size];
4068
+ const glyph = indeterminate ? "\u2014" : checked ? "\u2713" : "\u2715";
3894
4069
  return /* @__PURE__ */ jsxs("div", { "data-react-fancy-checkbox": "", className: cn("flex items-start gap-2", className), children: [
3895
- /* @__PURE__ */ jsx("div", { className: "relative flex items-center", children: /* @__PURE__ */ jsx(
4070
+ !showControl ? /* @__PURE__ */ jsx(
4071
+ "span",
4072
+ {
4073
+ "data-react-fancy-display": "",
4074
+ "data-mode": "view",
4075
+ role: interactive ? "button" : void 0,
4076
+ tabIndex: interactive ? 0 : void 0,
4077
+ title: interactive ? "Click to edit" : void 0,
4078
+ onClick: interactive ? enterEdit : void 0,
4079
+ onKeyDown: interactive ? (e) => {
4080
+ if (e.key === "Enter" || e.key === " ") {
4081
+ e.preventDefault();
4082
+ enterEdit();
4083
+ }
4084
+ } : void 0,
4085
+ className: cn(
4086
+ "flex shrink-0 items-center justify-center text-zinc-700 dark:text-zinc-200",
4087
+ sizeClasses6,
4088
+ interactive && "cursor-pointer rounded outline-none transition-colors hover:text-zinc-900 focus-visible:ring-2 focus-visible:ring-blue-500/40 dark:hover:text-white"
4089
+ ),
4090
+ "aria-hidden": interactive ? void 0 : "true",
4091
+ children: glyph
4092
+ }
4093
+ ) : /* @__PURE__ */ jsx("div", { className: "relative flex items-center", children: /* @__PURE__ */ jsx(
3896
4094
  "input",
3897
4095
  {
3898
4096
  ref: (node) => {
@@ -3909,6 +4107,10 @@ var Checkbox = forwardRef(
3909
4107
  disabled,
3910
4108
  required,
3911
4109
  checked,
4110
+ autoFocus: interactive,
4111
+ onBlur: () => {
4112
+ if (interactive) exitEdit();
4113
+ },
3912
4114
  onChange: (e) => setChecked(e.target.checked),
3913
4115
  className: cn(
3914
4116
  sizeClasses6,
@@ -3954,9 +4156,12 @@ function CheckboxGroup({
3954
4156
  value: controlledValue,
3955
4157
  defaultValue = [],
3956
4158
  onValueChange,
3957
- orientation = "vertical"
4159
+ orientation = "vertical",
4160
+ mode
3958
4161
  }) {
3959
4162
  const groupId = useId();
4163
+ const resolvedMode = useFieldMode(mode);
4164
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
3960
4165
  const [value, setValue] = useControllableState(
3961
4166
  controlledValue,
3962
4167
  defaultValue,
@@ -3973,10 +4178,13 @@ function CheckboxGroup({
3973
4178
  lg: "h-5 w-5",
3974
4179
  xl: "h-6 w-6"
3975
4180
  }[size];
3976
- const content = /* @__PURE__ */ jsx(
4181
+ const content = !showControl ? /* @__PURE__ */ jsx(DisplayValue, { size, interactive, onActivate: enterEdit, children: list.map(resolveOption).filter((o) => value.includes(o.value)).map((o) => o.label).join(", ") }) : /* @__PURE__ */ jsx(
3977
4182
  "div",
3978
4183
  {
3979
4184
  "data-react-fancy-checkbox-group": "",
4185
+ onBlur: (e) => {
4186
+ if (interactive && !e.currentTarget.contains(e.relatedTarget)) exitEdit();
4187
+ },
3980
4188
  className: cn(
3981
4189
  "flex gap-3",
3982
4190
  orientation === "vertical" ? "flex-col" : "flex-row flex-wrap",
@@ -3996,6 +4204,7 @@ function CheckboxGroup({
3996
4204
  value: String(resolved.value),
3997
4205
  checked: isChecked,
3998
4206
  disabled: disabled || resolved.disabled,
4207
+ autoFocus: interactive && index === 0,
3999
4208
  onChange: () => handleToggle(resolved.value),
4000
4209
  className: cn(
4001
4210
  sizeClasses6,
@@ -4053,10 +4262,13 @@ function RadioGroup({
4053
4262
  value: controlledValue,
4054
4263
  defaultValue,
4055
4264
  onValueChange,
4056
- orientation = "vertical"
4265
+ orientation = "vertical",
4266
+ mode
4057
4267
  }) {
4058
4268
  const groupId = useId();
4059
4269
  const radioName = name ?? groupId;
4270
+ const resolvedMode = useFieldMode(mode);
4271
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
4060
4272
  const [value, setValue] = useControllableState(
4061
4273
  controlledValue,
4062
4274
  defaultValue,
@@ -4069,11 +4281,15 @@ function RadioGroup({
4069
4281
  lg: "h-5 w-5",
4070
4282
  xl: "h-6 w-6"
4071
4283
  }[size];
4072
- const content = /* @__PURE__ */ jsx(
4284
+ const selectedIndex = list.map(resolveOption).findIndex((o) => o.value === value);
4285
+ const content = !showControl ? /* @__PURE__ */ jsx(DisplayValue, { size, interactive, onActivate: enterEdit, children: list.map(resolveOption).find((o) => o.value === value)?.label }) : /* @__PURE__ */ jsx(
4073
4286
  "div",
4074
4287
  {
4075
4288
  "data-react-fancy-radio-group": "",
4076
4289
  role: "radiogroup",
4290
+ onBlur: (e) => {
4291
+ if (interactive && !e.currentTarget.contains(e.relatedTarget)) exitEdit();
4292
+ },
4077
4293
  className: cn(
4078
4294
  "flex gap-3",
4079
4295
  orientation === "vertical" ? "flex-col" : "flex-row flex-wrap",
@@ -4083,6 +4299,7 @@ function RadioGroup({
4083
4299
  const resolved = resolveOption(option);
4084
4300
  const optionId = `${groupId}-${index}`;
4085
4301
  const isSelected = value === resolved.value;
4302
+ const shouldAutoFocus = interactive && (selectedIndex === -1 ? index === 0 : isSelected);
4086
4303
  return /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
4087
4304
  /* @__PURE__ */ jsx(
4088
4305
  "input",
@@ -4093,6 +4310,7 @@ function RadioGroup({
4093
4310
  value: String(resolved.value),
4094
4311
  checked: isSelected,
4095
4312
  disabled: disabled || resolved.disabled,
4313
+ autoFocus: shouldAutoFocus,
4096
4314
  onChange: () => setValue(resolved.value),
4097
4315
  className: cn(
4098
4316
  sizeClasses6,
@@ -4177,10 +4395,13 @@ var Switch = forwardRef(
4177
4395
  checked: controlledChecked,
4178
4396
  defaultChecked = false,
4179
4397
  onCheckedChange,
4180
- color = "blue"
4398
+ color = "blue",
4399
+ mode
4181
4400
  }, ref) => {
4182
4401
  const autoId = useId();
4183
4402
  const switchId = id ?? autoId;
4403
+ const resolvedMode = useFieldMode(mode);
4404
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
4184
4405
  const [checked, setChecked] = useControllableState(
4185
4406
  controlledChecked,
4186
4407
  defaultChecked,
@@ -4208,7 +4429,28 @@ var Switch = forwardRef(
4208
4429
  xl: "translate-x-6"
4209
4430
  }[size];
4210
4431
  return /* @__PURE__ */ jsxs("div", { "data-react-fancy-switch": "", className: cn("flex items-start gap-2", className), children: [
4211
- /* @__PURE__ */ jsx(
4432
+ !showControl ? /* @__PURE__ */ jsx(
4433
+ "span",
4434
+ {
4435
+ "data-react-fancy-display": "",
4436
+ "data-mode": "view",
4437
+ role: interactive ? "button" : void 0,
4438
+ tabIndex: interactive ? 0 : void 0,
4439
+ title: interactive ? "Click to edit" : void 0,
4440
+ onClick: interactive ? enterEdit : void 0,
4441
+ onKeyDown: interactive ? (e) => {
4442
+ if (e.key === "Enter" || e.key === " ") {
4443
+ e.preventDefault();
4444
+ enterEdit();
4445
+ }
4446
+ } : void 0,
4447
+ className: cn(
4448
+ "text-sm font-medium text-zinc-700 dark:text-zinc-200",
4449
+ interactive && "cursor-pointer rounded-md outline-none transition-colors hover:text-zinc-900 focus-visible:ring-2 focus-visible:ring-blue-500/40 dark:hover:text-white"
4450
+ ),
4451
+ children: checked ? "On" : "Off"
4452
+ }
4453
+ ) : /* @__PURE__ */ jsx(
4212
4454
  "button",
4213
4455
  {
4214
4456
  ref,
@@ -4217,6 +4459,10 @@ var Switch = forwardRef(
4217
4459
  role: "switch",
4218
4460
  "aria-checked": checked,
4219
4461
  disabled,
4462
+ autoFocus: interactive,
4463
+ onBlur: () => {
4464
+ if (interactive) exitEdit();
4465
+ },
4220
4466
  onClick: () => setChecked(!checked),
4221
4467
  className: cn(
4222
4468
  "relative inline-flex shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/40 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
@@ -4322,15 +4568,27 @@ var SingleSlider = forwardRef(
4322
4568
  marks,
4323
4569
  prefix,
4324
4570
  suffix,
4571
+ mode,
4325
4572
  ...rest
4326
4573
  }, ref) => {
4327
4574
  const singleProps = rest;
4575
+ const resolvedMode = useFieldMode(mode);
4576
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
4328
4577
  const [value, setValue] = useControllableState(
4329
4578
  singleProps.value,
4330
4579
  singleProps.defaultValue ?? min,
4331
4580
  singleProps.onValueChange
4332
4581
  );
4333
- const slider = /* @__PURE__ */ jsxs("div", { "data-react-fancy-slider": "", className: cn("flex flex-col gap-1", className), children: [
4582
+ const slider = !showControl ? /* @__PURE__ */ jsx(
4583
+ DisplayValue,
4584
+ {
4585
+ size,
4586
+ className,
4587
+ interactive,
4588
+ onActivate: enterEdit,
4589
+ children: `${prefix ?? ""}${value}${suffix ?? ""}`
4590
+ }
4591
+ ) : /* @__PURE__ */ jsxs("div", { "data-react-fancy-slider": "", className: cn("flex flex-col gap-1", className), children: [
4334
4592
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
4335
4593
  /* @__PURE__ */ jsx(
4336
4594
  "input",
@@ -4344,6 +4602,10 @@ var SingleSlider = forwardRef(
4344
4602
  step,
4345
4603
  value,
4346
4604
  disabled,
4605
+ autoFocus: interactive,
4606
+ onBlur: () => {
4607
+ if (interactive) exitEdit();
4608
+ },
4347
4609
  onChange: (e) => setValue(Number(e.target.value)),
4348
4610
  className: cn(
4349
4611
  "w-full cursor-pointer accent-blue-600 disabled:cursor-not-allowed disabled:opacity-50",
@@ -4392,9 +4654,12 @@ var RangeSlider = forwardRef(
4392
4654
  marks,
4393
4655
  prefix,
4394
4656
  suffix,
4657
+ mode,
4395
4658
  ...rest
4396
4659
  }, ref) => {
4397
4660
  const rangeProps = rest;
4661
+ const resolvedMode = useFieldMode(mode);
4662
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
4398
4663
  const [value, setValue] = useControllableState(
4399
4664
  rangeProps.value,
4400
4665
  rangeProps.defaultValue ?? [min, max],
@@ -4408,61 +4673,81 @@ var RangeSlider = forwardRef(
4408
4673
  };
4409
4674
  const leftPercent = (value[0] - min) / (max - min) * 100;
4410
4675
  const rightPercent = (value[1] - min) / (max - min) * 100;
4411
- const slider = /* @__PURE__ */ jsxs("div", { "data-react-fancy-slider": "", className: cn("flex flex-col gap-1", className), children: [
4412
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
4413
- /* @__PURE__ */ jsxs("div", { className: "relative w-full", children: [
4414
- /* @__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" }),
4415
- /* @__PURE__ */ jsx(
4416
- "div",
4417
- {
4418
- className: "pointer-events-none absolute top-1/2 h-1.5 -translate-y-1/2 rounded-full bg-blue-500",
4419
- style: { left: `${leftPercent}%`, width: `${rightPercent - leftPercent}%` }
4420
- }
4421
- ),
4422
- /* @__PURE__ */ jsx(
4423
- "input",
4424
- {
4425
- ref,
4426
- type: "range",
4427
- min,
4428
- max,
4429
- step,
4430
- value: value[0],
4431
- disabled,
4432
- onChange: (e) => handleMin(Number(e.target.value)),
4433
- className: cn(
4434
- "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",
4435
- dirtyRingClasses(dirty)
4436
- )
4437
- }
4438
- ),
4439
- /* @__PURE__ */ jsx(
4440
- "input",
4441
- {
4442
- type: "range",
4443
- min,
4444
- max,
4445
- step,
4446
- value: value[1],
4447
- disabled,
4448
- onChange: (e) => handleMax(Number(e.target.value)),
4449
- 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"
4450
- }
4451
- ),
4452
- /* @__PURE__ */ jsx("div", { className: "h-6" })
4453
- ] }),
4454
- 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: [
4455
- prefix,
4456
- value[0],
4457
- suffix,
4458
- "\u2013",
4459
- prefix,
4460
- value[1],
4461
- suffix
4462
- ] })
4463
- ] }),
4464
- marks && marks.length > 0 && /* @__PURE__ */ jsx(SliderMarks, { marks, min, max })
4465
- ] });
4676
+ const slider = !showControl ? /* @__PURE__ */ jsx(
4677
+ DisplayValue,
4678
+ {
4679
+ size,
4680
+ className,
4681
+ interactive,
4682
+ onActivate: enterEdit,
4683
+ children: `${prefix ?? ""}${value[0]}${suffix ?? ""}\u2013${prefix ?? ""}${value[1]}${suffix ?? ""}`
4684
+ }
4685
+ ) : /* @__PURE__ */ jsxs(
4686
+ "div",
4687
+ {
4688
+ "data-react-fancy-slider": "",
4689
+ className: cn("flex flex-col gap-1", className),
4690
+ onBlur: (e) => {
4691
+ if (interactive && !e.currentTarget.contains(e.relatedTarget)) exitEdit();
4692
+ },
4693
+ children: [
4694
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
4695
+ /* @__PURE__ */ jsxs("div", { className: "relative w-full", children: [
4696
+ /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute top-1/2 h-1.5 w-full -translate-y-1/2 rounded-full bg-zinc-200 dark:bg-zinc-700" }),
4697
+ /* @__PURE__ */ jsx(
4698
+ "div",
4699
+ {
4700
+ className: "pointer-events-none absolute top-1/2 h-1.5 -translate-y-1/2 rounded-full bg-blue-500",
4701
+ style: { left: `${leftPercent}%`, width: `${rightPercent - leftPercent}%` }
4702
+ }
4703
+ ),
4704
+ /* @__PURE__ */ jsx(
4705
+ "input",
4706
+ {
4707
+ ref,
4708
+ type: "range",
4709
+ min,
4710
+ max,
4711
+ step,
4712
+ value: value[0],
4713
+ disabled,
4714
+ autoFocus: interactive,
4715
+ onChange: (e) => handleMin(Number(e.target.value)),
4716
+ className: cn(
4717
+ "pointer-events-none absolute w-full cursor-pointer appearance-none bg-transparent [&::-webkit-slider-thumb]:pointer-events-auto [&::-webkit-slider-thumb]:h-4 [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-blue-600 [&::-webkit-slider-thumb]:shadow",
4718
+ dirtyRingClasses(dirty)
4719
+ )
4720
+ }
4721
+ ),
4722
+ /* @__PURE__ */ jsx(
4723
+ "input",
4724
+ {
4725
+ type: "range",
4726
+ min,
4727
+ max,
4728
+ step,
4729
+ value: value[1],
4730
+ disabled,
4731
+ onChange: (e) => handleMax(Number(e.target.value)),
4732
+ className: "pointer-events-none absolute w-full cursor-pointer appearance-none bg-transparent [&::-webkit-slider-thumb]:pointer-events-auto [&::-webkit-slider-thumb]:h-4 [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-blue-600 [&::-webkit-slider-thumb]:shadow"
4733
+ }
4734
+ ),
4735
+ /* @__PURE__ */ jsx("div", { className: "h-6" })
4736
+ ] }),
4737
+ showValue && /* @__PURE__ */ jsxs("span", { className: "min-w-[6ch] shrink-0 whitespace-nowrap text-right text-sm font-medium text-zinc-700 dark:text-zinc-300", children: [
4738
+ prefix,
4739
+ value[0],
4740
+ suffix,
4741
+ "\u2013",
4742
+ prefix,
4743
+ value[1],
4744
+ suffix
4745
+ ] })
4746
+ ] }),
4747
+ marks && marks.length > 0 && /* @__PURE__ */ jsx(SliderMarks, { marks, min, max })
4748
+ ]
4749
+ }
4750
+ );
4466
4751
  if (label || error || description) {
4467
4752
  return /* @__PURE__ */ jsx(Field, { label, description, error, required, htmlFor: id, size, children: slider });
4468
4753
  }
@@ -4510,10 +4795,13 @@ function MultiSwitch({
4510
4795
  value: controlledValue,
4511
4796
  defaultValue,
4512
4797
  onValueChange,
4513
- linear
4798
+ linear,
4799
+ mode
4514
4800
  }) {
4515
4801
  const resolvedOptions = list.map(resolveOption);
4516
4802
  const fallback = defaultValue ?? resolvedOptions[0]?.value;
4803
+ const resolvedMode = useFieldMode(mode);
4804
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
4517
4805
  const [value, setValue] = useControllableState(controlledValue, fallback, onValueChange);
4518
4806
  const containerRef = useRef(null);
4519
4807
  const itemRefs = useRef([]);
@@ -4538,13 +4826,16 @@ function MultiSwitch({
4538
4826
  useEffect(() => {
4539
4827
  updateIndicator();
4540
4828
  }, [updateIndicator]);
4541
- const control = /* @__PURE__ */ jsxs(
4829
+ const control = !showControl ? /* @__PURE__ */ jsx(DisplayValue, { size, interactive, onActivate: enterEdit, children: resolvedOptions.find((o) => o.value === value)?.label }) : /* @__PURE__ */ jsxs(
4542
4830
  "div",
4543
4831
  {
4544
4832
  ref: containerRef,
4545
4833
  "data-react-fancy-multi-switch": "",
4546
4834
  role: "radiogroup",
4547
4835
  id,
4836
+ onBlur: (e) => {
4837
+ if (interactive && !e.currentTarget.contains(e.relatedTarget)) exitEdit();
4838
+ },
4548
4839
  className: cn(
4549
4840
  "relative inline-flex rounded-lg border border-zinc-300 bg-zinc-100 dark:border-zinc-700 dark:bg-zinc-800",
4550
4841
  dirty && "ring-2 ring-amber-400/50",
@@ -4572,6 +4863,7 @@ function MultiSwitch({
4572
4863
  role: "radio",
4573
4864
  "aria-checked": isSelected,
4574
4865
  disabled: disabled || option.disabled,
4866
+ autoFocus: interactive && (selectedIndex === -1 ? index === 0 : isSelected),
4575
4867
  onClick: () => {
4576
4868
  if (linear) {
4577
4869
  const nextIndex = (selectedIndex + 1) % resolvedOptions.length;
@@ -4601,6 +4893,22 @@ function MultiSwitch({
4601
4893
  return control;
4602
4894
  }
4603
4895
  MultiSwitch.displayName = "MultiSwitch";
4896
+ function formatDateValue(iso, includeTime) {
4897
+ if (!iso) return "";
4898
+ const date = new Date(iso);
4899
+ if (Number.isNaN(date.getTime())) return iso;
4900
+ return includeTime ? date.toLocaleString(void 0, {
4901
+ year: "numeric",
4902
+ month: "short",
4903
+ day: "numeric",
4904
+ hour: "2-digit",
4905
+ minute: "2-digit"
4906
+ }) : date.toLocaleDateString(void 0, {
4907
+ year: "numeric",
4908
+ month: "short",
4909
+ day: "numeric"
4910
+ });
4911
+ }
4604
4912
  var DatePicker = forwardRef(
4605
4913
  (props, ref) => {
4606
4914
  const {
@@ -4667,17 +4975,30 @@ var SingleDatePicker = forwardRef(
4667
4975
  name,
4668
4976
  min,
4669
4977
  max,
4978
+ includeTime,
4979
+ mode,
4670
4980
  inputType,
4671
4981
  inputClasses,
4672
4982
  ...rest
4673
4983
  }, ref) => {
4674
4984
  const singleProps = rest;
4985
+ const resolvedMode = useFieldMode(mode);
4986
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
4675
4987
  const [value, setValue] = useControllableState(
4676
4988
  singleProps.value,
4677
4989
  singleProps.defaultValue ?? "",
4678
4990
  singleProps.onValueChange
4679
4991
  );
4680
- const input = /* @__PURE__ */ jsx(
4992
+ const input = !showControl ? /* @__PURE__ */ jsx(
4993
+ DisplayValue,
4994
+ {
4995
+ size,
4996
+ className,
4997
+ interactive,
4998
+ onActivate: enterEdit,
4999
+ children: formatDateValue(value, includeTime)
5000
+ }
5001
+ ) : /* @__PURE__ */ jsx(
4681
5002
  "input",
4682
5003
  {
4683
5004
  "data-react-fancy-date-picker": "",
@@ -4691,7 +5012,11 @@ var SingleDatePicker = forwardRef(
4691
5012
  disabled,
4692
5013
  required,
4693
5014
  onChange: (e) => setValue(e.target.value),
4694
- className: cn(inputClasses, className)
5015
+ className: cn(inputClasses, className),
5016
+ autoFocus: interactive,
5017
+ onBlur: () => {
5018
+ if (interactive) exitEdit();
5019
+ }
4695
5020
  }
4696
5021
  );
4697
5022
  if (label || error || description) {
@@ -4725,48 +5050,72 @@ var RangeDatePicker = forwardRef(
4725
5050
  name,
4726
5051
  min,
4727
5052
  max,
5053
+ includeTime,
5054
+ mode,
4728
5055
  inputType,
4729
5056
  inputClasses,
4730
5057
  ...rest
4731
5058
  }, ref) => {
4732
5059
  const rangeProps = rest;
5060
+ const resolvedMode = useFieldMode(mode);
5061
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
4733
5062
  const [value, setValue] = useControllableState(
4734
5063
  rangeProps.value,
4735
5064
  rangeProps.defaultValue ?? ["", ""],
4736
5065
  rangeProps.onValueChange
4737
5066
  );
4738
- const input = /* @__PURE__ */ jsxs("div", { "data-react-fancy-date-picker": "", className: cn("flex items-center gap-2", className), children: [
4739
- /* @__PURE__ */ jsx(
4740
- "input",
4741
- {
4742
- ref,
4743
- id,
4744
- type: inputType,
4745
- name: name ? `${name}_start` : void 0,
4746
- min,
4747
- max: value[1] || max,
4748
- value: value[0],
4749
- disabled,
4750
- required,
4751
- onChange: (e) => setValue([e.target.value, value[1]]),
4752
- className: inputClasses
4753
- }
4754
- ),
4755
- /* @__PURE__ */ jsx("span", { className: "text-sm text-zinc-500 dark:text-zinc-400", children: "to" }),
4756
- /* @__PURE__ */ jsx(
4757
- "input",
4758
- {
4759
- type: inputType,
4760
- name: name ? `${name}_end` : void 0,
4761
- min: value[0] || min,
4762
- max,
4763
- value: value[1],
4764
- disabled,
4765
- onChange: (e) => setValue([value[0], e.target.value]),
4766
- className: inputClasses
4767
- }
4768
- )
4769
- ] });
5067
+ const input = !showControl ? /* @__PURE__ */ jsx(
5068
+ DisplayValue,
5069
+ {
5070
+ size,
5071
+ className,
5072
+ interactive,
5073
+ onActivate: enterEdit,
5074
+ children: value[0] || value[1] ? `${formatDateValue(value[0], includeTime)} \u2013 ${formatDateValue(value[1], includeTime)}` : ""
5075
+ }
5076
+ ) : /* @__PURE__ */ jsxs(
5077
+ "div",
5078
+ {
5079
+ "data-react-fancy-date-picker": "",
5080
+ className: cn("flex items-center gap-2", className),
5081
+ onBlur: (e) => {
5082
+ if (interactive && !e.currentTarget.contains(e.relatedTarget)) exitEdit();
5083
+ },
5084
+ children: [
5085
+ /* @__PURE__ */ jsx(
5086
+ "input",
5087
+ {
5088
+ ref,
5089
+ id,
5090
+ type: inputType,
5091
+ name: name ? `${name}_start` : void 0,
5092
+ min,
5093
+ max: value[1] || max,
5094
+ value: value[0],
5095
+ disabled,
5096
+ required,
5097
+ onChange: (e) => setValue([e.target.value, value[1]]),
5098
+ className: inputClasses,
5099
+ autoFocus: interactive
5100
+ }
5101
+ ),
5102
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-zinc-500 dark:text-zinc-400", children: "to" }),
5103
+ /* @__PURE__ */ jsx(
5104
+ "input",
5105
+ {
5106
+ type: inputType,
5107
+ name: name ? `${name}_end` : void 0,
5108
+ min: value[0] || min,
5109
+ max,
5110
+ value: value[1],
5111
+ disabled,
5112
+ onChange: (e) => setValue([value[0], e.target.value]),
5113
+ className: inputClasses
5114
+ }
5115
+ )
5116
+ ]
5117
+ }
5118
+ );
4770
5119
  if (label || error || description) {
4771
5120
  return /* @__PURE__ */ jsx(
4772
5121
  Field,
@@ -4785,6 +5134,13 @@ var RangeDatePicker = forwardRef(
4785
5134
  }
4786
5135
  );
4787
5136
  RangeDatePicker.displayName = "RangeDatePicker";
5137
+ function FormProvider({ mode = "edit", children }) {
5138
+ const value = useMemo(() => ({ mode }), [mode]);
5139
+ return /* @__PURE__ */ jsx(FieldModeContext.Provider, { value, children });
5140
+ }
5141
+ function Form({ mode, children, ...formProps }) {
5142
+ return /* @__PURE__ */ jsx(FormProvider, { mode, children: /* @__PURE__ */ jsx("form", { "data-react-fancy-form": "", ...formProps, children }) });
5143
+ }
4788
5144
  var CarouselContext = createContext(null);
4789
5145
  CarouselContext.displayName = "CarouselContext";
4790
5146
  function useCarousel() {
@@ -5124,13 +5480,16 @@ var ColorPicker = forwardRef(
5124
5480
  size = "md",
5125
5481
  variant = "outline",
5126
5482
  disabled = false,
5127
- className
5483
+ className,
5484
+ mode
5128
5485
  }, ref) => {
5129
5486
  const [color, setColor] = useControllableState(
5130
5487
  value,
5131
5488
  defaultValue,
5132
5489
  onChange
5133
5490
  );
5491
+ const resolvedMode = useFieldMode(mode);
5492
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
5134
5493
  const inputRef = useRef(null);
5135
5494
  const datalistId = useId();
5136
5495
  const handleChange = (e) => {
@@ -5141,18 +5500,72 @@ var ColorPicker = forwardRef(
5141
5500
  inputRef.current?.click();
5142
5501
  }
5143
5502
  };
5503
+ if (!showControl) {
5504
+ return /* @__PURE__ */ jsxs(
5505
+ "div",
5506
+ {
5507
+ ref,
5508
+ "data-react-fancy-color-picker": "",
5509
+ "data-mode": "view",
5510
+ role: interactive ? "button" : void 0,
5511
+ tabIndex: interactive ? 0 : void 0,
5512
+ title: interactive ? "Click to edit" : void 0,
5513
+ onClick: interactive ? enterEdit : void 0,
5514
+ onKeyDown: interactive ? (e) => {
5515
+ if (e.key === "Enter" || e.key === " ") {
5516
+ e.preventDefault();
5517
+ enterEdit();
5518
+ }
5519
+ } : void 0,
5520
+ className: cn(
5521
+ "inline-flex items-center gap-2",
5522
+ interactive && "cursor-pointer rounded-md outline-none transition-opacity hover:opacity-80 focus-visible:ring-2 focus-visible:ring-blue-500/40",
5523
+ className
5524
+ ),
5525
+ children: [
5526
+ /* @__PURE__ */ jsx(
5527
+ "span",
5528
+ {
5529
+ className: cn(
5530
+ "shrink-0 rounded-full",
5531
+ SWATCH_SIZES[size],
5532
+ variant === "outline" && "ring-1 ring-zinc-300 dark:ring-zinc-600"
5533
+ ),
5534
+ style: { backgroundColor: color },
5535
+ "aria-label": `Color: ${color}`
5536
+ }
5537
+ ),
5538
+ /* @__PURE__ */ jsx(
5539
+ "span",
5540
+ {
5541
+ className: cn(
5542
+ "select-all font-mono uppercase",
5543
+ TEXT_SIZES[size],
5544
+ "text-zinc-700 dark:text-zinc-300"
5545
+ ),
5546
+ children: color.toUpperCase()
5547
+ }
5548
+ )
5549
+ ]
5550
+ }
5551
+ );
5552
+ }
5144
5553
  return /* @__PURE__ */ jsxs(
5145
5554
  "div",
5146
5555
  {
5147
5556
  ref,
5148
5557
  "data-react-fancy-color-picker": "",
5149
5558
  className: cn("inline-flex items-center gap-2", className),
5559
+ onBlur: (e) => {
5560
+ if (interactive && !e.currentTarget.contains(e.relatedTarget)) exitEdit();
5561
+ },
5150
5562
  children: [
5151
5563
  /* @__PURE__ */ jsx(
5152
5564
  "button",
5153
5565
  {
5154
5566
  type: "button",
5155
5567
  disabled,
5568
+ autoFocus: interactive,
5156
5569
  onClick: handleSwatchClick,
5157
5570
  className: cn(
5158
5571
  "relative shrink-0 rounded-full transition-shadow",
@@ -6030,6 +6443,14 @@ var Badge = forwardRef(
6030
6443
  }
6031
6444
  );
6032
6445
  Badge.displayName = "Badge";
6446
+ var glowColors = {
6447
+ on: "#a855f7",
6448
+ // violet — neutral
6449
+ xp: "#22c55e",
6450
+ // green
6451
+ achievement: "#f59e0b"
6452
+ // amber
6453
+ };
6033
6454
  var containerSizeClasses = {
6034
6455
  xs: "h-6 w-6",
6035
6456
  sm: "h-8 w-8",
@@ -6064,8 +6485,10 @@ var Avatar = forwardRef(
6064
6485
  fallback,
6065
6486
  size = "md",
6066
6487
  status,
6488
+ glow,
6067
6489
  className
6068
6490
  }, ref) => {
6491
+ const glowKey = glow === true ? "on" : glow || null;
6069
6492
  return /* @__PURE__ */ jsxs(
6070
6493
  "div",
6071
6494
  {
@@ -6078,6 +6501,15 @@ var Avatar = forwardRef(
6078
6501
  className
6079
6502
  ),
6080
6503
  children: [
6504
+ glowKey && /* @__PURE__ */ jsx(
6505
+ "span",
6506
+ {
6507
+ "aria-hidden": true,
6508
+ "data-react-fancy-avatar-glow": glowKey,
6509
+ className: "fancy-avatar-glow",
6510
+ style: { "--fancy-glow": glowColors[glowKey] }
6511
+ }
6512
+ ),
6081
6513
  src ? /* @__PURE__ */ jsx(
6082
6514
  "img",
6083
6515
  {
@@ -8868,13 +9300,16 @@ var Autocomplete = forwardRef(
8868
9300
  loading = false,
8869
9301
  emptyMessage = "No results found.",
8870
9302
  disabled = false,
8871
- className
9303
+ className,
9304
+ mode
8872
9305
  }, ref) {
8873
9306
  const [value, setValue] = useControllableState(
8874
9307
  controlledValue,
8875
9308
  defaultValue,
8876
9309
  onChange
8877
9310
  );
9311
+ const resolvedMode = useFieldMode(mode);
9312
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
8878
9313
  const [query, setQuery] = useState(value);
8879
9314
  const [open, setOpen] = useState(false);
8880
9315
  const [activeIndex, setActiveIndex] = useState(-1);
@@ -8927,63 +9362,105 @@ var Autocomplete = forwardRef(
8927
9362
  if (item && !item.disabled) select(item.value);
8928
9363
  }
8929
9364
  };
8930
- return /* @__PURE__ */ jsxs("div", { "data-react-fancy-autocomplete": "", ref: wrapperRef, className: cn("relative", className), children: [
8931
- /* @__PURE__ */ jsx(
8932
- "input",
8933
- {
8934
- ref: (node) => {
8935
- anchorRef.current = node;
8936
- if (typeof ref === "function") ref(node);
8937
- else if (ref) ref.current = node;
8938
- },
8939
- type: "text",
8940
- value: query,
8941
- onChange: (e) => {
8942
- setQuery(e.target.value);
8943
- setOpen(true);
8944
- setActiveIndex(-1);
8945
- },
8946
- onFocus: () => setOpen(true),
8947
- onKeyDown: handleKeyDown,
8948
- placeholder,
8949
- disabled,
8950
- role: "combobox",
8951
- "aria-expanded": open,
8952
- "aria-autocomplete": "list",
8953
- 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"
8954
- }
8955
- ),
8956
- open && /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(
9365
+ if (!showControl) {
9366
+ const matched = options.find((o) => o.value === value);
9367
+ return /* @__PURE__ */ jsx(
8957
9368
  "div",
8958
9369
  {
8959
- ref: listRef,
8960
- role: "listbox",
8961
- 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",
8962
- style: {
8963
- left: position.x,
8964
- top: position.y,
8965
- width: anchorRef.current?.offsetWidth
8966
- },
8967
- 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(
8968
- "button",
8969
- {
8970
- type: "button",
8971
- role: "option",
8972
- "aria-selected": i === activeIndex,
8973
- disabled: option.disabled,
8974
- onClick: () => select(option.value),
8975
- className: cn(
8976
- "flex w-full items-center rounded-lg px-3 py-2 text-left text-sm text-zinc-700 transition-colors dark:text-zinc-200",
8977
- i === activeIndex ? "bg-zinc-100 dark:bg-zinc-800" : "hover:bg-zinc-50 dark:hover:bg-zinc-800/50",
8978
- option.disabled && "cursor-not-allowed opacity-50"
8979
- ),
8980
- children: /* @__PURE__ */ jsx(HighlightMatch, { text: option.label, query })
8981
- },
8982
- option.value
8983
- ))
9370
+ "data-react-fancy-autocomplete": "",
9371
+ "data-mode": "view",
9372
+ ref: wrapperRef,
9373
+ role: interactive ? "button" : void 0,
9374
+ tabIndex: interactive ? 0 : void 0,
9375
+ title: interactive ? "Click to edit" : void 0,
9376
+ onClick: interactive ? enterEdit : void 0,
9377
+ onKeyDown: interactive ? (e) => {
9378
+ if (e.key === "Enter" || e.key === " ") {
9379
+ e.preventDefault();
9380
+ enterEdit();
9381
+ }
9382
+ } : void 0,
9383
+ className: cn(
9384
+ "text-sm text-zinc-900 dark:text-zinc-100",
9385
+ !value && "text-zinc-400 dark:text-zinc-500",
9386
+ interactive && "cursor-pointer rounded-md outline-none transition-colors hover:bg-zinc-100 focus-visible:ring-2 focus-visible:ring-blue-500/40 dark:hover:bg-zinc-800",
9387
+ className
9388
+ ),
9389
+ children: matched?.label ?? value ?? "\u2014"
8984
9390
  }
8985
- ) })
8986
- ] });
9391
+ );
9392
+ }
9393
+ return /* @__PURE__ */ jsxs(
9394
+ "div",
9395
+ {
9396
+ "data-react-fancy-autocomplete": "",
9397
+ ref: wrapperRef,
9398
+ className: cn("relative", className),
9399
+ onBlur: (e) => {
9400
+ if (interactive && !open && !e.currentTarget.contains(e.relatedTarget)) {
9401
+ exitEdit();
9402
+ }
9403
+ },
9404
+ children: [
9405
+ /* @__PURE__ */ jsx(
9406
+ "input",
9407
+ {
9408
+ ref: (node) => {
9409
+ anchorRef.current = node;
9410
+ if (typeof ref === "function") ref(node);
9411
+ else if (ref) ref.current = node;
9412
+ },
9413
+ type: "text",
9414
+ value: query,
9415
+ onChange: (e) => {
9416
+ setQuery(e.target.value);
9417
+ setOpen(true);
9418
+ setActiveIndex(-1);
9419
+ },
9420
+ onFocus: () => setOpen(true),
9421
+ onKeyDown: handleKeyDown,
9422
+ placeholder,
9423
+ disabled,
9424
+ autoFocus: interactive,
9425
+ role: "combobox",
9426
+ "aria-expanded": open,
9427
+ "aria-autocomplete": "list",
9428
+ className: "w-full rounded-lg border border-zinc-200 bg-white px-3 py-2 text-sm text-zinc-900 placeholder:text-zinc-400 outline-none transition-[border-color,box-shadow] duration-150 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/40 dark:border-zinc-700 dark:bg-[#1e1e24] dark:text-zinc-100 dark:placeholder:text-zinc-500 dark:focus:border-blue-400 dark:focus:ring-blue-400/20"
9429
+ }
9430
+ ),
9431
+ open && /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(
9432
+ "div",
9433
+ {
9434
+ ref: listRef,
9435
+ role: "listbox",
9436
+ className: "fixed z-50 max-h-60 min-w-[8rem] overflow-y-auto rounded-xl border border-zinc-200 bg-white p-1 shadow-lg dark:border-zinc-700 dark:bg-zinc-900 dark:shadow-zinc-950/50 fancy-scale-in",
9437
+ style: {
9438
+ left: position.x,
9439
+ top: position.y,
9440
+ width: anchorRef.current?.offsetWidth
9441
+ },
9442
+ children: loading ? /* @__PURE__ */ jsx("div", { className: "px-3 py-2 text-sm text-zinc-400", children: "Loading..." }) : filtered.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-3 py-2 text-sm text-zinc-400", children: emptyMessage }) : filtered.map((option, i) => /* @__PURE__ */ jsx(
9443
+ "button",
9444
+ {
9445
+ type: "button",
9446
+ role: "option",
9447
+ "aria-selected": i === activeIndex,
9448
+ disabled: option.disabled,
9449
+ onClick: () => select(option.value),
9450
+ className: cn(
9451
+ "flex w-full items-center rounded-lg px-3 py-2 text-left text-sm text-zinc-700 transition-colors dark:text-zinc-200",
9452
+ i === activeIndex ? "bg-zinc-100 dark:bg-zinc-800" : "hover:bg-zinc-50 dark:hover:bg-zinc-800/50",
9453
+ option.disabled && "cursor-not-allowed opacity-50"
9454
+ ),
9455
+ children: /* @__PURE__ */ jsx(HighlightMatch, { text: option.label, query })
9456
+ },
9457
+ option.value
9458
+ ))
9459
+ }
9460
+ ) })
9461
+ ]
9462
+ }
9463
+ );
8987
9464
  }
8988
9465
  );
8989
9466
  function HighlightMatch({ text, query }) {
@@ -9099,13 +9576,16 @@ var OtpInput = forwardRef(
9099
9576
  onChange,
9100
9577
  disabled = false,
9101
9578
  autoFocus = false,
9102
- className
9579
+ className,
9580
+ mode
9103
9581
  }, ref) {
9104
9582
  const [value, setValue] = useControllableState(
9105
9583
  controlledValue,
9106
9584
  "",
9107
9585
  onChange
9108
9586
  );
9587
+ const resolvedMode = useFieldMode(mode);
9588
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
9109
9589
  const inputsRef = useRef([]);
9110
9590
  const focusInput = useCallback(
9111
9591
  (index) => {
@@ -9159,27 +9639,64 @@ var OtpInput = forwardRef(
9159
9639
  },
9160
9640
  [length, setValue, focusInput]
9161
9641
  );
9162
- return /* @__PURE__ */ jsx("div", { "data-react-fancy-otp-input": "", ref, className: cn("flex gap-2", className), children: Array.from({ length }, (_, i) => /* @__PURE__ */ jsx(
9163
- "input",
9642
+ if (!showControl) {
9643
+ return /* @__PURE__ */ jsx(
9644
+ "div",
9645
+ {
9646
+ "data-react-fancy-otp-input": "",
9647
+ "data-mode": "view",
9648
+ ref,
9649
+ role: interactive ? "button" : void 0,
9650
+ tabIndex: interactive ? 0 : void 0,
9651
+ title: interactive ? "Click to edit" : void 0,
9652
+ onClick: interactive ? enterEdit : void 0,
9653
+ onKeyDown: interactive ? (e) => {
9654
+ if (e.key === "Enter" || e.key === " ") {
9655
+ e.preventDefault();
9656
+ enterEdit();
9657
+ }
9658
+ } : void 0,
9659
+ className: cn(
9660
+ "font-mono text-lg tracking-[0.4em] text-zinc-900 dark:text-zinc-100",
9661
+ interactive && "cursor-pointer rounded-md outline-none transition-opacity hover:opacity-80 focus-visible:ring-2 focus-visible:ring-blue-500/40",
9662
+ className
9663
+ ),
9664
+ children: value || "\u2014"
9665
+ }
9666
+ );
9667
+ }
9668
+ return /* @__PURE__ */ jsx(
9669
+ "div",
9164
9670
  {
9165
- ref: (el) => {
9166
- inputsRef.current[i] = el;
9671
+ "data-react-fancy-otp-input": "",
9672
+ ref,
9673
+ className: cn("flex gap-2", className),
9674
+ onBlur: (e) => {
9675
+ if (interactive && !e.currentTarget.contains(e.relatedTarget)) exitEdit();
9167
9676
  },
9168
- type: "text",
9169
- inputMode: "numeric",
9170
- maxLength: 1,
9171
- value: value[i] ?? "",
9172
- onChange: (e) => handleChange(i, e.target.value),
9173
- onKeyDown: (e) => handleKeyDown(i, e),
9174
- onPaste: handlePaste,
9175
- onFocus: (e) => e.target.select(),
9176
- disabled,
9177
- autoFocus: autoFocus && i === 0,
9178
- 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",
9179
- "aria-label": `Digit ${i + 1}`
9180
- },
9181
- i
9182
- )) });
9677
+ children: Array.from({ length }, (_, i) => /* @__PURE__ */ jsx(
9678
+ "input",
9679
+ {
9680
+ ref: (el) => {
9681
+ inputsRef.current[i] = el;
9682
+ },
9683
+ type: "text",
9684
+ inputMode: "numeric",
9685
+ maxLength: 1,
9686
+ value: value[i] ?? "",
9687
+ onChange: (e) => handleChange(i, e.target.value),
9688
+ onKeyDown: (e) => handleKeyDown(i, e),
9689
+ onPaste: handlePaste,
9690
+ onFocus: (e) => e.target.select(),
9691
+ disabled,
9692
+ autoFocus: (autoFocus || interactive) && i === 0,
9693
+ className: "h-12 w-10 rounded-lg border border-zinc-200 bg-white text-center text-lg font-medium text-zinc-900 outline-none transition-[border-color,box-shadow] duration-150 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/40 dark:border-zinc-700 dark:bg-[#1e1e24] dark:text-zinc-100 dark:focus:border-blue-400 dark:focus:ring-blue-400/20",
9694
+ "aria-label": `Digit ${i + 1}`
9695
+ },
9696
+ i
9697
+ ))
9698
+ }
9699
+ );
9183
9700
  }
9184
9701
  );
9185
9702
  OtpInput.displayName = "OtpInput";
@@ -9373,16 +9890,46 @@ var TimePicker = forwardRef(
9373
9890
  format = "12h",
9374
9891
  minuteStep = 1,
9375
9892
  disabled = false,
9376
- className
9893
+ className,
9894
+ mode
9377
9895
  }, ref) {
9378
9896
  const [value, setValue] = useControllableState(
9379
9897
  controlledValue,
9380
9898
  defaultValue,
9381
9899
  onChange
9382
9900
  );
9901
+ const resolvedMode = useFieldMode(mode);
9902
+ const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);
9383
9903
  const { hours: h24, minutes } = parseTime(value);
9384
9904
  const isPM = h24 >= 12;
9385
9905
  const displayHour = format === "12h" ? h24 % 12 || 12 : h24;
9906
+ if (!showControl) {
9907
+ const formatted = format === "12h" ? `${pad(displayHour)}:${pad(minutes)} ${isPM ? "PM" : "AM"}` : `${pad(displayHour)}:${pad(minutes)}`;
9908
+ return /* @__PURE__ */ jsx(
9909
+ "div",
9910
+ {
9911
+ "data-react-fancy-time-picker": "",
9912
+ "data-mode": "view",
9913
+ ref,
9914
+ role: interactive ? "button" : void 0,
9915
+ tabIndex: interactive ? 0 : void 0,
9916
+ title: interactive ? "Click to edit" : void 0,
9917
+ onClick: interactive ? enterEdit : void 0,
9918
+ onKeyDown: interactive ? (e) => {
9919
+ if (e.key === "Enter" || e.key === " ") {
9920
+ e.preventDefault();
9921
+ enterEdit();
9922
+ }
9923
+ } : void 0,
9924
+ className: cn(
9925
+ "text-sm font-medium tabular-nums text-zinc-900 dark:text-zinc-100",
9926
+ interactive && "cursor-pointer rounded-md outline-none transition-colors hover:bg-zinc-100 focus-visible:ring-2 focus-visible:ring-blue-500/40 dark:hover:bg-zinc-800",
9927
+ className
9928
+ ),
9929
+ children: formatted
9930
+ }
9931
+ );
9932
+ }
9386
9933
  const updateTime = useCallback(
9387
9934
  (hours, mins) => {
9388
9935
  setValue(`${pad(hours)}:${pad(mins)}`);
@@ -9413,6 +9960,9 @@ var TimePicker = forwardRef(
9413
9960
  {
9414
9961
  "data-react-fancy-time-picker": "",
9415
9962
  ref,
9963
+ onBlur: (e) => {
9964
+ if (interactive && !e.currentTarget.contains(e.relatedTarget)) exitEdit();
9965
+ },
9416
9966
  className: cn(
9417
9967
  "inline-flex items-center gap-1 rounded-lg border border-zinc-200 bg-white p-2 dark:border-zinc-700 dark:bg-zinc-900",
9418
9968
  disabled && "opacity-50",
@@ -9420,7 +9970,7 @@ var TimePicker = forwardRef(
9420
9970
  ),
9421
9971
  children: [
9422
9972
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center", children: [
9423
- /* @__PURE__ */ jsx("button", { type: "button", onClick: () => changeHour(1), disabled, className: spinBtnClass, "aria-label": "Increase hour", children: /* @__PURE__ */ jsx(ChevronUp, { size: 14 }) }),
9973
+ /* @__PURE__ */ jsx("button", { type: "button", autoFocus: interactive, onClick: () => changeHour(1), disabled, className: spinBtnClass, "aria-label": "Increase hour", children: /* @__PURE__ */ jsx(ChevronUp, { size: 14 }) }),
9424
9974
  /* @__PURE__ */ jsx("div", { className: displayClass, children: pad(displayHour) }),
9425
9975
  /* @__PURE__ */ jsx("button", { type: "button", onClick: () => changeHour(-1), disabled, className: spinBtnClass, "aria-label": "Decrease hour", children: /* @__PURE__ */ jsx(ChevronDown, { size: 14 }) })
9426
9976
  ] }),
@@ -13702,6 +14252,6 @@ function caretRect(ta, start, end) {
13702
14252
  return { x, y };
13703
14253
  }
13704
14254
 
13705
- 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, Dropdown, EMOJI_CATEGORY_ORDER, EMOJI_DATA, EMOJI_ENTRIES, Editor, Emoji, EmojiSelect, FauxClient, Field, FileUpload, 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, useFileUpload, useFloatingPosition, useFocusTrap, useId12 as useId, useKanban, useMenu, useMobileMenu, useModal, useNavbar, useNodeRegistry, useOutsideClick, usePanZoom, usePopover, useSidebar, useTabs, useToast, useTreeNav };
14255
+ 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 };
13706
14256
  //# sourceMappingURL=index.js.map
13707
14257
  //# sourceMappingURL=index.js.map