sdui-web 0.5.0 → 0.7.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,936 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/react/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ SduiRenderer: () => SduiRenderer
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/react/SduiRenderer.tsx
28
+ var import_react = require("react");
29
+
30
+ // src/utils.ts
31
+ function resolveColor(color, theme) {
32
+ if (!color) return void 0;
33
+ if (theme === "dark") {
34
+ return color.dark ?? color.light;
35
+ }
36
+ return color.light ?? color.dark;
37
+ }
38
+ function resolveSize(size) {
39
+ if (!size || !size.unit) return void 0;
40
+ switch (size.unit) {
41
+ case "dp":
42
+ return `${size.value ?? 0}px`;
43
+ case "match_parent":
44
+ return "100%";
45
+ case "wrap_content":
46
+ return "fit-content";
47
+ default:
48
+ return void 0;
49
+ }
50
+ }
51
+ function resolveSpacing(spacing) {
52
+ if (!spacing) return {};
53
+ return {
54
+ top: spacing.top != null ? `${spacing.top}px` : void 0,
55
+ bottom: spacing.bottom != null ? `${spacing.bottom}px` : void 0,
56
+ left: spacing.start != null ? `${spacing.start}px` : void 0,
57
+ right: spacing.end != null ? `${spacing.end}px` : void 0
58
+ };
59
+ }
60
+
61
+ // src/modifiers.ts
62
+ function applyModifier(element, modifier, theme) {
63
+ if (!modifier) return;
64
+ const s = element.style;
65
+ if (modifier.alignment) {
66
+ switch (modifier.alignment) {
67
+ // Single-axis (Column / Row children)
68
+ case "center":
69
+ case "center_horizontally":
70
+ case "center_vertically":
71
+ s.alignSelf = "center";
72
+ break;
73
+ case "start":
74
+ case "top":
75
+ s.alignSelf = "flex-start";
76
+ break;
77
+ case "end":
78
+ case "bottom":
79
+ s.alignSelf = "flex-end";
80
+ break;
81
+ // Two-axis (Box children — grid layout)
82
+ case "top_start":
83
+ s.alignSelf = "start";
84
+ s.justifySelf = "start";
85
+ break;
86
+ case "top_center":
87
+ s.alignSelf = "start";
88
+ s.justifySelf = "center";
89
+ break;
90
+ case "top_end":
91
+ s.alignSelf = "start";
92
+ s.justifySelf = "end";
93
+ break;
94
+ case "center_start":
95
+ s.alignSelf = "center";
96
+ s.justifySelf = "start";
97
+ break;
98
+ case "center_end":
99
+ s.alignSelf = "center";
100
+ s.justifySelf = "end";
101
+ break;
102
+ case "bottom_start":
103
+ s.alignSelf = "end";
104
+ s.justifySelf = "start";
105
+ break;
106
+ case "bottom_center":
107
+ s.alignSelf = "end";
108
+ s.justifySelf = "center";
109
+ break;
110
+ case "bottom_end":
111
+ s.alignSelf = "end";
112
+ s.justifySelf = "end";
113
+ break;
114
+ }
115
+ }
116
+ if (modifier.weight != null) {
117
+ s.flex = `${modifier.weight}`;
118
+ s.minWidth = "0";
119
+ s.minHeight = "0";
120
+ }
121
+ if (modifier.action) {
122
+ const action = modifier.action;
123
+ s.cursor = "pointer";
124
+ element.addEventListener("click", (e) => {
125
+ e.stopPropagation();
126
+ element.dispatchEvent(new CustomEvent("sdui:action", {
127
+ bubbles: true,
128
+ detail: { type: action.type, params: action.params }
129
+ }));
130
+ });
131
+ }
132
+ const mod = modifier;
133
+ const hasAspectRatio = "aspectRatio" in mod && mod.aspectRatio != null;
134
+ const hasWidth = "width" in mod && mod.width;
135
+ const hasHeight = "height" in mod && mod.height;
136
+ if (hasWidth) {
137
+ const w = resolveSize(mod.width);
138
+ if (w) s.width = w;
139
+ }
140
+ if (hasHeight && !(hasAspectRatio && hasWidth)) {
141
+ const h = resolveSize(mod.height);
142
+ if (h) s.height = h;
143
+ }
144
+ if (hasAspectRatio) {
145
+ s.aspectRatio = `${mod.aspectRatio}`;
146
+ }
147
+ if ("padding" in mod && mod.padding) {
148
+ const p = resolveSpacing(mod.padding);
149
+ if (p.top) s.paddingTop = p.top;
150
+ if (p.bottom) s.paddingBottom = p.bottom;
151
+ if (p.left) s.paddingLeft = p.left;
152
+ if (p.right) s.paddingRight = p.right;
153
+ }
154
+ if ("margin" in mod && mod.margin) {
155
+ const m = resolveSpacing(mod.margin);
156
+ if (m.top) s.marginTop = m.top;
157
+ if (m.bottom) s.marginBottom = m.bottom;
158
+ if (m.left) s.marginLeft = m.left;
159
+ if (m.right) s.marginRight = m.right;
160
+ }
161
+ if ("backgroundColor" in mod && mod.backgroundColor) {
162
+ applyBackground(s, mod.backgroundColor, theme);
163
+ }
164
+ }
165
+ function applyBackground(s, bg, theme) {
166
+ if (!bg.colors || bg.colors.length === 0) return;
167
+ const colors = bg.colors.map((c) => resolveColor(c, theme)).filter(Boolean);
168
+ if (colors.length === 0) return;
169
+ switch (bg.type) {
170
+ case "single":
171
+ s.backgroundColor = colors[0];
172
+ break;
173
+ case "vertical_gradient":
174
+ s.background = `linear-gradient(to bottom, ${colors.join(", ")})`;
175
+ break;
176
+ case "horizontal_gradient":
177
+ s.background = `linear-gradient(to right, ${colors.join(", ")})`;
178
+ break;
179
+ case "linear_gradient":
180
+ s.background = `linear-gradient(${colors.join(", ")})`;
181
+ break;
182
+ default:
183
+ if (colors.length === 1) {
184
+ s.backgroundColor = colors[0];
185
+ }
186
+ break;
187
+ }
188
+ }
189
+
190
+ // src/components/text.ts
191
+ function renderText(component, theme) {
192
+ const props = component.properties;
193
+ const el = document.createElement("span");
194
+ el.textContent = props?.text ?? "";
195
+ if (props?.fontSize != null) {
196
+ el.style.fontSize = `${props.fontSize}px`;
197
+ }
198
+ if (props?.color) {
199
+ const c = resolveColor(props.color, theme);
200
+ if (c) el.style.color = c;
201
+ }
202
+ if (props?.fontWeight) {
203
+ switch (props.fontWeight) {
204
+ case "bold":
205
+ el.style.fontWeight = "700";
206
+ break;
207
+ case "semi_bold":
208
+ el.style.fontWeight = "600";
209
+ break;
210
+ case "medium":
211
+ el.style.fontWeight = "500";
212
+ break;
213
+ case "normal":
214
+ el.style.fontWeight = "400";
215
+ break;
216
+ }
217
+ }
218
+ if (props?.textAlign) {
219
+ el.style.display = "block";
220
+ switch (props.textAlign) {
221
+ case "left":
222
+ case "start":
223
+ el.style.textAlign = "left";
224
+ break;
225
+ case "right":
226
+ case "end":
227
+ el.style.textAlign = "right";
228
+ break;
229
+ case "center":
230
+ el.style.textAlign = "center";
231
+ break;
232
+ case "justify":
233
+ el.style.textAlign = "justify";
234
+ break;
235
+ }
236
+ }
237
+ if (props?.overflow === "ellipsis") {
238
+ el.style.textOverflow = "ellipsis";
239
+ el.style.overflow = "hidden";
240
+ el.style.whiteSpace = "nowrap";
241
+ }
242
+ if (props?.maxLines != null) {
243
+ el.style.display = "-webkit-box";
244
+ el.style.webkitLineClamp = `${props.maxLines}`;
245
+ el.style["-webkit-box-orient"] = "vertical";
246
+ el.style.overflow = "hidden";
247
+ }
248
+ if (props?.minLines != null) {
249
+ el.style.minHeight = `${props.minLines * (props.fontSize ?? 16) * 1.4}px`;
250
+ }
251
+ applyModifier(el, props?.modifier, theme);
252
+ return el;
253
+ }
254
+
255
+ // src/components/image.ts
256
+ function renderImage(component, theme) {
257
+ const props = component.properties;
258
+ const el = document.createElement("img");
259
+ el.style.display = "block";
260
+ el.style.boxSizing = "border-box";
261
+ if (props?.image) {
262
+ el.src = props.image;
263
+ }
264
+ if (props?.contentDescription) {
265
+ el.alt = props.contentDescription;
266
+ }
267
+ switch (props?.scaleType) {
268
+ case "crop":
269
+ el.style.objectFit = "cover";
270
+ break;
271
+ case "fill_bounds":
272
+ el.style.objectFit = "fill";
273
+ break;
274
+ case "fill_width":
275
+ el.style.objectFit = "cover";
276
+ el.style.width = "100%";
277
+ break;
278
+ case "fill_height":
279
+ el.style.objectFit = "cover";
280
+ el.style.height = "100%";
281
+ break;
282
+ case "none":
283
+ el.style.objectFit = "none";
284
+ break;
285
+ default:
286
+ el.style.objectFit = "contain";
287
+ break;
288
+ }
289
+ if (props?.tintColor) {
290
+ const c = resolveColor(props.tintColor, theme);
291
+ if (c) {
292
+ el.style.filter = `opacity(0.5) drop-shadow(0 0 0 ${c})`;
293
+ }
294
+ }
295
+ if (props?.aspectRatio != null) {
296
+ el.style.aspectRatio = `${props.aspectRatio}`;
297
+ }
298
+ applyModifier(el, props?.modifier, theme);
299
+ return el;
300
+ }
301
+
302
+ // src/components/box.ts
303
+ function renderBox(component, theme) {
304
+ const props = component.properties;
305
+ const el = document.createElement("div");
306
+ el.style.display = "grid";
307
+ el.style.position = "relative";
308
+ el.style.overflow = "hidden";
309
+ if (props?.contentAlignment) {
310
+ switch (props.contentAlignment) {
311
+ case "top_start":
312
+ el.style.justifyItems = "start";
313
+ el.style.alignItems = "start";
314
+ break;
315
+ case "top_center":
316
+ el.style.justifyItems = "center";
317
+ el.style.alignItems = "start";
318
+ break;
319
+ case "top_end":
320
+ el.style.justifyItems = "end";
321
+ el.style.alignItems = "start";
322
+ break;
323
+ case "center_start":
324
+ el.style.justifyItems = "start";
325
+ el.style.alignItems = "center";
326
+ break;
327
+ case "center":
328
+ el.style.justifyItems = "center";
329
+ el.style.alignItems = "center";
330
+ break;
331
+ case "center_end":
332
+ el.style.justifyItems = "end";
333
+ el.style.alignItems = "center";
334
+ break;
335
+ case "bottom_start":
336
+ el.style.justifyItems = "start";
337
+ el.style.alignItems = "end";
338
+ break;
339
+ case "bottom_center":
340
+ el.style.justifyItems = "center";
341
+ el.style.alignItems = "end";
342
+ break;
343
+ case "bottom_end":
344
+ el.style.justifyItems = "end";
345
+ el.style.alignItems = "end";
346
+ break;
347
+ }
348
+ }
349
+ applyModifier(el, props?.modifier, theme);
350
+ return el;
351
+ }
352
+
353
+ // src/components/column.ts
354
+ function renderColumn(component, theme) {
355
+ const props = component.properties;
356
+ const el = document.createElement("div");
357
+ el.style.display = "flex";
358
+ el.style.flexDirection = "column";
359
+ if (props?.verticalArrangement) {
360
+ const arr = props.verticalArrangement;
361
+ switch (arr.type) {
362
+ case "top":
363
+ el.style.justifyContent = "flex-start";
364
+ break;
365
+ case "center":
366
+ el.style.justifyContent = "center";
367
+ break;
368
+ case "bottom":
369
+ el.style.justifyContent = "flex-end";
370
+ break;
371
+ case "space_between":
372
+ el.style.justifyContent = "space-between";
373
+ break;
374
+ case "space_evenly":
375
+ el.style.justifyContent = "space-evenly";
376
+ break;
377
+ case "space_around":
378
+ el.style.justifyContent = "space-around";
379
+ break;
380
+ case "spaced_by":
381
+ el.style.justifyContent = "flex-start";
382
+ if (arr.spacing != null) {
383
+ el.style.gap = `${arr.spacing}px`;
384
+ }
385
+ break;
386
+ }
387
+ }
388
+ if (props?.horizontalAlignment) {
389
+ switch (props.horizontalAlignment) {
390
+ case "start":
391
+ el.style.alignItems = "flex-start";
392
+ break;
393
+ case "end":
394
+ el.style.alignItems = "flex-end";
395
+ break;
396
+ case "center_horizontally":
397
+ el.style.alignItems = "center";
398
+ break;
399
+ }
400
+ }
401
+ if (props?.canScroll) {
402
+ el.style.overflowY = "auto";
403
+ }
404
+ applyModifier(el, props?.modifier, theme);
405
+ return el;
406
+ }
407
+
408
+ // src/components/row.ts
409
+ function renderRow(component, theme) {
410
+ const props = component.properties;
411
+ const el = document.createElement("div");
412
+ el.style.display = "flex";
413
+ el.style.flexDirection = "row";
414
+ if (props?.horizontalArrangement) {
415
+ const arr = props.horizontalArrangement;
416
+ switch (arr.type) {
417
+ case "start":
418
+ el.style.justifyContent = "flex-start";
419
+ break;
420
+ case "center":
421
+ el.style.justifyContent = "center";
422
+ break;
423
+ case "end":
424
+ el.style.justifyContent = "flex-end";
425
+ break;
426
+ case "space_between":
427
+ el.style.justifyContent = "space-between";
428
+ break;
429
+ case "space_evenly":
430
+ el.style.justifyContent = "space-evenly";
431
+ break;
432
+ case "space_around":
433
+ el.style.justifyContent = "space-around";
434
+ break;
435
+ case "spaced_by":
436
+ el.style.justifyContent = "flex-start";
437
+ if (arr.spacing != null) {
438
+ el.style.gap = `${arr.spacing}px`;
439
+ }
440
+ break;
441
+ }
442
+ }
443
+ if (props?.verticalAlignment) {
444
+ switch (props.verticalAlignment) {
445
+ case "top":
446
+ el.style.alignItems = "flex-start";
447
+ break;
448
+ case "bottom":
449
+ el.style.alignItems = "flex-end";
450
+ break;
451
+ case "center_vertically":
452
+ el.style.alignItems = "center";
453
+ break;
454
+ }
455
+ }
456
+ if (props?.canScroll) {
457
+ el.style.overflowX = "auto";
458
+ }
459
+ applyModifier(el, props?.modifier, theme);
460
+ return el;
461
+ }
462
+
463
+ // src/components/lazy-column.ts
464
+ function renderLazyColumn(component, theme) {
465
+ const props = component.properties;
466
+ const el = document.createElement("div");
467
+ el.style.display = "flex";
468
+ el.style.flexDirection = "column";
469
+ el.style.overflowY = "auto";
470
+ if (props?.verticalArrangement) {
471
+ const arr = props.verticalArrangement;
472
+ switch (arr.type) {
473
+ case "top":
474
+ el.style.justifyContent = "flex-start";
475
+ break;
476
+ case "center":
477
+ el.style.justifyContent = "center";
478
+ break;
479
+ case "bottom":
480
+ el.style.justifyContent = "flex-end";
481
+ break;
482
+ case "space_between":
483
+ el.style.justifyContent = "space-between";
484
+ break;
485
+ case "space_evenly":
486
+ el.style.justifyContent = "space-evenly";
487
+ break;
488
+ case "space_around":
489
+ el.style.justifyContent = "space-around";
490
+ break;
491
+ case "spaced_by":
492
+ el.style.justifyContent = "flex-start";
493
+ if (arr.spacing != null) {
494
+ el.style.gap = `${arr.spacing}px`;
495
+ }
496
+ break;
497
+ }
498
+ }
499
+ applyModifier(el, props?.modifier, theme);
500
+ return el;
501
+ }
502
+
503
+ // src/components/index.ts
504
+ var renderers = {
505
+ text: renderText,
506
+ image: renderImage,
507
+ box: renderBox,
508
+ column: renderColumn,
509
+ row: renderRow,
510
+ lazy_column: renderLazyColumn
511
+ };
512
+ function getRenderer(type) {
513
+ return renderers[type];
514
+ }
515
+
516
+ // src/render.ts
517
+ function renderComponent(component, theme) {
518
+ const renderer = getRenderer(component.type);
519
+ if (!renderer) {
520
+ console.warn(`[sdui] Unknown component type: "${component.type}"`);
521
+ const el2 = document.createElement("div");
522
+ el2.dataset.sduiUnknown = component.type;
523
+ return el2;
524
+ }
525
+ const el = renderer(component, theme);
526
+ el.dataset.sduiType = component.type;
527
+ if (component.__builderId) {
528
+ el.dataset.sduiId = component.__builderId;
529
+ }
530
+ if (component.children && component.children.length > 0) {
531
+ for (const child of component.children) {
532
+ el.appendChild(renderComponent(child, theme));
533
+ }
534
+ }
535
+ return el;
536
+ }
537
+
538
+ // src/component-meta.ts
539
+ function sizeModelProperties() {
540
+ return [
541
+ { name: "value", label: "Value", type: "number" },
542
+ { name: "unit", label: "Unit", type: "enum", enumValues: ["dp", "match_parent", "wrap_content"] }
543
+ ];
544
+ }
545
+ function spacingModelProperties() {
546
+ return [
547
+ { name: "top", label: "Top", type: "number" },
548
+ { name: "bottom", label: "Bottom", type: "number" },
549
+ { name: "start", label: "Start", type: "number" },
550
+ { name: "end", label: "End", type: "number" }
551
+ ];
552
+ }
553
+ function colorModelProperties() {
554
+ return [
555
+ { name: "light", label: "Light", type: "color" },
556
+ { name: "dark", label: "Dark", type: "color" }
557
+ ];
558
+ }
559
+ function actionModelProperties() {
560
+ return [
561
+ { name: "type", label: "Action Type", type: "string" },
562
+ { name: "params", label: "Parameters", type: "object" }
563
+ ];
564
+ }
565
+ function backgroundColorModelProperties() {
566
+ return [
567
+ {
568
+ name: "type",
569
+ label: "Background Type",
570
+ type: "enum",
571
+ enumValues: ["single", "vertical_gradient", "horizontal_gradient", "linear_gradient"]
572
+ },
573
+ {
574
+ name: "colors",
575
+ label: "Colors",
576
+ type: "object",
577
+ isArray: true,
578
+ properties: colorModelProperties()
579
+ }
580
+ ];
581
+ }
582
+ function modifierBaseProperties() {
583
+ return [
584
+ {
585
+ name: "alignment",
586
+ label: "Alignment",
587
+ type: "enum",
588
+ enumValues: ["start", "end", "center", "center_horizontally", "center_vertically", "top", "bottom", "top_start", "top_center", "top_end", "center_start", "center_end", "bottom_start", "bottom_center", "bottom_end"]
589
+ },
590
+ { name: "weight", label: "Weight", type: "number" },
591
+ { name: "action", label: "Action", type: "object", properties: actionModelProperties() }
592
+ ];
593
+ }
594
+ function textModifierDescriptor() {
595
+ return {
596
+ name: "modifier",
597
+ label: "Modifier",
598
+ type: "object",
599
+ properties: [
600
+ ...modifierBaseProperties(),
601
+ { name: "width", label: "Width", type: "object", properties: sizeModelProperties() },
602
+ { name: "margin", label: "Margin", type: "object", properties: spacingModelProperties() }
603
+ ]
604
+ };
605
+ }
606
+ function imageModifierDescriptor() {
607
+ return {
608
+ name: "modifier",
609
+ label: "Modifier",
610
+ type: "object",
611
+ properties: [
612
+ ...modifierBaseProperties(),
613
+ { name: "width", label: "Width", type: "object", properties: sizeModelProperties() },
614
+ { name: "height", label: "Height", type: "object", properties: sizeModelProperties() },
615
+ { name: "padding", label: "Padding", type: "object", properties: spacingModelProperties() },
616
+ { name: "margin", label: "Margin", type: "object", properties: spacingModelProperties() }
617
+ ]
618
+ };
619
+ }
620
+ function containerModifierDescriptor() {
621
+ return {
622
+ name: "modifier",
623
+ label: "Modifier",
624
+ type: "object",
625
+ properties: [
626
+ ...modifierBaseProperties(),
627
+ { name: "backgroundColor", label: "Background Color", type: "object", properties: backgroundColorModelProperties() },
628
+ { name: "width", label: "Width", type: "object", properties: sizeModelProperties() },
629
+ { name: "height", label: "Height", type: "object", properties: sizeModelProperties() },
630
+ { name: "padding", label: "Padding", type: "object", properties: spacingModelProperties() },
631
+ { name: "margin", label: "Margin", type: "object", properties: spacingModelProperties() },
632
+ { name: "aspectRatio", label: "Aspect Ratio", type: "number" }
633
+ ]
634
+ };
635
+ }
636
+ function verticalArrangementDescriptor() {
637
+ return {
638
+ name: "verticalArrangement",
639
+ label: "Vertical Arrangement",
640
+ type: "object",
641
+ properties: [
642
+ {
643
+ name: "type",
644
+ label: "Type",
645
+ type: "enum",
646
+ enumValues: ["top", "center", "bottom", "space_evenly", "space_between", "space_around", "spaced_by"]
647
+ },
648
+ { name: "spacing", label: "Spacing", type: "number" }
649
+ ]
650
+ };
651
+ }
652
+ function horizontalArrangementDescriptor() {
653
+ return {
654
+ name: "horizontalArrangement",
655
+ label: "Horizontal Arrangement",
656
+ type: "object",
657
+ properties: [
658
+ {
659
+ name: "type",
660
+ label: "Type",
661
+ type: "enum",
662
+ enumValues: ["start", "center", "end", "space_between", "space_evenly", "space_around", "spaced_by"]
663
+ },
664
+ { name: "spacing", label: "Spacing", type: "number" }
665
+ ]
666
+ };
667
+ }
668
+ var COMPONENT_DEFINITIONS = [
669
+ {
670
+ type: "text",
671
+ label: "Text",
672
+ acceptsChildren: false,
673
+ properties: [
674
+ { name: "text", label: "Text", type: "string", required: true, defaultValue: "Text" },
675
+ { name: "fontSize", label: "Font Size", type: "number" },
676
+ { name: "color", label: "Color", type: "object", properties: colorModelProperties() },
677
+ { name: "fontWeight", label: "Font Weight", type: "enum", enumValues: ["bold", "medium", "normal", "semi_bold"] },
678
+ { name: "textAlign", label: "Text Align", type: "enum", enumValues: ["left", "right", "center", "justify", "start", "end", "unspecified"] },
679
+ { name: "overflow", label: "Overflow", type: "enum", enumValues: ["none", "ellipsis"] },
680
+ { name: "maxLines", label: "Max Lines", type: "number" },
681
+ { name: "minLines", label: "Min Lines", type: "number", defaultValue: 1 },
682
+ textModifierDescriptor()
683
+ ]
684
+ },
685
+ {
686
+ type: "image",
687
+ label: "Image",
688
+ acceptsChildren: false,
689
+ properties: [
690
+ { name: "image", label: "Image URL", type: "string", required: true },
691
+ { name: "scaleType", label: "Scale Type", type: "enum", enumValues: ["fit", "crop", "fill_height", "fill_width", "fill_bounds", "inside", "none"] },
692
+ { name: "contentDescription", label: "Content Description", type: "string" },
693
+ { name: "aspectRatio", label: "Aspect Ratio", type: "number" },
694
+ { name: "tintColor", label: "Tint Color", type: "object", properties: colorModelProperties() },
695
+ imageModifierDescriptor()
696
+ ]
697
+ },
698
+ {
699
+ type: "box",
700
+ label: "Box",
701
+ acceptsChildren: true,
702
+ properties: [
703
+ {
704
+ name: "contentAlignment",
705
+ label: "Content Alignment",
706
+ type: "enum",
707
+ enumValues: ["top_start", "top_center", "top_end", "center_start", "center", "center_end", "bottom_start", "bottom_center", "bottom_end"]
708
+ },
709
+ containerModifierDescriptor()
710
+ ]
711
+ },
712
+ {
713
+ type: "column",
714
+ label: "Column",
715
+ acceptsChildren: true,
716
+ properties: [
717
+ containerModifierDescriptor(),
718
+ verticalArrangementDescriptor(),
719
+ { name: "horizontalAlignment", label: "Horizontal Alignment", type: "enum", enumValues: ["start", "end", "center_horizontally"] },
720
+ { name: "canScroll", label: "Can Scroll", type: "boolean" }
721
+ ]
722
+ },
723
+ {
724
+ type: "row",
725
+ label: "Row",
726
+ acceptsChildren: true,
727
+ properties: [
728
+ containerModifierDescriptor(),
729
+ { name: "verticalAlignment", label: "Vertical Alignment", type: "enum", enumValues: ["top", "bottom", "center_vertically"] },
730
+ horizontalArrangementDescriptor(),
731
+ { name: "canScroll", label: "Can Scroll", type: "boolean" }
732
+ ]
733
+ },
734
+ {
735
+ type: "lazy_column",
736
+ label: "Lazy Column",
737
+ acceptsChildren: true,
738
+ properties: [
739
+ containerModifierDescriptor(),
740
+ verticalArrangementDescriptor()
741
+ ]
742
+ }
743
+ ];
744
+
745
+ // src/index.ts
746
+ function render(json, container, options) {
747
+ const screen = JSON.parse(json);
748
+ const theme = options?.theme ?? "light";
749
+ const root = renderComponent(screen.root, theme);
750
+ container.innerHTML = "";
751
+ container.appendChild(root);
752
+ }
753
+
754
+ // src/react/SduiRenderer.tsx
755
+ var import_jsx_runtime = require("react/jsx-runtime");
756
+ function SduiRenderer({
757
+ json,
758
+ theme = "light",
759
+ deviceWidth,
760
+ deviceHeight,
761
+ filePreviewMap,
762
+ selectedId,
763
+ onClickNode,
764
+ onClickOutside,
765
+ className,
766
+ style
767
+ }) {
768
+ const renderRef = (0, import_react.useRef)(null);
769
+ const wrapperRef = (0, import_react.useRef)(null);
770
+ const [containerSize, setContainerSize] = (0, import_react.useState)({ w: 0, h: 0 });
771
+ const [error, setError] = (0, import_react.useState)(null);
772
+ (0, import_react.useEffect)(() => {
773
+ const el = wrapperRef.current;
774
+ if (!el) return;
775
+ const ro = new ResizeObserver((entries) => {
776
+ const { width, height } = entries[0].contentRect;
777
+ setContainerSize({ w: width, h: height });
778
+ });
779
+ ro.observe(el);
780
+ return () => ro.disconnect();
781
+ }, []);
782
+ (0, import_react.useEffect)(() => {
783
+ const el = renderRef.current;
784
+ if (!el) return;
785
+ try {
786
+ let renderJson = json;
787
+ if (filePreviewMap) {
788
+ for (const [fileName, blobUrl] of Object.entries(filePreviewMap)) {
789
+ renderJson = renderJson.replaceAll(JSON.stringify(fileName), JSON.stringify(blobUrl));
790
+ }
791
+ }
792
+ JSON.parse(renderJson);
793
+ setError(null);
794
+ render(renderJson, el, { theme });
795
+ } catch (err) {
796
+ setError(err instanceof Error ? err.message : "Render error");
797
+ el.innerHTML = "";
798
+ }
799
+ }, [json, theme, filePreviewMap]);
800
+ (0, import_react.useEffect)(() => {
801
+ const el = renderRef.current;
802
+ if (!el) return;
803
+ el.querySelectorAll("[data-sdui-selected]").forEach((node) => {
804
+ node.removeAttribute("data-sdui-selected");
805
+ });
806
+ if (selectedId) {
807
+ const selected = el.querySelector(`[data-sdui-id="${selectedId}"]`);
808
+ if (selected) {
809
+ selected.setAttribute("data-sdui-selected", "true");
810
+ }
811
+ }
812
+ }, [selectedId, json]);
813
+ (0, import_react.useEffect)(() => {
814
+ const el = renderRef.current;
815
+ if (!el || !onClickNode) return;
816
+ function handleClick(e) {
817
+ e.stopPropagation();
818
+ const target = e.target.closest("[data-sdui-id]");
819
+ if (target) {
820
+ onClickNode(target.dataset.sduiId);
821
+ }
822
+ }
823
+ el.addEventListener("click", handleClick);
824
+ return () => el.removeEventListener("click", handleClick);
825
+ }, [onClickNode]);
826
+ const padding = 48;
827
+ const totalH = deviceHeight + 10;
828
+ const scaleW = containerSize.w > 0 && deviceWidth > containerSize.w - padding ? (containerSize.w - padding) / deviceWidth : 1;
829
+ const scaleH = containerSize.h > 0 && totalH > containerSize.h - padding ? (containerSize.h - padding) / totalH : 1;
830
+ const scale = Math.min(scaleW, scaleH, 1);
831
+ let rootScrollable = false;
832
+ try {
833
+ const parsed = JSON.parse(json);
834
+ const root = parsed.root;
835
+ if (root) {
836
+ rootScrollable = root.type === "lazy_column" || root.properties?.canScroll === true;
837
+ }
838
+ } catch {
839
+ }
840
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
841
+ "div",
842
+ {
843
+ ref: wrapperRef,
844
+ className,
845
+ style: {
846
+ flex: 1,
847
+ overflow: "hidden",
848
+ display: "flex",
849
+ justifyContent: "center",
850
+ alignItems: "flex-start",
851
+ padding: "24px 16px",
852
+ ...style
853
+ },
854
+ onClick: onClickOutside,
855
+ children: [
856
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
857
+ "div",
858
+ {
859
+ onClick: (e) => e.stopPropagation(),
860
+ style: {
861
+ position: "relative",
862
+ alignSelf: "flex-start",
863
+ width: `${deviceWidth}px`,
864
+ transform: scale < 1 ? `scale(${scale})` : void 0,
865
+ transformOrigin: "top center"
866
+ },
867
+ children: [
868
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
869
+ "div",
870
+ {
871
+ style: {
872
+ borderRadius: "36px",
873
+ padding: "3px",
874
+ background: "linear-gradient(145deg, rgba(255,255,255,0.12), rgba(255,255,255,0.04))",
875
+ boxShadow: "0 24px 80px rgba(0,0,0,0.5), 0 8px 32px rgba(0,0,0,0.3), inset 0 1px 0 rgba(255,255,255,0.06)"
876
+ },
877
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { borderRadius: "33px", overflow: "hidden", background: "#1c1c1e" }, children: [
878
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", padding: "8px 24px", height: "28px" }, children: [
879
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: "11px", fontWeight: 600, color: "rgba(255,255,255,0.7)", fontFamily: "system-ui" }, children: "9:41" }),
880
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "6px" }, children: [
881
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "14", height: "10", viewBox: "0 0 14 10", fill: "none", children: [
882
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "0", y: "6", width: "2.5", height: "4", rx: "0.5", fill: "rgba(255,255,255,0.5)" }),
883
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "3.8", y: "4", width: "2.5", height: "6", rx: "0.5", fill: "rgba(255,255,255,0.5)" }),
884
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "7.6", y: "2", width: "2.5", height: "8", rx: "0.5", fill: "rgba(255,255,255,0.5)" }),
885
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "11.4", y: "0", width: "2.5", height: "10", rx: "0.5", fill: "rgba(255,255,255,0.5)" })
886
+ ] }),
887
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "20", height: "10", viewBox: "0 0 20 10", fill: "none", children: [
888
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "0.5", y: "0.5", width: "16", height: "9", rx: "2", stroke: "rgba(255,255,255,0.4)", strokeWidth: "1" }),
889
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "17", y: "3", width: "2", height: "4", rx: "0.5", fill: "rgba(255,255,255,0.3)" }),
890
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "1.5", y: "1.5", width: "14", height: "7", rx: "1", fill: "rgba(255,255,255,0.5)" })
891
+ ] })
892
+ ] })
893
+ ] }),
894
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", justifyContent: "center", marginBottom: "4px" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { width: "120px", height: "28px", background: "#000", borderRadius: "14px" } }) }),
895
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
896
+ "div",
897
+ {
898
+ ref: renderRef,
899
+ style: {
900
+ background: theme === "dark" ? "#121212" : "#ffffff",
901
+ color: theme === "dark" ? "#ffffff" : "#000000",
902
+ height: `${deviceHeight - 60}px`,
903
+ borderBottomLeftRadius: "33px",
904
+ borderBottomRightRadius: "33px",
905
+ overflow: rootScrollable ? "auto" : "hidden"
906
+ }
907
+ }
908
+ )
909
+ ] })
910
+ }
911
+ ),
912
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", justifyContent: "center", marginTop: "-18px", position: "relative", zIndex: 10 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { width: "120px", height: "4px", background: "rgba(255,255,255,0.2)", borderRadius: "2px" } }) })
913
+ ]
914
+ }
915
+ ),
916
+ error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
917
+ position: "absolute",
918
+ bottom: "24px",
919
+ left: "50%",
920
+ transform: "translateX(-50%)",
921
+ width: "100%",
922
+ maxWidth: "375px",
923
+ padding: "12px",
924
+ borderRadius: "8px",
925
+ background: "rgba(239,68,68,0.1)",
926
+ border: "1px solid rgba(239,68,68,0.2)"
927
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { fontSize: "13px", fontFamily: "monospace", color: "#ef4444", wordBreak: "break-all" }, children: error }) })
928
+ ]
929
+ }
930
+ );
931
+ }
932
+ // Annotate the CommonJS export names for ESM import in node:
933
+ 0 && (module.exports = {
934
+ SduiRenderer
935
+ });
936
+ //# sourceMappingURL=index.cjs.map