dune-react 0.0.5 → 0.0.6

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.
@@ -3,6 +3,15 @@ interface EditableConfig {
3
3
  type: string;
4
4
  fields: Record<string, any>;
5
5
  }
6
+ /**
7
+ * HOC: 让任意组件在编辑模式下可点击编辑
8
+ *
9
+ * 工作原理:
10
+ * 1. 通过 props 特征值深搜 appState.data,找到 componentId + propPath
11
+ * 2. 编辑模式下渲染原组件 + hover overlay
12
+ * 3. 点击打开 Dialog,传递 onSave 回调
13
+ * 4. 保存时用 dispatch 更新 Puck data
14
+ */
6
15
  export declare function withEditable<P extends Record<string, any>>(Component: ComponentType<P>, config: EditableConfig): (props: P & {
7
16
  onEdit?: (props: P, onSave: (updated: Partial<P>) => void) => void;
8
17
  }) => import("react/jsx-runtime").JSX.Element;
@@ -1,28 +1,60 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
- import { useRef, useCallback } from "react";
2
+ import { useRef, useEffect, useMemo, useCallback } from "react";
3
+ import { registerOverlayPortal } from "@puckeditor/core";
4
+ import { usePuckDispatch, usePuckAppState } from "./hooks.js";
3
5
  import { Pencil } from "lucide-react";
4
6
  import { useEditorContext } from "../editor-context.js";
7
+ import set from "../../../node_modules/.pnpm/lodash-es@4.17.23/node_modules/lodash-es/set.js";
5
8
  const BLOCK_LEVEL_TYPES = /* @__PURE__ */ new Set(["image"]);
6
9
  function withEditable(Component, config) {
7
10
  return function EditableComponent(props) {
8
11
  const { isEditor: isEditorMode } = useEditorContext();
12
+ const dispatch = usePuckDispatch();
13
+ const appState = usePuckAppState();
9
14
  const wrapperRef = useRef(null);
10
15
  const isBlock = BLOCK_LEVEL_TYPES.has(config.type);
16
+ useEffect(() => {
17
+ if (!isEditorMode || !wrapperRef.current) return;
18
+ return registerOverlayPortal(wrapperRef.current, { disableDrag: true });
19
+ }, [isEditorMode]);
20
+ const location = useMemo(() => {
21
+ if (!isEditorMode || !(appState == null ? void 0 : appState.data)) return null;
22
+ return findPropsLocation(appState.data, props, config.fields);
23
+ }, [isEditorMode, appState == null ? void 0 : appState.data, props]);
24
+ const handleSave = useCallback(
25
+ (updated) => {
26
+ if (!location) return;
27
+ dispatch({
28
+ type: "setData",
29
+ data: (prevData) => {
30
+ const newData = JSON.parse(JSON.stringify(prevData));
31
+ const { componentId, propPath } = location;
32
+ const component = findComponentById(newData, componentId);
33
+ if (!component) return prevData;
34
+ for (const [key, value] of Object.entries(updated)) {
35
+ const fullPath = propPath ? `${propPath}.${key}` : key;
36
+ set(component.props, fullPath, value);
37
+ }
38
+ return newData;
39
+ }
40
+ });
41
+ },
42
+ [location, dispatch]
43
+ );
11
44
  const handleEdit = useCallback(
12
45
  (e) => {
13
46
  e.preventDefault();
14
47
  e.stopPropagation();
15
48
  if (props.onEdit) {
16
- props.onEdit(props, () => {
17
- });
49
+ props.onEdit(props, handleSave);
18
50
  }
19
51
  },
20
- [props]
52
+ [props, handleSave]
21
53
  );
22
54
  if (!isEditorMode) {
23
55
  return /* @__PURE__ */ jsx(Component, { ...props });
24
56
  }
25
- return /* @__PURE__ */ jsxs("div", { ref: wrapperRef, className: isBlock ? "group relative h-full w-full" : "group relative inline-flex", children: [
57
+ return /* @__PURE__ */ jsxs("span", { ref: wrapperRef, className: isBlock ? "group relative block h-full w-full" : "group relative inline-flex", children: [
26
58
  /* @__PURE__ */ jsx(Component, { ...props }),
27
59
  /* @__PURE__ */ jsx(
28
60
  "span",
@@ -35,6 +67,85 @@ function withEditable(Component, config) {
35
67
  ] });
36
68
  };
37
69
  }
70
+ function findPropsLocation(data, targetProps, fields) {
71
+ if (!data) return null;
72
+ const content = Array.isArray(data.content) ? data.content : [];
73
+ for (const component of content) {
74
+ const result = searchInObject(component.props, targetProps, fields, component.props.id);
75
+ if (result) return result;
76
+ }
77
+ const zones = data.zones && typeof data.zones === "object" ? data.zones : {};
78
+ for (const zoneKey of Object.keys(zones)) {
79
+ const zoneComponents = Array.isArray(zones[zoneKey]) ? zones[zoneKey] : [];
80
+ for (const component of zoneComponents) {
81
+ const result = searchInObject(component.props, targetProps, fields, component.props.id);
82
+ if (result) return result;
83
+ }
84
+ }
85
+ return null;
86
+ }
87
+ function searchInObject(obj, targetProps, fields, componentId, currentPath = "") {
88
+ if (!obj || typeof obj !== "object") return null;
89
+ if (isMatchingProps(obj, targetProps, fields)) {
90
+ return { componentId, propPath: currentPath };
91
+ }
92
+ if (Array.isArray(obj)) {
93
+ for (let i = 0; i < obj.length; i++) {
94
+ const result = searchInObject(
95
+ obj[i],
96
+ targetProps,
97
+ fields,
98
+ componentId,
99
+ currentPath ? `${currentPath}[${i}]` : `[${i}]`
100
+ );
101
+ if (result) return result;
102
+ }
103
+ return null;
104
+ }
105
+ for (const key of Object.keys(obj)) {
106
+ const result = searchInObject(
107
+ obj[key],
108
+ targetProps,
109
+ fields,
110
+ componentId,
111
+ currentPath ? `${currentPath}.${key}` : key
112
+ );
113
+ if (result) return result;
114
+ }
115
+ return null;
116
+ }
117
+ function findComponentById(data, componentId) {
118
+ const content = Array.isArray(data == null ? void 0 : data.content) ? data.content : [];
119
+ const contentMatch = content.find((component) => {
120
+ var _a;
121
+ return ((_a = component == null ? void 0 : component.props) == null ? void 0 : _a.id) === componentId;
122
+ });
123
+ if (contentMatch) return contentMatch;
124
+ const zones = (data == null ? void 0 : data.zones) && typeof data.zones === "object" ? data.zones : {};
125
+ for (const zoneKey of Object.keys(zones)) {
126
+ const zoneComponents = Array.isArray(zones[zoneKey]) ? zones[zoneKey] : [];
127
+ const zoneMatch = zoneComponents.find((component) => {
128
+ var _a;
129
+ return ((_a = component == null ? void 0 : component.props) == null ? void 0 : _a.id) === componentId;
130
+ });
131
+ if (zoneMatch) return zoneMatch;
132
+ }
133
+ return null;
134
+ }
135
+ function isMatchingProps(candidate, target, fields) {
136
+ if (!candidate || typeof candidate !== "object") return false;
137
+ const fieldKeys = Object.keys(fields);
138
+ if (fieldKeys.length === 0) return false;
139
+ let matchCount = 0;
140
+ for (const key of fieldKeys) {
141
+ if (key in candidate && key in target) {
142
+ if (candidate[key] === target[key]) {
143
+ matchCount++;
144
+ }
145
+ }
146
+ }
147
+ return matchCount >= Math.min(2, fieldKeys.length);
148
+ }
38
149
  export {
39
150
  withEditable
40
151
  };
@@ -4,6 +4,7 @@ type EditorContextValue = {
4
4
  openAddSection: (index: number, zone: string) => void;
5
5
  isEditor: boolean;
6
6
  toolbarPortalRef: RefObject<HTMLDivElement | null> | null;
7
+ fetchLibraryImages?: () => Promise<string[]>;
7
8
  };
8
9
  export declare const EditorContextProvider: import("react").Provider<EditorContextValue>;
9
10
  export declare const useEditorContext: () => EditorContextValue;
@@ -1,8 +1,8 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
- import { ErrorBoundary as ErrorBoundary$1 } from "react-error-boundary";
2
+ import { ErrorBoundary as m } from "../../node_modules/.pnpm/react-error-boundary@6.1.1_react@19.2.4/node_modules/react-error-boundary/dist/react-error-boundary.js";
3
3
  import { Button } from "../shadcn/button.js";
4
4
  function ErrorBoundary({ children }) {
5
- return /* @__PURE__ */ jsx(ErrorBoundary$1, { FallbackComponent: Fallback, children });
5
+ return /* @__PURE__ */ jsx(m, { FallbackComponent: Fallback, children });
6
6
  }
7
7
  function Fallback({ error, resetErrorBoundary }) {
8
8
  const message = error instanceof Error ? error.message : String(error);
@@ -1,7 +1,7 @@
1
1
  import { jsxs, Fragment, jsx } from "react/jsx-runtime";
2
- import { useState, useRef, useCallback } from "react";
2
+ import { useState, useEffect, useRef, useCallback } from "react";
3
3
  import Cropper from "react-easy-crop";
4
- import { Pencil, Search, Upload, Check, Loader2 } from "lucide-react";
4
+ import { Pencil, Search, Upload, Loader2, Check } from "lucide-react";
5
5
  import { cn } from "../../utils/css-utils.js";
6
6
  import { withEditable } from "./core/with-editable.js";
7
7
  import { image } from "./core/fields.js";
@@ -13,6 +13,7 @@ import { ScrollArea } from "../shadcn/scroll-area.js";
13
13
  import { Slider } from "../shadcn/slider.js";
14
14
  import { Tabs, TabsList, TabsTrigger } from "../shadcn/tabs.js";
15
15
  import useUpload from "./use-upload.js";
16
+ import { useEditorContext } from "./editor-context.js";
16
17
  const createImage = (url) => new Promise((resolve, reject) => {
17
18
  const image2 = new Image();
18
19
  image2.addEventListener("load", () => resolve(image2));
@@ -44,15 +45,7 @@ const getCroppedImg = async (imageSrc, pixelCrop, rotation) => {
44
45
  canvas.toBlob((blob) => resolve(blob), "image/jpeg", 0.95);
45
46
  });
46
47
  };
47
- const LIBRARY_IMAGES = [
48
- "https://images.unsplash.com/photo-1506744038136-46273834b3fb?w=400&h=300&fit=crop",
49
- "https://images.unsplash.com/photo-1469474968028-56623f02e42e?w=400&h=300&fit=crop",
50
- "https://images.unsplash.com/photo-1447752875215-b2761acb3c5d?w=400&h=300&fit=crop",
51
- "https://images.unsplash.com/photo-1433086966358-54859d0ed716?w=400&h=300&fit=crop",
52
- "https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?w=400&h=300&fit=crop",
53
- "https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=400&h=300&fit=crop",
54
- "https://images.unsplash.com/photo-1507525428034-b723cf961d3e?w=400&h=300&fit=crop"
55
- ];
48
+ const DEFAULT_LIBRARY_IMAGES = [];
56
49
  function CompoundImageBase(props) {
57
50
  return /* @__PURE__ */ jsx("img", { src: props.src, alt: props.alt, className: cn("block h-full w-full object-cover", props.className) });
58
51
  }
@@ -64,6 +57,17 @@ function ImageEditDialog({
64
57
  }) {
65
58
  const [tab, setTab] = useState("library");
66
59
  const [alt, setAlt] = useState(initialData.alt ?? "");
60
+ const [libraryImages, setLibraryImages] = useState(DEFAULT_LIBRARY_IMAGES);
61
+ const [isLoadingImages, setIsLoadingImages] = useState(false);
62
+ const { fetchLibraryImages } = useEditorContext();
63
+ useEffect(() => {
64
+ if (!open || !fetchLibraryImages) return;
65
+ setIsLoadingImages(true);
66
+ fetchLibraryImages().then((images) => {
67
+ if (images.length > 0) setLibraryImages(images);
68
+ }).catch(() => {
69
+ }).finally(() => setIsLoadingImages(false));
70
+ }, [open, fetchLibraryImages]);
67
71
  const [crop, setCrop] = useState({ x: 0, y: 0 });
68
72
  const [zoom, setZoom] = useState(1);
69
73
  const [rotation, setRotation] = useState(0);
@@ -159,7 +163,11 @@ function ImageEditDialog({
159
163
  ] })
160
164
  ] })
161
165
  ] }),
162
- /* @__PURE__ */ jsx("div", { className: cn("flex flex-col gap-3 px-6 py-4", tab !== "library" && "hidden"), children: /* @__PURE__ */ jsx(ScrollArea, { className: "h-[280px]", children: /* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 gap-2", children: LIBRARY_IMAGES.map((src) => {
166
+ /* @__PURE__ */ jsx("div", { className: cn("flex flex-col gap-3 px-6 py-4", tab !== "library" && "hidden"), children: /* @__PURE__ */ jsx(ScrollArea, { className: "h-[280px]", children: isLoadingImages ? /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center py-20", children: /* @__PURE__ */ jsx(Loader2, { className: "h-6 w-6 animate-spin text-gray-400" }) }) : libraryImages.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col items-center justify-center py-20 text-center", children: [
167
+ /* @__PURE__ */ jsx(Search, { className: "h-8 w-8 text-gray-300" }),
168
+ /* @__PURE__ */ jsx("p", { className: "mt-3 text-sm font-medium text-gray-500", children: "No images available" }),
169
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-gray-400", children: "Try uploading an image instead" })
170
+ ] }) : /* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 gap-2", children: libraryImages.map((src) => {
163
171
  const isSelected = selectedImg === src;
164
172
  return /* @__PURE__ */ jsxs(
165
173
  "button",
@@ -0,0 +1,70 @@
1
+ import { Component, createElement, createContext } from "react";
2
+ const h = createContext(null), c = {
3
+ didCatch: false,
4
+ error: null
5
+ };
6
+ class m extends Component {
7
+ constructor(e) {
8
+ super(e), this.resetErrorBoundary = this.resetErrorBoundary.bind(this), this.state = c;
9
+ }
10
+ static getDerivedStateFromError(e) {
11
+ return { didCatch: true, error: e };
12
+ }
13
+ resetErrorBoundary(...e) {
14
+ var _a, _b;
15
+ const { error: t } = this.state;
16
+ t !== null && ((_b = (_a = this.props).onReset) == null ? void 0 : _b.call(_a, {
17
+ args: e,
18
+ reason: "imperative-api"
19
+ }), this.setState(c));
20
+ }
21
+ componentDidCatch(e, t) {
22
+ var _a, _b;
23
+ (_b = (_a = this.props).onError) == null ? void 0 : _b.call(_a, e, t);
24
+ }
25
+ componentDidUpdate(e, t) {
26
+ var _a, _b;
27
+ const { didCatch: o } = this.state, { resetKeys: s } = this.props;
28
+ o && t.error !== null && C(e.resetKeys, s) && ((_b = (_a = this.props).onReset) == null ? void 0 : _b.call(_a, {
29
+ next: s,
30
+ prev: e.resetKeys,
31
+ reason: "keys"
32
+ }), this.setState(c));
33
+ }
34
+ render() {
35
+ const { children: e, fallbackRender: t, FallbackComponent: o, fallback: s } = this.props, { didCatch: n, error: a } = this.state;
36
+ let i = e;
37
+ if (n) {
38
+ const u = {
39
+ error: a,
40
+ resetErrorBoundary: this.resetErrorBoundary
41
+ };
42
+ if (typeof t == "function")
43
+ i = t(u);
44
+ else if (o)
45
+ i = createElement(o, u);
46
+ else if (s !== void 0)
47
+ i = s;
48
+ else
49
+ throw a;
50
+ }
51
+ return createElement(
52
+ h.Provider,
53
+ {
54
+ value: {
55
+ didCatch: n,
56
+ error: a,
57
+ resetErrorBoundary: this.resetErrorBoundary
58
+ }
59
+ },
60
+ i
61
+ );
62
+ }
63
+ }
64
+ function C(r = [], e = []) {
65
+ return r.length !== e.length || r.some((t, o) => !Object.is(t, e[o]));
66
+ }
67
+ export {
68
+ m as ErrorBoundary,
69
+ h as ErrorBoundaryContext
70
+ };
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "dune-react",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "vite",
8
- "gen:meta": "tsx scripts/generate-component-meta.ts",
8
+ "gen:meta": "node --import ./scripts/register-mocks.mjs --import tsx/esm scripts/generate-component-meta.ts",
9
9
  "build": "pnpm gen:meta && vite build && tsc --project tsconfig.build.json",
10
10
  "watch": "vite build --watch & tsc --project tsconfig.build.json --watch --preserveWatchOutput &",
11
11
  "build:sync": "pnpm build && cd /Volumes/code/magent && pnpm sync:dune",
@@ -13,7 +13,7 @@
13
13
  "preview": "vite preview",
14
14
  "storybook": "storybook dev -p 8080",
15
15
  "preview-storybook": "npx http-server storybook-static",
16
- "build-storybook": "storybook build",
16
+ "build-storybook": "storybook build && cp -r skills storybook-static/skills",
17
17
  "deploy-storybook": "storybook-to-ghpages -- --existing-output-dir=storybook-static",
18
18
  "ondeploy": "pnpm build-storybook && pnpm deploy-storybook"
19
19
  },
@@ -70,7 +70,6 @@
70
70
  "eslint-plugin-storybook": "^10.2.19",
71
71
  "motion": "12.23.12",
72
72
  "react-easy-crop": "5.5.6",
73
- "react-error-boundary": "6.1.1",
74
73
  "sass": "^1.90.0",
75
74
  "sass-loader": "^16.0.5",
76
75
  "storybook": "^10.2.19",
@@ -143,7 +142,6 @@
143
142
  "react-day-picker": "^9.0.0",
144
143
  "react-dom": "^19.0.0",
145
144
  "react-easy-crop": "^5.0.0",
146
- "react-error-boundary": "^4.0.0",
147
145
  "react-is": "^19.0.0",
148
146
  "react-remove-scroll": "^2.0.0",
149
147
  "react-resizable-panels": "^2.0.0",
@@ -404,6 +402,7 @@
404
402
  "strip-ansi": "^6"
405
403
  },
406
404
  "dependencies": {
405
+ "react-error-boundary": "6.1.1",
407
406
  "shadcn": "4.0.8"
408
407
  }
409
408
  }