@particle-academy/react-fancy 1.0.0 → 1.3.1

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/README.md CHANGED
@@ -5,15 +5,26 @@ React UI component library — the React port of the `fancy-flux` Blade/Livewire
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
+ # npm
9
+ npm install @particle-academy/react-fancy
10
+
11
+ # pnpm
8
12
  pnpm add @particle-academy/react-fancy
13
+
14
+ # yarn
15
+ yarn add @particle-academy/react-fancy
9
16
  ```
10
17
 
11
- Peer dependencies: `react >= 18`, `react-dom >= 18`.
18
+ **Peer dependencies:** `react >= 18`, `react-dom >= 18`, `tailwindcss >= 4`
19
+
20
+ **Bundled dependencies:** `clsx`, `tailwind-merge`, `marked`
21
+
22
+ **External dependency:** `lucide-react` (default icon library)
12
23
 
13
24
  ## Usage
14
25
 
15
26
  ```tsx
16
- import { Action, Input, Select, Carousel } from "@particle-academy/react-fancy";
27
+ import { Action, Input, Modal, Dropdown } from "@particle-academy/react-fancy";
17
28
  import "@particle-academy/react-fancy/styles.css";
18
29
  ```
19
30
 
@@ -34,14 +45,21 @@ npx vite build # Build demo app (verifies imports work)
34
45
 
35
46
  ## Components
36
47
 
48
+ ### Core
49
+
37
50
  | Component | Description |
38
51
  |-----------|-------------|
39
52
  | Action | Button with colors, states, icons, emoji, avatar, badge, sort control |
40
- | Carousel | Slide carousel with controls, steps, and panels |
41
- | ColorPicker | Color selection widget |
53
+ | Carousel | Slide carousel with directional/wizard variants, autoplay, loop |
54
+ | ColorPicker | Native color input with swatch preview, hex display, presets |
42
55
  | Emoji | Emoji renderer from slugs |
43
56
  | EmojiSelect | Emoji search and selection dropdown |
44
57
  | Table | Data table with sorting, pagination, search, and tray |
58
+
59
+ ### Form Inputs
60
+
61
+ | Component | Description |
62
+ |-----------|-------------|
45
63
  | Field | Form field wrapper with label and error display |
46
64
  | Input | Text input |
47
65
  | Textarea | Multi-line text input |
@@ -51,6 +69,93 @@ npx vite build # Build demo app (verifies imports work)
51
69
  | Switch | Toggle switch |
52
70
  | Slider | Range slider (single and range modes) |
53
71
  | DatePicker | Date selection (single and range modes) |
72
+ | Autocomplete | Input with filtered dropdown suggestions, async search, keyboard nav |
73
+ | Pillbox | Tag/pill input with add/remove, backspace delete |
74
+ | OtpInput | Single-digit OTP code input with auto-advance and paste support |
75
+ | FileUpload | Drag-and-drop file upload with dropzone and file list |
76
+ | TimePicker | Hour/minute/AM-PM time selection |
77
+ | Calendar | Month grid with single, range, and multi-select modes |
78
+
79
+ ### Display
80
+
81
+ | Component | Description |
82
+ |-----------|-------------|
83
+ | Heading | Semantic heading (`h1`–`h6`) with size and weight props |
84
+ | Text | Paragraph/span with size, color, weight, and `as` prop |
85
+ | Separator | Horizontal/vertical divider with optional label |
86
+ | Badge | Inline label with color, variant, size, and dot indicator |
87
+ | Icon | Size wrapper around icon ReactNode |
88
+ | Avatar | Image with fallback initials, size variants, status indicator |
89
+ | Skeleton | Animated placeholder (rect, circle, text), pulse animation |
90
+ | Progress | Bar and circular variants, indeterminate mode |
91
+ | Brand | Logo + text lockup |
92
+ | Profile | Avatar + name + subtitle layout |
93
+ | Card | Container with Header, Body, Footer compound slots |
94
+ | Callout | Alert/info box with icon, color, and dismissible support |
95
+ | Timeline | Vertical event list with Item and Block sub-components |
96
+
97
+ ### Overlay & Floating
98
+
99
+ | Component | Description |
100
+ |-----------|-------------|
101
+ | Tooltip | Hover/focus tooltip with arrow and placement control |
102
+ | Popover | Click-triggered floating panel |
103
+ | Dropdown | Popover with keyboard-navigable menu items |
104
+ | ContextMenu | Right-click triggered dropdown |
105
+ | Modal | Full-screen backdrop dialog with focus trap and scroll lock |
106
+ | Toast | Notification stack with auto-dismiss, variants, and position options |
107
+ | Command | `Cmd+K` command palette with search and keyboard navigation |
108
+
109
+ ### Navigation & Layout
110
+
111
+ | Component | Description |
112
+ |-----------|-------------|
113
+ | Tabs | Tabbed content with underline, pills, and boxed variants |
114
+ | Accordion | Collapsible content sections (single/multiple mode) |
115
+ | Breadcrumbs | Navigation breadcrumb trail with separator |
116
+ | Navbar | Responsive navigation bar with hamburger collapse |
117
+ | Pagination | Page navigation with prev/next and ellipsis |
118
+
119
+ ### Rich Content
120
+
121
+ | Component | Description |
122
+ |-----------|-------------|
123
+ | Composer | Chat-style message input composing textarea + actions |
124
+ | Chart | SVG-based Bar and Donut charts |
125
+ | Editor | Toolbar chrome wrapper for contentEditable |
126
+ | Kanban | Drag-and-drop board with columns and cards (experimental) |
127
+
128
+ ### Utilities & Hooks
129
+
130
+ | Export | Description |
131
+ |--------|-------------|
132
+ | Portal | `createPortal` wrapper with automatic dark mode propagation |
133
+ | `cn()` | `clsx` + `tailwind-merge` for conditional class composition |
134
+ | `useControllableState` | Controlled/uncontrolled state management |
135
+ | `useFloatingPosition` | Anchor-relative positioning for floating elements |
136
+ | `useOutsideClick` | Close on click outside handler |
137
+ | `useEscapeKey` | Close on Escape key handler |
138
+ | `useFocusTrap` | Tab-cycle focus within container |
139
+ | `useAnimation` | Enter/exit CSS transitions with unmount |
140
+ | `useId` | Stable ID generation |
141
+
142
+ ## Customization
143
+
144
+ All components render a `data-react-fancy-*` attribute on their root element (e.g., `data-react-fancy-modal`, `data-react-fancy-dropdown-item`). Use these for external CSS targeting or JavaScript integration:
145
+
146
+ ```css
147
+ [data-react-fancy-modal] {
148
+ --custom-border-radius: 1rem;
149
+ }
150
+ ```
151
+
152
+ ```js
153
+ document.querySelectorAll("[data-react-fancy-dropdown-item]");
154
+ ```
155
+
156
+ ## Dark Mode
157
+
158
+ Dark mode works via Tailwind's `dark:` class strategy. The library's `Portal` component automatically detects the `dark` class (or `data-theme="dark"`) on `<html>` and propagates it into portaled content (modals, dropdowns, tooltips, toasts, etc.).
54
159
 
55
160
  ## Architecture
56
161
 
@@ -63,27 +168,30 @@ src/
63
168
  │ │ ├── Action.tsx # Component implementation
64
169
  │ │ ├── Action.types.ts # Props interface
65
170
  │ │ └── index.ts # Re-exports
66
- │ ├── Carousel/
171
+ │ ├── Modal/
172
+ │ │ ├── Modal.tsx # Root + Object.assign compound
173
+ │ │ ├── Modal.context.ts # React context (compound components)
174
+ │ │ ├── Modal.types.ts # Props interfaces
175
+ │ │ ├── ModalHeader.tsx # Sub-component
176
+ │ │ ├── ModalBody.tsx
177
+ │ │ ├── ModalFooter.tsx
178
+ │ │ └── index.ts
67
179
  │ ├── inputs/ # Form input components (Field, Input, Select, etc.)
68
180
  │ └── ...
69
181
  ├── data/ # Static data (emoji entries, etc.)
70
182
  ├── hooks/ # Shared React hooks
71
183
  ├── utils/ # Shared utilities (cn, types)
184
+ ├── styles.css # Keyframe animations
72
185
  └── index.ts # Public API — all exports
73
186
  ```
74
187
 
75
- ### Key Utilities
76
-
77
- - **`cn()`** (`utils/cn.ts`) — `clsx` + `tailwind-merge` for conditional class composition.
78
- - **`resolve(slug)`** (`data/emoji-utils.ts`) — Resolves emoji slugs (e.g., `"rocket"`) to Unicode characters.
79
- - **`useControllableState`** (`hooks/`) — For components supporting both controlled and uncontrolled modes.
80
-
81
188
  ### Shared Types (`utils/types.ts`)
82
189
 
83
190
  - `Size` — `"xs" | "sm" | "md" | "lg" | "xl"`
84
191
  - `Color` — Full Tailwind color palette (17 colors)
85
192
  - `ActionColor` — Subset of 10 standalone colors matching fancy-flux
86
193
  - `Variant` — `"solid" | "outline" | "ghost" | "soft"`
194
+ - `Placement` — `"top" | "bottom" | "left" | "right"` + start/end variants
87
195
 
88
196
  ## Demo Pages
89
197
 
@@ -100,9 +208,14 @@ Guidelines for AI agents (Claude Code, Copilot, etc.) working on this package.
100
208
  Every component follows this structure:
101
209
 
102
210
  1. **`ComponentName.types.ts`** — Props interface extending native HTML element attributes. Import shared types from `../../utils/types`.
103
- 2. **`ComponentName.tsx`** — Implementation using `forwardRef`. Always set `displayName`. Use `cn()` for class merging.
104
- 3. **`index.ts`** Re-exports both the component and its types.
105
- 4. **`src/index.ts`** — Must export the component and its prop types. Update this file when adding new components.
211
+ 2. **`ComponentName.tsx`** — Implementation using `forwardRef`. Always set `displayName`. Use `cn()` for class merging. Add `data-react-fancy-{name}=""` to the root element.
212
+ 3. **Compound components** — Use `Object.assign(Root, { Sub1, Sub2 })` pattern. Add a `.context.ts` with React context. Each sub-component gets its own `data-react-fancy-{parent}-{sub}` attribute.
213
+ 4. **`index.ts`** — Re-exports both the component and its types.
214
+ 5. **`src/index.ts`** — Must export the component and its prop types. Update this file when adding new components.
215
+
216
+ ### Icons
217
+
218
+ Use `lucide-react` as the default icon library. It is a dependency of this package and marked as external in tsup. Components should import icons directly (e.g., `import { X, ChevronDown } from "lucide-react"`).
106
219
 
107
220
  ### Parity with fancy-flux
108
221
 
@@ -113,8 +226,8 @@ Every component follows this structure:
113
226
  ### Styling
114
227
 
115
228
  - **Tailwind v4** — CSS-first config. Use `@import "tailwindcss"` not `@tailwind` directives.
116
- - **Dark mode** — Every color variant must include `dark:` equivalents. Check fancy-flux for the exact classes.
117
- - **No component library deps** — Only `clsx` and `tailwind-merge`. Don't add Radix, Headless UI, or similar.
229
+ - **Dark mode** — Every color variant must include `dark:` equivalents. Check fancy-flux for the exact classes. Portal components get dark mode automatically via the Portal wrapper.
230
+ - **No component library deps** — Only `clsx`, `tailwind-merge`, and `lucide-react`. Don't add Radix, Headless UI, or similar.
118
231
  - Class maps should be `Record<Size, string>` (or similar) constants outside the component function, not inline.
119
232
 
120
233
  ### TypeScript
@@ -126,6 +239,6 @@ Every component follows this structure:
126
239
  ### Build
127
240
 
128
241
  - tsup handles the build — ESM, CJS, and `.d.ts` generation.
129
- - `react` and `react-dom` are external peer dependencies, never bundled.
242
+ - `react`, `react-dom`, and `lucide-react` are external dependencies, never bundled.
130
243
  - After any change, verify with `pnpm --filter @particle-academy/react-fancy build` before considering the work done.
131
244
  - When updating a component, update its demo page in `resources/js/react-demos/pages/` to cover all new features.
@@ -0,0 +1,273 @@
1
+ // src/components/Diagram/diagram.serializers.ts
2
+ function serializeToERD(schema) {
3
+ const lines = [];
4
+ for (const entity of schema.entities) {
5
+ lines.push(`[${entity.name}]`);
6
+ if (entity.fields) {
7
+ for (const field of entity.fields) {
8
+ const parts = [` ${field.name}`];
9
+ if (field.type) parts.push(field.type);
10
+ if (field.primary) parts.push("PK");
11
+ if (field.foreign) parts.push("FK");
12
+ if (field.nullable) parts.push("?");
13
+ lines.push(parts.join(" "));
14
+ }
15
+ }
16
+ lines.push("");
17
+ }
18
+ for (const rel of schema.relations) {
19
+ const fromEntity = schema.entities.find((e) => e.id === rel.from);
20
+ const toEntity = schema.entities.find((e) => e.id === rel.to);
21
+ if (!fromEntity || !toEntity) continue;
22
+ const marker = getERDMarker(rel.type);
23
+ const parts = [fromEntity.name, marker, toEntity.name];
24
+ if (rel.label) parts.push(`: ${rel.label}`);
25
+ lines.push(parts.join(" "));
26
+ }
27
+ return lines.join("\n").trim();
28
+ }
29
+ function getERDMarker(type) {
30
+ switch (type) {
31
+ case "one-to-one":
32
+ return "1--1";
33
+ case "one-to-many":
34
+ return "1--*";
35
+ case "many-to-many":
36
+ return "*--*";
37
+ }
38
+ }
39
+ function serializeToUML(schema) {
40
+ const lines = ["@startuml"];
41
+ for (const entity of schema.entities) {
42
+ lines.push(`class ${entity.name} {`);
43
+ if (entity.fields) {
44
+ for (const field of entity.fields) {
45
+ const typeStr = field.type ?? "any";
46
+ const nullable = field.nullable ? "?" : "";
47
+ const stereotype = field.primary ? " <<PK>>" : field.foreign ? " <<FK>>" : "";
48
+ lines.push(` ${field.name} : ${typeStr}${nullable}${stereotype}`);
49
+ }
50
+ }
51
+ lines.push("}");
52
+ lines.push("");
53
+ }
54
+ for (const rel of schema.relations) {
55
+ const fromEntity = schema.entities.find((e) => e.id === rel.from);
56
+ const toEntity = schema.entities.find((e) => e.id === rel.to);
57
+ if (!fromEntity || !toEntity) continue;
58
+ const arrow = getUMLArrow(rel.type);
59
+ const label = rel.label ? ` : ${rel.label}` : "";
60
+ lines.push(`${fromEntity.name} ${arrow} ${toEntity.name}${label}`);
61
+ }
62
+ lines.push("@enduml");
63
+ return lines.join("\n");
64
+ }
65
+ function getUMLArrow(type) {
66
+ switch (type) {
67
+ case "one-to-one":
68
+ return '"1" -- "1"';
69
+ case "one-to-many":
70
+ return '"1" -- "*"';
71
+ case "many-to-many":
72
+ return '"*" -- "*"';
73
+ }
74
+ }
75
+ function serializeToDFD(schema) {
76
+ const lines = [];
77
+ for (const entity of schema.entities) {
78
+ lines.push(`entity ${entity.name}`);
79
+ }
80
+ lines.push("");
81
+ for (const rel of schema.relations) {
82
+ const fromEntity = schema.entities.find((e) => e.id === rel.from);
83
+ const toEntity = schema.entities.find((e) => e.id === rel.to);
84
+ if (!fromEntity || !toEntity) continue;
85
+ const label = rel.label ? ` "${rel.label}"` : "";
86
+ lines.push(`${fromEntity.name} -> ${toEntity.name}${label}`);
87
+ }
88
+ return lines.join("\n").trim();
89
+ }
90
+ function deserializeSchema(input, format) {
91
+ switch (format) {
92
+ case "erd":
93
+ return deserializeERD(input);
94
+ case "uml":
95
+ return deserializeUML(input);
96
+ case "dfd":
97
+ return deserializeDFD(input);
98
+ }
99
+ }
100
+ function deserializeERD(input) {
101
+ const entities = [];
102
+ const relations = [];
103
+ const lines = input.split("\n");
104
+ let currentEntity = null;
105
+ for (const rawLine of lines) {
106
+ const line = rawLine.trim();
107
+ const entityMatch = line.match(/^\[(.+)\]$/);
108
+ if (entityMatch) {
109
+ currentEntity = {
110
+ id: entityMatch[1].toLowerCase().replace(/\s+/g, "_"),
111
+ name: entityMatch[1],
112
+ fields: []
113
+ };
114
+ entities.push(currentEntity);
115
+ continue;
116
+ }
117
+ if (currentEntity && rawLine.startsWith(" ") && line.length > 0) {
118
+ const parts = line.split(/\s+/);
119
+ const field = { name: parts[0] };
120
+ if (parts.length > 1 && !["PK", "FK", "?"].includes(parts[1])) {
121
+ field.type = parts[1];
122
+ }
123
+ if (parts.includes("PK")) field.primary = true;
124
+ if (parts.includes("FK")) field.foreign = true;
125
+ if (parts.includes("?")) field.nullable = true;
126
+ currentEntity.fields.push(field);
127
+ continue;
128
+ }
129
+ const relMatch = line.match(
130
+ /^(\S+)\s+(1--1|1--\*|\*--\*)\s+(\S+)(?:\s*:\s*(.+))?$/
131
+ );
132
+ if (relMatch) {
133
+ currentEntity = null;
134
+ const fromName = relMatch[1];
135
+ const marker = relMatch[2];
136
+ const toName = relMatch[3];
137
+ const label = relMatch[4];
138
+ const fromEntity = entities.find((e) => e.name === fromName);
139
+ const toEntity = entities.find((e) => e.name === toName);
140
+ if (fromEntity && toEntity) {
141
+ relations.push({
142
+ id: `${fromEntity.id}_${toEntity.id}`,
143
+ from: fromEntity.id,
144
+ to: toEntity.id,
145
+ type: parseERDMarker(marker),
146
+ label
147
+ });
148
+ }
149
+ continue;
150
+ }
151
+ if (line === "") {
152
+ currentEntity = null;
153
+ }
154
+ }
155
+ return { entities, relations };
156
+ }
157
+ function parseERDMarker(marker) {
158
+ switch (marker) {
159
+ case "1--1":
160
+ return "one-to-one";
161
+ case "1--*":
162
+ return "one-to-many";
163
+ case "*--*":
164
+ return "many-to-many";
165
+ default:
166
+ return "one-to-many";
167
+ }
168
+ }
169
+ function deserializeUML(input) {
170
+ const entities = [];
171
+ const relations = [];
172
+ const lines = input.split("\n");
173
+ let currentEntity = null;
174
+ for (const rawLine of lines) {
175
+ const line = rawLine.trim();
176
+ if (line === "@startuml" || line === "@enduml" || line === "") continue;
177
+ const classMatch = line.match(/^class\s+(\S+)\s*\{$/);
178
+ if (classMatch) {
179
+ currentEntity = {
180
+ id: classMatch[1].toLowerCase().replace(/\s+/g, "_"),
181
+ name: classMatch[1],
182
+ fields: []
183
+ };
184
+ entities.push(currentEntity);
185
+ continue;
186
+ }
187
+ if (line === "}") {
188
+ currentEntity = null;
189
+ continue;
190
+ }
191
+ if (currentEntity) {
192
+ const fieldMatch = line.match(
193
+ /^(\S+)\s*:\s*(\S+?)(\?)?(?:\s*<<(PK|FK)>>)?$/
194
+ );
195
+ if (fieldMatch) {
196
+ const field = {
197
+ name: fieldMatch[1],
198
+ type: fieldMatch[2]
199
+ };
200
+ if (fieldMatch[3]) field.nullable = true;
201
+ if (fieldMatch[4] === "PK") field.primary = true;
202
+ if (fieldMatch[4] === "FK") field.foreign = true;
203
+ currentEntity.fields.push(field);
204
+ }
205
+ continue;
206
+ }
207
+ const relMatch = line.match(
208
+ /^(\S+)\s+"([1*])"\s+--\s+"([1*])"\s+(\S+)(?:\s*:\s*(.+))?$/
209
+ );
210
+ if (relMatch) {
211
+ const fromName = relMatch[1];
212
+ const fromCard = relMatch[2];
213
+ const toCard = relMatch[3];
214
+ const toName = relMatch[4];
215
+ const label = relMatch[5];
216
+ const fromEntity = entities.find((e) => e.name === fromName);
217
+ const toEntity = entities.find((e) => e.name === toName);
218
+ if (fromEntity && toEntity) {
219
+ const type = fromCard === "1" && toCard === "1" ? "one-to-one" : fromCard === "1" && toCard === "*" ? "one-to-many" : "many-to-many";
220
+ relations.push({
221
+ id: `${fromEntity.id}_${toEntity.id}`,
222
+ from: fromEntity.id,
223
+ to: toEntity.id,
224
+ type,
225
+ label
226
+ });
227
+ }
228
+ }
229
+ }
230
+ return { entities, relations };
231
+ }
232
+ function deserializeDFD(input) {
233
+ const entities = [];
234
+ const relations = [];
235
+ const lines = input.split("\n");
236
+ for (const rawLine of lines) {
237
+ const line = rawLine.trim();
238
+ if (line === "") continue;
239
+ const entityMatch = line.match(/^entity\s+(\S+)$/);
240
+ if (entityMatch) {
241
+ entities.push({
242
+ id: entityMatch[1].toLowerCase().replace(/\s+/g, "_"),
243
+ name: entityMatch[1],
244
+ fields: []
245
+ });
246
+ continue;
247
+ }
248
+ const flowMatch = line.match(
249
+ /^(\S+)\s+->\s+(\S+)(?:\s+"(.+)")?$/
250
+ );
251
+ if (flowMatch) {
252
+ const fromName = flowMatch[1];
253
+ const toName = flowMatch[2];
254
+ const label = flowMatch[3];
255
+ const fromEntity = entities.find((e) => e.name === fromName);
256
+ const toEntity = entities.find((e) => e.name === toName);
257
+ if (fromEntity && toEntity) {
258
+ relations.push({
259
+ id: `${fromEntity.id}_${toEntity.id}`,
260
+ from: fromEntity.id,
261
+ to: toEntity.id,
262
+ type: "one-to-many",
263
+ label
264
+ });
265
+ }
266
+ }
267
+ }
268
+ return { entities, relations };
269
+ }
270
+
271
+ export { deserializeSchema, serializeToDFD, serializeToERD, serializeToUML };
272
+ //# sourceMappingURL=diagram.serializers-OK4HP7AB.js.map
273
+ //# sourceMappingURL=diagram.serializers-OK4HP7AB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/Diagram/diagram.serializers.ts"],"names":[],"mappings":"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","file":"diagram.serializers-OK4HP7AB.js","sourcesContent":["import type {\n DiagramSchema,\n DiagramEntityData,\n DiagramRelationData,\n DiagramFieldData,\n ExportFormat,\n RelationType,\n} from \"./Diagram.types\";\n\n/**\n * Serialize a diagram schema to PlantUML-style ERD text.\n *\n * Example output:\n * ```\n * [Users]\n * id int PK\n * email varchar\n *\n * [Posts]\n * id int PK\n * user_id int FK\n *\n * Users 1--* Posts\n * ```\n */\nexport function serializeToERD(schema: DiagramSchema): string {\n const lines: string[] = [];\n\n for (const entity of schema.entities) {\n lines.push(`[${entity.name}]`);\n if (entity.fields) {\n for (const field of entity.fields) {\n const parts = [` ${field.name}`];\n if (field.type) parts.push(field.type);\n if (field.primary) parts.push(\"PK\");\n if (field.foreign) parts.push(\"FK\");\n if (field.nullable) parts.push(\"?\");\n lines.push(parts.join(\" \"));\n }\n }\n lines.push(\"\");\n }\n\n for (const rel of schema.relations) {\n const fromEntity = schema.entities.find((e) => e.id === rel.from);\n const toEntity = schema.entities.find((e) => e.id === rel.to);\n if (!fromEntity || !toEntity) continue;\n\n const marker = getERDMarker(rel.type);\n const parts = [fromEntity.name, marker, toEntity.name];\n if (rel.label) parts.push(`: ${rel.label}`);\n lines.push(parts.join(\" \"));\n }\n\n return lines.join(\"\\n\").trim();\n}\n\nfunction getERDMarker(type: RelationType): string {\n switch (type) {\n case \"one-to-one\":\n return \"1--1\";\n case \"one-to-many\":\n return \"1--*\";\n case \"many-to-many\":\n return \"*--*\";\n }\n}\n\n/**\n * Serialize a diagram schema to PlantUML class diagram text.\n */\nexport function serializeToUML(schema: DiagramSchema): string {\n const lines: string[] = [\"@startuml\"];\n\n for (const entity of schema.entities) {\n lines.push(`class ${entity.name} {`);\n if (entity.fields) {\n for (const field of entity.fields) {\n const typeStr = field.type ?? \"any\";\n const nullable = field.nullable ? \"?\" : \"\";\n const stereotype = field.primary\n ? \" <<PK>>\"\n : field.foreign\n ? \" <<FK>>\"\n : \"\";\n lines.push(` ${field.name} : ${typeStr}${nullable}${stereotype}`);\n }\n }\n lines.push(\"}\");\n lines.push(\"\");\n }\n\n for (const rel of schema.relations) {\n const fromEntity = schema.entities.find((e) => e.id === rel.from);\n const toEntity = schema.entities.find((e) => e.id === rel.to);\n if (!fromEntity || !toEntity) continue;\n\n const arrow = getUMLArrow(rel.type);\n const label = rel.label ? ` : ${rel.label}` : \"\";\n lines.push(`${fromEntity.name} ${arrow} ${toEntity.name}${label}`);\n }\n\n lines.push(\"@enduml\");\n return lines.join(\"\\n\");\n}\n\nfunction getUMLArrow(type: RelationType): string {\n switch (type) {\n case \"one-to-one\":\n return '\"1\" -- \"1\"';\n case \"one-to-many\":\n return '\"1\" -- \"*\"';\n case \"many-to-many\":\n return '\"*\" -- \"*\"';\n }\n}\n\n/**\n * Serialize a diagram schema to a simple DFD text format.\n */\nexport function serializeToDFD(schema: DiagramSchema): string {\n const lines: string[] = [];\n\n for (const entity of schema.entities) {\n lines.push(`entity ${entity.name}`);\n }\n\n lines.push(\"\");\n\n for (const rel of schema.relations) {\n const fromEntity = schema.entities.find((e) => e.id === rel.from);\n const toEntity = schema.entities.find((e) => e.id === rel.to);\n if (!fromEntity || !toEntity) continue;\n\n const label = rel.label ? ` \"${rel.label}\"` : \"\";\n lines.push(`${fromEntity.name} -> ${toEntity.name}${label}`);\n }\n\n return lines.join(\"\\n\").trim();\n}\n\n/**\n * Parse a serialized schema string back into a DiagramSchema.\n */\nexport function deserializeSchema(\n input: string,\n format: ExportFormat,\n): DiagramSchema {\n switch (format) {\n case \"erd\":\n return deserializeERD(input);\n case \"uml\":\n return deserializeUML(input);\n case \"dfd\":\n return deserializeDFD(input);\n }\n}\n\nfunction deserializeERD(input: string): DiagramSchema {\n const entities: DiagramEntityData[] = [];\n const relations: DiagramRelationData[] = [];\n const lines = input.split(\"\\n\");\n\n let currentEntity: DiagramEntityData | null = null;\n\n for (const rawLine of lines) {\n const line = rawLine.trim();\n\n // Entity header: [EntityName]\n const entityMatch = line.match(/^\\[(.+)\\]$/);\n if (entityMatch) {\n currentEntity = {\n id: entityMatch[1].toLowerCase().replace(/\\s+/g, \"_\"),\n name: entityMatch[1],\n fields: [],\n };\n entities.push(currentEntity);\n continue;\n }\n\n // Field line (indented): name type PK FK ?\n if (currentEntity && rawLine.startsWith(\" \") && line.length > 0) {\n const parts = line.split(/\\s+/);\n const field: DiagramFieldData = { name: parts[0] };\n if (parts.length > 1 && ![\"PK\", \"FK\", \"?\"].includes(parts[1])) {\n field.type = parts[1];\n }\n if (parts.includes(\"PK\")) field.primary = true;\n if (parts.includes(\"FK\")) field.foreign = true;\n if (parts.includes(\"?\")) field.nullable = true;\n currentEntity.fields!.push(field);\n continue;\n }\n\n // Relation: EntityA 1--* EntityB : label\n const relMatch = line.match(\n /^(\\S+)\\s+(1--1|1--\\*|\\*--\\*)\\s+(\\S+)(?:\\s*:\\s*(.+))?$/,\n );\n if (relMatch) {\n currentEntity = null;\n const fromName = relMatch[1];\n const marker = relMatch[2];\n const toName = relMatch[3];\n const label = relMatch[4];\n\n const fromEntity = entities.find((e) => e.name === fromName);\n const toEntity = entities.find((e) => e.name === toName);\n if (fromEntity && toEntity) {\n relations.push({\n id: `${fromEntity.id}_${toEntity.id}`,\n from: fromEntity.id,\n to: toEntity.id,\n type: parseERDMarker(marker),\n label,\n });\n }\n continue;\n }\n\n // Empty line resets current entity context\n if (line === \"\") {\n currentEntity = null;\n }\n }\n\n return { entities, relations };\n}\n\nfunction parseERDMarker(marker: string): RelationType {\n switch (marker) {\n case \"1--1\":\n return \"one-to-one\";\n case \"1--*\":\n return \"one-to-many\";\n case \"*--*\":\n return \"many-to-many\";\n default:\n return \"one-to-many\";\n }\n}\n\nfunction deserializeUML(input: string): DiagramSchema {\n const entities: DiagramEntityData[] = [];\n const relations: DiagramRelationData[] = [];\n const lines = input.split(\"\\n\");\n\n let currentEntity: DiagramEntityData | null = null;\n\n for (const rawLine of lines) {\n const line = rawLine.trim();\n\n if (line === \"@startuml\" || line === \"@enduml\" || line === \"\") continue;\n\n // Class header: class EntityName {\n const classMatch = line.match(/^class\\s+(\\S+)\\s*\\{$/);\n if (classMatch) {\n currentEntity = {\n id: classMatch[1].toLowerCase().replace(/\\s+/g, \"_\"),\n name: classMatch[1],\n fields: [],\n };\n entities.push(currentEntity);\n continue;\n }\n\n // Closing brace\n if (line === \"}\") {\n currentEntity = null;\n continue;\n }\n\n // Field: name : type<<stereotype>>\n if (currentEntity) {\n const fieldMatch = line.match(\n /^(\\S+)\\s*:\\s*(\\S+?)(\\?)?(?:\\s*<<(PK|FK)>>)?$/,\n );\n if (fieldMatch) {\n const field: DiagramFieldData = {\n name: fieldMatch[1],\n type: fieldMatch[2],\n };\n if (fieldMatch[3]) field.nullable = true;\n if (fieldMatch[4] === \"PK\") field.primary = true;\n if (fieldMatch[4] === \"FK\") field.foreign = true;\n currentEntity.fields!.push(field);\n }\n continue;\n }\n\n // Relation: EntityA \"1\" -- \"*\" EntityB : label\n const relMatch = line.match(\n /^(\\S+)\\s+\"([1*])\"\\s+--\\s+\"([1*])\"\\s+(\\S+)(?:\\s*:\\s*(.+))?$/,\n );\n if (relMatch) {\n const fromName = relMatch[1];\n const fromCard = relMatch[2];\n const toCard = relMatch[3];\n const toName = relMatch[4];\n const label = relMatch[5];\n\n const fromEntity = entities.find((e) => e.name === fromName);\n const toEntity = entities.find((e) => e.name === toName);\n if (fromEntity && toEntity) {\n const type: RelationType =\n fromCard === \"1\" && toCard === \"1\"\n ? \"one-to-one\"\n : fromCard === \"1\" && toCard === \"*\"\n ? \"one-to-many\"\n : \"many-to-many\";\n relations.push({\n id: `${fromEntity.id}_${toEntity.id}`,\n from: fromEntity.id,\n to: toEntity.id,\n type,\n label,\n });\n }\n }\n }\n\n return { entities, relations };\n}\n\nfunction deserializeDFD(input: string): DiagramSchema {\n const entities: DiagramEntityData[] = [];\n const relations: DiagramRelationData[] = [];\n const lines = input.split(\"\\n\");\n\n for (const rawLine of lines) {\n const line = rawLine.trim();\n if (line === \"\") continue;\n\n // Entity: entity EntityName\n const entityMatch = line.match(/^entity\\s+(\\S+)$/);\n if (entityMatch) {\n entities.push({\n id: entityMatch[1].toLowerCase().replace(/\\s+/g, \"_\"),\n name: entityMatch[1],\n fields: [],\n });\n continue;\n }\n\n // Flow: EntityA -> EntityB \"label\"\n const flowMatch = line.match(\n /^(\\S+)\\s+->\\s+(\\S+)(?:\\s+\"(.+)\")?$/,\n );\n if (flowMatch) {\n const fromName = flowMatch[1];\n const toName = flowMatch[2];\n const label = flowMatch[3];\n\n const fromEntity = entities.find((e) => e.name === fromName);\n const toEntity = entities.find((e) => e.name === toName);\n if (fromEntity && toEntity) {\n relations.push({\n id: `${fromEntity.id}_${toEntity.id}`,\n from: fromEntity.id,\n to: toEntity.id,\n type: \"one-to-many\",\n label,\n });\n }\n }\n }\n\n return { entities, relations };\n}\n"]}