create-mendix-widget-gleam 1.0.0 → 1.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-mendix-widget-gleam",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Scaffold a Mendix Pluggable Widget powered by Gleam",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -1,20 +1,18 @@
1
1
  // Mendix Pluggable Widget - "Hello World"
2
2
  // React 함수형 컴포넌트: fn(JsProps) -> ReactElement
3
3
 
4
- // 외부 타입 (JS 값의 opaque 핸들)
5
- pub type ReactElement
4
+ import widget/react.{type JsProps, type ReactElement}
5
+ import widget/react/html
6
+ import widget/react/prop
6
7
 
7
- pub type JsProps
8
-
9
- // FFI 바인딩
10
- @external(javascript, "./{{SNAKE_CASE}}_ffi.mjs", "create_div")
11
- fn create_div(class_name: String, text_content: String) -> ReactElement
12
-
13
- @external(javascript, "./{{SNAKE_CASE}}_ffi.mjs", "get_string_prop")
8
+ // Props에서 문자열 속성값 추출
9
+ @external(javascript, "./react_ffi.mjs", "get_string_prop")
14
10
  fn get_string_prop(props: JsProps, key: String) -> String
15
11
 
16
12
  /// 위젯 메인 함수 - Mendix 런타임이 React 컴포넌트로 호출
17
13
  pub fn widget(props: JsProps) -> ReactElement {
18
14
  let sample_text = get_string_prop(props, "sampleText")
19
- create_div("widget-hello-world", "Hello " <> sample_text)
15
+ html.div(prop.new() |> prop.class("widget-hello-world"), [
16
+ react.text("Hello " <> sample_text),
17
+ ])
20
18
  }
@@ -0,0 +1,33 @@
1
+ // React 이벤트 타입 + 값 추출 함수
2
+
3
+ // === 이벤트 타입 (opaque) ===
4
+
5
+ pub type Event
6
+
7
+ pub type MouseEvent
8
+
9
+ pub type ChangeEvent
10
+
11
+ pub type KeyboardEvent
12
+
13
+ pub type FormEvent
14
+
15
+ pub type FocusEvent
16
+
17
+ // === 값 추출 ===
18
+
19
+ /// input/textarea의 현재 값 추출
20
+ @external(javascript, "../react_ffi.mjs", "get_target_value")
21
+ pub fn target_value(event: event) -> String
22
+
23
+ /// 기본 동작 방지
24
+ @external(javascript, "../react_ffi.mjs", "prevent_default")
25
+ pub fn prevent_default(event: event) -> Nil
26
+
27
+ /// 이벤트 전파 중지
28
+ @external(javascript, "../react_ffi.mjs", "stop_propagation")
29
+ pub fn stop_propagation(event: event) -> Nil
30
+
31
+ /// 키보드 이벤트의 키 값
32
+ @external(javascript, "../react_ffi.mjs", "get_event_key")
33
+ pub fn key(event: event) -> String
@@ -0,0 +1,47 @@
1
+ // React Hooks - useState, useEffect 등
2
+
3
+ import widget/react.{type Ref}
4
+
5
+ // === useState ===
6
+
7
+ /// 상태 훅. #(현재값, 세터함수) 튜플 반환
8
+ @external(javascript, "../react_ffi.mjs", "use_state")
9
+ pub fn use_state(initial: a) -> #(a, fn(a) -> Nil)
10
+
11
+ // === useEffect ===
12
+
13
+ /// 의존성 배열과 함께 실행
14
+ @external(javascript, "../react_ffi.mjs", "use_effect")
15
+ pub fn use_effect(effect_fn: fn() -> Nil, deps: List(a)) -> Nil
16
+
17
+ /// 매 렌더링마다 실행 (deps 없음)
18
+ @external(javascript, "../react_ffi.mjs", "use_effect_always")
19
+ pub fn use_effect_always(effect_fn: fn() -> Nil) -> Nil
20
+
21
+ /// 마운트 시 한 번만 실행 (deps = [])
22
+ @external(javascript, "../react_ffi.mjs", "use_effect_once")
23
+ pub fn use_effect_once(effect_fn: fn() -> Nil) -> Nil
24
+
25
+ // === useMemo / useCallback ===
26
+
27
+ /// 메모이제이션된 값 계산
28
+ @external(javascript, "../react_ffi.mjs", "use_memo")
29
+ pub fn use_memo(compute: fn() -> a, deps: List(b)) -> a
30
+
31
+ /// 메모이제이션된 콜백
32
+ @external(javascript, "../react_ffi.mjs", "use_callback")
33
+ pub fn use_callback(callback: fn(a) -> b, deps: List(c)) -> fn(a) -> b
34
+
35
+ // === useRef ===
36
+
37
+ /// ref 생성
38
+ @external(javascript, "../react_ffi.mjs", "use_ref")
39
+ pub fn use_ref(initial: a) -> Ref(a)
40
+
41
+ /// ref 현재 값 읽기
42
+ @external(javascript, "../react_ffi.mjs", "get_ref_current")
43
+ pub fn get_ref(ref: Ref(a)) -> a
44
+
45
+ /// ref 현재 값 설정
46
+ @external(javascript, "../react_ffi.mjs", "set_ref_current")
47
+ pub fn set_ref(ref: Ref(a), value: a) -> Nil
@@ -0,0 +1,156 @@
1
+ // HTML 태그 편의 함수 - 순수 Gleam, FFI 없음
2
+ // react.el / react.el_ / react.void 래퍼
3
+
4
+ import widget/react.{type Props, type ReactElement}
5
+
6
+ // === 컨테이너 ===
7
+
8
+ pub fn div(props: Props, children: List(ReactElement)) -> ReactElement {
9
+ react.el("div", props, children)
10
+ }
11
+
12
+ pub fn div_(children: List(ReactElement)) -> ReactElement {
13
+ react.el_("div", children)
14
+ }
15
+
16
+ pub fn span(props: Props, children: List(ReactElement)) -> ReactElement {
17
+ react.el("span", props, children)
18
+ }
19
+
20
+ pub fn span_(children: List(ReactElement)) -> ReactElement {
21
+ react.el_("span", children)
22
+ }
23
+
24
+ pub fn section(props: Props, children: List(ReactElement)) -> ReactElement {
25
+ react.el("section", props, children)
26
+ }
27
+
28
+ pub fn main(props: Props, children: List(ReactElement)) -> ReactElement {
29
+ react.el("main", props, children)
30
+ }
31
+
32
+ // === 텍스트 ===
33
+
34
+ pub fn p(props: Props, children: List(ReactElement)) -> ReactElement {
35
+ react.el("p", props, children)
36
+ }
37
+
38
+ pub fn p_(children: List(ReactElement)) -> ReactElement {
39
+ react.el_("p", children)
40
+ }
41
+
42
+ pub fn h1(props: Props, children: List(ReactElement)) -> ReactElement {
43
+ react.el("h1", props, children)
44
+ }
45
+
46
+ pub fn h2(props: Props, children: List(ReactElement)) -> ReactElement {
47
+ react.el("h2", props, children)
48
+ }
49
+
50
+ pub fn h3(props: Props, children: List(ReactElement)) -> ReactElement {
51
+ react.el("h3", props, children)
52
+ }
53
+
54
+ pub fn h4(props: Props, children: List(ReactElement)) -> ReactElement {
55
+ react.el("h4", props, children)
56
+ }
57
+
58
+ pub fn h5(props: Props, children: List(ReactElement)) -> ReactElement {
59
+ react.el("h5", props, children)
60
+ }
61
+
62
+ pub fn h6(props: Props, children: List(ReactElement)) -> ReactElement {
63
+ react.el("h6", props, children)
64
+ }
65
+
66
+ // === 리스트 ===
67
+
68
+ pub fn ul(props: Props, children: List(ReactElement)) -> ReactElement {
69
+ react.el("ul", props, children)
70
+ }
71
+
72
+ pub fn ol(props: Props, children: List(ReactElement)) -> ReactElement {
73
+ react.el("ol", props, children)
74
+ }
75
+
76
+ pub fn li(props: Props, children: List(ReactElement)) -> ReactElement {
77
+ react.el("li", props, children)
78
+ }
79
+
80
+ // === 폼 ===
81
+
82
+ pub fn form(props: Props, children: List(ReactElement)) -> ReactElement {
83
+ react.el("form", props, children)
84
+ }
85
+
86
+ pub fn button(props: Props, children: List(ReactElement)) -> ReactElement {
87
+ react.el("button", props, children)
88
+ }
89
+
90
+ pub fn label(props: Props, children: List(ReactElement)) -> ReactElement {
91
+ react.el("label", props, children)
92
+ }
93
+
94
+ pub fn input(props: Props) -> ReactElement {
95
+ react.void("input", props)
96
+ }
97
+
98
+ pub fn textarea(props: Props, children: List(ReactElement)) -> ReactElement {
99
+ react.el("textarea", props, children)
100
+ }
101
+
102
+ pub fn select(props: Props, children: List(ReactElement)) -> ReactElement {
103
+ react.el("select", props, children)
104
+ }
105
+
106
+ pub fn option(props: Props, children: List(ReactElement)) -> ReactElement {
107
+ react.el("option", props, children)
108
+ }
109
+
110
+ // === 테이블 ===
111
+
112
+ pub fn table(props: Props, children: List(ReactElement)) -> ReactElement {
113
+ react.el("table", props, children)
114
+ }
115
+
116
+ pub fn thead(props: Props, children: List(ReactElement)) -> ReactElement {
117
+ react.el("thead", props, children)
118
+ }
119
+
120
+ pub fn tbody(props: Props, children: List(ReactElement)) -> ReactElement {
121
+ react.el("tbody", props, children)
122
+ }
123
+
124
+ pub fn tr(props: Props, children: List(ReactElement)) -> ReactElement {
125
+ react.el("tr", props, children)
126
+ }
127
+
128
+ pub fn td(props: Props, children: List(ReactElement)) -> ReactElement {
129
+ react.el("td", props, children)
130
+ }
131
+
132
+ pub fn th(props: Props, children: List(ReactElement)) -> ReactElement {
133
+ react.el("th", props, children)
134
+ }
135
+
136
+ // === 기타 ===
137
+
138
+ pub fn a(props: Props, children: List(ReactElement)) -> ReactElement {
139
+ react.el("a", props, children)
140
+ }
141
+
142
+ pub fn img(props: Props) -> ReactElement {
143
+ react.void("img", props)
144
+ }
145
+
146
+ pub fn br() -> ReactElement {
147
+ react.void("br", empty_props())
148
+ }
149
+
150
+ pub fn hr(props: Props) -> ReactElement {
151
+ react.void("hr", props)
152
+ }
153
+
154
+ // br에서 사용할 빈 props (순환 의존 방지용 내부 FFI)
155
+ @external(javascript, "../react_ffi.mjs", "empty_props")
156
+ fn empty_props() -> Props
@@ -0,0 +1,106 @@
1
+ // Props 빌더 - 파이프라인 API로 React props 구성
2
+
3
+ import widget/react.{type Props, type Ref}
4
+
5
+ // === Style 타입 ===
6
+
7
+ /// CSS 스타일 객체
8
+ pub type Style
9
+
10
+ // === Props 생성 ===
11
+
12
+ /// 빈 props 객체 생성
13
+ @external(javascript, "../react_ffi.mjs", "empty_props")
14
+ pub fn new() -> Props
15
+
16
+ // === 속성 설정 (모두 Props -> Props, 파이프라인 가능) ===
17
+
18
+ /// 문자열 속성
19
+ @external(javascript, "../react_ffi.mjs", "set_prop_string")
20
+ pub fn string(props: Props, key: String, value: String) -> Props
21
+
22
+ /// 정수 속성
23
+ @external(javascript, "../react_ffi.mjs", "set_prop_int")
24
+ pub fn int(props: Props, key: String, value: Int) -> Props
25
+
26
+ /// 실수 속성
27
+ @external(javascript, "../react_ffi.mjs", "set_prop_float")
28
+ pub fn float(props: Props, key: String, value: Float) -> Props
29
+
30
+ /// 불리언 속성
31
+ @external(javascript, "../react_ffi.mjs", "set_prop_bool")
32
+ pub fn bool(props: Props, key: String, value: Bool) -> Props
33
+
34
+ /// 임의 타입 속성
35
+ @external(javascript, "../react_ffi.mjs", "set_prop_any")
36
+ pub fn any(props: Props, key: String, value: a) -> Props
37
+
38
+ // === CSS 클래스 ===
39
+
40
+ /// className 설정
41
+ @external(javascript, "../react_ffi.mjs", "set_class_name")
42
+ pub fn class(props: Props, class_name: String) -> Props
43
+
44
+ /// 여러 클래스명을 공백으로 결합
45
+ @external(javascript, "../react_ffi.mjs", "set_class_names")
46
+ pub fn classes(props: Props, class_names: List(String)) -> Props
47
+
48
+ // === 특수 속성 ===
49
+
50
+ /// key 설정
51
+ @external(javascript, "../react_ffi.mjs", "set_key")
52
+ pub fn key(props: Props, key: String) -> Props
53
+
54
+ /// ref 설정
55
+ @external(javascript, "../react_ffi.mjs", "set_ref")
56
+ pub fn ref(props: Props, ref: Ref(a)) -> Props
57
+
58
+ /// style 객체 설정
59
+ @external(javascript, "../react_ffi.mjs", "set_style")
60
+ pub fn style(props: Props, style: Style) -> Props
61
+
62
+ // === 이벤트 핸들러 ===
63
+
64
+ /// 범용 이벤트 핸들러
65
+ @external(javascript, "../react_ffi.mjs", "set_prop_handler")
66
+ pub fn on(props: Props, event_name: String, handler: fn(e) -> Nil) -> Props
67
+
68
+ /// onClick
69
+ pub fn on_click(props: Props, handler: fn(e) -> Nil) -> Props {
70
+ on(props, "onClick", handler)
71
+ }
72
+
73
+ /// onChange
74
+ pub fn on_change(props: Props, handler: fn(e) -> Nil) -> Props {
75
+ on(props, "onChange", handler)
76
+ }
77
+
78
+ /// onSubmit
79
+ pub fn on_submit(props: Props, handler: fn(e) -> Nil) -> Props {
80
+ on(props, "onSubmit", handler)
81
+ }
82
+
83
+ /// onKeyDown
84
+ pub fn on_key_down(props: Props, handler: fn(e) -> Nil) -> Props {
85
+ on(props, "onKeyDown", handler)
86
+ }
87
+
88
+ /// onFocus
89
+ pub fn on_focus(props: Props, handler: fn(e) -> Nil) -> Props {
90
+ on(props, "onFocus", handler)
91
+ }
92
+
93
+ /// onBlur
94
+ pub fn on_blur(props: Props, handler: fn(e) -> Nil) -> Props {
95
+ on(props, "onBlur", handler)
96
+ }
97
+
98
+ // === Style 빌더 ===
99
+
100
+ /// 빈 스타일 객체 생성
101
+ @external(javascript, "../react_ffi.mjs", "empty_style")
102
+ pub fn new_style() -> Style
103
+
104
+ /// 스타일 속성 설정
105
+ @external(javascript, "../react_ffi.mjs", "set_style_prop")
106
+ pub fn set(style: Style, key: String, value: String) -> Style
@@ -0,0 +1,81 @@
1
+ // React 핵심 타입 + createElement + fragment/text/none
2
+
3
+ import gleam/option.{type Option, None, Some}
4
+
5
+ // === Opaque 타입 ===
6
+
7
+ /// React가 렌더링하는 요소
8
+ pub type ReactElement
9
+
10
+ /// Mendix가 전달하는 props 객체
11
+ pub type JsProps
12
+
13
+ /// React 컴포넌트 참조
14
+ pub type Component
15
+
16
+ /// React ref 객체
17
+ pub type Ref(a)
18
+
19
+ // Props 타입 (react/prop 모듈에서 빌더 제공)
20
+ pub type Props
21
+
22
+ // === 요소 생성 FFI 바인딩 ===
23
+
24
+ /// 범용 HTML 요소 생성
25
+ @external(javascript, "./react_ffi.mjs", "create_element")
26
+ pub fn el(tag: String, props: Props, children: List(ReactElement)) -> ReactElement
27
+
28
+ /// props 없이 자식만으로 요소 생성
29
+ @external(javascript, "./react_ffi.mjs", "create_element_no_props")
30
+ pub fn el_(tag: String, children: List(ReactElement)) -> ReactElement
31
+
32
+ /// self-closing 요소 (input, img, br 등)
33
+ @external(javascript, "./react_ffi.mjs", "create_void_element")
34
+ pub fn void(tag: String, props: Props) -> ReactElement
35
+
36
+ /// React 컴포넌트 합성
37
+ @external(javascript, "./react_ffi.mjs", "create_component")
38
+ pub fn component(
39
+ comp: Component,
40
+ props: Props,
41
+ children: List(ReactElement),
42
+ ) -> ReactElement
43
+
44
+ // === Fragment / null / text ===
45
+
46
+ /// Fragment로 여러 자식을 감싸기
47
+ @external(javascript, "./react_ffi.mjs", "fragment")
48
+ pub fn fragment(children: List(ReactElement)) -> ReactElement
49
+
50
+ /// key가 있는 Fragment
51
+ @external(javascript, "./react_ffi.mjs", "keyed_fragment")
52
+ pub fn keyed_fragment(key: String, children: List(ReactElement)) -> ReactElement
53
+
54
+ /// null 렌더링 (아무것도 표시하지 않음)
55
+ @external(javascript, "./react_ffi.mjs", "null_element")
56
+ pub fn none() -> ReactElement
57
+
58
+ /// 텍스트 노드
59
+ @external(javascript, "./react_ffi.mjs", "text")
60
+ pub fn text(content: String) -> ReactElement
61
+
62
+ // === 순수 Gleam 헬퍼 ===
63
+
64
+ /// Bool 기반 조건부 렌더링
65
+ pub fn when(condition: Bool, element_fn: fn() -> ReactElement) -> ReactElement {
66
+ case condition {
67
+ True -> element_fn()
68
+ False -> none()
69
+ }
70
+ }
71
+
72
+ /// Option 기반 조건부 렌더링
73
+ pub fn when_some(
74
+ option: Option(a),
75
+ render_fn: fn(a) -> ReactElement,
76
+ ) -> ReactElement {
77
+ case option {
78
+ Some(value) -> render_fn(value)
79
+ None -> none()
80
+ }
81
+ }
@@ -0,0 +1,184 @@
1
+ // React FFI 어댑터 - 모든 React 원시 함수를 Gleam에 노출
2
+ import * as React from "react";
3
+ import { toList } from "../gleam.mjs";
4
+
5
+ // === 요소 생성 ===
6
+
7
+ // 범용 요소 생성: tag + props + children(Gleam List)
8
+ export function create_element(tag, props, children) {
9
+ return React.createElement(tag, props, ...children.toArray());
10
+ }
11
+
12
+ // props 없이 자식만
13
+ export function create_element_no_props(tag, children) {
14
+ return React.createElement(tag, null, ...children.toArray());
15
+ }
16
+
17
+ // self-closing 요소 (input, img, br 등)
18
+ export function create_void_element(tag, props) {
19
+ return React.createElement(tag, props);
20
+ }
21
+
22
+ // React 컴포넌트 합성
23
+ export function create_component(component, props, children) {
24
+ return React.createElement(component, props, ...children.toArray());
25
+ }
26
+
27
+ // === Fragment / null / text ===
28
+
29
+ export function fragment(children) {
30
+ return React.createElement(React.Fragment, null, ...children.toArray());
31
+ }
32
+
33
+ export function keyed_fragment(key, children) {
34
+ return React.createElement(React.Fragment, { key }, ...children.toArray());
35
+ }
36
+
37
+ export function null_element() {
38
+ return null;
39
+ }
40
+
41
+ export function text(content) {
42
+ return content;
43
+ }
44
+
45
+ // === Props 빌더 ===
46
+
47
+ export function empty_props() {
48
+ return {};
49
+ }
50
+
51
+ export function set_prop_string(props, key, value) {
52
+ return { ...props, [key]: value };
53
+ }
54
+
55
+ export function set_prop_int(props, key, value) {
56
+ return { ...props, [key]: value };
57
+ }
58
+
59
+ export function set_prop_float(props, key, value) {
60
+ return { ...props, [key]: value };
61
+ }
62
+
63
+ export function set_prop_bool(props, key, value) {
64
+ return { ...props, [key]: value };
65
+ }
66
+
67
+ export function set_prop_handler(props, key, handler) {
68
+ return { ...props, [key]: handler };
69
+ }
70
+
71
+ export function set_prop_any(props, key, value) {
72
+ return { ...props, [key]: value };
73
+ }
74
+
75
+ export function set_class_name(props, class_name) {
76
+ return { ...props, className: class_name };
77
+ }
78
+
79
+ export function set_class_names(props, class_names) {
80
+ return { ...props, className: class_names.toArray().join(" ") };
81
+ }
82
+
83
+ export function set_key(props, key) {
84
+ return { ...props, key };
85
+ }
86
+
87
+ export function set_ref(props, ref) {
88
+ return { ...props, ref };
89
+ }
90
+
91
+ export function set_style(props, style_obj) {
92
+ return { ...props, style: style_obj };
93
+ }
94
+
95
+ // === Style 빌더 ===
96
+
97
+ export function empty_style() {
98
+ return {};
99
+ }
100
+
101
+ export function set_style_prop(style, key, value) {
102
+ return { ...style, [key]: value };
103
+ }
104
+
105
+ // === Props 읽기 (Mendix props에서 값 추출) ===
106
+
107
+ export function get_string_prop(props, key) {
108
+ const value = props[key];
109
+ return value !== undefined && value !== null ? String(value) : "";
110
+ }
111
+
112
+ export function get_prop(props, key) {
113
+ return props[key];
114
+ }
115
+
116
+ export function has_prop(props, key) {
117
+ return key in props && props[key] !== undefined && props[key] !== null;
118
+ }
119
+
120
+ // === React Hooks ===
121
+
122
+ export function use_state(initial) {
123
+ return React.useState(initial);
124
+ }
125
+
126
+ export function use_effect(effect_fn, deps) {
127
+ React.useEffect(effect_fn, deps.toArray());
128
+ }
129
+
130
+ export function use_effect_always(effect_fn) {
131
+ React.useEffect(effect_fn);
132
+ }
133
+
134
+ export function use_effect_once(effect_fn) {
135
+ React.useEffect(effect_fn, []);
136
+ }
137
+
138
+ export function use_memo(compute_fn, deps) {
139
+ return React.useMemo(compute_fn, deps.toArray());
140
+ }
141
+
142
+ export function use_callback(callback, deps) {
143
+ return React.useCallback(callback, deps.toArray());
144
+ }
145
+
146
+ export function use_ref(initial) {
147
+ return React.useRef(initial);
148
+ }
149
+
150
+ export function get_ref_current(ref) {
151
+ return ref.current;
152
+ }
153
+
154
+ export function set_ref_current(ref, value) {
155
+ ref.current = value;
156
+ }
157
+
158
+ // === 이벤트 ===
159
+
160
+ export function get_target_value(event) {
161
+ return event.target.value ?? "";
162
+ }
163
+
164
+ export function prevent_default(event) {
165
+ event.preventDefault();
166
+ }
167
+
168
+ export function stop_propagation(event) {
169
+ event.stopPropagation();
170
+ }
171
+
172
+ export function get_event_key(event) {
173
+ return event.key;
174
+ }
175
+
176
+ // === 유틸리티 ===
177
+
178
+ export function list_to_array(gleam_list) {
179
+ return gleam_list.toArray();
180
+ }
181
+
182
+ export function array_to_list(js_array) {
183
+ return toList(js_array);
184
+ }
@@ -1,13 +0,0 @@
1
- // React FFI 어댑터 - React 원시 함수만 노출
2
- import * as React from "react";
3
-
4
- // div 요소에 텍스트 자식을 렌더링
5
- export function create_div(class_name, text_content) {
6
- return React.createElement("div", { className: class_name }, text_content);
7
- }
8
-
9
- // props 객체에서 문자열 속성값 추출
10
- export function get_string_prop(props, key) {
11
- const value = props[key];
12
- return value !== undefined && value !== null ? String(value) : "";
13
- }