@pyreon/connector-document 0.3.0
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/lib/index.d.ts +70 -0
- package/lib/index.js +249 -0
- package/package.json +53 -0
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { DocChild, DocNode, NodeType, ResolvedStyles } from "@pyreon/document";
|
|
2
|
+
|
|
3
|
+
//#region src/cssValueParser.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Parse a CSS dimension value to a number.
|
|
6
|
+
*
|
|
7
|
+
* - `14` → `14`
|
|
8
|
+
* - `'14px'` → `14`
|
|
9
|
+
* - `'1.5rem'` → `24` (with rootSize=16)
|
|
10
|
+
* - `'12pt'` → `16` (pt × 1.333)
|
|
11
|
+
* - `'auto'` → `undefined`
|
|
12
|
+
*/
|
|
13
|
+
declare function parseCssDimension(value: string | number | null | undefined, rootSize?: number): number | undefined;
|
|
14
|
+
type BoxModelResult = number | [number, number] | [number, number, number, number] | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Parse a CSS padding/margin shorthand to document tuple format.
|
|
17
|
+
*
|
|
18
|
+
* - `8` → `8`
|
|
19
|
+
* - `'8px'` → `8`
|
|
20
|
+
* - `'8px 16px'` → `[8, 16]`
|
|
21
|
+
* - `'8px 16px 8px 16px'` → `[8, 16, 8, 16]`
|
|
22
|
+
* - `'8px 16px 12px'` → `[8, 16, 12, 16]` (CSS 3-value shorthand)
|
|
23
|
+
*/
|
|
24
|
+
declare function parseBoxModel(value: string | number | undefined, rootSize?: number): BoxModelResult;
|
|
25
|
+
/**
|
|
26
|
+
* Parse a CSS font-weight value.
|
|
27
|
+
*/
|
|
28
|
+
declare function parseFontWeight(value: string | number | undefined): "normal" | "bold" | number | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Parse a CSS line-height value to a unitless number.
|
|
31
|
+
*/
|
|
32
|
+
declare function parseLineHeight(value: string | number | undefined, rootSize?: number): number | undefined;
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region src/extractDocumentTree.d.ts
|
|
35
|
+
/** Marker interface: components with _documentType are extractable. */
|
|
36
|
+
interface DocumentMarker {
|
|
37
|
+
_documentType: NodeType;
|
|
38
|
+
}
|
|
39
|
+
interface ExtractOptions {
|
|
40
|
+
/** Root font size for rem→px conversion. Default: 16. */
|
|
41
|
+
rootSize?: number;
|
|
42
|
+
/** Include resolved styles from $rocketstyle. Default: true. */
|
|
43
|
+
includeStyles?: boolean;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Walk a Pyreon VNode tree and extract a `DocNode` tree for `@pyreon/document`.
|
|
47
|
+
*
|
|
48
|
+
* For each VNode whose component has a `_documentType` marker:
|
|
49
|
+
* 1. Read `_documentType` → `DocNode.type`
|
|
50
|
+
* 2. Read `_documentProps` → `DocNode.props`
|
|
51
|
+
* 3. Read `$rocketstyle` → `resolveStyles()` → `DocNode.styles`
|
|
52
|
+
* 4. Recurse into children
|
|
53
|
+
*
|
|
54
|
+
* VNodes without `_documentType` are transparent — their children
|
|
55
|
+
* are flattened into the parent's children list.
|
|
56
|
+
*/
|
|
57
|
+
declare function extractDocumentTree(vnode: unknown, options?: ExtractOptions): DocNode;
|
|
58
|
+
//#endregion
|
|
59
|
+
//#region src/resolveStyles.d.ts
|
|
60
|
+
/**
|
|
61
|
+
* Convert a rocketstyle `$rocketstyle` theme object into a `ResolvedStyles`
|
|
62
|
+
* object compatible with `@pyreon/document`.
|
|
63
|
+
*
|
|
64
|
+
* Only extracts properties that `ResolvedStyles` supports — everything else
|
|
65
|
+
* (transitions, cursor, display, etc.) is silently ignored.
|
|
66
|
+
*/
|
|
67
|
+
declare function resolveStyles(rocketstyle: Record<string, unknown>, rootSize?: number): ResolvedStyles;
|
|
68
|
+
//#endregion
|
|
69
|
+
export { type DocChild, type DocNode, type DocumentMarker, type ExtractOptions, type NodeType, type ResolvedStyles, extractDocumentTree, parseBoxModel, parseCssDimension, parseFontWeight, parseLineHeight, resolveStyles };
|
|
70
|
+
//# sourceMappingURL=index2.d.ts.map
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
//#region src/cssValueParser.ts
|
|
2
|
+
const PX_RE = /^(-?\d+(?:\.\d+)?)px$/;
|
|
3
|
+
const REM_RE = /^(-?\d+(?:\.\d+)?)rem$/;
|
|
4
|
+
const EM_RE = /^(-?\d+(?:\.\d+)?)em$/;
|
|
5
|
+
const PT_RE = /^(-?\d+(?:\.\d+)?)pt$/;
|
|
6
|
+
const NUMBER_RE = /^-?\d+(?:\.\d+)?$/;
|
|
7
|
+
const DEFAULT_ROOT_SIZE = 16;
|
|
8
|
+
/**
|
|
9
|
+
* Parse a CSS dimension value to a number.
|
|
10
|
+
*
|
|
11
|
+
* - `14` → `14`
|
|
12
|
+
* - `'14px'` → `14`
|
|
13
|
+
* - `'1.5rem'` → `24` (with rootSize=16)
|
|
14
|
+
* - `'12pt'` → `16` (pt × 1.333)
|
|
15
|
+
* - `'auto'` → `undefined`
|
|
16
|
+
*/
|
|
17
|
+
function parseCssDimension(value, rootSize = DEFAULT_ROOT_SIZE) {
|
|
18
|
+
if (value == null) return void 0;
|
|
19
|
+
if (typeof value === "number") return value;
|
|
20
|
+
if (typeof value !== "string") return void 0;
|
|
21
|
+
const trimmed = value.trim();
|
|
22
|
+
const pxMatch = PX_RE.exec(trimmed);
|
|
23
|
+
if (pxMatch?.[1]) return Number.parseFloat(pxMatch[1]);
|
|
24
|
+
const remMatch = REM_RE.exec(trimmed);
|
|
25
|
+
if (remMatch?.[1]) return Number.parseFloat(remMatch[1]) * rootSize;
|
|
26
|
+
const emMatch = EM_RE.exec(trimmed);
|
|
27
|
+
if (emMatch?.[1]) return Number.parseFloat(emMatch[1]) * rootSize;
|
|
28
|
+
const ptMatch = PT_RE.exec(trimmed);
|
|
29
|
+
if (ptMatch?.[1]) return Number.parseFloat(ptMatch[1]) * (4 / 3);
|
|
30
|
+
if (NUMBER_RE.test(trimmed)) return Number.parseFloat(trimmed);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Parse a CSS padding/margin shorthand to document tuple format.
|
|
34
|
+
*
|
|
35
|
+
* - `8` → `8`
|
|
36
|
+
* - `'8px'` → `8`
|
|
37
|
+
* - `'8px 16px'` → `[8, 16]`
|
|
38
|
+
* - `'8px 16px 8px 16px'` → `[8, 16, 8, 16]`
|
|
39
|
+
* - `'8px 16px 12px'` → `[8, 16, 12, 16]` (CSS 3-value shorthand)
|
|
40
|
+
*/
|
|
41
|
+
function parseBoxModel(value, rootSize = DEFAULT_ROOT_SIZE) {
|
|
42
|
+
if (value == null) return void 0;
|
|
43
|
+
if (typeof value === "number") return value;
|
|
44
|
+
const parts = value.trim().split(/\s+/).map((p) => parseCssDimension(p, rootSize));
|
|
45
|
+
const nums = parts.filter((p) => p != null);
|
|
46
|
+
if (nums.length !== parts.length) return void 0;
|
|
47
|
+
if (nums.length === 1) return nums[0];
|
|
48
|
+
if (nums.length === 2) return [nums[0], nums[1]];
|
|
49
|
+
if (nums.length === 3) return [
|
|
50
|
+
nums[0],
|
|
51
|
+
nums[1],
|
|
52
|
+
nums[2],
|
|
53
|
+
nums[1]
|
|
54
|
+
];
|
|
55
|
+
if (nums.length === 4) return [
|
|
56
|
+
nums[0],
|
|
57
|
+
nums[1],
|
|
58
|
+
nums[2],
|
|
59
|
+
nums[3]
|
|
60
|
+
];
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Parse a CSS font-weight value.
|
|
64
|
+
*/
|
|
65
|
+
function parseFontWeight(value) {
|
|
66
|
+
if (value == null) return void 0;
|
|
67
|
+
if (typeof value === "number") return value;
|
|
68
|
+
if (value === "normal" || value === "bold") return value;
|
|
69
|
+
const num = Number.parseInt(value, 10);
|
|
70
|
+
if (!Number.isNaN(num)) return num;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Parse a CSS line-height value to a unitless number.
|
|
74
|
+
*/
|
|
75
|
+
function parseLineHeight(value, rootSize = DEFAULT_ROOT_SIZE) {
|
|
76
|
+
if (value == null) return void 0;
|
|
77
|
+
if (typeof value === "number") return value;
|
|
78
|
+
if (value === "normal") return void 0;
|
|
79
|
+
const dim = parseCssDimension(value, rootSize);
|
|
80
|
+
if (dim != null) return dim;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
//#endregion
|
|
84
|
+
//#region src/resolveStyles.ts
|
|
85
|
+
const TEXT_ALIGN_VALUES = new Set([
|
|
86
|
+
"left",
|
|
87
|
+
"center",
|
|
88
|
+
"right",
|
|
89
|
+
"justify"
|
|
90
|
+
]);
|
|
91
|
+
const FONT_STYLE_VALUES = new Set(["normal", "italic"]);
|
|
92
|
+
const TEXT_DECORATION_VALUES = new Set([
|
|
93
|
+
"none",
|
|
94
|
+
"underline",
|
|
95
|
+
"line-through"
|
|
96
|
+
]);
|
|
97
|
+
const BORDER_STYLE_VALUES = new Set([
|
|
98
|
+
"solid",
|
|
99
|
+
"dashed",
|
|
100
|
+
"dotted"
|
|
101
|
+
]);
|
|
102
|
+
/**
|
|
103
|
+
* Convert a rocketstyle `$rocketstyle` theme object into a `ResolvedStyles`
|
|
104
|
+
* object compatible with `@pyreon/document`.
|
|
105
|
+
*
|
|
106
|
+
* Only extracts properties that `ResolvedStyles` supports — everything else
|
|
107
|
+
* (transitions, cursor, display, etc.) is silently ignored.
|
|
108
|
+
*/
|
|
109
|
+
function resolveStyles(rocketstyle, rootSize = 16) {
|
|
110
|
+
const styles = {};
|
|
111
|
+
const fontSize = parseCssDimension(rocketstyle.fontSize, rootSize);
|
|
112
|
+
if (fontSize != null) styles.fontSize = fontSize;
|
|
113
|
+
if (typeof rocketstyle.fontFamily === "string") styles.fontFamily = rocketstyle.fontFamily;
|
|
114
|
+
const fontWeight = parseFontWeight(rocketstyle.fontWeight);
|
|
115
|
+
if (fontWeight != null) styles.fontWeight = fontWeight;
|
|
116
|
+
if (typeof rocketstyle.fontStyle === "string" && FONT_STYLE_VALUES.has(rocketstyle.fontStyle)) styles.fontStyle = rocketstyle.fontStyle;
|
|
117
|
+
if (typeof rocketstyle.textDecoration === "string" && TEXT_DECORATION_VALUES.has(rocketstyle.textDecoration)) styles.textDecoration = rocketstyle.textDecoration;
|
|
118
|
+
if (typeof rocketstyle.color === "string") styles.color = rocketstyle.color;
|
|
119
|
+
if (typeof rocketstyle.backgroundColor === "string") styles.backgroundColor = rocketstyle.backgroundColor;
|
|
120
|
+
if (typeof rocketstyle.textAlign === "string" && TEXT_ALIGN_VALUES.has(rocketstyle.textAlign)) styles.textAlign = rocketstyle.textAlign;
|
|
121
|
+
const lineHeight = parseLineHeight(rocketstyle.lineHeight, rootSize);
|
|
122
|
+
if (lineHeight != null) styles.lineHeight = lineHeight;
|
|
123
|
+
const letterSpacing = parseCssDimension(rocketstyle.letterSpacing, rootSize);
|
|
124
|
+
if (letterSpacing != null) styles.letterSpacing = letterSpacing;
|
|
125
|
+
const padding = parseBoxModel(rocketstyle.padding, rootSize);
|
|
126
|
+
if (padding != null) styles.padding = padding;
|
|
127
|
+
const margin = parseBoxModel(rocketstyle.margin, rootSize);
|
|
128
|
+
if (margin != null) styles.margin = margin;
|
|
129
|
+
const borderRadius = parseCssDimension(rocketstyle.borderRadius, rootSize);
|
|
130
|
+
if (borderRadius != null) styles.borderRadius = borderRadius;
|
|
131
|
+
const borderWidth = parseCssDimension(rocketstyle.borderWidth, rootSize);
|
|
132
|
+
if (borderWidth != null) styles.borderWidth = borderWidth;
|
|
133
|
+
if (typeof rocketstyle.borderColor === "string") styles.borderColor = rocketstyle.borderColor;
|
|
134
|
+
if (typeof rocketstyle.borderStyle === "string" && BORDER_STYLE_VALUES.has(rocketstyle.borderStyle)) styles.borderStyle = rocketstyle.borderStyle;
|
|
135
|
+
if (rocketstyle.width != null) styles.width = parseCssDimension(rocketstyle.width, rootSize) ?? rocketstyle.width;
|
|
136
|
+
if (rocketstyle.height != null) styles.height = parseCssDimension(rocketstyle.height, rootSize) ?? rocketstyle.height;
|
|
137
|
+
if (rocketstyle.maxWidth != null) styles.maxWidth = parseCssDimension(rocketstyle.maxWidth, rootSize) ?? rocketstyle.maxWidth;
|
|
138
|
+
if (typeof rocketstyle.opacity === "number") styles.opacity = rocketstyle.opacity;
|
|
139
|
+
return styles;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
//#endregion
|
|
143
|
+
//#region src/extractDocumentTree.ts
|
|
144
|
+
function isVNode(value) {
|
|
145
|
+
return value != null && typeof value === "object" && "type" in value && "props" in value;
|
|
146
|
+
}
|
|
147
|
+
function getDocumentType(fn) {
|
|
148
|
+
if (typeof fn !== "function") return void 0;
|
|
149
|
+
const meta = fn.meta;
|
|
150
|
+
if (meta?._documentType) return meta._documentType;
|
|
151
|
+
if ("_documentType" in fn) return fn._documentType;
|
|
152
|
+
}
|
|
153
|
+
function flattenChildren(children) {
|
|
154
|
+
const result = [];
|
|
155
|
+
for (const child of children) if (Array.isArray(child)) result.push(...flattenChildren(child));
|
|
156
|
+
else if (typeof child === "function") {
|
|
157
|
+
const resolved = child();
|
|
158
|
+
if (Array.isArray(resolved)) result.push(...flattenChildren(resolved));
|
|
159
|
+
else result.push(resolved);
|
|
160
|
+
} else result.push(child);
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
function extractChildren(children, options) {
|
|
164
|
+
const flat = flattenChildren(children);
|
|
165
|
+
const result = [];
|
|
166
|
+
for (const child of flat) {
|
|
167
|
+
if (child == null || child === false || child === true) continue;
|
|
168
|
+
if (typeof child === "string") {
|
|
169
|
+
result.push(child);
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if (typeof child === "number") {
|
|
173
|
+
result.push(String(child));
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (isVNode(child)) {
|
|
177
|
+
const extracted = extractNode(child, options);
|
|
178
|
+
if (Array.isArray(extracted)) result.push(...extracted);
|
|
179
|
+
else if (extracted != null) result.push(extracted);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
function extractNode(vnode, options) {
|
|
185
|
+
const { type, props, children } = vnode;
|
|
186
|
+
const includeStyles = options.includeStyles !== false;
|
|
187
|
+
const rootSize = options.rootSize ?? 16;
|
|
188
|
+
const docType = getDocumentType(type);
|
|
189
|
+
if (docType) {
|
|
190
|
+
const docProps = {};
|
|
191
|
+
if (props._documentProps && typeof props._documentProps === "object") Object.assign(docProps, props._documentProps);
|
|
192
|
+
const styles = includeStyles && props.$rocketstyle ? resolveStyles(props.$rocketstyle, rootSize) : void 0;
|
|
193
|
+
const node = {
|
|
194
|
+
type: docType,
|
|
195
|
+
props: docProps,
|
|
196
|
+
children: extractChildren(children ?? [], options)
|
|
197
|
+
};
|
|
198
|
+
if (styles && Object.keys(styles).length > 0) node.styles = styles;
|
|
199
|
+
return node;
|
|
200
|
+
}
|
|
201
|
+
if (typeof type === "function") {
|
|
202
|
+
const mergedProps = { ...props };
|
|
203
|
+
if (children && children.length > 0) mergedProps.children = children.length === 1 ? children[0] : children;
|
|
204
|
+
const result = type(mergedProps);
|
|
205
|
+
if (isVNode(result)) return extractNode(result, options);
|
|
206
|
+
if (typeof result === "string") return [result];
|
|
207
|
+
if (typeof result === "number") return [String(result)];
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
if (typeof type === "string") {
|
|
211
|
+
const docChildren = extractChildren(children ?? [], options);
|
|
212
|
+
if (docChildren.length > 0) return docChildren;
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Walk a Pyreon VNode tree and extract a `DocNode` tree for `@pyreon/document`.
|
|
219
|
+
*
|
|
220
|
+
* For each VNode whose component has a `_documentType` marker:
|
|
221
|
+
* 1. Read `_documentType` → `DocNode.type`
|
|
222
|
+
* 2. Read `_documentProps` → `DocNode.props`
|
|
223
|
+
* 3. Read `$rocketstyle` → `resolveStyles()` → `DocNode.styles`
|
|
224
|
+
* 4. Recurse into children
|
|
225
|
+
*
|
|
226
|
+
* VNodes without `_documentType` are transparent — their children
|
|
227
|
+
* are flattened into the parent's children list.
|
|
228
|
+
*/
|
|
229
|
+
function extractDocumentTree(vnode, options = {}) {
|
|
230
|
+
if (isVNode(vnode)) {
|
|
231
|
+
const result = extractNode(vnode, options);
|
|
232
|
+
if (result && !Array.isArray(result)) return result;
|
|
233
|
+
return {
|
|
234
|
+
type: "document",
|
|
235
|
+
props: {},
|
|
236
|
+
children: Array.isArray(result) ? result : []
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
if (typeof vnode === "function") return extractDocumentTree(vnode(), options);
|
|
240
|
+
return {
|
|
241
|
+
type: "document",
|
|
242
|
+
props: {},
|
|
243
|
+
children: []
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
//#endregion
|
|
248
|
+
export { extractDocumentTree, parseBoxModel, parseCssDimension, parseFontWeight, parseLineHeight, resolveStyles };
|
|
249
|
+
//# sourceMappingURL=index.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pyreon/connector-document",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "https://github.com/pyreon/ui-system",
|
|
7
|
+
"directory": "packages/connector-document"
|
|
8
|
+
},
|
|
9
|
+
"description": "Bridge between @pyreon/ui-system styled components and @pyreon/document rendering",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"type": "module",
|
|
12
|
+
"sideEffects": false,
|
|
13
|
+
"exports": {
|
|
14
|
+
"source": "./src/index.ts",
|
|
15
|
+
"import": "./lib/index.js",
|
|
16
|
+
"types": "./lib/index.d.ts"
|
|
17
|
+
},
|
|
18
|
+
"types": "./lib/index.d.ts",
|
|
19
|
+
"main": "./lib/index.js",
|
|
20
|
+
"files": [
|
|
21
|
+
"lib",
|
|
22
|
+
"!lib/**/*.map",
|
|
23
|
+
"!lib/analysis",
|
|
24
|
+
"README.md",
|
|
25
|
+
"LICENSE"
|
|
26
|
+
],
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">= 18"
|
|
29
|
+
},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"prepublish": "bun run build",
|
|
35
|
+
"build": "bun run vl_rolldown_build",
|
|
36
|
+
"build:watch": "bun run vl_rolldown_build-watch",
|
|
37
|
+
"lint": "biome check src/",
|
|
38
|
+
"test": "vitest run",
|
|
39
|
+
"test:coverage": "vitest run --coverage",
|
|
40
|
+
"test:watch": "vitest",
|
|
41
|
+
"typecheck": "tsc --noEmit"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"@pyreon/core": ">=0.7.0",
|
|
45
|
+
"@pyreon/document": ">=0.10.0"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@pyreon/core": "^0.7.11",
|
|
49
|
+
"@pyreon/document": "^0.10.0",
|
|
50
|
+
"@vitus-labs/tools-rolldown": "^1.15.4",
|
|
51
|
+
"@pyreon/typescript": "^0.7.11"
|
|
52
|
+
}
|
|
53
|
+
}
|