@microsoft/webui-framework 0.0.17 → 0.0.18

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.
@@ -0,0 +1,22 @@
1
+ import type { CompiledAttrPart } from './template.js';
2
+ import { type AttrBinding, type CondBinding, type RepeatBinding, type ScopeFrame, type TextBinding } from './element/types.js';
3
+ export interface MismatchContext {
4
+ resolver: (path: string, scope?: unknown) => unknown;
5
+ resolveParts: (parts: CompiledAttrPart[], scope?: ScopeFrame) => string;
6
+ resolveValue: (path: string, scope?: ScopeFrame) => unknown;
7
+ }
8
+ export interface PathBindings {
9
+ texts: TextBinding[];
10
+ attrs: AttrBinding[];
11
+ conds: CondBinding[];
12
+ repeats: RepeatBinding[];
13
+ }
14
+ export declare function reportHydrationMismatch(tag: string, writes: ReadonlySet<string>, index: ReadonlyMap<string, PathBindings>, ctx: MismatchContext): void;
15
+ export declare function bindingsDisagreeWithDom(entry: PathBindings, ctx: MismatchContext): boolean;
16
+ export declare function textDiffersFromDom(b: TextBinding, ctx: MismatchContext): boolean;
17
+ export declare function attrDiffersFromDom(b: AttrBinding, ctx: MismatchContext): boolean;
18
+ export declare function condDiffersFromDom(c: CondBinding, ctx: MismatchContext): boolean;
19
+ export declare function repeatDiffersFromDom(r: RepeatBinding, ctx: MismatchContext): boolean;
20
+ export declare function formatHydrationMismatch(tag: string, paths: string[]): string;
21
+ export declare function warnHydrationMismatch(tag: string, paths: string[]): void;
22
+ export declare function resetHydrationMismatchWarnings(): void;
@@ -0,0 +1,87 @@
1
+ import { ATTR_KIND_BOOLEAN, ATTR_KIND_COMPLEX, ATTR_KIND_TEMPLATE, } from './element/types.js';
2
+ export function reportHydrationMismatch(tag, writes, index, ctx) {
3
+ const mismatched = [];
4
+ for (const path of writes) {
5
+ const entry = index.get(path);
6
+ if (entry && bindingsDisagreeWithDom(entry, ctx))
7
+ mismatched.push(path);
8
+ }
9
+ if (mismatched.length !== 0)
10
+ warnHydrationMismatch(tag, mismatched);
11
+ }
12
+ export function bindingsDisagreeWithDom(entry, ctx) {
13
+ const { texts, attrs, conds, repeats } = entry;
14
+ for (let i = 0; i < texts.length; i++)
15
+ if (textDiffersFromDom(texts[i], ctx))
16
+ return true;
17
+ for (let i = 0; i < attrs.length; i++)
18
+ if (attrDiffersFromDom(attrs[i], ctx))
19
+ return true;
20
+ for (let i = 0; i < conds.length; i++)
21
+ if (condDiffersFromDom(conds[i], ctx))
22
+ return true;
23
+ for (let i = 0; i < repeats.length; i++)
24
+ if (repeatDiffersFromDom(repeats[i], ctx))
25
+ return true;
26
+ return false;
27
+ }
28
+ export function textDiffersFromDom(b, ctx) {
29
+ if (b.raw)
30
+ return false;
31
+ let expected;
32
+ if (b.parts) {
33
+ expected = ctx.resolveParts(b.parts, b.scope);
34
+ }
35
+ else if (b.path) {
36
+ const raw = ctx.resolveValue(b.path, b.scope);
37
+ expected = raw == null ? '' : String(raw);
38
+ }
39
+ else {
40
+ return false;
41
+ }
42
+ return b.node.data !== expected;
43
+ }
44
+ export function attrDiffersFromDom(b, ctx) {
45
+ const el = b.element;
46
+ switch (b.kind) {
47
+ case ATTR_KIND_COMPLEX:
48
+ return false;
49
+ case ATTR_KIND_BOOLEAN:
50
+ return el.hasAttribute(b.name) !== !!b.condition[0](ctx.resolver, b.scope);
51
+ case ATTR_KIND_TEMPLATE:
52
+ return (el.getAttribute(b.name) ?? '') !== ctx.resolveParts(b.parts, b.scope);
53
+ default: {
54
+ if (b.name === 'value' || b.name === 'checked' || b.name === 'selected')
55
+ return false;
56
+ const v = ctx.resolveValue(b.path, b.scope);
57
+ return (el.getAttribute(b.name) ?? '') !== (v == null ? '' : String(v));
58
+ }
59
+ }
60
+ }
61
+ export function condDiffersFromDom(c, ctx) {
62
+ return (c.instance != null) !== !!c.condition[0](ctx.resolver, c.scope);
63
+ }
64
+ export function repeatDiffersFromDom(r, ctx) {
65
+ const resolved = ctx.resolveValue(r.collection, r.scope);
66
+ const expectedLength = Array.isArray(resolved) ? resolved.length : 0;
67
+ return expectedLength !== r.instances.length;
68
+ }
69
+ export function formatHydrationMismatch(tag, paths) {
70
+ const list = paths.map((p) => `"${p}"`).join(', ');
71
+ return (`[WebUI] Hydration mismatch on <${tag}>: ` +
72
+ `${list} changed at or before super.connectedCallback() to a value that ` +
73
+ `differs from the server-rendered DOM. The DOM keeps the server value ` +
74
+ `(SSR content is trusted), so the element's state and DOM disagree. ` +
75
+ `Add the value to the SSR state, or assign it after super.connectedCallback().`);
76
+ }
77
+ const warnedMismatches = new Set();
78
+ export function warnHydrationMismatch(tag, paths) {
79
+ const key = `${tag}\u0000${[...paths].sort().join('\u0000')}`;
80
+ if (warnedMismatches.has(key))
81
+ return;
82
+ warnedMismatches.add(key);
83
+ console.warn(formatHydrationMismatch(tag, paths));
84
+ }
85
+ export function resetHydrationMismatchWarnings() {
86
+ warnedMismatches.clear();
87
+ }
package/dist/index.d.ts CHANGED
@@ -1,22 +1,3 @@
1
- /**
2
- * @microsoft/webui-framework — lightweight Web Component runtime with SSR hydration.
3
- *
4
- * Provides a reactive base class, decorators, and hydration utilities for
5
- * building Web Components that work with WebUI's server-side rendering pipeline.
6
- *
7
- * @example
8
- * ```ts
9
- * import { WebUIElement, observable, attr } from '@microsoft/webui-framework';
10
- *
11
- * class MyCounter extends WebUIElement {
12
- * @attr count = 0;
13
- * @observable label = 'Count';
14
- * }
15
- * MyCounter.define('my-counter');
16
- * ```
17
- *
18
- * @packageDocumentation
19
- */
20
1
  export { WebUIElement } from './element.js';
21
2
  export { observable, attr } from './decorators.js';
22
3
  export { getTemplate, registerTemplateData } from './template.js';
package/dist/index.js CHANGED
@@ -1,24 +1,5 @@
1
- // Copyright (c) Microsoft Corporation.
2
- // Licensed under the MIT license.
3
- /**
4
- * @microsoft/webui-framework — lightweight Web Component runtime with SSR hydration.
5
- *
6
- * Provides a reactive base class, decorators, and hydration utilities for
7
- * building Web Components that work with WebUI's server-side rendering pipeline.
8
- *
9
- * @example
10
- * ```ts
11
- * import { WebUIElement, observable, attr } from '@microsoft/webui-framework';
12
- *
13
- * class MyCounter extends WebUIElement {
14
- * @attr count = 0;
15
- * @observable label = 'Count';
16
- * }
17
- * MyCounter.define('my-counter');
18
- * ```
19
- *
20
- * @packageDocumentation
21
- */
1
+ import { installTemplateElementRuntime } from './static-host.js';
2
+ installTemplateElementRuntime();
22
3
  export { WebUIElement } from './element.js';
23
4
  export { observable, attr } from './decorators.js';
24
5
  export { getTemplate, registerTemplateData } from './template.js';
@@ -1,10 +1,2 @@
1
- /**
2
- * Call before a component begins hydration.
3
- * Increments the pending counter and (once) places the global start mark.
4
- */
5
1
  export declare function hydrationStart(): void;
6
- /**
7
- * Call after a component has finished hydration.
8
- * When the last component finishes, fires the global event + measure.
9
- */
10
2
  export declare function hydrationEnd(): void;
package/dist/lifecycle.js CHANGED
@@ -1,34 +1,6 @@
1
- // Copyright (c) Microsoft Corporation.
2
- // Licensed under the MIT license.
3
- /**
4
- * Hydration lifecycle tracker.
5
- *
6
- * Tracks aggregate hydration timing via the Performance API and fires a
7
- * global `webui:hydration-complete` event on `window` once every registered
8
- * component has finished hydrating.
9
- *
10
- * ## Performance marks
11
- *
12
- * Global:
13
- * - `webui:hydrate:total:start` — first component begins hydrating
14
- * - `webui:hydrate:total:end` — last component finishes
15
- * - measure `webui:hydrate:total`
16
- *
17
- * ## Window event
18
- *
19
- * `webui:hydration-complete` — dispatched once on `window` when all
20
- * components are hydrated.
21
- */
22
- /** How many components are still waiting to hydrate. */
23
1
  let pendingCount = 0;
24
- /** Whether the global start mark has been placed. */
25
2
  let started = false;
26
- /** Whether the global complete event has already fired. */
27
3
  let completed = false;
28
- /**
29
- * Call before a component begins hydration.
30
- * Increments the pending counter and (once) places the global start mark.
31
- */
32
4
  export function hydrationStart() {
33
5
  if (!started) {
34
6
  performance.mark('webui:hydrate:total:start');
@@ -36,10 +8,6 @@ export function hydrationStart() {
36
8
  }
37
9
  pendingCount++;
38
10
  }
39
- /**
40
- * Call after a component has finished hydration.
41
- * When the last component finishes, fires the global event + measure.
42
- */
43
11
  export function hydrationEnd() {
44
12
  pendingCount--;
45
13
  if (pendingCount <= 0 && !completed) {
@@ -0,0 +1 @@
1
+ export declare function installTemplateElementRuntime(): void;
@@ -0,0 +1,58 @@
1
+ import { TemplateElement } from './template-element.js';
2
+ import { getTemplateRegistry } from './template.js';
3
+ import { templateAttributeForRoot, templateNeedsStaticHost } from './template-roots.js';
4
+ import { TEMPLATES_REGISTERED_EVENT, templateRegistrationDetail, } from './template-events.js';
5
+ let runtimeInstalled = false;
6
+ function defineTemplateHost(tag, meta) {
7
+ const w = window;
8
+ if (!w.__webui)
9
+ w.__webui = {};
10
+ if (!w.__webui.templates)
11
+ w.__webui.templates = {};
12
+ if (!w.__webui.templates[tag])
13
+ w.__webui.templates[tag] = meta;
14
+ class StaticTemplateHost extends TemplateElement {
15
+ $shouldApplyTemplateStateFromSSR(key) {
16
+ const attr = templateAttributeForRoot(meta, key);
17
+ return attr === undefined || !this.hasAttribute(attr);
18
+ }
19
+ }
20
+ StaticTemplateHost.define(tag);
21
+ }
22
+ function defineMissingTemplateHost(tag, meta) {
23
+ if (!templateNeedsStaticHost(meta) || customElements.get(tag))
24
+ return;
25
+ defineTemplateHost(tag, meta);
26
+ }
27
+ function defineTemplateHosts(templates = getTemplateRegistry()) {
28
+ if (!templates)
29
+ return;
30
+ const tags = Object.keys(templates);
31
+ for (let i = 0; i < tags.length; i++) {
32
+ const tag = tags[i];
33
+ const meta = templates[tag];
34
+ if (meta)
35
+ defineMissingTemplateHost(tag, meta);
36
+ }
37
+ }
38
+ export function installTemplateElementRuntime() {
39
+ if (runtimeInstalled) {
40
+ defineTemplateHosts();
41
+ return;
42
+ }
43
+ if (typeof window === 'undefined' || typeof document === 'undefined')
44
+ return;
45
+ runtimeInstalled = true;
46
+ window.addEventListener(TEMPLATES_REGISTERED_EVENT, (event) => {
47
+ const detail = templateRegistrationDetail(event);
48
+ if (!detail)
49
+ return;
50
+ if (detail.templates)
51
+ defineTemplateHosts(detail.templates);
52
+ });
53
+ if (document.readyState === 'loading') {
54
+ document.addEventListener('DOMContentLoaded', () => defineTemplateHosts(), { once: true });
55
+ return;
56
+ }
57
+ defineTemplateHosts();
58
+ }
@@ -0,0 +1,76 @@
1
+ import type { TemplateBlockMeta, TemplateNodePath } from './template.js';
2
+ import type { ScopeFrame, TemplateInstance } from './element/types.js';
3
+ declare const WEBUI_SET_STATE_KEY: unique symbol;
4
+ export declare class TemplateElement extends HTMLElement {
5
+ private $root;
6
+ private $meta?;
7
+ private $ready;
8
+ private $hydrated;
9
+ private $templateState;
10
+ private $dirtyPaths;
11
+ private $pendingFlush;
12
+ private $preReadyWrites;
13
+ private $resolver;
14
+ private $pathIndex?;
15
+ private $wildcardBindings?;
16
+ [WEBUI_SET_STATE_KEY](key: string, value: unknown): boolean;
17
+ static define(tagName: string): void;
18
+ connectedCallback(): void;
19
+ private $mount;
20
+ disconnectedCallback(): void;
21
+ $destroy(): void;
22
+ private $teardown;
23
+ private $teardownInstanceCleanups;
24
+ attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
25
+ setState(state: Record<string, unknown>): void;
26
+ protected $observableNames(): Set<string>;
27
+ protected $shouldApplySSRState(_key: string): boolean;
28
+ protected $syncAuthoredAttributes(): void;
29
+ protected $shouldApplyTemplateStateFromSSR(_key: string): boolean;
30
+ protected $setTemplateState(key: string, value: unknown): void;
31
+ private $writeTemplateState;
32
+ private $templateMeta;
33
+ private $templateRootForAttribute;
34
+ private $usesTemplateState;
35
+ private $setStateKey;
36
+ private $applySSRState;
37
+ $update(path?: string): void;
38
+ $flushUpdates(): void;
39
+ private $flush;
40
+ private $recordPreReadyWrite;
41
+ private $checkHydrationMismatch;
42
+ private $resolve;
43
+ private $resolveSSR;
44
+ private $parseTemplate;
45
+ private $createStagingRoot;
46
+ private $appendStagedChildren;
47
+ private $releaseStagingRepeatContainers;
48
+ private $wire;
49
+ private $hydrate;
50
+ private $collectBetween;
51
+ private $hydrateCondContent;
52
+ private $findMarker;
53
+ private $hasContentAfterMarker;
54
+ private $findSSRText;
55
+ private $findSSRSlotRef;
56
+ private $rootTag;
57
+ private $wireAttrs;
58
+ protected $finalize(_instance: TemplateInstance, _root: Node, _meta: TemplateBlockMeta, _resolver: (root: Node, path: TemplateNodePath) => Node | null, _scope?: ScopeFrame): void;
59
+ private $makeAttr;
60
+ private $repeatMaps;
61
+ private $buildPathIndex;
62
+ private $updateBindings;
63
+ $updateInstance(instance: TemplateInstance): void;
64
+ private $patchText;
65
+ private $patchAttr;
66
+ private $toggleCond;
67
+ $resolveValue(path: string, scope?: ScopeFrame): unknown;
68
+ private $resolveComponentRoot;
69
+ private $resolveParts;
70
+ $block(blockIndex: number): TemplateBlockMeta | undefined;
71
+ $createBlockInstance(blockIndex: number, scope?: ScopeFrame): TemplateInstance | null;
72
+ $removeInstance(instance: TemplateInstance): void;
73
+ $insertInstanceAfter(cursor: Node | null, container: ParentNode & Node, instance: TemplateInstance): Node | null;
74
+ private $singleDynamic;
75
+ }
76
+ export {};