desen 0.0.1 → 1.0.0-draft.2

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.
@@ -0,0 +1,5 @@
1
+
2
+ 
3
+ > desen@1.0.0-draft.2 build /Users/selmanay/Desktop/desen/packages/react
4
+ > tsc
5
+
package/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # 💠 DESEN (Design Environment Protocol)
2
+
3
+ > A governed, executable representation of UI intent.
4
+
5
+ [![Version](https://img.shields.io/badge/version-1.0.3--draft-blue.svg)](#)
6
+ [![License](https://img.shields.io/badge/license-Apache%202.0-green.svg)](#)
7
+ [![npm package](https://img.shields.io/npm/v/desen.svg?label=desen&color=blue)](https://www.npmjs.com/package/desen)
8
+
9
+ DESEN is not another UI component library. It is a domain-specific language (DESEN-DSL), a deterministic runtime, and a Governance Core that makes user interfaces **measurable, auditable, and safe to operate at scale**.
10
+
11
+ Instead of writing custom React for every layout change, DESEN allows designers and engineers to define surfaces as strict, schema-bound JSON. Changes can be reviewed, rolled out, and rolled back based on verifiable telemetry outcomes.
12
+
13
+ * 🛡️ **Fail-Closed Runtime:** If a data binding fails or a schema is violated, DESEN gracefully degrades (fails-closed) instead of failing-open.
14
+ * ⚖️ **AST-Based Protocol:** Declarative, pure JSON UI descriptions enforcing design systems with DTCG design tokens.
15
+ * 📊 **Telemetry by Design:** Privacy-preserving observability built natively.
16
+
17
+ ## End-to-End Workflow (The Happy Path)
18
+
19
+ Here is how designers and developers bring a DESEN surface to life from scratch:
20
+
21
+ ### Step 1: Cold Start (The Daemon)
22
+ The developer or designer starts everything by launching the **DESEN CLI Daemon**. This sync server will listen on `localhost:3000` for JSON payloads sent out by Figma.
23
+
24
+ In any empty directory where you want to scaffold your project, run:
25
+ ```bash
26
+ npx desen daemon
27
+ ```
28
+ *(If you are inside the monorepo root, you can also run `pnpm desen daemon`)*
29
+
30
+ You will see:
31
+ ```text
32
+ 🚀 DESEN Daemon Active
33
+ The Synchronization (Sync) server is waiting for incoming UI AST data from Figma.
34
+ ```
35
+
36
+ ### Step 2: The Designer's Flow (Figma)
37
+ With the receiver (Daemon) ready, the designer takes the stage.
38
+ 1. Open **Figma** and the UI design you are working on.
39
+ 2. Launch the **DESEN Figma Plugin**.
40
+ 3. Select or configure UI components and tokens within the plugin.
41
+ 4. Click the **"Sync to Localhost"** button in the plugin UI.
42
+
43
+ ### Step 3: The Synchronization Dance (Figma ➔ Daemon ➔ Scaffolding)
44
+ Figma compiles the design into a protocol-compliant **UI AST (JSON)** and POSTs it to the waiting `localhost:3000/sync`.
45
+
46
+ 1. **Security & Validation:** The Daemon does not blindly accept JSON. It validates against `SPEC.md` rules using `desen-core`.
47
+ 2. **Scaffolding:** If valid and no existing project is found, the Daemon automatically creates a `desen-workspace` folder in the current directory.
48
+ 3. This folder is populated with a clean Next.js application wired up with the `desen` rendering engine, and the received `.desen.json` file is saved to disk.
49
+
50
+ ### Step 4: Live Preview (Render Engine)
51
+ To see the design vividly interact in the browser, spin up the newly created local application on port `3001`.
52
+
53
+ 1. **Without stopping the Daemon**, open a **new terminal tab**.
54
+ 2. Navigate into the scaffolded environment and launch it:
55
+ ```bash
56
+ cd desen-workspace
57
+ npm install
58
+ npm run dev
59
+ ```
60
+ 3. Visit `localhost:3001` in your browser.
61
+ 4. **The Result:** The UI compiled from Figma is now fully rendered interactively by the `desen` React engine with guaranteed telemetry (VIEW_ELEMENT, INTERACT) support! Every subsequent "Sync" from Figma updates the screen in seconds via Fast Refresh.
@@ -0,0 +1,32 @@
1
+ import React, { ReactNode } from "react";
2
+ import { ActionSpec } from "desen-core";
3
+ export interface TelemetryEnvelope {
4
+ session_id: string;
5
+ element_id: string;
6
+ timestamp: string;
7
+ revision_id: string;
8
+ event_type: "VIEW_ELEMENT" | "INTERACT" | "BINDING_ERROR" | string;
9
+ payload: Record<string, any>;
10
+ }
11
+ export interface DesenRegistry {
12
+ [key: string]: React.ComponentType<any>;
13
+ }
14
+ export interface DesenContextType {
15
+ registry: DesenRegistry;
16
+ onTelemetry: (event: TelemetryEnvelope) => void;
17
+ onAction: (action: ActionSpec) => void;
18
+ tokens?: Record<string, any>;
19
+ session_id: string;
20
+ revision_id: string;
21
+ }
22
+ export interface DesenProviderProps {
23
+ children: ReactNode;
24
+ registry: DesenRegistry;
25
+ onTelemetry: (event: TelemetryEnvelope) => void;
26
+ onAction: (action: ActionSpec) => void;
27
+ tokens?: Record<string, any>;
28
+ session_id: string;
29
+ revision_id: string;
30
+ }
31
+ export declare const DesenProvider: React.FC<DesenProviderProps>;
32
+ export declare const useDesen: () => DesenContextType;
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.useDesen = exports.DesenProvider = void 0;
37
+ const react_1 = __importStar(require("react"));
38
+ const DesenContext = (0, react_1.createContext)(undefined);
39
+ const DesenProvider = ({ children, registry, onTelemetry, onAction, tokens, session_id, revision_id }) => {
40
+ return (react_1.default.createElement(DesenContext.Provider, { value: {
41
+ registry,
42
+ onTelemetry,
43
+ onAction,
44
+ tokens,
45
+ session_id,
46
+ revision_id
47
+ } }, children));
48
+ };
49
+ exports.DesenProvider = DesenProvider;
50
+ const useDesen = () => {
51
+ const context = (0, react_1.useContext)(DesenContext);
52
+ if (!context) {
53
+ throw new Error("useDesen must be used within a DesenProvider");
54
+ }
55
+ return context;
56
+ };
57
+ exports.useDesen = useDesen;
@@ -0,0 +1,6 @@
1
+ import React from "react";
2
+ import type { NodeSpec } from "desen-core";
3
+ export interface DesenRendererProps {
4
+ node: NodeSpec;
5
+ }
6
+ export declare const DesenRenderer: React.FC<DesenRendererProps>;
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.DesenRenderer = void 0;
37
+ const react_1 = __importStar(require("react"));
38
+ const DesenProvider_1 = require("./DesenProvider");
39
+ const ErrorBoundary_1 = require("./ErrorBoundary");
40
+ const parseDesenStyles_1 = require("./utils/parseDesenStyles");
41
+ const BaseRenderer = ({ node }) => {
42
+ const { registry, tokens } = (0, DesenProvider_1.useDesen)();
43
+ const RegistryComponent = (0, react_1.useMemo)(() => {
44
+ // node.type üzerinden eşleşmeyi arıyoruz
45
+ return registry[node.type];
46
+ }, [registry, node.type]);
47
+ if (!RegistryComponent) {
48
+ // Fail-Closed: Bileşen yoksa null dön. UI çökertecek belirsiz durumdan kaçın.
49
+ console.warn(`[DESEN] RegistryComponent not found for type: ${node.type}`);
50
+ return null;
51
+ }
52
+ // Process DESEN styles securely with DTCG resolver
53
+ const parsedStyle = (0, react_1.useMemo)(() => {
54
+ const rawStyle = node.style || {};
55
+ const hasAction = !!node.action;
56
+ return (0, parseDesenStyles_1.parseDesenStyles)(rawStyle, hasAction, tokens);
57
+ }, [node, tokens]);
58
+ // Common Props — spread all node fields EXCEPT `children` and `root`
59
+ // (which would override the recursively-rendered React children).
60
+ const { children: _rawChildren, root: _rawRoot, ...nodeWithoutChildren } = node;
61
+ const props = {
62
+ ...nodeWithoutChildren,
63
+ ...(node.props || {}),
64
+ id: node.id,
65
+ type: node.type,
66
+ layout: node.layout,
67
+ constraints: node.constraints,
68
+ parsedStyle,
69
+ };
70
+ // Recursively render child nodes: supports both `children` array and `root` property
71
+ const children = (0, react_1.useMemo)(() => {
72
+ const pNode = node;
73
+ // Surface nodes use `root` instead of `children`
74
+ if (pNode.root && typeof pNode.root === 'object') {
75
+ // Inject _isSurfaceRoot flag so the registry component fills the viewport
76
+ const rootNode = { ...pNode.root, _isSurfaceRoot: true };
77
+ return react_1.default.createElement(exports.DesenRenderer, { node: rootNode });
78
+ }
79
+ // Composition nodes use `children` array
80
+ if (pNode.children && Array.isArray(pNode.children)) {
81
+ return pNode.children.map((child, index) => {
82
+ const uniqueKey = `${child.id || 'node'}-${index}`;
83
+ return react_1.default.createElement(exports.DesenRenderer, { key: uniqueKey, node: child });
84
+ });
85
+ }
86
+ return null;
87
+ }, [node]);
88
+ return react_1.default.createElement(RegistryComponent, { ...props }, children);
89
+ };
90
+ // Her renderer adımı ErrorBoundary ile sarılarak fail-closed emniyeti uygulanır
91
+ const DesenRenderer = ({ node }) => {
92
+ return (react_1.default.createElement(ErrorBoundary_1.ErrorBoundary, { elementId: node.id },
93
+ react_1.default.createElement(BaseRenderer, { node: node })));
94
+ };
95
+ exports.DesenRenderer = DesenRenderer;
@@ -0,0 +1,21 @@
1
+ import React, { Component, ReactNode } from "react";
2
+ import { useDesen } from "./DesenProvider";
3
+ interface ErrorBoundaryProps {
4
+ children: ReactNode;
5
+ elementId?: string;
6
+ fallback?: ReactNode;
7
+ onTelemetry: ReturnType<typeof useDesen>["onTelemetry"];
8
+ session_id: string;
9
+ revision_id: string;
10
+ }
11
+ interface ErrorBoundaryState {
12
+ hasError: boolean;
13
+ }
14
+ export declare class ErrorBoundaryClass extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
15
+ constructor(props: ErrorBoundaryProps);
16
+ static getDerivedStateFromError(_: Error): ErrorBoundaryState;
17
+ componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void;
18
+ render(): React.ReactNode;
19
+ }
20
+ export declare const ErrorBoundary: React.FC<Omit<ErrorBoundaryProps, "onTelemetry" | "session_id" | "revision_id">>;
21
+ export {};
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.ErrorBoundary = exports.ErrorBoundaryClass = void 0;
37
+ const react_1 = __importStar(require("react"));
38
+ const DesenProvider_1 = require("./DesenProvider");
39
+ class ErrorBoundaryClass extends react_1.Component {
40
+ constructor(props) {
41
+ super(props);
42
+ this.state = { hasError: false };
43
+ }
44
+ static getDerivedStateFromError(_) {
45
+ return { hasError: true };
46
+ }
47
+ componentDidCatch(error, errorInfo) {
48
+ const { onTelemetry, elementId, session_id, revision_id } = this.props;
49
+ onTelemetry({
50
+ session_id,
51
+ element_id: elementId || "unknown",
52
+ timestamp: new Date().toISOString(),
53
+ revision_id,
54
+ event_type: "BINDING_ERROR",
55
+ payload: {
56
+ error_class: "RENDER_ERROR",
57
+ message: error.message,
58
+ componentStack: errorInfo.componentStack,
59
+ },
60
+ });
61
+ }
62
+ render() {
63
+ if (this.state.hasError) {
64
+ // Fail-closed mantığı gereği ya null ya da verilmiş fallback
65
+ return this.props.fallback || null;
66
+ }
67
+ return this.props.children;
68
+ }
69
+ }
70
+ exports.ErrorBoundaryClass = ErrorBoundaryClass;
71
+ // Hook tabanlı context bilgilerini Class bazlı bileşene enjekte etmek için wrapper
72
+ const ErrorBoundary = (props) => {
73
+ const desenContext = (0, DesenProvider_1.useDesen)();
74
+ return (react_1.default.createElement(ErrorBoundaryClass, { ...props, onTelemetry: desenContext.onTelemetry, session_id: desenContext.session_id, revision_id: desenContext.revision_id }));
75
+ };
76
+ exports.ErrorBoundary = ErrorBoundary;
package/dist/bin.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ declare const args: string[];
3
+ declare const command: string;
package/dist/bin.js ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ const args = process.argv.slice(2);
4
+ const command = args[0];
5
+ if (command === "daemon" || command === "audit") {
6
+ // This will execute the exported CLI process from desen-cli since it runs side-effects directly
7
+ require("desen-cli");
8
+ }
9
+ else {
10
+ console.log(`
11
+ \x1b[36m💠 DESEN Protocol CLI\x1b[0m
12
+
13
+ Usage:
14
+ \x1b[1mnpx desen daemon\x1b[0m Start the local synchronization daemon
15
+ \x1b[1mnpx desen audit\x1b[0m Audit DESEN JSON files
16
+ `);
17
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ export * from 'desen-cli';
package/dist/cli.js ADDED
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("desen-cli"), exports);
package/dist/core.d.ts ADDED
@@ -0,0 +1 @@
1
+ export * from 'desen-core';
package/dist/core.js ADDED
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("desen-core"), exports);
@@ -0,0 +1,4 @@
1
+ export * from "./DesenProvider";
2
+ export * from "./ErrorBoundary";
3
+ export * from "./DesenRenderer";
4
+ export * from "./utils/parseDesenStyles";
package/dist/index.js ADDED
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./DesenProvider"), exports);
18
+ __exportStar(require("./ErrorBoundary"), exports);
19
+ __exportStar(require("./DesenRenderer"), exports);
20
+ __exportStar(require("./utils/parseDesenStyles"), exports);
File without changes
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ // Extracted out to avoid ts compilation removing the shebang if it is not a direct entry point
3
+ console.log("\n\x1b[36m💠 DESEN yüklendi! Çalıştırmak için: npx desen daemon\x1b[0m\n");
@@ -0,0 +1,5 @@
1
+ export interface ParentState {
2
+ isHovered: boolean;
3
+ isActive: boolean;
4
+ }
5
+ export declare const ParentStateContext: import("react").Context<ParentState>;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ParentStateContext = void 0;
4
+ const react_1 = require("react");
5
+ exports.ParentStateContext = (0, react_1.createContext)({
6
+ isHovered: false,
7
+ isActive: false
8
+ });
@@ -0,0 +1,12 @@
1
+ import React from "react";
2
+ import type { TokenTheme } from "desen-core";
3
+ /**
4
+ * Resolves a dot-notation token path within a Theme/Tokens object following DTCG schema
5
+ */
6
+ export declare function resolveTokenValue(path: string, theme?: TokenTheme | Record<string, any>): string | number | undefined;
7
+ /**
8
+ * Parses a generic DESEN style object into React.CSSProperties
9
+ * Replaces token attributes by looking up the dot notation string recursively in the theme context.
10
+ * Falls back to literals when available or strictly ignores (Fail-Closed) if neither works.
11
+ */
12
+ export declare function parseDesenStyles(style?: any, hasAction?: boolean, theme?: Record<string, any>): React.CSSProperties;
@@ -0,0 +1,208 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveTokenValue = resolveTokenValue;
4
+ exports.parseDesenStyles = parseDesenStyles;
5
+ /**
6
+ * Resolves a dot-notation token path within a Theme/Tokens object following DTCG schema
7
+ */
8
+ function resolveTokenValue(path, theme) {
9
+ if (!path || !theme)
10
+ return undefined;
11
+ // Clean string formatting like "{color.bg.primary}" if exists
12
+ const cleanPath = path.replace(/^\{|\}$/g, '');
13
+ const parts = cleanPath.split('.');
14
+ let current = theme;
15
+ for (const part of parts) {
16
+ if (current && typeof current === 'object' && part in current) {
17
+ current = current[part];
18
+ }
19
+ else {
20
+ return undefined;
21
+ }
22
+ }
23
+ // Resolve DTCG $value leaf
24
+ if (current && typeof current === 'object') {
25
+ if ('$value' in current && current.$value !== undefined)
26
+ return current.$value;
27
+ if ('value' in current && current.value !== undefined)
28
+ return current.value;
29
+ }
30
+ // Resolve direct primitive mapping
31
+ if (typeof current === 'string' || typeof current === 'number') {
32
+ return current;
33
+ }
34
+ return undefined;
35
+ }
36
+ /**
37
+ * Parses a generic DESEN style object into React.CSSProperties
38
+ * Replaces token attributes by looking up the dot notation string recursively in the theme context.
39
+ * Falls back to literals when available or strictly ignores (Fail-Closed) if neither works.
40
+ */
41
+ function parseDesenStyles(style, hasAction, theme) {
42
+ if (!style && !hasAction)
43
+ return {};
44
+ const css = {};
45
+ if (hasAction || (style && (style.hover || style.active))) {
46
+ css.cursor = "pointer";
47
+ }
48
+ if (!style)
49
+ return css;
50
+ const resolvedProps = new Set();
51
+ // 1. Resolve Authored Tokens using `theme/tokens` context
52
+ if (theme) {
53
+ // Standard mapping for DESEN property bases to CSS properties
54
+ const STYLE_MAP = {
55
+ bg_color: 'backgroundColor',
56
+ text_color: 'color',
57
+ border_color: 'borderColor',
58
+ border_width: 'borderWidth',
59
+ border_top_width: 'borderTopWidth',
60
+ border_right_width: 'borderRightWidth',
61
+ border_bottom_width: 'borderBottomWidth',
62
+ border_left_width: 'borderLeftWidth',
63
+ border_radius: 'borderRadius',
64
+ border_top_left_radius: 'borderTopLeftRadius',
65
+ border_top_right_radius: 'borderTopRightRadius',
66
+ border_bottom_left_radius: 'borderBottomLeftRadius',
67
+ border_bottom_right_radius: 'borderBottomRightRadius',
68
+ };
69
+ for (const [key, value] of Object.entries(style)) {
70
+ if (key.endsWith('_token') && typeof value === 'string') {
71
+ const baseKey = key.replace('_token', '');
72
+ const resolvedValue = resolveTokenValue(value, theme);
73
+ if (resolvedValue !== undefined) {
74
+ const cssKey = STYLE_MAP[baseKey] || baseKey.replace(/_([a-z])/g, (g) => g[1].toUpperCase());
75
+ // Specific formatting for dimension-based token values (convert raw numbers to px)
76
+ if (baseKey.includes('width') ||
77
+ baseKey.includes('radius') ||
78
+ baseKey === 'font_size' ||
79
+ baseKey === 'min_height' ||
80
+ baseKey === 'height' ||
81
+ baseKey === 'line_height' ||
82
+ baseKey === 'padding' ||
83
+ baseKey === 'margin') {
84
+ css[cssKey] = (typeof resolvedValue === 'number' && resolvedValue !== 0) ? `${resolvedValue}px` : resolvedValue;
85
+ }
86
+ else {
87
+ css[cssKey] = resolvedValue;
88
+ }
89
+ if (baseKey === 'border_color' && css.borderWidth) {
90
+ css.borderStyle = "solid";
91
+ }
92
+ else if (baseKey === 'border_color') {
93
+ css.borderStyle = "solid";
94
+ }
95
+ resolvedProps.add(baseKey);
96
+ }
97
+ }
98
+ }
99
+ }
100
+ // 2. Apply Literal Fallbacks only if their corresponding _token wasn't resolved
101
+ if (!resolvedProps.has('bg_color') && style.bg_color)
102
+ css.backgroundColor = style.bg_color;
103
+ if (!resolvedProps.has('text_color') && style.text_color)
104
+ css.color = style.text_color;
105
+ if (!resolvedProps.has('border_color') && style.border_color) {
106
+ css.borderColor = style.border_color;
107
+ css.borderStyle = "solid";
108
+ }
109
+ if (!resolvedProps.has('border_width') && style.border_width !== undefined) {
110
+ css.borderWidth = typeof style.border_width === 'number' ? `${style.border_width}px` : style.border_width;
111
+ }
112
+ if (!resolvedProps.has('border_top_width') && style.border_top_width !== undefined) {
113
+ css.borderTopWidth = typeof style.border_top_width === 'number' ? `${style.border_top_width}px` : style.border_top_width;
114
+ }
115
+ if (!resolvedProps.has('border_right_width') && style.border_right_width !== undefined) {
116
+ css.borderRightWidth = typeof style.border_right_width === 'number' ? `${style.border_right_width}px` : style.border_right_width;
117
+ }
118
+ if (!resolvedProps.has('border_bottom_width') && style.border_bottom_width !== undefined) {
119
+ css.borderBottomWidth = typeof style.border_bottom_width === 'number' ? `${style.border_bottom_width}px` : style.border_bottom_width;
120
+ }
121
+ if (!resolvedProps.has('border_left_width') && style.border_left_width !== undefined) {
122
+ css.borderLeftWidth = typeof style.border_left_width === 'number' ? `${style.border_left_width}px` : style.border_left_width;
123
+ }
124
+ if (!resolvedProps.has('border_radius') && style.border_radius !== undefined) {
125
+ css.borderRadius = typeof style.border_radius === 'number' ? `${style.border_radius}px` : style.border_radius;
126
+ }
127
+ if (!resolvedProps.has('border_top_left_radius') && style.border_top_left_radius !== undefined) {
128
+ css.borderTopLeftRadius = typeof style.border_top_left_radius === 'number' ? `${style.border_top_left_radius}px` : style.border_top_left_radius;
129
+ }
130
+ if (!resolvedProps.has('border_top_right_radius') && style.border_top_right_radius !== undefined) {
131
+ css.borderTopRightRadius = typeof style.border_top_right_radius === 'number' ? `${style.border_top_right_radius}px` : style.border_top_right_radius;
132
+ }
133
+ if (!resolvedProps.has('border_bottom_left_radius') && style.border_bottom_left_radius !== undefined) {
134
+ css.borderBottomLeftRadius = typeof style.border_bottom_left_radius === 'number' ? `${style.border_bottom_left_radius}px` : style.border_bottom_left_radius;
135
+ }
136
+ if (!resolvedProps.has('border_bottom_right_radius') && style.border_bottom_right_radius !== undefined) {
137
+ css.borderBottomRightRadius = typeof style.border_bottom_right_radius === 'number' ? `${style.border_bottom_right_radius}px` : style.border_bottom_right_radius;
138
+ }
139
+ if (!resolvedProps.has('font_size') && style.font_size !== undefined) {
140
+ css.fontSize = typeof style.font_size === 'number' ? `${style.font_size}px` : style.font_size;
141
+ }
142
+ if (!resolvedProps.has('font_weight') && style.font_weight !== undefined)
143
+ css.fontWeight = style.font_weight;
144
+ if (!resolvedProps.has('font_family') && style.font_family)
145
+ css.fontFamily = style.font_family;
146
+ if (!resolvedProps.has('letter_spacing') && style.letter_spacing)
147
+ css.letterSpacing = style.letter_spacing;
148
+ if (!resolvedProps.has('line_height') && style.line_height)
149
+ css.lineHeight = typeof style.line_height === 'number' ? `${style.line_height}px` : style.line_height;
150
+ if (!resolvedProps.has('text_decoration') && style.text_decoration)
151
+ css.textDecoration = style.text_decoration;
152
+ if (!resolvedProps.has('text_transform') && style.text_transform)
153
+ css.textTransform = style.text_transform;
154
+ if (!resolvedProps.has('justify_content') && style.justify_content)
155
+ css.justifyContent = style.justify_content;
156
+ if (!resolvedProps.has('align_self') && style.align_self)
157
+ css.alignSelf = style.align_self;
158
+ if (!resolvedProps.has('flex_grow') && style.flex_grow !== undefined)
159
+ css.flexGrow = style.flex_grow;
160
+ if (!resolvedProps.has('text_align') && style.text_align)
161
+ css.textAlign = style.text_align;
162
+ if (!resolvedProps.has('width') && style.width !== undefined) {
163
+ css.width = (typeof style.width === 'number' || (typeof style.width === 'string' && !style.width.includes('%')))
164
+ ? `${parseInt(style.width)}px`
165
+ : style.width;
166
+ }
167
+ if (!resolvedProps.has('height') && style.height !== undefined) {
168
+ css.height = (typeof style.height === 'number' || (typeof style.height === 'string' && !style.height.includes('%')))
169
+ ? `${parseInt(style.height)}px`
170
+ : style.height;
171
+ }
172
+ if (!resolvedProps.has('padding') && style.padding) {
173
+ const pt = typeof style.padding.top === 'number' ? `${style.padding.top}px` : (style.padding.top || "0px");
174
+ const pr = typeof style.padding.right === 'number' ? `${style.padding.right}px` : (style.padding.right || "0px");
175
+ const pb = typeof style.padding.bottom === 'number' ? `${style.padding.bottom}px` : (style.padding.bottom || "0px");
176
+ const pl = typeof style.padding.left === 'number' ? `${style.padding.left}px` : (style.padding.left || "0px");
177
+ css.padding = `${pt} ${pr} ${pb} ${pl}`;
178
+ }
179
+ if (!resolvedProps.has('min_height') && style.min_height !== undefined) {
180
+ css.minHeight = typeof style.min_height === 'number' ? `${style.min_height}px` : style.min_height;
181
+ }
182
+ if (!resolvedProps.has('box_shadow') && style.box_shadow)
183
+ css.boxShadow = style.box_shadow;
184
+ if (!resolvedProps.has('transform_y') && style.transform_y !== undefined) {
185
+ css.transform = `translateY(${style.transform_y}px)`;
186
+ css.marginBottom = `${style.transform_y}px`;
187
+ }
188
+ if (!resolvedProps.has('opacity') && style.opacity !== undefined)
189
+ css.opacity = style.opacity;
190
+ // Pass through standard CSS if already passed in camelCase implicitly
191
+ const HANDLED_KEYS = new Set([
192
+ 'bg_color', 'text_color', 'border_color',
193
+ 'border_width', 'border_top_width', 'border_right_width', 'border_bottom_width', 'border_left_width',
194
+ 'border_radius', 'border_top_left_radius', 'border_top_right_radius', 'border_bottom_left_radius', 'border_bottom_right_radius',
195
+ 'font_size', 'font_weight', 'font_family', 'letter_spacing', 'line_height', 'text_decoration', 'text_transform', 'opacity',
196
+ 'padding', 'min_height',
197
+ 'justify_content', 'align_self', 'flex_grow', 'text_align',
198
+ 'width', 'height', 'transform_y', 'box_shadow',
199
+ 'hover', 'active', '_inherited' // Don't pass these meta-objects/flags to CSS
200
+ ]);
201
+ for (const key of Object.keys(style)) {
202
+ if (!HANDLED_KEYS.has(key) && !key.endsWith('_token')) {
203
+ // Keep existing keys if they are valid css rules
204
+ css[key] = style[key];
205
+ }
206
+ }
207
+ return css;
208
+ }
package/package.json CHANGED
@@ -1,15 +1,36 @@
1
1
  {
2
2
  "name": "desen",
3
- "version": "0.0.1",
4
- "description": "desen CLI (name reserved)",
5
- "license": "MIT",
3
+ "version": "1.0.0-draft.2",
4
+ "main": "dist/index.js",
5
+ "types": "dist/index.d.ts",
6
+ "exports": {
7
+ ".": "./dist/index.js",
8
+ "./core": "./dist/core.js",
9
+ "./cli": "./dist/cli.js"
10
+ },
6
11
  "bin": {
7
- "desen": "./cli.js"
12
+ "desen": "./dist/bin.js"
13
+ },
14
+ "dependencies": {
15
+ "react": "^18.2.0",
16
+ "react-dom": "^18.2.0",
17
+ "desen-core": "1.0.0-draft.2",
18
+ "desen-cli": "1.0.0-draft.2"
19
+ },
20
+ "devDependencies": {
21
+ "typescript": "^5.0.0",
22
+ "@types/node": "^20.0.0",
23
+ "@types/react": "^18.2.0",
24
+ "@types/react-dom": "^18.2.0"
25
+ },
26
+ "peerDependencies": {
27
+ "react": "^18.2.0",
28
+ "react-dom": "^18.2.0"
8
29
  },
9
- "files": [
10
- "cli.js"
11
- ],
12
- "engines": {
13
- "node": ">=18"
30
+ "scripts": {
31
+ "postinstall": "node ./dist/postinstall.js 2>/dev/null || true",
32
+ "build": "tsc",
33
+ "dev": "tsc -w",
34
+ "clean": "rm -rf dist"
14
35
  }
15
- }
36
+ }
@@ -0,0 +1,69 @@
1
+ import React, { createContext, useContext, ReactNode } from "react";
2
+ import { ActionSpec } from "desen-core";
3
+
4
+ export interface TelemetryEnvelope {
5
+ session_id: string;
6
+ element_id: string;
7
+ timestamp: string;
8
+ revision_id: string;
9
+ event_type: "VIEW_ELEMENT" | "INTERACT" | "BINDING_ERROR" | string;
10
+ payload: Record<string, any>;
11
+ }
12
+
13
+ export interface DesenRegistry {
14
+ [key: string]: React.ComponentType<any>;
15
+ }
16
+
17
+ export interface DesenContextType {
18
+ registry: DesenRegistry;
19
+ onTelemetry: (event: TelemetryEnvelope) => void;
20
+ onAction: (action: ActionSpec) => void;
21
+ tokens?: Record<string, any>;
22
+ session_id: string;
23
+ revision_id: string;
24
+ }
25
+
26
+ const DesenContext = createContext<DesenContextType | undefined>(undefined);
27
+
28
+ export interface DesenProviderProps {
29
+ children: ReactNode;
30
+ registry: DesenRegistry;
31
+ onTelemetry: (event: TelemetryEnvelope) => void;
32
+ onAction: (action: ActionSpec) => void;
33
+ tokens?: Record<string, any>;
34
+ session_id: string;
35
+ revision_id: string;
36
+ }
37
+
38
+ export const DesenProvider: React.FC<DesenProviderProps> = ({
39
+ children,
40
+ registry,
41
+ onTelemetry,
42
+ onAction,
43
+ tokens,
44
+ session_id,
45
+ revision_id
46
+ }) => {
47
+ return (
48
+ <DesenContext.Provider
49
+ value={{
50
+ registry,
51
+ onTelemetry,
52
+ onAction,
53
+ tokens,
54
+ session_id,
55
+ revision_id
56
+ }}
57
+ >
58
+ {children}
59
+ </DesenContext.Provider>
60
+ );
61
+ };
62
+
63
+ export const useDesen = (): DesenContextType => {
64
+ const context = useContext(DesenContext);
65
+ if (!context) {
66
+ throw new Error("useDesen must be used within a DesenProvider");
67
+ }
68
+ return context;
69
+ };
@@ -0,0 +1,77 @@
1
+ import React, { useMemo } from "react";
2
+ import { useDesen } from "./DesenProvider";
3
+ import { ErrorBoundary } from "./ErrorBoundary";
4
+ import type { NodeSpec, CompositionSpec } from "desen-core";
5
+
6
+ import { parseDesenStyles } from "./utils/parseDesenStyles";
7
+
8
+ export interface DesenRendererProps {
9
+ node: NodeSpec;
10
+ }
11
+
12
+ const BaseRenderer: React.FC<DesenRendererProps> = ({ node }) => {
13
+ const { registry, tokens } = useDesen();
14
+
15
+ const RegistryComponent = useMemo(() => {
16
+ // node.type üzerinden eşleşmeyi arıyoruz
17
+ return registry[node.type];
18
+ }, [registry, node.type]);
19
+
20
+ if (!RegistryComponent) {
21
+ // Fail-Closed: Bileşen yoksa null dön. UI çökertecek belirsiz durumdan kaçın.
22
+ console.warn(`[DESEN] RegistryComponent not found for type: ${node.type}`);
23
+ return null;
24
+ }
25
+
26
+ // Process DESEN styles securely with DTCG resolver
27
+ const parsedStyle = useMemo(() => {
28
+ const rawStyle = (node as any).style || {};
29
+ const hasAction = !!(node as any).action;
30
+ return parseDesenStyles(rawStyle, hasAction, tokens);
31
+ }, [node, tokens]);
32
+
33
+ // Common Props — spread all node fields EXCEPT `children` and `root`
34
+ // (which would override the recursively-rendered React children).
35
+ const { children: _rawChildren, root: _rawRoot, ...nodeWithoutChildren } = node as any;
36
+ const props = {
37
+ ...nodeWithoutChildren,
38
+ ...((node as any).props || {}),
39
+ id: node.id,
40
+ type: node.type,
41
+ layout: (node as CompositionSpec).layout,
42
+ constraints: (node as any).constraints,
43
+ parsedStyle,
44
+ };
45
+
46
+ // Recursively render child nodes: supports both `children` array and `root` property
47
+ const children = useMemo(() => {
48
+ const pNode = node as any;
49
+
50
+ // Surface nodes use `root` instead of `children`
51
+ if (pNode.root && typeof pNode.root === 'object') {
52
+ // Inject _isSurfaceRoot flag so the registry component fills the viewport
53
+ const rootNode = { ...pNode.root, _isSurfaceRoot: true };
54
+ return <DesenRenderer node={rootNode as NodeSpec} />;
55
+ }
56
+
57
+ // Composition nodes use `children` array
58
+ if (pNode.children && Array.isArray(pNode.children)) {
59
+ return pNode.children.map((child: any, index: number) => {
60
+ const uniqueKey = `${child.id || 'node'}-${index}`;
61
+ return <DesenRenderer key={uniqueKey} node={child as NodeSpec} />;
62
+ });
63
+ }
64
+ return null;
65
+ }, [node]);
66
+
67
+ return <RegistryComponent {...props}>{children}</RegistryComponent>;
68
+ };
69
+
70
+ // Her renderer adımı ErrorBoundary ile sarılarak fail-closed emniyeti uygulanır
71
+ export const DesenRenderer: React.FC<DesenRendererProps> = ({ node }) => {
72
+ return (
73
+ <ErrorBoundary elementId={node.id}>
74
+ <BaseRenderer node={node} />
75
+ </ErrorBoundary>
76
+ );
77
+ };
@@ -0,0 +1,65 @@
1
+ import React, { Component, ReactNode } from "react";
2
+ import { useDesen } from "./DesenProvider";
3
+
4
+ interface ErrorBoundaryProps {
5
+ children: ReactNode;
6
+ elementId?: string;
7
+ fallback?: ReactNode;
8
+ onTelemetry: ReturnType<typeof useDesen>["onTelemetry"];
9
+ session_id: string;
10
+ revision_id: string;
11
+ }
12
+
13
+ interface ErrorBoundaryState {
14
+ hasError: boolean;
15
+ }
16
+
17
+ export class ErrorBoundaryClass extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
18
+ constructor(props: ErrorBoundaryProps) {
19
+ super(props);
20
+ this.state = { hasError: false };
21
+ }
22
+
23
+ static getDerivedStateFromError(_: Error): ErrorBoundaryState {
24
+ return { hasError: true };
25
+ }
26
+
27
+ componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
28
+ const { onTelemetry, elementId, session_id, revision_id } = this.props;
29
+
30
+ onTelemetry({
31
+ session_id,
32
+ element_id: elementId || "unknown",
33
+ timestamp: new Date().toISOString(),
34
+ revision_id,
35
+ event_type: "BINDING_ERROR",
36
+ payload: {
37
+ error_class: "RENDER_ERROR",
38
+ message: error.message,
39
+ componentStack: errorInfo.componentStack,
40
+ },
41
+ });
42
+ }
43
+
44
+ render() {
45
+ if (this.state.hasError) {
46
+ // Fail-closed mantığı gereği ya null ya da verilmiş fallback
47
+ return this.props.fallback || null;
48
+ }
49
+
50
+ return this.props.children;
51
+ }
52
+ }
53
+
54
+ // Hook tabanlı context bilgilerini Class bazlı bileşene enjekte etmek için wrapper
55
+ export const ErrorBoundary: React.FC<Omit<ErrorBoundaryProps, "onTelemetry" | "session_id" | "revision_id">> = (props) => {
56
+ const desenContext = useDesen();
57
+ return (
58
+ <ErrorBoundaryClass
59
+ {...props}
60
+ onTelemetry={desenContext.onTelemetry}
61
+ session_id={desenContext.session_id}
62
+ revision_id={desenContext.revision_id}
63
+ />
64
+ );
65
+ };
package/src/bin.ts ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+
3
+ const args = process.argv.slice(2);
4
+ const command = args[0];
5
+
6
+ if (command === "daemon" || command === "audit") {
7
+ // This will execute the exported CLI process from desen-cli since it runs side-effects directly
8
+ require("desen-cli");
9
+ } else {
10
+ console.log(`
11
+ \x1b[36m💠 DESEN Protocol CLI\x1b[0m
12
+
13
+ Usage:
14
+ \x1b[1mnpx desen daemon\x1b[0m Start the local synchronization daemon
15
+ \x1b[1mnpx desen audit\x1b[0m Audit DESEN JSON files
16
+ `);
17
+ }
package/src/cli.ts ADDED
@@ -0,0 +1 @@
1
+ export * from 'desen-cli';
package/src/core.ts ADDED
@@ -0,0 +1 @@
1
+ export * from 'desen-core';
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from "./DesenProvider";
2
+ export * from "./ErrorBoundary";
3
+ export * from "./DesenRenderer";
4
+ export * from "./utils/parseDesenStyles";
@@ -0,0 +1,2 @@
1
+ // Extracted out to avoid ts compilation removing the shebang if it is not a direct entry point
2
+ console.log("\n\x1b[36m💠 DESEN yüklendi! Çalıştırmak için: npx desen daemon\x1b[0m\n");
@@ -0,0 +1,11 @@
1
+ import { createContext } from 'react';
2
+
3
+ export interface ParentState {
4
+ isHovered: boolean;
5
+ isActive: boolean;
6
+ }
7
+
8
+ export const ParentStateContext = createContext<ParentState>({
9
+ isHovered: false,
10
+ isActive: false
11
+ });
@@ -0,0 +1,217 @@
1
+ import React from "react";
2
+ import type { TokenTheme } from "desen-core";
3
+
4
+ /**
5
+ * Resolves a dot-notation token path within a Theme/Tokens object following DTCG schema
6
+ */
7
+ export function resolveTokenValue(path: string, theme?: TokenTheme | Record<string, any>): string | number | undefined {
8
+ if (!path || !theme) return undefined;
9
+
10
+ // Clean string formatting like "{color.bg.primary}" if exists
11
+ const cleanPath = path.replace(/^\{|\}$/g, '');
12
+ const parts = cleanPath.split('.');
13
+
14
+ let current: any = theme;
15
+
16
+ for (const part of parts) {
17
+ if (current && typeof current === 'object' && part in current) {
18
+ current = current[part];
19
+ } else {
20
+ return undefined;
21
+ }
22
+ }
23
+
24
+ // Resolve DTCG $value leaf
25
+ if (current && typeof current === 'object') {
26
+ if ('$value' in current && current.$value !== undefined) return current.$value;
27
+ if ('value' in current && current.value !== undefined) return current.value;
28
+ }
29
+
30
+ // Resolve direct primitive mapping
31
+ if (typeof current === 'string' || typeof current === 'number') {
32
+ return current;
33
+ }
34
+
35
+ return undefined;
36
+ }
37
+
38
+ /**
39
+ * Parses a generic DESEN style object into React.CSSProperties
40
+ * Replaces token attributes by looking up the dot notation string recursively in the theme context.
41
+ * Falls back to literals when available or strictly ignores (Fail-Closed) if neither works.
42
+ */
43
+ export function parseDesenStyles(style?: any, hasAction?: boolean, theme?: Record<string, any>): React.CSSProperties {
44
+ if (!style && !hasAction) return {};
45
+
46
+ const css: React.CSSProperties = {};
47
+
48
+ if (hasAction || (style && (style.hover || style.active))) {
49
+ css.cursor = "pointer";
50
+ }
51
+
52
+ if (!style) return css;
53
+
54
+ const resolvedProps = new Set<string>();
55
+
56
+ // 1. Resolve Authored Tokens using `theme/tokens` context
57
+ if (theme) {
58
+ // Standard mapping for DESEN property bases to CSS properties
59
+ const STYLE_MAP: Record<string, keyof React.CSSProperties> = {
60
+ bg_color: 'backgroundColor',
61
+ text_color: 'color',
62
+ border_color: 'borderColor',
63
+ border_width: 'borderWidth',
64
+ border_top_width: 'borderTopWidth',
65
+ border_right_width: 'borderRightWidth',
66
+ border_bottom_width: 'borderBottomWidth',
67
+ border_left_width: 'borderLeftWidth',
68
+ border_radius: 'borderRadius',
69
+ border_top_left_radius: 'borderTopLeftRadius',
70
+ border_top_right_radius: 'borderTopRightRadius',
71
+ border_bottom_left_radius: 'borderBottomLeftRadius',
72
+ border_bottom_right_radius: 'borderBottomRightRadius',
73
+ };
74
+
75
+ for (const [key, value] of Object.entries(style)) {
76
+ if (key.endsWith('_token') && typeof value === 'string') {
77
+ const baseKey = key.replace('_token', '');
78
+ const resolvedValue = resolveTokenValue(value, theme);
79
+
80
+ if (resolvedValue !== undefined) {
81
+ const cssKey = STYLE_MAP[baseKey] || baseKey.replace(/_([a-z])/g, (g) => g[1].toUpperCase()) as keyof React.CSSProperties;
82
+
83
+ // Specific formatting for dimension-based token values (convert raw numbers to px)
84
+ if (
85
+ baseKey.includes('width') ||
86
+ baseKey.includes('radius') ||
87
+ baseKey === 'font_size' ||
88
+ baseKey === 'min_height' ||
89
+ baseKey === 'height' ||
90
+ baseKey === 'line_height' ||
91
+ baseKey === 'padding' ||
92
+ baseKey === 'margin'
93
+ ) {
94
+ (css as any)[cssKey] = (typeof resolvedValue === 'number' && resolvedValue !== 0) ? `${resolvedValue}px` : resolvedValue;
95
+ } else {
96
+ (css as any)[cssKey] = resolvedValue;
97
+ }
98
+
99
+ if (baseKey === 'border_color' && css.borderWidth) {
100
+ css.borderStyle = "solid";
101
+ } else if (baseKey === 'border_color') {
102
+ css.borderStyle = "solid";
103
+ }
104
+
105
+ resolvedProps.add(baseKey);
106
+ }
107
+ }
108
+ }
109
+ }
110
+
111
+ // 2. Apply Literal Fallbacks only if their corresponding _token wasn't resolved
112
+ if (!resolvedProps.has('bg_color') && style.bg_color) css.backgroundColor = style.bg_color;
113
+ if (!resolvedProps.has('text_color') && style.text_color) css.color = style.text_color;
114
+
115
+ if (!resolvedProps.has('border_color') && style.border_color) {
116
+ css.borderColor = style.border_color;
117
+ css.borderStyle = "solid";
118
+ }
119
+
120
+ if (!resolvedProps.has('border_width') && style.border_width !== undefined) {
121
+ css.borderWidth = typeof style.border_width === 'number' ? `${style.border_width}px` : style.border_width;
122
+ }
123
+ if (!resolvedProps.has('border_top_width') && style.border_top_width !== undefined) {
124
+ css.borderTopWidth = typeof style.border_top_width === 'number' ? `${style.border_top_width}px` : style.border_top_width;
125
+ }
126
+ if (!resolvedProps.has('border_right_width') && style.border_right_width !== undefined) {
127
+ css.borderRightWidth = typeof style.border_right_width === 'number' ? `${style.border_right_width}px` : style.border_right_width;
128
+ }
129
+ if (!resolvedProps.has('border_bottom_width') && style.border_bottom_width !== undefined) {
130
+ css.borderBottomWidth = typeof style.border_bottom_width === 'number' ? `${style.border_bottom_width}px` : style.border_bottom_width;
131
+ }
132
+ if (!resolvedProps.has('border_left_width') && style.border_left_width !== undefined) {
133
+ css.borderLeftWidth = typeof style.border_left_width === 'number' ? `${style.border_left_width}px` : style.border_left_width;
134
+ }
135
+
136
+ if (!resolvedProps.has('border_radius') && style.border_radius !== undefined) {
137
+ css.borderRadius = typeof style.border_radius === 'number' ? `${style.border_radius}px` : style.border_radius;
138
+ }
139
+ if (!resolvedProps.has('border_top_left_radius') && style.border_top_left_radius !== undefined) {
140
+ css.borderTopLeftRadius = typeof style.border_top_left_radius === 'number' ? `${style.border_top_left_radius}px` : style.border_top_left_radius;
141
+ }
142
+ if (!resolvedProps.has('border_top_right_radius') && style.border_top_right_radius !== undefined) {
143
+ css.borderTopRightRadius = typeof style.border_top_right_radius === 'number' ? `${style.border_top_right_radius}px` : style.border_top_right_radius;
144
+ }
145
+ if (!resolvedProps.has('border_bottom_left_radius') && style.border_bottom_left_radius !== undefined) {
146
+ css.borderBottomLeftRadius = typeof style.border_bottom_left_radius === 'number' ? `${style.border_bottom_left_radius}px` : style.border_bottom_left_radius;
147
+ }
148
+ if (!resolvedProps.has('border_bottom_right_radius') && style.border_bottom_right_radius !== undefined) {
149
+ css.borderBottomRightRadius = typeof style.border_bottom_right_radius === 'number' ? `${style.border_bottom_right_radius}px` : style.border_bottom_right_radius;
150
+ }
151
+
152
+ if (!resolvedProps.has('font_size') && style.font_size !== undefined) {
153
+ css.fontSize = typeof style.font_size === 'number' ? `${style.font_size}px` : style.font_size;
154
+ }
155
+ if (!resolvedProps.has('font_weight') && style.font_weight !== undefined) css.fontWeight = style.font_weight;
156
+ if (!resolvedProps.has('font_family') && style.font_family) css.fontFamily = style.font_family;
157
+ if (!resolvedProps.has('letter_spacing') && style.letter_spacing) css.letterSpacing = style.letter_spacing;
158
+ if (!resolvedProps.has('line_height') && style.line_height) css.lineHeight = typeof style.line_height === 'number' ? `${style.line_height}px` : style.line_height;
159
+ if (!resolvedProps.has('text_decoration') && style.text_decoration) css.textDecoration = style.text_decoration;
160
+ if (!resolvedProps.has('text_transform') && style.text_transform) css.textTransform = style.text_transform;
161
+
162
+ if (!resolvedProps.has('justify_content') && style.justify_content) css.justifyContent = style.justify_content;
163
+ if (!resolvedProps.has('align_self') && style.align_self) css.alignSelf = style.align_self;
164
+ if (!resolvedProps.has('flex_grow') && style.flex_grow !== undefined) css.flexGrow = style.flex_grow;
165
+ if (!resolvedProps.has('text_align') && style.text_align) css.textAlign = style.text_align;
166
+
167
+ if (!resolvedProps.has('width') && style.width !== undefined) {
168
+ css.width = (typeof style.width === 'number' || (typeof style.width === 'string' && !style.width.includes('%')))
169
+ ? `${parseInt(style.width)}px`
170
+ : style.width;
171
+ }
172
+ if (!resolvedProps.has('height') && style.height !== undefined) {
173
+ css.height = (typeof style.height === 'number' || (typeof style.height === 'string' && !style.height.includes('%')))
174
+ ? `${parseInt(style.height)}px`
175
+ : style.height;
176
+ }
177
+
178
+ if (!resolvedProps.has('padding') && style.padding) {
179
+ const pt = typeof style.padding.top === 'number' ? `${style.padding.top}px` : (style.padding.top || "0px");
180
+ const pr = typeof style.padding.right === 'number' ? `${style.padding.right}px` : (style.padding.right || "0px");
181
+ const pb = typeof style.padding.bottom === 'number' ? `${style.padding.bottom}px` : (style.padding.bottom || "0px");
182
+ const pl = typeof style.padding.left === 'number' ? `${style.padding.left}px` : (style.padding.left || "0px");
183
+ css.padding = `${pt} ${pr} ${pb} ${pl}`;
184
+ }
185
+
186
+ if (!resolvedProps.has('min_height') && style.min_height !== undefined) {
187
+ css.minHeight = typeof style.min_height === 'number' ? `${style.min_height}px` : style.min_height;
188
+ }
189
+ if (!resolvedProps.has('box_shadow') && style.box_shadow) css.boxShadow = style.box_shadow;
190
+
191
+ if (!resolvedProps.has('transform_y') && style.transform_y !== undefined) {
192
+ css.transform = `translateY(${style.transform_y}px)`;
193
+ css.marginBottom = `${style.transform_y}px`;
194
+ }
195
+ if (!resolvedProps.has('opacity') && style.opacity !== undefined) css.opacity = style.opacity;
196
+
197
+ // Pass through standard CSS if already passed in camelCase implicitly
198
+ const HANDLED_KEYS = new Set([
199
+ 'bg_color', 'text_color', 'border_color',
200
+ 'border_width', 'border_top_width', 'border_right_width', 'border_bottom_width', 'border_left_width',
201
+ 'border_radius', 'border_top_left_radius', 'border_top_right_radius', 'border_bottom_left_radius', 'border_bottom_right_radius',
202
+ 'font_size', 'font_weight', 'font_family', 'letter_spacing', 'line_height', 'text_decoration', 'text_transform', 'opacity',
203
+ 'padding', 'min_height',
204
+ 'justify_content', 'align_self', 'flex_grow', 'text_align',
205
+ 'width', 'height', 'transform_y', 'box_shadow',
206
+ 'hover', 'active', '_inherited' // Don't pass these meta-objects/flags to CSS
207
+ ]);
208
+
209
+ for (const key of Object.keys(style)) {
210
+ if (!HANDLED_KEYS.has(key) && !key.endsWith('_token')) {
211
+ // Keep existing keys if they are valid css rules
212
+ (css as any)[key] = style[key];
213
+ }
214
+ }
215
+
216
+ return css;
217
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "CommonJS",
5
+ "declaration": true,
6
+ "outDir": "./dist",
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "jsx": "react",
11
+ "forceConsistentCasingInFileNames": true
12
+ },
13
+ "include": ["src/**/*"]
14
+ }
package/cli.js DELETED
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- console.log("desen reserved — coming soon")