@junnyontop-pixel/neo-app 2.2.0 → 2.3.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.
@@ -6,7 +6,8 @@ export class NeoParser {
6
6
  styles: [],
7
7
  innerHTML: "",
8
8
  events: [],
9
- children: []
9
+ children: [],
10
+ attrs: {}
10
11
  };
11
12
 
12
13
  // @ID:Tag
@@ -28,9 +29,22 @@ export class NeoParser {
28
29
  // { content }
29
30
  const contentMatch = rawCode.match(/\{([\s\S]*?)\}$/);
30
31
  if (contentMatch) {
31
- let content = contentMatch[1].trim(); // ⭐ 이게 content
32
- let rest = content; // ⭐ 여기서 rest 생성
32
+ let rest = contentMatch[1].trim();
33
33
 
34
+ // // ✅ 0) ::attrs 먼저 처리 (메타 블록은 먼저 제거해야 함)
35
+ // while (rest.includes("::attrs")) {
36
+ // const start = rest.indexOf("::attrs");
37
+ // const { block, nextIndex } = extractBlock(rest, start);
38
+
39
+ // // attrs 누적 (여러 ::attrs 지원 가능)
40
+ // const parsed = parseAttrs(block);
41
+ // result.attrs = { ...result.attrs, ...parsed };
42
+
43
+ // // ::attrs 블록 제거
44
+ // rest = rest.slice(0, start) + rest.slice(nextIndex);
45
+ // }
46
+
47
+ // ✅ 1) children 태그들 파싱
34
48
  while (rest.includes('@')) {
35
49
  const start = rest.indexOf('@');
36
50
  const { block, nextIndex } = extractTag(rest, start);
@@ -40,27 +54,42 @@ export class NeoParser {
40
54
  rest = rest.slice(0, start) + rest.slice(nextIndex);
41
55
  }
42
56
 
57
+ // ✅ 2) ::attrs 메타 블록 파싱
58
+ if (rest.includes('::attrs')) {
59
+ const start = rest.indexOf('::attrs');
60
+ const { block, nextIndex } = extractBlock(rest, start);
61
+
62
+ result.attrs = parseAttrs(block);
63
+
64
+ rest = rest.slice(0, start) + rest.slice(nextIndex);
65
+ }
66
+
67
+ // ✅ 2) 남은 것에서 innerHTML / on: 만 파싱
43
68
  const lines = rest.split('\n');
44
69
 
45
70
  lines.forEach(line => {
46
71
  const text = line.trim();
72
+ if (!text) return;
47
73
 
48
74
  if (text.startsWith('innerHTML:')) {
49
75
  let value = text.slice('innerHTML:'.length).trim();
76
+
77
+ // 문자열 리터럴이면 바깥 따옴표만 제거
50
78
  if (
51
79
  (value.startsWith('"') && value.endsWith('"')) ||
52
80
  (value.startsWith("'") && value.endsWith("'"))
53
81
  ) {
54
82
  value = value.slice(1, -1);
55
83
  }
84
+
56
85
  result.innerHTML = value;
57
86
  }
58
87
 
59
88
  if (text.startsWith('on:')) {
60
- const [, type, ...rest] = text.split(':');
89
+ const [, type, ...restParts] = text.split(':');
61
90
  result.events.push({
62
91
  type: type.trim(),
63
- action: rest.join(':').trim()
92
+ action: restParts.join(':').trim()
64
93
  });
65
94
  }
66
95
  });
@@ -93,4 +122,52 @@ function extractTag(code, startIndex) {
93
122
  block: code.slice(startIndex, i).trim(),
94
123
  nextIndex: i
95
124
  };
125
+ }
126
+
127
+ function extractBlock(code, startIndex) {
128
+ const open = code.indexOf('{', startIndex);
129
+ if (open === -1) {
130
+ return { block: "", nextIndex: startIndex };
131
+ }
132
+
133
+ let depth = 1;
134
+ let i = open + 1;
135
+
136
+ while (i < code.length && depth > 0) {
137
+ if (code[i] === '{') depth++;
138
+ if (code[i] === '}') depth--;
139
+ i++;
140
+ }
141
+
142
+ return {
143
+ block: code.slice(open + 1, i - 1),
144
+ nextIndex: i
145
+ };
146
+ }
147
+
148
+ function parseAttrs(block) {
149
+ const attrs = {};
150
+
151
+ block.split('\n').forEach(line => {
152
+ const text = line.trim();
153
+ if (!text) return;
154
+
155
+ // "type: "text"," 같은 마지막 콤마 제거
156
+ const cleaned = text.replace(/,\s*$/, '');
157
+ if (!cleaned.includes(':')) return;
158
+
159
+ const [key, ...rest] = cleaned.split(':');
160
+ let value = rest.join(':').trim();
161
+
162
+ if (
163
+ (value.startsWith('"') && value.endsWith('"')) ||
164
+ (value.startsWith("'") && value.endsWith("'"))
165
+ ) {
166
+ value = value.slice(1, -1);
167
+ }
168
+
169
+ attrs[key.trim()] = value;
170
+ });
171
+
172
+ return attrs;
96
173
  }
package/core/NeoCore.js CHANGED
@@ -7,6 +7,32 @@ export class NeoCore {
7
7
  el.className = data.styles.join(' ');
8
8
  }
9
9
 
10
+ if (data.attrs && typeof data.attrs === 'object') {
11
+ for (const [key, value] of Object.entries(data.attrs)) {
12
+
13
+ // boolean attr
14
+ if (typeof value === 'boolean') {
15
+ el[key] = value;
16
+ continue;
17
+ }
18
+
19
+ // input 관련 핵심 property
20
+ if (
21
+ el instanceof HTMLInputElement ||
22
+ el instanceof HTMLTextAreaElement ||
23
+ el instanceof HTMLSelectElement
24
+ ) {
25
+ if (key in el) {
26
+ el[key] = value;
27
+ continue;
28
+ }
29
+ }
30
+
31
+ // fallback: 일반 attribute
32
+ el.setAttribute(key, String(value));
33
+ }
34
+ }
35
+
10
36
  // 1️⃣ 텍스트 먼저
11
37
  if (data.innerHTML) {
12
38
  el.innerHTML = NeoCore.renderTemplate(data.innerHTML);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@junnyontop-pixel/neo-app",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "나만의 커스텀 프레임워크 Neo",
5
5
  "main": "core/NeoCore.js",
6
6
  "type": "module",
package/src/App.neo CHANGED
@@ -23,6 +23,12 @@
23
23
  innerHTML: "Increase"
24
24
  on:click: Store.add()
25
25
  }
26
- }
27
26
 
27
+ @input:input [hello] {
28
+ ::attrs: {
29
+ type: "text",
30
+ placeholder: "Neo"
31
+ }
32
+ }
33
+ }
28
34
  }