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.
package/README.md ADDED
@@ -0,0 +1,18 @@
1
+ # km-card-layout-core
2
+
3
+ 纯函数渲染核心:负责将布局 JSON 与数据合成渲染树,处理绑定路径、repeatable-group 展开、样式单位(px/rpx)等。
4
+
5
+ ## API
6
+
7
+ - `normalizeLayout(layout)`:清洗/容错布局对象或 JSON 字符串,补齐默认尺寸/children。
8
+ - `buildRenderResult(layout, data, unit?)`:返回 `{ renderTree, cardStyle, backgroundImage, backgroundStyle }`,可直接用于 PC/Vue、小程序等多端。
9
+ - `buildRenderNodes(children, data, unit?, context?)`:仅展开 children 为渲染节点列表。
10
+ - 其他工具:`resolveBindingValue`(绑定读取,支持 `$item.` 与 dataPath 上下文)、`styleObjectToString`、`addUnit`。
11
+
12
+ ## 适用场景
13
+
14
+ - PC 端:在 Vue 组件里调用 `buildRenderResult`,把 renderTree 映射为 VDOM。
15
+ - 小程序:被 `km-card-layout-component-miniprogram` 依赖,组件中通过 core 得到 renderTree。
16
+ - 预渲染/快照:Node 环境按相同逻辑生成渲染树后做静态输出或图片化。
17
+
18
+ 核心不依赖 DOM/平台 API,可直接通过 `require('km-card-layout-core')` 使用。
package/dist/index.js ADDED
@@ -0,0 +1,524 @@
1
+ "use strict";
2
+ /**
3
+ * CardMaster 布局 JSON 渲染核心。
4
+ *
5
+ * - 平台无关:不依赖 DOM/小程序 API,方便在 Web/小程序/Node 复用。
6
+ * - 职责:将布局 Schema 与业务数据合成为带内联样式的渲染树,外层只需将节点映射到各端组件。
7
+ */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
20
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
21
+ };
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.buildRenderResult = exports.buildRenderNodes = exports.resolveBindingValue = exports.normalizeLayout = exports.styleObjectToString = exports.addUnit = void 0;
24
+ __exportStar(require("./interface"), exports);
25
+ /** ---------- 常量 ---------- */
26
+ const DEFAULT_CARD_WIDTH = 343; // 默认卡片宽度(像素)
27
+ const DEFAULT_CARD_HEIGHT = 210; // 默认卡片高度(像素)
28
+ const DEFAULT_GROUP_GAP = 22; // repeatable-item 默认纵向间距
29
+ const DIMENSION_PROPS = new Set([
30
+ 'width',
31
+ 'height',
32
+ 'top',
33
+ 'right',
34
+ 'bottom',
35
+ 'left',
36
+ 'padding',
37
+ 'paddingTop',
38
+ 'paddingBottom',
39
+ 'paddingLeft',
40
+ 'paddingRight',
41
+ 'margin',
42
+ 'marginTop',
43
+ 'marginBottom',
44
+ 'marginLeft',
45
+ 'marginRight',
46
+ 'fontSize',
47
+ 'lineHeight',
48
+ 'borderRadius',
49
+ 'borderWidth',
50
+ 'letterSpacing',
51
+ 'gap',
52
+ 'rowGap',
53
+ 'columnGap',
54
+ 'flexBasis',
55
+ ]);
56
+ /** ---------- 基础工具 ---------- */
57
+ /**
58
+ * 安全转 number,非数值或 NaN 返回 undefined。
59
+ */
60
+ const toNumber = (value) => {
61
+ const num = Number(value);
62
+ return Number.isFinite(num) ? num : undefined;
63
+ };
64
+ /**
65
+ * camelCase 转 kebab-case(用于内联样式 key)。
66
+ */
67
+ const toKebab = (key) => key.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
68
+ /**
69
+ * 数字追加单位,非数字原样转字符串。
70
+ * 数字追加单位,非数字原样转字符串。
71
+ */
72
+ const addUnit = (value, unit) => {
73
+ if (value === undefined || value === null || value === '')
74
+ return undefined;
75
+ if (typeof value === 'number') {
76
+ const ratio = unit === 'rpx' ? 2 : 1;
77
+ return `${value * ratio}${unit}`;
78
+ }
79
+ if (typeof value === 'string') {
80
+ const parsed = Number(value);
81
+ if (Number.isFinite(parsed)) {
82
+ const ratio = unit === 'rpx' ? 2 : 1;
83
+ return `${parsed * ratio}${unit}`;
84
+ }
85
+ }
86
+ return `${value}`;
87
+ };
88
+ exports.addUnit = addUnit;
89
+ /**
90
+ * 样式对象转内联样式字符串,对需要单位的字段自动补单位。
91
+ */
92
+ const styleObjectToString = (style, unit = 'px') => {
93
+ if (!style)
94
+ return '';
95
+ const pairs = [];
96
+ Object.keys(style).forEach(key => {
97
+ const value = style[key];
98
+ if (value === undefined || value === null || value === '')
99
+ return;
100
+ const useUnit = DIMENSION_PROPS.has(key)
101
+ ? (0, exports.addUnit)(value, unit)
102
+ : value;
103
+ if (useUnit === undefined || useUnit === null || useUnit === '')
104
+ return;
105
+ pairs.push(`${toKebab(key)}:${useUnit}`);
106
+ });
107
+ return pairs.join(';');
108
+ };
109
+ exports.styleObjectToString = styleObjectToString;
110
+ /**
111
+ * 容错 JSON 解析:字符串用 JSON.parse,其他保持原样或返回 null。
112
+ */
113
+ const parseJson = (input) => {
114
+ if (typeof input !== 'string')
115
+ return input !== null && input !== void 0 ? input : null;
116
+ try {
117
+ return JSON.parse(input);
118
+ }
119
+ catch (err) {
120
+ console.warn('[km-card-layout-core] JSON parse failed', err);
121
+ return null;
122
+ }
123
+ };
124
+ const isObject = (val) => Boolean(val) && typeof val === 'object';
125
+ const getAbsLayout = (el) => el.layout && el.layout.mode === 'absolute'
126
+ ? el.layout
127
+ : null;
128
+ const getFlexLayout = (el) => el.layout && el.layout.mode === 'flex'
129
+ ? el.layout
130
+ : null;
131
+ /** ---------- 布局与数据解析 ---------- */
132
+ /**
133
+ * 归一化布局输入(对象或 JSON 字符串),补齐宽高/容器/children 默认值。
134
+ */
135
+ const normalizeLayout = (layout) => {
136
+ if (!Array.isArray(layout))
137
+ return [];
138
+ return layout
139
+ .map(item => {
140
+ var _a, _b;
141
+ if (!item || typeof item !== 'object')
142
+ return null;
143
+ const parsed = item;
144
+ return {
145
+ ...parsed,
146
+ width: (_a = toNumber(parsed.width)) !== null && _a !== void 0 ? _a : DEFAULT_CARD_WIDTH,
147
+ height: (_b = toNumber(parsed.height)) !== null && _b !== void 0 ? _b : DEFAULT_CARD_HEIGHT,
148
+ container: parsed.container || { mode: 'absolute' },
149
+ children: Array.isArray(parsed.children)
150
+ ? parsed.children
151
+ : [],
152
+ };
153
+ })
154
+ .filter((v) => Boolean(v));
155
+ };
156
+ exports.normalizeLayout = normalizeLayout;
157
+ /**
158
+ * 将绑定路径拆分为片段,支持点语法与数组下标。
159
+ */
160
+ const pathToSegments = (path) => `${path || ''}`
161
+ .replace(/\[(\d+)\]/g, '.$1')
162
+ .split('.')
163
+ .map(p => p.trim())
164
+ .filter(Boolean);
165
+ /**
166
+ * 按路径访问对象/数组,缺失时返回 undefined/null。
167
+ */
168
+ const readByPath = (data, path) => {
169
+ if (path === undefined || path === null || path === '')
170
+ return data;
171
+ const segments = pathToSegments(path);
172
+ let cursor = data;
173
+ for (let i = 0; i < segments.length; i += 1) {
174
+ if (!isObject(cursor) && !Array.isArray(cursor))
175
+ return undefined;
176
+ const key = segments[i];
177
+ if (Array.isArray(cursor)) {
178
+ const idx = Number(key);
179
+ cursor = Number.isNaN(idx) ? undefined : cursor[idx];
180
+ }
181
+ else {
182
+ cursor = cursor[key];
183
+ }
184
+ if (cursor === undefined || cursor === null) {
185
+ return cursor;
186
+ }
187
+ }
188
+ return cursor;
189
+ };
190
+ /**
191
+ * 解析元素 binding 对应的数据:
192
+ * - 全局 binding:直接基于根数据;
193
+ * - repeatable-group:binding 等于/前缀 dataPath 或以 `$item.` 开头时,改用当前条目数据。
194
+ */
195
+ const resolveBindingValue = (binding, rootData, context) => {
196
+ if (!binding)
197
+ return undefined;
198
+ const { contextBinding, contextData } = context || {};
199
+ let target = rootData;
200
+ let path = binding;
201
+ // repeatable-group: binding 等于 dataPath 或以其为前缀时,切换到当前条目数据
202
+ if (contextBinding &&
203
+ (binding === contextBinding || binding.startsWith(`${contextBinding}.`))) {
204
+ target = contextData;
205
+ path =
206
+ binding === contextBinding
207
+ ? ''
208
+ : binding.slice(contextBinding.length + 1);
209
+ }
210
+ else if (binding.startsWith('$item.')) {
211
+ target = contextData;
212
+ path = binding.slice('$item.'.length);
213
+ }
214
+ const value = readByPath(target, path);
215
+ return value === undefined ? undefined : value;
216
+ };
217
+ exports.resolveBindingValue = resolveBindingValue;
218
+ /** ---------- 样式构建 ---------- */
219
+ /**
220
+ * 生成元素外层样式(绝对/弹性布局),始终返回内联样式字符串。
221
+ */
222
+ const buildWrapperStyle = (el, unit) => {
223
+ var _a, _b;
224
+ const abs = getAbsLayout(el);
225
+ if (abs) {
226
+ const textAlign = (_a = el === null || el === void 0 ? void 0 : el.style) === null || _a === void 0 ? void 0 : _a.textAlign;
227
+ return (0, exports.styleObjectToString)({
228
+ position: 'absolute',
229
+ left: (0, exports.addUnit)(abs.x, unit),
230
+ top: (0, exports.addUnit)(abs.y, unit),
231
+ width: (0, exports.addUnit)(abs.width, unit),
232
+ height: (0, exports.addUnit)(abs.height, unit),
233
+ zIndex: abs.zIndex,
234
+ boxSizing: 'border-box',
235
+ textAlign,
236
+ }, unit);
237
+ }
238
+ const flex = getFlexLayout(el);
239
+ if (!flex)
240
+ return '';
241
+ const item = flex.item || {};
242
+ return (0, exports.styleObjectToString)({
243
+ width: (0, exports.addUnit)(flex.width, unit),
244
+ height: (0, exports.addUnit)(flex.height, unit),
245
+ order: item.order,
246
+ flexGrow: item.flexGrow,
247
+ flexShrink: item.flexShrink,
248
+ flexBasis: item.flexBasis,
249
+ alignSelf: item.alignSelf,
250
+ boxSizing: 'border-box',
251
+ textAlign: (_b = el === null || el === void 0 ? void 0 : el.style) === null || _b === void 0 ? void 0 : _b.textAlign,
252
+ }, unit);
253
+ };
254
+ /**
255
+ * padding 数组/数字转 CSS 缩写字符串。
256
+ */
257
+ const formatPadding = (padding, unit) => {
258
+ if (padding === undefined || padding === null)
259
+ return undefined;
260
+ if (typeof padding === 'number')
261
+ return (0, exports.addUnit)(padding, unit);
262
+ if (Array.isArray(padding)) {
263
+ const list = padding
264
+ .filter(v => v !== undefined && v !== null)
265
+ .map(v => (0, exports.addUnit)(v, unit));
266
+ if (!list.length)
267
+ return undefined;
268
+ if (list.length === 2)
269
+ return `${list[0]} ${list[1]}`;
270
+ if (list.length === 3)
271
+ return `${list[0]} ${list[1]} ${list[2]}`;
272
+ if (list.length >= 4)
273
+ return `${list[0]} ${list[1]} ${list[2]} ${list[3]}`;
274
+ }
275
+ return undefined;
276
+ };
277
+ /**
278
+ * 构建 layout-panel 的容器样式(flex)。
279
+ */
280
+ const buildPanelStyle = (el, unit) => {
281
+ const options = (el.container && el.container.options) || {};
282
+ const style = {
283
+ display: 'flex',
284
+ flexDirection: options.direction || 'row',
285
+ flexWrap: options.wrap || 'nowrap',
286
+ justifyContent: options.justifyContent,
287
+ alignItems: options.alignItems,
288
+ padding: formatPadding(options.padding, unit),
289
+ };
290
+ if (options.gap && typeof options.gap === 'number') {
291
+ style.gap = (0, exports.addUnit)(options.gap, unit);
292
+ }
293
+ else if (options.gap && isObject(options.gap)) {
294
+ style.rowGap = (0, exports.addUnit)(options.gap.row, unit);
295
+ style.columnGap = (0, exports.addUnit)(options.gap.column, unit);
296
+ }
297
+ return (0, exports.styleObjectToString)(style, unit);
298
+ };
299
+ /**
300
+ * 元素样式转内联字符串(自动补单位)。
301
+ */
302
+ const normalizeElementStyle = (style, unit) => {
303
+ if (!style)
304
+ return '';
305
+ return (0, exports.styleObjectToString)(style, unit);
306
+ };
307
+ /** ---------- Repeatable 相关 ---------- */
308
+ /**
309
+ * 通过前两条 item 的布局推断间距,不足则回落 DEFAULT_GROUP_GAP。
310
+ */
311
+ const inferGroupGap = (group) => {
312
+ const items = group.items || [];
313
+ if (items.length >= 2) {
314
+ const first = (items[0].elements || []).map(getAbsLayout).find(Boolean);
315
+ const second = (items[1].elements || []).map(getAbsLayout).find(Boolean);
316
+ if (first &&
317
+ second &&
318
+ typeof first.y === 'number' &&
319
+ typeof second.y === 'number') {
320
+ return Math.abs(second.y - first.y);
321
+ }
322
+ }
323
+ return DEFAULT_GROUP_GAP;
324
+ };
325
+ /**
326
+ * 展开 repeatable-group 为具体元素实例,并附带条目数据。
327
+ * 优先使用设计器保存的 `items`;否则按推断间距克隆 `itemTemplate`。
328
+ */
329
+ const materializeRepeatableItems = (group, rootData) => {
330
+ const result = [];
331
+ const dataset = (0, exports.resolveBindingValue)(group.dataPath, rootData, {}) || [];
332
+ const dataList = Array.isArray(dataset) ? dataset : [];
333
+ const maxItems = group.maxItems ||
334
+ dataList.length ||
335
+ (group.items || []).length ||
336
+ (group.itemTemplate || []).length;
337
+ const template = (group.items && group.items[0] && group.items[0].elements) ||
338
+ group.itemTemplate ||
339
+ [];
340
+ const gap = inferGroupGap(group);
341
+ // Use saved items (from designer) first
342
+ if (group.items && group.items.length) {
343
+ group.items.slice(0, maxItems).forEach((item, idx) => {
344
+ const payload = dataList[idx] !== undefined ? dataList[idx] : item.data;
345
+ (item.elements || []).forEach(el => {
346
+ result.push({
347
+ element: el,
348
+ contextData: payload,
349
+ contextBinding: group.dataPath,
350
+ });
351
+ });
352
+ });
353
+ return result;
354
+ }
355
+ // Otherwise clone from template by gap
356
+ dataList.slice(0, maxItems).forEach((payload, idx) => {
357
+ template.forEach(el => {
358
+ const abs = getAbsLayout(el);
359
+ const cloned = abs
360
+ ? {
361
+ ...el,
362
+ layout: { ...abs, y: abs.y + idx * gap },
363
+ }
364
+ : el;
365
+ result.push({
366
+ element: cloned,
367
+ contextData: payload,
368
+ contextBinding: group.dataPath,
369
+ });
370
+ });
371
+ });
372
+ return result;
373
+ };
374
+ /** ---------- 渲染树构建 ---------- */
375
+ /**
376
+ * 将 children 展开为渲染节点,处理 repeatable-group 展开与 mutualExcludes 隐藏。
377
+ */
378
+ const buildRenderNodes = (children, rootData, unit = 'px', context = {}) => {
379
+ if (!Array.isArray(children))
380
+ return [];
381
+ // Mark mutually exclusive bindings to hide them when repeatable-group is present
382
+ const excluded = new Set();
383
+ children.forEach(el => {
384
+ if (el && el.type === 'repeatable-group' && !!el.visible) {
385
+ (el.mutualExcludes || []).forEach(b => excluded.add(b));
386
+ }
387
+ });
388
+ const nodes = [];
389
+ children.forEach(el => {
390
+ if (!el || el.visible === false)
391
+ return;
392
+ if (el.type === 'repeatable-group') {
393
+ const instances = materializeRepeatableItems(el, rootData);
394
+ instances.forEach(({ element, contextData, contextBinding }) => {
395
+ const node = buildNode(element, rootData, unit, {
396
+ ...context,
397
+ contextData,
398
+ contextBinding,
399
+ });
400
+ if (node)
401
+ nodes.push(node);
402
+ });
403
+ return;
404
+ }
405
+ if (el.binding && excluded.has(el.binding))
406
+ return;
407
+ const node = buildNode(el, rootData, unit, context);
408
+ if (node)
409
+ nodes.push(node);
410
+ });
411
+ return nodes;
412
+ };
413
+ exports.buildRenderNodes = buildRenderNodes;
414
+ /**
415
+ * 构建单个渲染节点(layout-panel 递归处理子元素)。
416
+ */
417
+ const buildNode = (el, rootData, unit, context) => {
418
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
419
+ if (!el || el.visible === false)
420
+ return null;
421
+ const wrapperStyle = buildWrapperStyle(el, unit);
422
+ if (el.type === 'layout-panel') {
423
+ return {
424
+ id: el.id,
425
+ type: el.type,
426
+ visible: !!el.visible,
427
+ wrapperStyle,
428
+ contentStyle: buildPanelStyle(el, unit),
429
+ children: (0, exports.buildRenderNodes)(el.children || [], rootData, unit, context),
430
+ };
431
+ }
432
+ const baseStyle = normalizeElementStyle(el.style, unit);
433
+ if (el.type === 'text') {
434
+ const align = ((_a = el.style) === null || _a === void 0 ? void 0 : _a.textAlign) || el.align;
435
+ const textStyle = align ? `${baseStyle};text-align:${align}` : baseStyle;
436
+ const value = (_c = (_b = (0, exports.resolveBindingValue)(el.binding, rootData, context)) !== null && _b !== void 0 ? _b : el.defaultValue) !== null && _c !== void 0 ? _c : '';
437
+ return {
438
+ id: el.id,
439
+ type: el.type,
440
+ wrapperStyle,
441
+ contentStyle: textStyle,
442
+ text: `${value}`,
443
+ visible: !!el.visible,
444
+ };
445
+ }
446
+ if (el.type === 'image') {
447
+ const src = (_f = (_e = (_d = (0, exports.resolveBindingValue)(el.binding, rootData, context)) !== null && _d !== void 0 ? _d : el.defaultUrl) !== null && _e !== void 0 ? _e : el.defaultValue) !== null && _f !== void 0 ? _f : '';
448
+ const mode = el.fit === 'contain' ? 'aspectFit' : 'aspectFill';
449
+ return {
450
+ id: el.id,
451
+ type: el.type,
452
+ wrapperStyle,
453
+ contentStyle: baseStyle,
454
+ src,
455
+ mode,
456
+ visible: !!el.visible,
457
+ };
458
+ }
459
+ if (el.type === 'icon') {
460
+ const name = (_j = (_h = (_g = (0, exports.resolveBindingValue)(el.binding, rootData, context)) !== null && _g !== void 0 ? _g : el.name) !== null && _h !== void 0 ? _h : el.defaultValue) !== null && _j !== void 0 ? _j : '';
461
+ return {
462
+ id: el.id,
463
+ type: el.type,
464
+ wrapperStyle,
465
+ contentStyle: baseStyle,
466
+ name: `${name}`,
467
+ text: `${name}`,
468
+ visible: !!el.visible,
469
+ };
470
+ }
471
+ if (el.type === 'custom') {
472
+ return {
473
+ id: el.id,
474
+ type: el.type,
475
+ wrapperStyle,
476
+ contentStyle: baseStyle,
477
+ visible: !!el.visible,
478
+ };
479
+ }
480
+ // Unknown type fallback to simple text node
481
+ return {
482
+ id: el.id,
483
+ type: 'text',
484
+ wrapperStyle,
485
+ contentStyle: baseStyle,
486
+ text: el.defaultValue || '',
487
+ visible: !!el.visible,
488
+ };
489
+ };
490
+ /**
491
+ * 主入口:合并布局 Schema 与数据,生成供前端使用的渲染结果。
492
+ */
493
+ const buildRenderResult = (layoutInput, dataInput, unit = 'px') => {
494
+ const layouts = (0, exports.normalizeLayout)(layoutInput);
495
+ return layouts.map(layout => {
496
+ const cardStyle = (0, exports.styleObjectToString)({
497
+ width: (0, exports.addUnit)(layout.width, unit),
498
+ height: (0, exports.addUnit)(layout.height, unit),
499
+ color: layout.fontColor,
500
+ borderRadius: layout.borderRadius !== undefined
501
+ ? (0, exports.addUnit)(layout.borderRadius, unit)
502
+ : undefined,
503
+ padding: layout.padding !== undefined
504
+ ? (0, exports.addUnit)(layout.padding, unit)
505
+ : undefined,
506
+ position: 'relative',
507
+ }, unit);
508
+ const bgStyle = (0, exports.styleObjectToString)({
509
+ zIndex: layout.backgroundZIndex,
510
+ borderRadius: layout.borderRadius !== undefined
511
+ ? (0, exports.addUnit)(layout.borderRadius, unit)
512
+ : undefined,
513
+ }, unit);
514
+ const renderTree = (0, exports.buildRenderNodes)(layout.children || [], dataInput || {}, unit);
515
+ return {
516
+ renderTree,
517
+ cardStyle,
518
+ backgroundImage: layout.backgroundImage || '',
519
+ backgroundStyle: bgStyle,
520
+ };
521
+ });
522
+ };
523
+ exports.buildRenderResult = buildRenderResult;
524
+ __exportStar(require("./utils"), exports);
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,19 @@
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("./elements"), exports);
18
+ __exportStar(require("./layout"), exports);
19
+ __exportStar(require("./render"), exports);
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/dist/utils.js ADDED
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.backgroundChange = backgroundChange;
4
+ function backgroundChange(bg, layout) {
5
+ const toNameArray = (name) => {
6
+ if (Array.isArray(name))
7
+ return name.filter(Boolean);
8
+ if (!name)
9
+ return [];
10
+ return `${name}`
11
+ .split(',')
12
+ .map(n => n.trim())
13
+ .filter(Boolean);
14
+ };
15
+ const applySpecialColor = (el) => {
16
+ const extras = bg.fontColorExtra || [];
17
+ if (!extras.length)
18
+ return el;
19
+ const keys = [];
20
+ if (el.binding)
21
+ keys.push(String(el.binding));
22
+ if (el.id)
23
+ keys.push(String(el.id));
24
+ if (el.type === 'icon')
25
+ keys.push('icon');
26
+ if (el.type === 'custom')
27
+ keys.push('decor');
28
+ const matched = extras.find(sc => toNameArray(sc.name).some(n => keys.some(k => k === null || k === void 0 ? void 0 : k.startsWith(n))));
29
+ if (!matched)
30
+ return el;
31
+ const baseStyle = { ...(el.style || {}) };
32
+ if (el.type === 'custom') {
33
+ return { ...el, style: { ...baseStyle, backgroundColor: matched.color } };
34
+ }
35
+ return { ...el, style: { ...baseStyle, color: matched.color } };
36
+ };
37
+ const traverse = (children = []) => children.map(el => {
38
+ if (!el)
39
+ return el;
40
+ if (el.type === 'layout-panel') {
41
+ return {
42
+ ...el,
43
+ children: traverse(el.children || []),
44
+ };
45
+ }
46
+ if (el.type === 'repeatable-group') {
47
+ return {
48
+ ...el,
49
+ itemTemplate: traverse(el.itemTemplate || []),
50
+ items: (el.items || []).map(item => ({
51
+ ...item,
52
+ elements: traverse(item.elements || []),
53
+ })),
54
+ };
55
+ }
56
+ return applySpecialColor(el);
57
+ });
58
+ return {
59
+ ...layout,
60
+ backgroundImage: bg.imgUrl,
61
+ fontColor: bg.fontColor || layout.fontColor,
62
+ children: traverse(layout.children || []),
63
+ };
64
+ }