create-mendix-widget-gleam 3.0.1 → 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,23 +46,25 @@ glendix는 Gleam으로 Mendix Pluggable Widget을 작성하는 FFI 라이브러
43
46
 
44
47
  ```toml
45
48
  [dependencies]
46
- glendix = ">= 3.0.0 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" },
54
- "overrides": { "@types/react": "19.0.0", "@types/react-dom": "19.0.0" },
55
- "resolutions": { "@types/react": "19.0.0", "@types/react-dom": "19.0.0" }
57
+ "dependencies": { "big.js": "^6.0.0" }, // decimal 속성 사용 시
58
+ "overrides": { "react": "19.0.0", "react-dom": "19.0.0", "@types/react": "19.0.0", "@types/react-dom": "19.0.0" },
59
+ "resolutions": { "react": "19.0.0", "react-dom": "19.0.0", "@types/react": "19.0.0", "@types/react-dom": "19.0.0" }
56
60
  }
57
61
  ```
58
62
 
59
- > `react`/`react-dom`은 `dependencies`에 넣지 않는다. `pluggable-widgets-tools`가 자동 제공하며, 직접 선언하면 마이그레이션 충돌이 발생한다.
63
+ > - `react`/`react-dom`은 `dependencies`에 넣지 않는다. `pluggable-widgets-tools`가 자동 제공하며, 직접 선언하면 번들 충돌이 발생한다.
64
+ > - `overrides`/`resolutions`에서 반드시 **캐럿(`^`) 없이 정확한 버전**을 지정한다. `^19.0.0`은 react와 react-dom이 서로 다른 19.x.x로 해석되어 런타임 버전 불일치 에러를 일으킨다.
60
65
 
61
66
  ```bash
62
- gleam run -m glendix/install # 의존성 설치 + 바인딩 생성
67
+ gleam run -m glendix/install # 의존성 설치 + TOML 위젯 다운로드 + 바인딩 생성
63
68
  gleam build # 컴파일 확인
64
69
  ```
65
70
 
@@ -70,18 +75,18 @@ gleam build # 컴파일 확인
70
75
  모든 Mendix Pluggable Widget은 이 시그니처를 따릅니다:
71
76
 
72
77
  ```gleam
73
- import glendix/mendix.{type JsProps}
78
+ import mendraw/mendix.{type JsProps}
74
79
  import redraw.{type Element}
75
80
 
76
81
  pub fn widget(props: JsProps) -> Element
77
82
  ```
78
83
 
79
- - `JsProps` — Mendix가 전달하는 props 객체 (opaque). `glendix/mendix` 모듈의 접근자로만 읽는다.
84
+ - `JsProps` — Mendix가 전달하는 props 객체 (opaque). `mendraw/mendix` 모듈의 접근자로만 읽는다.
80
85
  - `Element` — redraw의 React 엘리먼트 타입. `redraw/dom/html`, `redraw.fragment()` 등으로 생성한다.
81
86
 
82
87
  ---
83
88
 
84
- ## 3. 렌더링 경로 선택
89
+ ## 4. 렌더링 경로 선택
85
90
 
86
91
  glendix는 두 가지 렌더링 경로를 지원합니다. 둘 다 `redraw.Element`를 반환하므로 자유롭게 합성 가능합니다.
87
92
 
@@ -97,19 +102,19 @@ glendix는 두 가지 렌더링 경로를 지원합니다. 둘 다 `redraw.Eleme
97
102
 
98
103
  ---
99
104
 
100
- ## 4. redraw 렌더링 경로 — 레퍼런스
105
+ ## 5. redraw 렌더링 경로 — 레퍼런스
101
106
 
102
- ### 4.1 필수 import 패턴
107
+ ### 5.1 필수 import 패턴
103
108
 
104
109
  ```gleam
105
- import glendix/mendix.{type JsProps} // Mendix props 타입
110
+ import mendraw/mendix.{type JsProps} // Mendix props 타입
106
111
  import redraw.{type Element} // 반환 타입
107
112
  import redraw/dom/html // HTML 태그 함수
108
113
  import redraw/dom/attribute // HTML 속성
109
114
  import redraw/dom/events // 이벤트 핸들러
110
115
  ```
111
116
 
112
- ### 4.2 HTML 엘리먼트 생성
117
+ ### 5.2 HTML 엘리먼트 생성
113
118
 
114
119
  ```gleam
115
120
  // 속성 + 자식
@@ -124,7 +129,7 @@ html.img([attribute.src("image.png"), attribute.alt("설명")])
124
129
  html.br([])
125
130
  ```
126
131
 
127
- ### 4.3 텍스트, 빈 렌더링, Fragment
132
+ ### 5.3 텍스트, 빈 렌더링, Fragment
128
133
 
129
134
  ```gleam
130
135
  html.text("안녕하세요") // 텍스트 노드
@@ -135,7 +140,7 @@ html.none() // 아무것도 렌더링하지 않음 (Rea
135
140
  redraw.fragment([child1, child2]) // Fragment
136
141
  ```
137
142
 
138
- ### 4.4 조건부 렌더링
143
+ ### 5.4 조건부 렌더링
139
144
 
140
145
  v3.0에서는 Gleam `case` 표현식을 직접 사용합니다:
141
146
 
@@ -160,7 +165,7 @@ case mendix.get_status(value) {
160
165
  }
161
166
  ```
162
167
 
163
- ### 4.5 리스트 렌더링
168
+ ### 5.5 리스트 렌더링
164
169
 
165
170
  ```gleam
166
171
  import gleam/list
@@ -174,7 +179,7 @@ html.ul([], list.map(items, fn(item) {
174
179
 
175
180
  > 리스트 렌더링 시 `attribute.key()`를 항상 설정해야 합니다. React reconciliation에 필요합니다.
176
181
 
177
- ### 4.6 속성
182
+ ### 5.6 속성
178
183
 
179
184
  ```gleam
180
185
  import redraw/dom/attribute
@@ -199,7 +204,7 @@ attribute.attribute("data-custom", "value")
199
204
  attribute.ref(my_ref)
200
205
  ```
201
206
 
202
- ### 4.7 이벤트 핸들러
207
+ ### 5.7 이벤트 핸들러
203
208
 
204
209
  ```gleam
205
210
  import redraw/dom/events
@@ -216,7 +221,7 @@ events.on_blur(fn(e) { Nil })
216
221
  events.on_click_capture(fn(e) { Nil })
217
222
  ```
218
223
 
219
- ### 4.8 Hooks
224
+ ### 5.8 Hooks
220
225
 
221
226
  모든 훅은 `redraw` 메인 모듈에 있습니다:
222
227
 
@@ -233,8 +238,11 @@ redraw.use_effect(fn() { Nil }, deps) // 의존성 지정
233
238
  redraw.use_effect_(fn() { fn() { cleanup() } }, deps) // 클린업 포함
234
239
 
235
240
  // Ref
236
- let my_ref = redraw.use_ref() // Option(a)
237
- let my_ref = redraw.use_ref_(initial) // 초기값 지정
241
+ import redraw/ref
242
+ let my_ref = redraw.use_ref() // Ref(Option(a))
243
+ let my_ref = redraw.use_ref_(initial) // Ref(a) — 초기값 지정
244
+ ref.current(my_ref) // 현재 값 읽기
245
+ ref.assign(my_ref, new_value) // 값 쓰기
238
246
 
239
247
  // 메모이제이션
240
248
  let result = redraw.use_memo(fn() { expensive(data) }, data)
@@ -252,7 +260,7 @@ let #(is_pending, start) = redraw.use_transition()
252
260
  let deferred = redraw.use_deferred_value(value)
253
261
  ```
254
262
 
255
- ### 4.9 컴포넌트 정의
263
+ ### 5.9 컴포넌트 정의
256
264
 
257
265
  ```gleam
258
266
  import redraw
@@ -266,7 +274,7 @@ let my_comp = redraw.component_("MyComponent", fn(props) {
266
274
  let memoized = redraw.memoize_(my_comp)
267
275
  ```
268
276
 
269
- ### 4.10 Context API
277
+ ### 5.10 Context API
270
278
 
271
279
  ```gleam
272
280
  import redraw
@@ -280,7 +288,7 @@ redraw.provider(theme_ctx, "dark", [child_elements])
280
288
  let theme = redraw.use_context(theme_ctx)
281
289
  ```
282
290
 
283
- ### 4.11 SVG
291
+ ### 5.11 SVG
284
292
 
285
293
  ```gleam
286
294
  import redraw/dom/svg
@@ -298,16 +306,16 @@ svg.svg([attribute.attribute("viewBox", "0 0 100 100")], [
298
306
 
299
307
  ---
300
308
 
301
- ## 5. lustre 렌더링 경로 — 레퍼런스
309
+ ## 6. lustre 렌더링 경로 — 레퍼런스
302
310
 
303
- ### 5.1 TEA 패턴 (use_tea)
311
+ ### 6.1 TEA 패턴 (use_tea)
304
312
 
305
313
  `update`와 `view`는 표준 lustre 코드와 100% 동일합니다. 진입점만 `glendix/lustre.use_tea()`를 사용합니다.
306
314
 
307
315
  ```gleam
308
316
  import gleam/int
309
317
  import glendix/lustre as gl
310
- import glendix/mendix.{type JsProps}
318
+ import mendraw/mendix.{type JsProps}
311
319
  import lustre/effect
312
320
  import lustre/element/html
313
321
  import lustre/event
@@ -347,7 +355,7 @@ pub fn widget(_props: JsProps) -> Element {
347
355
  }
348
356
  ```
349
357
 
350
- ### 5.2 Simple TEA (use_simple) — Effect 없음
358
+ ### 6.2 Simple TEA (use_simple) — Effect 없음
351
359
 
352
360
  ```gleam
353
361
  import glendix/lustre as gl
@@ -364,7 +372,7 @@ fn update_simple(model: Model, msg: Msg) -> Model {
364
372
  }
365
373
  ```
366
374
 
367
- ### 5.3 Lustre Element를 수동으로 변환 (render)
375
+ ### 6.3 Lustre Element를 수동으로 변환 (render)
368
376
 
369
377
  lustre 뷰를 React 트리 안에 삽입할 때 사용합니다:
370
378
 
@@ -374,7 +382,7 @@ import glendix/lustre as gl
374
382
  let react_element = gl.render(lustre_element, dispatch_fn)
375
383
  ```
376
384
 
377
- ### 5.4 redraw Element를 lustre 트리에 삽입 (embed)
385
+ ### 6.4 redraw Element를 lustre 트리에 삽입 (embed)
378
386
 
379
387
  lustre view 안에서 redraw 컴포넌트를 사용할 때 호출합니다:
380
388
 
@@ -401,33 +409,30 @@ fn view(model: Model) {
401
409
 
402
410
  ---
403
411
 
404
- ## 6. 외부 컴포넌트 통합
412
+ ## 7. 외부 컴포넌트 통합
405
413
 
406
- ### 6.1 모듈 선택 가이드
414
+ ### 7.1 모듈 선택 가이드
407
415
 
408
416
  | 컴포넌트 출처 | 사용 모듈 | 예시 |
409
417
  |--------------|----------|------|
410
- | npm 패키지 (React 컴포넌트) | `glendix/binding` + `glendix/interop` | recharts, @mui |
411
- | `.mpk` Pluggable 위젯 | `glendix/widget` + `glendix/interop` | Switch.mpk, Badge.mpk |
412
- | `.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 |
413
421
 
414
- ### 6.2 외부 React 컴포넌트 (binding + interop)
422
+ ### 7.2 외부 React 컴포넌트 (binding + interop)
415
423
 
416
- **설정:** `bindings.json` 작성 → `npm install 패키지명` → `gleam run -m glendix/install`
424
+ **설정:** `gleam.toml`에 바인딩 추가 → `npm install 패키지명` → `gleam run -m glendix/install`
417
425
 
418
- ```json
419
- {
420
- "recharts": {
421
- "components": ["PieChart", "Pie", "Cell", "Tooltip"]
422
- }
423
- }
426
+ ```toml
427
+ [tools.glendix.bindings]
428
+ recharts = ["PieChart", "Pie", "Cell", "Tooltip"]
424
429
  ```
425
430
 
426
431
  **Gleam 래퍼 작성:**
427
432
 
428
433
  ```gleam
429
434
  import glendix/binding
430
- import glendix/interop
435
+ import mendraw/interop
431
436
  import redraw.{type Element}
432
437
  import redraw/dom/attribute.{type Attribute}
433
438
 
@@ -450,17 +455,26 @@ pub fn tooltip(attrs: List(Attribute)) -> Element {
450
455
  | `interop.component_el_(comp, children)` | 자식만 |
451
456
  | `interop.void_component_el(comp, attrs)` | self-closing (자식 없음) |
452
457
 
453
- ### 6.3 .mpk Pluggable 위젯 (widget + interop)
458
+ ### 7.3 .mpk Pluggable 위젯 (widget + interop)
459
+
460
+ `gleam.toml`에 위젯을 등록하고 `gleam run -m glendix/install`로 자동 다운로드합니다:
454
461
 
455
- **설정:** `.mpk`를 `widgets/`에 배치 → `gleam run -m glendix/install`
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에 자동 추가됩니다.
456
470
 
457
471
  자동 생성되는 `src/widgets/*.gleam`:
458
472
 
459
473
  ```gleam
460
- import glendix/interop
461
- import glendix/mendix
462
- import glendix/mendix.{type JsProps}
463
- import glendix/widget
474
+ import mendraw/interop
475
+ import mendraw/mendix
476
+ import mendraw/mendix.{type JsProps}
477
+ import mendraw/widget
464
478
  import redraw.{type Element}
465
479
  import redraw/dom/attribute
466
480
 
@@ -482,8 +496,8 @@ pub fn render(props: JsProps) -> Element {
482
496
  | `widget.action_prop(key, handler)` | ActionValue | 액션 콜백 (onClick 등) |
483
497
 
484
498
  ```gleam
485
- import glendix/widget
486
- import glendix/interop
499
+ import mendraw/widget
500
+ import mendraw/interop
487
501
 
488
502
  let comp = widget.component("Badge button")
489
503
  interop.component_el(comp, [
@@ -495,11 +509,11 @@ interop.component_el(comp, [
495
509
 
496
510
  > Mendix에서 받은 prop (JsProps에서 꺼낸 값)은 이미 올바른 형식이므로 `attribute.attribute(key, value)`로 그대로 전달합니다.
497
511
 
498
- ### 6.4 Classic (Dojo) 위젯
512
+ ### 7.4 Classic (Dojo) 위젯
499
513
 
500
514
  ```gleam
501
515
  import gleam/dynamic
502
- import glendix/classic
516
+ import mendraw/classic
503
517
 
504
518
  classic.render("CameraWidget.widget.CameraWidget", [
505
519
  #("mfToExecute", classic.to_dynamic(mf_value)),
@@ -511,14 +525,14 @@ classic.render("CameraWidget.widget.CameraWidget", [
511
525
 
512
526
  ---
513
527
 
514
- ## 7. Mendix API 레퍼런스
528
+ ## 8. Mendix API 레퍼런스
515
529
 
516
- ### 7.1 Props 접근 (`glendix/mendix`)
530
+ ### 8.1 Props 접근 (`mendraw/mendix`)
517
531
 
518
532
  `JsProps`는 opaque 타입입니다. 접근자 함수로만 읽습니다.
519
533
 
520
534
  ```gleam
521
- import glendix/mendix
535
+ import mendraw/mendix
522
536
 
523
537
  // Option 반환 (undefined → None)
524
538
  mendix.get_prop(props, "myAttr") // Option(a)
@@ -533,12 +547,12 @@ mendix.get_string_prop(props, "caption") // String
533
547
  mendix.has_prop(props, "onClick") // Bool
534
548
  ```
535
549
 
536
- ### 7.2 ValueStatus 확인
550
+ ### 8.2 ValueStatus 확인
537
551
 
538
552
  Mendix의 모든 동적 값은 상태를 가집니다:
539
553
 
540
554
  ```gleam
541
- import glendix/mendix.{Available, Loading, Unavailable}
555
+ import mendraw/mendix.{Available, Loading, Unavailable}
542
556
 
543
557
  case mendix.get_status(some_value) {
544
558
  Available -> // 값 사용 가능
@@ -547,12 +561,12 @@ case mendix.get_status(some_value) {
547
561
  }
548
562
  ```
549
563
 
550
- ### 7.3 EditableValue (`glendix/mendix/editable_value`)
564
+ ### 8.3 EditableValue (`mendraw/mendix/editable_value`)
551
565
 
552
566
  텍스트, 숫자, 날짜 등 편집 가능한 Mendix 속성:
553
567
 
554
568
  ```gleam
555
- import glendix/mendix/editable_value as ev
569
+ import mendraw/mendix/editable_value as ev
556
570
 
557
571
  // 읽기
558
572
  ev.value(attr) // Option(a)
@@ -577,12 +591,12 @@ ev.set_validator(attr, Some(fn(value) {
577
591
  ev.universe(attr) // Option(List(a))
578
592
  ```
579
593
 
580
- ### 7.4 ActionValue (`glendix/mendix/action`)
594
+ ### 8.4 ActionValue (`mendraw/mendix/action`)
581
595
 
582
596
  Mendix 마이크로플로우/나노플로우 실행:
583
597
 
584
598
  ```gleam
585
- import glendix/mendix/action
599
+ import mendraw/mendix/action
586
600
 
587
601
  action.execute(my_action) // 직접 실행
588
602
  action.execute_if_can(my_action) // can_execute가 True일 때만
@@ -592,24 +606,24 @@ action.can_execute(my_action) // Bool
592
606
  action.is_executing(my_action) // Bool
593
607
  ```
594
608
 
595
- ### 7.5 DynamicValue (`glendix/mendix/dynamic_value`)
609
+ ### 8.5 DynamicValue (`mendraw/mendix/dynamic_value`)
596
610
 
597
611
  읽기 전용 표현식 속성:
598
612
 
599
613
  ```gleam
600
- import glendix/mendix/dynamic_value as dv
614
+ import mendraw/mendix/dynamic_value as dv
601
615
 
602
616
  dv.value(expr) // Option(a)
603
617
  dv.status(expr) // String
604
618
  dv.is_available(expr) // Bool
605
619
  ```
606
620
 
607
- ### 7.6 ListValue (`glendix/mendix/list_value`)
621
+ ### 8.6 ListValue (`mendraw/mendix/list_value`)
608
622
 
609
623
  Mendix 데이터 소스 리스트:
610
624
 
611
625
  ```gleam
612
- import glendix/mendix/list_value as lv
626
+ import mendraw/mendix/list_value as lv
613
627
 
614
628
  // 아이템 접근
615
629
  lv.items(list_val) // Option(List(ObjectItem))
@@ -637,12 +651,12 @@ lv.set_filter(list_val, None) // 필터 해제
637
651
  lv.reload(list_val)
638
652
  ```
639
653
 
640
- ### 7.7 ListAttribute (`glendix/mendix/list_attribute`)
654
+ ### 8.7 ListAttribute (`mendraw/mendix/list_attribute`)
641
655
 
642
656
  리스트의 각 아이템에서 속성/액션/위젯 추출:
643
657
 
644
658
  ```gleam
645
- import glendix/mendix/list_attribute as la
659
+ import mendraw/mendix/list_attribute as la
646
660
 
647
661
  la.get_attribute(attr, item) // EditableValue 반환
648
662
  la.get_action(action, item) // Option(ActionValue)
@@ -657,10 +671,10 @@ la.attr_type(attr) // "String", "Integer" 등
657
671
  la.attr_formatter(attr) // ValueFormatter
658
672
  ```
659
673
 
660
- ### 7.8 Selection (`glendix/mendix/selection`)
674
+ ### 8.8 Selection (`mendraw/mendix/selection`)
661
675
 
662
676
  ```gleam
663
- import glendix/mendix/selection
677
+ import mendraw/mendix/selection
664
678
 
665
679
  // 단일 선택
666
680
  selection.selection(single_sel) // Option(ObjectItem)
@@ -672,11 +686,11 @@ selection.selections(multi_sel) // List(ObjectItem)
672
686
  selection.set_selections(multi_sel, [item1, item2])
673
687
  ```
674
688
 
675
- ### 7.9 Reference / ReferenceSet
689
+ ### 8.9 Reference / ReferenceSet
676
690
 
677
691
  ```gleam
678
- import glendix/mendix/reference as ref
679
- import glendix/mendix/reference_set as ref_set
692
+ import mendraw/mendix/reference as ref
693
+ import mendraw/mendix/reference_set as ref_set
680
694
 
681
695
  // 단일 참조
682
696
  ref.value(my_ref) // Option(a)
@@ -689,10 +703,10 @@ ref_set.value(my_ref_set) // Option(List(a))
689
703
  ref_set.set_value(my_ref_set, Some([item1, item2]))
690
704
  ```
691
705
 
692
- ### 7.10 Filter (`glendix/mendix/filter`)
706
+ ### 8.10 Filter (`mendraw/mendix/filter`)
693
707
 
694
708
  ```gleam
695
- import glendix/mendix/filter
709
+ import mendraw/mendix/filter
696
710
 
697
711
  // 비교 연산
698
712
  filter.equals(filter.attribute("Status"), filter.literal("Active"))
@@ -715,12 +729,12 @@ filter.literal(value) // 상수 값
715
729
  filter.empty() // null 비교용
716
730
  ```
717
731
 
718
- ### 7.11 날짜 (`glendix/mendix/date`)
732
+ ### 8.11 날짜 (`mendraw/mendix/date`)
719
733
 
720
734
  > Gleam month는 1-based (1~12), JS는 0-based. glendix가 자동 변환합니다.
721
735
 
722
736
  ```gleam
723
- import glendix/mendix/date
737
+ import mendraw/mendix/date
724
738
 
725
739
  date.now()
726
740
  date.from_iso("2024-03-15T10:30:00Z")
@@ -737,52 +751,81 @@ date.to_input_value(d) // "2024-03-15" (input[type="date"]용)
737
751
  date.from_input_value(s) // Option(JsDate)
738
752
  ```
739
753
 
740
- ### 7.12 Big (`glendix/mendix/big`)
754
+ ### 8.12 Decimal (`mendraw/mendix/decimal`)
741
755
 
742
- Big.js 래퍼. Mendix Decimal 타입 처리용:
756
+ Mendix Decimal 속성의 경계 변환 전용. Big.js 객체 Gleam 변환만 담당합니다.
757
+ 산술/비교 연산이 필요하면 위젯 프로젝트에서 [dee](https://hexdocs.pm/dee/) 패키지를 추가하세요.
743
758
 
744
759
  ```gleam
745
- 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
+ ```
746
773
 
747
- big.from_string("123.456")
748
- big.from_int(100)
774
+ **dee와 함께 사용하는 패턴:**
749
775
 
750
- big.add(a, b) big.subtract(a, b)
751
- big.multiply(a, b) big.divide(a, b)
752
- 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
+ ```
753
788
 
754
- big.compare(a, b) // gleam/order.Order
755
- big.equal(a, b) // Bool
789
+ ### 8.12.1 조건부 CSS 클래스 (`cx`)
756
790
 
757
- big.to_string(a) big.to_float(a)
758
- 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일 때)
759
802
  ```
760
803
 
761
- ### 7.13 File, Icon, Formatter
804
+ ### 8.13 File, Icon, Formatter
762
805
 
763
806
  ```gleam
764
807
  // FileValue / WebImage
765
- import glendix/mendix/file
808
+ import mendraw/mendix/file
766
809
  file.uri(file_val) // String
767
810
  file.name(file_val) // Option(String)
768
811
  file.image_uri(img) // String
769
812
  file.alt_text(img) // Option(String)
770
813
 
771
814
  // WebIcon
772
- import glendix/mendix/icon
815
+ import mendraw/mendix/icon
773
816
  icon.icon_type(i) // Glyph | Image | IconFont
774
817
  icon.icon_class(i) // String
775
818
  icon.icon_url(i) // String
776
819
 
777
820
  // ValueFormatter
778
- import glendix/mendix/formatter
821
+ import mendraw/mendix/formatter
779
822
  formatter.format(fmt, Some(value)) // String
780
823
  formatter.parse(fmt, "123.45") // Result(Option(a), Nil)
781
824
  ```
782
825
 
783
826
  ---
784
827
 
785
- ## 8. Editor Configuration (`glendix/editor_config`)
828
+ ## 9. Editor Configuration (`glendix/editor_config`)
786
829
 
787
830
  Studio Pro의 editorConfig 로직을 Gleam으로 작성합니다.
788
831
 
@@ -790,8 +833,8 @@ Studio Pro의 editorConfig 로직을 Gleam으로 작성합니다.
790
833
 
791
834
  ```gleam
792
835
  import glendix/editor_config.{type Properties}
793
- import glendix/mendix
794
- import glendix/mendix.{type JsProps}
836
+ import mendraw/mendix
837
+ import mendraw/mendix.{type JsProps}
795
838
 
796
839
  const bar_keys = "barWidth,barColor"
797
840
  const line_keys = "lineStyle,lineCurve"
@@ -829,7 +872,7 @@ pub fn get_properties(
829
872
 
830
873
  ---
831
874
 
832
- ## 9. JS Interop Escape Hatch (`glendix/js/*`)
875
+ ## 10. JS Interop Escape Hatch (`glendix/js/*`)
833
876
 
834
877
  외부 JS 라이브러리(SpreadJS, Chart.js 등)와 직접 상호작용할 때 사용합니다. 모든 값은 `Dynamic` 타입. 가능하면 `glendix/binding`을 먼저 고려하세요.
835
878
 
@@ -841,14 +884,14 @@ array.to_list(js_arr) // JS Array → Gleam List
841
884
 
842
885
  // 객체
843
886
  import glendix/js/object
844
- object.object([#("width", dynamic.from(800))])
887
+ object.object([#("width", dynamic.int(800))])
845
888
  object.get(obj, "key")
846
- object.set(obj, "key", dynamic.from(val))
889
+ object.set(obj, "key", dynamic.string(val))
847
890
  object.call_method(obj, "method", [arg1, arg2])
848
891
 
849
892
  // JSON
850
893
  import glendix/js/json
851
- json.stringify(dynamic.from(data)) // String
894
+ json.stringify(data) // String
852
895
  json.parse("{\"k\":\"v\"}") // Result(Dynamic, String)
853
896
 
854
897
  // Promise
@@ -876,35 +919,98 @@ timer.clear_interval(id)
876
919
 
877
920
  ---
878
921
 
879
- ## 10. 빌드 & 도구
922
+ ## 11. 빌드 & 도구
880
923
 
881
924
  | 명령어 | 설명 |
882
925
  |--------|------|
883
926
  | `gleam build` | 컴파일 |
884
- | `gleam run -m glendix/install` | 의존성 + 바인딩 + 위젯 .gleam 생성 |
927
+ | `gleam run -m glendix/install` | 의존성 + mendraw 위젯 다운로드 + 바인딩 + 위젯 .gleam 생성 |
885
928
  | `gleam run -m glendix/dev` | 개발 서버 (HMR) |
886
929
  | `gleam run -m glendix/build` | 프로덕션 빌드 (.mpk) |
887
930
  | `gleam run -m glendix/start` | Mendix 테스트 프로젝트 연동 |
888
931
  | `gleam run -m glendix/release` | 릴리즈 빌드 |
889
932
  | `gleam run -m glendix/lint` | ESLint 검사 |
890
933
  | `gleam run -m glendix/lint_fix` | ESLint 자동 수정 |
891
- | `gleam run -m glendix/marketplace` | Marketplace 위젯 다운로드 (인터랙티브) |
934
+ | `gleam run -m mendraw/marketplace` | Marketplace 위젯 다운로드 (인터랙티브, gleam.toml 자동 기록) |
892
935
  | `gleam run -m glendix/define` | 위젯 프로퍼티 정의 TUI 에디터 |
893
936
 
894
- **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`를 읽어 출력 경로 결정
895
1001
 
896
1002
  ---
897
1003
 
898
- ## 11. 실전 패턴
1004
+ ## 13. 실전 패턴
899
1005
 
900
- ### 11.1 폼 입력 위젯
1006
+ ### 12.1 폼 입력 위젯
901
1007
 
902
1008
  ```gleam
903
1009
  import gleam/option.{None, Some}
904
- import glendix/mendix
905
- import glendix/mendix.{type JsProps}
906
- import glendix/mendix/action
907
- 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
908
1014
  import redraw.{type Element}
909
1015
  import redraw/dom/attribute
910
1016
  import redraw/dom/events
@@ -948,16 +1054,16 @@ pub fn text_input_widget(props: JsProps) -> Element {
948
1054
  }
949
1055
  ```
950
1056
 
951
- ### 11.2 데이터 테이블 위젯
1057
+ ### 12.2 데이터 테이블 위젯
952
1058
 
953
1059
  ```gleam
954
1060
  import gleam/list
955
1061
  import gleam/option.{None, Some}
956
- import glendix/mendix
957
- import glendix/mendix.{type JsProps}
958
- import glendix/mendix/editable_value as ev
959
- import glendix/mendix/list_attribute as la
960
- 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
961
1067
  import redraw.{type Element}
962
1068
  import redraw/dom/attribute
963
1069
  import redraw/dom/html
@@ -982,14 +1088,14 @@ pub fn data_table(props: JsProps) -> Element {
982
1088
  }
983
1089
  ```
984
1090
 
985
- ### 11.3 검색 가능한 리스트
1091
+ ### 12.3 검색 가능한 리스트
986
1092
 
987
1093
  ```gleam
988
1094
  import gleam/option.{None, Some}
989
- import glendix/mendix
990
- import glendix/mendix.{type JsProps}
991
- import glendix/mendix/filter
992
- 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
993
1099
  import redraw.{type Element}
994
1100
  import redraw/dom/attribute
995
1101
  import redraw/dom/events
@@ -1024,23 +1130,24 @@ pub fn searchable_list(props: JsProps) -> Element {
1024
1130
 
1025
1131
  ---
1026
1132
 
1027
- ## 12. 절대 하지 말 것
1133
+ ## 14. 절대 하지 말 것
1028
1134
 
1029
1135
  | 실수 | 올바른 방법 |
1030
1136
  |------|------------|
1031
1137
  | `import glendix/react` | **삭제됨.** `import redraw` 사용 |
1138
+ | `react`/`react-dom`을 `dependencies`에 추가 | `pluggable-widgets-tools`가 제공. 직접 넣으면 버전 충돌 |
1032
1139
  | 조건 안에서 Hook 호출 | Hook은 항상 함수 최상위에서 호출 |
1033
1140
  | `html.text("")`로 빈 렌더링 | `html.none()` 사용 |
1034
1141
  | `binding.resolve(m(), "pie_chart")` | JS 원본 이름 유지: `"PieChart"` |
1035
- | 외부 React 컴포넌트용 `.mjs` 직접 작성 | `bindings.json` + `glendix/binding` 사용 |
1036
- | `.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` 사용 |
1037
1144
  | `date.month()`에 0-based 값 전달 | glendix가 1↔0 자동 변환 |
1038
1145
  | Editor config에서 Gleam List 사용 | 콤마 구분 String 사용 (Jint 호환) |
1039
1146
  | FFI `.mjs`에 비즈니스 로직 | `.gleam`에 작성. `.mjs`는 JS 런타임 접근만 |
1040
1147
 
1041
1148
  ---
1042
1149
 
1043
- ## 13. 트러블슈팅
1150
+ ## 15. 트러블슈팅
1044
1151
 
1045
1152
  | 문제 | 원인 | 해결 |
1046
1153
  |------|------|------|
@@ -1048,9 +1155,8 @@ pub fn searchable_list(props: JsProps) -> Element {
1048
1155
  | `Cannot read property of undefined` | 없는 prop 접근 | `get_prop` (Option) 사용, prop 이름 확인 |
1049
1156
  | Hook 순서 에러 | 조건부 Hook 호출 | 항상 동일 순서로 호출 (React Rules) |
1050
1157
  | 바인딩 미생성 | `binding_ffi.mjs` 스텁 상태 | `gleam run -m glendix/install` |
1051
- | 위젯 바인딩 미생성 | `widget_ffi.mjs` 스텁 상태 | `widgets/`에 `.mpk` 배치 후 install |
1158
+ | 위젯 바인딩 미생성 | `widget_ffi.mjs` 스텁 상태 | `gleam.toml [tools.mendraw.widgets.*]` 설정 후 install |
1052
1159
  | `could not be resolved` | npm 패키지 미설치 | `npm install <패키지명>` |
1053
1160
  | `.env` PAT 오류 | marketplace 인증 실패 | [Developer Settings](https://user-settings.mendix.com/link/developersettings)에서 PAT 재발급 |
1054
- | Playwright 오류 | chromium 미설치 | `npx playwright install chromium` |
1055
1161
 
1056
1162
  ---