@microsoft/webui-framework 0.0.18 → 0.0.20

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 (46) hide show
  1. package/README.md +130 -70
  2. package/dist/decorators.d.ts +1 -0
  3. package/dist/decorators.js +3 -0
  4. package/dist/element/diff.d.ts +3 -1
  5. package/dist/element/diff.js +197 -90
  6. package/dist/element/markers.js +13 -5
  7. package/dist/element/types.d.ts +18 -10
  8. package/dist/element/types.js +3 -0
  9. package/dist/element.d.ts +0 -4
  10. package/dist/element.js +11 -69
  11. package/dist/hydration-mismatch.js +3 -2
  12. package/dist/index.js +1 -1
  13. package/dist/lifecycle.d.ts +17 -0
  14. package/dist/lifecycle.js +66 -3
  15. package/dist/static-host.js +20 -10
  16. package/dist/streaming-activation.d.ts +2 -0
  17. package/dist/streaming-activation.js +13 -0
  18. package/dist/streaming-bootstrap.d.ts +2 -0
  19. package/dist/streaming-bootstrap.js +48 -0
  20. package/dist/streaming-cleanup.d.ts +4 -0
  21. package/dist/streaming-cleanup.js +66 -0
  22. package/dist/streaming-coordinator.d.ts +7 -0
  23. package/dist/streaming-coordinator.js +367 -0
  24. package/dist/streaming-deferred.d.ts +22 -0
  25. package/dist/streaming-deferred.js +273 -0
  26. package/dist/streaming-dom.d.ts +32 -0
  27. package/dist/streaming-dom.js +148 -0
  28. package/dist/streaming-entry.d.ts +1 -0
  29. package/dist/streaming-entry.js +2 -0
  30. package/dist/streaming-install.d.ts +2 -0
  31. package/dist/streaming-install.js +25 -0
  32. package/dist/streaming-mode.d.ts +5 -0
  33. package/dist/streaming-mode.js +14 -0
  34. package/dist/streaming-protocol.d.ts +32 -0
  35. package/dist/streaming-protocol.js +27 -0
  36. package/dist/streaming.d.ts +5 -0
  37. package/dist/streaming.js +9 -0
  38. package/dist/template-element.d.ts +28 -9
  39. package/dist/template-element.js +448 -238
  40. package/dist/template-events.js +1 -1
  41. package/dist/template-roots.d.ts +0 -1
  42. package/dist/template-roots.js +0 -11
  43. package/dist/template-types.d.ts +7 -1
  44. package/dist/template.d.ts +2 -0
  45. package/dist/template.js +46 -19
  46. package/package.json +9 -3
@@ -1,9 +1,10 @@
1
- import { getTemplate } from './template.js';
1
+ import { deferTemplateDefinition, getTemplate } from './template.js';
2
2
  import { hydrationStart, hydrationEnd } from './lifecycle.js';
3
- import { syncRepeat, dotWalk } from './element/diff.js';
3
+ import { isStreamingHydrationMode, PENDING_ROOT_CONNECTED, STREAMED_HOST_ATTR, STREAMING_BOUNDARY_ACTIVATE, } from './streaming-mode.js';
4
+ import { createRepeatKeyState, seedHydratedRepeatKeys, syncRepeat, dotWalk, } from './element/diff.js';
4
5
  import { collectItemMarkers, nextElement, findByOrdinal, MARKER_COND_START, MARKER_COND_END, MARKER_REPEAT_START, } from './element/markers.js';
5
6
  import { injectModuleStyle, } from './element/styles.js';
6
- import { ATTR_KIND_BOOLEAN, ATTR_KIND_COMPLEX, ATTR_KIND_TEMPLATE, } from './element/types.js';
7
+ import { ATTR_KIND_BOOLEAN, ATTR_KIND_COMPLEX, ATTR_KIND_TEMPLATE, hasNativeLiveProperty, } from './element/types.js';
7
8
  import { templateHasRoot, templateRootForAttribute } from './template-roots.js';
8
9
  const DEV = typeof __WEBUI_DEV__ === 'undefined' || __WEBUI_DEV__;
9
10
  const templateCache = new WeakMap();
@@ -35,28 +36,11 @@ function getTplOrdinals(tplNode) {
35
36
  const EMPTY_ARR = [];
36
37
  const EMPTY_SET = Object.freeze(new Set());
37
38
  const WEBUI_SET_STATE_KEY = Symbol.for('microsoft.webui.setStateKey');
39
+ const STREAMING_BOUNDARY_ABANDON = Symbol.for('microsoft.webui.boundaryAbandon');
40
+ const ACTIVATION_ACTIVATED = 1;
41
+ const ACTIVATION_STATIC_HOST_OPT_OUT = 2;
42
+ const ACTIVATION_MISSING_TEMPLATE = 3;
38
43
  const templateMetaByCtor = new WeakMap();
39
- let domReadyQueue = null;
40
- function runWhenDomReady(callback) {
41
- if (document.readyState !== 'loading') {
42
- callback();
43
- return;
44
- }
45
- if (domReadyQueue) {
46
- domReadyQueue.push(callback);
47
- return;
48
- }
49
- domReadyQueue = [callback];
50
- document.addEventListener('DOMContentLoaded', flushDomReadyQueue, { once: true });
51
- }
52
- function flushDomReadyQueue() {
53
- const queue = domReadyQueue;
54
- domReadyQueue = null;
55
- if (!queue)
56
- return;
57
- for (let i = 0; i < queue.length; i++)
58
- queue[i]();
59
- }
60
44
  function childNodesArray(parent) {
61
45
  const children = parent.childNodes;
62
46
  const len = children.length;
@@ -74,8 +58,7 @@ function getTemplateDom(meta) {
74
58
  templateDOMCache.set(meta, div);
75
59
  return div;
76
60
  }
77
- function installTemplateObservedAttributes(ctor, tagName) {
78
- const meta = getTemplate(tagName);
61
+ function installTemplateObservedAttributes(ctor, tagName, meta = getTemplate(tagName)) {
79
62
  if (!meta)
80
63
  return;
81
64
  templateMetaByCtor.set(ctor, meta);
@@ -111,6 +94,10 @@ function installTemplateObservedAttributes(ctor, tagName) {
111
94
  configurable: true,
112
95
  });
113
96
  }
97
+ function defineTemplateConstructor(ctor, tagName, meta) {
98
+ installTemplateObservedAttributes(ctor, tagName, meta);
99
+ customElements.define(tagName, ctor);
100
+ }
114
101
  function hasAuthoredMember(instance, key) {
115
102
  if (Object.prototype.hasOwnProperty.call(instance, key))
116
103
  return true;
@@ -128,6 +115,10 @@ export class TemplateElement extends HTMLElement {
128
115
  this.$root = null;
129
116
  this.$ready = false;
130
117
  this.$hydrated = false;
118
+ this.$hydratedCallbackCalled = false;
119
+ this.$deferredSSR = false;
120
+ this.$activatingDeferredSSR = false;
121
+ this.$hasUnknownScopes = false;
131
122
  this.$templateState = null;
132
123
  this.$dirtyPaths = null;
133
124
  this.$pendingFlush = false;
@@ -135,39 +126,85 @@ export class TemplateElement extends HTMLElement {
135
126
  this.$resolver = (p, s) => this.$resolveValue(p, s);
136
127
  }
137
128
  [WEBUI_SET_STATE_KEY](key, value) {
138
- return this.$setStateKey(key, value);
129
+ const wasDeferred = this.$deferredSSR;
130
+ this.$beforeExternalStateWrite();
131
+ const owned = this.$setStateKey(key, value);
132
+ this.$afterExternalStateWrite(owned);
133
+ if (owned && wasDeferred && !this.$deferredSSR)
134
+ this.$update(key);
135
+ return owned;
136
+ }
137
+ [STREAMING_BOUNDARY_ACTIVATE](state) {
138
+ if (!this.$deferredSSR && this.hasAttribute(STREAMED_HOST_ATTR)) {
139
+ this.$deferredSSR = true;
140
+ this.$ready = true;
141
+ }
142
+ if (!this.$deferredSSR)
143
+ return ACTIVATION_ACTIVATED;
144
+ const meta = this.$templateMeta();
145
+ if (!meta) {
146
+ return ACTIVATION_MISSING_TEMPLATE;
147
+ }
148
+ this.$meta = meta;
149
+ if (!this.$shouldActivateOnBoundaryCommit())
150
+ return ACTIVATION_STATIC_HOST_OPT_OUT;
151
+ this.$activateDeferredSSR(state);
152
+ return ACTIVATION_ACTIVATED;
153
+ }
154
+ [STREAMING_BOUNDARY_ABANDON]() {
155
+ this.$deferredSSR = false;
156
+ this.$activatingDeferredSSR = false;
157
+ this.$ready = false;
158
+ this.$preReadyWrites = null;
139
159
  }
140
160
  static define(tagName) {
141
- installTemplateObservedAttributes(this, tagName);
142
- customElements.define(tagName, this);
161
+ const ctor = this;
162
+ const meta = getTemplate(tagName);
163
+ if (!meta && isStreamingHydrationMode()) {
164
+ deferTemplateDefinition(tagName, ctor, () => {
165
+ defineTemplateConstructor(ctor, tagName, getTemplate(tagName));
166
+ });
167
+ return;
168
+ }
169
+ defineTemplateConstructor(ctor, tagName, meta);
143
170
  }
144
171
  connectedCallback() {
145
172
  const tag = this.tagName.toLowerCase();
173
+ if (this.$deferredSSR) {
174
+ this.$ready = true;
175
+ return;
176
+ }
146
177
  if (this.$hydrated && this.$root) {
147
178
  hydrationStart();
179
+ try {
180
+ this.$ready = true;
181
+ this.$update();
182
+ }
183
+ finally {
184
+ hydrationEnd();
185
+ }
186
+ return;
187
+ }
188
+ if (isStreamingHydrationMode() && this.hasAttribute(STREAMED_HOST_ATTR)) {
189
+ this.$deferredSSR = true;
148
190
  this.$ready = true;
149
- this.$update();
150
- hydrationEnd();
191
+ const resume = this[PENDING_ROOT_CONNECTED];
192
+ if (typeof resume === 'function')
193
+ resume.call(this);
151
194
  return;
152
195
  }
153
- const meta = getTemplate(tag);
196
+ const meta = this.$templateMeta();
154
197
  if (!meta) {
155
198
  console.warn(`[WebUI] Template metadata for <${tag}> not found. ` +
156
199
  `Ensure the component is included in the SSR output or registered via __webui.templates.`);
157
200
  return;
158
201
  }
159
202
  this.$meta = meta;
160
- if (document.readyState === 'loading') {
161
- runWhenDomReady(() => this.$mount(meta));
162
- }
163
- else {
164
- this.$mount(meta);
165
- }
203
+ this.$mount(meta, false);
166
204
  }
167
- $mount(meta) {
205
+ $mount(meta, forceSSR, ssrState) {
168
206
  if (this.$hydrated)
169
207
  return;
170
- hydrationStart();
171
208
  const hasShadow = !!this.shadowRoot;
172
209
  const wantShadow = hasShadow || !!meta.sd;
173
210
  let root;
@@ -189,33 +226,48 @@ export class TemplateElement extends HTMLElement {
189
226
  root = this;
190
227
  isSSR = false;
191
228
  }
192
- if (meta.sa)
193
- injectModuleStyle(meta.sa, this.shadowRoot);
194
- if (isSSR) {
195
- this.$applySSRState();
196
- this.$root = this.$hydrate(root, meta, getTemplateDom(meta));
197
- }
198
- else {
199
- clientRoot = this.$createStagingRoot(meta);
200
- this.$root = this.$wire(clientRoot, meta);
229
+ if (isSSR && !forceSSR && this.$shouldDeferSSRHydration()) {
230
+ this.$meta = meta;
231
+ this.$deferredSSR = true;
232
+ this.$ready = true;
233
+ return;
201
234
  }
202
- this.$meta = meta;
203
- this.$hydrated = true;
204
- this.$ready = true;
205
- this.$syncAuthoredAttributes();
206
- if (isSSR && DEV)
207
- this.$checkHydrationMismatch();
208
- else
209
- this.$preReadyWrites = null;
210
- if (!isSSR && clientRoot) {
211
- this.$updateInstance(this.$root);
212
- if (this.$root.repeats.length !== 0 || this.$root.conds.length !== 0) {
213
- this.$root.nodes = childNodesArray(clientRoot);
214
- this.$releaseStagingRepeatContainers(this.$root, clientRoot);
235
+ hydrationStart();
236
+ try {
237
+ if (meta.sa)
238
+ injectModuleStyle(meta.sa, this.shadowRoot);
239
+ if (isSSR) {
240
+ if (this.$shouldApplySSRBootstrapState()) {
241
+ this.$applySSRState(forceSSR ? ssrState : window.__webui?.state);
242
+ }
243
+ this.$root = this.$hydrate(root, meta, getTemplateDom(meta));
244
+ }
245
+ else {
246
+ clientRoot = this.$createStagingRoot(meta);
247
+ this.$root = this.$wire(clientRoot, meta);
215
248
  }
216
- this.$appendStagedChildren(root, clientRoot);
249
+ this.$meta = meta;
250
+ this.$hydrated = true;
251
+ this.$ready = true;
252
+ this.$syncAuthoredAttributes();
253
+ if (isSSR && DEV)
254
+ this.$checkHydrationMismatch();
255
+ else
256
+ this.$preReadyWrites = null;
257
+ if (!isSSR && clientRoot) {
258
+ this.$updateInstance(this.$root);
259
+ if (this.$root.repeats.length !== 0 || this.$root.conds.length !== 0) {
260
+ this.$root.nodes = childNodesArray(clientRoot);
261
+ this.$replaceInstanceContainer(this.$root, clientRoot, root);
262
+ }
263
+ this.$appendStagedChildren(root, clientRoot);
264
+ this.$root.container = root;
265
+ }
266
+ this.$notifyHydrated();
267
+ }
268
+ finally {
269
+ hydrationEnd();
217
270
  }
218
- hydrationEnd();
219
271
  }
220
272
  disconnectedCallback() {
221
273
  if (this.$root) {
@@ -226,9 +278,13 @@ export class TemplateElement extends HTMLElement {
226
278
  }
227
279
  }
228
280
  $destroy() {
229
- if (!this.$root)
281
+ if (!this.$root) {
282
+ this.$deferredSSR = false;
283
+ this.$ready = false;
284
+ this.$hasUnknownScopes = false;
230
285
  return;
231
- this.$teardown(this.$root);
286
+ }
287
+ this.$disposeInstance(this.$root, false);
232
288
  this.$root = null;
233
289
  this.$pathIndex = undefined;
234
290
  this.$wildcardBindings = undefined;
@@ -236,67 +292,115 @@ export class TemplateElement extends HTMLElement {
236
292
  this.$pendingFlush = false;
237
293
  this.$preReadyWrites = null;
238
294
  this.$ready = false;
239
- }
240
- $teardown(instance) {
241
- this.$teardownInstanceCleanups(instance);
242
- for (const c of instance.conds) {
243
- if (c.instance)
244
- this.$teardown(c.instance);
245
- c.instance = null;
246
- }
247
- for (const r of instance.repeats) {
248
- for (const item of r.instances)
249
- this.$teardown(item.instance);
250
- r.instances.length = 0;
251
- r.container = null;
252
- r.start = null;
253
- r.end = null;
254
- }
295
+ this.$hasUnknownScopes = false;
296
+ }
297
+ $clearRepeatBinding(repeat) {
298
+ repeat.instances.length = 0;
299
+ repeat.container = null;
300
+ repeat.start = null;
301
+ repeat.end = null;
302
+ const state = repeat.keyState;
303
+ if (state) {
304
+ state.keys.length = 0;
305
+ state.nextKeys.length = 0;
306
+ state.map.clear();
307
+ }
308
+ }
309
+ $clearInstance(instance) {
310
+ instance.scope = undefined;
311
+ instance.parent = undefined;
312
+ instance.container = null;
255
313
  instance.nodes.length = 0;
256
314
  instance.texts.length = 0;
257
315
  instance.attrs.length = 0;
258
316
  instance.conds.length = 0;
259
317
  instance.repeats.length = 0;
260
318
  }
261
- $teardownInstanceCleanups(instance) {
262
- const cleanups = instance.cleanups;
263
- if (!cleanups)
264
- return;
265
- for (let i = 0; i < cleanups.length; i++)
266
- cleanups[i]();
267
- cleanups.length = 0;
268
- }
269
319
  attributeChangedCallback(name, oldValue, newValue) {
270
320
  if (Object.is(oldValue, newValue))
271
321
  return;
322
+ const wasDeferred = this.$deferredSSR;
323
+ this.$beforeExternalStateWrite();
272
324
  const property = this.$templateRootForAttribute(name);
325
+ let changed = false;
273
326
  if (property && this.$usesTemplateState(property)) {
274
- this.$setTemplateState(property, newValue);
327
+ changed = this.$setTemplateState(property, newValue);
275
328
  }
329
+ this.$afterExternalStateWrite(changed);
330
+ if (changed && property && wasDeferred && !this.$deferredSSR)
331
+ this.$update(property);
276
332
  }
277
333
  setState(state) {
334
+ const wasDeferred = this.$deferredSSR;
335
+ this.$beforeExternalStateWrite();
278
336
  const keys = Object.keys(state);
337
+ let owned = false;
279
338
  for (let i = 0; i < keys.length; i++) {
280
339
  const key = keys[i];
281
- this.$setStateKey(key, state[key]);
340
+ owned = this.$setStateKey(key, state[key]) || owned;
341
+ }
342
+ this.$afterExternalStateWrite(owned);
343
+ if (owned && wasDeferred && !this.$deferredSSR) {
344
+ for (let i = 0; i < keys.length; i++)
345
+ this.$update(keys[i]);
282
346
  }
283
347
  this.$flushUpdates();
284
348
  }
285
349
  $observableNames() {
286
350
  return EMPTY_SET;
287
351
  }
352
+ $beforeExternalStateWrite() {
353
+ }
354
+ $afterExternalStateWrite(_applied) {
355
+ }
356
+ $shouldDeferSSRHydration() {
357
+ return false;
358
+ }
359
+ $shouldActivateOnBoundaryCommit() {
360
+ return true;
361
+ }
362
+ $activateDeferredSSR(state) {
363
+ if (!this.$deferredSSR)
364
+ return;
365
+ const meta = this.$meta;
366
+ if (!meta)
367
+ return;
368
+ this.$deferredSSR = false;
369
+ this.$ready = false;
370
+ this.$preReadyWrites = null;
371
+ this.$activatingDeferredSSR = true;
372
+ try {
373
+ this.$mount(meta, true, state);
374
+ }
375
+ finally {
376
+ this.$activatingDeferredSSR = false;
377
+ }
378
+ }
379
+ $shouldApplySSRBootstrapState() {
380
+ return true;
381
+ }
288
382
  $shouldApplySSRState(_key) {
289
383
  return true;
290
384
  }
291
385
  $syncAuthoredAttributes() {
292
386
  }
387
+ hydratedCallback() {
388
+ }
389
+ $notifyHydrated() {
390
+ if (this.$hydratedCallbackCalled)
391
+ return;
392
+ this.$hydratedCallbackCalled = true;
393
+ this.hydratedCallback();
394
+ }
293
395
  $shouldApplyTemplateStateFromSSR(_key) {
294
396
  return true;
295
397
  }
296
398
  $setTemplateState(key, value) {
297
- if (this.$writeTemplateState(key, value)) {
399
+ const changed = this.$writeTemplateState(key, value);
400
+ if (changed) {
298
401
  this.$update(key);
299
402
  }
403
+ return changed;
300
404
  }
301
405
  $writeTemplateState(key, value) {
302
406
  if (!this.$templateState) {
@@ -335,8 +439,7 @@ export class TemplateElement extends HTMLElement {
335
439
  }
336
440
  return false;
337
441
  }
338
- $applySSRState() {
339
- const state = window.__webui?.state;
442
+ $applySSRState(state) {
340
443
  if (!state || typeof state !== 'object')
341
444
  return;
342
445
  const observableNames = this.$observableNames();
@@ -382,24 +485,34 @@ export class TemplateElement extends HTMLElement {
382
485
  this.$flush();
383
486
  }
384
487
  $flush() {
385
- if (!this.$ready || !this.$root || !this.$pathIndex) {
488
+ if (!this.$ready || !this.$root) {
386
489
  this.$dirtyPaths = null;
387
490
  this.$pendingFlush = false;
388
491
  return;
389
492
  }
493
+ if (!this.$pathIndex)
494
+ this.$buildPathIndex();
495
+ if (!this.$pathIndex)
496
+ return;
390
497
  while (this.$dirtyPaths && this.$dirtyPaths.size > 0) {
391
498
  const dirty = this.$dirtyPaths;
392
499
  this.$dirtyPaths = null;
393
500
  for (const path of dirty) {
394
- const entry = this.$pathIndex.get(path);
501
+ if (!this.$pathIndex)
502
+ this.$buildPathIndex();
503
+ const entry = this.$pathIndex?.get(path);
395
504
  if (entry) {
396
505
  this.$updateBindings(entry.texts, entry.attrs, entry.conds, entry.repeats);
397
506
  }
398
507
  }
508
+ if (!this.$pathIndex)
509
+ this.$buildPathIndex();
399
510
  if (this.$wildcardBindings) {
400
511
  const wc = this.$wildcardBindings;
401
512
  this.$updateBindings(wc.texts, wc.attrs, wc.conds, wc.repeats);
402
513
  }
514
+ if (!this.$pathIndex)
515
+ this.$buildPathIndex();
403
516
  }
404
517
  this.$pendingFlush = false;
405
518
  }
@@ -501,34 +614,34 @@ export class TemplateElement extends HTMLElement {
501
614
  }
502
615
  root.appendChild(fragment);
503
616
  }
504
- $releaseStagingRepeatContainers(instance, stagingRoot) {
505
- if (!instance || !stagingRoot)
506
- return;
507
- if (instance.repeats.length === 0 && instance.conds.length === 0)
617
+ $replaceInstanceContainer(instance, previous, next) {
618
+ if (previous === next)
508
619
  return;
509
620
  const stack = [instance];
510
621
  while (stack.length > 0) {
511
622
  const current = stack.pop();
512
623
  if (!current)
513
624
  continue;
514
- for (let i = 0; i < current.repeats.length; i++) {
515
- const repeat = current.repeats[i];
516
- if (repeat.container === stagingRoot)
517
- repeat.container = null;
518
- for (let j = 0; j < repeat.instances.length; j++) {
519
- stack.push(repeat.instances[j].instance);
520
- }
521
- }
625
+ if (current.container === previous)
626
+ current.container = next;
522
627
  for (let i = 0; i < current.conds.length; i++) {
523
628
  const child = current.conds[i].instance;
524
629
  if (child)
525
630
  stack.push(child);
526
631
  }
632
+ for (let i = 0; i < current.repeats.length; i++) {
633
+ const repeat = current.repeats[i];
634
+ if (repeat.container === previous)
635
+ repeat.container = next;
636
+ const children = repeat.instances;
637
+ for (let j = 0; j < children.length; j++)
638
+ stack.push(children[j]);
639
+ }
527
640
  }
528
641
  }
529
642
  $wire(root, meta, scope) {
530
643
  const instance = {
531
- scope, nodes: childNodesArray(root),
644
+ scope, container: root, nodes: childNodesArray(root),
532
645
  texts: [], attrs: [], conds: [], repeats: [],
533
646
  };
534
647
  const textRefs = new Array(meta.tx?.length ?? 0);
@@ -563,12 +676,19 @@ export class TemplateElement extends HTMLElement {
563
676
  let repRefCount = 0;
564
677
  if (meta.r) {
565
678
  for (let i = 0; i < meta.r.length; i++) {
566
- const [collection, itemVar, blockIndex, slotMeta] = meta.r[i];
679
+ const [collection, itemVar, blockIndex, slotMeta, keyPath] = meta.r[i];
567
680
  const [parentPath, beforeIndex] = slotMeta;
568
681
  const parent = parentPath.length > 0 ? this.$resolve(root, parentPath) : root;
569
682
  if (!parent || (parent.nodeType !== 1 && parent.nodeType !== 11))
570
683
  continue;
571
- repRefs[repRefCount] = { parent, ref: parent.childNodes[beforeIndex] || null, collection, itemVar, blockIndex };
684
+ repRefs[repRefCount] = {
685
+ parent,
686
+ ref: parent.childNodes[beforeIndex] || null,
687
+ collection,
688
+ itemVar,
689
+ blockIndex,
690
+ keyPath,
691
+ };
572
692
  repRefCount += 1;
573
693
  }
574
694
  }
@@ -594,19 +714,28 @@ export class TemplateElement extends HTMLElement {
594
714
  const c = condRefs[i];
595
715
  const anchor = document.createComment('');
596
716
  c.parent.insertBefore(anchor, c.ref);
597
- instance.conds.push({ condition: c.condition, blockIndex: c.blockIndex, anchor, scope, instance: null });
717
+ instance.conds.push({
718
+ condition: c.condition,
719
+ blockIndex: c.blockIndex,
720
+ anchor,
721
+ scope,
722
+ owner: instance,
723
+ instance: null,
724
+ });
598
725
  }
599
726
  for (let i = 0; i < repRefCount; i++) {
600
727
  const r = repRefs[i];
601
728
  const anchor = document.createComment('');
602
729
  r.parent.insertBefore(anchor, r.ref);
603
- const { keyAttribute, keyPath } = this.$repeatMaps(r.blockIndex, r.itemVar);
604
- instance.repeats.push({
730
+ const binding = {
605
731
  markerId: i, collection: r.collection, itemVar: r.itemVar, blockIndex: r.blockIndex,
606
732
  container: r.parent, start: anchor, end: null,
607
- scope, owner: instance, instances: [], rootTag: null,
608
- keyAttribute, keyPath,
609
- });
733
+ scope, owner: instance, instances: [],
734
+ };
735
+ if (r.keyPath !== undefined) {
736
+ binding.keyState = createRepeatKeyState(r.keyPath);
737
+ }
738
+ instance.repeats.push(binding);
610
739
  }
611
740
  for (let i = 0; i < instance.conds.length; i++)
612
741
  this.$toggleCond(instance.conds[i]);
@@ -615,6 +744,7 @@ export class TemplateElement extends HTMLElement {
615
744
  $hydrate(ssrRoot, meta, tplDom, scope, pathStart = 0) {
616
745
  const instance = {
617
746
  scope,
747
+ container: (pathStart > 0 ? ssrRoot.parentNode : ssrRoot),
618
748
  nodes: pathStart > 0 ? [ssrRoot] : childNodesArray(ssrRoot),
619
749
  texts: [], attrs: [], conds: [], repeats: [],
620
750
  };
@@ -657,7 +787,6 @@ export class TemplateElement extends HTMLElement {
657
787
  const [parentPath] = slotMeta;
658
788
  const ssrParent = this.$resolveSSR(ssrRoot, tplDom, parentPath, pathStart) ?? ssrRoot;
659
789
  const blockMeta = this.$block(blockIndex);
660
- const shown = condition[0](this.$resolver, scope);
661
790
  let condInstance = null;
662
791
  if (ssrParent !== lastCondParent) {
663
792
  lastCondMarker = null;
@@ -676,11 +805,10 @@ export class TemplateElement extends HTMLElement {
676
805
  }
677
806
  if (marker)
678
807
  lastCondMarker = marker;
679
- const ssrContentPresent = marker && blockMeta && this.$hasContentAfterMarker(condAnchor, MARKER_COND_END);
680
- if (blockMeta && (shown || ssrContentPresent)) {
681
- if (marker) {
682
- condInstance = this.$hydrateCondContent(condAnchor, blockMeta, scope);
683
- }
808
+ if (marker && blockMeta) {
809
+ condInstance = this.$hydrateCondContent(condAnchor, blockMeta, scope);
810
+ if (condInstance)
811
+ condInstance.parent = instance;
684
812
  }
685
813
  if (marker) {
686
814
  const lastNode = condInstance ? condInstance.nodes[condInstance.nodes.length - 1] : condAnchor;
@@ -692,7 +820,7 @@ export class TemplateElement extends HTMLElement {
692
820
  instance.conds.push({
693
821
  condition: condition, blockIndex,
694
822
  anchor: condAnchor,
695
- scope, instance: condInstance,
823
+ scope, owner: instance, instance: condInstance,
696
824
  });
697
825
  }
698
826
  }
@@ -700,7 +828,7 @@ export class TemplateElement extends HTMLElement {
700
828
  let lastRepMarker = null;
701
829
  let lastRepParent = null;
702
830
  for (let i = 0; i < meta.r.length; i++) {
703
- const [collection, itemVar, blockIndex, slotMeta] = meta.r[i];
831
+ const [collection, itemVar, blockIndex, slotMeta, keyPath] = meta.r[i];
704
832
  const [parentPath] = slotMeta;
705
833
  const ssrParent = this.$resolveSSR(ssrRoot, tplDom, parentPath, pathStart) ?? ssrRoot;
706
834
  if (ssrParent !== lastRepParent) {
@@ -708,7 +836,6 @@ export class TemplateElement extends HTMLElement {
708
836
  lastRepParent = ssrParent;
709
837
  }
710
838
  const blockMeta = this.$block(blockIndex);
711
- const { keyAttribute, keyPath } = this.$repeatMaps(blockIndex, itemVar);
712
839
  const blockTplDom = blockMeta ? getTemplateDom(blockMeta) : null;
713
840
  const rootTag = blockMeta && blockTplDom?.childNodes.length === 1 && blockTplDom.children.length === 1
714
841
  ? this.$rootTag(blockMeta)
@@ -726,29 +853,37 @@ export class TemplateElement extends HTMLElement {
726
853
  const insertRef = ssrParent.childNodes[Math.min(beforeIndex ?? staticCount, ssrParent.childNodes.length)] ?? null;
727
854
  ssrParent.insertBefore(anchor, insertRef);
728
855
  }
729
- lastRepMarker = anchor;
730
856
  const repeatInsts = [];
857
+ const hasCollectionState = this.$hasStateRoot(collection, scope);
731
858
  const itemsArr = this.$resolveValue(collection, scope);
732
859
  const items = Array.isArray(itemsArr) ? itemsArr : [];
733
860
  const { items: itemMarkers, end: endMarker } = marker
734
861
  ? collectItemMarkers(anchor)
735
862
  : { items: [], end: null };
736
- if (blockMeta && blockTplDom && items.length > 0 && anchor.parentNode && itemMarkers.length > 0) {
737
- if (itemMarkers.length !== items.length) {
863
+ lastRepMarker = endMarker ?? anchor;
864
+ if (blockMeta && blockTplDom && anchor.parentNode && itemMarkers.length > 0) {
865
+ if (!this.$activatingDeferredSSR
866
+ && hasCollectionState
867
+ && itemMarkers.length !== items.length) {
738
868
  console.warn(`[webui] hydration: repeat marker count (${itemMarkers.length}) ≠ data length (${items.length}) for "${collection}"`);
739
869
  }
740
- const limit = Math.min(itemMarkers.length, items.length);
741
- for (let j = 0; j < limit; j++) {
870
+ for (let j = 0; j < itemMarkers.length; j++) {
742
871
  const itemValue = items[j];
743
- const itemScope = { name: itemVar, value: itemValue, parent: scope };
872
+ const known = hasCollectionState && j < items.length;
873
+ if (!known)
874
+ this.$hasUnknownScopes = true;
875
+ const itemScope = {
876
+ name: itemVar,
877
+ value: itemValue,
878
+ parent: scope,
879
+ known,
880
+ };
744
881
  if (rootTag) {
745
882
  const itemEl = nextElement(itemMarkers[j]);
746
883
  if (itemEl) {
747
- const key = keyAttribute !== undefined
748
- ? itemEl.getAttribute(keyAttribute)
749
- : String(j);
750
884
  const childInstance = this.$hydrate(itemEl, blockMeta, blockTplDom, itemScope, 1);
751
- repeatInsts.push({ key, value: itemValue, instance: childInstance });
885
+ childInstance.parent = instance;
886
+ repeatInsts.push(childInstance);
752
887
  }
753
888
  }
754
889
  else {
@@ -762,6 +897,7 @@ export class TemplateElement extends HTMLElement {
762
897
  cursor = next;
763
898
  }
764
899
  const inst = this.$hydrate(wrapper, blockMeta, blockTplDom, itemScope);
900
+ inst.parent = instance;
765
901
  inst.nodes = childNodesArray(wrapper);
766
902
  let afterNode = itemMarkers[j];
767
903
  for (let nodeIndex = 0; nodeIndex < inst.nodes.length; nodeIndex++) {
@@ -769,8 +905,10 @@ export class TemplateElement extends HTMLElement {
769
905
  itemParent?.insertBefore(node, afterNode.nextSibling);
770
906
  afterNode = node;
771
907
  }
772
- const key = keyPath ? String(dotWalk(itemValue, keyPath, 0) ?? '') : null;
773
- repeatInsts.push({ key, value: itemValue, instance: inst });
908
+ if (itemParent) {
909
+ this.$replaceInstanceContainer(inst, wrapper, itemParent);
910
+ }
911
+ repeatInsts.push(inst);
774
912
  }
775
913
  }
776
914
  for (let m = 0; m < itemMarkers.length; m++) {
@@ -779,43 +917,76 @@ export class TemplateElement extends HTMLElement {
779
917
  }
780
918
  if (endMarker)
781
919
  staleMarkers.push(endMarker);
782
- instance.repeats.push({
920
+ const binding = {
783
921
  markerId: i, collection, itemVar, blockIndex,
784
922
  container: (anchor.parentNode ?? ssrRoot),
785
923
  start: anchor, end: null,
786
- scope, owner: instance, instances: repeatInsts, rootTag,
787
- keyAttribute, keyPath, synced: true,
788
- });
924
+ scope, owner: instance, instances: repeatInsts,
925
+ synced: hasCollectionState,
926
+ };
927
+ if (keyPath !== undefined) {
928
+ binding.keyState = createRepeatKeyState(keyPath);
929
+ if (hasCollectionState) {
930
+ seedHydratedRepeatKeys(binding, items);
931
+ }
932
+ }
933
+ instance.repeats.push(binding);
789
934
  }
790
935
  }
791
936
  this.$finalize(instance, ssrRoot, meta, (r, p) => this.$resolveSSR(r, tplDom, p, pathStart), scope);
792
937
  for (let i = 0; i < staleMarkers.length; i++) {
793
938
  staleMarkers[i].parentNode?.removeChild(staleMarkers[i]);
794
939
  }
940
+ this.$compactNodeArray(instance);
795
941
  return instance;
796
942
  }
797
- $collectBetween(start, endData) {
943
+ $collectConditionalRange(start) {
798
944
  const nodes = [];
945
+ let depth = 0;
799
946
  let node = start.nextSibling;
800
947
  while (node) {
801
- if (node.nodeType === 8 && node.data === endData)
802
- break;
948
+ if (node.nodeType === 8) {
949
+ const data = node.data;
950
+ if (data === MARKER_COND_START) {
951
+ depth++;
952
+ }
953
+ else if (data === MARKER_COND_END) {
954
+ if (depth === 0)
955
+ break;
956
+ depth--;
957
+ }
958
+ }
803
959
  nodes.push(node);
804
960
  node = node.nextSibling;
805
961
  }
806
962
  return nodes;
807
963
  }
964
+ $hasRootStructuralSlot(meta) {
965
+ if (meta.c) {
966
+ for (let i = 0; i < meta.c.length; i++) {
967
+ if (meta.c[i][2][0].length === 0)
968
+ return true;
969
+ }
970
+ }
971
+ if (meta.r) {
972
+ for (let i = 0; i < meta.r.length; i++) {
973
+ if (meta.r[i][3][0].length === 0)
974
+ return true;
975
+ }
976
+ }
977
+ return false;
978
+ }
808
979
  $hydrateCondContent(condAnchor, blockMeta, scope) {
809
980
  const rootTag = this.$rootTag(blockMeta);
810
981
  const tplDom = getTemplateDom(blockMeta);
811
- if (rootTag && tplDom.children.length === 1) {
982
+ if (rootTag && tplDom.children.length === 1 && !this.$hasRootStructuralSlot(blockMeta)) {
812
983
  const el = nextElement(condAnchor);
813
984
  if (el) {
814
985
  return this.$hydrate(el, blockMeta, tplDom, scope, 1);
815
986
  }
816
987
  return null;
817
988
  }
818
- const condNodes = this.$collectBetween(condAnchor, MARKER_COND_END);
989
+ const condNodes = this.$collectConditionalRange(condAnchor);
819
990
  if (condNodes.length === 0)
820
991
  return null;
821
992
  const wrapper = document.createElement('div');
@@ -828,6 +999,9 @@ export class TemplateElement extends HTMLElement {
828
999
  condAnchor.parentNode?.insertBefore(inst.nodes[cn], afterNode.nextSibling);
829
1000
  afterNode = inst.nodes[cn];
830
1001
  }
1002
+ const container = condAnchor.parentNode;
1003
+ if (container)
1004
+ this.$replaceInstanceContainer(inst, wrapper, container);
831
1005
  return inst;
832
1006
  }
833
1007
  $findMarker(parent, data, after) {
@@ -840,16 +1014,6 @@ export class TemplateElement extends HTMLElement {
840
1014
  }
841
1015
  return null;
842
1016
  }
843
- $hasContentAfterMarker(anchor, endData) {
844
- let sibling = anchor.nextSibling;
845
- while (sibling) {
846
- if (sibling.nodeType === 8 && sibling.data === endData) {
847
- return false;
848
- }
849
- return true;
850
- }
851
- return false;
852
- }
853
1017
  $findSSRText(ssrParent, tplParent, beforeIndex) {
854
1018
  const ordinals = getTplOrdinals(tplParent);
855
1019
  let textOrd = 0;
@@ -926,32 +1090,6 @@ export class TemplateElement extends HTMLElement {
926
1090
  return { element: el, name, kind, parts: entry[2], scope };
927
1091
  return { element: el, name, kind: kind, path: entry[2] || '', scope };
928
1092
  }
929
- $repeatMaps(blockIndex, itemVar) {
930
- let keyAttribute;
931
- let keyPath;
932
- const bm = this.$block(blockIndex);
933
- if (bm?.a && bm.ag) {
934
- for (let g = 0; g < bm.ag.length; g++) {
935
- const [tp, s, c] = bm.ag[g];
936
- const isRoot = tp.length === 0 || (tp.length === 1 && tp[0] === 0);
937
- if (isRoot) {
938
- for (let j = 0; j < c; j++) {
939
- const entry = bm.a[s + j];
940
- if (entry) {
941
- if (entry[1] === 0 || entry[1] === 3) {
942
- const dp = this.$singleDynamic(entry[1] === 3 ? entry[2] : [[entry[2]]]);
943
- if (dp && keyPath === undefined && dp.path.startsWith(itemVar + '.')) {
944
- keyAttribute = entry[0];
945
- keyPath = dp.path.slice(itemVar.length + 1);
946
- }
947
- }
948
- }
949
- }
950
- }
951
- }
952
- }
953
- return { keyAttribute, keyPath };
954
- }
955
1093
  $buildPathIndex() {
956
1094
  if (!this.$root)
957
1095
  return;
@@ -1025,7 +1163,7 @@ export class TemplateElement extends HTMLElement {
1025
1163
  ensure(keyFor(rep.collection)).repeats.push(rep);
1026
1164
  }
1027
1165
  for (let i = 0; i < rep.instances.length; i++) {
1028
- visit(rep.instances[i].instance);
1166
+ visit(rep.instances[i]);
1029
1167
  }
1030
1168
  }
1031
1169
  };
@@ -1041,16 +1179,37 @@ export class TemplateElement extends HTMLElement {
1041
1179
  this.$pathIndex = index;
1042
1180
  }
1043
1181
  $updateBindings(texts, attrs, conds, repeats) {
1044
- for (let i = 0; i < texts.length; i++)
1045
- this.$patchText(texts[i]);
1046
- for (let i = 0; i < attrs.length; i++)
1047
- this.$patchAttr(attrs[i]);
1048
- for (let i = 0; i < conds.length; i++)
1049
- this.$toggleCond(conds[i]);
1182
+ const gated = this.$hasUnknownScopes;
1183
+ for (let i = 0; i < texts.length; i++) {
1184
+ const binding = texts[i];
1185
+ if (!gated || !binding.scope || this.$scopeIsKnown(binding.scope))
1186
+ this.$patchText(binding);
1187
+ }
1188
+ for (let i = 0; i < attrs.length; i++) {
1189
+ const binding = attrs[i];
1190
+ if (!gated || !binding.scope || this.$scopeIsKnown(binding.scope))
1191
+ this.$patchAttr(binding);
1192
+ }
1193
+ for (let i = 0; i < conds.length; i++) {
1194
+ const binding = conds[i];
1195
+ if (!gated || !binding.scope || this.$scopeIsKnown(binding.scope))
1196
+ this.$toggleCond(binding);
1197
+ }
1050
1198
  for (let i = 0; i < repeats.length; i++) {
1051
- syncRepeat(this, repeats[i]);
1199
+ const binding = repeats[i];
1200
+ if (!gated || !binding.scope || this.$scopeIsKnown(binding.scope))
1201
+ syncRepeat(this, binding);
1052
1202
  }
1053
1203
  }
1204
+ $scopeIsKnown(scope) {
1205
+ let frame = scope;
1206
+ while (frame) {
1207
+ if (frame.known === false)
1208
+ return false;
1209
+ frame = frame.parent;
1210
+ }
1211
+ return true;
1212
+ }
1054
1213
  $updateInstance(instance) {
1055
1214
  this.$updateBindings(instance.texts, instance.attrs, instance.conds, instance.repeats);
1056
1215
  }
@@ -1099,7 +1258,8 @@ export class TemplateElement extends HTMLElement {
1099
1258
  el.setAttribute(b.name, '');
1100
1259
  else
1101
1260
  el.removeAttribute(b.name);
1102
- if (b.name === 'checked' || b.name === 'selected' || b.name === 'disabled') {
1261
+ if ((b.name === 'checked' || b.name === 'selected' || b.name === 'disabled')
1262
+ && hasNativeLiveProperty(el, b.name)) {
1103
1263
  el[b.name] = show;
1104
1264
  }
1105
1265
  break;
@@ -1113,12 +1273,14 @@ export class TemplateElement extends HTMLElement {
1113
1273
  default: {
1114
1274
  const v = this.$resolveValue(b.path, b.scope);
1115
1275
  const s = v == null ? '' : String(v);
1116
- if (b.name === 'checked' || b.name === 'selected') {
1276
+ if ((b.name === 'checked' || b.name === 'selected')
1277
+ && hasNativeLiveProperty(el, b.name)) {
1117
1278
  el[b.name] = !!v && v !== 'false' && v !== '0';
1118
1279
  }
1119
- else if (b.name === 'value') {
1120
- if (el.value !== s)
1121
- el.value = s;
1280
+ else if (b.name === 'value' && hasNativeLiveProperty(el, b.name)) {
1281
+ const target = el;
1282
+ if (target.value !== s)
1283
+ target.value = s;
1122
1284
  }
1123
1285
  else {
1124
1286
  if (el.getAttribute(b.name) !== s)
@@ -1131,22 +1293,25 @@ export class TemplateElement extends HTMLElement {
1131
1293
  $toggleCond(c) {
1132
1294
  const show = c.condition[0](this.$resolver, c.scope);
1133
1295
  if (show) {
1296
+ let created = false;
1297
+ const container = c.anchor.parentNode;
1134
1298
  if (!c.instance) {
1135
- c.instance = this.$createBlockInstance(c.blockIndex, c.scope);
1136
- if (c.instance) {
1137
- const frag = document.createDocumentFragment();
1138
- for (const n of c.instance.nodes)
1139
- frag.appendChild(n);
1140
- c.anchor.parentNode?.insertBefore(frag, c.anchor.nextSibling);
1141
- }
1299
+ c.instance = this.$createBlockInstance(c.blockIndex, c.scope, c.owner, container ?? undefined);
1300
+ created = c.instance !== null;
1142
1301
  }
1143
1302
  else {
1144
1303
  this.$updateInstance(c.instance);
1145
1304
  }
1305
+ if (c.instance && container) {
1306
+ this.$insertInstanceAfter(c.anchor, container, c.instance);
1307
+ if (created)
1308
+ this.$changeStructure();
1309
+ }
1146
1310
  }
1147
1311
  else if (c.instance) {
1148
1312
  this.$removeInstance(c.instance);
1149
1313
  c.instance = null;
1314
+ this.$changeStructure(c.owner);
1150
1315
  }
1151
1316
  }
1152
1317
  $resolveValue(path, scope) {
@@ -1164,6 +1329,23 @@ export class TemplateElement extends HTMLElement {
1164
1329
  return this.$resolveComponentRoot(path);
1165
1330
  return dotWalk(this.$resolveComponentRoot(path.substring(0, dot)), path, dot + 1);
1166
1331
  }
1332
+ $hasStateRoot(path, scope) {
1333
+ let frame = scope;
1334
+ while (frame) {
1335
+ if (path === frame.name
1336
+ || (path.length > frame.name.length
1337
+ && path.charCodeAt(frame.name.length) === 46
1338
+ && path.startsWith(frame.name))) {
1339
+ return frame.value !== undefined;
1340
+ }
1341
+ frame = frame.parent;
1342
+ }
1343
+ const dot = path.indexOf('.');
1344
+ const root = dot === -1 ? path : path.substring(0, dot);
1345
+ return hasAuthoredMember(this, root)
1346
+ || (this.$templateState !== null
1347
+ && Object.prototype.hasOwnProperty.call(this.$templateState, root));
1348
+ }
1167
1349
  $resolveComponentRoot(root) {
1168
1350
  const instance = this;
1169
1351
  if (this.$templateState &&
@@ -1189,64 +1371,92 @@ export class TemplateElement extends HTMLElement {
1189
1371
  $block(blockIndex) {
1190
1372
  return this.$meta?.b?.[blockIndex];
1191
1373
  }
1192
- $createBlockInstance(blockIndex, scope) {
1374
+ $createBlockInstance(blockIndex, scope, parent, container) {
1193
1375
  const bm = this.$block(blockIndex);
1194
1376
  if (!bm)
1195
1377
  return null;
1196
1378
  const wrapper = this.$createStagingRoot(bm);
1197
1379
  const inst = this.$wire(wrapper, bm, scope);
1380
+ inst.parent = parent;
1198
1381
  inst.nodes = childNodesArray(wrapper);
1199
1382
  this.$updateInstance(inst);
1200
1383
  if (inst.repeats.length !== 0 || inst.conds.length !== 0) {
1201
1384
  inst.nodes = childNodesArray(wrapper);
1202
- this.$releaseStagingRepeatContainers(inst, wrapper);
1385
+ this.$replaceInstanceContainer(inst, wrapper, container ?? null);
1386
+ }
1387
+ else if (container) {
1388
+ inst.container = container;
1203
1389
  }
1204
1390
  return inst;
1205
1391
  }
1206
1392
  $removeInstance(instance) {
1207
- this.$teardownInstanceCleanups(instance);
1208
- for (const n of instance.nodes)
1209
- n.parentNode?.removeChild(n);
1210
- for (const c of instance.conds) {
1211
- if (c.instance)
1212
- this.$removeInstance(c.instance);
1393
+ this.$disposeInstance(instance, true);
1394
+ }
1395
+ $disposeInstance(root, removeNodes) {
1396
+ const stack = [root];
1397
+ while (stack.length > 0) {
1398
+ const instance = stack.pop();
1399
+ if (!instance)
1400
+ continue;
1401
+ const cleanups = instance.cleanups;
1402
+ if (cleanups) {
1403
+ for (const cleanup of cleanups)
1404
+ cleanup();
1405
+ cleanups.length = 0;
1406
+ }
1407
+ if (removeNodes) {
1408
+ for (const node of instance.nodes)
1409
+ node.parentNode?.removeChild(node);
1410
+ }
1411
+ for (const binding of instance.conds) {
1412
+ if (binding.instance)
1413
+ stack.push(binding.instance);
1414
+ binding.instance = null;
1415
+ }
1416
+ for (const repeat of instance.repeats) {
1417
+ for (const child of repeat.instances)
1418
+ stack.push(child);
1419
+ this.$clearRepeatBinding(repeat);
1420
+ }
1421
+ this.$clearInstance(instance);
1213
1422
  }
1214
- for (const r of instance.repeats) {
1215
- for (const item of r.instances)
1216
- this.$removeInstance(item.instance);
1423
+ }
1424
+ $compactNodeArray(instance) {
1425
+ const container = instance.container;
1426
+ if (!container)
1427
+ return;
1428
+ const nodes = instance.nodes;
1429
+ let write = 0;
1430
+ for (let read = 0; read < nodes.length; read++) {
1431
+ const node = nodes[read];
1432
+ if (node.parentNode === container) {
1433
+ nodes[write] = node;
1434
+ write++;
1435
+ }
1217
1436
  }
1437
+ nodes.length = write;
1438
+ }
1439
+ $changeStructure(removedFrom) {
1440
+ if (removedFrom) {
1441
+ let current = removedFrom;
1442
+ while (current) {
1443
+ this.$compactNodeArray(current);
1444
+ current = current.parent;
1445
+ }
1446
+ }
1447
+ this.$pathIndex = undefined;
1448
+ this.$wildcardBindings = undefined;
1218
1449
  }
1219
1450
  $insertInstanceAfter(cursor, container, instance) {
1451
+ this.$replaceInstanceContainer(instance, instance.container, container);
1220
1452
  const nodes = instance.nodes;
1221
1453
  if (nodes.length === 0)
1222
1454
  return cursor;
1223
1455
  const ref = cursor ? cursor.nextSibling : container.firstChild;
1224
1456
  if (nodes[0] === ref)
1225
1457
  return nodes[nodes.length - 1];
1226
- const frag = document.createDocumentFragment();
1227
1458
  for (let i = 0; i < nodes.length; i++)
1228
- frag.appendChild(nodes[i]);
1229
- container.insertBefore(frag, ref);
1459
+ container.insertBefore(nodes[i], ref);
1230
1460
  return nodes[nodes.length - 1];
1231
1461
  }
1232
- $singleDynamic(parts) {
1233
- let path = '';
1234
- let prefix = '';
1235
- let suffix = '';
1236
- let seen = false;
1237
- for (const p of parts) {
1238
- if (typeof p === 'string') {
1239
- if (seen)
1240
- suffix += p;
1241
- else
1242
- prefix += p;
1243
- continue;
1244
- }
1245
- if (seen)
1246
- return null;
1247
- path = p[0];
1248
- seen = true;
1249
- }
1250
- return seen ? { path, prefix, suffix } : null;
1251
- }
1252
1462
  }