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.
- package/.turbo/turbo-build.log +5 -0
- package/README.md +61 -0
- package/dist/DesenProvider.d.ts +32 -0
- package/dist/DesenProvider.js +57 -0
- package/dist/DesenRenderer.d.ts +6 -0
- package/dist/DesenRenderer.js +95 -0
- package/dist/ErrorBoundary.d.ts +21 -0
- package/dist/ErrorBoundary.js +76 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.js +17 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +17 -0
- package/dist/core.d.ts +1 -0
- package/dist/core.js +17 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +20 -0
- package/dist/postinstall.d.ts +0 -0
- package/dist/postinstall.js +3 -0
- package/dist/utils/ParentStateContext.d.ts +5 -0
- package/dist/utils/ParentStateContext.js +8 -0
- package/dist/utils/parseDesenStyles.d.ts +12 -0
- package/dist/utils/parseDesenStyles.js +208 -0
- package/package.json +31 -10
- package/src/DesenProvider.tsx +69 -0
- package/src/DesenRenderer.tsx +77 -0
- package/src/ErrorBoundary.tsx +65 -0
- package/src/bin.ts +17 -0
- package/src/cli.ts +1 -0
- package/src/core.ts +1 -0
- package/src/index.ts +4 -0
- package/src/postinstall.ts +2 -0
- package/src/utils/ParentStateContext.ts +11 -0
- package/src/utils/parseDesenStyles.ts +217 -0
- package/tsconfig.json +14 -0
- package/cli.js +0 -3
package/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# 💠 DESEN (Design Environment Protocol)
|
|
2
|
+
|
|
3
|
+
> A governed, executable representation of UI intent.
|
|
4
|
+
|
|
5
|
+
[](#)
|
|
6
|
+
[](#)
|
|
7
|
+
[](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,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
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);
|
package/dist/index.d.ts
ADDED
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,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.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
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": "./
|
|
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
|
-
"
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"
|
|
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,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