@adaas/are-html 0.0.21 → 0.0.22

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 (55) hide show
  1. package/.conf/tsconfig.base.json +1 -0
  2. package/.conf/tsconfig.browser.json +1 -0
  3. package/.conf/tsconfig.node.json +1 -0
  4. package/dist/browser/index.d.mts +45 -2
  5. package/dist/browser/index.mjs +170 -10
  6. package/dist/browser/index.mjs.map +1 -1
  7. package/dist/node/directives/AreDirectiveFor.directive.d.mts +37 -1
  8. package/dist/node/directives/AreDirectiveFor.directive.d.ts +37 -1
  9. package/dist/node/directives/AreDirectiveFor.directive.js +85 -4
  10. package/dist/node/directives/AreDirectiveFor.directive.js.map +1 -1
  11. package/dist/node/directives/AreDirectiveFor.directive.mjs +85 -4
  12. package/dist/node/directives/AreDirectiveFor.directive.mjs.map +1 -1
  13. package/dist/node/engine/AreHTML.lifecycle.d.mts +8 -1
  14. package/dist/node/engine/AreHTML.lifecycle.d.ts +8 -1
  15. package/dist/node/engine/AreHTML.lifecycle.js +46 -3
  16. package/dist/node/engine/AreHTML.lifecycle.js.map +1 -1
  17. package/dist/node/engine/AreHTML.lifecycle.mjs +46 -3
  18. package/dist/node/engine/AreHTML.lifecycle.mjs.map +1 -1
  19. package/dist/node/helpers/AreScheduler.helper.d.mts +39 -0
  20. package/dist/node/helpers/AreScheduler.helper.d.ts +39 -0
  21. package/dist/node/helpers/AreScheduler.helper.js +40 -0
  22. package/dist/node/helpers/AreScheduler.helper.js.map +1 -0
  23. package/dist/node/helpers/AreScheduler.helper.mjs +40 -0
  24. package/dist/node/helpers/AreScheduler.helper.mjs.map +1 -0
  25. package/dist/node/lib/AreRoot/AreRoot.component.js +1 -1
  26. package/dist/node/lib/AreRoot/AreRoot.component.js.map +1 -1
  27. package/dist/node/lib/AreRoot/AreRoot.component.mjs +1 -1
  28. package/dist/node/lib/AreRoot/AreRoot.component.mjs.map +1 -1
  29. package/examples/dashboard/dist/index.html +1 -1
  30. package/examples/dashboard/dist/{mq19zxz4-mnlgmd.js → mqh9ryml-xat335.js} +1922 -1316
  31. package/examples/dashboard/src/concept.ts +3 -2
  32. package/examples/for-perf/concept.ts +45 -0
  33. package/examples/for-perf/containers/UI.container.ts +161 -0
  34. package/examples/for-perf/dist/index.html +270 -0
  35. package/examples/for-perf/dist/mqh9ryde-m243t8.js +15223 -0
  36. package/examples/for-perf/dist/mqh9ryfo-6a8d0o.js +15223 -0
  37. package/examples/for-perf/dist/mqh9ryfq-4pf5cv.js +15223 -0
  38. package/examples/for-perf/public/index.html +270 -0
  39. package/examples/for-perf/src/components/PerfApp.component.ts +37 -0
  40. package/examples/for-perf/src/components/PerfControls.component.ts +34 -0
  41. package/examples/for-perf/src/components/PerfGrid.component.ts +225 -0
  42. package/examples/for-perf/src/components/PerfHeader.component.ts +34 -0
  43. package/examples/for-perf/src/components/PerfStats.component.ts +43 -0
  44. package/examples/for-perf/src/concept.ts +94 -0
  45. package/examples/jumpstart/dist/index.html +1 -1
  46. package/examples/jumpstart/dist/{mq7hqrxy-4kus50.js → mq7mgf58-vbf07e.js} +269 -91
  47. package/examples/signal-routing/dist/index.html +1 -1
  48. package/examples/signal-routing/dist/{mq7k53th-qiwy4x.js → mqh9ryc9-dkcbkx.js} +1726 -1419
  49. package/jest.config.ts +1 -0
  50. package/package.json +10 -9
  51. package/src/directives/AreDirectiveFor.directive.ts +141 -10
  52. package/src/engine/AreHTML.lifecycle.ts +83 -6
  53. package/src/helpers/AreScheduler.helper.ts +61 -0
  54. package/src/lib/AreRoot/AreRoot.component.ts +4 -1
  55. package/tsconfig.json +1 -0
@@ -69,6 +69,7 @@
69
69
  "@adaas/are-html/root/*": ["../src/lib/AreRoot/*"],
70
70
  "@adaas/are-html/directives/*": ["../src/directives/*"],
71
71
  "@adaas/are-html/style/*": ["../src/lib/AreStyle/*"],
72
+ "@adaas/are-html/helpers/*": ["../src/helpers/*"],
72
73
  "@adaas/are-html/lib/*": ["../src/lib/*"],
73
74
  },
74
75
  /* ===============================
@@ -13,6 +13,7 @@
13
13
  "@adaas/are-html/instructions/*": ["src/instructions/*"],
14
14
  "@adaas/are-html/watchers/*": ["src/watchers/*"],
15
15
  "@adaas/are-html/signals/*": ["src/signals/*"],
16
+ "@adaas/are-html/helpers/*": ["src/helpers/*"],
16
17
  // Custom Lib Exports
17
18
  "@adaas/are-html/style/*": ["src/lib/AreStyle/*"],
18
19
  "@adaas/are-html/directive/*": ["src/lib/AreDirective/*"],
@@ -13,6 +13,7 @@
13
13
  "@adaas/are-html/instructions/*": ["src/instructions/*"],
14
14
  "@adaas/are-html/watchers/*": ["src/watchers/*"],
15
15
  "@adaas/are-html/signals/*": ["src/signals/*"],
16
+ "@adaas/are-html/helpers/*": ["src/helpers/*"],
16
17
  // Custom Lib Exports
17
18
  "@adaas/are-html/style/*": ["src/lib/AreStyle/*"],
18
19
  "@adaas/are-html/directive/*": ["src/lib/AreDirective/*"],
@@ -123,9 +123,45 @@ declare class AreBindingAttribute extends AreHTMLAttribute {
123
123
  }
124
124
 
125
125
  declare class AreDirectiveFor extends AreDirective {
126
+ /**
127
+ * Lists whose number of NEW item nodes is at or below this threshold render
128
+ * fully synchronously — byte-for-byte the previous behavior. Typical UIs
129
+ * (menus, small tables) are therefore completely unaffected; only genuinely
130
+ * large lists pay the (tiny) scheduling cost to keep the main thread responsive.
131
+ */
132
+ private static readonly SYNC_THRESHOLD;
133
+ /**
134
+ * Per-chunk time budget (ms). During a large-list render we mount item nodes
135
+ * until this much time has elapsed, then yield to the browser so it can paint
136
+ * and process input before the next chunk. ~16ms targets one animation frame.
137
+ */
138
+ private static readonly CHUNK_BUDGET_MS;
139
+ /**
140
+ * Per-attribute serialization state. A new update() that arrives while a
141
+ * chunked render of the SAME `$for` is still in flight does NOT start a second
142
+ * concurrent pass (which could interleave mutations on the shared children
143
+ * list); instead it marks `pending` and the in-flight run re-runs once more
144
+ * with the latest data when it finishes. This guarantees the children list is
145
+ * only ever mutated by one pass at a time and the final state always reflects
146
+ * the most recent store value.
147
+ */
148
+ private static readonly renderState;
126
149
  transform(attribute: AreDirectiveAttribute, scope: A_Scope, store: AreStore, scene: AreScene, logger: A_Logger, ...args: any[]): void;
127
150
  compile(attribute: AreDirectiveAttribute, store: AreStore, scene: AreScene, ...args: any[]): void;
128
- update(attribute: AreDirectiveAttribute, store: AreStore, scene: AreScene, ...args: any[]): void;
151
+ update(attribute: AreDirectiveAttribute, store: AreStore, scene: AreScene, ...args: any[]): void | Promise<void>;
152
+ /**
153
+ * Core of the `$for` update: re-diff the source array against the current
154
+ * children, reconcile reused/removed items, then mount the new ones (small
155
+ * lists synchronously, large lists time-sliced). Never called while another
156
+ * pass for the same `$for` is in flight (see `update`).
157
+ */
158
+ private performUpdate;
159
+ /**
160
+ * Completes an update pass. If another update() arrived while a chunked
161
+ * render was streaming, run exactly one more pass now from the latest store
162
+ * value so the final DOM always reflects the most recent data.
163
+ */
164
+ private finishUpdate;
129
165
  /**
130
166
  * Walks the node's ancestor chain (inclusive) and reports whether the
131
167
  * whole path is currently active — i.e. the subtree is actually rendered
@@ -671,6 +707,13 @@ declare class AreHTMLInterpreter extends AreInterpreter {
671
707
  }
672
708
 
673
709
  declare class AreHTMLLifecycle extends AreLifecycle {
710
+ /**
711
+ * Per-chunk time budget (ms) for the time-sliced initial mount walk. While
712
+ * mounting a large subtree we keep applying nodes until this much wall-clock
713
+ * time has elapsed, then yield to the browser so it can paint and process
714
+ * input before the next chunk. ~16ms targets a single animation frame.
715
+ */
716
+ private static readonly MOUNT_BUDGET_MS;
674
717
  initComponent(node: AreHTMLNode, scope: A_Scope, context: AreHTMLEngineContext, signalsContext?: AreSignalsContext, logger?: A_Logger, ...args: any[]): void;
675
718
  initRoot(node: AreHTMLNode, scope: A_Scope, context: AreHTMLEngineContext, signalsContext?: AreSignalsContext, logger?: A_Logger, ...args: any[]): void;
676
719
  initText(node: AreHTMLNode, scope: A_Scope, context: AreHTMLEngineContext, logger?: A_Logger, ...args: any[]): void;
@@ -683,7 +726,7 @@ declare class AreHTMLLifecycle extends AreLifecycle {
683
726
  /**
684
727
  * Node Content
685
728
  */
686
- scene: AreScene, logger?: A_Logger, ...args: any[]): void;
729
+ scene: AreScene, logger?: A_Logger, ...args: any[]): void | Promise<void>;
687
730
  updateDirectiveAttribute(directive: AreDirectiveAttribute, scope: A_Scope, feature: A_Feature, logger?: A_Logger, ...args: any[]): void;
688
731
  }
689
732
 
@@ -1,4 +1,4 @@
1
- import { AreStore, AreScene, AreSyntax, AreCompiler, AreInterpreter, AreInstructionDefaultNames, AreNodeFeatures, AreContext, AreLifecycle, AreSignalsContext, AreAttributeFeatures, Are, AreNode, AreDeclaration, AreAttribute, AreCompilerError, AreMutation, AreSignal, AreInterpreterError, AreTokenizer, AreTransformer, AreEngine, AreSignals, AreEvent } from '@adaas/are';
1
+ import { AreStore, AreScene, AreSyntax, AreCompiler, AreInterpreter, AreInstructionDefaultNames, AreNodeFeatures, AreContext, AreLifecycle, AreSignalsContext, AreAttributeFeatures, Are, AreCompilerError, AreNode, AreDeclaration, AreAttribute, AreMutation, AreSignal, AreInterpreterError, AreTokenizer, AreTransformer, AreEngine, AreSignals, AreEvent } from '@adaas/are';
2
2
  import { A_Frame } from '@adaas/a-frame/core';
3
3
  import { A_Inject, A_Caller, A_Feature, A_Meta, A_Scope, A_Dependency, A_Component, A_Context, A_ComponentMeta, A_FormatterHelper, A_Fragment } from '@adaas/a-concept';
4
4
  import { A_Logger } from '@adaas/a-utils/a-logger';
@@ -236,6 +236,44 @@ var AreDirectiveContext = class extends A_ExecutionContext {
236
236
  this.scope = {};
237
237
  }
238
238
  };
239
+
240
+ // src/helpers/AreScheduler.helper.ts
241
+ var AreSchedulerHelper = class {
242
+ /**
243
+ * High-resolution wall-clock time in milliseconds. Uses `performance.now()`
244
+ * when available (monotonic, sub-millisecond), falling back to `Date.now()`.
245
+ */
246
+ static now() {
247
+ return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
248
+ }
249
+ /**
250
+ * Schedule `fn` to run on the next macrotask.
251
+ *
252
+ * `MessageChannel` yields a true macrotask without the ~4ms clamp that nested
253
+ * `setTimeout(0)` calls incur, so the browser can paint between chunks with
254
+ * minimal scheduling overhead. Falls back to `setTimeout` in non-DOM
255
+ * environments (e.g. tests / SSR).
256
+ */
257
+ static scheduleMacrotask(fn) {
258
+ if (typeof MessageChannel === "undefined") {
259
+ setTimeout(fn, 0);
260
+ return;
261
+ }
262
+ if (!this._channel) {
263
+ this._channel = new MessageChannel();
264
+ this._channel.port1.onmessage = () => {
265
+ const next = this._queue.shift();
266
+ if (next) next();
267
+ };
268
+ }
269
+ this._queue.push(fn);
270
+ this._channel.port2.postMessage(null);
271
+ }
272
+ };
273
+ /** FIFO queue of callbacks waiting for their posted macrotask to fire. */
274
+ AreSchedulerHelper._queue = [];
275
+
276
+ // src/directives/AreDirectiveFor.directive.ts
239
277
  var AreDirectiveFor = class extends AreDirective {
240
278
  transform(attribute, scope, store, scene, logger, ...args) {
241
279
  logger.debug(`[Transform] directive $FOR for <${attribute.owner.aseid.toString()}>`);
@@ -264,6 +302,24 @@ var AreDirectiveFor = class extends AreDirective {
264
302
  scene.unPlan(hostInstruction);
265
303
  }
266
304
  update(attribute, store, scene, ...args) {
305
+ let state = AreDirectiveFor.renderState.get(attribute);
306
+ if (!state) {
307
+ state = { running: false, pending: false };
308
+ AreDirectiveFor.renderState.set(attribute, state);
309
+ }
310
+ if (state.running) {
311
+ state.pending = true;
312
+ return;
313
+ }
314
+ return this.performUpdate(attribute, store, scene, state);
315
+ }
316
+ /**
317
+ * Core of the `$for` update: re-diff the source array against the current
318
+ * children, reconcile reused/removed items, then mount the new ones (small
319
+ * lists synchronously, large lists time-sliced). Never called while another
320
+ * pass for the same `$for` is in flight (see `update`).
321
+ */
322
+ performUpdate(attribute, store, scene, state) {
267
323
  const { key, index, arrayExpr, trackExpr } = this.parseExpression(attribute.content);
268
324
  const newArray = this.resolveArray(store, arrayExpr, attribute.content);
269
325
  const owner = attribute.owner;
@@ -280,7 +336,7 @@ var AreDirectiveFor = class extends AreDirective {
280
336
  childByKey.set(k, child);
281
337
  remaining.add(child);
282
338
  }
283
- const newOnes = [];
339
+ const toCreate = [];
284
340
  for (let i = 0; i < newArray.length; i++) {
285
341
  const item = newArray[i];
286
342
  const k = computeKey(item, i);
@@ -298,18 +354,57 @@ var AreDirectiveFor = class extends AreDirective {
298
354
  [index || "index"]: i
299
355
  };
300
356
  } else {
301
- const itemNode = this.spawnItemNode(attribute.template, owner, key, index, item, i);
302
- newOnes.push(itemNode);
357
+ toCreate.push({ item, idx: i });
303
358
  }
304
359
  }
305
360
  for (const child of remaining) {
306
361
  if (attached) child.unmount();
307
362
  owner.removeChild(child);
308
363
  }
309
- for (const child of newOnes) {
364
+ const createItem = (desc) => {
365
+ const child = this.spawnItemNode(attribute.template, owner, key, index, desc.item, desc.idx);
310
366
  child.transform();
311
367
  child.compile();
312
368
  if (attached) child.mount();
369
+ };
370
+ if (toCreate.length <= AreDirectiveFor.SYNC_THRESHOLD) {
371
+ for (const desc of toCreate) createItem(desc);
372
+ return this.finishUpdate(attribute, store, scene, state);
373
+ }
374
+ state.running = true;
375
+ let cursor = 0;
376
+ const processChunk = () => {
377
+ try {
378
+ const start = AreSchedulerHelper.now();
379
+ while (cursor < toCreate.length) {
380
+ createItem(toCreate[cursor]);
381
+ cursor++;
382
+ if (AreSchedulerHelper.now() - start >= AreDirectiveFor.CHUNK_BUDGET_MS) break;
383
+ }
384
+ } catch (error) {
385
+ state.running = false;
386
+ state.pending = false;
387
+ throw error;
388
+ }
389
+ if (cursor < toCreate.length) {
390
+ return new Promise((resolve) => {
391
+ AreSchedulerHelper.scheduleMacrotask(() => resolve(processChunk()));
392
+ });
393
+ }
394
+ return this.finishUpdate(attribute, store, scene, state);
395
+ };
396
+ return processChunk();
397
+ }
398
+ /**
399
+ * Completes an update pass. If another update() arrived while a chunked
400
+ * render was streaming, run exactly one more pass now from the latest store
401
+ * value so the final DOM always reflects the most recent data.
402
+ */
403
+ finishUpdate(attribute, store, scene, state) {
404
+ state.running = false;
405
+ if (state.pending) {
406
+ state.pending = false;
407
+ return this.performUpdate(attribute, store, scene, state);
313
408
  }
314
409
  }
315
410
  /**
@@ -470,6 +565,29 @@ var AreDirectiveFor = class extends AreDirective {
470
565
  return itemNode;
471
566
  }
472
567
  };
568
+ /**
569
+ * Lists whose number of NEW item nodes is at or below this threshold render
570
+ * fully synchronously — byte-for-byte the previous behavior. Typical UIs
571
+ * (menus, small tables) are therefore completely unaffected; only genuinely
572
+ * large lists pay the (tiny) scheduling cost to keep the main thread responsive.
573
+ */
574
+ AreDirectiveFor.SYNC_THRESHOLD = 100;
575
+ /**
576
+ * Per-chunk time budget (ms). During a large-list render we mount item nodes
577
+ * until this much time has elapsed, then yield to the browser so it can paint
578
+ * and process input before the next chunk. ~16ms targets one animation frame.
579
+ */
580
+ AreDirectiveFor.CHUNK_BUDGET_MS = 16;
581
+ /**
582
+ * Per-attribute serialization state. A new update() that arrives while a
583
+ * chunked render of the SAME `$for` is still in flight does NOT start a second
584
+ * concurrent pass (which could interleave mutations on the shared children
585
+ * list); instead it marks `pending` and the in-flight run re-runs once more
586
+ * with the latest data when it finishes. This guarantees the children list is
587
+ * only ever mutated by one pass at a time and the final state always reflects
588
+ * the most recent store value.
589
+ */
590
+ AreDirectiveFor.renderState = /* @__PURE__ */ new WeakMap();
473
591
  __decorateClass([
474
592
  AreDirective.Transform,
475
593
  __decorateParam(0, A_Inject(A_Caller)),
@@ -2026,10 +2144,45 @@ var AreHTMLLifecycle = class extends AreLifecycle {
2026
2144
  logger?.debug(`[Mount] Component Trigger for <${node.aseid.entity}> with aseid :{${node.aseid.toString()}}`);
2027
2145
  if (scene.isInactive) return;
2028
2146
  node.interpret();
2029
- for (let i = 0; i < node.children.length; i++) {
2030
- const child = node.children[i];
2031
- child.mount();
2032
- }
2147
+ const stack = [];
2148
+ for (let i = node.children.length - 1; i >= 0; i--) {
2149
+ stack.push({ node: node.children[i], entered: false });
2150
+ }
2151
+ const step = () => {
2152
+ const frame = stack[stack.length - 1];
2153
+ const current = frame.node;
2154
+ if (frame.entered) {
2155
+ stack.pop();
2156
+ current.call(AreNodeFeatures.onAfterMount, current.scope);
2157
+ return;
2158
+ }
2159
+ frame.entered = true;
2160
+ current.call(AreNodeFeatures.onBeforeMount, current.scope);
2161
+ if (!current.scene.isInactive) {
2162
+ current.interpret();
2163
+ for (let i = current.children.length - 1; i >= 0; i--) {
2164
+ stack.push({ node: current.children[i], entered: false });
2165
+ }
2166
+ }
2167
+ };
2168
+ const drive = () => {
2169
+ const start = AreSchedulerHelper.now();
2170
+ while (stack.length > 0) {
2171
+ step();
2172
+ if (stack.length > 0 && AreSchedulerHelper.now() - start >= AreHTMLLifecycle.MOUNT_BUDGET_MS) {
2173
+ return new Promise((resolve, reject) => {
2174
+ AreSchedulerHelper.scheduleMacrotask(() => {
2175
+ try {
2176
+ resolve(drive());
2177
+ } catch (error) {
2178
+ reject(error);
2179
+ }
2180
+ });
2181
+ });
2182
+ }
2183
+ }
2184
+ };
2185
+ return drive();
2033
2186
  }
2034
2187
  updateDirectiveAttribute(directive, scope, feature, logger, ...args) {
2035
2188
  if (directive.component) {
@@ -2039,6 +2192,13 @@ var AreHTMLLifecycle = class extends AreLifecycle {
2039
2192
  }
2040
2193
  }
2041
2194
  };
2195
+ /**
2196
+ * Per-chunk time budget (ms) for the time-sliced initial mount walk. While
2197
+ * mounting a large subtree we keep applying nodes until this much wall-clock
2198
+ * time has elapsed, then yield to the browser so it can paint and process
2199
+ * input before the next chunk. ~16ms targets a single animation frame.
2200
+ */
2201
+ AreHTMLLifecycle.MOUNT_BUDGET_MS = 16;
2042
2202
  __decorateClass([
2043
2203
  AreLifecycle.Init(AreComponentNode),
2044
2204
  __decorateParam(0, A_Inject(A_Caller)),
@@ -2469,7 +2629,7 @@ var AreRoot = class extends Are {
2469
2629
  }
2470
2630
  child.transform();
2471
2631
  child.compile();
2472
- child.mount();
2632
+ await child.mount();
2473
2633
  }
2474
2634
  }
2475
2635
  /**