@esphome/compose-ui 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/dist/index.mjs ADDED
@@ -0,0 +1,632 @@
1
+ // src/theme/context.ts
2
+ import { createContext, useContext, createContextProvider } from "@esphome/compose";
3
+
4
+ // src/theme/dark.ts
5
+ var darkTheme = {
6
+ name: "Dark",
7
+ colors: {
8
+ primary: { bg: "#1E88E5", text: "#FFFFFF", bgPressed: "#1565C0" },
9
+ secondary: { bg: "#546E7A", text: "#FFFFFF", bgPressed: "#37474F" },
10
+ success: { bg: "#43A047", text: "#FFFFFF", bgPressed: "#2E7D32" },
11
+ warning: { bg: "#FB8C00", text: "#000000", bgPressed: "#E65100" },
12
+ danger: { bg: "#E53935", text: "#FFFFFF", bgPressed: "#C62828" },
13
+ background: "#121212",
14
+ surface: "#1E1E1E",
15
+ surfaceAlt: "#2C2C2C",
16
+ border: "#3A3A3A",
17
+ textPrimary: "#E0E0E0",
18
+ textSecondary: "#9E9E9E",
19
+ textDisabled: "#616161"
20
+ },
21
+ typography: {
22
+ title: { fontFamily: "montserrat", fontSize: 28 },
23
+ subtitle: { fontFamily: "montserrat", fontSize: 20 },
24
+ body: { fontFamily: "montserrat", fontSize: 16 },
25
+ caption: { fontFamily: "montserrat", fontSize: 12 }
26
+ },
27
+ spacing: {
28
+ none: 0,
29
+ xs: 4,
30
+ sm: 8,
31
+ md: 16,
32
+ lg: 24,
33
+ xl: 32
34
+ },
35
+ radii: {
36
+ none: 0,
37
+ sm: 4,
38
+ md: 8,
39
+ lg: 16,
40
+ full: 9999
41
+ },
42
+ sizes: {
43
+ xs: { height: 28, fontSize: 12, paddingX: 8, paddingY: 4 },
44
+ sm: { height: 36, fontSize: 14, paddingX: 12, paddingY: 6 },
45
+ md: { height: 44, fontSize: 16, paddingX: 16, paddingY: 8 },
46
+ lg: { height: 52, fontSize: 18, paddingX: 20, paddingY: 10 },
47
+ xl: { height: 64, fontSize: 22, paddingX: 24, paddingY: 12 }
48
+ }
49
+ };
50
+
51
+ // src/theme/context.ts
52
+ var ThemeContext = createContext(darkTheme);
53
+ function useTheme() {
54
+ return useContext(ThemeContext);
55
+ }
56
+ var ThemeProvider = createContextProvider(ThemeContext);
57
+
58
+ // src/theme/light.ts
59
+ var lightTheme = {
60
+ name: "Light",
61
+ colors: {
62
+ primary: { bg: "#1565C0", text: "#FFFFFF", bgPressed: "#0D47A1" },
63
+ secondary: { bg: "#78909C", text: "#FFFFFF", bgPressed: "#546E7A" },
64
+ success: { bg: "#2E7D32", text: "#FFFFFF", bgPressed: "#1B5E20" },
65
+ warning: { bg: "#EF6C00", text: "#000000", bgPressed: "#E65100" },
66
+ danger: { bg: "#C62828", text: "#FFFFFF", bgPressed: "#B71C1C" },
67
+ background: "#FAFAFA",
68
+ surface: "#FFFFFF",
69
+ surfaceAlt: "#F5F5F5",
70
+ border: "#E0E0E0",
71
+ textPrimary: "#212121",
72
+ textSecondary: "#757575",
73
+ textDisabled: "#BDBDBD"
74
+ },
75
+ typography: {
76
+ title: { fontFamily: "montserrat", fontSize: 28 },
77
+ subtitle: { fontFamily: "montserrat", fontSize: 20 },
78
+ body: { fontFamily: "montserrat", fontSize: 16 },
79
+ caption: { fontFamily: "montserrat", fontSize: 12 }
80
+ },
81
+ spacing: {
82
+ none: 0,
83
+ xs: 4,
84
+ sm: 8,
85
+ md: 16,
86
+ lg: 24,
87
+ xl: 32
88
+ },
89
+ radii: {
90
+ none: 0,
91
+ sm: 4,
92
+ md: 8,
93
+ lg: 16,
94
+ full: 9999
95
+ },
96
+ sizes: {
97
+ xs: { height: 28, fontSize: 12, paddingX: 8, paddingY: 4 },
98
+ sm: { height: 36, fontSize: 14, paddingX: 12, paddingY: 6 },
99
+ md: { height: 44, fontSize: 16, paddingX: 16, paddingY: 8 },
100
+ lg: { height: 52, fontSize: 18, paddingX: 20, paddingY: 10 },
101
+ xl: { height: 64, fontSize: 22, paddingX: 24, paddingY: 12 }
102
+ }
103
+ };
104
+
105
+ // src/theme/json.ts
106
+ function themeToJSON(theme) {
107
+ return JSON.stringify(theme, null, 2);
108
+ }
109
+ function themeFromJSON(json) {
110
+ return JSON.parse(json);
111
+ }
112
+
113
+ // src/theme/resolvers.ts
114
+ function resolveSpacing(value) {
115
+ if (typeof value === "number") return value;
116
+ return useTheme().spacing[value];
117
+ }
118
+ function resolveSize(value) {
119
+ return useTheme().sizes[value];
120
+ }
121
+ function resolveStatus(value) {
122
+ return useTheme().colors[value];
123
+ }
124
+ function resolveTypography(variant) {
125
+ return useTheme().typography[variant];
126
+ }
127
+ function fontDefToLvgl(def) {
128
+ return `${def.fontFamily}_${def.fontSize}`;
129
+ }
130
+ function resolveRadius(value) {
131
+ if (typeof value === "number") return value;
132
+ return useTheme().radii[value];
133
+ }
134
+
135
+ // src/intents.ts
136
+ var COMPOSE_UI_INTENTS = {
137
+ /** Col can only be placed inside Row. */
138
+ COL: "compose-ui:col",
139
+ /** GridItem can only be placed inside Grid. */
140
+ GRID_ITEM: "compose-ui:grid-item"
141
+ };
142
+
143
+ // src/components/Screen.ts
144
+ import { createIntentComponent, LVGL_INTENTS } from "@esphome/compose";
145
+ var Screen = createIntentComponent(
146
+ (props) => {
147
+ const padding = props.padding != null ? resolveSpacing(props.padding) : void 0;
148
+ const theme = useTheme();
149
+ const bgColor = props.bgColor ?? theme.colors.background;
150
+ return {
151
+ type: "lvgl-page",
152
+ props: {
153
+ bgColor,
154
+ borderWidth: props.borderWidth ?? 0,
155
+ ...props.borderColor != null ? { borderColor: props.borderColor } : {},
156
+ ...padding != null ? { padAll: padding } : {},
157
+ ...props.skip != null ? { skip: props.skip } : {},
158
+ ...props.children ? { children: Array.isArray(props.children) ? props.children : [props.children] } : {}
159
+ }
160
+ };
161
+ },
162
+ {
163
+ intents: [LVGL_INTENTS.WIDGET],
164
+ allowedChildIntents: [LVGL_INTENTS.WIDGET]
165
+ }
166
+ );
167
+
168
+ // src/components/Space.ts
169
+ import { createIntentComponent as createIntentComponent2, LVGL_INTENTS as LVGL_INTENTS2 } from "@esphome/compose";
170
+ function buildFlexLayout(flow, gapKey, props) {
171
+ const gap = props.gap != null ? resolveSpacing(props.gap) : void 0;
172
+ return {
173
+ type: "flex",
174
+ flex_flow: flow,
175
+ ...props.align ? { flex_align_main: props.align } : {},
176
+ ...props.crossAlign ? { flex_align_cross: props.crossAlign } : {},
177
+ ...gap != null ? { [gapKey]: gap } : {}
178
+ };
179
+ }
180
+ function buildSpaceElement(props) {
181
+ const isRow = (props.direction ?? "vertical") === "horizontal";
182
+ const baseFlow = isRow ? "ROW" : "COLUMN";
183
+ const flow = props.wrap ? `${baseFlow}_WRAP` : baseFlow;
184
+ const gapKey = isRow ? "pad_column" : "pad_row";
185
+ const padding = props.padding != null ? resolveSpacing(props.padding) : void 0;
186
+ return {
187
+ type: "lvgl-obj",
188
+ props: {
189
+ ...props.width != null ? { width: props.width } : {},
190
+ ...props.height != null ? { height: props.height } : {},
191
+ ...padding != null ? { padAll: padding } : {},
192
+ ...props.bgColor != null ? { bgColor: props.bgColor } : {},
193
+ ...props.bgOpa != null ? { bgOpa: props.bgOpa } : { bgOpa: "TRANSP" },
194
+ ...props.radius != null ? { radius: props.radius } : {},
195
+ borderWidth: props.borderWidth ?? 0,
196
+ ...props.borderColor != null ? { borderColor: props.borderColor } : {},
197
+ "x:custom": {
198
+ layout: buildFlexLayout(flow, gapKey, props)
199
+ },
200
+ ...props.children ? { children: Array.isArray(props.children) ? props.children : [props.children] } : {}
201
+ }
202
+ };
203
+ }
204
+ var LAYOUT_INTENTS = {
205
+ intents: [LVGL_INTENTS2.WIDGET],
206
+ allowedChildIntents: [LVGL_INTENTS2.WIDGET],
207
+ contextTransparent: true
208
+ };
209
+ var Space = createIntentComponent2(
210
+ (props) => buildSpaceElement(props),
211
+ LAYOUT_INTENTS
212
+ );
213
+ var VStack = createIntentComponent2(
214
+ (props) => buildSpaceElement({ ...props, direction: "vertical" }),
215
+ LAYOUT_INTENTS
216
+ );
217
+ var HStack = createIntentComponent2(
218
+ (props) => buildSpaceElement({ ...props, direction: "horizontal" }),
219
+ LAYOUT_INTENTS
220
+ );
221
+
222
+ // src/components/Row.ts
223
+ import { createIntentComponent as createIntentComponent3, LVGL_INTENTS as LVGL_INTENTS3 } from "@esphome/compose";
224
+ var Row = createIntentComponent3(
225
+ (props) => {
226
+ const wrap = props.wrap !== false;
227
+ const flow = wrap ? "ROW_WRAP" : "ROW";
228
+ let padColumn;
229
+ let padRow;
230
+ if (Array.isArray(props.gutter)) {
231
+ padColumn = resolveSpacing(props.gutter[0]);
232
+ padRow = resolveSpacing(props.gutter[1]);
233
+ } else if (props.gutter != null) {
234
+ padColumn = resolveSpacing(props.gutter);
235
+ }
236
+ return {
237
+ type: "lvgl-obj",
238
+ props: {
239
+ width: props.width ?? "100%",
240
+ ...props.height != null ? { height: props.height } : {},
241
+ bgOpa: "TRANSP",
242
+ borderWidth: props.borderWidth ?? 0,
243
+ ...props.borderColor != null ? { borderColor: props.borderColor } : {},
244
+ "x:custom": {
245
+ layout: {
246
+ type: "flex",
247
+ flex_flow: flow,
248
+ ...props.justify ? { flex_align_main: props.justify } : {},
249
+ ...props.align ? { flex_align_cross: props.align } : {},
250
+ ...padColumn != null ? { pad_column: padColumn } : {},
251
+ ...padRow != null ? { pad_row: padRow } : {}
252
+ }
253
+ },
254
+ ...props.children ? { children: Array.isArray(props.children) ? props.children : [props.children] } : {}
255
+ }
256
+ };
257
+ },
258
+ {
259
+ intents: [LVGL_INTENTS3.WIDGET],
260
+ allowedChildIntents: [COMPOSE_UI_INTENTS.COL],
261
+ contextTransparent: true
262
+ }
263
+ );
264
+ var Col = createIntentComponent3(
265
+ (props) => {
266
+ const span = props.span ?? 1;
267
+ return {
268
+ type: "lvgl-obj",
269
+ props: {
270
+ bgOpa: "TRANSP",
271
+ borderWidth: props.borderWidth ?? 0,
272
+ ...props.borderColor != null ? { borderColor: props.borderColor } : {},
273
+ ...props.width != null ? { width: props.width } : {},
274
+ ...props.height != null ? { height: props.height } : {},
275
+ "x:custom": {
276
+ flex_grow: span
277
+ },
278
+ ...props.children ? { children: Array.isArray(props.children) ? props.children : [props.children] } : {}
279
+ }
280
+ };
281
+ },
282
+ {
283
+ intents: [COMPOSE_UI_INTENTS.COL, LVGL_INTENTS3.WIDGET],
284
+ allowedChildIntents: [LVGL_INTENTS3.WIDGET],
285
+ contextTransparent: true
286
+ }
287
+ );
288
+
289
+ // src/components/Grid.ts
290
+ import { createIntentComponent as createIntentComponent4, LVGL_INTENTS as LVGL_INTENTS4 } from "@esphome/compose";
291
+ var Grid = createIntentComponent4(
292
+ (props) => {
293
+ const colGap = props.columnGap != null ? resolveSpacing(props.columnGap) : props.gap != null ? resolveSpacing(props.gap) : void 0;
294
+ const rowGap = props.rowGap != null ? resolveSpacing(props.rowGap) : props.gap != null ? resolveSpacing(props.gap) : void 0;
295
+ return {
296
+ type: "lvgl-obj",
297
+ props: {
298
+ ...props.width != null ? { width: props.width } : {},
299
+ ...props.height != null ? { height: props.height } : {},
300
+ ...props.bgColor != null ? { bgColor: props.bgColor } : {},
301
+ ...props.bgOpa != null ? { bgOpa: props.bgOpa } : { bgOpa: "TRANSP" },
302
+ borderWidth: props.borderWidth ?? 0,
303
+ ...props.borderColor != null ? { borderColor: props.borderColor } : {},
304
+ "x:custom": {
305
+ layout: {
306
+ type: "grid",
307
+ grid_columns: props.columns,
308
+ grid_rows: props.rows,
309
+ ...colGap != null ? { pad_column: colGap } : {},
310
+ ...rowGap != null ? { pad_row: rowGap } : {},
311
+ ...props.alignColumns ? { grid_column_align: props.alignColumns } : {},
312
+ ...props.alignRows ? { grid_row_align: props.alignRows } : {}
313
+ }
314
+ },
315
+ ...props.children ? { children: Array.isArray(props.children) ? props.children : [props.children] } : {}
316
+ }
317
+ };
318
+ },
319
+ {
320
+ intents: [LVGL_INTENTS4.WIDGET],
321
+ allowedChildIntents: [COMPOSE_UI_INTENTS.GRID_ITEM],
322
+ contextTransparent: true
323
+ }
324
+ );
325
+ var GridItem = createIntentComponent4(
326
+ (props) => {
327
+ return {
328
+ type: "lvgl-obj",
329
+ props: {
330
+ bgOpa: "TRANSP",
331
+ borderWidth: props.borderWidth ?? 0,
332
+ ...props.borderColor != null ? { borderColor: props.borderColor } : {},
333
+ "x:custom": {
334
+ grid_cell_column_pos: props.col,
335
+ grid_cell_row_pos: props.row,
336
+ ...props.colSpan != null && props.colSpan !== 1 ? { grid_cell_column_span: props.colSpan } : {},
337
+ ...props.rowSpan != null && props.rowSpan !== 1 ? { grid_cell_row_span: props.rowSpan } : {},
338
+ ...props.colAlign ? { grid_cell_x_align: props.colAlign } : {},
339
+ ...props.rowAlign ? { grid_cell_y_align: props.rowAlign } : {}
340
+ },
341
+ ...props.children ? { children: Array.isArray(props.children) ? props.children : [props.children] } : {}
342
+ }
343
+ };
344
+ },
345
+ {
346
+ intents: [COMPOSE_UI_INTENTS.GRID_ITEM, LVGL_INTENTS4.WIDGET],
347
+ allowedChildIntents: [LVGL_INTENTS4.WIDGET],
348
+ contextTransparent: true
349
+ }
350
+ );
351
+
352
+ // src/components/Text.ts
353
+ import { createIntentComponent as createIntentComponent5, LVGL_INTENTS as LVGL_INTENTS5 } from "@esphome/compose";
354
+ var Text = createIntentComponent5(
355
+ (props) => {
356
+ const variant = props.variant ?? "body";
357
+ const fontDef = resolveTypography(variant);
358
+ const color = props.color ?? useTheme().colors.textPrimary;
359
+ return {
360
+ type: "lvgl-label",
361
+ props: {
362
+ ...props.text != null ? { text: props.text } : {},
363
+ textFont: fontDefToLvgl(fontDef),
364
+ ...props.align != null ? { textAlign: props.align } : {},
365
+ textColor: color,
366
+ ...props.longMode != null ? { longMode: props.longMode } : {},
367
+ ...props.x != null ? { x: props.x } : {},
368
+ ...props.y != null ? { y: props.y } : {},
369
+ ...props.width != null ? { width: props.width } : {}
370
+ }
371
+ };
372
+ },
373
+ {
374
+ intents: [LVGL_INTENTS5.WIDGET],
375
+ allowedChildIntents: []
376
+ }
377
+ );
378
+
379
+ // src/components/Button.ts
380
+ import { createIntentComponent as createIntentComponent6, LVGL_INTENTS as LVGL_INTENTS6 } from "@esphome/compose";
381
+ var Button = createIntentComponent6(
382
+ (props) => {
383
+ const status = props.status ?? "primary";
384
+ const size = props.size ?? "md";
385
+ const variant = props.variant ?? "solid";
386
+ const colors = resolveStatus(status);
387
+ const dims = resolveSize(size);
388
+ const theme = useTheme();
389
+ const isSolid = variant === "solid";
390
+ const textFont = fontDefToLvgl({ fontFamily: theme.typography.body.fontFamily, fontSize: dims.fontSize });
391
+ const buttonProps = {
392
+ width: props.width ?? dims.paddingX * 2 + 80,
393
+ height: props.height ?? dims.height,
394
+ ...props.x != null ? { x: props.x } : {},
395
+ ...props.y != null ? { y: props.y } : {},
396
+ ...isSolid ? {
397
+ bgColor: colors.bg,
398
+ pressed: { bgColor: colors.bgPressed }
399
+ } : {
400
+ bgOpa: "TRANSP",
401
+ borderColor: colors.bg,
402
+ borderWidth: 2,
403
+ pressed: { bgColor: colors.bg, bgOpa: "COVER" }
404
+ },
405
+ ...props.onPress != null ? { "x:custom": { on_press: props.onPress } } : {}
406
+ };
407
+ const label = {
408
+ type: "lvgl-label",
409
+ props: {
410
+ text: props.text ?? "",
411
+ textColor: isSolid ? colors.text : colors.bg,
412
+ textFont,
413
+ align: "CENTER"
414
+ }
415
+ };
416
+ return {
417
+ type: "lvgl-button",
418
+ props: {
419
+ ...buttonProps,
420
+ children: [label]
421
+ }
422
+ };
423
+ },
424
+ {
425
+ intents: [LVGL_INTENTS6.WIDGET],
426
+ allowedChildIntents: []
427
+ }
428
+ );
429
+
430
+ // src/components/Card.ts
431
+ import { createIntentComponent as createIntentComponent7, LVGL_INTENTS as LVGL_INTENTS7 } from "@esphome/compose";
432
+ var Card = createIntentComponent7(
433
+ (props) => {
434
+ const padding = resolveSpacing(props.padding ?? "md");
435
+ const radius = resolveRadius(props.radius ?? "md");
436
+ const theme = useTheme();
437
+ const gap = props.gap != null ? resolveSpacing(props.gap) : void 0;
438
+ return {
439
+ type: "lvgl-obj",
440
+ props: {
441
+ padAll: padding,
442
+ radius,
443
+ bgColor: props.bgColor ?? theme.colors.surfaceAlt,
444
+ ...props.borderColor != null ? { borderColor: props.borderColor } : {},
445
+ borderWidth: props.borderWidth ?? 0,
446
+ ...props.width != null ? { width: props.width } : {},
447
+ ...props.height != null ? { height: props.height } : {},
448
+ "x:custom": {
449
+ layout: {
450
+ type: "flex",
451
+ flex_flow: "COLUMN",
452
+ ...gap != null ? { pad_row: gap } : {}
453
+ }
454
+ },
455
+ ...props.children ? { children: Array.isArray(props.children) ? props.children : [props.children] } : {}
456
+ }
457
+ };
458
+ },
459
+ {
460
+ intents: [LVGL_INTENTS7.WIDGET],
461
+ allowedChildIntents: [LVGL_INTENTS7.WIDGET],
462
+ contextTransparent: true
463
+ }
464
+ );
465
+
466
+ // src/components/SliderField.ts
467
+ import { createIntentComponent as createIntentComponent8, LVGL_INTENTS as LVGL_INTENTS8 } from "@esphome/compose";
468
+ var SliderField = createIntentComponent8(
469
+ (props) => {
470
+ const theme = useTheme();
471
+ const gap = props.gap != null ? resolveSpacing(props.gap) : void 0;
472
+ const label = {
473
+ type: "lvgl-label",
474
+ props: {
475
+ text: props.label,
476
+ textFont: fontDefToLvgl(theme.typography.body),
477
+ textColor: theme.colors.textPrimary
478
+ }
479
+ };
480
+ const sliderProps = {
481
+ ...props.min != null ? { minValue: props.min } : {},
482
+ ...props.max != null ? { maxValue: props.max } : {},
483
+ ...props.value != null ? { value: props.value } : {},
484
+ ...props.onChange != null ? { "x:custom": { on_change: props.onChange } } : {}
485
+ };
486
+ const slider = {
487
+ type: "lvgl-slider",
488
+ props: sliderProps
489
+ };
490
+ return {
491
+ type: "lvgl-obj",
492
+ props: {
493
+ bgOpa: "TRANSP",
494
+ ...props.width != null ? { width: props.width } : { width: "100%" },
495
+ height: "SIZE_CONTENT",
496
+ "x:custom": {
497
+ layout: {
498
+ type: "flex",
499
+ flex_flow: "COLUMN",
500
+ ...gap != null ? { pad_row: gap } : {}
501
+ }
502
+ },
503
+ children: [label, slider]
504
+ }
505
+ };
506
+ },
507
+ {
508
+ intents: [LVGL_INTENTS8.WIDGET],
509
+ allowedChildIntents: []
510
+ }
511
+ );
512
+
513
+ // src/components/SwitchField.ts
514
+ import { createIntentComponent as createIntentComponent9, LVGL_INTENTS as LVGL_INTENTS9 } from "@esphome/compose";
515
+ var SwitchField = createIntentComponent9(
516
+ (props) => {
517
+ const theme = useTheme();
518
+ const label = {
519
+ type: "lvgl-label",
520
+ props: {
521
+ text: props.label,
522
+ textFont: fontDefToLvgl(theme.typography.body),
523
+ textColor: theme.colors.textPrimary
524
+ }
525
+ };
526
+ const switchProps = {
527
+ ...props.value != null ? { "x:custom": { value: props.value } } : {},
528
+ ...props.onChange != null ? { "x:custom": { on_change: props.onChange } } : {}
529
+ };
530
+ const switchEl = {
531
+ type: "lvgl-switch",
532
+ props: switchProps
533
+ };
534
+ return {
535
+ type: "lvgl-obj",
536
+ props: {
537
+ bgOpa: "TRANSP",
538
+ ...props.width != null ? { width: props.width } : { width: "100%" },
539
+ height: "SIZE_CONTENT",
540
+ "x:custom": {
541
+ layout: {
542
+ type: "flex",
543
+ flex_flow: "ROW",
544
+ flex_align_main: "SPACE_BETWEEN",
545
+ flex_align_cross: "CENTER"
546
+ }
547
+ },
548
+ children: [label, switchEl]
549
+ }
550
+ };
551
+ },
552
+ {
553
+ intents: [LVGL_INTENTS9.WIDGET],
554
+ allowedChildIntents: []
555
+ }
556
+ );
557
+
558
+ // src/components/DropdownField.ts
559
+ import { createIntentComponent as createIntentComponent10, LVGL_INTENTS as LVGL_INTENTS10 } from "@esphome/compose";
560
+ var DropdownField = createIntentComponent10(
561
+ (props) => {
562
+ const theme = useTheme();
563
+ const gap = props.gap != null ? resolveSpacing(props.gap) : void 0;
564
+ const label = {
565
+ type: "lvgl-label",
566
+ props: {
567
+ text: props.label,
568
+ textFont: fontDefToLvgl(theme.typography.body),
569
+ textColor: theme.colors.textPrimary
570
+ }
571
+ };
572
+ const dropdownProps = {
573
+ options: props.options,
574
+ ...props.value != null ? { selected: props.value } : {},
575
+ ...props.onChange != null ? { "x:custom": { on_change: props.onChange } } : {}
576
+ };
577
+ const dropdown = {
578
+ type: "lvgl-dropdown",
579
+ props: dropdownProps
580
+ };
581
+ return {
582
+ type: "lvgl-obj",
583
+ props: {
584
+ bgOpa: "TRANSP",
585
+ ...props.width != null ? { width: props.width } : { width: "100%" },
586
+ height: "SIZE_CONTENT",
587
+ "x:custom": {
588
+ layout: {
589
+ type: "flex",
590
+ flex_flow: "COLUMN",
591
+ ...gap != null ? { pad_row: gap } : {}
592
+ }
593
+ },
594
+ children: [label, dropdown]
595
+ }
596
+ };
597
+ },
598
+ {
599
+ intents: [LVGL_INTENTS10.WIDGET],
600
+ allowedChildIntents: []
601
+ }
602
+ );
603
+ export {
604
+ Button,
605
+ COMPOSE_UI_INTENTS,
606
+ Card,
607
+ Col,
608
+ DropdownField,
609
+ Grid,
610
+ GridItem,
611
+ HStack,
612
+ Row,
613
+ Screen,
614
+ SliderField,
615
+ Space,
616
+ SwitchField,
617
+ Text,
618
+ ThemeContext,
619
+ ThemeProvider,
620
+ VStack,
621
+ darkTheme,
622
+ fontDefToLvgl,
623
+ lightTheme,
624
+ resolveRadius,
625
+ resolveSize,
626
+ resolveSpacing,
627
+ resolveStatus,
628
+ resolveTypography,
629
+ themeFromJSON,
630
+ themeToJSON,
631
+ useTheme
632
+ };
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@esphome/compose-ui",
3
+ "version": "0.1.0",
4
+ "description": "LVGL Design System components for ESPHome Compose",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "keywords": [
19
+ "esphome",
20
+ "lvgl",
21
+ "ui",
22
+ "design-system"
23
+ ],
24
+ "author": "",
25
+ "license": "MIT",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/xmlguy74/espcompose.git",
29
+ "directory": "packages/ui"
30
+ },
31
+ "engines": {
32
+ "node": ">=22"
33
+ },
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "dependencies": {
38
+ "@esphome/compose": "0.1.3"
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^22.0.0",
42
+ "tsup": "^8.0.0",
43
+ "tsx": "^4.0.0",
44
+ "typescript": "^5.4.0",
45
+ "rimraf": "^6.1.3",
46
+ "vitest": "^2.0.0"
47
+ },
48
+ "scripts": {
49
+ "build": "tsup src/index.ts --format cjs,esm --dts --tsconfig tsconfig.json",
50
+ "clean": "rimraf dist",
51
+ "lint": "eslint src",
52
+ "test": "vitest run --passWithNoTests"
53
+ }
54
+ }