targetj 1.0.231 → 1.0.232

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.
package/README.md CHANGED
@@ -1,11 +1,13 @@
1
- # TargetJS: State as Destination, UI as Sequence
1
+ # TargetJS: State as Destination, Code Order as UI Sequence
2
2
 
3
3
  **[targetjs.io](https://targetjs.io)**
4
4
  [![MIT LICENSE](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/livetrails/targetjs/blob/main/LICENSE)
5
5
  [![Stars](https://img.shields.io/github/stars/livetrails/targetjs.svg)](https://github.com/livetrails/targetjs/stargazers)
6
6
  [![npm version](https://img.shields.io/npm/v/targetj.svg)](https://www.npmjs.com/package/targetj)
7
7
 
8
- TargetJS is a high-performance JavaScript UI framework with ultra-compact syntax. It replaces the "State → Render" model with "State → transition → Render". It unifies UI, animations, APIs, event handling, and state into self-contained "Targets" that stack together like intelligent Lego pieces using Code-Ordered Reactivity.
8
+ Most frameworks are great at rendering the next state. TargetJS is designed for the journey between states.
9
+
10
+ TargetJS is a JavaScript UI framework that replaces the "State → Render" model with "State → transition → Render". It also lets code order directly define the UI sequence. It unifies UI, animations, API calls, event handling, and state into self-contained "Targets" that stack together like intelligent Lego pieces using Code-Ordered Reactivity.
9
11
 
10
12
  It can be used as a full-featured framework or as a lightweight library alongside other frameworks. It is also a highly performant web framework, as shown in the [framework benchmark](https://krausest.github.io/js-framework-benchmark/current.html).
11
13
 
@@ -36,6 +38,14 @@ TargetJS code order and target reactivity allow the implementation to more close
36
38
 
37
39
  With its compact style, TargetJS makes the journey from A → B explicit and efficient, with significantly less code than traditional frameworks.
38
40
 
41
+ ## 🚀 Why TargetJS?
42
+
43
+ 1. Unified State: State isn't "elsewhere". It is built into every Target.
44
+ 2. Animation by Default: High-performance animations are baked into the logic.
45
+ 3. Ultra-Compact: Write 30% to 70% less code than standard frameworks.
46
+ 4. UI as Sequence: Code describes the UI story from top to bottom, exactly how the user experiences the interaction: "When this finishes, do that."
47
+ 5. Zero Boilerplate Async: Targets can handle waiting for asynchronous operations automatically.
48
+
39
49
  ## ⚡ Quick Start (30 Seconds)
40
50
 
41
51
  **1. Install**
@@ -65,18 +75,21 @@ App({
65
75
  In TargetJS, targets are the fundamental unit of behavior instead of methods.
66
76
  Methods and properties both are internally transformed into targets that the framework schedules and executes.
67
77
 
78
+ ### Mental Model
68
79
 
69
- ### Execution Syntax
70
-
71
- Target names can include special symbols that determine **when they execute**.
72
-
73
- | Symbol | Name | Behavior |
74
- |------|------|------|
75
- | `name` | Standard | Runs immediately in the order it appears. |
76
- | `name$` | Reactive | Runs every time the previous sibling target runs. |
77
- | `name$$` | Deferred | Runs only after the entire preceding target chain (including children, animations, and API calls) completes. |
78
- | `_name` | Inactive | Does not run automatically. Trigger it manually via `.activateTarget()`. |
80
+ A target can:
81
+ - execute a method
82
+ - hold a value
83
+ - move toward that value over time
84
+ - wait for previous targets
85
+ - react when previous targets update
86
+ - fetch data
87
+ - react to an event
88
+ - create children
89
+ - run callbacks
90
+ - control its own lifecycle
79
91
 
92
+ This lets UI code follow the same order as the user experience.
80
93
 
81
94
  ### Target Controls
82
95
 
@@ -94,10 +107,24 @@ A target can also be defined as an object with optional controls that manage its
94
107
  | `easing` | Predefined easing function controlling how values update over steps. |
95
108
  | `onComplete` | Callback triggered when this target (and its children) finishes. |
96
109
  | `onValueChange` | Callback triggered when the target emits a new value. |
110
+ | `onChange` | Callback triggered when the target emits a new value. |
111
+ | `on<PropertyName>Step` | Callback triggered on every step of a specific property. |
112
+
113
+ ### Compact Execution Syntax
114
+
115
+ Target names can include special symbols that define when they execute. This provides a compact alternative to implementing the same behavior with callbacks.
116
+
117
+ | Symbol | Name | Behavior |
118
+ |------|------|------|
119
+ | `name` | Standard | Runs immediately in the order it appears. |
120
+ | `name$` | Reactive | Runs every time the previous sibling target updates. Equivalent to using `on<PropertyName>Step()` or `onValueChange()` to activate the next target . |
121
+ | `name$$` | Deferred | Runs only after the entire preceding target chain, including children, animations, and API calls, completes. Equivalent to using `onComplete()` to activate the next target. |
122
+ | `_name` | Inactive | Does not run automatically. Trigger it manually with `.activateTarget()`. Equivalent to `{ active: false }`. |
123
+
97
124
 
98
125
  ## Examples: Like Button → Animated Like (in 3 Steps)
99
126
 
100
- Let’s see how TargetJS handles a complex interaction that would usually require 50+ lines of React/CSS. The example demonstrates how to run four asynchronous operations in a strict sequential sequence. In other words, each step has to wait for all the previous ones to complete.
127
+ Let’s see how TargetJS handles a complex interaction that would usually require 50+ lines of React/CSS. The example demonstrates how to run four asynchronous operations in a strict sequence. In other words, each step has to wait for all the previous ones to complete.
101
128
 
102
129
  ### 1) Like button
103
130
 
@@ -203,9 +230,8 @@ Each target has its own state and lifecycle. Targets execute automatically in th
203
230
 
204
231
  1. [📦 Alternative Installation Via CDN](#-alternative-installation-via-cdn)
205
232
  1. [Using TargetJS as a Library](#using-targetjs-as-a-library)
206
- 1. [🚀 Why TargetJS?](#-why-targetjs)
207
233
  1. Deeper Examples:
208
- - [Loading Five Users Example](#loading-five-users-example)
234
+ - [Search Fetch → Replace → Highlight Example](#search--fetch--replace--highlight)
209
235
  - [Infinite Loading and Scrolling Example](#infinite-loading-and-scrolling-example)
210
236
  1. [Special Target Names](#special-target-names)
211
237
  1. [How to Debug in TargetJS](#how-to-debug-in-targetjs)
@@ -283,90 +309,86 @@ export default function TargetIsland() {
283
309
  }
284
310
  ```
285
311
 
286
- ## 🚀 Why TargetJS?
287
-
288
- 1. Zero Boilerplate Async: The $$ postfix handles the "wait" for you.
289
- 2. Unified State: State isn't "elsewhere". It's built into every Target.
290
- 3. Animation by Default: High-performance animations are baked into the logic.
291
- 4. Ultra-Compact: Write 70% less code than standard frameworks.
292
- 5. Lower Cognitive Load: Code reads from top to bottom, exactly how the user experiences the interaction.
293
-
294
312
  ## Deeper Examples
295
313
 
296
- ### Loading Five Users Example
314
+ ### Search Fetch → Replace → Highlight
297
315
 
298
- In this example, we load five separate users and display five boxes, each containing a user's name and email.
316
+ This example shows how TargetJS models a UI workflow directly in code order:
317
+ Click → animate button → fetch users → remove old results → add new results → pause → highlight one result
299
318
 
300
- - `fetch` calls five APIs to retrieve details for five users.
301
- - `child` is a special target that adds a new item to the parent each time it executes. Because it ends with `$` in this example, it executes every time an API call returns a result.
302
- - TargetJS ensures that API results are processed in the same sequence as the API calls. For example, if the user1 API result arrives before user0, `child` will not execute until the result for user0 has been received.
319
+ The `fetch` target is initially set to `active: false`, which means it waits for an explicit trigger. When the user clicks, the `fetch` target is activated. TargetJS understands that fetching data is an asynchronous operation.
303
320
 
304
- <img src="https://targetjs.io/img/fetch-5-users.gif" width="130" />
305
-
306
- ```html
307
- <div id="users"></div>
308
- ```
309
- ```javascript
310
- import { App } from "targetj";
321
+ The `$$` postfix means that a target waits for the preceding sibling targets to complete before running. In this example, `removeChildren$$` waits for `fetch` to complete before it begins. `addChildren$$` begins after both `fetch` and `removeChildren$$` are completed.
311
322
 
312
- App({
313
- gap: 10,
314
- fetch: ['https://targetjs.io/api/randomUser?id=user0',
315
- 'https://targetjs.io/api/randomUser?id=user1',
316
- 'https://targetjs.io/api/randomUser?id=user2',
317
- 'https://targetjs.io/api/randomUser?id=user3',
318
- 'https://targetjs.io/api/randomUser?id=user4'
319
- ],
320
- child$() {
321
- // prevTargetValue Holds the previous target’s value. For fetch targets, this is each API result in code order,
322
- // not the order in which responses arrive in the browser.
323
- const user = this.prevTargetValue;
324
- return {
325
- width: 200,
326
- height: 65,
327
- borderRadius: 10,
328
- boxSizing: "border-box",
329
- padding: 10,
330
- fontSize: 14,
331
- backgroundColor: "#f0f0f0",
332
- scale: { value: [0.8, 1], steps: 14, interval: 12 },
333
- userName$$: {
334
- padding: "10px 0 5px 10px",
335
- boxSizing: "border-box",
336
- fontWeight: 600,
337
- opacity: { value: [0, 1], steps: 50 },
338
- html: user.name
339
- },
340
- userEmail$$: {
341
- paddingLeft: 10,
342
- boxSizing: "border-box",
343
- opacity: { value: [0, 0.7], steps: 50 },
344
- html: user.email
345
- }
346
- };
347
- }
348
- }).mount("#users");
349
- ```
323
+ Notice how `fetch`, `removeChildren$$`, and `addChildren$$` appear in the same order as the UI sequence. The code is organized around the experience itself.
350
324
 
351
- It can also be written using a target’s `cycles` and `interval` properties/methods to fetch users at intervals instead of in a single batch. In this example, we set interval to 1000, making the API call once every second.
325
+ Lastly, `pause$$` adds a short pause before highlighting the first user with an animation. `setTarget` is an imperative way to implement targets within methods.
352
326
 
353
- <img src="https://targetjs.io/img/fetch-5-users2.gif" width="130" />
354
327
 
328
+ ```js
329
+ import { App } from "targetj";
355
330
 
356
- ```javascript
357
331
  App({
358
- gap: 10,
359
- fetch: {
360
- interval: 1000,
361
- cycles: 5,
362
- value(i) { return `https://targetjs.io/api/randomUser?id=user${i}`; }
332
+ searchButton: {
333
+ element: 'button',
334
+ type: 'button',
335
+ y: 20, x: 20,
336
+ width: 220, height: 60, lineHeight: 60,
337
+ borderRadius: 10, border: 0, backgroundColor: '#f5f5f5',
338
+ cursor: 'pointer', textAlign: 'center',
339
+ html: 'Search',
340
+ onClick() {
341
+ this.setTarget('scale', {value: [1, 1.15, 1], steps: 8, interval: 12 });
342
+ this.setTarget('backgroundColor', {value: [ '#ffe8ec', '#f5f5f5' ], steps: 12, interval: 12});
343
+ this.parent.getChild('users').activateTarget('fetch', { reset: true });
344
+ }
363
345
  },
364
- child$() {
365
- return {
366
- // …same as the previous example…
367
- };
346
+ users: {
347
+ y: 90,
348
+ x: 20,
349
+ gap: 10,
350
+ containerOverflowMode: 'always',
351
+ fetch: {
352
+ active: false,
353
+ value: 'https://targetjs.io/api/randomUsers'
354
+ },
355
+ removeChildren$$() {
356
+ this.removeChildren();
357
+ },
358
+ addChildren$$: {
359
+ cycles() { return this.val('fetch').length; },
360
+ value(i) {
361
+ const user = this.val('fetch')[i];
362
+ return {
363
+ width: 360,
364
+ backgroundColor: "#fafafa",
365
+ scale: {value: {list: [0.8, 1]}, steps: 14},
366
+ boxShadow: "0 6px 16px rgba(0,0,0,.08)",
367
+ containerOverflowMode: 'always',
368
+ userName: {
369
+ padding: 10,
370
+ height: 30,
371
+ fontWeight: 600,
372
+ opacity: { value: [0, 1], steps: 50 },
373
+ html() { return user.name; }
374
+ },
375
+ userEmail: {
376
+ padding: 10,
377
+ opacity: { value: [0, 0.7], steps: 50 },
378
+ html() { return user.email; }
379
+ }
380
+ };
381
+ },
382
+ pause$$: { interval: 150 },
383
+ highlightOne$$() {
384
+ const user = this.getChild(0);
385
+ user.setTarget('backgroundColor', { value: ['#fff7cc', '#fff1a8'], steps: 14 });
386
+ user.setTarget('scale', { value: [1, 1.04, 1], steps: 14 });
387
+ user.setTarget('boxShadow', '0 10px 24px rgba(0,0,0,.14)');
388
+ }
389
+ }
368
390
  }
369
- }).mount("#users");
391
+ }).mount('#app');
370
392
  ```
371
393
 
372
394
  ### Infinite Loading and Scrolling Example
@@ -452,6 +452,7 @@ var LocationManager = exports.LocationManager = /*#__PURE__*/function () {
452
452
  } else {
453
453
  nowVisible = !!tmodel.targets.isVisible;
454
454
  }
455
+ tmodel.actualValues.isVisible = nowVisible;
455
456
  tmodel.isNowVisible = !wasVisible && nowVisible;
456
457
  tmodel.isNowInvisible = (wasVisible || wasVisible === undefined) && !nowVisible;
457
458
  } else {
@@ -68,33 +68,42 @@ var PageManager = exports.PageManager = /*#__PURE__*/function () {
68
68
  return _App.tApp.reset();
69
69
  case 4:
70
70
  link = _TUtil.TUtil.getFullLink(link);
71
- if (!this.pageCache[link]) {
72
- _App.tApp.tRoot.$dom.innerHTML("");
73
- _App.App.oids = {};
74
- _App.App.tmodelIdMap = {};
75
- _App.tApp.tRoot = _App.tApp.tRootFactory();
76
- this.lastLink = link;
77
- _App.tApp.start();
78
- } else {
79
- _App.tApp.tRoot = this.pageCache[link].tRoot;
80
- _App.App.oids = this.pageCache[link].oids;
81
- _App.App.tmodelIdMap = this.pageCache[link].tmodelIdMap;
82
- _App.tApp.tRoot.$dom = _$Dom.$Dom.query('#tgjs-root') ? new _$Dom.$Dom('#tgjs-root') : new _$Dom.$Dom('body');
83
- _App.tApp.tRoot.$dom.innerHTML(this.pageCache[link].html);
84
- visibles = Object.values(this.pageCache[link].visibleOidMap);
85
- newVisibles = _DomInit.DomInit.initCacheDoms(visibles);
86
- visibles.forEach(function (tmodel) {
87
- tmodel.visibilityStatus = undefined;
88
- });
89
- _App.tApp.manager.visibleOidMap = _objectSpread({}, this.pageCache[link].visibleOidMap);
90
- newVisibles.forEach(function (visible) {
91
- _App.tApp.manager.visibleOidMap[visible.oid] = visible;
92
- });
93
- window.scrollTo(this.pageCache[link].scrollLeft, this.pageCache[link].scrollTop);
94
- this.lastLink = link;
95
- _App.tApp.start();
71
+ if (this.pageCache[link]) {
72
+ _context.next = 15;
73
+ break;
96
74
  }
97
- case 6:
75
+ _App.tApp.tRoot.$dom.innerHTML("");
76
+ _App.App.oids = {};
77
+ _App.App.tmodelIdMap = {};
78
+ _App.tApp.tRoot = _App.tApp.tRootFactory();
79
+ this.lastLink = link;
80
+ _context.next = 13;
81
+ return _App.tApp.start();
82
+ case 13:
83
+ _context.next = 30;
84
+ break;
85
+ case 15:
86
+ _App.tApp.tRoot = this.pageCache[link].tRoot;
87
+ _App.App.oids = this.pageCache[link].oids;
88
+ _App.App.tmodelIdMap = this.pageCache[link].tmodelIdMap;
89
+ _App.tApp.tRoot.$dom = _$Dom.$Dom.query('#tgjs-root') ? new _$Dom.$Dom('#tgjs-root') : new _$Dom.$Dom('body');
90
+ _App.tApp.tRoot.$dom.innerHTML(this.pageCache[link].html);
91
+ visibles = Object.values(this.pageCache[link].visibleOidMap);
92
+ newVisibles = _DomInit.DomInit.initCacheDoms(visibles);
93
+ visibles.forEach(function (tmodel) {
94
+ tmodel.visibilityStatus = undefined;
95
+ });
96
+ _App.tApp.manager.visibleOidMap = _objectSpread({}, this.pageCache[link].visibleOidMap);
97
+ newVisibles.forEach(function (visible) {
98
+ _App.tApp.manager.visibleOidMap[visible.oid] = visible;
99
+ });
100
+ window.scrollTo(this.pageCache[link].scrollLeft, this.pageCache[link].scrollTop);
101
+ this.lastLink = link;
102
+ _context.next = 29;
103
+ return _App.tApp.start();
104
+ case 29:
105
+ (0, _App.getRunScheduler)().restoreSnapshot(this.pageCache[link].runSnapshot);
106
+ case 30:
98
107
  case "end":
99
108
  return _context.stop();
100
109
  }
@@ -176,7 +185,8 @@ var PageManager = exports.PageManager = /*#__PURE__*/function () {
176
185
  visibleOidMap: _objectSpread({}, _App.tApp.manager.visibleOidMap),
177
186
  scrollLeft: _$Dom.$Dom.getWindowScrollLeft() || 0,
178
187
  scrollTop: _$Dom.$Dom.getWindowScrollTop() || 0,
179
- tRoot: _App.tApp.tRoot
188
+ tRoot: _App.tApp.tRoot,
189
+ runSnapshot: (0, _App.getRunScheduler)().getSnapshot()
180
190
  };
181
191
  }
182
192
  if (updateHistory) {
@@ -211,7 +221,8 @@ var PageManager = exports.PageManager = /*#__PURE__*/function () {
211
221
  visibleOidMap: _objectSpread({}, _App.tApp.manager.visibleOidMap),
212
222
  scrollLeft: _$Dom.$Dom.getWindowScrollLeft() || 0,
213
223
  scrollTop: _$Dom.$Dom.getWindowScrollTop() || 0,
214
- tRoot: _App.tApp.tRoot
224
+ tRoot: _App.tApp.tRoot,
225
+ runSnapshot: (0, _App.getRunScheduler)().getSnapshot()
215
226
  };
216
227
  if (updateHistory) {
217
228
  history.pushState({
@@ -43,7 +43,6 @@ var RunScheduler = exports.RunScheduler = /*#__PURE__*/function () {
43
43
  key: "resetRuns",
44
44
  value: function () {
45
45
  var _resetRuns = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee() {
46
- var _this$delayProcess;
47
46
  return _regeneratorRuntime().wrap(function _callee$(_context) {
48
47
  while (1) switch (_context.prev = _context.next) {
49
48
  case 0:
@@ -53,9 +52,7 @@ var RunScheduler = exports.RunScheduler = /*#__PURE__*/function () {
53
52
  return requestAnimationFrame(resolve);
54
53
  });
55
54
  case 3:
56
- if ((_this$delayProcess = this.delayProcess) !== null && _this$delayProcess !== void 0 && _this$delayProcess.timeoutId) {
57
- clearTimeout(this.delayProcess.timeoutId);
58
- }
55
+ this.clearDelayProcess();
59
56
  this.nextRuns = [];
60
57
  this.domProcessing = 0;
61
58
  this.runningFlag = false;
@@ -113,7 +110,7 @@ var RunScheduler = exports.RunScheduler = /*#__PURE__*/function () {
113
110
  key: "run",
114
111
  value: function () {
115
112
  var _run = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2(delay, runId) {
116
- var _this$delayProcess2;
113
+ var _this$delayProcess;
117
114
  return _regeneratorRuntime().wrap(function _callee2$(_context2) {
118
115
  while (1) switch (_context2.prev = _context2.next) {
119
116
  case 0:
@@ -137,7 +134,7 @@ var RunScheduler = exports.RunScheduler = /*#__PURE__*/function () {
137
134
  this.runningFlag = true;
138
135
  this.runStartTime = _TUtil.TUtil.now();
139
136
  if (_App.tApp.debugLevel === 1) {
140
- _TUtil.TUtil.log(true)("Request from: ".concat(runId, " delay: ").concat(delay, " runningStep:").concat(this.runningStep, " dom:").concat(this.domProcessing, " runs:").concat(this.nextRuns.length, " D:").concat((_this$delayProcess2 = this.delayProcess) === null || _this$delayProcess2 === void 0 ? void 0 : _this$delayProcess2.delay, " events:").concat((0, _App.getEvents)().eventQueue.length));
137
+ _TUtil.TUtil.log(true)("Request from: ".concat(runId, " delay: ").concat(delay, " runningStep:").concat(this.runningStep, " dom:").concat(this.domProcessing, " runs:").concat(this.nextRuns.length, " D:").concat((_this$delayProcess = this.delayProcess) === null || _this$delayProcess === void 0 ? void 0 : _this$delayProcess.delay, " events:").concat((0, _App.getEvents)().eventQueue.length));
141
138
  }
142
139
  if (!(this.phase === 0)) {
143
140
  _context2.next = 16;
@@ -291,31 +288,52 @@ var RunScheduler = exports.RunScheduler = /*#__PURE__*/function () {
291
288
  _this3.needsRerun();
292
289
  });
293
290
  }
291
+ }, {
292
+ key: "clearDelayProcess",
293
+ value: function clearDelayProcess() {
294
+ var _this$delayProcess2;
295
+ if ((_this$delayProcess2 = this.delayProcess) !== null && _this$delayProcess2 !== void 0 && _this$delayProcess2.timeoutId) {
296
+ clearTimeout(this.delayProcess.timeoutId);
297
+ this.delayProcess.timeoutId = undefined;
298
+ }
299
+ this.delayProcess = undefined;
300
+ }
294
301
  }, {
295
302
  key: "setDelayProcess",
296
303
  value: function setDelayProcess(runId, insertTime, interval, runTime, delay) {
297
304
  var _this4 = this;
298
- this.delayProcess = {
305
+ var delayProcess = {
299
306
  runId: runId,
300
307
  insertTime: insertTime,
301
308
  runTime: runTime,
302
309
  interval: interval,
303
- timeoutId: setTimeout( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee3() {
304
- return _regeneratorRuntime().wrap(function _callee3$(_context3) {
305
- while (1) switch (_context3.prev = _context3.next) {
306
- case 0:
307
- _this4.delayProcess.timeoutId = undefined;
308
- _context3.next = 3;
309
- return _this4.run(delay, runId);
310
- case 3:
311
- _this4.executeNextRun();
312
- case 4:
313
- case "end":
314
- return _context3.stop();
315
- }
316
- }, _callee3);
317
- })), Math.max(0, delay))
310
+ delay: delay,
311
+ timeoutId: undefined
318
312
  };
313
+ delayProcess.timeoutId = setTimeout( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee3() {
314
+ return _regeneratorRuntime().wrap(function _callee3$(_context3) {
315
+ while (1) switch (_context3.prev = _context3.next) {
316
+ case 0:
317
+ if (!(_this4.delayProcess !== delayProcess)) {
318
+ _context3.next = 2;
319
+ break;
320
+ }
321
+ return _context3.abrupt("return");
322
+ case 2:
323
+ delayProcess.timeoutId = undefined;
324
+ _context3.next = 5;
325
+ return _this4.run(delay, runId);
326
+ case 5:
327
+ if (_this4.delayProcess === delayProcess) {
328
+ _this4.executeNextRun();
329
+ }
330
+ case 6:
331
+ case "end":
332
+ return _context3.stop();
333
+ }
334
+ }, _callee3);
335
+ })), Math.max(0, delay));
336
+ this.delayProcess = delayProcess;
319
337
  }
320
338
  }, {
321
339
  key: "executeNextRun",
@@ -341,7 +359,7 @@ var RunScheduler = exports.RunScheduler = /*#__PURE__*/function () {
341
359
  var _newDelay = Math.max(1, nextValidRun.delay - (_now - nextValidRun.insertTime));
342
360
  this.setDelayProcess(nextValidRun.runId, nextValidRun.insertTime, nextValidRun.delay, _now + _newDelay, _newDelay);
343
361
  } else {
344
- this.delayProcess = undefined;
362
+ this.clearDelayProcess();
345
363
  }
346
364
  }
347
365
  }, {
@@ -383,6 +401,52 @@ var RunScheduler = exports.RunScheduler = /*#__PURE__*/function () {
383
401
  delay: newDelay
384
402
  });
385
403
  }
404
+ }, {
405
+ key: "getSnapshot",
406
+ value: function getSnapshot() {
407
+ var now = _TUtil.TUtil.now();
408
+ var runs = [];
409
+ var addRun = function addRun(run) {
410
+ if (!run) {
411
+ return;
412
+ }
413
+ var runTime = _TUtil.TUtil.isDefined(run.runTime) ? run.runTime : run.insertTime + run.delay;
414
+ runs.push({
415
+ runId: run.runId,
416
+ delay: Math.max(0, runTime - now)
417
+ });
418
+ };
419
+ addRun(this.delayProcess);
420
+ this.nextRuns.forEach(addRun);
421
+ var immediateRuns = runs.filter(function (run) {
422
+ return run.delay === 0;
423
+ });
424
+ var delayedRuns = runs.filter(function (run) {
425
+ return run.delay > 0;
426
+ });
427
+ var result = [];
428
+ if (immediateRuns.length) {
429
+ result.push({
430
+ runId: immediateRuns.map(function (run) {
431
+ return run.runId;
432
+ }).join('-'),
433
+ delay: 0
434
+ });
435
+ }
436
+ delayedRuns.forEach(function (run) {
437
+ result.push(run);
438
+ });
439
+ return result;
440
+ }
441
+ }, {
442
+ key: "restoreSnapshot",
443
+ value: function restoreSnapshot() {
444
+ var _this5 = this;
445
+ var snapshot = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
446
+ snapshot.forEach(function (run) {
447
+ _this5.schedule(run.delay, "restore-".concat(run.runId));
448
+ });
449
+ }
386
450
  }]);
387
451
  }();
388
452
  _defineProperty(RunScheduler, "domSteps", [function () {
@@ -114,7 +114,9 @@ var TModelManager = exports.TModelManager = /*#__PURE__*/function () {
114
114
  if (!tmodel.canHaveDom() || !tmodel.isIncluded() || tmodel.canDeleteDom() && !visible && !keepDom) {
115
115
  this.addToInvisibleDom(tmodel);
116
116
  tmodel.getChildren().forEach(function (tmodel) {
117
- _this.addToRecursiveInvisibleDom(tmodel);
117
+ if (!tmodel.managesOwnScroll()) {
118
+ _this.addToRecursiveInvisibleDom(tmodel);
119
+ }
118
120
  });
119
121
  }
120
122
  }
@@ -232,7 +234,9 @@ var TModelManager = exports.TModelManager = /*#__PURE__*/function () {
232
234
  this.lists.invisibleDom.push(tmodel);
233
235
  }
234
236
  tmodel.getChildren().forEach(function (tmodel) {
235
- _this2.addToRecursiveInvisibleDom(tmodel);
237
+ if (!tmodel.managesOwnScroll()) {
238
+ _this2.addToRecursiveInvisibleDom(tmodel);
239
+ }
236
240
  });
237
241
  }
238
242
  }
@@ -260,10 +264,11 @@ var TModelManager = exports.TModelManager = /*#__PURE__*/function () {
260
264
  }, {
261
265
  key: "needsRerender",
262
266
  value: function needsRerender(tmodel) {
263
- if (tmodel.hasDom() && _TUtil.TUtil.isDefined(tmodel.getHtml()) && (tmodel.$dom.innerHTML() !== tmodel.getHtml() || tmodel.$dom.textOnly !== tmodel.isTextOnly())) {
264
- return true;
267
+ var html = tmodel.getHtml();
268
+ if (!tmodel.hasDom() || !_TUtil.TUtil.isDefined(html)) {
269
+ return false;
265
270
  }
266
- return false;
271
+ return tmodel.$dom.innerHTML() !== String(html) || tmodel.$dom.textOnly !== tmodel.isTextOnly();
267
272
  }
268
273
  }, {
269
274
  key: "needsRestyle",
package/build/TUtil.js CHANGED
@@ -82,7 +82,7 @@ var TUtil = exports.TUtil = /*#__PURE__*/function () {
82
82
  }, {
83
83
  key: "getVisibilityClipRect",
84
84
  value: function getVisibilityClipRect(container) {
85
- var rect = null;
85
+ var rect = TUtil.getScreenViewportRect();
86
86
  while (container && container !== (0, _App.tRoot)()) {
87
87
  if (this.shouldClipByAncestor(container)) {
88
88
  var ancestorRect = this.getAncestorViewportRect(container);
@@ -95,6 +95,17 @@ var TUtil = exports.TUtil = /*#__PURE__*/function () {
95
95
  }
96
96
  return rect;
97
97
  }
98
+ }, {
99
+ key: "getScreenViewportRect",
100
+ value: function getScreenViewportRect() {
101
+ return {
102
+ x: 0,
103
+ y: 0,
104
+ r: (0, _App.getScreenWidth)(),
105
+ b: (0, _App.getScreenHeight)(),
106
+ source: "screen"
107
+ };
108
+ }
98
109
  }, {
99
110
  key: "shouldClipByAncestor",
100
111
  value: function shouldClipByAncestor(ancestor) {