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 +1 -1
- package/template/src/widget/__widget_name__.gleam +8 -10
- package/template/src/widget/react/event.gleam +33 -0
- package/template/src/widget/react/hook.gleam +47 -0
- package/template/src/widget/react/html.gleam +156 -0
- package/template/src/widget/react/prop.gleam +106 -0
- package/template/src/widget/react.gleam +81 -0
- package/template/src/widget/react_ffi.mjs +184 -0
- package/template/src/widget/__widget_name___ffi.mjs +0 -13
package/package.json
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
// Mendix Pluggable Widget - "Hello World"
|
|
2
2
|
// React 함수형 컴포넌트: fn(JsProps) -> ReactElement
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
import widget/react.{type JsProps, type ReactElement}
|
|
5
|
+
import widget/react/html
|
|
6
|
+
import widget/react/prop
|
|
6
7
|
|
|
7
|
-
|
|
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
|
-
|
|
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
|
-
}
|