@reidelsaltres/pureper 0.1.157 → 0.1.162

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 (107) hide show
  1. package/out/foundation/Triplet.d.ts.map +1 -1
  2. package/out/foundation/Triplet.js +4 -5
  3. package/out/foundation/Triplet.js.map +1 -1
  4. package/out/foundation/api/Observer.d.ts +30 -0
  5. package/out/foundation/api/Observer.d.ts.map +1 -1
  6. package/out/foundation/api/Observer.js +48 -0
  7. package/out/foundation/api/Observer.js.map +1 -1
  8. package/out/foundation/component_api/Component.d.ts +2 -2
  9. package/out/foundation/component_api/Component.d.ts.map +1 -1
  10. package/out/foundation/component_api/Component.js.map +1 -1
  11. package/out/foundation/component_api/UniHtml.d.ts +4 -10
  12. package/out/foundation/component_api/UniHtml.d.ts.map +1 -1
  13. package/out/foundation/component_api/UniHtml.js +7 -15
  14. package/out/foundation/component_api/UniHtml.js.map +1 -1
  15. package/out/foundation/engine/BalancedParser.d.ts +58 -0
  16. package/out/foundation/engine/BalancedParser.d.ts.map +1 -0
  17. package/out/foundation/engine/BalancedParser.js +301 -0
  18. package/out/foundation/engine/BalancedParser.js.map +1 -0
  19. package/out/foundation/engine/EscapeHandler.d.ts +27 -0
  20. package/out/foundation/engine/EscapeHandler.d.ts.map +1 -0
  21. package/out/foundation/engine/EscapeHandler.js +47 -0
  22. package/out/foundation/engine/EscapeHandler.js.map +1 -0
  23. package/out/foundation/engine/Expression.d.ts +83 -0
  24. package/out/foundation/engine/Expression.d.ts.map +1 -0
  25. package/out/foundation/engine/Expression.js +256 -0
  26. package/out/foundation/engine/Expression.js.map +1 -0
  27. package/out/foundation/engine/Rule.d.ts +85 -0
  28. package/out/foundation/engine/Rule.d.ts.map +1 -0
  29. package/out/foundation/engine/Rule.js +69 -0
  30. package/out/foundation/engine/Rule.js.map +1 -0
  31. package/out/foundation/engine/Scope.d.ts +61 -0
  32. package/out/foundation/engine/Scope.d.ts.map +1 -0
  33. package/out/foundation/engine/Scope.js +156 -0
  34. package/out/foundation/engine/Scope.js.map +1 -0
  35. package/out/foundation/engine/TemplateEngine.d.ts +96 -0
  36. package/out/foundation/engine/TemplateEngine.d.ts.map +1 -0
  37. package/out/foundation/engine/TemplateEngine.js +235 -0
  38. package/out/foundation/engine/TemplateEngine.js.map +1 -0
  39. package/out/foundation/engine/TemplateInstance.d.ts +241 -0
  40. package/out/foundation/engine/TemplateInstance.d.ts.map +1 -0
  41. package/out/foundation/engine/TemplateInstance.js +637 -0
  42. package/out/foundation/engine/TemplateInstance.js.map +1 -0
  43. package/out/foundation/engine/TemplateInstance.old.d.ts +219 -0
  44. package/out/foundation/engine/TemplateInstance.old.d.ts.map +1 -0
  45. package/out/foundation/engine/TemplateInstance.old.js +487 -0
  46. package/out/foundation/engine/TemplateInstance.old.js.map +1 -0
  47. package/out/foundation/engine/exceptions/TemplateExceptions.d.ts +21 -0
  48. package/out/foundation/engine/exceptions/TemplateExceptions.d.ts.map +1 -0
  49. package/out/foundation/engine/exceptions/TemplateExceptions.js +26 -0
  50. package/out/foundation/engine/exceptions/TemplateExceptions.js.map +1 -0
  51. package/out/foundation/engine/index.d.ts +18 -0
  52. package/out/foundation/engine/index.d.ts.map +1 -0
  53. package/out/foundation/engine/index.js +19 -0
  54. package/out/foundation/engine/index.js.map +1 -0
  55. package/out/foundation/engine/rules/attribute/EventRule.d.ts +22 -0
  56. package/out/foundation/engine/rules/attribute/EventRule.d.ts.map +1 -0
  57. package/out/foundation/engine/rules/attribute/EventRule.js +129 -0
  58. package/out/foundation/engine/rules/attribute/EventRule.js.map +1 -0
  59. package/out/foundation/engine/rules/attribute/InjectionRule.d.ts +20 -0
  60. package/out/foundation/engine/rules/attribute/InjectionRule.d.ts.map +1 -0
  61. package/out/foundation/engine/rules/attribute/InjectionRule.js +108 -0
  62. package/out/foundation/engine/rules/attribute/InjectionRule.js.map +1 -0
  63. package/out/foundation/engine/rules/attribute/RefRule.d.ts +23 -0
  64. package/out/foundation/engine/rules/attribute/RefRule.d.ts.map +1 -0
  65. package/out/foundation/engine/rules/attribute/RefRule.js +104 -0
  66. package/out/foundation/engine/rules/attribute/RefRule.js.map +1 -0
  67. package/out/foundation/engine/rules/syntax/ExpressionRule.d.ts +19 -0
  68. package/out/foundation/engine/rules/syntax/ExpressionRule.d.ts.map +1 -0
  69. package/out/foundation/engine/rules/syntax/ExpressionRule.js +82 -0
  70. package/out/foundation/engine/rules/syntax/ExpressionRule.js.map +1 -0
  71. package/out/foundation/engine/rules/syntax/ForRule.d.ts +19 -0
  72. package/out/foundation/engine/rules/syntax/ForRule.d.ts.map +1 -0
  73. package/out/foundation/engine/rules/syntax/ForRule.js +226 -0
  74. package/out/foundation/engine/rules/syntax/ForRule.js.map +1 -0
  75. package/out/foundation/engine/rules/syntax/IfRule.d.ts +17 -0
  76. package/out/foundation/engine/rules/syntax/IfRule.d.ts.map +1 -0
  77. package/out/foundation/engine/rules/syntax/IfRule.js +220 -0
  78. package/out/foundation/engine/rules/syntax/IfRule.js.map +1 -0
  79. package/out/foundation/worker/Router.d.ts.map +1 -1
  80. package/out/foundation/worker/Router.js.map +1 -1
  81. package/out/index.d.ts +2 -0
  82. package/out/index.d.ts.map +1 -1
  83. package/out/index.js +2 -0
  84. package/out/index.js.map +1 -1
  85. package/package.json +1 -1
  86. package/src/foundation/Triplet.ts +6 -6
  87. package/src/foundation/api/Observer.ts +60 -0
  88. package/src/foundation/component_api/Component.ts +2 -1
  89. package/src/foundation/component_api/UniHtml.ts +12 -22
  90. package/src/foundation/engine/BalancedParser.ts +353 -0
  91. package/src/foundation/engine/EscapeHandler.ts +54 -0
  92. package/src/foundation/engine/Expression.ts +285 -0
  93. package/src/foundation/engine/Rule.ts +138 -0
  94. package/src/foundation/engine/Scope.ts +176 -0
  95. package/src/foundation/engine/TemplateEngine.ts +318 -0
  96. package/src/foundation/engine/TemplateInstance.md +110 -0
  97. package/src/foundation/engine/TemplateInstance.old.ts +673 -0
  98. package/src/foundation/engine/TemplateInstance.ts +843 -0
  99. package/src/foundation/engine/exceptions/TemplateExceptions.ts +27 -0
  100. package/src/foundation/engine/rules/attribute/EventRule.ts +171 -0
  101. package/src/foundation/engine/rules/attribute/InjectionRule.ts +140 -0
  102. package/src/foundation/engine/rules/attribute/RefRule.ts +126 -0
  103. package/src/foundation/engine/rules/syntax/ExpressionRule.ts +102 -0
  104. package/src/foundation/engine/rules/syntax/ForRule.ts +267 -0
  105. package/src/foundation/engine/rules/syntax/IfRule.ts +261 -0
  106. package/src/foundation/worker/Router.ts +1 -1
  107. package/src/index.ts +8 -0
@@ -0,0 +1,267 @@
1
+ import { SyntaxRule } from '../../Rule.js';
2
+ import type { RuleMatch, RuleResult } from '../../Rule.js';
3
+ import Scope from '../../Scope.js';
4
+ import Expression from '../../Expression.js';
5
+ import { InvalidTemplateEngineSyntaxException } from '../../exceptions/TemplateExceptions.js';
6
+ import { isObservable } from '../../../api/Observer.js';
7
+
8
+ interface ForMatch extends RuleMatch {
9
+ data: {
10
+ /** Вариант: 'single' (item in expr), 'indexed' (idx, item in expr), 'numeric' (i in number) */
11
+ variant: 'single' | 'indexed' | 'numeric';
12
+ /** Имена локальных переменных */
13
+ variables: string[];
14
+ /** Выражение коллекции/числа */
15
+ expression: string;
16
+ /** Тело цикла */
17
+ block: string;
18
+ };
19
+ }
20
+
21
+ /**
22
+ * ForRule - обработка @for
23
+ * Варианты:
24
+ * 1. @for(item in collection) { ... }
25
+ * 2. @for(idx, item in collection) { ... }
26
+ * 3. @for(i in 5) { ... } - числовая итерация 0..4
27
+ */
28
+ export default class ForRule extends SyntaxRule {
29
+ public readonly name = 'for';
30
+ public readonly priority = 10; // Выполняется раньше всех
31
+
32
+ public find(template: string): RuleMatch[] {
33
+ const results: RuleMatch[] = [];
34
+ const lowerTemplate = template.toLowerCase();
35
+ let i = 0;
36
+
37
+ while (i < template.length) {
38
+ const idx = lowerTemplate.indexOf('@for', i);
39
+ if (idx === -1) break;
40
+
41
+ // Check for @@ escape
42
+ if (idx > 0 && template[idx - 1] === '@') {
43
+ i = idx + 1;
44
+ continue;
45
+ }
46
+
47
+ const parsed = this.parseForStatement(template, idx);
48
+ if (parsed) {
49
+ results.push({
50
+ fullMatch: template.slice(parsed.start, parsed.end),
51
+ start: parsed.start,
52
+ end: parsed.end,
53
+ data: {
54
+ variant: parsed.variant,
55
+ variables: parsed.variables,
56
+ expression: parsed.expression,
57
+ block: parsed.block
58
+ }
59
+ });
60
+ i = parsed.end;
61
+ } else {
62
+ i = idx + 1;
63
+ }
64
+ }
65
+
66
+ return results;
67
+ }
68
+
69
+ private parseForStatement(template: string, start: number): {
70
+ start: number;
71
+ end: number;
72
+ variant: 'single' | 'indexed' | 'numeric';
73
+ variables: string[];
74
+ expression: string;
75
+ block: string;
76
+ } | null {
77
+ let pos = start + 4; // '@for'.length
78
+
79
+ // Skip whitespace
80
+ while (pos < template.length && /\s/.test(template[pos])) {
81
+ pos++;
82
+ }
83
+
84
+ // Expect (
85
+ if (template[pos] !== '(') return null;
86
+
87
+ // Parse balanced parentheses
88
+ const conditionStart = pos + 1;
89
+ pos++;
90
+ let depth = 1;
91
+
92
+ while (pos < template.length && depth > 0) {
93
+ const ch = template[pos];
94
+ if (ch === '"' || ch === "'" || ch === '`') {
95
+ pos = this.skipString(template, pos, ch);
96
+ continue;
97
+ }
98
+ if (ch === '(') depth++;
99
+ else if (ch === ')') depth--;
100
+ pos++;
101
+ }
102
+
103
+ if (depth !== 0) return null;
104
+ const conditionContent = template.slice(conditionStart, pos - 1).trim();
105
+
106
+ // Parse condition: "var in expr" or "idx, var in expr"
107
+ const inMatch = conditionContent.match(/^(.+?)\s+in\s+(.+)$/);
108
+ if (!inMatch) return null;
109
+
110
+ const varPart = inMatch[1].trim();
111
+ const expression = inMatch[2].trim();
112
+
113
+ // Parse variables
114
+ let variables: string[];
115
+ let variant: 'single' | 'indexed' | 'numeric';
116
+
117
+ if (varPart.includes(',')) {
118
+ // Indexed variant: "idx, item"
119
+ variables = varPart.split(',').map(v => v.trim());
120
+ if (variables.length !== 2) return null;
121
+ variant = 'indexed';
122
+ } else {
123
+ variables = [varPart];
124
+ variant = 'single'; // Will be determined at execution time if numeric
125
+ }
126
+
127
+ // Skip whitespace
128
+ while (pos < template.length && /\s/.test(template[pos])) {
129
+ pos++;
130
+ }
131
+
132
+ // Expect {
133
+ if (template[pos] !== '{') return null;
134
+
135
+ // Parse balanced braces
136
+ const blockStart = pos + 1;
137
+ pos++;
138
+ depth = 1;
139
+
140
+ while (pos < template.length && depth > 0) {
141
+ const ch = template[pos];
142
+ if (ch === '"' || ch === "'" || ch === '`') {
143
+ pos = this.skipString(template, pos, ch);
144
+ continue;
145
+ }
146
+ if (ch === '{') depth++;
147
+ else if (ch === '}') depth--;
148
+ pos++;
149
+ }
150
+
151
+ if (depth !== 0) return null;
152
+ const block = template.slice(blockStart, pos - 1);
153
+
154
+ return { start, end: pos, variant, variables, expression, block };
155
+ }
156
+
157
+ private skipString(input: string, pos: number, quote: string): number {
158
+ pos++;
159
+ while (pos < input.length) {
160
+ if (input[pos] === '\\') {
161
+ pos += 2;
162
+ continue;
163
+ }
164
+ if (input[pos] === quote) {
165
+ return pos + 1;
166
+ }
167
+ pos++;
168
+ }
169
+ return pos;
170
+ }
171
+
172
+ public execute(match: RuleMatch, scope: Scope, engine?: any): RuleResult {
173
+ const data = (match as ForMatch).data;
174
+ const observables: any[] = [];
175
+ const outputs: string[] = [];
176
+
177
+ // Evaluate expression
178
+ const expr = new Expression(data.expression);
179
+
180
+ // Находим Observable в выражении
181
+ const exprObservables = expr.findObservables(scope);
182
+ observables.push(...exprObservables);
183
+
184
+ let collection = expr.execute(scope);
185
+
186
+ // Check if Observable
187
+ if (isObservable(collection)) {
188
+ observables.push(collection);
189
+ collection = collection.getObject?.() ?? collection;
190
+ }
191
+
192
+ // Determine iteration type
193
+ if (typeof collection === 'number') {
194
+ // Numeric iteration: 0 to collection-1
195
+ if (data.variant === 'indexed') {
196
+ throw new InvalidTemplateEngineSyntaxException(
197
+ '@for with numeric value does not support indexed variant (idx, var). Use single variable.'
198
+ );
199
+ }
200
+
201
+ const count = Math.floor(collection);
202
+ if (count < 0) {
203
+ throw new InvalidTemplateEngineSyntaxException(
204
+ `@for numeric value must be non-negative, got: ${count}`
205
+ );
206
+ }
207
+
208
+ for (let i = 0; i < count; i++) {
209
+ const localScope = scope.createChild({ [data.variables[0]]: i });
210
+
211
+ if (engine) {
212
+ const result = engine.processTemplate(data.block, localScope);
213
+ outputs.push(result.output);
214
+ if (result.observables) observables.push(...result.observables);
215
+ } else {
216
+ outputs.push(data.block);
217
+ }
218
+ }
219
+ } else if (Array.isArray(collection) || (collection && typeof collection[Symbol.iterator] === 'function')) {
220
+ // Array or iterable
221
+ const items = Array.isArray(collection) ? collection : Array.from(collection);
222
+
223
+ if (data.variant === 'indexed') {
224
+ // (idx, item in collection)
225
+ items.forEach((item, idx) => {
226
+ const localScope = scope.createChild({
227
+ [data.variables[0]]: idx,
228
+ [data.variables[1]]: item
229
+ });
230
+
231
+ if (engine) {
232
+ const result = engine.processTemplate(data.block, localScope);
233
+ outputs.push(result.output);
234
+ if (result.observables) observables.push(...result.observables);
235
+ } else {
236
+ outputs.push(data.block);
237
+ }
238
+ });
239
+ } else {
240
+ // (item in collection)
241
+ items.forEach((item) => {
242
+ const localScope = scope.createChild({ [data.variables[0]]: item });
243
+
244
+ if (engine) {
245
+ const result = engine.processTemplate(data.block, localScope);
246
+ outputs.push(result.output);
247
+ if (result.observables) observables.push(...result.observables);
248
+ } else {
249
+ outputs.push(data.block);
250
+ }
251
+ });
252
+ }
253
+ } else if (typeof collection === 'string') {
254
+ throw new InvalidTemplateEngineSyntaxException(
255
+ `@for does not support string iteration. Got: "${collection}"`
256
+ );
257
+ } else if (collection === null || collection === undefined) {
258
+ // Empty result
259
+ } else {
260
+ throw new InvalidTemplateEngineSyntaxException(
261
+ `@for expression must return a number, array, or iterable. Got: ${typeof collection}`
262
+ );
263
+ }
264
+
265
+ return { output: outputs.join(''), observables };
266
+ }
267
+ }
@@ -0,0 +1,261 @@
1
+ import { SyntaxRule } from '../../Rule.js';
2
+ import type { RuleMatch, RuleResult } from '../../Rule.js';
3
+ import Scope from '../../Scope.js';
4
+ import Expression from '../../Expression.js';
5
+ import BalancedParser from '../../BalancedParser.js';
6
+ import { InvalidTemplateEngineSyntaxException } from '../../exceptions/TemplateExceptions.js';
7
+ import { isObservable } from '../../../api/Observer.js';
8
+
9
+ interface IfChainMatch extends RuleMatch {
10
+ data: {
11
+ chain: Array<{
12
+ type: 'if' | 'elseif' | 'else';
13
+ condition?: string;
14
+ block: string;
15
+ }>;
16
+ };
17
+ }
18
+
19
+ /**
20
+ * IfRule - обработка @if/@elseif/@else
21
+ */
22
+ export default class IfRule extends SyntaxRule {
23
+ public readonly name = 'if';
24
+ public readonly priority = 20; // Выполняется раньше @expression
25
+
26
+ public find(template: string): RuleMatch[] {
27
+ const results: RuleMatch[] = [];
28
+ const lowerTemplate = template.toLowerCase();
29
+ let i = 0;
30
+
31
+ while (i < template.length) {
32
+ const idx = lowerTemplate.indexOf('@if', i);
33
+ if (idx === -1) break;
34
+
35
+ // Check for @@ escape
36
+ if (idx > 0 && template[idx - 1] === '@') {
37
+ i = idx + 1;
38
+ continue;
39
+ }
40
+
41
+ // Parse the full if/elseif/else chain
42
+ const chain = this.parseIfChain(template, idx);
43
+ if (chain) {
44
+ results.push({
45
+ fullMatch: template.slice(chain.start, chain.end),
46
+ start: chain.start,
47
+ end: chain.end,
48
+ data: { chain: chain.items }
49
+ });
50
+ i = chain.end;
51
+ } else {
52
+ i = idx + 1;
53
+ }
54
+ }
55
+
56
+ return results;
57
+ }
58
+
59
+ private parseIfChain(template: string, startIdx: number): {
60
+ start: number;
61
+ end: number;
62
+ items: Array<{ type: 'if' | 'elseif' | 'else'; condition?: string; block: string }>
63
+ } | null {
64
+ const items: Array<{ type: 'if' | 'elseif' | 'else'; condition?: string; block: string }> = [];
65
+ let pos = startIdx;
66
+
67
+ // Parse @if
68
+ const ifParsed = this.parseConditionBlock(template, pos, '@if');
69
+ if (!ifParsed) return null;
70
+
71
+ items.push({ type: 'if', condition: ifParsed.condition, block: ifParsed.block });
72
+ pos = ifParsed.end;
73
+
74
+ // Parse @elseif/@else chain
75
+ while (pos < template.length) {
76
+ // Skip whitespace
77
+ while (pos < template.length && /\s/.test(template[pos])) {
78
+ pos++;
79
+ }
80
+
81
+ const remaining = template.slice(pos).toLowerCase();
82
+
83
+ if (remaining.startsWith('@elseif')) {
84
+ const elseifParsed = this.parseConditionBlock(template, pos, '@elseif');
85
+ if (!elseifParsed) break;
86
+ items.push({ type: 'elseif', condition: elseifParsed.condition, block: elseifParsed.block });
87
+ pos = elseifParsed.end;
88
+ } else if (remaining.startsWith('@else') && !remaining.startsWith('@elseif')) {
89
+ const elseParsed = this.parseElseBlock(template, pos);
90
+ if (!elseParsed) break;
91
+ items.push({ type: 'else', block: elseParsed.block });
92
+ pos = elseParsed.end;
93
+ break; // @else is always last
94
+ } else {
95
+ break;
96
+ }
97
+ }
98
+
99
+ return { start: startIdx, end: pos, items };
100
+ }
101
+
102
+ private parseConditionBlock(template: string, start: number, keyword: string): {
103
+ condition: string;
104
+ block: string;
105
+ end: number;
106
+ } | null {
107
+ let pos = start + keyword.length;
108
+
109
+ // Skip whitespace
110
+ while (pos < template.length && /\s/.test(template[pos])) {
111
+ pos++;
112
+ }
113
+
114
+ // Expect (
115
+ if (template[pos] !== '(') return null;
116
+
117
+ // Parse balanced condition
118
+ const conditionStart = pos + 1;
119
+ pos++;
120
+ let depth = 1;
121
+
122
+ while (pos < template.length && depth > 0) {
123
+ const ch = template[pos];
124
+ if (ch === '"' || ch === "'" || ch === '`') {
125
+ pos = this.skipString(template, pos, ch);
126
+ continue;
127
+ }
128
+ if (ch === '(') depth++;
129
+ else if (ch === ')') depth--;
130
+ pos++;
131
+ }
132
+
133
+ if (depth !== 0) return null;
134
+ const condition = template.slice(conditionStart, pos - 1);
135
+
136
+ // Skip whitespace
137
+ while (pos < template.length && /\s/.test(template[pos])) {
138
+ pos++;
139
+ }
140
+
141
+ // Expect {
142
+ if (template[pos] !== '{') return null;
143
+
144
+ // Parse balanced block
145
+ const blockStart = pos + 1;
146
+ pos++;
147
+ depth = 1;
148
+
149
+ while (pos < template.length && depth > 0) {
150
+ const ch = template[pos];
151
+ if (ch === '"' || ch === "'" || ch === '`') {
152
+ pos = this.skipString(template, pos, ch);
153
+ continue;
154
+ }
155
+ if (ch === '{') depth++;
156
+ else if (ch === '}') depth--;
157
+ pos++;
158
+ }
159
+
160
+ if (depth !== 0) return null;
161
+ const block = template.slice(blockStart, pos - 1);
162
+
163
+ return { condition, block, end: pos };
164
+ }
165
+
166
+ private parseElseBlock(template: string, start: number): { block: string; end: number } | null {
167
+ let pos = start + 5; // '@else'.length
168
+
169
+ // Skip whitespace
170
+ while (pos < template.length && /\s/.test(template[pos])) {
171
+ pos++;
172
+ }
173
+
174
+ // Expect {
175
+ if (template[pos] !== '{') return null;
176
+
177
+ // Parse balanced block
178
+ const blockStart = pos + 1;
179
+ pos++;
180
+ let depth = 1;
181
+
182
+ while (pos < template.length && depth > 0) {
183
+ const ch = template[pos];
184
+ if (ch === '"' || ch === "'" || ch === '`') {
185
+ pos = this.skipString(template, pos, ch);
186
+ continue;
187
+ }
188
+ if (ch === '{') depth++;
189
+ else if (ch === '}') depth--;
190
+ pos++;
191
+ }
192
+
193
+ if (depth !== 0) return null;
194
+ const block = template.slice(blockStart, pos - 1);
195
+
196
+ return { block, end: pos };
197
+ }
198
+
199
+ private skipString(input: string, pos: number, quote: string): number {
200
+ pos++;
201
+ while (pos < input.length) {
202
+ if (input[pos] === '\\') {
203
+ pos += 2;
204
+ continue;
205
+ }
206
+ if (input[pos] === quote) {
207
+ return pos + 1;
208
+ }
209
+ pos++;
210
+ }
211
+ return pos;
212
+ }
213
+
214
+ public execute(match: RuleMatch, scope: Scope, engine?: any): RuleResult {
215
+ const chain = (match as IfChainMatch).data.chain;
216
+ const observables: any[] = [];
217
+
218
+ for (const item of chain) {
219
+ if (item.type === 'else') {
220
+ // Process else block
221
+ if (engine) {
222
+ const result = engine.processTemplate(item.block, scope);
223
+ return { output: result.output, observables: [...observables, ...(result.observables || [])] };
224
+ }
225
+ return { output: item.block, observables };
226
+ }
227
+
228
+ // Evaluate condition
229
+ const expr = new Expression(item.condition!);
230
+
231
+ // Находим Observable в условии
232
+ const conditionObservables = expr.findObservables(scope);
233
+ observables.push(...conditionObservables);
234
+
235
+ const conditionResult = expr.execute(scope);
236
+
237
+ // Check if result is Observable
238
+ if (isObservable(conditionResult)) {
239
+ observables.push(conditionResult);
240
+ const value = conditionResult.getObject?.() ?? conditionResult;
241
+ if (value) {
242
+ if (engine) {
243
+ const result = engine.processTemplate(item.block, scope);
244
+ return { output: result.output, observables: [...observables, ...(result.observables || [])] };
245
+ }
246
+ return { output: item.block, observables };
247
+ }
248
+ } else if (conditionResult) {
249
+ // Condition is truthy
250
+ if (engine) {
251
+ const result = engine.processTemplate(item.block, scope);
252
+ return { output: result.output, observables: [...observables, ...(result.observables || [])] };
253
+ }
254
+ return { output: item.block, observables };
255
+ }
256
+ }
257
+
258
+ // No condition matched and no else
259
+ return { output: '', observables };
260
+ }
261
+ }
@@ -42,13 +42,13 @@ export abstract class Router {
42
42
  }
43
43
  }
44
44
 
45
-
46
45
  public static legacyRouteTo(route: string) {
47
46
  const url = new URL(Fetcher.resolveUrl(route));
48
47
  if (window.location.pathname !== url.pathname) {
49
48
  window.location.replace(url.href);
50
49
  }
51
50
  }
51
+
52
52
  public static tryRouteTo(url: URL, pushState: boolean = true) {
53
53
  const urlH = new URL(Fetcher.resolveUrl(url.href));
54
54
  try {
package/src/index.ts CHANGED
@@ -16,12 +16,20 @@ export { ReComponent, RePage } from './foundation/TripletDecorator.js';
16
16
  export { default as Fetcher } from './foundation/Fetcher.js';
17
17
  export { default as HMLEParser } from './foundation/HMLEParser.js';
18
18
 
19
+ export * from './foundation/engine/TemplateEngine.js';
20
+
21
+ export {
22
+ InvalidDynamicRuleUsage,
23
+ InvalidTemplateEngineSyntaxException
24
+ } from './foundation/engine/exceptions/TemplateExceptions.js';
25
+
19
26
  export { Router } from './foundation/worker/Router.js';
20
27
  export { default as ServiceWorker } from './foundation/worker/ServiceWorker.js';
21
28
 
22
29
  export * from './foundation/Hosting.js';
23
30
  export * from './foundation/Theme.js';
24
31
 
32
+
25
33
  // derive the part of href after the origin (e.g. "/path?query#hash")
26
34
  /*export const HOSTING: string = window.location.href.startsWith(window.location.origin)
27
35
  ? window.location.href.substring(window.location.origin.length)