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.
- package/dist/components/puck-base/button.js +1 -1
- package/dist/components/puck-base/core/fields.d.ts +33 -10
- package/dist/components/puck-base/core/fields.js +27 -3
- package/dist/components/puck-base/core/styles.d.ts +1 -5
- package/dist/components/puck-base/core/styles.js +1 -5
- package/dist/components/puck-base/core/with-editable.js +33 -21
- package/dist/components/puck-base/editor-context.d.ts +2 -0
- package/dist/components/puck-base/fields/action-field.js +19 -43
- package/dist/components/puck-base/fields/auto-field.js +27 -214
- package/dist/components/puck-base/fields/color-field.d.ts +6 -0
- package/dist/components/puck-base/fields/color-field.js +37 -0
- package/dist/components/puck-base/fields/index.d.ts +8 -0
- package/dist/components/puck-base/fields/location-field.d.ts +44 -0
- package/dist/components/puck-base/fields/location-field.js +207 -0
- package/dist/components/puck-base/fields/object-field.d.ts +8 -0
- package/dist/components/puck-base/fields/object-field.js +30 -0
- package/dist/components/puck-base/fields/radio-toggle-field.d.ts +10 -0
- package/dist/components/puck-base/fields/radio-toggle-field.js +53 -0
- package/dist/components/puck-base/fields/virtualized-select-field.d.ts +13 -0
- package/dist/components/puck-base/fields/virtualized-select-field.js +146 -0
- package/dist/components/puck-base/image.js +175 -104
- package/dist/components/puck-base/index.d.ts +2 -3
- package/dist/components/puck-block/location-sections/location-1/index.js +17 -22
- package/dist/components/puck-block/location-sections/location-1/location.d.ts +5 -7
- package/dist/components/puck-block/location-sections/location-1/location.js +15 -12
- package/dist/components/puck-block/location-sections/location-2/index.js +28 -24
- package/dist/components/puck-block/location-sections/location-2/location.d.ts +6 -8
- package/dist/components/puck-block/location-sections/location-2/location.js +18 -15
- package/dist/components/puck-block/location-sections/location-3/index.js +43 -20
- package/dist/components/puck-block/location-sections/location-3/location.d.ts +5 -6
- package/dist/components/puck-block/location-sections/location-3/location.js +96 -86
- package/dist/components/puck-block/location-sections/props.d.ts +9 -10
- package/dist/components/shadcn/slider.js +4 -1
- 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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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
|
|
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
|
-
|
|
23
|
-
|
|
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
|
-
|
|
67
|
-
"span",
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
"
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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,
|
|
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__ */
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
{
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
className: "
|
|
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
|
|
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 {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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:
|
|
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: (
|
|
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,
|