km-card-layout-core 0.1.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.
@@ -0,0 +1,110 @@
1
+ export type CardElementType =
2
+ | 'text'
3
+ | 'image'
4
+ | 'icon'
5
+ | 'custom'
6
+ | 'layout-panel'
7
+ | 'repeatable-group';
8
+
9
+ export interface AbsoluteLayoutDefinition {
10
+ mode: 'absolute';
11
+ x: number;
12
+ y: number;
13
+ width: number;
14
+ height: number;
15
+ zIndex?: number;
16
+ }
17
+
18
+ export interface FlexItemOptions {
19
+ flexGrow?: number;
20
+ flexShrink?: number;
21
+ flexBasis?: number | string;
22
+ order?: number;
23
+ alignSelf?: 'flex-start' | 'flex-end' | 'center' | 'stretch' | 'baseline';
24
+ }
25
+
26
+ export interface FlexItemLayoutDefinition {
27
+ mode: 'flex';
28
+ item?: FlexItemOptions;
29
+ width?: number;
30
+ height?: number;
31
+ }
32
+
33
+ export type ElementLayout = AbsoluteLayoutDefinition | FlexItemLayoutDefinition;
34
+
35
+ export interface CardElementBase {
36
+ id: string;
37
+ type: CardElementType;
38
+ layout: ElementLayout;
39
+ visible?: boolean;
40
+ binding?: string;
41
+ style?: Record<string, string | number | undefined>;
42
+ defaultValue?: string;
43
+ }
44
+
45
+ export interface TextElement extends CardElementBase {
46
+ type: 'text';
47
+ align?: 'left' | 'center' | 'right';
48
+ multiline?: boolean;
49
+ }
50
+
51
+ export interface ImageElement extends CardElementBase {
52
+ type: 'image';
53
+ alt?: string;
54
+ fit?: 'cover' | 'contain';
55
+ defaultUrl?: string;
56
+ }
57
+
58
+ export interface IconElement extends CardElementBase {
59
+ type: 'icon';
60
+ name?: string;
61
+ }
62
+
63
+ export interface CustomElement extends CardElementBase {
64
+ type: 'custom';
65
+ }
66
+
67
+ export interface LayoutPanelElement extends CardElementBase {
68
+ type: 'layout-panel';
69
+ container: {
70
+ mode: 'absolute' | 'flex';
71
+ options?: {
72
+ direction?: 'row' | 'column';
73
+ wrap?: 'nowrap' | 'wrap';
74
+ justifyContent?:
75
+ | 'flex-start'
76
+ | 'flex-end'
77
+ | 'center'
78
+ | 'space-between'
79
+ | 'space-around'
80
+ | 'space-evenly';
81
+ alignItems?: 'flex-start' | 'flex-end' | 'center' | 'stretch' | 'baseline';
82
+ gap?: number | { row: number; column: number };
83
+ padding?: number | [number, number] | [number, number, number, number];
84
+ };
85
+ };
86
+ children?: CardElement[];
87
+ }
88
+
89
+ export interface RepeatableGroupItem {
90
+ id: string;
91
+ elements: CardElement[];
92
+ data?: Record<string, any>;
93
+ }
94
+
95
+ export interface RepeatableGroupElement extends CardElementBase {
96
+ type: 'repeatable-group';
97
+ dataPath: string;
98
+ itemTemplate: CardElement[];
99
+ items: RepeatableGroupItem[];
100
+ maxItems?: number;
101
+ mutualExcludes?: string[];
102
+ }
103
+
104
+ export type CardElement =
105
+ | TextElement
106
+ | ImageElement
107
+ | IconElement
108
+ | CustomElement
109
+ | LayoutPanelElement
110
+ | RepeatableGroupElement;
@@ -0,0 +1,3 @@
1
+ export * from './elements';
2
+ export * from './layout';
3
+ export * from './render';
@@ -0,0 +1,19 @@
1
+ import type { CardElement } from './elements';
2
+
3
+ export interface CardLayoutSchema {
4
+ name?: string;
5
+ container: {
6
+ mode: 'absolute';
7
+ };
8
+ width: number;
9
+ height: number;
10
+ backgroundImage?: string;
11
+ backgroundZIndex?: number;
12
+ fontColor?: string;
13
+ borderRadius?: number;
14
+ padding?: number;
15
+ children: CardElement[];
16
+ thumbnail?: string;
17
+ }
18
+
19
+ export type CardLayoutInput = CardLayoutSchema[];
@@ -0,0 +1,42 @@
1
+ import type { CardElementType, CardElement } from './elements';
2
+ import type { CardLayoutInput, CardLayoutSchema } from './layout';
3
+
4
+ export interface RenderNode {
5
+ id: string;
6
+ type: CardElementType | 'text';
7
+ wrapperStyle: string;
8
+ contentStyle: string;
9
+ name?: string;
10
+ text?: string;
11
+ src?: string;
12
+ mode?: string;
13
+ children?: RenderNode[];
14
+ visible?: boolean;
15
+ }
16
+
17
+ export interface RenderPageResult {
18
+ renderTree: RenderNode[];
19
+ cardStyle: string;
20
+ backgroundImage: string;
21
+ backgroundStyle: string;
22
+ }
23
+
24
+ export type RenderResult = RenderPageResult[];
25
+
26
+ export interface BindingContext {
27
+ /**
28
+ * 当前上下文数据的绑定前缀(repeatable-group 内为 dataPath)
29
+ */
30
+ contextBinding?: string;
31
+ /**
32
+ * 当前上下文数据(repeatable-group 单条数据)
33
+ */
34
+ contextData?: any;
35
+ }
36
+
37
+ export type {
38
+ CardElement,
39
+ CardElementType,
40
+ CardLayoutInput,
41
+ CardLayoutSchema,
42
+ };
package/package.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "km-card-layout-core",
3
+ "version": "0.1.0",
4
+ "description": "Shared render helpers for CardMaster layout JSON (binding resolution, repeatable groups, layout normalization).",
5
+ "main": "index.js",
6
+ "types": "types.d.ts",
7
+ "license": "MIT",
8
+ "scripts": {
9
+ "build": "tsc -p tsconfig.build.json",
10
+ "release": "pnpm run build && npm publish --access public"
11
+ }
12
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "noEmit": false,
5
+ "declaration": false,
6
+ "outDir": "./dist",
7
+ "rootDir": "./",
8
+ "module": "CommonJS",
9
+ "moduleResolution": "Node",
10
+ "target": "ES2019",
11
+ "allowImportingTsExtensions": false
12
+ },
13
+ "include": ["./**/*.ts"],
14
+ "exclude": ["./dist", "./node_modules"]
15
+ }
package/types.d.ts ADDED
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Declaration file for km-card-layout-core.
3
+ * Mirrors the exported types/functions from index.ts.
4
+ */
5
+
6
+ export * from './interface';
7
+
8
+ import type {
9
+ BindingContext,
10
+ CardElement,
11
+ CardLayoutInput,
12
+ CardLayoutSchema,
13
+ RenderNode,
14
+ RenderResult,
15
+ } from './interface';
16
+
17
+ export function addUnit(
18
+ value: string | number | undefined | null,
19
+ unit: 'px' | 'rpx'
20
+ ): string | undefined;
21
+
22
+ export function styleObjectToString(
23
+ style?: Record<string, any>,
24
+ unit?: 'px' | 'rpx'
25
+ ): string;
26
+
27
+ export function normalizeLayout(layout: CardLayoutInput): CardLayoutSchema[];
28
+
29
+ export function resolveBindingValue(
30
+ binding: string | undefined,
31
+ rootData: Record<string, any>,
32
+ context?: BindingContext
33
+ ): any;
34
+
35
+ export function buildRenderNodes(
36
+ children: CardElement[],
37
+ data: Record<string, any>,
38
+ unit?: 'px' | 'rpx',
39
+ context?: BindingContext
40
+ ): RenderNode[];
41
+
42
+ export function buildRenderResult(
43
+ layout: CardLayoutInput,
44
+ data: Record<string, any>,
45
+ unit?: 'px' | 'rpx'
46
+ ): RenderResult;
package/utils.ts ADDED
@@ -0,0 +1,67 @@
1
+ import type { CardElement, CardLayoutSchema } from './interface';
2
+ import { TemplateBackground } from './interface/data/payload';
3
+
4
+ export function backgroundChange(
5
+ bg: TemplateBackground,
6
+ layout: CardLayoutSchema
7
+ ): CardLayoutSchema {
8
+ const toNameArray = (name?: string | string[]) => {
9
+ if (Array.isArray(name)) return name.filter(Boolean);
10
+ if (!name) return [];
11
+ return `${name}`
12
+ .split(',')
13
+ .map(n => n.trim())
14
+ .filter(Boolean);
15
+ };
16
+
17
+ const applySpecialColor = (el: CardElement): CardElement => {
18
+ const extras = bg.fontColorExtra || [];
19
+ if (!extras.length) return el;
20
+
21
+ const keys: string[] = [];
22
+ if (el.binding) keys.push(String(el.binding));
23
+ if (el.id) keys.push(String(el.id));
24
+ if (el.type === 'icon') keys.push('icon');
25
+ if (el.type === 'custom') keys.push('decor');
26
+
27
+ const matched = extras.find(sc =>
28
+ toNameArray(sc.name).some(n => keys.some(k => k?.startsWith(n)))
29
+ );
30
+ if (!matched) return el;
31
+
32
+ const baseStyle = { ...(el.style || {}) };
33
+ if (el.type === 'custom') {
34
+ return { ...el, style: { ...baseStyle, backgroundColor: matched.color } };
35
+ }
36
+ return { ...el, style: { ...baseStyle, color: matched.color } };
37
+ };
38
+
39
+ const traverse = (children: CardElement[] = []): CardElement[] =>
40
+ children.map(el => {
41
+ if (!el) return el;
42
+ if (el.type === 'layout-panel') {
43
+ return {
44
+ ...el,
45
+ children: traverse(el.children || []),
46
+ };
47
+ }
48
+ if (el.type === 'repeatable-group') {
49
+ return {
50
+ ...el,
51
+ itemTemplate: traverse(el.itemTemplate || []),
52
+ items: (el.items || []).map(item => ({
53
+ ...item,
54
+ elements: traverse(item.elements || []),
55
+ })),
56
+ };
57
+ }
58
+ return applySpecialColor(el);
59
+ });
60
+
61
+ return {
62
+ ...layout,
63
+ backgroundImage: bg.imgUrl,
64
+ fontColor: bg.fontColor || layout.fontColor,
65
+ children: traverse(layout.children || []),
66
+ };
67
+ }