create-mendix-widget-gleam 3.0.2 → 4.0.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.
@@ -1,4 +1,4 @@
1
- # glendix v3.0 — Agent Reference Guide
1
+ # glendix v4.0.2 — Agent Reference Guide
2
2
 
3
3
  > 이 문서는 AI 에이전트(LLM)가 glendix 코드를 작성할 때 참조하는 가이드입니다. 각 섹션은 독립적으로 읽을 수 있습니다.
4
4
 
@@ -14,9 +14,10 @@ glendix는 Gleam으로 Mendix Pluggable Widget을 작성하는 FFI 라이브러
14
14
  |--------|------------|-------------|
15
15
  | React 바인딩 (엘리먼트, 훅, 이벤트, HTML/SVG) | `redraw`, `redraw_dom` | 사용하지 않음 — 직접 import |
16
16
  | TEA 패턴 (Model-Update-View) | `lustre` | `glendix/lustre` 브릿지 제공 |
17
- | Mendix API (JsProps, EditableValue, ListValue 등) | `glendix` | 핵심 담당 |
18
- | 외부 JS 컴포넌트 (widget, binding) → React | `glendix/interop` | 브릿지 제공 |
19
- | 빌드/설치/마켓플레이스 | `glendix` | 핵심 담당 |
17
+ | Mendix API (JsProps, EditableValue, ListValue 등) | `mendraw` | 핵심 담당 |
18
+ | 외부 JS 컴포넌트 (widget, binding) → React | `mendraw/interop` | 브릿지 제공 |
19
+ | Mendix 위젯 (.mpk, Classic, 마켓플레이스) | `mendraw` | 핵심 담당 |
20
+ | 빌드/설치 | `glendix` | 핵심 담당 |
20
21
 
21
22
  **의존성 구조:**
22
23
 
@@ -25,13 +26,15 @@ glendix는 Gleam으로 Mendix Pluggable Widget을 작성하는 FFI 라이브러
25
26
  ├── redraw ← React 훅, 컴포넌트, fragment 등
26
27
  ├── redraw_dom ← HTML/SVG 태그, 속성, 이벤트
27
28
  ├── lustre ← TEA update/view (선택)
29
+ ├── mendraw
30
+ │ ├── mendix ← Mendix API 타입 + props 접근
31
+ │ ├── interop ← 외부 JS 컴포넌트 → redraw.Element
32
+ │ ├── widget ← .mpk 위젯 컴포넌트 (gleam.toml 자동 다운로드)
33
+ │ ├── classic ← Classic (Dojo) 위젯
34
+ │ └── marketplace ← Mendix Marketplace 검색/다운로드
28
35
  └── glendix
29
- ├── mendix ← Mendix API 타입 + props 접근
30
- ├── interop ← 외부 JS 컴포넌트 → redraw.Element
31
36
  ├── lustre ← Lustre Element → redraw.Element 브릿지
32
- ├── widget .mpk 위젯 컴포넌트
33
- ├── binding ← bindings.json 외부 React 컴포넌트
34
- ├── classic ← Classic (Dojo) 위젯
37
+ ├── binding 외부 React 컴포넌트 (gleam.toml [tools.glendix.bindings])
35
38
  └── js/* ← JS interop escape hatch
36
39
  ```
37
40
 
@@ -43,14 +46,15 @@ glendix는 Gleam으로 Mendix Pluggable Widget을 작성하는 FFI 라이브러
43
46
 
44
47
  ```toml
45
48
  [dependencies]
46
- glendix = ">= 3.0.2 and < 4.0.0"
49
+ glendix = ">= 4.0.2 and < 5.0.0"
50
+ mendraw = ">= 1.1.10 and < 2.0.0"
47
51
  ```
48
52
 
49
53
  Peer dependency (위젯 프로젝트 `package.json`):
50
54
 
51
55
  ```json
52
56
  {
53
- "dependencies": { "big.js": "^6.0.0" },
57
+ "dependencies": { "big.js": "^6.0.0" }, // decimal 속성 사용 시
54
58
  "overrides": { "react": "19.0.0", "react-dom": "19.0.0", "@types/react": "19.0.0", "@types/react-dom": "19.0.0" },
55
59
  "resolutions": { "react": "19.0.0", "react-dom": "19.0.0", "@types/react": "19.0.0", "@types/react-dom": "19.0.0" }
56
60
  }
@@ -60,7 +64,7 @@ Peer dependency (위젯 프로젝트 `package.json`):
60
64
  > - `overrides`/`resolutions`에서 반드시 **캐럿(`^`) 없이 정확한 버전**을 지정한다. `^19.0.0`은 react와 react-dom이 서로 다른 19.x.x로 해석되어 런타임 버전 불일치 에러를 일으킨다.
61
65
 
62
66
  ```bash
63
- gleam run -m glendix/install # 의존성 설치 + 바인딩 생성
67
+ gleam run -m glendix/install # 의존성 설치 + TOML 위젯 다운로드 + 바인딩 생성
64
68
  gleam build # 컴파일 확인
65
69
  ```
66
70
 
@@ -71,13 +75,13 @@ gleam build # 컴파일 확인
71
75
  모든 Mendix Pluggable Widget은 이 시그니처를 따릅니다:
72
76
 
73
77
  ```gleam
74
- import glendix/mendix.{type JsProps}
78
+ import mendraw/mendix.{type JsProps}
75
79
  import redraw.{type Element}
76
80
 
77
81
  pub fn widget(props: JsProps) -> Element
78
82
  ```
79
83
 
80
- - `JsProps` — Mendix가 전달하는 props 객체 (opaque). `glendix/mendix` 모듈의 접근자로만 읽는다.
84
+ - `JsProps` — Mendix가 전달하는 props 객체 (opaque). `mendraw/mendix` 모듈의 접근자로만 읽는다.
81
85
  - `Element` — redraw의 React 엘리먼트 타입. `redraw/dom/html`, `redraw.fragment()` 등으로 생성한다.
82
86
 
83
87
  ---
@@ -103,7 +107,7 @@ glendix는 두 가지 렌더링 경로를 지원합니다. 둘 다 `redraw.Eleme
103
107
  ### 5.1 필수 import 패턴
104
108
 
105
109
  ```gleam
106
- import glendix/mendix.{type JsProps} // Mendix props 타입
110
+ import mendraw/mendix.{type JsProps} // Mendix props 타입
107
111
  import redraw.{type Element} // 반환 타입
108
112
  import redraw/dom/html // HTML 태그 함수
109
113
  import redraw/dom/attribute // HTML 속성
@@ -311,7 +315,7 @@ svg.svg([attribute.attribute("viewBox", "0 0 100 100")], [
311
315
  ```gleam
312
316
  import gleam/int
313
317
  import glendix/lustre as gl
314
- import glendix/mendix.{type JsProps}
318
+ import mendraw/mendix.{type JsProps}
315
319
  import lustre/effect
316
320
  import lustre/element/html
317
321
  import lustre/event
@@ -411,27 +415,24 @@ fn view(model: Model) {
411
415
 
412
416
  | 컴포넌트 출처 | 사용 모듈 | 예시 |
413
417
  |--------------|----------|------|
414
- | npm 패키지 (React 컴포넌트) | `glendix/binding` + `glendix/interop` | recharts, @mui |
415
- | `.mpk` Pluggable 위젯 | `glendix/widget` + `glendix/interop` | Switch.mpk, Badge.mpk |
416
- | `.mpk` Classic (Dojo) 위젯 | `glendix/classic` | CameraWidget.mpk |
418
+ | npm 패키지 (React 컴포넌트) | `glendix/binding` + `mendraw/interop` | recharts, @mui |
419
+ | `.mpk` Pluggable 위젯 (gleam.toml) | `mendraw/widget` + `mendraw/interop` | Switch, Badge |
420
+ | `.mpk` Classic (Dojo) 위젯 (gleam.toml) | `mendraw/classic` | CameraWidget |
417
421
 
418
422
  ### 7.2 외부 React 컴포넌트 (binding + interop)
419
423
 
420
- **설정:** `bindings.json` 작성 → `npm install 패키지명` → `gleam run -m glendix/install`
424
+ **설정:** `gleam.toml`에 바인딩 추가 → `npm install 패키지명` → `gleam run -m glendix/install`
421
425
 
422
- ```json
423
- {
424
- "recharts": {
425
- "components": ["PieChart", "Pie", "Cell", "Tooltip"]
426
- }
427
- }
426
+ ```toml
427
+ [tools.glendix.bindings]
428
+ recharts = ["PieChart", "Pie", "Cell", "Tooltip"]
428
429
  ```
429
430
 
430
431
  **Gleam 래퍼 작성:**
431
432
 
432
433
  ```gleam
433
434
  import glendix/binding
434
- import glendix/interop
435
+ import mendraw/interop
435
436
  import redraw.{type Element}
436
437
  import redraw/dom/attribute.{type Attribute}
437
438
 
@@ -456,15 +457,24 @@ pub fn tooltip(attrs: List(Attribute)) -> Element {
456
457
 
457
458
  ### 7.3 .mpk Pluggable 위젯 (widget + interop)
458
459
 
459
- **설정:** `.mpk`를 `widgets/`에 배치 `gleam run -m glendix/install`
460
+ `gleam.toml`에 위젯을 등록하고 `gleam run -m glendix/install`로 자동 다운로드합니다:
461
+
462
+ ```toml
463
+ [tools.mendraw.widgets.Charts]
464
+ version = "3.0.0"
465
+ # s3_id = "com/..." ← 있으면 인증 없이 직접 다운로드
466
+ ```
467
+
468
+ `build/widgets/`에 캐시하고 바인딩을 자동 생성합니다.
469
+ Marketplace TUI(`gleam run -m mendraw/marketplace`)에서 다운로드하면 gleam.toml에 자동 추가됩니다.
460
470
 
461
471
  자동 생성되는 `src/widgets/*.gleam`:
462
472
 
463
473
  ```gleam
464
- import glendix/interop
465
- import glendix/mendix
466
- import glendix/mendix.{type JsProps}
467
- import glendix/widget
474
+ import mendraw/interop
475
+ import mendraw/mendix
476
+ import mendraw/mendix.{type JsProps}
477
+ import mendraw/widget
468
478
  import redraw.{type Element}
469
479
  import redraw/dom/attribute
470
480
 
@@ -486,8 +496,8 @@ pub fn render(props: JsProps) -> Element {
486
496
  | `widget.action_prop(key, handler)` | ActionValue | 액션 콜백 (onClick 등) |
487
497
 
488
498
  ```gleam
489
- import glendix/widget
490
- import glendix/interop
499
+ import mendraw/widget
500
+ import mendraw/interop
491
501
 
492
502
  let comp = widget.component("Badge button")
493
503
  interop.component_el(comp, [
@@ -503,7 +513,7 @@ interop.component_el(comp, [
503
513
 
504
514
  ```gleam
505
515
  import gleam/dynamic
506
- import glendix/classic
516
+ import mendraw/classic
507
517
 
508
518
  classic.render("CameraWidget.widget.CameraWidget", [
509
519
  #("mfToExecute", classic.to_dynamic(mf_value)),
@@ -517,12 +527,12 @@ classic.render("CameraWidget.widget.CameraWidget", [
517
527
 
518
528
  ## 8. Mendix API 레퍼런스
519
529
 
520
- ### 8.1 Props 접근 (`glendix/mendix`)
530
+ ### 8.1 Props 접근 (`mendraw/mendix`)
521
531
 
522
532
  `JsProps`는 opaque 타입입니다. 접근자 함수로만 읽습니다.
523
533
 
524
534
  ```gleam
525
- import glendix/mendix
535
+ import mendraw/mendix
526
536
 
527
537
  // Option 반환 (undefined → None)
528
538
  mendix.get_prop(props, "myAttr") // Option(a)
@@ -542,7 +552,7 @@ mendix.has_prop(props, "onClick") // Bool
542
552
  Mendix의 모든 동적 값은 상태를 가집니다:
543
553
 
544
554
  ```gleam
545
- import glendix/mendix.{Available, Loading, Unavailable}
555
+ import mendraw/mendix.{Available, Loading, Unavailable}
546
556
 
547
557
  case mendix.get_status(some_value) {
548
558
  Available -> // 값 사용 가능
@@ -551,12 +561,12 @@ case mendix.get_status(some_value) {
551
561
  }
552
562
  ```
553
563
 
554
- ### 8.3 EditableValue (`glendix/mendix/editable_value`)
564
+ ### 8.3 EditableValue (`mendraw/mendix/editable_value`)
555
565
 
556
566
  텍스트, 숫자, 날짜 등 편집 가능한 Mendix 속성:
557
567
 
558
568
  ```gleam
559
- import glendix/mendix/editable_value as ev
569
+ import mendraw/mendix/editable_value as ev
560
570
 
561
571
  // 읽기
562
572
  ev.value(attr) // Option(a)
@@ -581,12 +591,12 @@ ev.set_validator(attr, Some(fn(value) {
581
591
  ev.universe(attr) // Option(List(a))
582
592
  ```
583
593
 
584
- ### 8.4 ActionValue (`glendix/mendix/action`)
594
+ ### 8.4 ActionValue (`mendraw/mendix/action`)
585
595
 
586
596
  Mendix 마이크로플로우/나노플로우 실행:
587
597
 
588
598
  ```gleam
589
- import glendix/mendix/action
599
+ import mendraw/mendix/action
590
600
 
591
601
  action.execute(my_action) // 직접 실행
592
602
  action.execute_if_can(my_action) // can_execute가 True일 때만
@@ -596,24 +606,24 @@ action.can_execute(my_action) // Bool
596
606
  action.is_executing(my_action) // Bool
597
607
  ```
598
608
 
599
- ### 8.5 DynamicValue (`glendix/mendix/dynamic_value`)
609
+ ### 8.5 DynamicValue (`mendraw/mendix/dynamic_value`)
600
610
 
601
611
  읽기 전용 표현식 속성:
602
612
 
603
613
  ```gleam
604
- import glendix/mendix/dynamic_value as dv
614
+ import mendraw/mendix/dynamic_value as dv
605
615
 
606
616
  dv.value(expr) // Option(a)
607
617
  dv.status(expr) // String
608
618
  dv.is_available(expr) // Bool
609
619
  ```
610
620
 
611
- ### 8.6 ListValue (`glendix/mendix/list_value`)
621
+ ### 8.6 ListValue (`mendraw/mendix/list_value`)
612
622
 
613
623
  Mendix 데이터 소스 리스트:
614
624
 
615
625
  ```gleam
616
- import glendix/mendix/list_value as lv
626
+ import mendraw/mendix/list_value as lv
617
627
 
618
628
  // 아이템 접근
619
629
  lv.items(list_val) // Option(List(ObjectItem))
@@ -641,12 +651,12 @@ lv.set_filter(list_val, None) // 필터 해제
641
651
  lv.reload(list_val)
642
652
  ```
643
653
 
644
- ### 8.7 ListAttribute (`glendix/mendix/list_attribute`)
654
+ ### 8.7 ListAttribute (`mendraw/mendix/list_attribute`)
645
655
 
646
656
  리스트의 각 아이템에서 속성/액션/위젯 추출:
647
657
 
648
658
  ```gleam
649
- import glendix/mendix/list_attribute as la
659
+ import mendraw/mendix/list_attribute as la
650
660
 
651
661
  la.get_attribute(attr, item) // EditableValue 반환
652
662
  la.get_action(action, item) // Option(ActionValue)
@@ -661,10 +671,10 @@ la.attr_type(attr) // "String", "Integer" 등
661
671
  la.attr_formatter(attr) // ValueFormatter
662
672
  ```
663
673
 
664
- ### 8.8 Selection (`glendix/mendix/selection`)
674
+ ### 8.8 Selection (`mendraw/mendix/selection`)
665
675
 
666
676
  ```gleam
667
- import glendix/mendix/selection
677
+ import mendraw/mendix/selection
668
678
 
669
679
  // 단일 선택
670
680
  selection.selection(single_sel) // Option(ObjectItem)
@@ -679,8 +689,8 @@ selection.set_selections(multi_sel, [item1, item2])
679
689
  ### 8.9 Reference / ReferenceSet
680
690
 
681
691
  ```gleam
682
- import glendix/mendix/reference as ref
683
- import glendix/mendix/reference_set as ref_set
692
+ import mendraw/mendix/reference as ref
693
+ import mendraw/mendix/reference_set as ref_set
684
694
 
685
695
  // 단일 참조
686
696
  ref.value(my_ref) // Option(a)
@@ -693,10 +703,10 @@ ref_set.value(my_ref_set) // Option(List(a))
693
703
  ref_set.set_value(my_ref_set, Some([item1, item2]))
694
704
  ```
695
705
 
696
- ### 8.10 Filter (`glendix/mendix/filter`)
706
+ ### 8.10 Filter (`mendraw/mendix/filter`)
697
707
 
698
708
  ```gleam
699
- import glendix/mendix/filter
709
+ import mendraw/mendix/filter
700
710
 
701
711
  // 비교 연산
702
712
  filter.equals(filter.attribute("Status"), filter.literal("Active"))
@@ -719,12 +729,12 @@ filter.literal(value) // 상수 값
719
729
  filter.empty() // null 비교용
720
730
  ```
721
731
 
722
- ### 8.11 날짜 (`glendix/mendix/date`)
732
+ ### 8.11 날짜 (`mendraw/mendix/date`)
723
733
 
724
734
  > Gleam month는 1-based (1~12), JS는 0-based. glendix가 자동 변환합니다.
725
735
 
726
736
  ```gleam
727
- import glendix/mendix/date
737
+ import mendraw/mendix/date
728
738
 
729
739
  date.now()
730
740
  date.from_iso("2024-03-15T10:30:00Z")
@@ -741,45 +751,74 @@ date.to_input_value(d) // "2024-03-15" (input[type="date"]용)
741
751
  date.from_input_value(s) // Option(JsDate)
742
752
  ```
743
753
 
744
- ### 8.12 Big (`glendix/mendix/big`)
754
+ ### 8.12 Decimal (`mendraw/mendix/decimal`)
745
755
 
746
- Big.js 래퍼. Mendix Decimal 타입 처리용:
756
+ Mendix Decimal 속성의 경계 변환 전용. Big.js 객체 Gleam 변환만 담당합니다.
757
+ 산술/비교 연산이 필요하면 위젯 프로젝트에서 [dee](https://hexdocs.pm/dee/) 패키지를 추가하세요.
747
758
 
748
759
  ```gleam
749
- import glendix/mendix/big
760
+ import mendraw/mendix/decimal
761
+
762
+ // 생성 (Gleam → Mendix Big.js)
763
+ decimal.from_string("123.456")
764
+ decimal.from_int(100)
765
+ decimal.from_float(3.14) // 정밀도 손실 주의
766
+
767
+ // 변환 (Mendix Big.js → Gleam)
768
+ decimal.to_string(d) // "123.456"
769
+ decimal.to_float(d) // 123.456 (정밀도 손실 가능)
770
+ decimal.to_int(d) // 123 (소수점 이하 버림)
771
+ decimal.to_fixed(d, 2) // "123.46"
772
+ ```
750
773
 
751
- big.from_string("123.456")
752
- big.from_int(100)
774
+ **dee와 함께 사용하는 패턴:**
753
775
 
754
- big.add(a, b) big.subtract(a, b)
755
- big.multiply(a, b) big.divide(a, b)
756
- big.absolute(a) big.negate(a)
776
+ ```gleam
777
+ import dee
778
+ import mendraw/mendix/decimal
779
+
780
+ // Mendix → dee (연산) → Mendix
781
+ let value = decimal.to_string(mendix_decimal) // Big.js → String
782
+ let result = dee.from_string(value) // String → dee
783
+ |> result.map(fn(d) { dee.add(d, dee.from_int(1)) })
784
+ |> result.map(dee.to_string) // dee → String
785
+ |> result.unwrap("0")
786
+ decimal.from_string(result) // String → Big.js
787
+ ```
757
788
 
758
- big.compare(a, b) // gleam/order.Order
759
- big.equal(a, b) // Bool
789
+ ### 8.12.1 조건부 CSS 클래스 (`cx`)
760
790
 
761
- big.to_string(a) big.to_float(a)
762
- big.to_int(a) big.to_fixed(a, 2)
791
+ `mendraw/mendix` 모듈의 `cx` 함수로 CSS 클래스를 조건부 조합합니다:
792
+
793
+ ```gleam
794
+ import mendraw/mendix
795
+
796
+ mendix.cx([
797
+ #("widget-container", True),
798
+ #("active", is_active),
799
+ #("disabled", !is_editable),
800
+ ])
801
+ // → "widget-container active" (is_active=True, is_editable=True일 때)
763
802
  ```
764
803
 
765
804
  ### 8.13 File, Icon, Formatter
766
805
 
767
806
  ```gleam
768
807
  // FileValue / WebImage
769
- import glendix/mendix/file
808
+ import mendraw/mendix/file
770
809
  file.uri(file_val) // String
771
810
  file.name(file_val) // Option(String)
772
811
  file.image_uri(img) // String
773
812
  file.alt_text(img) // Option(String)
774
813
 
775
814
  // WebIcon
776
- import glendix/mendix/icon
815
+ import mendraw/mendix/icon
777
816
  icon.icon_type(i) // Glyph | Image | IconFont
778
817
  icon.icon_class(i) // String
779
818
  icon.icon_url(i) // String
780
819
 
781
820
  // ValueFormatter
782
- import glendix/mendix/formatter
821
+ import mendraw/mendix/formatter
783
822
  formatter.format(fmt, Some(value)) // String
784
823
  formatter.parse(fmt, "123.45") // Result(Option(a), Nil)
785
824
  ```
@@ -794,8 +833,8 @@ Studio Pro의 editorConfig 로직을 Gleam으로 작성합니다.
794
833
 
795
834
  ```gleam
796
835
  import glendix/editor_config.{type Properties}
797
- import glendix/mendix
798
- import glendix/mendix.{type JsProps}
836
+ import mendraw/mendix
837
+ import mendraw/mendix.{type JsProps}
799
838
 
800
839
  const bar_keys = "barWidth,barColor"
801
840
  const line_keys = "lineStyle,lineCurve"
@@ -885,30 +924,93 @@ timer.clear_interval(id)
885
924
  | 명령어 | 설명 |
886
925
  |--------|------|
887
926
  | `gleam build` | 컴파일 |
888
- | `gleam run -m glendix/install` | 의존성 + 바인딩 + 위젯 .gleam 생성 |
927
+ | `gleam run -m glendix/install` | 의존성 + mendraw 위젯 다운로드 + 바인딩 + 위젯 .gleam 생성 |
889
928
  | `gleam run -m glendix/dev` | 개발 서버 (HMR) |
890
929
  | `gleam run -m glendix/build` | 프로덕션 빌드 (.mpk) |
891
930
  | `gleam run -m glendix/start` | Mendix 테스트 프로젝트 연동 |
892
931
  | `gleam run -m glendix/release` | 릴리즈 빌드 |
893
932
  | `gleam run -m glendix/lint` | ESLint 검사 |
894
933
  | `gleam run -m glendix/lint_fix` | ESLint 자동 수정 |
895
- | `gleam run -m glendix/marketplace` | Marketplace 위젯 다운로드 (인터랙티브) |
934
+ | `gleam run -m mendraw/marketplace` | Marketplace 위젯 다운로드 (인터랙티브, gleam.toml 자동 기록) |
896
935
  | `gleam run -m glendix/define` | 위젯 프로퍼티 정의 TUI 에디터 |
897
936
 
898
- **PM 자동 감지:** `pnpm-lock.yaml` → pnpm / `bun.lockb`·`bun.lock` → bun / 기본값 → npm
937
+ **PM 감지:** `gleam.toml`의 `[tools.glendix] pm = "pnpm"` 오버라이드 우선. 없으면 lock 파일 기반: `pnpm-lock.yaml` → pnpm / `bun.lockb`·`bun.lock` → bun / 기본값 → npm
938
+
939
+ ---
940
+
941
+ ## 12. 멀티 위젯 컴포넌트
942
+
943
+ 하나의 `.mpk` 파일에 여러 위젯 컴포넌트를 묶어서 개발할 수 있습니다 (예: Charts.mpk).
944
+
945
+ ### 12.1 설정
946
+
947
+ `package.json`에 `widgets` 맵을 추가합니다:
948
+
949
+ ```json
950
+ {
951
+ "widgetName": "AreaChart",
952
+ "widgets": {
953
+ "AreaChart": "area_chart",
954
+ "BarChart": "bar_chart",
955
+ "PieChart": "pie_chart"
956
+ }
957
+ }
958
+ ```
959
+
960
+ - `widgetName`: 기본 위젯 이름 (`pluggable-widgets-tools` 진입점)
961
+ - `widgets`: 컴포넌트명(PascalCase) → Gleam 함수명(snake_case) 맵
962
+ - `widgets` 미지정 시 기존 단일 위젯 동작 유지 (하위 호환)
963
+
964
+ ### 12.2 Gleam 코드
965
+
966
+ 메인 모듈에서 모든 위젯 함수를 export합니다:
967
+
968
+ ```gleam
969
+ import mendraw/mendix.{type JsProps}
970
+ import redraw.{type Element}
971
+
972
+ pub fn area_chart(props: JsProps) -> Element { ... }
973
+ pub fn bar_chart(props: JsProps) -> Element { ... }
974
+ pub fn pie_chart(props: JsProps) -> Element { ... }
975
+ ```
976
+
977
+ ### 12.3 CSS 전략
978
+
979
+ 위젯별 CSS 파일이 있으면 우선 사용, 없으면 공유 CSS 폴백:
980
+
981
+ 1. `src/ui/{ComponentName}.css` 존재 → 해당 파일 import
982
+ 2. 미존재 + `src/ui/{widgetName}.css` 존재 → 공유 CSS import
983
+ 3. 둘 다 미존재 → CSS import 생략
984
+
985
+ ### 12.4 Editor Config / Preview
986
+
987
+ 위젯별 파일 → 공유 파일 폴백:
988
+
989
+ 1. `src/{snake_fn}_editor_config.gleam` 존재 → 위젯별 editor config
990
+ 2. 미존재 + `src/editor_config.gleam` 존재 → 공유 editor config
991
+ 3. Preview도 동일 패턴 (`{snake_fn}_editor_preview.gleam`)
992
+
993
+ ### 12.5 빌드
994
+
995
+ `gleam run -m glendix/build` 실행 시 자동으로:
996
+
997
+ - 각 위젯별 브릿지 파일 (`src/{ComponentName}.js`) 생성
998
+ - 기본 위젯은 `pluggable-widgets-tools`가 처리
999
+ - 추가 위젯은 rollup config에서 엔트리 자동 추가
1000
+ - 각 위젯의 XML에서 `id`를 읽어 출력 경로 결정
899
1001
 
900
1002
  ---
901
1003
 
902
- ## 12. 실전 패턴
1004
+ ## 13. 실전 패턴
903
1005
 
904
1006
  ### 12.1 폼 입력 위젯
905
1007
 
906
1008
  ```gleam
907
1009
  import gleam/option.{None, Some}
908
- import glendix/mendix
909
- import glendix/mendix.{type JsProps}
910
- import glendix/mendix/action
911
- import glendix/mendix/editable_value as ev
1010
+ import mendraw/mendix
1011
+ import mendraw/mendix.{type JsProps}
1012
+ import mendraw/mendix/action
1013
+ import mendraw/mendix/editable_value as ev
912
1014
  import redraw.{type Element}
913
1015
  import redraw/dom/attribute
914
1016
  import redraw/dom/events
@@ -957,11 +1059,11 @@ pub fn text_input_widget(props: JsProps) -> Element {
957
1059
  ```gleam
958
1060
  import gleam/list
959
1061
  import gleam/option.{None, Some}
960
- import glendix/mendix
961
- import glendix/mendix.{type JsProps}
962
- import glendix/mendix/editable_value as ev
963
- import glendix/mendix/list_attribute as la
964
- import glendix/mendix/list_value as lv
1062
+ import mendraw/mendix
1063
+ import mendraw/mendix.{type JsProps}
1064
+ import mendraw/mendix/editable_value as ev
1065
+ import mendraw/mendix/list_attribute as la
1066
+ import mendraw/mendix/list_value as lv
965
1067
  import redraw.{type Element}
966
1068
  import redraw/dom/attribute
967
1069
  import redraw/dom/html
@@ -990,10 +1092,10 @@ pub fn data_table(props: JsProps) -> Element {
990
1092
 
991
1093
  ```gleam
992
1094
  import gleam/option.{None, Some}
993
- import glendix/mendix
994
- import glendix/mendix.{type JsProps}
995
- import glendix/mendix/filter
996
- import glendix/mendix/list_value as lv
1095
+ import mendraw/mendix
1096
+ import mendraw/mendix.{type JsProps}
1097
+ import mendraw/mendix/filter
1098
+ import mendraw/mendix/list_value as lv
997
1099
  import redraw.{type Element}
998
1100
  import redraw/dom/attribute
999
1101
  import redraw/dom/events
@@ -1028,7 +1130,7 @@ pub fn searchable_list(props: JsProps) -> Element {
1028
1130
 
1029
1131
  ---
1030
1132
 
1031
- ## 13. 절대 하지 말 것
1133
+ ## 14. 절대 하지 말 것
1032
1134
 
1033
1135
  | 실수 | 올바른 방법 |
1034
1136
  |------|------------|
@@ -1037,15 +1139,15 @@ pub fn searchable_list(props: JsProps) -> Element {
1037
1139
  | 조건 안에서 Hook 호출 | Hook은 항상 함수 최상위에서 호출 |
1038
1140
  | `html.text("")`로 빈 렌더링 | `html.none()` 사용 |
1039
1141
  | `binding.resolve(m(), "pie_chart")` | JS 원본 이름 유지: `"PieChart"` |
1040
- | 외부 React 컴포넌트용 `.mjs` 직접 작성 | `bindings.json` + `glendix/binding` 사용 |
1041
- | `.mpk` 위젯용 `.mjs` 직접 작성 | `widgets/` + `glendix/widget` 사용 |
1142
+ | 외부 React 컴포넌트용 `.mjs` 직접 작성 | `gleam.toml [tools.glendix.bindings]` + `glendix/binding` 사용 |
1143
+ | `.mpk` 위젯용 `.mjs` 직접 작성 | `gleam.toml [tools.mendraw.widgets.*]` + `mendraw/widget` 사용 |
1042
1144
  | `date.month()`에 0-based 값 전달 | glendix가 1↔0 자동 변환 |
1043
1145
  | Editor config에서 Gleam List 사용 | 콤마 구분 String 사용 (Jint 호환) |
1044
1146
  | FFI `.mjs`에 비즈니스 로직 | `.gleam`에 작성. `.mjs`는 JS 런타임 접근만 |
1045
1147
 
1046
1148
  ---
1047
1149
 
1048
- ## 14. 트러블슈팅
1150
+ ## 15. 트러블슈팅
1049
1151
 
1050
1152
  | 문제 | 원인 | 해결 |
1051
1153
  |------|------|------|
@@ -1053,9 +1155,8 @@ pub fn searchable_list(props: JsProps) -> Element {
1053
1155
  | `Cannot read property of undefined` | 없는 prop 접근 | `get_prop` (Option) 사용, prop 이름 확인 |
1054
1156
  | Hook 순서 에러 | 조건부 Hook 호출 | 항상 동일 순서로 호출 (React Rules) |
1055
1157
  | 바인딩 미생성 | `binding_ffi.mjs` 스텁 상태 | `gleam run -m glendix/install` |
1056
- | 위젯 바인딩 미생성 | `widget_ffi.mjs` 스텁 상태 | `widgets/`에 `.mpk` 배치 후 install |
1158
+ | 위젯 바인딩 미생성 | `widget_ffi.mjs` 스텁 상태 | `gleam.toml [tools.mendraw.widgets.*]` 설정 후 install |
1057
1159
  | `could not be resolved` | npm 패키지 미설치 | `npm install <패키지명>` |
1058
1160
  | `.env` PAT 오류 | marketplace 인증 실패 | [Developer Settings](https://user-settings.mendix.com/link/developersettings)에서 PAT 재발급 |
1059
- | Playwright 오류 | chromium 미설치 | `npx playwright install chromium` |
1060
1161
 
1061
1162
  ---