@microsoft/webui-framework 0.0.22 → 0.0.24

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
@@ -108,6 +108,63 @@ lifecycle code, imperative methods, or state that TypeScript code reads or
108
108
  mutates. `@observable` and `@attr` are optional; add them when JavaScript needs
109
109
  to access the value or when the value is part of the component's public API.
110
110
 
111
+ ### Offscreen work reduction
112
+
113
+ For components repeated beyond the initial viewport, put the complete policy on
114
+ the component template:
115
+
116
+ ```html
117
+ <template
118
+ w-render="lazy"
119
+ w-reserve-block-size="18rem"
120
+ >
121
+ <!-- Component content -->
122
+ </template>
123
+ ```
124
+
125
+ `w-render="lazy"` combines visibility-deferred hydration with the browser's
126
+ `content-visibility: auto` rendering deferral. The reservation is the typical
127
+ rendered block size of one instance; WebUI emits it as
128
+ `contain-intrinsic-block-size: auto 18rem` before first layout. The SSR DOM
129
+ remains present, searchable, and accessible while the browser skips offscreen
130
+ style, layout, and paint work. The generated policy applies to instances in the
131
+ document, Light DOM, and standard Shadow DOM components. A `--dom=light`
132
+ component inside an authored shadow root needs the `style` CSS strategy or an
133
+ equivalent rule in that root's stylesheet because document styles cannot cross
134
+ the boundary.
135
+
136
+ Import the optional coordinator entry once before component modules:
137
+
138
+ ```ts
139
+ import '@microsoft/webui-framework/lazy-hydration.js';
140
+ import './product-card.js';
141
+ ```
142
+
143
+ Use hydration-only deferral when rendering containment is not safe for a
144
+ component:
145
+
146
+ ```html
147
+ <template w-hydrate="lazy">
148
+ <!-- Component content -->
149
+ </template>
150
+ ```
151
+
152
+ Components are eager by default. Instance attributes provide explicit escape
153
+ hatches:
154
+
155
+ ```html
156
+ <!-- Keep rendering deferral, but hydrate this instance immediately. -->
157
+ <product-card w-hydrate="eager"></product-card>
158
+
159
+ <!-- Disable both rendering and hydration deferral for this instance. -->
160
+ <product-card w-render="eager"></product-card>
161
+ ```
162
+
163
+ Client-created instances and reconnects after a successful mount remain eager.
164
+ If the optional entry or `IntersectionObserver` is unavailable, hydration falls
165
+ back to eager; `content-visibility` remains browser-managed. See
166
+ [Lazy Hydration](https://microsoft.github.io/webui/guide/concepts/hydration#lazy-hydration).
167
+
111
168
  ### Build with the WebUI plugin
112
169
 
113
170
  ```bash
@@ -184,17 +241,17 @@ development-only and is dead-code-eliminated from production bundles via the
184
241
  Override the protected `hydratedCallback()` hook for work that requires the
185
242
  component's bindings, events, and `w-ref` references to be ready. It runs
186
243
  synchronously exactly once after the first successful ordinary SSR hydration,
187
- client-created mount, deferred streamed activation, or dormant static-host wake.
188
- Its once-latch is set before author code runs, so a thrown callback is not
189
- retried on reconnect.
244
+ client-created mount, lazy activation, deferred streamed activation, or dormant
245
+ static-host wake. Its once-latch is set before author code runs, so a thrown
246
+ callback is not retried on reconnect.
190
247
 
191
248
  `connectedCallback()` remains a native per-connection lifecycle. On ordinary
192
249
  SSR and client-created mounts, `super.connectedCallback()` hydrates
193
- synchronously, but a streamed `data-ws` root returns while still deferred and
194
- hydrates only when its boundary commits. Therefore `connectedCallback()` cannot
195
- be used as a universal post-hydration signal. Descendants must not structurally
196
- mutate a containing component's SSR subtree before it hydrates, because
197
- hydration relies on stable compiled paths.
250
+ synchronously, but a lazy root or streamed `data-ws` root can return while still
251
+ deferred. Therefore `connectedCallback()` cannot be used as a universal
252
+ post-hydration signal. Descendants must not structurally mutate a containing
253
+ component's SSR subtree before it hydrates, because hydration relies on stable
254
+ compiled paths.
198
255
 
199
256
  ### DOM strategy (`--dom`)
200
257
 
@@ -405,7 +462,7 @@ resource-constrained devices.
405
462
 
406
463
  5. **Single-pass hydration via path mapping.**
407
464
  SSR DOM is matched to compiled template bindings through
408
- template-parallel traversal (`$resolveSSR`). Ordinary buffered hydration
465
+ the lockstep hydration walk (`buildSSRIndex`). Ordinary buffered hydration
409
466
  needs no marker comments or data attributes for binding resolution. The
410
467
  hydration walk touches each DOM node exactly once.
411
468
 
@@ -469,7 +526,7 @@ Angular all require a JavaScript runtime on the server. This framework's SSR
469
526
  is driven by data (template metadata + state values), not code. Any language
470
527
  that can read the compiled metadata and produce HTML can serve as the SSR
471
528
  backend. No comment markers or data attributes are needed — the runtime
472
- resolves ordinary buffered SSR nodes via template-parallel path traversal.
529
+ resolves ordinary buffered SSR nodes via the lockstep hydration walk.
473
530
  Progressive streaming uses temporary checkpoint scaffolding only to delay
474
531
  activation until a complete region arrives; it removes that scaffolding after
475
532
  commit.
@@ -505,7 +562,7 @@ flowchart LR
505
562
 
506
563
  ```mermaid
507
564
  graph TD
508
- EL["element.ts (~850 lines)<br/><i>Orchestrator</i><br/>$mount, $wire, $hydrate,<br/>$resolveSSR, $applySSRState,<br/>$update, events, cleanup"]
565
+ EL["element.ts (~850 lines)<br/><i>Orchestrator</i><br/>$mount, $wire, $hydrate,<br/>buildSSRIndex, $applySSRState,<br/>$update, events, cleanup"]
509
566
 
510
567
  DIFF["element/diff.ts<br/><i>List Reconciliation</i><br/>positional + explicit-key diffing<br/>for &lt;for&gt; repeat blocks"]
511
568
 
@@ -556,7 +613,7 @@ sequenceDiagram
556
613
  FW->>FW: SSR DOM detected (shadow root or children exist)
557
614
  FW->>FW: $applySSRState() — seed decorated state
558
615
  FW->>FW: $hydrate() — template-parallel path resolution
559
- FW->>FW: $resolveSSR() — match SSR nodes via ordinal traversal
616
+ FW->>FW: buildSSRIndex() — number SSR nodes in one pre-order walk
560
617
  FW->>FW: $wireEvents() + $wireRefs()
561
618
  FW->>FW: $buildPathIndex(), $ready = true
562
619
  FW->>CE: hydratedCallback() (once)
@@ -605,7 +662,7 @@ interface TemplateMeta {
605
662
  ag?: [path, start, count][]; // Attribute target groups
606
663
  c?: [conditionAST, blockIndex, slot][]; // Conditional blocks
607
664
  r?: [collection, itemVar, blockIdx, slot][]; // Repeat blocks
608
- eg?: [event, [[handler, argSpecs, targetPath, usesEvent?]]][]; // Events
665
+ eg?: [event, [[handler, argSpecs, targetIndex, usesEvent?]]][]; // Events
609
666
  b?: TemplateBlockMeta[]; // Nested block metadata
610
667
  sa?: string; // Adopted stylesheet specifier
611
668
  sd?: 1; // Shadow DOM flag for client-created
@@ -760,7 +817,7 @@ valid identity is re-established.
760
817
 
761
818
  On initial hydration, the repeat system walks existing SSR children and
762
819
  reconstructs collection instances by matching them against the compiled
763
- template via `$resolveSSR` path traversal. State is already seeded from
820
+ template via the `buildSSRIndex` walk. State is already seeded from
764
821
  `window.__webui.state`, so repeat items reflect the server-rendered list
765
822
  without parsing marker comments.
766
823
 
@@ -785,40 +842,44 @@ stylesheet specifier for a component.
785
842
  ## Path-Based Binding Resolution
786
843
 
787
844
  Unlike frameworks that use comment markers or data attributes to locate each
788
- dynamic binding, this framework uses **compiled template paths** — arrays of
789
- child-node indices that describe exactly where each binding lives in the DOM
790
- tree. Progressive streaming's temporary boundary markers locate complete
845
+ dynamic binding, this framework uses **compiled element indices** — each
846
+ binding names its element by pre-order position within its compiled section. Progressive streaming's temporary boundary markers locate complete
791
847
  activation regions, not individual bindings.
792
848
 
793
- ### Client-created resolution (`$resolve`)
849
+ ### Client-created resolution (`collectTemplateElements`)
794
850
 
795
- For client-created components, the DOM matches `meta.h` exactly (it was cloned
796
- from the parsed template fragment). Resolution is a simple child-node index
797
- walk:
851
+ For client-created components the DOM matches `meta.h` exactly (it was cloned
852
+ from the parsed template fragment), so a plain pre-order walk reproduces the
853
+ compiled numbering:
798
854
 
799
855
  ```typescript
800
- // path = [1, 0] → root.childNodes[1].childNodes[0]
801
- let cur: Node = root;
802
- for (const idx of path) {
803
- cur = cur.childNodes[idx];
804
- }
856
+ // elements[0] is the section root; elements[i] is the i-th element
857
+ // a depth-first walk of the template meets.
858
+ const elements = collectTemplateElements(root);
859
+ const target = elements[index];
805
860
  ```
806
861
 
807
- ### SSR resolution (`$resolveSSR`)
862
+ ### SSR resolution (`buildSSRIndex`)
808
863
 
809
- SSR DOM may differ from the compiled template — the browser's HTML parser can
810
- strip whitespace-only text nodes. `$resolveSSR` walks the SSR DOM and the
811
- compiled template DOM **in parallel**, translating each child-node index into
812
- an element-ordinal or text-ordinal lookup:
864
+ SSR DOM differs from the compiled template: the renderer strips inter-element
865
+ whitespace, and `<if>` / `<for>` bodies are rendered inline between markers.
866
+ `buildSSRIndex` walks the SSR DOM and the compiled template DOM **in lockstep**,
867
+ skipping whole marker ranges, and numbers the result the same way:
813
868
 
814
869
  ```typescript
815
- // For element nodes: count element siblings up to idx in template,
816
- // then find the element at that ordinal in SSR DOM.
817
- // For text nodes: same approach with text node ordinals.
870
+ // Elements are paired positionally and numbered in pre-order.
871
+ // Structural ranges are skipped whole — that content belongs to
872
+ // the block's own metadata.
873
+ // Text is the exception: whitespace stripping means text nodes do
874
+ // not line up, so text slots still resolve by ordinal.
818
875
  ```
819
876
 
820
- This template-parallel traversal eliminates the need for any marker comments,
821
- `data-*` attributes, or DOM annotations. The SSR server emits clean HTML.
877
+ This eliminates all *per-binding* annotation: no `data-w-*` attributes, no
878
+ comment per text run, no DOM markers around individual bindings. What the SSR
879
+ server does emit is the five structural comments documented above
880
+ (`<!--wr-->`, `<!--wi-->`, `<!--/wr-->`, `<!--wc-->`, `<!--/wc-->`), which
881
+ delimit `<if>` / `<for>` bodies and are what the walk skips over and anchors
882
+ blocks on. They are removed once hydration completes.
822
883
 
823
884
  ---
824
885
 
@@ -826,7 +887,7 @@ This template-parallel traversal eliminates the need for any marker comments,
826
887
 
827
888
  | Operation | Cost | Why |
828
889
  |-----------|------|-----|
829
- | Initial hydration | O(bindings) | Single pass over compiled path mappings |
890
+ | Initial hydration | O(nodes) | One pre-order walk numbers the subtree; each binding is then an index lookup |
830
891
  | Reactive update | O(affected) | Per-path index skips unrelated bindings |
831
892
  | Conditional toggle | O(block size) | Create/destroy a block instance |
832
893
  | Repeat reconciliation | O(items) | Positional scan; explicit keys use a reusable map only when order changes |
@@ -852,10 +913,15 @@ The runtime exposes hydration timing via the Performance API:
852
913
 
853
914
  ```ts
854
915
  window.addEventListener('webui:hydration-complete', () => {
855
- console.log('All initial framework components are hydrated.');
916
+ console.log('The startup hydration cohort is complete.');
856
917
  });
857
918
  ```
858
919
 
920
+ Parser-startup lazy components hold this event only through their first
921
+ intersection result. Initially visible roots finish first; dormant roots do not
922
+ hold the one-shot event open or redispatch it later. Use `hydratedCallback()`
923
+ for instance readiness.
924
+
859
925
  ---
860
926
 
861
927
  ## Where to Look Next
@@ -163,8 +163,14 @@ function createReactiveProperty(proto, name, attrDefinition) {
163
163
  },
164
164
  set(newValue) {
165
165
  const oldValue = this[backingKey];
166
- if (Object.is(oldValue, newValue))
166
+ if (Object.is(oldValue, newValue)) {
167
+ if (this['$deferredSSR'] === true) {
168
+ const update = this['$update'];
169
+ if (update)
170
+ update.call(this, name);
171
+ }
167
172
  return;
173
+ }
168
174
  this[backingKey] = newValue;
169
175
  if (attrDefinition && this['$ready'] === true) {
170
176
  reflectPropertyToAttribute(this, attrDefinition, newValue);
@@ -173,7 +179,8 @@ function createReactiveProperty(proto, name, attrDefinition) {
173
179
  if (typeof cb === 'function') {
174
180
  cb.call(this, oldValue, newValue);
175
181
  }
176
- if (this.isConnected) {
182
+ if (this.isConnected ||
183
+ this['$deferredSSR'] === true) {
177
184
  const upd = this['$update'];
178
185
  if (upd)
179
186
  upd.call(this, name);
@@ -7,4 +7,12 @@ export declare function collectItemMarkers(repeatStart: Comment): {
7
7
  end: Comment | null;
8
8
  };
9
9
  export declare function nextElement(marker: Comment): Element | null;
10
+ export declare function skipBlockRange(start: Comment, data: string): ChildNode | null;
11
+ export declare function collectTemplateElements(root: Node): Array<Node | undefined>;
12
+ export interface SSRIndex {
13
+ elements: Array<Node | undefined>;
14
+ conds: Comment[];
15
+ repeats: Comment[];
16
+ }
17
+ export declare function buildSSRIndex(tplRoot: Node, ssrRoot: Node, needMarkers: boolean, ssrIsSectionChild?: boolean): SSRIndex;
10
18
  export declare function findByOrdinal(parent: Node, nodeType: number, ordinal: number): Node | null;
@@ -43,6 +43,100 @@ export function nextElement(marker) {
43
43
  }
44
44
  return null;
45
45
  }
46
+ export function skipBlockRange(start, data) {
47
+ const endTag = data === MARKER_COND_START ? MARKER_COND_END : MARKER_REPEAT_END;
48
+ let depth = 1;
49
+ let node = start.nextSibling;
50
+ while (node && depth > 0) {
51
+ if (node.nodeType === 8) {
52
+ const d = node.data;
53
+ if (d === data)
54
+ depth++;
55
+ else if (d === endTag)
56
+ depth--;
57
+ }
58
+ if (depth > 0)
59
+ node = node.nextSibling;
60
+ }
61
+ return node ? node.nextSibling : null;
62
+ }
63
+ export function collectTemplateElements(root) {
64
+ const elements = [root];
65
+ const stack = [root.firstChild];
66
+ while (stack.length > 0) {
67
+ const node = stack.pop();
68
+ let child = node ?? null;
69
+ while (child) {
70
+ if (child.nodeType === 1) {
71
+ elements.push(child);
72
+ if (child.firstChild) {
73
+ stack.push(child.nextSibling);
74
+ child = child.firstChild;
75
+ continue;
76
+ }
77
+ }
78
+ child = child.nextSibling;
79
+ }
80
+ }
81
+ return elements;
82
+ }
83
+ export function buildSSRIndex(tplRoot, ssrRoot, needMarkers, ssrIsSectionChild = false) {
84
+ const elements = [ssrIsSectionChild ? undefined : ssrRoot];
85
+ const conds = [];
86
+ const repeats = [];
87
+ const stack = [
88
+ {
89
+ t: tplRoot.firstChild,
90
+ s: ssrIsSectionChild ? ssrRoot : ssrRoot.firstChild,
91
+ solo: ssrIsSectionChild,
92
+ },
93
+ ];
94
+ let index = 0;
95
+ while (stack.length > 0) {
96
+ const frame = stack[stack.length - 1];
97
+ let s = frame.s;
98
+ while (s) {
99
+ const type = s.nodeType;
100
+ if (type === 8) {
101
+ const data = s.data;
102
+ if (data === MARKER_COND_START) {
103
+ conds.push(s);
104
+ s = skipBlockRange(s, data);
105
+ continue;
106
+ }
107
+ if (data === MARKER_REPEAT_START) {
108
+ repeats.push(s);
109
+ s = skipBlockRange(s, data);
110
+ continue;
111
+ }
112
+ }
113
+ else if (type === 1) {
114
+ break;
115
+ }
116
+ s = s.nextSibling;
117
+ }
118
+ let t = frame.t;
119
+ while (t && t.nodeType !== 1)
120
+ t = t.nextSibling;
121
+ if (!t) {
122
+ stack.pop();
123
+ continue;
124
+ }
125
+ index++;
126
+ if (s) {
127
+ elements[index] = s;
128
+ frame.s = frame.solo ? null : s.nextSibling;
129
+ }
130
+ frame.t = t.nextSibling;
131
+ if (t.firstChild) {
132
+ stack.push({ t: t.firstChild, s: s ? s.firstChild : null, solo: false });
133
+ }
134
+ else if (needMarkers && s && s.firstChild && t.tagName.indexOf('-') < 0) {
135
+ stack.push({ t: null, s: s.firstChild, solo: false });
136
+ }
137
+ }
138
+ return { elements, conds, repeats };
139
+ }
46
140
  export function findByOrdinal(parent, nodeType, ordinal) {
47
141
  let count = 0;
48
142
  let child = parent.firstChild;
@@ -50,22 +144,7 @@ export function findByOrdinal(parent, nodeType, ordinal) {
50
144
  if (child.nodeType === 8) {
51
145
  const data = child.data;
52
146
  if (data === MARKER_COND_START || data === MARKER_REPEAT_START) {
53
- const endTag = data === MARKER_COND_START ? MARKER_COND_END : MARKER_REPEAT_END;
54
- let depth = 1;
55
- child = child.nextSibling;
56
- while (child && depth > 0) {
57
- if (child.nodeType === 8) {
58
- const d = child.data;
59
- if (d === data)
60
- depth++;
61
- else if (d === endTag)
62
- depth--;
63
- }
64
- if (depth > 0)
65
- child = child.nextSibling;
66
- }
67
- if (child)
68
- child = child.nextSibling;
147
+ child = skipBlockRange(child, data);
69
148
  continue;
70
149
  }
71
150
  }
package/dist/element.d.ts CHANGED
@@ -1,12 +1,21 @@
1
1
  import { TemplateElement } from './template-element.js';
2
- import type { TemplateBlockMeta, TemplateNodePath } from './template.js';
2
+ import { LAZY_HYDRATION_ACTIVATE } from './lazy-hydration-contract.js';
3
+ import type { TemplateBlockMeta, TemplateMeta, TemplateNodeIndex } from './template.js';
3
4
  import type { ScopeFrame, TemplateInstance } from './element/types.js';
4
5
  export declare class WebUIElement extends TemplateElement {
6
+ private $lazyHydrationMode;
7
+ protected $shouldDeferSSRHydration(meta?: TemplateMeta): boolean;
8
+ protected $didDeferSSRHydration(): void;
9
+ protected $didDeferStreamedSSRHydration(state: Record<string, unknown> | undefined): void;
10
+ protected $activateDeferredSSR(state?: Record<string, unknown>): void;
11
+ private $resolveLazyHydrationMode;
12
+ [LAZY_HYDRATION_ACTIVATE](state: Record<string, unknown> | undefined): void;
13
+ disconnectedCallback(): void;
5
14
  protected $observableNames(): Set<string>;
6
15
  protected $shouldApplySSRState(key: string): boolean;
7
16
  protected $syncAuthoredAttributes(): void;
8
17
  $emit(name: string, detail?: unknown): boolean;
9
- protected $finalize(instance: TemplateInstance, root: Node, meta: TemplateBlockMeta, resolver: (root: Node, path: TemplateNodePath) => Node | null, scope?: ScopeFrame): void;
18
+ protected $finalize(instance: TemplateInstance, root: Node, meta: TemplateBlockMeta, resolver: (root: Node, path: TemplateNodeIndex) => Node | null, scope?: ScopeFrame): void;
10
19
  private $wireEvents;
11
20
  private $wireRoot;
12
21
  private $addEvent;
package/dist/element.js CHANGED
@@ -1,6 +1,92 @@
1
1
  import { TemplateElement } from './template-element.js';
2
+ import { disconnectLazyHydration, isStreamedLazyActivation, isLazyHydrationCoordinatorInstalled, LAZY_HYDRATION_ACTIVATE, LAZY_HYDRATION_CONTENT_VISIBILITY, LAZY_HYDRATION_VIEWPORT, observeLazyHydration, observeStreamedLazyHydration, supportsLazyHydration, } from './lazy-hydration-contract.js';
3
+ import { isStreamingHydrationMode, STREAMED_HOST_ATTR, } from './streaming-mode.js';
2
4
  import { attributeNameForProperty, getObservableNames, syncAttrProperties, } from './decorators.js';
5
+ const HYDRATE_ATTR = 'w-hydrate';
6
+ const RENDER_ATTR = 'w-render';
7
+ const HYDRATE_EAGER = 'eager';
8
+ const DEV = typeof __WEBUI_DEV__ === 'undefined' || __WEBUI_DEV__;
9
+ let warnedMissingLazyHydrationEntry = false;
10
+ function warnMissingLazyHydrationEntry(tag, policy) {
11
+ if (!DEV || warnedMissingLazyHydrationEntry)
12
+ return;
13
+ warnedMissingLazyHydrationEntry = true;
14
+ const directive = policy === LAZY_HYDRATION_CONTENT_VISIBILITY
15
+ ? 'w-render="lazy"'
16
+ : 'w-hydrate="lazy"';
17
+ console.warn(`[WebUI] <${tag}> was compiled with \`${directive}\`, but ` +
18
+ "'@microsoft/webui-framework/lazy-hydration.js' was never imported, " +
19
+ 'so it hydrated eagerly instead. Import that optional entry once before ' +
20
+ 'your component definitions to enable visibility-deferred hydration.');
21
+ }
3
22
  export class WebUIElement extends TemplateElement {
23
+ $shouldDeferSSRHydration(meta) {
24
+ this.$lazyHydrationMode = this.$resolveLazyHydrationMode(meta);
25
+ return this.$lazyHydrationMode !== undefined;
26
+ }
27
+ $didDeferSSRHydration() {
28
+ if (!isStreamingHydrationMode() ||
29
+ !this.hasAttribute(STREAMED_HOST_ATTR)) {
30
+ this.$primeSSRStateForDeferral();
31
+ const mode = this.$lazyHydrationMode;
32
+ if (mode !== undefined)
33
+ observeLazyHydration(this, mode);
34
+ }
35
+ }
36
+ $didDeferStreamedSSRHydration(state) {
37
+ const mode = this.$lazyHydrationMode;
38
+ if (mode === undefined) {
39
+ super.$didDeferStreamedSSRHydration(state);
40
+ return;
41
+ }
42
+ observeStreamedLazyHydration(this, state, mode);
43
+ }
44
+ $activateDeferredSSR(state) {
45
+ if (isStreamedLazyActivation(this)) {
46
+ this.$lazyHydrationMode = undefined;
47
+ super.$activateDeferredSSRFromBoundary(state);
48
+ return;
49
+ }
50
+ const mode = this.$lazyHydrationMode ??
51
+ this.$resolveLazyHydrationMode(this.$currentTemplateMetadata());
52
+ if (isStreamingHydrationMode() &&
53
+ this.hasAttribute(STREAMED_HOST_ATTR) &&
54
+ mode !== undefined) {
55
+ this.$lazyHydrationMode = mode;
56
+ observeStreamedLazyHydration(this, state, mode);
57
+ return;
58
+ }
59
+ this.$lazyHydrationMode = undefined;
60
+ super.$activateDeferredSSR(state);
61
+ }
62
+ $resolveLazyHydrationMode(meta) {
63
+ const policy = meta?.wp;
64
+ if (policy === undefined)
65
+ return undefined;
66
+ if (policy === LAZY_HYDRATION_CONTENT_VISIBILITY &&
67
+ this.getAttribute(RENDER_ATTR) === HYDRATE_EAGER)
68
+ return undefined;
69
+ if (this.getAttribute(HYDRATE_ATTR) === HYDRATE_EAGER)
70
+ return undefined;
71
+ if (!isLazyHydrationCoordinatorInstalled()) {
72
+ warnMissingLazyHydrationEntry(this.tagName, policy);
73
+ return undefined;
74
+ }
75
+ if (!supportsLazyHydration())
76
+ return undefined;
77
+ return policy === LAZY_HYDRATION_CONTENT_VISIBILITY
78
+ ? LAZY_HYDRATION_CONTENT_VISIBILITY
79
+ : LAZY_HYDRATION_VIEWPORT;
80
+ }
81
+ [LAZY_HYDRATION_ACTIVATE](state) {
82
+ this.$activateDeferredSSR(state);
83
+ }
84
+ disconnectedCallback() {
85
+ if (this.$lazyHydrationMode !== undefined) {
86
+ disconnectLazyHydration(this);
87
+ }
88
+ super.disconnectedCallback();
89
+ }
4
90
  $observableNames() {
5
91
  return getObservableNames(this.constructor);
6
92
  }
@@ -41,9 +127,8 @@ export class WebUIElement extends TemplateElement {
41
127
  }
42
128
  }
43
129
  $wireRoot(instance, re) {
44
- const target = this.shadowRoot ?? this;
45
130
  for (let i = 0; i < re.length; i++) {
46
- this.$addEvent(instance, target, re[i][0], re[i][1], re[i][2], undefined);
131
+ this.$addEvent(instance, this, re[i][0], re[i][1], re[i][2], undefined);
47
132
  }
48
133
  }
49
134
  $addEvent(instance, target, eventName, handlerName, args, scope) {
@@ -0,0 +1,22 @@
1
+ export declare const LAZY_HYDRATION_ACTIVATE: unique symbol;
2
+ export declare const LAZY_HYDRATION_VIEWPORT = 1;
3
+ export declare const LAZY_HYDRATION_CONTENT_VISIBILITY = 2;
4
+ export type LazyHydrationMode = typeof LAZY_HYDRATION_VIEWPORT | typeof LAZY_HYDRATION_CONTENT_VISIBILITY;
5
+ export type LazyHydrationTarget = HTMLElement & {
6
+ [LAZY_HYDRATION_ACTIVATE](state: Record<string, unknown> | undefined): void;
7
+ };
8
+ export interface LazyHydrationCoordinator {
9
+ supportsLazyHydration(): boolean;
10
+ observe(target: LazyHydrationTarget, mode: LazyHydrationMode): void;
11
+ observeStreamed(target: LazyHydrationTarget, state: Record<string, unknown> | undefined, mode: LazyHydrationMode): void;
12
+ disconnect(target: LazyHydrationTarget): void;
13
+ isStreamedActivation(target: LazyHydrationTarget): boolean;
14
+ }
15
+ export declare function registerLazyHydrationCoordinator(impl: LazyHydrationCoordinator): void;
16
+ export declare function isLazyHydrationCoordinatorInstalled(): boolean;
17
+ export declare function supportsLazyHydration(): boolean;
18
+ export declare function observeLazyHydration(target: LazyHydrationTarget, mode: LazyHydrationMode): void;
19
+ export declare function observeStreamedLazyHydration(target: LazyHydrationTarget, state: Record<string, unknown> | undefined, mode: LazyHydrationMode): void;
20
+ export declare function disconnectLazyHydration(target: LazyHydrationTarget): void;
21
+ export declare function isStreamedLazyActivation(target: LazyHydrationTarget): boolean;
22
+ export declare function __resetLazyHydrationContractForTests(): void;
@@ -0,0 +1,28 @@
1
+ export const LAZY_HYDRATION_ACTIVATE = Symbol();
2
+ export const LAZY_HYDRATION_VIEWPORT = 1;
3
+ export const LAZY_HYDRATION_CONTENT_VISIBILITY = 2;
4
+ let coordinator;
5
+ export function registerLazyHydrationCoordinator(impl) {
6
+ coordinator = impl;
7
+ }
8
+ export function isLazyHydrationCoordinatorInstalled() {
9
+ return coordinator !== undefined;
10
+ }
11
+ export function supportsLazyHydration() {
12
+ return coordinator?.supportsLazyHydration() ?? false;
13
+ }
14
+ export function observeLazyHydration(target, mode) {
15
+ coordinator?.observe(target, mode);
16
+ }
17
+ export function observeStreamedLazyHydration(target, state, mode) {
18
+ coordinator?.observeStreamed(target, state, mode);
19
+ }
20
+ export function disconnectLazyHydration(target) {
21
+ coordinator?.disconnect(target);
22
+ }
23
+ export function isStreamedLazyActivation(target) {
24
+ return coordinator?.isStreamedActivation(target) ?? false;
25
+ }
26
+ export function __resetLazyHydrationContractForTests() {
27
+ coordinator = undefined;
28
+ }
@@ -0,0 +1,2 @@
1
+ export declare function installLazyHydrationCoordinator(): void;
2
+ export declare function __resetLazyHydrationCoordinatorForTests(): void;