@junnyontop-pixel/neo-app 2.5.2 → 2.6.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.
@@ -54,6 +54,29 @@ export class NeoParser {
54
54
  rest = rest.slice(0, start) + " ".repeat(nextIndex - start) + rest.slice(nextIndex);
55
55
  }
56
56
 
57
+ while (rest.includes('::for')) {
58
+ const start = rest.indexOf('::for');
59
+
60
+ const forMatch = rest.slice(start).match(/::for\s*\((.*?)\s+in\s+(.*?)\)/);
61
+
62
+ const itemName = forMatch ? forMatch[1].trim() : "item"; // "item" 추출
63
+ const listPath = forMatch ? forMatch[2].trim() : "[]"; // "Store.items" 추출
64
+
65
+ const { block, nextIndex } = extractBlock(rest, start);
66
+ const dummyParsed = this.parse(`@for-container:div { ${block} }`);
67
+
68
+ result.children.push({
69
+ type: "forBlock",
70
+ itemName: itemName,
71
+ listPath: listPath,
72
+ children: dummyParsed.children,
73
+ _pos: start
74
+ });
75
+
76
+ // rest = rest.slice(0, start) + rest.slice(nextIndex);
77
+ rest = rest.slice(0, start) + " ".repeat(nextIndex - start) + rest.slice(nextIndex);
78
+ }
79
+
57
80
  // ✅ 1) children 태그들 파싱
58
81
  while (rest.includes('@')) {
59
82
  const start = rest.indexOf('@');
package/core/NeoCore.js CHANGED
@@ -1,23 +1,23 @@
1
1
  export class NeoCore {
2
- static create(data) {
2
+ static create(data, loopContext = {}) {
3
3
 
4
4
  if (data.type === "ifBlock") {
5
5
  let isTrue = false;
6
6
  try {
7
7
  // "$Store.state" -> "false" 로 변환
8
- const conditionStr = NeoCore.renderTemplate(data.condition);
8
+ const conditionStr = NeoCore.renderTemplate(data.condition, loopContext);
9
9
  // "return false" 를 자바스크립트로 실행해서 진짜 false로 만듦
10
10
  isTrue = new Function(`return ${conditionStr}`)();
11
11
  } catch (e) {
12
12
  console.warn("Neo if condition error:", e);
13
- }
13
+ }
14
14
 
15
15
  if (isTrue) {
16
16
  // 참일 때: 껍데기(div) 없이 알맹이만 DocumentFragment로 묶어서 반환
17
17
  const frag = document.createDocumentFragment();
18
18
  if (Array.isArray(data.children)) {
19
19
  data.children.forEach(child => {
20
- frag.appendChild(NeoCore.create(child));
20
+ frag.appendChild(NeoCore.create(child, loopContext));
21
21
  });
22
22
  }
23
23
  return frag;
@@ -27,6 +27,29 @@ export class NeoCore {
27
27
  }
28
28
  }
29
29
 
30
+ if (data.type === "forBlock") {
31
+ let list = [];
32
+ try {
33
+ const listPath = data.listPath;
34
+ list = listPath
35
+ .split('.')
36
+ .reduce((obj, key) => obj?.[key], window) ?? [];
37
+ } catch (e) {
38
+ console.warn("Neo for list error:", e);
39
+ }
40
+
41
+ const frag = document.createDocumentFragment();
42
+ if (Array.isArray(list)) {
43
+ list.forEach((item, index) => {
44
+ data.children.forEach(child => {
45
+ const loopContext = { [data.itemName]: item, index: index };
46
+ frag.appendChild(NeoCore.create(child, loopContext));
47
+ });
48
+ });
49
+ }
50
+ return frag;
51
+ }
52
+
30
53
  const el = document.createElement(data.tag || 'div');
31
54
 
32
55
  if (data.id) el.id = data.id;
@@ -38,7 +61,7 @@ export class NeoCore {
38
61
  for (const [key, rawValue] of Object.entries(data.attrs)) {
39
62
 
40
63
  // ⭐ 핵심: 먼저 렌더링
41
- const value = NeoCore.renderTemplate(String(rawValue));
64
+ const value = NeoCore.renderTemplate(String(rawValue), loopContext);
42
65
 
43
66
  // boolean attribute
44
67
  if (value === 'true' || value === 'false') {
@@ -65,27 +88,34 @@ export class NeoCore {
65
88
 
66
89
  // 1️⃣ 텍스트 먼저
67
90
  if (data.innerHTML) {
68
- el.innerHTML = NeoCore.renderTemplate(data.innerHTML);
91
+ el.innerHTML = NeoCore.renderTemplate(data.innerHTML, loopContext);
69
92
  }
70
93
 
71
94
  // 2️⃣ ⭐ 자식 태그 렌더링 (이게 핵심)
72
95
  if (Array.isArray(data.children)) {
73
96
  data.children.forEach(child => {
74
- const childEl = NeoCore.create(child);
97
+ const childEl = NeoCore.create(child, loopContext);
75
98
  el.appendChild(childEl);
76
99
  });
77
100
  }
78
101
 
79
102
  (data.events || []).forEach(evt => {
80
- el.addEventListener(evt.type, () => {
103
+ el.addEventListener(evt.type, (e) => {
81
104
  try {
82
- new Function(evt.action)();
105
+ const keys = Object.keys(loopContext);
106
+ const values = Object.values(loopContext);
107
+
108
+ // 1. 함수 생성
109
+ const runner = new Function(...keys, evt.action);
110
+
111
+ // 2. ⭐ 핵심: .call()을 사용해서 'this'를 현재 엘리먼트(el)로 고정!
112
+ runner.call(el, ...values);
83
113
 
84
114
  if (window.__neoRender) {
85
115
  window.__neoRender();
86
116
  }
87
- } catch (e) {
88
- console.error("Neo Event Error:", e);
117
+ } catch (err) {
118
+ console.error("Neo Event Error:", err);
89
119
  }
90
120
  });
91
121
  });
@@ -93,11 +123,18 @@ export class NeoCore {
93
123
  return el;
94
124
  }
95
125
 
96
- static renderTemplate(template = "") {
126
+ static renderTemplate(template = "", loopContext = {}) {
97
127
  return template.replace(/\$([\w.]+)/g, (_, path) => {
98
- return path
99
- .split('.')
100
- .reduce((obj, key) => obj?.[key], window) ?? '';
128
+ const keys = path.split('.');
129
+ const firstKey = keys[0];
130
+
131
+ // 1. 만약 loopContext(예: todo)에 첫 번째 키가 있다면 거기서 찾기
132
+ if (loopContext.hasOwnProperty(firstKey)) {
133
+ return keys.reduce((obj, key) => obj?.[key], loopContext) ?? '';
134
+ }
135
+
136
+ // 2. 없다면 전역 window(Store)에서 찾기
137
+ return keys.reduce((obj, key) => obj?.[key], window) ?? '';
101
138
  });
102
139
  }
103
140
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@junnyontop-pixel/neo-app",
3
- "version": "2.5.2",
3
+ "version": "2.6.0",
4
4
  "description": "나만의 커스텀 프레임워크 Neo",
5
5
  "main": "core/NeoCore.js",
6
6
  "type": "module",
package/src/App.neo CHANGED
@@ -1,38 +1,36 @@
1
- @App:div [p-6, bg-gray-100] {
2
-
3
- innerHTML: "Neo Nested Test"
4
-
5
- @Header:div [mb-4, p-3, bg-white, rounded] {
6
-
7
- @Title:h1 [text-2xl, font-bold] {
8
- innerHTML: "Header Title"
9
- }
10
-
11
- @Sub:p [text-gray-500] {
12
- innerHTML: "This is a nested paragraph"
13
- }
1
+ @TodoApp:div [p-6, bg-gray-50] {
2
+ @Header:h1 [text-2xl, font-bold, mb-4] {
3
+ innerHTML: "$Store.user.name 님의 오늘 할 일"
14
4
  }
15
-
16
- @Content:div [p-4, bg-blue-50, rounded] {
17
-
18
- @Counter:p [mb-2] {
19
- innerHTML: "Count: $Store.count"
5
+ @ListContainer:div [bg-white, shadow, rounded-lg] {
6
+ ::attrs {
7
+ "data-version": "2.6.0"
20
8
  }
21
-
22
- @Button:button [px-3, py-2, bg-blue-600, text-white, rounded] {
23
- innerHTML: "Increase"
24
- on:click: Store.add()
9
+ ::for(todo in Store.todoList) {
10
+ @Task:div [flex, items-center, p-4, border-b] {
11
+ @Checkbox:input {
12
+ ::attrs {
13
+ type: "checkbox",
14
+ checked: "$todo.completed"
15
+ }
16
+
17
+ on:change: todo.completed = this.checked
18
+ }
19
+ @Title:span [ml-3, text-gray-700] {
20
+ innerHTML: "$todo.text"
21
+ }
22
+ }
25
23
  }
26
-
27
- @input:input [hello] {
28
- ::attrs: {
29
- type: "text",
30
- placeholder: $Store.count
24
+ }
25
+ @Footer:p [mt-4, text-sm, text-gray-400] {
26
+ ::if($Store.remainingCount > 0) {
27
+ @Status:span {
28
+ innerHTML: "아직 $Store.remainingCount개의 할 일이 남았습니다."
31
29
  }
32
30
  }
33
- ::if($Store.count > 14){
34
- @if_content:div[if_content]{
35
- innerHTML: "NeoNeo"
31
+ ::if($Store.remainingCount === 0) {
32
+ @Status:span [text-green-500, font-bold] {
33
+ innerHTML: "🎉 모든 할 일을 끝냈습니다!"
36
34
  }
37
35
  }
38
36
  }
package/src/state.js CHANGED
@@ -1,9 +1,29 @@
1
- let count = 0;
2
-
3
1
  export const Store = {
4
- count,
2
+ // 1. 유저 정보 ($Store.user.name)
3
+ user: {
4
+ name: "안유준"
5
+ },
6
+
7
+ // 2. 할 일 목록 (::for(todo in Store.todoList) 에서 쓰임)
8
+ todoList: [
9
+ { text: "Neo 프레임워크 v2.6.0 파서 완성", completed: true },
10
+ { text: "::for 루프 데이터 바인딩 로직 구현", completed: false },
11
+ { text: "락토프리 간식 먹으면서 쉬기", completed: false }
12
+ ],
13
+
14
+ // 3. 할 일을 추가하는 기능 (나중에 버튼 만들면 쓸 수 있게!)
15
+ addTodo(text) {
16
+ this.todoList.push({ text, completed: false });
17
+ // 여기서 리렌더링 트리거를 해주면 최고!
18
+ },
19
+
20
+ get remainingCount() {
21
+ return this.todoList.filter(todo => !todo.completed).length;
22
+ },
5
23
 
6
- add() {
7
- Store.count++;
24
+ update() {
25
+ if (window.__neoRender) {
26
+ window.__neoRender();
27
+ }
8
28
  }
9
29
  };