dune-react 0.0.15 → 0.0.18

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.
Files changed (34) hide show
  1. package/dist/components/puck-base/button.js +1 -1
  2. package/dist/components/puck-base/core/fields.d.ts +33 -10
  3. package/dist/components/puck-base/core/fields.js +27 -3
  4. package/dist/components/puck-base/core/styles.d.ts +1 -5
  5. package/dist/components/puck-base/core/styles.js +1 -5
  6. package/dist/components/puck-base/core/with-editable.js +33 -21
  7. package/dist/components/puck-base/editor-context.d.ts +2 -0
  8. package/dist/components/puck-base/fields/action-field.js +19 -43
  9. package/dist/components/puck-base/fields/auto-field.js +27 -214
  10. package/dist/components/puck-base/fields/color-field.d.ts +6 -0
  11. package/dist/components/puck-base/fields/color-field.js +37 -0
  12. package/dist/components/puck-base/fields/index.d.ts +8 -0
  13. package/dist/components/puck-base/fields/location-field.d.ts +44 -0
  14. package/dist/components/puck-base/fields/location-field.js +207 -0
  15. package/dist/components/puck-base/fields/object-field.d.ts +8 -0
  16. package/dist/components/puck-base/fields/object-field.js +30 -0
  17. package/dist/components/puck-base/fields/radio-toggle-field.d.ts +10 -0
  18. package/dist/components/puck-base/fields/radio-toggle-field.js +53 -0
  19. package/dist/components/puck-base/fields/virtualized-select-field.d.ts +13 -0
  20. package/dist/components/puck-base/fields/virtualized-select-field.js +146 -0
  21. package/dist/components/puck-base/image.js +175 -104
  22. package/dist/components/puck-base/index.d.ts +2 -3
  23. package/dist/components/puck-block/location-sections/location-1/index.js +17 -22
  24. package/dist/components/puck-block/location-sections/location-1/location.d.ts +5 -7
  25. package/dist/components/puck-block/location-sections/location-1/location.js +15 -12
  26. package/dist/components/puck-block/location-sections/location-2/index.js +28 -24
  27. package/dist/components/puck-block/location-sections/location-2/location.d.ts +6 -8
  28. package/dist/components/puck-block/location-sections/location-2/location.js +18 -15
  29. package/dist/components/puck-block/location-sections/location-3/index.js +43 -20
  30. package/dist/components/puck-block/location-sections/location-3/location.d.ts +5 -6
  31. package/dist/components/puck-block/location-sections/location-3/location.js +96 -86
  32. package/dist/components/puck-block/location-sections/props.d.ts +9 -10
  33. package/dist/components/shadcn/slider.js +4 -1
  34. package/package.json +4 -2
@@ -4,11 +4,11 @@ import { useState, useCallback } from "react";
4
4
  import { SafeDynamicIcon } from "./safe-dynamic-icon.js";
5
5
  import { Button, buttonVariants } from "../shadcn/button.js";
6
6
  import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "../shadcn/dialog.js";
7
- import { AutoField } from "./fields/auto-field.js";
8
7
  import { button } from "./core/fields.js";
9
8
  import { cn } from "../../utils/css-utils.js";
10
9
  import { resolveActionUrl } from "./core/types.js";
11
10
  import { withEditable } from "./core/with-editable.js";
11
+ import { AutoField } from "./fields/auto-field.js";
12
12
  const BUTTON_FIELDS = button.objectFields;
13
13
  function ButtonEditDialog({
14
14
  open,
@@ -13,9 +13,9 @@ export declare const action: {
13
13
  render: typeof ActionField;
14
14
  };
15
15
  export declare const actionDefaults: {
16
- readonly type: "external";
16
+ readonly type: "page";
17
17
  readonly pageUrl: "";
18
- readonly externalUrl: "#";
18
+ readonly externalUrl: "";
19
19
  readonly openInNewTab: "false";
20
20
  readonly email: "";
21
21
  readonly subject: "";
@@ -139,9 +139,9 @@ export declare const buttons: {
139
139
  readonly defaultItemProps: {
140
140
  readonly label: "Button";
141
141
  readonly action: {
142
- readonly type: "external";
142
+ readonly type: "page";
143
143
  readonly pageUrl: "";
144
- readonly externalUrl: "#";
144
+ readonly externalUrl: "";
145
145
  readonly openInNewTab: "false";
146
146
  readonly email: "";
147
147
  readonly subject: "";
@@ -354,9 +354,9 @@ export declare const contentFields: {
354
354
  readonly defaultItemProps: {
355
355
  readonly label: "Button";
356
356
  readonly action: {
357
- readonly type: "external";
357
+ readonly type: "page";
358
358
  readonly pageUrl: "";
359
- readonly externalUrl: "#";
359
+ readonly externalUrl: "";
360
360
  readonly openInNewTab: "false";
361
361
  readonly email: "";
362
362
  readonly subject: "";
@@ -496,9 +496,9 @@ export declare const contentFieldsWithFeatures: {
496
496
  readonly defaultItemProps: {
497
497
  readonly label: "Button";
498
498
  readonly action: {
499
- readonly type: "external";
499
+ readonly type: "page";
500
500
  readonly pageUrl: "";
501
- readonly externalUrl: "#";
501
+ readonly externalUrl: "";
502
502
  readonly openInNewTab: "false";
503
503
  readonly email: "";
504
504
  readonly subject: "";
@@ -668,9 +668,9 @@ export declare const cards: {
668
668
  readonly button: {
669
669
  readonly label: "Button";
670
670
  readonly action: {
671
- readonly type: "external";
671
+ readonly type: "page";
672
672
  readonly pageUrl: "";
673
- readonly externalUrl: "#";
673
+ readonly externalUrl: "";
674
674
  readonly openInNewTab: "false";
675
675
  readonly email: "";
676
676
  readonly subject: "";
@@ -801,4 +801,27 @@ export declare const form: {
801
801
  };
802
802
  };
803
803
  export declare const formDefaults: CompoundFormProps;
804
+ /** 地图定位的核心数据:地址 + 坐标 */
805
+ export interface MapLocation {
806
+ /** 用户可读的完整地址(显示 & 搜索用) */
807
+ address: string;
808
+ /** 纬度 */
809
+ lat: number;
810
+ /** 经度 */
811
+ lng: number;
812
+ /** 地图缩放级别,默认 14 */
813
+ zoom?: number;
814
+ /** Google Place ID(可选,便于精确定位) */
815
+ placeId?: string;
816
+ }
817
+ /** 从 MapLocation 生成 Google Maps embed URL */
818
+ export declare function getMapEmbedUrl(loc: MapLocation): string;
819
+ /** 从 MapLocation 生成 Google Maps 导航 URL */
820
+ export declare function getDirectionsUrl(loc: MapLocation): string;
821
+ export declare const locationDefaults: MapLocation;
822
+ export declare const location: {
823
+ type: "custom";
824
+ label: string;
825
+ render: any;
826
+ };
804
827
  export * from "./styles";
@@ -1,6 +1,7 @@
1
1
  import { fieldTypes } from "../field.js";
2
2
  import { ActionField } from "../fields/action-field.js";
3
3
  import { IconPickerField } from "../icon-picker-field.js";
4
+ import { LocationField } from "../fields/location-field.js";
4
5
  const formMethods = ["get", "post", "put", "patch", "delete"];
5
6
  const icon = {
6
7
  type: "custom",
@@ -12,9 +13,9 @@ const action = {
12
13
  render: ActionField
13
14
  };
14
15
  const actionDefaults = {
15
- type: "external",
16
+ type: "page",
16
17
  pageUrl: "",
17
- externalUrl: "#",
18
+ externalUrl: "",
18
19
  openInNewTab: "false",
19
20
  email: "",
20
21
  subject: "",
@@ -240,6 +241,25 @@ const formDefaults = {
240
241
  icon: "move-right"
241
242
  }
242
243
  };
244
+ function getMapEmbedUrl(loc) {
245
+ const { lat, lng, zoom = 14 } = loc;
246
+ return `https://maps.google.com/maps?q=${lat},${lng}&z=${zoom}&output=embed`;
247
+ }
248
+ function getDirectionsUrl(loc) {
249
+ return `https://www.google.com/maps/dir/?api=1&destination=${loc.lat},${loc.lng}`;
250
+ }
251
+ const locationDefaults = {
252
+ address: "San Francisco, CA, USA",
253
+ lat: 37.7749,
254
+ lng: -122.4194,
255
+ zoom: 13
256
+ };
257
+ const location = {
258
+ type: "custom",
259
+ label: "Location",
260
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
261
+ render: LocationField
262
+ };
243
263
  export {
244
264
  action,
245
265
  actionDefaults,
@@ -256,6 +276,8 @@ export {
256
276
  fieldDefaults,
257
277
  form,
258
278
  formDefaults,
279
+ getDirectionsUrl,
280
+ getMapEmbedUrl,
259
281
  getPlaceholderImageUrl,
260
282
  heading,
261
283
  icon,
@@ -263,5 +285,7 @@ export {
263
285
  image16x9Placeholder,
264
286
  image1x1Placeholder,
265
287
  image9x16Placeholder,
266
- images
288
+ images,
289
+ location,
290
+ locationDefaults
267
291
  };
@@ -1394,11 +1394,7 @@ export declare const contactStylesDefaults: {
1394
1394
  readonly bottom: "medium";
1395
1395
  };
1396
1396
  };
1397
- export interface LocationSectionStyles extends SectionBaseStyles {
1398
- mapHeight?: number;
1399
- mapFilter?: string;
1400
- mapTintColor?: string;
1401
- }
1397
+ export type LocationSectionStyles = SectionBaseStyles;
1402
1398
  export declare const locationStylesField: {
1403
1399
  type: "object";
1404
1400
  label: string;
@@ -216,11 +216,7 @@ createStylesField({
216
216
  }
217
217
  });
218
218
  createStylesDefaults();
219
- createStylesField({
220
- mapHeight: { type: "number", label: "Map Height", min: 200, max: 800 },
221
- mapFilter: { type: "text", label: "Map Filter (CSS)" },
222
- mapTintColor: { type: "text", label: "Map Tint Color" }
223
- });
219
+ createStylesField();
224
220
  createStylesDefaults();
225
221
  createStylesField();
226
222
  createStylesDefaults();
@@ -17,11 +17,13 @@ function withEditable(Component, config) {
17
17
  const dispatch = usePuckDispatch();
18
18
  const appState = usePuckAppState();
19
19
  const wrapperRef = useRef(null);
20
+ const portalRef = useRef(null);
20
21
  const isBlock = BLOCK_LEVEL_TYPES.has(config.type);
21
22
  useEffect(() => {
22
- if (!wrapperRef.current) return;
23
- return registerOverlayPortal(wrapperRef.current, { disableDrag: true });
24
- }, []);
23
+ const target = isBlock ? portalRef.current : wrapperRef.current;
24
+ if (!target) return;
25
+ return registerOverlayPortal(target, { disableDrag: true });
26
+ }, [isBlock]);
25
27
  const getLocation = useCallback(() => {
26
28
  const componentId = getComponentIdFromDOM(wrapperRef.current);
27
29
  if (!componentId || !(appState == null ? void 0 : appState.data)) return null;
@@ -53,6 +55,8 @@ function withEditable(Component, config) {
53
55
  },
54
56
  [getLocation, dispatch]
55
57
  );
58
+ const { previewScale = 1 } = useEditorContext();
59
+ const inverseScale = 1 / previewScale;
56
60
  const handleEdit = useCallback(
57
61
  (e) => {
58
62
  e.preventDefault();
@@ -63,24 +67,32 @@ function withEditable(Component, config) {
63
67
  },
64
68
  [props, handleSave]
65
69
  );
66
- return /* @__PURE__ */ jsxs(
67
- "span",
68
- {
69
- ref: wrapperRef,
70
- className: isBlock ? "group relative block h-full w-full" : "group relative inline-flex",
71
- children: [
72
- /* @__PURE__ */ jsx(Component, { ...props }),
73
- /* @__PURE__ */ jsx(
74
- "span",
75
- {
76
- onClick: handleEdit,
77
- className: "absolute inset-0 z-40 flex items-center justify-center opacity-0 transition-opacity group-hover:opacity-100",
78
- children: /* @__PURE__ */ jsx("span", { className: "flex h-8 w-8 items-center justify-center rounded-full bg-black/50 shadow-md backdrop-blur-sm", children: /* @__PURE__ */ jsx(Pencil, { className: "h-4 w-4 text-white" }) })
79
- }
80
- )
81
- ]
82
- }
83
- );
70
+ if (isBlock) {
71
+ return /* @__PURE__ */ jsxs("span", { ref: wrapperRef, className: "group relative block h-full w-full", children: [
72
+ /* @__PURE__ */ jsx(Component, { ...props }),
73
+ /* @__PURE__ */ jsx("span", { className: "absolute inset-0 z-40 flex items-center justify-center", children: /* @__PURE__ */ jsx(
74
+ "span",
75
+ {
76
+ ref: portalRef,
77
+ onClick: handleEdit,
78
+ style: { transform: `scale(${inverseScale})`, transformOrigin: "center center" },
79
+ className: "flex h-8 w-8 items-center justify-center rounded-full bg-black/50 shadow-md backdrop-blur-sm",
80
+ children: /* @__PURE__ */ jsx(Pencil, { className: "h-4 w-4 text-white" })
81
+ }
82
+ ) })
83
+ ] });
84
+ }
85
+ return /* @__PURE__ */ jsxs("span", { ref: wrapperRef, className: "group relative inline-flex", children: [
86
+ /* @__PURE__ */ jsx(Component, { ...props }),
87
+ /* @__PURE__ */ jsx("span", { className: "absolute inset-0 z-40 flex items-center justify-center opacity-0 transition-opacity group-hover:opacity-100", children: /* @__PURE__ */ jsx(
88
+ "span",
89
+ {
90
+ onClick: handleEdit,
91
+ className: "flex h-8 w-8 items-center justify-center rounded-full bg-black/50 shadow-md backdrop-blur-sm",
92
+ children: /* @__PURE__ */ jsx(Pencil, { className: "h-4 w-4 text-white" })
93
+ }
94
+ ) })
95
+ ] });
84
96
  }
85
97
  return function EditableComponent(props) {
86
98
  const { isEditor: isEditorMode } = useEditorContext();
@@ -17,6 +17,8 @@ type EditorContextValue = {
17
17
  domain?: string;
18
18
  /** 站内可选页面列表,用于 url 字段快捷选择 */
19
19
  sitePages?: SitePage[];
20
+ /** Google Maps API Key,用于 LocationField 地址搜索 */
21
+ googleMapsApiKey?: string;
20
22
  };
21
23
  export declare const EditorContextProvider: import("react").Provider<EditorContextValue>;
22
24
  export declare const useEditorContext: () => EditorContextValue;
@@ -2,10 +2,9 @@
2
2
  import { jsxs, jsx, Fragment } from "react/jsx-runtime";
3
3
  import { memo, useCallback } from "react";
4
4
  import { Input } from "../../shadcn/input.js";
5
- import { Button } from "../../shadcn/button.js";
6
5
  import { Label } from "../../shadcn/label.js";
7
6
  import { Tabs, TabsList, TabsTrigger } from "../../shadcn/tabs.js";
8
- import { LinkIcon, XIcon, ClipboardIcon, GlobeIcon } from "lucide-react";
7
+ import { LinkIcon, XIcon, GlobeIcon } from "lucide-react";
9
8
  import { useEditorContext } from "../editor-context.js";
10
9
  const ACTION_TYPE_OPTIONS = [
11
10
  { label: "Page Link", value: "page" },
@@ -25,53 +24,30 @@ const PageActionFields = memo(function PageActionFields2({
25
24
  const pages = sitePages ?? [];
26
25
  const pageUrl = value.pageUrl ?? "";
27
26
  const selectedSlug = pageUrl.startsWith("/") ? pageUrl.slice(1) : null;
28
- const handlePaste = useCallback(async () => {
29
- try {
30
- const text = await navigator.clipboard.readText();
31
- if (text) onChange({ pageUrl: text.trim() });
32
- } catch {
33
- }
34
- }, [onChange]);
35
27
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
36
28
  /* @__PURE__ */ jsx(Label, { children: "Page URL" }),
37
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
38
- /* @__PURE__ */ jsxs("div", { className: "flex flex-1 items-center gap-2 rounded-lg border px-3 py-2", children: [
39
- /* @__PURE__ */ jsx(LinkIcon, { className: "text-muted-foreground size-4 shrink-0" }),
40
- /* @__PURE__ */ jsx(
41
- "input",
42
- {
43
- name: `${name}.pageUrl`,
44
- className: "w-full bg-transparent text-sm outline-none placeholder:text-muted-foreground",
45
- placeholder: "/about",
46
- value: pageUrl,
47
- onChange: (e) => onChange({ pageUrl: e.target.value })
48
- }
49
- ),
50
- pageUrl && /* @__PURE__ */ jsx(
51
- "button",
52
- {
53
- type: "button",
54
- className: "text-muted-foreground hover:text-foreground shrink-0",
55
- onClick: () => onChange({ pageUrl: "" }),
56
- children: /* @__PURE__ */ jsx(XIcon, { className: "size-3.5" })
57
- }
58
- )
59
- ] }),
60
- /* @__PURE__ */ jsxs(
61
- Button,
29
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-1 items-center gap-2 rounded-lg border px-3 py-2", children: [
30
+ /* @__PURE__ */ jsx(LinkIcon, { className: "text-muted-foreground size-4 shrink-0" }),
31
+ /* @__PURE__ */ jsx(
32
+ "input",
33
+ {
34
+ name: `${name}.pageUrl`,
35
+ className: "w-full bg-transparent text-sm outline-none placeholder:text-muted-foreground",
36
+ placeholder: "/about",
37
+ value: pageUrl,
38
+ onChange: (e) => onChange({ pageUrl: e.target.value })
39
+ }
40
+ ),
41
+ pageUrl && /* @__PURE__ */ jsx(
42
+ "button",
62
43
  {
63
44
  type: "button",
64
- variant: "outline",
65
- size: "sm",
66
- className: "shrink-0 gap-1.5",
67
- onClick: handlePaste,
68
- children: [
69
- /* @__PURE__ */ jsx(ClipboardIcon, { className: "size-3.5" }),
70
- "Paste"
71
- ]
45
+ className: "text-muted-foreground hover:text-foreground shrink-0",
46
+ onClick: () => onChange({ pageUrl: "" }),
47
+ children: /* @__PURE__ */ jsx(XIcon, { className: "size-3.5" })
72
48
  }
73
49
  )
74
- ] }),
50
+ ] }) }),
75
51
  pages.length > 0 && /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
76
52
  /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-xs font-medium", children: "Site pages" }),
77
53
  /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: pages.map((page) => {
@@ -1,225 +1,33 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
- import { memo, useState, useRef, useMemo, useCallback, useEffect } from "react";
2
+ import { memo } from "react";
3
3
  import { Input } from "../../shadcn/input.js";
4
4
  import { Textarea } from "../../shadcn/textarea.js";
5
5
  import { Label } from "../../shadcn/label.js";
6
- import { Switch } from "../../shadcn/switch.js";
7
- import { Button } from "../../shadcn/button.js";
8
6
  import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "../../shadcn/select.js";
9
- import { Popover, PopoverTrigger, PopoverContent } from "../../shadcn/popover.js";
10
- import { ToggleGroup, ToggleGroupItem } from "../../shadcn/toggle-group.js";
11
- import { ChevronDownIcon, CheckIcon } from "lucide-react";
12
- const LARGE_SELECT_THRESHOLD = 200;
13
- const VIRTUAL_ITEM_HEIGHT = 32;
14
- const VIRTUAL_VIEWPORT_HEIGHT = 256;
15
- const VIRTUAL_OVERSCAN = 6;
16
- const RadioToggleField = memo(function RadioToggleField2({
17
- label,
18
- options,
19
- value,
20
- onChange
21
- }) {
22
- const isBooleanToggle = options.length === 2 && options.some((o) => o.value === true) && options.some((o) => o.value === false);
23
- if (isBooleanToggle) {
24
- return /* @__PURE__ */ jsxs("div", { className: "bg-muted/50 mb-3 flex items-center justify-between rounded-xl p-3", children: [
25
- /* @__PURE__ */ jsx(Label, { children: label }),
26
- /* @__PURE__ */ jsx(
27
- Switch,
28
- {
29
- checked: !!value,
30
- onCheckedChange: (checked) => onChange(checked)
31
- }
32
- )
33
- ] });
34
- }
35
- return /* @__PURE__ */ jsxs("div", { className: "mb-4 space-y-1.5", children: [
36
- /* @__PURE__ */ jsx(Label, { children: label }),
37
- /* @__PURE__ */ jsx(
38
- ToggleGroup,
39
- {
40
- type: "single",
41
- value: String(value ?? ""),
42
- onValueChange: (val) => {
43
- if (!val) return;
44
- const opt = options.find((o) => String(o.value) === val);
45
- if (opt) onChange(opt.value);
46
- },
47
- className: "justify-start",
48
- children: options.map((opt) => /* @__PURE__ */ jsx(
49
- ToggleGroupItem,
50
- {
51
- value: String(opt.value),
52
- size: "sm",
53
- children: opt.label
54
- },
55
- String(opt.value)
56
- ))
57
- }
58
- )
59
- ] });
60
- });
61
- const ObjectField = memo(function ObjectField2({
62
- field,
63
- name,
64
- value,
65
- onChange
66
- }) {
67
- const objectFields = field.objectFields || {};
68
- const label = field.label || name;
69
- const objValue = value && typeof value === "object" ? value : {};
70
- return /* @__PURE__ */ jsxs("div", { className: "mb-4 space-y-1.5", children: [
71
- /* @__PURE__ */ jsx(Label, { children: label }),
72
- /* @__PURE__ */ jsx("div", { className: "rounded-lg border p-3", children: Object.entries(objectFields).map(([key, subField]) => /* @__PURE__ */ jsx(
73
- AutoField,
74
- {
75
- field: subField,
76
- name: key,
77
- value: objValue[key],
78
- onChange: (val) => onChange({ ...objValue, [key]: val })
79
- },
80
- key
81
- )) })
82
- ] });
83
- });
84
- const VirtualizedSelectField = memo(function VirtualizedSelectField2({
85
- label,
86
- options,
87
- value,
88
- onChange
89
- }) {
90
- const [open, setOpen] = useState(false);
91
- const [search, setSearch] = useState("");
92
- const [scrollTop, setScrollTop] = useState(0);
93
- const viewportRef = useRef(null);
94
- const normalizedOptions = useMemo(
95
- () => options.map((opt) => ({
96
- ...opt,
97
- stringValue: String(opt.value)
98
- })),
99
- [options]
100
- );
101
- const selectedString = String(value ?? "");
102
- const filteredOptions = useMemo(() => {
103
- const query = search.trim().toLowerCase();
104
- if (!query) return normalizedOptions;
105
- return normalizedOptions.filter(
106
- (opt) => opt.label.toLowerCase().includes(query) || opt.stringValue.toLowerCase().includes(query)
107
- );
108
- }, [normalizedOptions, search]);
109
- const selectedLabel = useMemo(() => {
110
- const selected = normalizedOptions.find(
111
- (opt) => opt.stringValue === selectedString
112
- );
113
- return (selected == null ? void 0 : selected.label) ?? selectedString;
114
- }, [normalizedOptions, selectedString]);
115
- const totalHeight = filteredOptions.length * VIRTUAL_ITEM_HEIGHT;
116
- const startIndex = Math.max(
117
- 0,
118
- Math.floor(scrollTop / VIRTUAL_ITEM_HEIGHT) - VIRTUAL_OVERSCAN
119
- );
120
- const visibleCount = Math.ceil(VIRTUAL_VIEWPORT_HEIGHT / VIRTUAL_ITEM_HEIGHT) + VIRTUAL_OVERSCAN * 2;
121
- const endIndex = Math.min(filteredOptions.length, startIndex + visibleCount);
122
- const visibleOptions = filteredOptions.slice(startIndex, endIndex);
123
- const handleSelect = useCallback(
124
- (stringValue) => {
125
- const selected = normalizedOptions.find(
126
- (opt) => opt.stringValue === stringValue
127
- );
128
- onChange(selected ? selected.value : stringValue);
129
- setOpen(false);
130
- },
131
- [normalizedOptions, onChange]
132
- );
133
- useEffect(() => {
134
- if (!open || !viewportRef.current) return;
135
- const selectedIndex = filteredOptions.findIndex(
136
- (opt) => opt.stringValue === selectedString
137
- );
138
- if (selectedIndex < 0) return;
139
- const targetTop = selectedIndex * VIRTUAL_ITEM_HEIGHT - VIRTUAL_VIEWPORT_HEIGHT / 2;
140
- const clampedTop = Math.max(
141
- 0,
142
- Math.min(targetTop, totalHeight - VIRTUAL_VIEWPORT_HEIGHT)
143
- );
144
- viewportRef.current.scrollTop = clampedTop;
145
- setScrollTop(clampedTop);
146
- }, [open, filteredOptions, selectedString, totalHeight]);
147
- return /* @__PURE__ */ jsxs("div", { className: "mb-4 space-y-1.5", children: [
148
- /* @__PURE__ */ jsx(Label, { children: label }),
149
- /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, children: [
150
- /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
151
- Button,
152
- {
153
- type: "button",
154
- variant: "outline",
155
- className: "w-full justify-between font-normal",
156
- children: [
157
- /* @__PURE__ */ jsx("span", { className: "truncate", children: selectedLabel || "Select..." }),
158
- /* @__PURE__ */ jsx(ChevronDownIcon, { className: "size-4 opacity-60" })
159
- ]
160
- }
161
- ) }),
162
- /* @__PURE__ */ jsxs(PopoverContent, { className: "w-(--radix-popover-trigger-width) p-2", children: [
163
- /* @__PURE__ */ jsx(
164
- Input,
165
- {
166
- value: search,
167
- onChange: (e) => setSearch(e.target.value),
168
- placeholder: "Search options...",
169
- className: "mb-2"
170
- }
171
- ),
172
- /* @__PURE__ */ jsx(
173
- "div",
174
- {
175
- ref: viewportRef,
176
- className: "relative touch-pan-y overflow-y-auto overscroll-contain rounded-md border",
177
- style: {
178
- height: VIRTUAL_VIEWPORT_HEIGHT,
179
- WebkitOverflowScrolling: "touch"
180
- },
181
- onScroll: (e) => setScrollTop(e.currentTarget.scrollTop),
182
- children: filteredOptions.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-muted-foreground p-3 text-sm", children: "No options found." }) : /* @__PURE__ */ jsx("div", { style: { height: totalHeight, position: "relative" }, children: /* @__PURE__ */ jsx(
183
- "div",
184
- {
185
- style: {
186
- position: "absolute",
187
- top: startIndex * VIRTUAL_ITEM_HEIGHT,
188
- left: 0,
189
- right: 0
190
- },
191
- children: visibleOptions.map((opt) => {
192
- const isSelected = opt.stringValue === selectedString;
193
- return /* @__PURE__ */ jsxs(
194
- "button",
195
- {
196
- type: "button",
197
- className: "hover:bg-accent hover:text-accent-foreground flex h-8 w-full items-center justify-between px-2 text-left text-sm",
198
- onClick: () => handleSelect(opt.stringValue),
199
- children: [
200
- /* @__PURE__ */ jsx("span", { className: "truncate", children: opt.label }),
201
- isSelected ? /* @__PURE__ */ jsx(CheckIcon, { className: "text-primary ml-2 size-4 shrink-0" }) : null
202
- ]
203
- },
204
- opt.stringValue
205
- );
206
- })
207
- }
208
- ) })
209
- }
210
- )
211
- ] })
212
- ] })
213
- ] });
214
- });
7
+ import { ColorField } from "./color-field.js";
8
+ import { RadioToggleField } from "./radio-toggle-field.js";
9
+ import { ObjectField } from "./object-field.js";
10
+ import { LARGE_SELECT_THRESHOLD, VirtualizedSelectField } from "./virtualized-select-field.js";
11
+ const KEY_FIELD_MAP = {
12
+ color: ColorField,
13
+ backgroundColor: ColorField,
14
+ "background-color": ColorField,
15
+ bgColor: ColorField,
16
+ borderColor: ColorField,
17
+ "border-color": ColorField
18
+ };
215
19
  const AutoField = memo(function AutoField2({
216
20
  field,
217
21
  name,
218
22
  value,
219
23
  onChange
220
24
  }) {
221
- var _a, _b;
25
+ var _a, _b, _c;
222
26
  const label = field.label || name;
27
+ const KeyOverride = KEY_FIELD_MAP[name];
28
+ if (KeyOverride) {
29
+ return /* @__PURE__ */ jsx(KeyOverride, { label, value, onChange });
30
+ }
223
31
  switch (field.type) {
224
32
  case "text":
225
33
  return /* @__PURE__ */ jsxs("div", { className: "mb-4 space-y-1.5", children: [
@@ -266,7 +74,7 @@ const AutoField = memo(function AutoField2({
266
74
  onChange
267
75
  }
268
76
  );
269
- case "select":
77
+ case "select": {
270
78
  if ((((_a = field.options) == null ? void 0 : _a.length) || 0) > LARGE_SELECT_THRESHOLD) {
271
79
  return /* @__PURE__ */ jsx(
272
80
  VirtualizedSelectField,
@@ -278,24 +86,29 @@ const AutoField = memo(function AutoField2({
278
86
  }
279
87
  );
280
88
  }
89
+ const stringValue = String(value ?? "");
90
+ const selectedOption = (_b = field.options) == null ? void 0 : _b.find(
91
+ (o) => String(o.value) === stringValue
92
+ );
281
93
  return /* @__PURE__ */ jsxs("div", { className: "mb-4 space-y-1.5", children: [
282
94
  /* @__PURE__ */ jsx(Label, { children: label }),
283
95
  /* @__PURE__ */ jsxs(
284
96
  Select,
285
97
  {
286
- value: String(value ?? ""),
98
+ value: stringValue,
287
99
  onValueChange: (val) => {
288
100
  var _a2;
289
101
  const opt = (_a2 = field.options) == null ? void 0 : _a2.find((o) => String(o.value) === val);
290
102
  onChange(opt ? opt.value : val);
291
103
  },
292
104
  children: [
293
- /* @__PURE__ */ jsx(SelectTrigger, { className: "w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
294
- /* @__PURE__ */ jsx(SelectContent, { children: (_b = field.options) == null ? void 0 : _b.map((opt) => /* @__PURE__ */ jsx(SelectItem, { value: String(opt.value), children: opt.label }, String(opt.value))) })
105
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "w-full", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: label, children: (selectedOption == null ? void 0 : selectedOption.label) ?? stringValue }) }),
106
+ /* @__PURE__ */ jsx(SelectContent, { children: (_c = field.options) == null ? void 0 : _c.map((opt) => /* @__PURE__ */ jsx(SelectItem, { value: String(opt.value), children: opt.label }, String(opt.value))) })
295
107
  ]
296
108
  }
297
109
  )
298
110
  ] });
111
+ }
299
112
  case "object":
300
113
  return /* @__PURE__ */ jsx(
301
114
  ObjectField,
@@ -0,0 +1,6 @@
1
+ /** 颜色选择器:color swatch + 文本输入 */
2
+ export declare const ColorField: import("react").NamedExoticComponent<{
3
+ label: string;
4
+ value: any;
5
+ onChange: (value: any) => void;
6
+ }>;