@microsoft/webui-framework 0.0.18 → 0.0.19

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.
@@ -1,9 +1,9 @@
1
1
  import { getTemplate } from './template.js';
2
2
  import { hydrationStart, hydrationEnd } from './lifecycle.js';
3
- import { syncRepeat, dotWalk } from './element/diff.js';
3
+ import { createRepeatKeyState, seedHydratedRepeatKeys, syncRepeat, dotWalk, } from './element/diff.js';
4
4
  import { collectItemMarkers, nextElement, findByOrdinal, MARKER_COND_START, MARKER_COND_END, MARKER_REPEAT_START, } from './element/markers.js';
5
5
  import { injectModuleStyle, } from './element/styles.js';
6
- import { ATTR_KIND_BOOLEAN, ATTR_KIND_COMPLEX, ATTR_KIND_TEMPLATE, } from './element/types.js';
6
+ import { ATTR_KIND_BOOLEAN, ATTR_KIND_COMPLEX, ATTR_KIND_TEMPLATE, hasNativeLiveProperty, } from './element/types.js';
7
7
  import { templateHasRoot, templateRootForAttribute } from './template-roots.js';
8
8
  const DEV = typeof __WEBUI_DEV__ === 'undefined' || __WEBUI_DEV__;
9
9
  const templateCache = new WeakMap();
@@ -36,27 +36,6 @@ const EMPTY_ARR = [];
36
36
  const EMPTY_SET = Object.freeze(new Set());
37
37
  const WEBUI_SET_STATE_KEY = Symbol.for('microsoft.webui.setStateKey');
38
38
  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
39
  function childNodesArray(parent) {
61
40
  const children = parent.childNodes;
62
41
  const len = children.length;
@@ -128,6 +107,9 @@ export class TemplateElement extends HTMLElement {
128
107
  this.$root = null;
129
108
  this.$ready = false;
130
109
  this.$hydrated = false;
110
+ this.$deferredSSR = false;
111
+ this.$activatingDeferredSSR = false;
112
+ this.$hasUnknownScopes = false;
131
113
  this.$templateState = null;
132
114
  this.$dirtyPaths = null;
133
115
  this.$pendingFlush = false;
@@ -135,7 +117,13 @@ export class TemplateElement extends HTMLElement {
135
117
  this.$resolver = (p, s) => this.$resolveValue(p, s);
136
118
  }
137
119
  [WEBUI_SET_STATE_KEY](key, value) {
138
- return this.$setStateKey(key, value);
120
+ const wasDeferred = this.$deferredSSR;
121
+ this.$beforeExternalStateWrite();
122
+ const owned = this.$setStateKey(key, value);
123
+ this.$afterExternalStateWrite(owned);
124
+ if (owned && wasDeferred && !this.$deferredSSR)
125
+ this.$update(key);
126
+ return owned;
139
127
  }
140
128
  static define(tagName) {
141
129
  installTemplateObservedAttributes(this, tagName);
@@ -143,6 +131,10 @@ export class TemplateElement extends HTMLElement {
143
131
  }
144
132
  connectedCallback() {
145
133
  const tag = this.tagName.toLowerCase();
134
+ if (this.$deferredSSR) {
135
+ this.$ready = true;
136
+ return;
137
+ }
146
138
  if (this.$hydrated && this.$root) {
147
139
  hydrationStart();
148
140
  this.$ready = true;
@@ -157,17 +149,11 @@ export class TemplateElement extends HTMLElement {
157
149
  return;
158
150
  }
159
151
  this.$meta = meta;
160
- if (document.readyState === 'loading') {
161
- runWhenDomReady(() => this.$mount(meta));
162
- }
163
- else {
164
- this.$mount(meta);
165
- }
152
+ this.$mount(meta, false);
166
153
  }
167
- $mount(meta) {
154
+ $mount(meta, forceSSR) {
168
155
  if (this.$hydrated)
169
156
  return;
170
- hydrationStart();
171
157
  const hasShadow = !!this.shadowRoot;
172
158
  const wantShadow = hasShadow || !!meta.sd;
173
159
  let root;
@@ -189,10 +175,18 @@ export class TemplateElement extends HTMLElement {
189
175
  root = this;
190
176
  isSSR = false;
191
177
  }
178
+ if (isSSR && !forceSSR && this.$shouldDeferSSRHydration()) {
179
+ this.$meta = meta;
180
+ this.$deferredSSR = true;
181
+ this.$ready = true;
182
+ return;
183
+ }
184
+ hydrationStart();
192
185
  if (meta.sa)
193
186
  injectModuleStyle(meta.sa, this.shadowRoot);
194
187
  if (isSSR) {
195
- this.$applySSRState();
188
+ if (this.$shouldApplySSRBootstrapState())
189
+ this.$applySSRState();
196
190
  this.$root = this.$hydrate(root, meta, getTemplateDom(meta));
197
191
  }
198
192
  else {
@@ -211,9 +205,10 @@ export class TemplateElement extends HTMLElement {
211
205
  this.$updateInstance(this.$root);
212
206
  if (this.$root.repeats.length !== 0 || this.$root.conds.length !== 0) {
213
207
  this.$root.nodes = childNodesArray(clientRoot);
214
- this.$releaseStagingRepeatContainers(this.$root, clientRoot);
208
+ this.$replaceInstanceContainer(this.$root, clientRoot, root);
215
209
  }
216
210
  this.$appendStagedChildren(root, clientRoot);
211
+ this.$root.container = root;
217
212
  }
218
213
  hydrationEnd();
219
214
  }
@@ -226,9 +221,13 @@ export class TemplateElement extends HTMLElement {
226
221
  }
227
222
  }
228
223
  $destroy() {
229
- if (!this.$root)
224
+ if (!this.$root) {
225
+ this.$deferredSSR = false;
226
+ this.$ready = false;
227
+ this.$hasUnknownScopes = false;
230
228
  return;
231
- this.$teardown(this.$root);
229
+ }
230
+ this.$disposeInstance(this.$root, false);
232
231
  this.$root = null;
233
232
  this.$pathIndex = undefined;
234
233
  this.$wildcardBindings = undefined;
@@ -236,55 +235,87 @@ export class TemplateElement extends HTMLElement {
236
235
  this.$pendingFlush = false;
237
236
  this.$preReadyWrites = null;
238
237
  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
- }
238
+ this.$hasUnknownScopes = false;
239
+ }
240
+ $clearRepeatBinding(repeat) {
241
+ repeat.instances.length = 0;
242
+ repeat.container = null;
243
+ repeat.start = null;
244
+ repeat.end = null;
245
+ const state = repeat.keyState;
246
+ if (state) {
247
+ state.keys.length = 0;
248
+ state.nextKeys.length = 0;
249
+ state.map.clear();
250
+ }
251
+ }
252
+ $clearInstance(instance) {
253
+ instance.scope = undefined;
254
+ instance.parent = undefined;
255
+ instance.container = null;
255
256
  instance.nodes.length = 0;
256
257
  instance.texts.length = 0;
257
258
  instance.attrs.length = 0;
258
259
  instance.conds.length = 0;
259
260
  instance.repeats.length = 0;
260
261
  }
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
262
  attributeChangedCallback(name, oldValue, newValue) {
270
263
  if (Object.is(oldValue, newValue))
271
264
  return;
265
+ const wasDeferred = this.$deferredSSR;
266
+ this.$beforeExternalStateWrite();
272
267
  const property = this.$templateRootForAttribute(name);
268
+ let changed = false;
273
269
  if (property && this.$usesTemplateState(property)) {
274
- this.$setTemplateState(property, newValue);
270
+ changed = this.$setTemplateState(property, newValue);
275
271
  }
272
+ this.$afterExternalStateWrite(changed);
273
+ if (changed && property && wasDeferred && !this.$deferredSSR)
274
+ this.$update(property);
276
275
  }
277
276
  setState(state) {
277
+ const wasDeferred = this.$deferredSSR;
278
+ this.$beforeExternalStateWrite();
278
279
  const keys = Object.keys(state);
280
+ let owned = false;
279
281
  for (let i = 0; i < keys.length; i++) {
280
282
  const key = keys[i];
281
- this.$setStateKey(key, state[key]);
283
+ owned = this.$setStateKey(key, state[key]) || owned;
284
+ }
285
+ this.$afterExternalStateWrite(owned);
286
+ if (owned && wasDeferred && !this.$deferredSSR) {
287
+ for (let i = 0; i < keys.length; i++)
288
+ this.$update(keys[i]);
282
289
  }
283
290
  this.$flushUpdates();
284
291
  }
285
292
  $observableNames() {
286
293
  return EMPTY_SET;
287
294
  }
295
+ $beforeExternalStateWrite() {
296
+ }
297
+ $afterExternalStateWrite(_applied) {
298
+ }
299
+ $shouldDeferSSRHydration() {
300
+ return false;
301
+ }
302
+ $activateDeferredSSR() {
303
+ if (!this.$deferredSSR || !this.$meta)
304
+ return;
305
+ this.$deferredSSR = false;
306
+ this.$ready = false;
307
+ this.$preReadyWrites = null;
308
+ this.$activatingDeferredSSR = true;
309
+ try {
310
+ this.$mount(this.$meta, true);
311
+ }
312
+ finally {
313
+ this.$activatingDeferredSSR = false;
314
+ }
315
+ }
316
+ $shouldApplySSRBootstrapState() {
317
+ return true;
318
+ }
288
319
  $shouldApplySSRState(_key) {
289
320
  return true;
290
321
  }
@@ -294,9 +325,11 @@ export class TemplateElement extends HTMLElement {
294
325
  return true;
295
326
  }
296
327
  $setTemplateState(key, value) {
297
- if (this.$writeTemplateState(key, value)) {
328
+ const changed = this.$writeTemplateState(key, value);
329
+ if (changed) {
298
330
  this.$update(key);
299
331
  }
332
+ return changed;
300
333
  }
301
334
  $writeTemplateState(key, value) {
302
335
  if (!this.$templateState) {
@@ -382,24 +415,34 @@ export class TemplateElement extends HTMLElement {
382
415
  this.$flush();
383
416
  }
384
417
  $flush() {
385
- if (!this.$ready || !this.$root || !this.$pathIndex) {
418
+ if (!this.$ready || !this.$root) {
386
419
  this.$dirtyPaths = null;
387
420
  this.$pendingFlush = false;
388
421
  return;
389
422
  }
423
+ if (!this.$pathIndex)
424
+ this.$buildPathIndex();
425
+ if (!this.$pathIndex)
426
+ return;
390
427
  while (this.$dirtyPaths && this.$dirtyPaths.size > 0) {
391
428
  const dirty = this.$dirtyPaths;
392
429
  this.$dirtyPaths = null;
393
430
  for (const path of dirty) {
394
- const entry = this.$pathIndex.get(path);
431
+ if (!this.$pathIndex)
432
+ this.$buildPathIndex();
433
+ const entry = this.$pathIndex?.get(path);
395
434
  if (entry) {
396
435
  this.$updateBindings(entry.texts, entry.attrs, entry.conds, entry.repeats);
397
436
  }
398
437
  }
438
+ if (!this.$pathIndex)
439
+ this.$buildPathIndex();
399
440
  if (this.$wildcardBindings) {
400
441
  const wc = this.$wildcardBindings;
401
442
  this.$updateBindings(wc.texts, wc.attrs, wc.conds, wc.repeats);
402
443
  }
444
+ if (!this.$pathIndex)
445
+ this.$buildPathIndex();
403
446
  }
404
447
  this.$pendingFlush = false;
405
448
  }
@@ -501,34 +544,34 @@ export class TemplateElement extends HTMLElement {
501
544
  }
502
545
  root.appendChild(fragment);
503
546
  }
504
- $releaseStagingRepeatContainers(instance, stagingRoot) {
505
- if (!instance || !stagingRoot)
506
- return;
507
- if (instance.repeats.length === 0 && instance.conds.length === 0)
547
+ $replaceInstanceContainer(instance, previous, next) {
548
+ if (previous === next)
508
549
  return;
509
550
  const stack = [instance];
510
551
  while (stack.length > 0) {
511
552
  const current = stack.pop();
512
553
  if (!current)
513
554
  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
- }
555
+ if (current.container === previous)
556
+ current.container = next;
522
557
  for (let i = 0; i < current.conds.length; i++) {
523
558
  const child = current.conds[i].instance;
524
559
  if (child)
525
560
  stack.push(child);
526
561
  }
562
+ for (let i = 0; i < current.repeats.length; i++) {
563
+ const repeat = current.repeats[i];
564
+ if (repeat.container === previous)
565
+ repeat.container = next;
566
+ const children = repeat.instances;
567
+ for (let j = 0; j < children.length; j++)
568
+ stack.push(children[j]);
569
+ }
527
570
  }
528
571
  }
529
572
  $wire(root, meta, scope) {
530
573
  const instance = {
531
- scope, nodes: childNodesArray(root),
574
+ scope, container: root, nodes: childNodesArray(root),
532
575
  texts: [], attrs: [], conds: [], repeats: [],
533
576
  };
534
577
  const textRefs = new Array(meta.tx?.length ?? 0);
@@ -563,12 +606,19 @@ export class TemplateElement extends HTMLElement {
563
606
  let repRefCount = 0;
564
607
  if (meta.r) {
565
608
  for (let i = 0; i < meta.r.length; i++) {
566
- const [collection, itemVar, blockIndex, slotMeta] = meta.r[i];
609
+ const [collection, itemVar, blockIndex, slotMeta, keyPath] = meta.r[i];
567
610
  const [parentPath, beforeIndex] = slotMeta;
568
611
  const parent = parentPath.length > 0 ? this.$resolve(root, parentPath) : root;
569
612
  if (!parent || (parent.nodeType !== 1 && parent.nodeType !== 11))
570
613
  continue;
571
- repRefs[repRefCount] = { parent, ref: parent.childNodes[beforeIndex] || null, collection, itemVar, blockIndex };
614
+ repRefs[repRefCount] = {
615
+ parent,
616
+ ref: parent.childNodes[beforeIndex] || null,
617
+ collection,
618
+ itemVar,
619
+ blockIndex,
620
+ keyPath,
621
+ };
572
622
  repRefCount += 1;
573
623
  }
574
624
  }
@@ -594,19 +644,28 @@ export class TemplateElement extends HTMLElement {
594
644
  const c = condRefs[i];
595
645
  const anchor = document.createComment('');
596
646
  c.parent.insertBefore(anchor, c.ref);
597
- instance.conds.push({ condition: c.condition, blockIndex: c.blockIndex, anchor, scope, instance: null });
647
+ instance.conds.push({
648
+ condition: c.condition,
649
+ blockIndex: c.blockIndex,
650
+ anchor,
651
+ scope,
652
+ owner: instance,
653
+ instance: null,
654
+ });
598
655
  }
599
656
  for (let i = 0; i < repRefCount; i++) {
600
657
  const r = repRefs[i];
601
658
  const anchor = document.createComment('');
602
659
  r.parent.insertBefore(anchor, r.ref);
603
- const { keyAttribute, keyPath } = this.$repeatMaps(r.blockIndex, r.itemVar);
604
- instance.repeats.push({
660
+ const binding = {
605
661
  markerId: i, collection: r.collection, itemVar: r.itemVar, blockIndex: r.blockIndex,
606
662
  container: r.parent, start: anchor, end: null,
607
- scope, owner: instance, instances: [], rootTag: null,
608
- keyAttribute, keyPath,
609
- });
663
+ scope, owner: instance, instances: [],
664
+ };
665
+ if (r.keyPath !== undefined) {
666
+ binding.keyState = createRepeatKeyState(r.keyPath);
667
+ }
668
+ instance.repeats.push(binding);
610
669
  }
611
670
  for (let i = 0; i < instance.conds.length; i++)
612
671
  this.$toggleCond(instance.conds[i]);
@@ -615,6 +674,7 @@ export class TemplateElement extends HTMLElement {
615
674
  $hydrate(ssrRoot, meta, tplDom, scope, pathStart = 0) {
616
675
  const instance = {
617
676
  scope,
677
+ container: (pathStart > 0 ? ssrRoot.parentNode : ssrRoot),
618
678
  nodes: pathStart > 0 ? [ssrRoot] : childNodesArray(ssrRoot),
619
679
  texts: [], attrs: [], conds: [], repeats: [],
620
680
  };
@@ -657,7 +717,6 @@ export class TemplateElement extends HTMLElement {
657
717
  const [parentPath] = slotMeta;
658
718
  const ssrParent = this.$resolveSSR(ssrRoot, tplDom, parentPath, pathStart) ?? ssrRoot;
659
719
  const blockMeta = this.$block(blockIndex);
660
- const shown = condition[0](this.$resolver, scope);
661
720
  let condInstance = null;
662
721
  if (ssrParent !== lastCondParent) {
663
722
  lastCondMarker = null;
@@ -676,11 +735,10 @@ export class TemplateElement extends HTMLElement {
676
735
  }
677
736
  if (marker)
678
737
  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
- }
738
+ if (marker && blockMeta) {
739
+ condInstance = this.$hydrateCondContent(condAnchor, blockMeta, scope);
740
+ if (condInstance)
741
+ condInstance.parent = instance;
684
742
  }
685
743
  if (marker) {
686
744
  const lastNode = condInstance ? condInstance.nodes[condInstance.nodes.length - 1] : condAnchor;
@@ -692,7 +750,7 @@ export class TemplateElement extends HTMLElement {
692
750
  instance.conds.push({
693
751
  condition: condition, blockIndex,
694
752
  anchor: condAnchor,
695
- scope, instance: condInstance,
753
+ scope, owner: instance, instance: condInstance,
696
754
  });
697
755
  }
698
756
  }
@@ -700,7 +758,7 @@ export class TemplateElement extends HTMLElement {
700
758
  let lastRepMarker = null;
701
759
  let lastRepParent = null;
702
760
  for (let i = 0; i < meta.r.length; i++) {
703
- const [collection, itemVar, blockIndex, slotMeta] = meta.r[i];
761
+ const [collection, itemVar, blockIndex, slotMeta, keyPath] = meta.r[i];
704
762
  const [parentPath] = slotMeta;
705
763
  const ssrParent = this.$resolveSSR(ssrRoot, tplDom, parentPath, pathStart) ?? ssrRoot;
706
764
  if (ssrParent !== lastRepParent) {
@@ -708,7 +766,6 @@ export class TemplateElement extends HTMLElement {
708
766
  lastRepParent = ssrParent;
709
767
  }
710
768
  const blockMeta = this.$block(blockIndex);
711
- const { keyAttribute, keyPath } = this.$repeatMaps(blockIndex, itemVar);
712
769
  const blockTplDom = blockMeta ? getTemplateDom(blockMeta) : null;
713
770
  const rootTag = blockMeta && blockTplDom?.childNodes.length === 1 && blockTplDom.children.length === 1
714
771
  ? this.$rootTag(blockMeta)
@@ -728,27 +785,35 @@ export class TemplateElement extends HTMLElement {
728
785
  }
729
786
  lastRepMarker = anchor;
730
787
  const repeatInsts = [];
788
+ const hasCollectionState = this.$hasStateRoot(collection, scope);
731
789
  const itemsArr = this.$resolveValue(collection, scope);
732
790
  const items = Array.isArray(itemsArr) ? itemsArr : [];
733
791
  const { items: itemMarkers, end: endMarker } = marker
734
792
  ? collectItemMarkers(anchor)
735
793
  : { items: [], end: null };
736
- if (blockMeta && blockTplDom && items.length > 0 && anchor.parentNode && itemMarkers.length > 0) {
737
- if (itemMarkers.length !== items.length) {
794
+ if (blockMeta && blockTplDom && anchor.parentNode && itemMarkers.length > 0) {
795
+ if (!this.$activatingDeferredSSR
796
+ && hasCollectionState
797
+ && itemMarkers.length !== items.length) {
738
798
  console.warn(`[webui] hydration: repeat marker count (${itemMarkers.length}) ≠ data length (${items.length}) for "${collection}"`);
739
799
  }
740
- const limit = Math.min(itemMarkers.length, items.length);
741
- for (let j = 0; j < limit; j++) {
800
+ for (let j = 0; j < itemMarkers.length; j++) {
742
801
  const itemValue = items[j];
743
- const itemScope = { name: itemVar, value: itemValue, parent: scope };
802
+ const known = hasCollectionState && j < items.length;
803
+ if (!known)
804
+ this.$hasUnknownScopes = true;
805
+ const itemScope = {
806
+ name: itemVar,
807
+ value: itemValue,
808
+ parent: scope,
809
+ known,
810
+ };
744
811
  if (rootTag) {
745
812
  const itemEl = nextElement(itemMarkers[j]);
746
813
  if (itemEl) {
747
- const key = keyAttribute !== undefined
748
- ? itemEl.getAttribute(keyAttribute)
749
- : String(j);
750
814
  const childInstance = this.$hydrate(itemEl, blockMeta, blockTplDom, itemScope, 1);
751
- repeatInsts.push({ key, value: itemValue, instance: childInstance });
815
+ childInstance.parent = instance;
816
+ repeatInsts.push(childInstance);
752
817
  }
753
818
  }
754
819
  else {
@@ -762,6 +827,7 @@ export class TemplateElement extends HTMLElement {
762
827
  cursor = next;
763
828
  }
764
829
  const inst = this.$hydrate(wrapper, blockMeta, blockTplDom, itemScope);
830
+ inst.parent = instance;
765
831
  inst.nodes = childNodesArray(wrapper);
766
832
  let afterNode = itemMarkers[j];
767
833
  for (let nodeIndex = 0; nodeIndex < inst.nodes.length; nodeIndex++) {
@@ -769,8 +835,10 @@ export class TemplateElement extends HTMLElement {
769
835
  itemParent?.insertBefore(node, afterNode.nextSibling);
770
836
  afterNode = node;
771
837
  }
772
- const key = keyPath ? String(dotWalk(itemValue, keyPath, 0) ?? '') : null;
773
- repeatInsts.push({ key, value: itemValue, instance: inst });
838
+ if (itemParent) {
839
+ this.$replaceInstanceContainer(inst, wrapper, itemParent);
840
+ }
841
+ repeatInsts.push(inst);
774
842
  }
775
843
  }
776
844
  for (let m = 0; m < itemMarkers.length; m++) {
@@ -779,43 +847,76 @@ export class TemplateElement extends HTMLElement {
779
847
  }
780
848
  if (endMarker)
781
849
  staleMarkers.push(endMarker);
782
- instance.repeats.push({
850
+ const binding = {
783
851
  markerId: i, collection, itemVar, blockIndex,
784
852
  container: (anchor.parentNode ?? ssrRoot),
785
853
  start: anchor, end: null,
786
- scope, owner: instance, instances: repeatInsts, rootTag,
787
- keyAttribute, keyPath, synced: true,
788
- });
854
+ scope, owner: instance, instances: repeatInsts,
855
+ synced: hasCollectionState,
856
+ };
857
+ if (keyPath !== undefined) {
858
+ binding.keyState = createRepeatKeyState(keyPath);
859
+ if (hasCollectionState) {
860
+ seedHydratedRepeatKeys(binding, items);
861
+ }
862
+ }
863
+ instance.repeats.push(binding);
789
864
  }
790
865
  }
791
866
  this.$finalize(instance, ssrRoot, meta, (r, p) => this.$resolveSSR(r, tplDom, p, pathStart), scope);
792
867
  for (let i = 0; i < staleMarkers.length; i++) {
793
868
  staleMarkers[i].parentNode?.removeChild(staleMarkers[i]);
794
869
  }
870
+ this.$compactNodeArray(instance);
795
871
  return instance;
796
872
  }
797
- $collectBetween(start, endData) {
873
+ $collectConditionalRange(start) {
798
874
  const nodes = [];
875
+ let depth = 0;
799
876
  let node = start.nextSibling;
800
877
  while (node) {
801
- if (node.nodeType === 8 && node.data === endData)
802
- break;
878
+ if (node.nodeType === 8) {
879
+ const data = node.data;
880
+ if (data === MARKER_COND_START) {
881
+ depth++;
882
+ }
883
+ else if (data === MARKER_COND_END) {
884
+ if (depth === 0)
885
+ break;
886
+ depth--;
887
+ }
888
+ }
803
889
  nodes.push(node);
804
890
  node = node.nextSibling;
805
891
  }
806
892
  return nodes;
807
893
  }
894
+ $hasRootStructuralSlot(meta) {
895
+ if (meta.c) {
896
+ for (let i = 0; i < meta.c.length; i++) {
897
+ if (meta.c[i][2][0].length === 0)
898
+ return true;
899
+ }
900
+ }
901
+ if (meta.r) {
902
+ for (let i = 0; i < meta.r.length; i++) {
903
+ if (meta.r[i][3][0].length === 0)
904
+ return true;
905
+ }
906
+ }
907
+ return false;
908
+ }
808
909
  $hydrateCondContent(condAnchor, blockMeta, scope) {
809
910
  const rootTag = this.$rootTag(blockMeta);
810
911
  const tplDom = getTemplateDom(blockMeta);
811
- if (rootTag && tplDom.children.length === 1) {
912
+ if (rootTag && tplDom.children.length === 1 && !this.$hasRootStructuralSlot(blockMeta)) {
812
913
  const el = nextElement(condAnchor);
813
914
  if (el) {
814
915
  return this.$hydrate(el, blockMeta, tplDom, scope, 1);
815
916
  }
816
917
  return null;
817
918
  }
818
- const condNodes = this.$collectBetween(condAnchor, MARKER_COND_END);
919
+ const condNodes = this.$collectConditionalRange(condAnchor);
819
920
  if (condNodes.length === 0)
820
921
  return null;
821
922
  const wrapper = document.createElement('div');
@@ -828,6 +929,9 @@ export class TemplateElement extends HTMLElement {
828
929
  condAnchor.parentNode?.insertBefore(inst.nodes[cn], afterNode.nextSibling);
829
930
  afterNode = inst.nodes[cn];
830
931
  }
932
+ const container = condAnchor.parentNode;
933
+ if (container)
934
+ this.$replaceInstanceContainer(inst, wrapper, container);
831
935
  return inst;
832
936
  }
833
937
  $findMarker(parent, data, after) {
@@ -840,16 +944,6 @@ export class TemplateElement extends HTMLElement {
840
944
  }
841
945
  return null;
842
946
  }
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
947
  $findSSRText(ssrParent, tplParent, beforeIndex) {
854
948
  const ordinals = getTplOrdinals(tplParent);
855
949
  let textOrd = 0;
@@ -926,32 +1020,6 @@ export class TemplateElement extends HTMLElement {
926
1020
  return { element: el, name, kind, parts: entry[2], scope };
927
1021
  return { element: el, name, kind: kind, path: entry[2] || '', scope };
928
1022
  }
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
1023
  $buildPathIndex() {
956
1024
  if (!this.$root)
957
1025
  return;
@@ -1025,7 +1093,7 @@ export class TemplateElement extends HTMLElement {
1025
1093
  ensure(keyFor(rep.collection)).repeats.push(rep);
1026
1094
  }
1027
1095
  for (let i = 0; i < rep.instances.length; i++) {
1028
- visit(rep.instances[i].instance);
1096
+ visit(rep.instances[i]);
1029
1097
  }
1030
1098
  }
1031
1099
  };
@@ -1041,16 +1109,37 @@ export class TemplateElement extends HTMLElement {
1041
1109
  this.$pathIndex = index;
1042
1110
  }
1043
1111
  $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]);
1112
+ const gated = this.$hasUnknownScopes;
1113
+ for (let i = 0; i < texts.length; i++) {
1114
+ const binding = texts[i];
1115
+ if (!gated || !binding.scope || this.$scopeIsKnown(binding.scope))
1116
+ this.$patchText(binding);
1117
+ }
1118
+ for (let i = 0; i < attrs.length; i++) {
1119
+ const binding = attrs[i];
1120
+ if (!gated || !binding.scope || this.$scopeIsKnown(binding.scope))
1121
+ this.$patchAttr(binding);
1122
+ }
1123
+ for (let i = 0; i < conds.length; i++) {
1124
+ const binding = conds[i];
1125
+ if (!gated || !binding.scope || this.$scopeIsKnown(binding.scope))
1126
+ this.$toggleCond(binding);
1127
+ }
1050
1128
  for (let i = 0; i < repeats.length; i++) {
1051
- syncRepeat(this, repeats[i]);
1129
+ const binding = repeats[i];
1130
+ if (!gated || !binding.scope || this.$scopeIsKnown(binding.scope))
1131
+ syncRepeat(this, binding);
1052
1132
  }
1053
1133
  }
1134
+ $scopeIsKnown(scope) {
1135
+ let frame = scope;
1136
+ while (frame) {
1137
+ if (frame.known === false)
1138
+ return false;
1139
+ frame = frame.parent;
1140
+ }
1141
+ return true;
1142
+ }
1054
1143
  $updateInstance(instance) {
1055
1144
  this.$updateBindings(instance.texts, instance.attrs, instance.conds, instance.repeats);
1056
1145
  }
@@ -1099,7 +1188,8 @@ export class TemplateElement extends HTMLElement {
1099
1188
  el.setAttribute(b.name, '');
1100
1189
  else
1101
1190
  el.removeAttribute(b.name);
1102
- if (b.name === 'checked' || b.name === 'selected' || b.name === 'disabled') {
1191
+ if ((b.name === 'checked' || b.name === 'selected' || b.name === 'disabled')
1192
+ && hasNativeLiveProperty(el, b.name)) {
1103
1193
  el[b.name] = show;
1104
1194
  }
1105
1195
  break;
@@ -1113,12 +1203,14 @@ export class TemplateElement extends HTMLElement {
1113
1203
  default: {
1114
1204
  const v = this.$resolveValue(b.path, b.scope);
1115
1205
  const s = v == null ? '' : String(v);
1116
- if (b.name === 'checked' || b.name === 'selected') {
1206
+ if ((b.name === 'checked' || b.name === 'selected')
1207
+ && hasNativeLiveProperty(el, b.name)) {
1117
1208
  el[b.name] = !!v && v !== 'false' && v !== '0';
1118
1209
  }
1119
- else if (b.name === 'value') {
1120
- if (el.value !== s)
1121
- el.value = s;
1210
+ else if (b.name === 'value' && hasNativeLiveProperty(el, b.name)) {
1211
+ const target = el;
1212
+ if (target.value !== s)
1213
+ target.value = s;
1122
1214
  }
1123
1215
  else {
1124
1216
  if (el.getAttribute(b.name) !== s)
@@ -1131,22 +1223,25 @@ export class TemplateElement extends HTMLElement {
1131
1223
  $toggleCond(c) {
1132
1224
  const show = c.condition[0](this.$resolver, c.scope);
1133
1225
  if (show) {
1226
+ let created = false;
1227
+ const container = c.anchor.parentNode;
1134
1228
  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
- }
1229
+ c.instance = this.$createBlockInstance(c.blockIndex, c.scope, c.owner, container ?? undefined);
1230
+ created = c.instance !== null;
1142
1231
  }
1143
1232
  else {
1144
1233
  this.$updateInstance(c.instance);
1145
1234
  }
1235
+ if (c.instance && container) {
1236
+ this.$insertInstanceAfter(c.anchor, container, c.instance);
1237
+ if (created)
1238
+ this.$changeStructure();
1239
+ }
1146
1240
  }
1147
1241
  else if (c.instance) {
1148
1242
  this.$removeInstance(c.instance);
1149
1243
  c.instance = null;
1244
+ this.$changeStructure(c.owner);
1150
1245
  }
1151
1246
  }
1152
1247
  $resolveValue(path, scope) {
@@ -1164,6 +1259,23 @@ export class TemplateElement extends HTMLElement {
1164
1259
  return this.$resolveComponentRoot(path);
1165
1260
  return dotWalk(this.$resolveComponentRoot(path.substring(0, dot)), path, dot + 1);
1166
1261
  }
1262
+ $hasStateRoot(path, scope) {
1263
+ let frame = scope;
1264
+ while (frame) {
1265
+ if (path === frame.name
1266
+ || (path.length > frame.name.length
1267
+ && path.charCodeAt(frame.name.length) === 46
1268
+ && path.startsWith(frame.name))) {
1269
+ return frame.value !== undefined;
1270
+ }
1271
+ frame = frame.parent;
1272
+ }
1273
+ const dot = path.indexOf('.');
1274
+ const root = dot === -1 ? path : path.substring(0, dot);
1275
+ return hasAuthoredMember(this, root)
1276
+ || (this.$templateState !== null
1277
+ && Object.prototype.hasOwnProperty.call(this.$templateState, root));
1278
+ }
1167
1279
  $resolveComponentRoot(root) {
1168
1280
  const instance = this;
1169
1281
  if (this.$templateState &&
@@ -1189,64 +1301,92 @@ export class TemplateElement extends HTMLElement {
1189
1301
  $block(blockIndex) {
1190
1302
  return this.$meta?.b?.[blockIndex];
1191
1303
  }
1192
- $createBlockInstance(blockIndex, scope) {
1304
+ $createBlockInstance(blockIndex, scope, parent, container) {
1193
1305
  const bm = this.$block(blockIndex);
1194
1306
  if (!bm)
1195
1307
  return null;
1196
1308
  const wrapper = this.$createStagingRoot(bm);
1197
1309
  const inst = this.$wire(wrapper, bm, scope);
1310
+ inst.parent = parent;
1198
1311
  inst.nodes = childNodesArray(wrapper);
1199
1312
  this.$updateInstance(inst);
1200
1313
  if (inst.repeats.length !== 0 || inst.conds.length !== 0) {
1201
1314
  inst.nodes = childNodesArray(wrapper);
1202
- this.$releaseStagingRepeatContainers(inst, wrapper);
1315
+ this.$replaceInstanceContainer(inst, wrapper, container ?? null);
1316
+ }
1317
+ else if (container) {
1318
+ inst.container = container;
1203
1319
  }
1204
1320
  return inst;
1205
1321
  }
1206
1322
  $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);
1323
+ this.$disposeInstance(instance, true);
1324
+ }
1325
+ $disposeInstance(root, removeNodes) {
1326
+ const stack = [root];
1327
+ while (stack.length > 0) {
1328
+ const instance = stack.pop();
1329
+ if (!instance)
1330
+ continue;
1331
+ const cleanups = instance.cleanups;
1332
+ if (cleanups) {
1333
+ for (const cleanup of cleanups)
1334
+ cleanup();
1335
+ cleanups.length = 0;
1336
+ }
1337
+ if (removeNodes) {
1338
+ for (const node of instance.nodes)
1339
+ node.parentNode?.removeChild(node);
1340
+ }
1341
+ for (const binding of instance.conds) {
1342
+ if (binding.instance)
1343
+ stack.push(binding.instance);
1344
+ binding.instance = null;
1345
+ }
1346
+ for (const repeat of instance.repeats) {
1347
+ for (const child of repeat.instances)
1348
+ stack.push(child);
1349
+ this.$clearRepeatBinding(repeat);
1350
+ }
1351
+ this.$clearInstance(instance);
1352
+ }
1353
+ }
1354
+ $compactNodeArray(instance) {
1355
+ const container = instance.container;
1356
+ if (!container)
1357
+ return;
1358
+ const nodes = instance.nodes;
1359
+ let write = 0;
1360
+ for (let read = 0; read < nodes.length; read++) {
1361
+ const node = nodes[read];
1362
+ if (node.parentNode === container) {
1363
+ nodes[write] = node;
1364
+ write++;
1365
+ }
1213
1366
  }
1214
- for (const r of instance.repeats) {
1215
- for (const item of r.instances)
1216
- this.$removeInstance(item.instance);
1367
+ nodes.length = write;
1368
+ }
1369
+ $changeStructure(removedFrom) {
1370
+ if (removedFrom) {
1371
+ let current = removedFrom;
1372
+ while (current) {
1373
+ this.$compactNodeArray(current);
1374
+ current = current.parent;
1375
+ }
1217
1376
  }
1377
+ this.$pathIndex = undefined;
1378
+ this.$wildcardBindings = undefined;
1218
1379
  }
1219
1380
  $insertInstanceAfter(cursor, container, instance) {
1381
+ this.$replaceInstanceContainer(instance, instance.container, container);
1220
1382
  const nodes = instance.nodes;
1221
1383
  if (nodes.length === 0)
1222
1384
  return cursor;
1223
1385
  const ref = cursor ? cursor.nextSibling : container.firstChild;
1224
1386
  if (nodes[0] === ref)
1225
1387
  return nodes[nodes.length - 1];
1226
- const frag = document.createDocumentFragment();
1227
1388
  for (let i = 0; i < nodes.length; i++)
1228
- frag.appendChild(nodes[i]);
1229
- container.insertBefore(frag, ref);
1389
+ container.insertBefore(nodes[i], ref);
1230
1390
  return nodes[nodes.length - 1];
1231
1391
  }
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
1392
  }