create-mendix-widget-gleam 3.0.2 → 4.0.1

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.3 — 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.3 and < 5.0.0"
50
+ mendraw = ">= 1.1.11 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,23 @@ 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` Classic (Dojo) 위젯 (gleam.toml) | `mendraw/classic` | CameraWidget |
417
420
 
418
421
  ### 7.2 외부 React 컴포넌트 (binding + interop)
419
422
 
420
- **설정:** `bindings.json` 작성 → `npm install 패키지명` → `gleam run -m glendix/install`
423
+ **설정:** `gleam.toml`에 바인딩 추가 → `npm install 패키지명` → `gleam run -m glendix/install`
421
424
 
422
- ```json
423
- {
424
- "recharts": {
425
- "components": ["PieChart", "Pie", "Cell", "Tooltip"]
426
- }
427
- }
425
+ ```toml
426
+ [tools.glendix.bindings]
427
+ recharts = ["PieChart", "Pie", "Cell", "Tooltip"]
428
428
  ```
429
429
 
430
430
  **Gleam 래퍼 작성:**
431
431
 
432
432
  ```gleam
433
433
  import glendix/binding
434
- import glendix/interop
434
+ import mendraw/interop
435
435
  import redraw.{type Element}
436
436
  import redraw/dom/attribute.{type Attribute}
437
437
 
@@ -454,48 +454,6 @@ pub fn tooltip(attrs: List(Attribute)) -> Element {
454
454
  | `interop.component_el_(comp, children)` | 자식만 |
455
455
  | `interop.void_component_el(comp, attrs)` | self-closing (자식 없음) |
456
456
 
457
- ### 7.3 .mpk Pluggable 위젯 (widget + interop)
458
-
459
- **설정:** `.mpk`를 `widgets/`에 배치 → `gleam run -m glendix/install`
460
-
461
- 자동 생성되는 `src/widgets/*.gleam`:
462
-
463
- ```gleam
464
- import glendix/interop
465
- import glendix/mendix
466
- import glendix/mendix.{type JsProps}
467
- import glendix/widget
468
- import redraw.{type Element}
469
- import redraw/dom/attribute
470
-
471
- pub fn render(props: JsProps) -> Element {
472
- let boolean_attribute = mendix.get_prop_required(props, "booleanAttribute")
473
- let comp = widget.component("Switch")
474
- interop.component_el(comp, [
475
- attribute.attribute("booleanAttribute", boolean_attribute),
476
- ], [])
477
- }
478
- ```
479
-
480
- **위젯 prop 헬퍼:** 코드에서 직접 값을 생성하여 .mpk 위젯에 전달할 때 사용합니다.
481
-
482
- | 함수 | Mendix 타입 | 용도 |
483
- |------|------------|------|
484
- | `widget.prop(key, value)` | DynamicValue | 읽기 전용 (expression, textTemplate) |
485
- | `widget.editable_prop(key, value, display, set_value)` | EditableValue | 편집 가능한 속성 |
486
- | `widget.action_prop(key, handler)` | ActionValue | 액션 콜백 (onClick 등) |
487
-
488
- ```gleam
489
- import glendix/widget
490
- import glendix/interop
491
-
492
- let comp = widget.component("Badge button")
493
- interop.component_el(comp, [
494
- widget.prop("caption", "제목"),
495
- widget.editable_prop("textAttr", model.text, model.text, set_text),
496
- widget.action_prop("onClick", fn() { handle_click() }),
497
- ], [])
498
- ```
499
457
 
500
458
  > Mendix에서 받은 prop (JsProps에서 꺼낸 값)은 이미 올바른 형식이므로 `attribute.attribute(key, value)`로 그대로 전달합니다.
501
459
 
@@ -503,7 +461,7 @@ interop.component_el(comp, [
503
461
 
504
462
  ```gleam
505
463
  import gleam/dynamic
506
- import glendix/classic
464
+ import mendraw/classic
507
465
 
508
466
  classic.render("CameraWidget.widget.CameraWidget", [
509
467
  #("mfToExecute", classic.to_dynamic(mf_value)),
@@ -517,12 +475,12 @@ classic.render("CameraWidget.widget.CameraWidget", [
517
475
 
518
476
  ## 8. Mendix API 레퍼런스
519
477
 
520
- ### 8.1 Props 접근 (`glendix/mendix`)
478
+ ### 8.1 Props 접근 (`mendraw/mendix`)
521
479
 
522
480
  `JsProps`는 opaque 타입입니다. 접근자 함수로만 읽습니다.
523
481
 
524
482
  ```gleam
525
- import glendix/mendix
483
+ import mendraw/mendix
526
484
 
527
485
  // Option 반환 (undefined → None)
528
486
  mendix.get_prop(props, "myAttr") // Option(a)
@@ -542,7 +500,7 @@ mendix.has_prop(props, "onClick") // Bool
542
500
  Mendix의 모든 동적 값은 상태를 가집니다:
543
501
 
544
502
  ```gleam
545
- import glendix/mendix.{Available, Loading, Unavailable}
503
+ import mendraw/mendix.{Available, Loading, Unavailable}
546
504
 
547
505
  case mendix.get_status(some_value) {
548
506
  Available -> // 값 사용 가능
@@ -551,12 +509,12 @@ case mendix.get_status(some_value) {
551
509
  }
552
510
  ```
553
511
 
554
- ### 8.3 EditableValue (`glendix/mendix/editable_value`)
512
+ ### 8.3 EditableValue (`mendraw/mendix/editable_value`)
555
513
 
556
514
  텍스트, 숫자, 날짜 등 편집 가능한 Mendix 속성:
557
515
 
558
516
  ```gleam
559
- import glendix/mendix/editable_value as ev
517
+ import mendraw/mendix/editable_value as ev
560
518
 
561
519
  // 읽기
562
520
  ev.value(attr) // Option(a)
@@ -581,12 +539,12 @@ ev.set_validator(attr, Some(fn(value) {
581
539
  ev.universe(attr) // Option(List(a))
582
540
  ```
583
541
 
584
- ### 8.4 ActionValue (`glendix/mendix/action`)
542
+ ### 8.4 ActionValue (`mendraw/mendix/action`)
585
543
 
586
544
  Mendix 마이크로플로우/나노플로우 실행:
587
545
 
588
546
  ```gleam
589
- import glendix/mendix/action
547
+ import mendraw/mendix/action
590
548
 
591
549
  action.execute(my_action) // 직접 실행
592
550
  action.execute_if_can(my_action) // can_execute가 True일 때만
@@ -596,24 +554,24 @@ action.can_execute(my_action) // Bool
596
554
  action.is_executing(my_action) // Bool
597
555
  ```
598
556
 
599
- ### 8.5 DynamicValue (`glendix/mendix/dynamic_value`)
557
+ ### 8.5 DynamicValue (`mendraw/mendix/dynamic_value`)
600
558
 
601
559
  읽기 전용 표현식 속성:
602
560
 
603
561
  ```gleam
604
- import glendix/mendix/dynamic_value as dv
562
+ import mendraw/mendix/dynamic_value as dv
605
563
 
606
564
  dv.value(expr) // Option(a)
607
565
  dv.status(expr) // String
608
566
  dv.is_available(expr) // Bool
609
567
  ```
610
568
 
611
- ### 8.6 ListValue (`glendix/mendix/list_value`)
569
+ ### 8.6 ListValue (`mendraw/mendix/list_value`)
612
570
 
613
571
  Mendix 데이터 소스 리스트:
614
572
 
615
573
  ```gleam
616
- import glendix/mendix/list_value as lv
574
+ import mendraw/mendix/list_value as lv
617
575
 
618
576
  // 아이템 접근
619
577
  lv.items(list_val) // Option(List(ObjectItem))
@@ -641,12 +599,12 @@ lv.set_filter(list_val, None) // 필터 해제
641
599
  lv.reload(list_val)
642
600
  ```
643
601
 
644
- ### 8.7 ListAttribute (`glendix/mendix/list_attribute`)
602
+ ### 8.7 ListAttribute (`mendraw/mendix/list_attribute`)
645
603
 
646
604
  리스트의 각 아이템에서 속성/액션/위젯 추출:
647
605
 
648
606
  ```gleam
649
- import glendix/mendix/list_attribute as la
607
+ import mendraw/mendix/list_attribute as la
650
608
 
651
609
  la.get_attribute(attr, item) // EditableValue 반환
652
610
  la.get_action(action, item) // Option(ActionValue)
@@ -661,10 +619,10 @@ la.attr_type(attr) // "String", "Integer" 등
661
619
  la.attr_formatter(attr) // ValueFormatter
662
620
  ```
663
621
 
664
- ### 8.8 Selection (`glendix/mendix/selection`)
622
+ ### 8.8 Selection (`mendraw/mendix/selection`)
665
623
 
666
624
  ```gleam
667
- import glendix/mendix/selection
625
+ import mendraw/mendix/selection
668
626
 
669
627
  // 단일 선택
670
628
  selection.selection(single_sel) // Option(ObjectItem)
@@ -679,8 +637,8 @@ selection.set_selections(multi_sel, [item1, item2])
679
637
  ### 8.9 Reference / ReferenceSet
680
638
 
681
639
  ```gleam
682
- import glendix/mendix/reference as ref
683
- import glendix/mendix/reference_set as ref_set
640
+ import mendraw/mendix/reference as ref
641
+ import mendraw/mendix/reference_set as ref_set
684
642
 
685
643
  // 단일 참조
686
644
  ref.value(my_ref) // Option(a)
@@ -693,10 +651,10 @@ ref_set.value(my_ref_set) // Option(List(a))
693
651
  ref_set.set_value(my_ref_set, Some([item1, item2]))
694
652
  ```
695
653
 
696
- ### 8.10 Filter (`glendix/mendix/filter`)
654
+ ### 8.10 Filter (`mendraw/mendix/filter`)
697
655
 
698
656
  ```gleam
699
- import glendix/mendix/filter
657
+ import mendraw/mendix/filter
700
658
 
701
659
  // 비교 연산
702
660
  filter.equals(filter.attribute("Status"), filter.literal("Active"))
@@ -719,12 +677,12 @@ filter.literal(value) // 상수 값
719
677
  filter.empty() // null 비교용
720
678
  ```
721
679
 
722
- ### 8.11 날짜 (`glendix/mendix/date`)
680
+ ### 8.11 날짜 (`mendraw/mendix/date`)
723
681
 
724
682
  > Gleam month는 1-based (1~12), JS는 0-based. glendix가 자동 변환합니다.
725
683
 
726
684
  ```gleam
727
- import glendix/mendix/date
685
+ import mendraw/mendix/date
728
686
 
729
687
  date.now()
730
688
  date.from_iso("2024-03-15T10:30:00Z")
@@ -741,45 +699,74 @@ date.to_input_value(d) // "2024-03-15" (input[type="date"]용)
741
699
  date.from_input_value(s) // Option(JsDate)
742
700
  ```
743
701
 
744
- ### 8.12 Big (`glendix/mendix/big`)
702
+ ### 8.12 Decimal (`mendraw/mendix/decimal`)
703
+
704
+ Mendix Decimal 속성의 경계 변환 전용. Big.js 객체 ↔ Gleam 값 변환만 담당합니다.
705
+ 산술/비교 연산이 필요하면 위젯 프로젝트에서 [dee](https://hexdocs.pm/dee/) 패키지를 추가하세요.
706
+
707
+ ```gleam
708
+ import mendraw/mendix/decimal
709
+
710
+ // 생성 (Gleam → Mendix Big.js)
711
+ decimal.from_string("123.456")
712
+ decimal.from_int(100)
713
+ decimal.from_float(3.14) // 정밀도 손실 주의
714
+
715
+ // 변환 (Mendix Big.js → Gleam)
716
+ decimal.to_string(d) // "123.456"
717
+ decimal.to_float(d) // 123.456 (정밀도 손실 가능)
718
+ decimal.to_int(d) // 123 (소수점 이하 버림)
719
+ decimal.to_fixed(d, 2) // "123.46"
720
+ ```
745
721
 
746
- Big.js 래퍼. Mendix Decimal 타입 처리용:
722
+ **dee와 함께 사용하는 패턴:**
747
723
 
748
724
  ```gleam
749
- import glendix/mendix/big
725
+ import dee
726
+ import mendraw/mendix/decimal
727
+
728
+ // Mendix → dee (연산) → Mendix
729
+ let value = decimal.to_string(mendix_decimal) // Big.js → String
730
+ let result = dee.from_string(value) // String → dee
731
+ |> result.map(fn(d) { dee.add(d, dee.from_int(1)) })
732
+ |> result.map(dee.to_string) // dee → String
733
+ |> result.unwrap("0")
734
+ decimal.from_string(result) // String → Big.js
735
+ ```
750
736
 
751
- big.from_string("123.456")
752
- big.from_int(100)
737
+ ### 8.12.1 조건부 CSS 클래스 (`cx`)
753
738
 
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)
739
+ `mendraw/mendix` 모듈의 `cx` 함수로 CSS 클래스를 조건부 조합합니다:
757
740
 
758
- big.compare(a, b) // gleam/order.Order
759
- big.equal(a, b) // Bool
741
+ ```gleam
742
+ import mendraw/mendix
760
743
 
761
- big.to_string(a) big.to_float(a)
762
- big.to_int(a) big.to_fixed(a, 2)
744
+ mendix.cx([
745
+ #("widget-container", True),
746
+ #("active", is_active),
747
+ #("disabled", !is_editable),
748
+ ])
749
+ // → "widget-container active" (is_active=True, is_editable=True일 때)
763
750
  ```
764
751
 
765
752
  ### 8.13 File, Icon, Formatter
766
753
 
767
754
  ```gleam
768
755
  // FileValue / WebImage
769
- import glendix/mendix/file
756
+ import mendraw/mendix/file
770
757
  file.uri(file_val) // String
771
758
  file.name(file_val) // Option(String)
772
759
  file.image_uri(img) // String
773
760
  file.alt_text(img) // Option(String)
774
761
 
775
762
  // WebIcon
776
- import glendix/mendix/icon
763
+ import mendraw/mendix/icon
777
764
  icon.icon_type(i) // Glyph | Image | IconFont
778
765
  icon.icon_class(i) // String
779
766
  icon.icon_url(i) // String
780
767
 
781
768
  // ValueFormatter
782
- import glendix/mendix/formatter
769
+ import mendraw/mendix/formatter
783
770
  formatter.format(fmt, Some(value)) // String
784
771
  formatter.parse(fmt, "123.45") // Result(Option(a), Nil)
785
772
  ```
@@ -794,8 +781,8 @@ Studio Pro의 editorConfig 로직을 Gleam으로 작성합니다.
794
781
 
795
782
  ```gleam
796
783
  import glendix/editor_config.{type Properties}
797
- import glendix/mendix
798
- import glendix/mendix.{type JsProps}
784
+ import mendraw/mendix
785
+ import mendraw/mendix.{type JsProps}
799
786
 
800
787
  const bar_keys = "barWidth,barColor"
801
788
  const line_keys = "lineStyle,lineCurve"
@@ -885,30 +872,93 @@ timer.clear_interval(id)
885
872
  | 명령어 | 설명 |
886
873
  |--------|------|
887
874
  | `gleam build` | 컴파일 |
888
- | `gleam run -m glendix/install` | 의존성 + 바인딩 + 위젯 .gleam 생성 |
875
+ | `gleam run -m glendix/install` | 의존성 + mendraw 위젯 다운로드 + 바인딩 + 위젯 .gleam 생성 |
889
876
  | `gleam run -m glendix/dev` | 개발 서버 (HMR) |
890
877
  | `gleam run -m glendix/build` | 프로덕션 빌드 (.mpk) |
891
878
  | `gleam run -m glendix/start` | Mendix 테스트 프로젝트 연동 |
892
879
  | `gleam run -m glendix/release` | 릴리즈 빌드 |
893
880
  | `gleam run -m glendix/lint` | ESLint 검사 |
894
881
  | `gleam run -m glendix/lint_fix` | ESLint 자동 수정 |
895
- | `gleam run -m glendix/marketplace` | Marketplace 위젯 다운로드 (인터랙티브) |
882
+ | `gleam run -m mendraw/marketplace` | Marketplace 위젯 다운로드 (인터랙티브, gleam.toml 자동 기록) |
896
883
  | `gleam run -m glendix/define` | 위젯 프로퍼티 정의 TUI 에디터 |
897
884
 
898
- **PM 자동 감지:** `pnpm-lock.yaml` → pnpm / `bun.lockb`·`bun.lock` → bun / 기본값 → npm
885
+ **PM 감지:** `gleam.toml`의 `[tools.glendix] pm = "pnpm"` 오버라이드 우선. 없으면 lock 파일 기반: `pnpm-lock.yaml` → pnpm / `bun.lockb`·`bun.lock` → bun / 기본값 → npm
886
+
887
+ ---
888
+
889
+ ## 12. 멀티 위젯 컴포넌트
890
+
891
+ 하나의 `.mpk` 파일에 여러 위젯 컴포넌트를 묶어서 개발할 수 있습니다 (예: Charts.mpk).
892
+
893
+ ### 12.1 설정
894
+
895
+ `package.json`에 `widgets` 맵을 추가합니다:
896
+
897
+ ```json
898
+ {
899
+ "widgetName": "AreaChart",
900
+ "widgets": {
901
+ "AreaChart": "area_chart",
902
+ "BarChart": "bar_chart",
903
+ "PieChart": "pie_chart"
904
+ }
905
+ }
906
+ ```
907
+
908
+ - `widgetName`: 기본 위젯 이름 (`pluggable-widgets-tools` 진입점)
909
+ - `widgets`: 컴포넌트명(PascalCase) → Gleam 함수명(snake_case) 맵
910
+ - `widgets` 미지정 시 기존 단일 위젯 동작 유지 (하위 호환)
911
+
912
+ ### 12.2 Gleam 코드
913
+
914
+ 메인 모듈에서 모든 위젯 함수를 export합니다:
915
+
916
+ ```gleam
917
+ import mendraw/mendix.{type JsProps}
918
+ import redraw.{type Element}
919
+
920
+ pub fn area_chart(props: JsProps) -> Element { ... }
921
+ pub fn bar_chart(props: JsProps) -> Element { ... }
922
+ pub fn pie_chart(props: JsProps) -> Element { ... }
923
+ ```
924
+
925
+ ### 12.3 CSS 전략
926
+
927
+ 위젯별 CSS 파일이 있으면 우선 사용, 없으면 공유 CSS 폴백:
928
+
929
+ 1. `src/ui/{ComponentName}.css` 존재 → 해당 파일 import
930
+ 2. 미존재 + `src/ui/{widgetName}.css` 존재 → 공유 CSS import
931
+ 3. 둘 다 미존재 → CSS import 생략
932
+
933
+ ### 12.4 Editor Config / Preview
934
+
935
+ 위젯별 파일 → 공유 파일 폴백:
936
+
937
+ 1. `src/{snake_fn}_editor_config.gleam` 존재 → 위젯별 editor config
938
+ 2. 미존재 + `src/editor_config.gleam` 존재 → 공유 editor config
939
+ 3. Preview도 동일 패턴 (`{snake_fn}_editor_preview.gleam`)
940
+
941
+ ### 12.5 빌드
942
+
943
+ `gleam run -m glendix/build` 실행 시 자동으로:
944
+
945
+ - 각 위젯별 브릿지 파일 (`src/{ComponentName}.js`) 생성
946
+ - 기본 위젯은 `pluggable-widgets-tools`가 처리
947
+ - 추가 위젯은 rollup config에서 엔트리 자동 추가
948
+ - 각 위젯의 XML에서 `id`를 읽어 출력 경로 결정
899
949
 
900
950
  ---
901
951
 
902
- ## 12. 실전 패턴
952
+ ## 13. 실전 패턴
903
953
 
904
954
  ### 12.1 폼 입력 위젯
905
955
 
906
956
  ```gleam
907
957
  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
958
+ import mendraw/mendix
959
+ import mendraw/mendix.{type JsProps}
960
+ import mendraw/mendix/action
961
+ import mendraw/mendix/editable_value as ev
912
962
  import redraw.{type Element}
913
963
  import redraw/dom/attribute
914
964
  import redraw/dom/events
@@ -957,11 +1007,11 @@ pub fn text_input_widget(props: JsProps) -> Element {
957
1007
  ```gleam
958
1008
  import gleam/list
959
1009
  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
1010
+ import mendraw/mendix
1011
+ import mendraw/mendix.{type JsProps}
1012
+ import mendraw/mendix/editable_value as ev
1013
+ import mendraw/mendix/list_attribute as la
1014
+ import mendraw/mendix/list_value as lv
965
1015
  import redraw.{type Element}
966
1016
  import redraw/dom/attribute
967
1017
  import redraw/dom/html
@@ -990,10 +1040,10 @@ pub fn data_table(props: JsProps) -> Element {
990
1040
 
991
1041
  ```gleam
992
1042
  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
1043
+ import mendraw/mendix
1044
+ import mendraw/mendix.{type JsProps}
1045
+ import mendraw/mendix/filter
1046
+ import mendraw/mendix/list_value as lv
997
1047
  import redraw.{type Element}
998
1048
  import redraw/dom/attribute
999
1049
  import redraw/dom/events
@@ -1028,7 +1078,7 @@ pub fn searchable_list(props: JsProps) -> Element {
1028
1078
 
1029
1079
  ---
1030
1080
 
1031
- ## 13. 절대 하지 말 것
1081
+ ## 14. 절대 하지 말 것
1032
1082
 
1033
1083
  | 실수 | 올바른 방법 |
1034
1084
  |------|------------|
@@ -1037,15 +1087,14 @@ pub fn searchable_list(props: JsProps) -> Element {
1037
1087
  | 조건 안에서 Hook 호출 | Hook은 항상 함수 최상위에서 호출 |
1038
1088
  | `html.text("")`로 빈 렌더링 | `html.none()` 사용 |
1039
1089
  | `binding.resolve(m(), "pie_chart")` | JS 원본 이름 유지: `"PieChart"` |
1040
- | 외부 React 컴포넌트용 `.mjs` 직접 작성 | `bindings.json` + `glendix/binding` 사용 |
1041
- | `.mpk` 위젯용 `.mjs` 직접 작성 | `widgets/` + `glendix/widget` 사용 |
1090
+ | 외부 React 컴포넌트용 `.mjs` 직접 작성 | `gleam.toml [tools.glendix.bindings]` + `glendix/binding` 사용 |
1042
1091
  | `date.month()`에 0-based 값 전달 | glendix가 1↔0 자동 변환 |
1043
1092
  | Editor config에서 Gleam List 사용 | 콤마 구분 String 사용 (Jint 호환) |
1044
1093
  | FFI `.mjs`에 비즈니스 로직 | `.gleam`에 작성. `.mjs`는 JS 런타임 접근만 |
1045
1094
 
1046
1095
  ---
1047
1096
 
1048
- ## 14. 트러블슈팅
1097
+ ## 15. 트러블슈팅
1049
1098
 
1050
1099
  | 문제 | 원인 | 해결 |
1051
1100
  |------|------|------|
@@ -1053,9 +1102,7 @@ pub fn searchable_list(props: JsProps) -> Element {
1053
1102
  | `Cannot read property of undefined` | 없는 prop 접근 | `get_prop` (Option) 사용, prop 이름 확인 |
1054
1103
  | Hook 순서 에러 | 조건부 Hook 호출 | 항상 동일 순서로 호출 (React Rules) |
1055
1104
  | 바인딩 미생성 | `binding_ffi.mjs` 스텁 상태 | `gleam run -m glendix/install` |
1056
- | 위젯 바인딩 미생성 | `widget_ffi.mjs` 스텁 상태 | `widgets/`에 `.mpk` 배치 후 install |
1057
1105
  | `could not be resolved` | npm 패키지 미설치 | `npm install <패키지명>` |
1058
1106
  | `.env` PAT 오류 | marketplace 인증 실패 | [Developer Settings](https://user-settings.mendix.com/link/developersettings)에서 PAT 재발급 |
1059
- | Playwright 오류 | chromium 미설치 | `npx playwright install chromium` |
1060
1107
 
1061
1108
  ---