create-mendix-widget-gleam 2.0.19 → 3.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.
Files changed (71) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/src/i18n.mjs +394 -334
  4. package/src/index.mjs +284 -231
  5. package/src/licenses.mjs +147 -0
  6. package/src/prompts.mjs +247 -142
  7. package/src/scaffold.mjs +108 -97
  8. package/src/templates/claude_md.mjs +7 -6
  9. package/src/templates/readme_md.mjs +88 -76
  10. package/src/templates/widgets_readme.mjs +18 -15
  11. package/template/docs/glendix_guide.md +1050 -2538
  12. package/template/gleam.toml +5 -2
  13. package/template/package.json +10 -7
  14. package/template/src/__WidgetName__.xml +1 -1
  15. package/template/src/__widget_name__.gleam +3 -3
  16. package/template/src/components/hello_world.gleam +5 -5
  17. package/template/src/editor_config.gleam +4 -8
  18. package/template/src/editor_preview.gleam +3 -3
  19. package/template/src/package.xml +2 -2
  20. package/tui/build/dev/javascript/etch/_gleam_artefacts/etch@command.cache +0 -0
  21. package/tui/build/dev/javascript/etch/_gleam_artefacts/etch@cursor.cache +0 -0
  22. package/tui/build/dev/javascript/etch/_gleam_artefacts/etch@cursor.cache_meta +0 -0
  23. package/tui/build/dev/javascript/etch/_gleam_artefacts/etch@event.cache +0 -0
  24. package/tui/build/dev/javascript/etch/_gleam_artefacts/etch@internal@consts.cache +0 -0
  25. package/tui/build/dev/javascript/etch/_gleam_artefacts/etch@stdout.cache +0 -0
  26. package/tui/build/dev/javascript/etch/_gleam_artefacts/etch@style.cache +0 -0
  27. package/tui/build/dev/javascript/etch/_gleam_artefacts/etch@terminal.cache +0 -0
  28. package/tui/build/dev/javascript/etch/etch/event.mjs +36 -30
  29. package/tui/build/dev/javascript/gleam_erlang/_gleam_artefacts/gleam@erlang@application.cache +0 -0
  30. package/tui/build/dev/javascript/gleam_erlang/_gleam_artefacts/gleam@erlang@atom.cache +0 -0
  31. package/tui/build/dev/javascript/gleam_erlang/_gleam_artefacts/gleam@erlang@charlist.cache +0 -0
  32. package/tui/build/dev/javascript/gleam_erlang/_gleam_artefacts/gleam@erlang@node.cache +0 -0
  33. package/tui/build/dev/javascript/gleam_erlang/_gleam_artefacts/gleam@erlang@port.cache +0 -0
  34. package/tui/build/dev/javascript/gleam_erlang/_gleam_artefacts/gleam@erlang@process.cache +0 -0
  35. package/tui/build/dev/javascript/gleam_erlang/_gleam_artefacts/gleam@erlang@reference.cache +0 -0
  36. package/tui/build/dev/javascript/gleam_javascript/_gleam_artefacts/gleam@javascript@array.cache +0 -0
  37. package/tui/build/dev/javascript/gleam_javascript/_gleam_artefacts/gleam@javascript@promise.cache +0 -0
  38. package/tui/build/dev/javascript/gleam_javascript/_gleam_artefacts/gleam@javascript@symbol.cache +0 -0
  39. package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@bit_array.cache +0 -0
  40. package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@bool.cache +0 -0
  41. package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@bool.cache_inline +0 -0
  42. package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@bytes_tree.cache +0 -0
  43. package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@dict.cache +0 -0
  44. package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@dynamic.cache +0 -0
  45. package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@dynamic@decode.cache +0 -0
  46. package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@dynamic@decode.cache_meta +0 -0
  47. package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@float.cache +0 -0
  48. package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@function.cache +0 -0
  49. package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@int.cache +0 -0
  50. package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@io.cache +0 -0
  51. package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@list.cache +0 -0
  52. package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@option.cache +0 -0
  53. package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@order.cache +0 -0
  54. package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@pair.cache +0 -0
  55. package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@result.cache +0 -0
  56. package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@result.cache_inline +0 -0
  57. package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@set.cache +0 -0
  58. package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@string.cache +0 -0
  59. package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@string.cache_meta +0 -0
  60. package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@string_tree.cache +0 -0
  61. package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@uri.cache +0 -0
  62. package/tui/build/dev/javascript/gleam_version +1 -1
  63. package/tui/build/dev/javascript/prelude.mjs +10 -26
  64. package/tui/build/dev/javascript/tui/_gleam_artefacts/tui.cache +0 -0
  65. package/tui/build/dev/javascript/tui/_gleam_artefacts/tui.cache_meta +0 -0
  66. package/tui/build/dev/javascript/tui/_gleam_artefacts/tui@prompt.cache +0 -0
  67. package/tui/build/dev/javascript/tui/_gleam_artefacts/tui@prompt.cache_meta +0 -0
  68. package/tui/build/dev/javascript/tui/tui/prompt.mjs +713 -52
  69. package/tui/build/dev/javascript/tui/tui.mjs +438 -51
  70. package/tui/build/dev/javascript/tui/tui_ffi.mjs +12 -0
  71. package/template/LICENSE +0 -15
package/src/scaffold.mjs CHANGED
@@ -1,97 +1,108 @@
1
- /**
2
- * 파일 복사 + 템플릿 치환
3
- */
4
-
5
- import { readdir, readFile, writeFile, mkdir, copyFile } from "node:fs/promises";
6
- import { join, relative } from "node:path";
7
-
8
- /** 바이너리 판별용 확장자 */
9
- const BINARY_EXTS = new Set([".png", ".jpg", ".gif", ".ico", ".woff", ".woff2", ".ttf", ".eot"]);
10
-
11
- /** 재귀적으로 디렉토리 내 모든 파일 경로를 수집 */
12
- async function walkDir(dir) {
13
- const entries = await readdir(dir, { withFileTypes: true });
14
- const files = [];
15
- for (const entry of entries) {
16
- const fullPath = join(dir, entry.name);
17
- if (entry.isDirectory()) {
18
- files.push(...(await walkDir(fullPath)));
19
- } else {
20
- files.push(fullPath);
21
- }
22
- }
23
- return files;
24
- }
25
-
26
- // npm publish 시 제외되는 dotfile을 언더스코어 접두사로 보관하고 복원
27
- const DOTFILE_MAP = { _gitignore: ".gitignore" };
28
-
29
- /** 파일명에서 플레이스홀더 치환 + dotfile 복원 */
30
- function replaceFileName(name, names) {
31
- if (DOTFILE_MAP[name]) return DOTFILE_MAP[name];
32
- return name
33
- .replace(/__WidgetName__/g, names.pascalCase)
34
- .replace(/__widget_name__/g, names.snakeCase);
35
- }
36
-
37
- /** 파일 내용에서 플레이스홀더 치환 */
38
- function replaceContent(content, names, pmConfig, templateComments) {
39
- let result = content
40
- .replace(/\{\{PASCAL_CASE\}\}/g, names.pascalCase)
41
- .replace(/\{\{SNAKE_CASE\}\}/g, names.snakeCase)
42
- .replace(/\{\{LOWERCASE\}\}/g, names.lowerCase)
43
- .replace(/\{\{DISPLAY_NAME\}\}/g, names.displayName)
44
- .replace(/\{\{KEBAB_CASE\}\}/g, names.kebabCase);
45
-
46
- if (templateComments) {
47
- result = result.replace(/\{\{I18N:(\w+)\}\}/g, (_, key) => {
48
- return templateComments[key] ?? `{{I18N:${key}}}`;
49
- });
50
- }
51
-
52
- return result;
53
- }
54
-
55
- /**
56
- * 템플릿을 대상 디렉토리에 스케폴딩
57
- * @param {string} templateDir - 템플릿 디렉토리 경로
58
- * @param {string} targetDir - 생성할 프로젝트 디렉토리 경로
59
- * @param {object} names - 이름 변환 결과
60
- * @param {object} pmConfig - 패키지 매니저 설정
61
- * @param {object} [templateComments] - i18n 템플릿 주석 ({{I18N:*}} 치환용)
62
- */
63
- export async function scaffold(templateDir, targetDir, names, pmConfig, templateComments) {
64
- const files = await walkDir(templateDir);
65
- const created = [];
66
-
67
- for (const srcPath of files) {
68
- // 템플릿 기준 상대 경로
69
- const relPath = relative(templateDir, srcPath);
70
-
71
- // 경로의 부분에서 파일명 치환
72
- const destRelPath = relPath
73
- .split(/[\\/]/)
74
- .map((part) => replaceFileName(part, names))
75
- .join("/");
76
-
77
- const destPath = join(targetDir, destRelPath);
78
- const ext = srcPath.substring(srcPath.lastIndexOf(".")).toLowerCase();
79
-
80
- // 디렉토리 생성
81
- await mkdir(join(destPath, ".."), { recursive: true });
82
-
83
- if (BINARY_EXTS.has(ext)) {
84
- // 바이너리 파일은 그대로 복사
85
- await copyFile(srcPath, destPath);
86
- } else {
87
- // 텍스트 파일은 내용 치환
88
- const content = await readFile(srcPath, "utf-8");
89
- const replaced = replaceContent(content, names, pmConfig, templateComments);
90
- await writeFile(destPath, replaced, "utf-8");
91
- }
92
-
93
- created.push(destRelPath);
94
- }
95
-
96
- return created;
97
- }
1
+ /**
2
+ * 파일 복사 + 템플릿 치환
3
+ */
4
+
5
+ import { readdir, readFile, writeFile, mkdir, copyFile } from "node:fs/promises";
6
+ import { join, relative } from "node:path";
7
+
8
+ /** 바이너리 판별용 확장자 */
9
+ const BINARY_EXTS = new Set([".png", ".jpg", ".gif", ".ico", ".woff", ".woff2", ".ttf", ".eot"]);
10
+
11
+ /** 재귀적으로 디렉토리 내 모든 파일 경로를 수집 */
12
+ async function walkDir(dir) {
13
+ const entries = await readdir(dir, { withFileTypes: true });
14
+ const files = [];
15
+ for (const entry of entries) {
16
+ const fullPath = join(dir, entry.name);
17
+ if (entry.isDirectory()) {
18
+ files.push(...(await walkDir(fullPath)));
19
+ } else {
20
+ files.push(fullPath);
21
+ }
22
+ }
23
+ return files;
24
+ }
25
+
26
+ // npm publish 시 제외되는 dotfile을 언더스코어 접두사로 보관하고 복원
27
+ const DOTFILE_MAP = { _gitignore: ".gitignore" };
28
+
29
+ /** 파일명에서 플레이스홀더 치환 + dotfile 복원 */
30
+ function replaceFileName(name, names) {
31
+ if (DOTFILE_MAP[name]) return DOTFILE_MAP[name];
32
+ return name
33
+ .replace(/__WidgetName__/g, names.pascalCase)
34
+ .replace(/__widget_name__/g, names.snakeCase);
35
+ }
36
+
37
+ /** 파일 내용에서 플레이스홀더 치환 */
38
+ function replaceContent(content, names, pmConfig, templateComments, options) {
39
+ let result = content
40
+ .replace(/\{\{PASCAL_CASE\}\}/g, names.pascalCase)
41
+ .replace(/\{\{SNAKE_CASE\}\}/g, names.snakeCase)
42
+ .replace(/\{\{LOWERCASE\}\}/g, names.lowerCase)
43
+ .replace(/\{\{DISPLAY_NAME\}\}/g, names.displayName)
44
+ .replace(/\{\{KEBAB_CASE\}\}/g, names.kebabCase);
45
+
46
+ if (options) {
47
+ result = result
48
+ .replace(/\{\{ORGANIZATION\}\}/g, options.organization)
49
+ .replace(/\{\{COPYRIGHT\}\}/g, options.copyright)
50
+ .replace(/\{\{LICENSE_ID\}\}/g, options.license)
51
+ .replace(/\{\{VERSION\}\}/g, options.version)
52
+ .replace(/\{\{AUTHOR\}\}/g, options.author)
53
+ .replace(/\{\{PROJECT_PATH\}\}/g, options.projectPath);
54
+ }
55
+
56
+ if (templateComments) {
57
+ result = result.replace(/\{\{I18N:(\w+)\}\}/g, (_, key) => {
58
+ return templateComments[key] ?? `{{I18N:${key}}}`;
59
+ });
60
+ }
61
+
62
+ return result;
63
+ }
64
+
65
+ /**
66
+ * 템플릿을 대상 디렉토리에 스케폴딩
67
+ * @param {string} templateDir - 템플릿 디렉토리 경로
68
+ * @param {string} targetDir - 생성할 프로젝트 디렉토리 경로
69
+ * @param {object} names - 이름 변환 결과
70
+ * @param {object} pmConfig - 패키지 매니저 설정
71
+ * @param {object} [templateComments] - i18n 템플릿 주석 ({{I18N:*}} 치환용)
72
+ * @param {object} [options] - 추가 옵션 (organization, copyright, license, version, author, projectPath)
73
+ */
74
+ export async function scaffold(templateDir, targetDir, names, pmConfig, templateComments, options) {
75
+ const files = await walkDir(templateDir);
76
+ const created = [];
77
+
78
+ for (const srcPath of files) {
79
+ // 템플릿 기준 상대 경로
80
+ const relPath = relative(templateDir, srcPath);
81
+
82
+ // 경로의 각 부분에서 파일명 치환
83
+ const destRelPath = relPath
84
+ .split(/[\\/]/)
85
+ .map((part) => replaceFileName(part, names))
86
+ .join("/");
87
+
88
+ const destPath = join(targetDir, destRelPath);
89
+ const ext = srcPath.substring(srcPath.lastIndexOf(".")).toLowerCase();
90
+
91
+ // 디렉토리 생성
92
+ await mkdir(join(destPath, ".."), { recursive: true });
93
+
94
+ if (BINARY_EXTS.has(ext)) {
95
+ // 바이너리 파일은 그대로 복사
96
+ await copyFile(srcPath, destPath);
97
+ } else {
98
+ // 텍스트 파일은 내용 치환
99
+ const content = await readFile(srcPath, "utf-8");
100
+ const replaced = replaceContent(content, names, pmConfig, templateComments, options);
101
+ await writeFile(destPath, replaced, "utf-8");
102
+ }
103
+
104
+ created.push(destRelPath);
105
+ }
106
+
107
+ return created;
108
+ }
@@ -9,7 +9,7 @@ const COMMENT_LANG_INSTRUCTIONS = {
9
9
  ja: "Use Japanese comments",
10
10
  };
11
11
 
12
- export function generateClaudeMdContent(lang, names, pm, pmConfig) {
12
+ export function generateClaudeMdContent(lang, names, pm, pmConfig, organization) {
13
13
  const commentInstruction =
14
14
  COMMENT_LANG_INSTRUCTIONS[lang] ?? COMMENT_LANG_INSTRUCTIONS["en"];
15
15
 
@@ -41,7 +41,7 @@ IMPORTANT: Breaking these rules will break the build or compromise the architect
41
41
  - **Do not write JSX/JS files directly.** All widget logic and UI must be written in Gleam
42
42
  - **Do not write FFI files (.mjs) in the widget project.** React/Mendix FFI is provided by the glendix package
43
43
  - **Do not manually manage bridge JS files (src/*.js).** glendix auto-generates/deletes them at build time
44
- - **Do not use external Gleam React libraries such as Redraw.** Use only glendix for React bindings
44
+ - **React bindings use \`redraw\`/\`redraw_dom\` packages.** glendix v3.0 no longer provides React bindings directly
45
45
  - The Gleam compilation output path (\`build/dev/javascript/{gleam.toml name}/\`) must match the Rollup input path
46
46
  - Mendix widget names allow only alphabetic characters (a-zA-Z)
47
47
 
@@ -53,7 +53,7 @@ IMPORTANT: Breaking these rules will break the build or compromise the architect
53
53
 
54
54
  ## Architecture
55
55
 
56
- Widget entry point signature: \`pub fn widget(props: JsProps) -> ReactElement\` — identical to a React functional component.
56
+ Widget entry point signature: \`pub fn widget(props: JsProps) -> Element\` — identical to a React functional component. \`JsProps\` from \`glendix/mendix\`, \`Element\` from \`redraw\`.
57
57
 
58
58
  - \`src/${names.snakeCase}.gleam\` — Main widget (called by Mendix runtime)
59
59
  - \`src/editor_config.gleam\` — Studio Pro property panel configuration
@@ -72,8 +72,8 @@ src/*.gleam → gleam build → build/dev/javascript/**/*.mjs → Bridge JS (aut
72
72
 
73
73
  ## Mendix Widget Conventions
74
74
 
75
- - Widget ID: \`mendix.${names.lowerCase}.${names.pascalCase}\`
76
- - \`packagePath: "mendix"\` in \`package.json\` determines the deployment path
75
+ - Widget ID: \`${organization}.${names.lowerCase}.${names.pascalCase}\`
76
+ - \`packagePath: "${organization}"\` in \`package.json\` determines the deployment path
77
77
  - \`needsEntityContext="true"\` → Requires Mendix data context
78
78
  - \`offlineCapable="true"\` → Offline support
79
79
  - \`.mpk\` output: \`dist/\` directory
@@ -84,7 +84,8 @@ src/*.gleam → gleam build → build/dev/javascript/**/*.mjs → Bridge JS (aut
84
84
  - Mendix props (\`JsProps\`) are accessed via \`mendix.get_prop\`/\`mendix.get_string_prop\` etc.
85
85
  - Mendix complex types (\`EditableValue\`, \`ActionValue\`, \`ListValue\`) are opaque types with FFI accessors
86
86
  - JS \`undefined\` ↔ Gleam \`Option\` conversion is handled automatically at the FFI boundary
87
- - HTML attributes use the Attribute list API: \`[attribute.class("x"), event.on_click(handler)]\`
87
+ - HTML elements use \`redraw/dom/html\`, attributes use \`redraw/dom/attribute\`, events use \`redraw/dom/events\`
88
+ - lustre TEA pattern is supported via \`glendix/lustre\` bridge (optional)
88
89
  - Gleam tuples \`#(a, b)\` = JS \`[a, b]\` — directly compatible with \`useState\` return values
89
90
 
90
91
  ## Reference Docs
@@ -2,7 +2,7 @@
2
2
  * README.md template — 3 language versions
3
3
  */
4
4
 
5
- export function generateReadmeContent(lang, names, pm, pmConfig) {
5
+ export function generateReadmeContent(lang, names, pm, pmConfig, license) {
6
6
  const installCmd =
7
7
  pm === "npm"
8
8
  ? "npm install"
@@ -14,11 +14,11 @@ export function generateReadmeContent(lang, names, pm, pmConfig) {
14
14
 
15
15
  switch (lang) {
16
16
  case "ko":
17
- return generateKo(names, pm, installCmd);
17
+ return generateKo(names, pm, installCmd, license);
18
18
  case "ja":
19
- return generateJa(names, pm, installCmd);
19
+ return generateJa(names, pm, installCmd, license);
20
20
  default:
21
- return generateEn(names, pm, installCmd);
21
+ return generateEn(names, pm, installCmd, license);
22
22
  }
23
23
  }
24
24
 
@@ -26,26 +26,26 @@ export function generateReadmeContent(lang, names, pm, pmConfig) {
26
26
  // English
27
27
  // ---------------------------------------------------------------------------
28
28
 
29
- function generateEn(names, pm, installCmd) {
29
+ function generateEn(names, pm, installCmd, license) {
30
30
  return `# ${names.pascalCase}
31
31
 
32
32
  A Mendix Pluggable Widget written in Gleam.
33
33
 
34
34
  ## Core Principles
35
35
 
36
- The Gleam function \`fn(JsProps) -> ReactElement\` has the same signature as a React functional component. glendix provides type-safe access to React primitives and Mendix runtime type accessors, so widget projects only need to focus on business logic.
36
+ The Gleam function \`fn(JsProps) -> Element\` has the same signature as a React functional component. React bindings come from the \`redraw\`/\`redraw_dom\` packages, while glendix handles Mendix API access and JS interop, so widget projects only need to focus on business logic.
37
37
 
38
38
  \`\`\`gleam
39
39
  // src/${names.snakeCase}.gleam
40
- import glendix/mendix
41
- import glendix/react.{type JsProps, type ReactElement}
42
- import glendix/react/attribute
43
- import glendix/react/html
40
+ import glendix/mendix.{type JsProps}
41
+ import redraw.{type Element}
42
+ import redraw/dom/attribute
43
+ import redraw/dom/html
44
44
 
45
- pub fn widget(props: JsProps) -> ReactElement {
45
+ pub fn widget(props: JsProps) -> Element {
46
46
  let sample_text = mendix.get_string_prop(props, "sampleText")
47
47
  html.div([attribute.class("widget-hello-world")], [
48
- react.text("Hello " <> sample_text),
48
+ html.text("Hello " <> sample_text),
49
49
  ])
50
50
  }
51
51
  \`\`\`
@@ -53,11 +53,12 @@ pub fn widget(props: JsProps) -> ReactElement {
53
53
  Mendix complex types can also be used type-safely from Gleam:
54
54
 
55
55
  \`\`\`gleam
56
- import glendix/mendix
56
+ import glendix/mendix.{type JsProps}
57
57
  import glendix/mendix/editable_value
58
58
  import glendix/mendix/action
59
+ import redraw.{type Element}
59
60
 
60
- pub fn widget(props: JsProps) -> ReactElement {
61
+ pub fn widget(props: JsProps) -> Element {
61
62
  // Access EditableValue
62
63
  let name_attr: EditableValue = mendix.get_prop_required(props, "name")
63
64
  let display = editable_value.display_value(name_attr)
@@ -126,7 +127,7 @@ bindings.json # External React component binding configurat
126
127
  package.json # npm dependencies (React, external libraries, etc.)
127
128
  \`\`\`
128
129
 
129
- React/Mendix FFI and JS Interop bindings are provided by the [glendix](https://hexdocs.pm/glendix/) Hex package.
130
+ React bindings come from [redraw](https://hexdocs.pm/redraw/)/[redraw_dom](https://hexdocs.pm/redraw_dom/), while Mendix API and JS Interop bindings are provided by [glendix](https://hexdocs.pm/glendix/).
130
131
 
131
132
  ## Using External React Components
132
133
 
@@ -162,17 +163,18 @@ gleam run -m glendix/install
162
163
 
163
164
  \`\`\`gleam
164
165
  import glendix/binding
165
- import glendix/react.{type ReactElement}
166
- import glendix/react/attribute.{type Attribute}
166
+ import glendix/interop
167
+ import redraw.{type Element}
168
+ import redraw/dom/attribute.{type Attribute}
167
169
 
168
170
  fn m() { binding.module("recharts") }
169
171
 
170
- pub fn pie_chart(attrs: List(Attribute), children: List(ReactElement)) -> ReactElement {
171
- react.component_el(binding.resolve(m(), "PieChart"), attrs, children)
172
+ pub fn pie_chart(attrs: List(Attribute), children: List(Element)) -> Element {
173
+ interop.component_el(binding.resolve(m(), "PieChart"), attrs, children)
172
174
  }
173
175
 
174
- pub fn tooltip(attrs: List(Attribute)) -> ReactElement {
175
- react.void_component_el(binding.resolve(m(), "Tooltip"), attrs)
176
+ pub fn tooltip(attrs: List(Attribute)) -> Element {
177
+ interop.void_component_el(binding.resolve(m(), "Tooltip"), attrs)
176
178
  }
177
179
  \`\`\`
178
180
 
@@ -229,18 +231,19 @@ This automatically:
229
231
 
230
232
  \`\`\`gleam
231
233
  // src/widgets/switch.gleam (auto-generated)
232
- import glendix/mendix
233
- import glendix/react.{type JsProps, type ReactElement}
234
- import glendix/react/attribute
234
+ import glendix/mendix.{type JsProps}
235
+ import glendix/interop
236
+ import redraw.{type Element}
237
+ import redraw/dom/attribute
235
238
  import glendix/widget
236
239
 
237
240
  /// Render Switch widget - reads properties from props and passes them to the widget
238
- pub fn render(props: JsProps) -> ReactElement {
241
+ pub fn render(props: JsProps) -> Element {
239
242
  let boolean_attribute = mendix.get_prop_required(props, "booleanAttribute")
240
243
  let action = mendix.get_prop_required(props, "action")
241
244
 
242
245
  let comp = widget.component("Switch")
243
- react.component_el(
246
+ interop.component_el(
244
247
  comp,
245
248
  [
246
249
  attribute.attribute("booleanAttribute", boolean_attribute),
@@ -267,13 +270,14 @@ Widget names use the \`<name>\` value from the \`.mpk\`'s internal XML, and prop
267
270
  ## Tech Stack
268
271
 
269
272
  - **Gleam** → JavaScript compilation
270
- - **[glendix](https://hexdocs.pm/glendix/)** — React + Mendix API + JS Interop Gleam bindings
273
+ - **[glendix](https://hexdocs.pm/glendix/)** — Mendix API + JS Interop Gleam bindings
274
+ - **[redraw](https://hexdocs.pm/redraw/)** / **[redraw_dom](https://hexdocs.pm/redraw_dom/)** — React Gleam bindings
271
275
  - **Mendix Pluggable Widget** (React 19)
272
276
  - **${pm}** — Package manager
273
277
 
274
278
  ## License
275
279
 
276
- Apache-2.0
280
+ ${license}
277
281
  `;
278
282
  }
279
283
 
@@ -281,26 +285,26 @@ Apache-2.0
281
285
  // Korean
282
286
  // ---------------------------------------------------------------------------
283
287
 
284
- function generateKo(names, pm, installCmd) {
288
+ function generateKo(names, pm, installCmd, license) {
285
289
  return `# ${names.pascalCase}
286
290
 
287
291
  Gleam 언어로 작성된 Mendix Pluggable Widget.
288
292
 
289
293
  ## 핵심 원리
290
294
 
291
- Gleam 함수 \`fn(JsProps) -> ReactElement\`는 React 함수형 컴포넌트와 동일한 시그니처다. glendix가 React 원시 함수와 Mendix 런타임 타입 접근자를 타입 안전하게 제공하므로, 위젯 프로젝트에서는 비즈니스 로직에만 집중하면 된다.
295
+ Gleam 함수 \`fn(JsProps) -> Element\`는 React 함수형 컴포넌트와 동일한 시그니처다. React 바인딩은 \`redraw\`/\`redraw_dom\` 패키지가, Mendix API 접근과 JS interop은 glendix가 제공하므로, 위젯 프로젝트에서는 비즈니스 로직에만 집중하면 된다.
292
296
 
293
297
  \`\`\`gleam
294
298
  // src/${names.snakeCase}.gleam
295
- import glendix/mendix
296
- import glendix/react.{type JsProps, type ReactElement}
297
- import glendix/react/attribute
298
- import glendix/react/html
299
+ import glendix/mendix.{type JsProps}
300
+ import redraw.{type Element}
301
+ import redraw/dom/attribute
302
+ import redraw/dom/html
299
303
 
300
- pub fn widget(props: JsProps) -> ReactElement {
304
+ pub fn widget(props: JsProps) -> Element {
301
305
  let sample_text = mendix.get_string_prop(props, "sampleText")
302
306
  html.div([attribute.class("widget-hello-world")], [
303
- react.text("Hello " <> sample_text),
307
+ html.text("Hello " <> sample_text),
304
308
  ])
305
309
  }
306
310
  \`\`\`
@@ -308,11 +312,12 @@ pub fn widget(props: JsProps) -> ReactElement {
308
312
  Mendix 복합 타입도 Gleam에서 타입 안전하게 사용할 수 있다:
309
313
 
310
314
  \`\`\`gleam
311
- import glendix/mendix
315
+ import glendix/mendix.{type JsProps}
312
316
  import glendix/mendix/editable_value
313
317
  import glendix/mendix/action
318
+ import redraw.{type Element}
314
319
 
315
- pub fn widget(props: JsProps) -> ReactElement {
320
+ pub fn widget(props: JsProps) -> Element {
316
321
  // EditableValue 접근
317
322
  let name_attr: EditableValue = mendix.get_prop_required(props, "name")
318
323
  let display = editable_value.display_value(name_attr)
@@ -381,7 +386,7 @@ bindings.json # 외부 React 컴포넌트 바인딩 설정
381
386
  package.json # npm 의존성 (React, 외부 라이브러리 등)
382
387
  \`\`\`
383
388
 
384
- React/Mendix FFI 및 JS Interop 바인딩은 [glendix](https://hexdocs.pm/glendix/) Hex 패키지로 제공됩니다.
389
+ React 바인딩은 [redraw](https://hexdocs.pm/redraw/)/[redraw_dom](https://hexdocs.pm/redraw_dom/)이, Mendix API 및 JS Interop 바인딩은 [glendix](https://hexdocs.pm/glendix/) 제공합니다.
385
390
 
386
391
  ## 외부 React 컴포넌트 사용
387
392
 
@@ -417,17 +422,18 @@ gleam run -m glendix/install
417
422
 
418
423
  \`\`\`gleam
419
424
  import glendix/binding
420
- import glendix/react.{type ReactElement}
421
- import glendix/react/attribute.{type Attribute}
425
+ import glendix/interop
426
+ import redraw.{type Element}
427
+ import redraw/dom/attribute.{type Attribute}
422
428
 
423
429
  fn m() { binding.module("recharts") }
424
430
 
425
- pub fn pie_chart(attrs: List(Attribute), children: List(ReactElement)) -> ReactElement {
426
- react.component_el(binding.resolve(m(), "PieChart"), attrs, children)
431
+ pub fn pie_chart(attrs: List(Attribute), children: List(Element)) -> Element {
432
+ interop.component_el(binding.resolve(m(), "PieChart"), attrs, children)
427
433
  }
428
434
 
429
- pub fn tooltip(attrs: List(Attribute)) -> ReactElement {
430
- react.void_component_el(binding.resolve(m(), "Tooltip"), attrs)
435
+ pub fn tooltip(attrs: List(Attribute)) -> Element {
436
+ interop.void_component_el(binding.resolve(m(), "Tooltip"), attrs)
431
437
  }
432
438
  \`\`\`
433
439
 
@@ -484,18 +490,19 @@ gleam run -m glendix/install
484
490
 
485
491
  \`\`\`gleam
486
492
  // src/widgets/switch.gleam (자동 생성)
487
- import glendix/mendix
488
- import glendix/react.{type JsProps, type ReactElement}
489
- import glendix/react/attribute
493
+ import glendix/mendix.{type JsProps}
494
+ import glendix/interop
495
+ import redraw.{type Element}
496
+ import redraw/dom/attribute
490
497
  import glendix/widget
491
498
 
492
499
  /// Switch 위젯 렌더링 - props에서 속성을 읽어 위젯에 전달
493
- pub fn render(props: JsProps) -> ReactElement {
500
+ pub fn render(props: JsProps) -> Element {
494
501
  let boolean_attribute = mendix.get_prop_required(props, "booleanAttribute")
495
502
  let action = mendix.get_prop_required(props, "action")
496
503
 
497
504
  let comp = widget.component("Switch")
498
- react.component_el(
505
+ interop.component_el(
499
506
  comp,
500
507
  [
501
508
  attribute.attribute("booleanAttribute", boolean_attribute),
@@ -522,13 +529,14 @@ switch.render(props)
522
529
  ## 기술 스택
523
530
 
524
531
  - **Gleam** → JavaScript 컴파일
525
- - **[glendix](https://hexdocs.pm/glendix/)** — React + Mendix API + JS Interop Gleam 바인딩
532
+ - **[glendix](https://hexdocs.pm/glendix/)** — Mendix API + JS Interop Gleam 바인딩
533
+ - **[redraw](https://hexdocs.pm/redraw/)** / **[redraw_dom](https://hexdocs.pm/redraw_dom/)** — React Gleam 바인딩
526
534
  - **Mendix Pluggable Widget** (React 19)
527
535
  - **${pm}** — 패키지 매니저
528
536
 
529
537
  ## 라이센스
530
538
 
531
- Apache-2.0
539
+ ${license}
532
540
  `;
533
541
  }
534
542
 
@@ -536,26 +544,26 @@ Apache-2.0
536
544
  // Japanese
537
545
  // ---------------------------------------------------------------------------
538
546
 
539
- function generateJa(names, pm, installCmd) {
547
+ function generateJa(names, pm, installCmd, license) {
540
548
  return `# ${names.pascalCase}
541
549
 
542
550
  Gleam言語で作成されたMendix Pluggable Widget。
543
551
 
544
552
  ## 基本原理
545
553
 
546
- Gleam関数 \`fn(JsProps) -> ReactElement\` はReact関数コンポーネントと同一のシグネチャを持つ。glendixがReactプリミティブ関数とMendixランタイム型アクセサを型安全に提供するため、ウィジェットプロジェクトではビジネスロジックにのみ集中すればよい。
554
+ Gleam関数 \`fn(JsProps) -> Element\` はReact関数コンポーネントと同一のシグネチャを持つ。Reactバインディングは\`redraw\`/\`redraw_dom\`パッケージが、Mendix APIアクセスとJS interopはglendixが提供するため、ウィジェットプロジェクトではビジネスロジックにのみ集中すればよい。
547
555
 
548
556
  \`\`\`gleam
549
557
  // src/${names.snakeCase}.gleam
550
- import glendix/mendix
551
- import glendix/react.{type JsProps, type ReactElement}
552
- import glendix/react/attribute
553
- import glendix/react/html
558
+ import glendix/mendix.{type JsProps}
559
+ import redraw.{type Element}
560
+ import redraw/dom/attribute
561
+ import redraw/dom/html
554
562
 
555
- pub fn widget(props: JsProps) -> ReactElement {
563
+ pub fn widget(props: JsProps) -> Element {
556
564
  let sample_text = mendix.get_string_prop(props, "sampleText")
557
565
  html.div([attribute.class("widget-hello-world")], [
558
- react.text("Hello " <> sample_text),
566
+ html.text("Hello " <> sample_text),
559
567
  ])
560
568
  }
561
569
  \`\`\`
@@ -563,11 +571,12 @@ pub fn widget(props: JsProps) -> ReactElement {
563
571
  Mendixの複合型もGleamから型安全に使用できる:
564
572
 
565
573
  \`\`\`gleam
566
- import glendix/mendix
574
+ import glendix/mendix.{type JsProps}
567
575
  import glendix/mendix/editable_value
568
576
  import glendix/mendix/action
577
+ import redraw.{type Element}
569
578
 
570
- pub fn widget(props: JsProps) -> ReactElement {
579
+ pub fn widget(props: JsProps) -> Element {
571
580
  // EditableValueへのアクセス
572
581
  let name_attr: EditableValue = mendix.get_prop_required(props, "name")
573
582
  let display = editable_value.display_value(name_attr)
@@ -636,7 +645,7 @@ bindings.json # 外部Reactコンポーネントバイン
636
645
  package.json # npm依存関係(React、外部ライブラリなど)
637
646
  \`\`\`
638
647
 
639
- React/Mendix FFIおよびJS Interopバインディングは[glendix](https://hexdocs.pm/glendix/) Hexパッケージとして提供される。
648
+ Reactバインディングは[redraw](https://hexdocs.pm/redraw/)/[redraw_dom](https://hexdocs.pm/redraw_dom/)が、Mendix APIおよびJS Interopバインディングは[glendix](https://hexdocs.pm/glendix/)が提供する。
640
649
 
641
650
  ## 外部Reactコンポーネントの使用
642
651
 
@@ -672,17 +681,18 @@ gleam run -m glendix/install
672
681
 
673
682
  \`\`\`gleam
674
683
  import glendix/binding
675
- import glendix/react.{type ReactElement}
676
- import glendix/react/attribute.{type Attribute}
684
+ import glendix/interop
685
+ import redraw.{type Element}
686
+ import redraw/dom/attribute.{type Attribute}
677
687
 
678
688
  fn m() { binding.module("recharts") }
679
689
 
680
- pub fn pie_chart(attrs: List(Attribute), children: List(ReactElement)) -> ReactElement {
681
- react.component_el(binding.resolve(m(), "PieChart"), attrs, children)
690
+ pub fn pie_chart(attrs: List(Attribute), children: List(Element)) -> Element {
691
+ interop.component_el(binding.resolve(m(), "PieChart"), attrs, children)
682
692
  }
683
693
 
684
- pub fn tooltip(attrs: List(Attribute)) -> ReactElement {
685
- react.void_component_el(binding.resolve(m(), "Tooltip"), attrs)
694
+ pub fn tooltip(attrs: List(Attribute)) -> Element {
695
+ interop.void_component_el(binding.resolve(m(), "Tooltip"), attrs)
686
696
  }
687
697
  \`\`\`
688
698
 
@@ -739,18 +749,19 @@ gleam run -m glendix/install
739
749
 
740
750
  \`\`\`gleam
741
751
  // src/widgets/switch.gleam(自動生成)
742
- import glendix/mendix
743
- import glendix/react.{type JsProps, type ReactElement}
744
- import glendix/react/attribute
752
+ import glendix/mendix.{type JsProps}
753
+ import glendix/interop
754
+ import redraw.{type Element}
755
+ import redraw/dom/attribute
745
756
  import glendix/widget
746
757
 
747
758
  /// Switchウィジェットのレンダリング - propsからプロパティを読み取りウィジェットに渡す
748
- pub fn render(props: JsProps) -> ReactElement {
759
+ pub fn render(props: JsProps) -> Element {
749
760
  let boolean_attribute = mendix.get_prop_required(props, "booleanAttribute")
750
761
  let action = mendix.get_prop_required(props, "action")
751
762
 
752
763
  let comp = widget.component("Switch")
753
- react.component_el(
764
+ interop.component_el(
754
765
  comp,
755
766
  [
756
767
  attribute.attribute("booleanAttribute", boolean_attribute),
@@ -777,12 +788,13 @@ switch.render(props)
777
788
  ## 技術スタック
778
789
 
779
790
  - **Gleam** → JavaScriptコンパイル
780
- - **[glendix](https://hexdocs.pm/glendix/)** — React + Mendix API + JS Interop Gleamバインディング
791
+ - **[glendix](https://hexdocs.pm/glendix/)** — Mendix API + JS Interop Gleamバインディング
792
+ - **[redraw](https://hexdocs.pm/redraw/)** / **[redraw_dom](https://hexdocs.pm/redraw_dom/)** — React Gleamバインディング
781
793
  - **Mendix Pluggable Widget**(React 19)
782
794
  - **${pm}** — パッケージマネージャー
783
795
 
784
796
  ## ライセンス
785
797
 
786
- Apache-2.0
798
+ ${license}
787
799
  `;
788
800
  }