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