@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 +130 -17
- package/dist/diagram.serializers-OK4HP7AB.js +273 -0
- package/dist/diagram.serializers-OK4HP7AB.js.map +1 -0
- package/dist/index.cjs +7489 -329
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1681 -34
- package/dist/index.d.ts +1681 -34
- package/dist/index.js +7106 -326
- package/dist/index.js.map +1 -1
- package/dist/styles.css +116 -0
- package/dist/styles.css.map +1 -1
- package/package.json +13 -11
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
|
|
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,
|
|
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
|
|
41
|
-
| ColorPicker |
|
|
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
|
-
│ ├──
|
|
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.
|
|
105
|
-
4. **`
|
|
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 `
|
|
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
|
|
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"]}
|