@reidelsaltres/pureper 0.1.160 → 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 (48) 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/component_api/Component.d.ts +2 -2
  5. package/out/foundation/component_api/Component.d.ts.map +1 -1
  6. package/out/foundation/component_api/Component.js.map +1 -1
  7. package/out/foundation/component_api/UniHtml.d.ts +4 -10
  8. package/out/foundation/component_api/UniHtml.d.ts.map +1 -1
  9. package/out/foundation/component_api/UniHtml.js +7 -15
  10. package/out/foundation/component_api/UniHtml.js.map +1 -1
  11. package/out/foundation/engine/Rule.d.ts +2 -0
  12. package/out/foundation/engine/Rule.d.ts.map +1 -1
  13. package/out/foundation/engine/Rule.js.map +1 -1
  14. package/out/foundation/engine/Scope.d.ts +5 -1
  15. package/out/foundation/engine/Scope.d.ts.map +1 -1
  16. package/out/foundation/engine/Scope.js +12 -3
  17. package/out/foundation/engine/Scope.js.map +1 -1
  18. package/out/foundation/engine/TemplateEngine.d.ts +17 -0
  19. package/out/foundation/engine/TemplateEngine.d.ts.map +1 -1
  20. package/out/foundation/engine/TemplateEngine.js +51 -3
  21. package/out/foundation/engine/TemplateEngine.js.map +1 -1
  22. package/out/foundation/engine/TemplateInstance.d.ts +167 -47
  23. package/out/foundation/engine/TemplateInstance.d.ts.map +1 -1
  24. package/out/foundation/engine/TemplateInstance.js +500 -118
  25. package/out/foundation/engine/TemplateInstance.js.map +1 -1
  26. package/out/foundation/engine/TemplateInstance.old.d.ts +219 -0
  27. package/out/foundation/engine/TemplateInstance.old.d.ts.map +1 -0
  28. package/out/foundation/engine/TemplateInstance.old.js +487 -0
  29. package/out/foundation/engine/TemplateInstance.old.js.map +1 -0
  30. package/out/foundation/engine/rules/attribute/RefRule.d.ts.map +1 -1
  31. package/out/foundation/engine/rules/attribute/RefRule.js +7 -1
  32. package/out/foundation/engine/rules/attribute/RefRule.js.map +1 -1
  33. package/out/index.d.ts +0 -1
  34. package/out/index.d.ts.map +1 -1
  35. package/out/index.js +0 -1
  36. package/out/index.js.map +1 -1
  37. package/package.json +1 -1
  38. package/src/foundation/Triplet.ts +6 -6
  39. package/src/foundation/component_api/Component.ts +2 -1
  40. package/src/foundation/component_api/UniHtml.ts +12 -22
  41. package/src/foundation/engine/Rule.ts +2 -0
  42. package/src/foundation/engine/Scope.ts +13 -3
  43. package/src/foundation/engine/TemplateEngine.ts +79 -4
  44. package/src/foundation/engine/TemplateInstance.md +110 -0
  45. package/src/foundation/engine/TemplateInstance.old.ts +673 -0
  46. package/src/foundation/engine/TemplateInstance.ts +635 -147
  47. package/src/foundation/engine/rules/attribute/RefRule.ts +8 -1
  48. package/src/index.ts +0 -1
@@ -6,6 +6,7 @@
6
6
  * Designed to replace legacy Page and Component base classes.
7
7
  */
8
8
  import IElementHolder from "../api/ElementHolder.js";
9
+ import { TemplateInstance } from "../engine/TemplateEngine.js";
9
10
 
10
11
 
11
12
  /**
@@ -13,7 +14,6 @@ import IElementHolder from "../api/ElementHolder.js";
13
14
  * Use static factory methods for instantiation.
14
15
  */
15
16
  export default class UniHtml {
16
-
17
17
  /**
18
18
  * Unified component lifecycle entrypoint.
19
19
  * Loads HTML, then calls preLoadJS, render, and postLoadJS hooks in order.
@@ -21,30 +21,27 @@ export default class UniHtml {
21
21
  */
22
22
  public async load(element: HTMLElement | ShadowRoot): Promise<void> {;
23
23
  await this.preInit();
24
- const preHtml: DocumentFragment = await this._init();
25
- const html: DocumentFragment = await this._postInit(preHtml);
24
+ const preHtml: TemplateInstance = await this._init();
25
+ const html: TemplateInstance = await this._postInit(preHtml);
26
26
 
27
- const localRoot = html;
28
-
29
- const holder : IElementHolder = { element: localRoot };
30
27
 
31
28
  // ВАЖНО: preLoad() вызывается ДО монтирования в DOM/Shadow DOM.
32
29
  // Для компонентов (UniHtmlComponent) на этом этапе ещё нельзя полагаться на this.shadowRoot —
33
30
  // используйте переданный localRoot для подготовки DOM, данных и навешивания обработчиков.
34
31
  // Это предпочтительный этап инициализации для компонентов.
35
- await this.preLoad(holder);
32
+ await this.preLoad(html);
36
33
  // render() отвечает за помещение содержимого из localRoot в конечную цель (renderTarget).
37
34
  // В UniHtmlComponent.render() после вызова базового render() происходит добавление wrapper в shadowRoot.
38
- await this.render(holder, element);
35
+ await this.render(html, element);
39
36
  // postLoad() вызывается ПОСЛЕ render(). Для компонентов к этому моменту содержимое уже добавлено
40
37
  // внутрь shadowRoot, и можно безопасно работать с this.shadowRoot, измерениями layout и т.п.
41
- await this.postLoad(holder);
38
+ await this.postLoad(html);
42
39
  }
43
40
 
44
- private async _postInit(html: DocumentFragment): Promise<DocumentFragment> {
41
+ private async _postInit(html: TemplateInstance): Promise<TemplateInstance> {
45
42
  throw new Error("Method not implemented.");
46
43
  }
47
- private async _init(): Promise<DocumentFragment> {
44
+ private async _init(): Promise<TemplateInstance> {
48
45
  throw new Error("Method not implemented.");
49
46
  }
50
47
 
@@ -55,34 +52,27 @@ export default class UniHtml {
55
52
  * РЕКОМЕНДАЦИЯ: предпочитайте выполнять основную подготовку, поиск элементов, навешивание обработчиков
56
53
  * на узлы из localRoot именно здесь; затем render() вставит их в целевой контейнер/теневой DOM.
57
54
  */
58
- protected async preLoad(holder : IElementHolder) { }
55
+ protected async preLoad(template: TemplateInstance) { }
59
56
  /**
60
57
  * Hook after rendering (e.g., event binding).
61
58
  * Для компонентов вызывается после того, как содержимое вставлено в shadowRoot (см. UniHtmlComponent.render()).
62
59
  * Используйте этот этап только когда необходим доступ к реально смонтированному DOM (layout/measurements,
63
60
  * интеграции, требующие присутствия в документе). В остальных случаях предпочитайте preLoad().
64
61
  */
65
- protected async postLoad(holder: IElementHolder) { }
62
+ protected async postLoad(template: TemplateInstance) { }
66
63
  /**
67
64
  * Main rendering step. By default, simply inserts HTML into the container.
68
65
  * Override in subclasses for custom rendering logic.
69
66
  * @param element Target container
70
67
  * @param html HTML content
71
68
  */
72
- protected async render(holder: IElementHolder, renderTarget: HTMLElement | DocumentFragment): Promise<void> {
69
+ protected async render(template: TemplateInstance, renderTarget: HTMLElement | DocumentFragment): Promise<void> {
73
70
  // Clear renderTarget
74
71
  while (renderTarget.firstChild) {
75
72
  renderTarget.removeChild(renderTarget.firstChild);
76
73
  }
77
-
78
- // Move all children from holder.element to renderTarget
79
- const children = Array.from(holder.element.childNodes);
80
- for (const child of children) {
81
- renderTarget.appendChild(child);
82
- }
74
+ template.bind(renderTarget as any);
83
75
 
84
- // Update holder to point to renderTarget (now contains the content)
85
- (holder as { element: HTMLElement | DocumentFragment }).element = renderTarget;
86
76
  return Promise.resolve();
87
77
  }
88
78
  }
@@ -25,6 +25,8 @@ export interface RuleResult {
25
25
  observables?: any[];
26
26
  /** Дочерние Rule (для вложенных структур) */
27
27
  children?: Rule[];
28
+ /** Дополнительные данные (например, refName для RefRule) */
29
+ data?: Record<string, any>;
28
30
  }
29
31
 
30
32
  /**
@@ -5,6 +5,8 @@
5
5
  export default class Scope {
6
6
  private variables: Record<string, any> = {};
7
7
  private readonly debugWarnings: boolean;
8
+ /** Оригинальный объект для синхронизации refs */
9
+ private originalSource: object | null = null;
8
10
 
9
11
  constructor(options?: { debugWarnings?: boolean }) {
10
12
  this.debugWarnings = options?.debugWarnings ?? true;
@@ -19,12 +21,18 @@ export default class Scope {
19
21
 
20
22
  /**
21
23
  * Установить переменную в Scope
24
+ * @param syncToOriginal - синхронизировать с оригинальным объектом (по умолчанию true)
22
25
  */
23
- public set(key: string, value: any): void {
24
- if (this.debugWarnings && key in this.variables) {
25
- console.warn(`[Scope] Warning: Variable "${key}" is being overwritten`);
26
+ public set(key: string, value: any, syncToOriginal: boolean = true): void {
27
+ if (this.debugWarnings && key in this.variables && this.variables[key] !== undefined) {
28
+ // Не предупреждаем при установке ref (значение было undefined)
26
29
  }
27
30
  this.variables[key] = value;
31
+
32
+ // Синхронизируем с оригинальным объектом если он есть
33
+ if (syncToOriginal && this.originalSource) {
34
+ (this.originalSource as any)[key] = value;
35
+ }
28
36
  }
29
37
 
30
38
  /**
@@ -146,9 +154,11 @@ export default class Scope {
146
154
 
147
155
  /**
148
156
  * Статический метод для создания Scope из объекта
157
+ * Сохраняет ссылку на оригинальный объект для синхронизации refs
149
158
  */
150
159
  public static from(source: object | Record<string, any>, options?: { debugWarnings?: boolean }): Scope {
151
160
  const scope = new Scope(options);
161
+ scope.originalSource = source;
152
162
  scope.merge(source);
153
163
  return scope;
154
164
  }
@@ -1,7 +1,7 @@
1
1
  import Scope from './Scope.js';
2
2
  import Expression from './Expression.js';
3
3
  import EscapeHandler from './EscapeHandler.js';
4
- import TemplateInstance, { TemplateSection } from './TemplateInstance.js';
4
+ import TemplateInstance, { TemplateSection, FragmentBinding } from './TemplateInstance.js';
5
5
  import Rule, { RuleMatch, RuleResult, SyntaxRule, AttributeRule } from './Rule.js';
6
6
  import Observable from '../api/Observer.js';
7
7
 
@@ -30,6 +30,8 @@ export interface ProcessResult {
30
30
  output: string;
31
31
  observables: Observable<any>[];
32
32
  sections: TemplateSection[];
33
+ /** ID созданного фрагмента (если был создан) */
34
+ fragmentId?: string;
33
35
  }
34
36
 
35
37
  /**
@@ -97,10 +99,15 @@ export default class TemplateEngine {
97
99
  * Обработать шаблон и вернуть TemplateInstance
98
100
  */
99
101
  public parse(template: string): TemplateInstance {
100
- const result = this.processTemplate(template, this.scope);
101
- const templateInstance = new TemplateInstance(result.output, this.scope);
102
+ const templateInstance = new TemplateInstance(this.scope);
103
+ const result = this.processTemplateWithFragments(template, this.scope, templateInstance, null);
102
104
 
103
- // Add sections
105
+ // Устанавливаем корневой фрагмент
106
+ if (result.fragmentId) {
107
+ templateInstance.setRootFragment(result.fragmentId);
108
+ }
109
+
110
+ // Add sections and track observables
104
111
  for (const section of result.sections) {
105
112
  templateInstance.addSection(section);
106
113
 
@@ -115,6 +122,31 @@ export default class TemplateEngine {
115
122
  return templateInstance;
116
123
  }
117
124
 
125
+ /**
126
+ * Обработать шаблон с созданием фрагментов
127
+ */
128
+ private processTemplateWithFragments(
129
+ template: string,
130
+ scope: Scope,
131
+ instance: TemplateInstance,
132
+ parentFragmentId: string | null
133
+ ): ProcessResult {
134
+ const result = this.processTemplate(template, scope);
135
+
136
+ // Создаём фрагмент для этого результата
137
+ const fragmentId = instance.createFragment(
138
+ result.output,
139
+ template,
140
+ result.sections,
141
+ parentFragmentId
142
+ );
143
+
144
+ return {
145
+ ...result,
146
+ fragmentId
147
+ };
148
+ }
149
+
118
150
  /**
119
151
  * Обработать шаблон (внутренний метод, используется Rule для рекурсии)
120
152
  */
@@ -219,6 +251,49 @@ export default class TemplateEngine {
219
251
  .replace(/@injection\[(head|tail)\]\s*=\s*["'][^"']*["']/gi, '');
220
252
  }
221
253
 
254
+ /**
255
+ * Добавить новый шаблон в существующий TemplateInstance.
256
+ * Обрабатывает шаблон и добавляет результат как новый фрагмент.
257
+ * Если instance привязан к контейнерам, DOM обновится автоматически.
258
+ *
259
+ * @param instance - существующий TemplateInstance
260
+ * @param template - новый шаблон для добавления
261
+ * @param customScope - опциональный scope для нового шаблона
262
+ * @returns ID созданного фрагмента
263
+ */
264
+ public appendTemplate(instance: TemplateInstance, template: string, customScope?: Scope | object): string {
265
+ const scope = customScope
266
+ ? (customScope instanceof Scope ? customScope : Scope.from(customScope))
267
+ : instance.getScope();
268
+
269
+ // Обрабатываем шаблон
270
+ const result = this.processTemplate(template, scope);
271
+
272
+ // Создаём фрагмент
273
+ const fragmentId = instance.createFragment(
274
+ result.output,
275
+ template,
276
+ result.sections,
277
+ null // без родителя - это отдельный фрагмент
278
+ );
279
+
280
+ // Добавляем секции и отслеживаем Observable
281
+ for (const section of result.sections) {
282
+ instance.addSection(section);
283
+
284
+ for (const observable of section.result.observables || []) {
285
+ instance.trackObservable(observable, section, (s) => {
286
+ return this.processTemplate(s.sourceTemplate, scope);
287
+ });
288
+ }
289
+ }
290
+
291
+ // Вставляем в привязанные контейнеры
292
+ instance.insertAppendedFragment(fragmentId);
293
+
294
+ return fragmentId;
295
+ }
296
+
222
297
  /**
223
298
  * Статический метод для быстрой обработки
224
299
  */
@@ -0,0 +1,110 @@
1
+ Проанализируй текущий код TemplateInstance, Переделай TemplateInstance
2
+
3
+ # Предположительный пример структуры и работы
4
+
5
+ Должен содержать множество различных мелких фрагментов, и при обновлении rebuild-ить только те на которые изменения повлияли, т.е
6
+ Если:
7
+ <div>@(observable1)</div>
8
+ <div>@(observable2)</div>
9
+
10
+ должно создатся 2 фрагмента
11
+ 1. <div>@(observable1)</div> - пересоздастся только при изменении observable1, изменения observable2 не повлияют на него
12
+ 2. <div>@(observable2)</div> - пересоздастся только при изменении observable2, изменения observable1 не повлияют на него
13
+
14
+ ---
15
+
16
+ Если:
17
+ <div>@(observable1) и @(observable2)</div> то изменения observable1 и изменение observable2 должны влиять
18
+
19
+ здесь 1 фрагмент:
20
+ 1. <div>@(observable1) и @(observable2)</div> #id1
21
+
22
+ ---
23
+
24
+ Если:
25
+ <div @[ref]="'outer'">
26
+ <span>@(observable1)</span>
27
+ <span>@(observable2)</span>
28
+ @for (observableVar in observableCollection) {
29
+ <span>@(observableVar)</span>
30
+ }
31
+ </div>
32
+
33
+ здесь 5+ фрагментов:
34
+ 1. <div @[ref]="'outer'"> #id1
35
+ #id2
36
+ #id3
37
+ #id4
38
+ </div>
39
+ 2. <span>@(observable1)</span> #id2
40
+ 3. <span>@(observable2)</span> #id3
41
+ 4. *RuleFragment* @for (observableVar in observableCollection) { #id5 } #id4
42
+ 5+ <span>@(observableVar)</span> #id5
43
+
44
+ 3 отдельных фрагмента (#id5-a, #id5-b, #id5-c), каждый обновляется независимо
45
+
46
+ Если изменится observable1 то пересоздастся должен только #id2
47
+ Если изменится observableCollection то пересоздастся должен #id4 и соответсвенно это коснётся и вложенного в него #id5
48
+ Измеения в var в #id5 должно пересоздать только #id5
49
+
50
+
51
+ Если Scope { observable1 = new Observable(1),
52
+ observable1 = new Observable(2),
53
+ observableCollection = new Observable([55,56,57]) }
54
+
55
+ то результат будет выглядеть так:
56
+
57
+ <div id="'outer'">
58
+ <span>1</span>
59
+ <span>2</span>
60
+ <span>55</span>
61
+ <span>56</span>
62
+ <span>57</span>
63
+ </div>
64
+
65
+ но при изменении observable1.setObject(999) то результат станет:
66
+
67
+ <div id="'outer'">
68
+ <span>999</span>
69
+ <span>2</span>
70
+ <span>55</span>
71
+ <span>56</span>
72
+ <span>57</span>
73
+ </div>
74
+
75
+ если TemplateInstance имеет биндинг с контейнером, то аналогичные изменения должны происходить и в контейнере
76
+
77
+ ---
78
+
79
+
80
+
81
+ изменения observable1 повлияет только на #id1
82
+ изменения observabıe2 повлияет только на #id2
83
+
84
+ TemplateInstance должен иметь эмитить ивенты на которые можно подписатся
85
+ onFragmentChange(event: FragmentChangeEvent)
86
+
87
+ должен иметь методы
88
+ bindRefs() - должен создавать в Scope переменные из @[ref] правила
89
+ unbindRefs() - должен удалять из Scope переменные из @[ref] правила (приравнивать к null)
90
+
91
+ bindEvents() - должен добавлять обработчик событий к html-element из @on[event] правила
92
+ unbindEvents() - должен удалять обработчик событий из html-element по @on[event] правилу
93
+
94
+ bind(container) - должен связывать внутреннюю DOM структуру с container, должен вставлять DOM в container
95
+ unbind(container) - должен удалять связь внутренней DOM структуры с container, оставляет элементы в container, но при изменении в TemplateInstance container уже не должен меняется
96
+
97
+ Если существует binding с container то изменения внутри TemplateInstance должны так же отражатся и на container,
98
+ удаление и добавление ивентов, динамическое обновление и пересоздание фагментов,
99
+
100
+ TemplateInstance может иметь множество контейнеров binding-ов
101
+
102
+ bind(container) - должен автоматически вызывать bindRefs,bindEvents
103
+ unbind(container) - должен автоматически вызывать unbindRefs,unbindEvents
104
+
105
+ # Уточнения
106
+ 1. Архитектурная проблема — TemplateEngine не создаёт фрагменты:
107
+ Сейчас TemplateEngine.parse() возвращает один общий результат. Разбиение на фрагменты по Observable не происходит. Нужно менять логику парсинга. Изменения TemplateEngine приемлимы если требуются
108
+
109
+
110
+