@microsoft/webui-framework 0.0.24 → 0.0.26

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 (53) hide show
  1. package/README.md +209 -53
  2. package/dist/component-asset/asset.d.ts +4 -2
  3. package/dist/component-asset/asset.js +19 -3
  4. package/dist/component-asset/generated-manifest.d.ts +1 -0
  5. package/dist/component-asset/generated-manifest.js +32 -0
  6. package/dist/component-asset/loader.d.ts +2 -1
  7. package/dist/component-asset/loader.js +98 -22
  8. package/dist/component-asset/manifest.d.ts +2 -1
  9. package/dist/component-asset.d.ts +1 -1
  10. package/dist/component-asset.js +12 -2
  11. package/dist/element/link-styles.d.ts +20 -0
  12. package/dist/element/link-styles.js +1030 -0
  13. package/dist/element/markers.d.ts +4 -0
  14. package/dist/element/markers.js +60 -5
  15. package/dist/element/styles.d.ts +39 -1
  16. package/dist/element/styles.js +623 -13
  17. package/dist/element/types.d.ts +5 -3
  18. package/dist/element.js +9 -1
  19. package/dist/index.d.ts +2 -0
  20. package/dist/index.js +1 -0
  21. package/dist/interaction-hydration.d.ts +8 -0
  22. package/dist/interaction-hydration.js +167 -0
  23. package/dist/static-host.js +43 -1
  24. package/dist/streaming-activation.d.ts +2 -2
  25. package/dist/streaming-activation.js +4 -2
  26. package/dist/streaming-bootstrap.d.ts +2 -2
  27. package/dist/streaming-bootstrap.js +7 -1
  28. package/dist/streaming-cleanup.d.ts +1 -0
  29. package/dist/streaming-cleanup.js +66 -14
  30. package/dist/streaming-coordinator.d.ts +3 -2
  31. package/dist/streaming-coordinator.js +107 -47
  32. package/dist/streaming-deferred.d.ts +14 -4
  33. package/dist/streaming-deferred.js +200 -87
  34. package/dist/streaming-dom.d.ts +12 -3
  35. package/dist/streaming-dom.js +41 -27
  36. package/dist/streaming-mode.d.ts +5 -0
  37. package/dist/streaming-mode.js +4 -0
  38. package/dist/streaming-protocol.d.ts +19 -3
  39. package/dist/streaming-protocol.js +4 -3
  40. package/dist/streaming-spans.d.ts +9 -0
  41. package/dist/streaming-spans.js +200 -0
  42. package/dist/template-content.d.ts +17 -0
  43. package/dist/template-content.js +106 -0
  44. package/dist/template-element.d.ts +17 -2
  45. package/dist/template-element.js +493 -177
  46. package/dist/template-events.d.ts +7 -0
  47. package/dist/template-events.js +7 -1
  48. package/dist/template-types.d.ts +1 -2
  49. package/dist/template.d.ts +15 -8
  50. package/dist/template.js +79 -16
  51. package/package.json +9 -3
  52. package/dist/component-asset/resources.d.ts +0 -4
  53. package/dist/component-asset/resources.js +0 -83
@@ -0,0 +1,200 @@
1
+ import { MAX_MARKER_SCAN_NODES, safeRemove, safeRemoveAttribute, SPAN_START_PREFIX, STREAMING_SPAN_HOST_ATTR, } from './streaming-dom.js';
2
+ export const MAX_SPAN_NESTING = 32;
3
+ const INVALID_SPAN_ID = -1;
4
+ const NOT_A_SPAN_HOST = -2;
5
+ const openSpans = new Map();
6
+ const hostScratch = [];
7
+ const idScratch = [];
8
+ let nextExpectedSpanInstanceId = 0;
9
+ function spanIdOf(element) {
10
+ if (typeof element.hasAttribute !== 'function')
11
+ return NOT_A_SPAN_HOST;
12
+ if (!element.hasAttribute(STREAMING_SPAN_HOST_ATTR))
13
+ return NOT_A_SPAN_HOST;
14
+ const raw = element.getAttribute(STREAMING_SPAN_HOST_ATTR);
15
+ if (raw === null || raw.length === 0)
16
+ return INVALID_SPAN_ID;
17
+ let value = 0;
18
+ for (let i = 0; i < raw.length; i++) {
19
+ const code = raw.charCodeAt(i) - 48;
20
+ if (code < 0 || code > 9)
21
+ return INVALID_SPAN_ID;
22
+ value = value * 10 + code;
23
+ if (!Number.isSafeInteger(value))
24
+ return INVALID_SPAN_ID;
25
+ }
26
+ return String(value) === raw ? value : INVALID_SPAN_ID;
27
+ }
28
+ function invalidSpanAttrReason() {
29
+ return `invalid ${STREAMING_SPAN_HOST_ATTR} value`;
30
+ }
31
+ export function spanHostFor(id) {
32
+ return openSpans.get(id)?.host;
33
+ }
34
+ export function registerEnclosingSpans(renderRoot, enclosingSpanInstanceId) {
35
+ hostScratch.length = 0;
36
+ idScratch.length = 0;
37
+ let current = renderRoot;
38
+ let hops = 0;
39
+ let firstSpan = true;
40
+ while (current && hops < MAX_MARKER_SCAN_NODES) {
41
+ hops++;
42
+ const element = current.nodeType === 1
43
+ ? current
44
+ : null;
45
+ const id = element ? spanIdOf(element) : NOT_A_SPAN_HOST;
46
+ if (id !== NOT_A_SPAN_HOST) {
47
+ if (hostScratch.length >= MAX_SPAN_NESTING) {
48
+ clearScratch();
49
+ return `runtime component span nesting exceeds ${MAX_SPAN_NESTING}`;
50
+ }
51
+ if (id === INVALID_SPAN_ID) {
52
+ clearScratch();
53
+ return invalidSpanAttrReason();
54
+ }
55
+ if (firstSpan && id !== enclosingSpanInstanceId) {
56
+ clearScratch();
57
+ return `boundary declares enclosing span ${enclosingSpanInstanceId}, but its nearest spanning ancestor is ${id}`;
58
+ }
59
+ firstSpan = false;
60
+ hostScratch.push(element);
61
+ idScratch.push(id);
62
+ }
63
+ current = ascendRenderRoots(current);
64
+ }
65
+ if (firstSpan) {
66
+ clearScratch();
67
+ return `boundary declares enclosing span ${enclosingSpanInstanceId}, but no spanning ancestor was found`;
68
+ }
69
+ if (current) {
70
+ clearScratch();
71
+ return `spanning ancestor walk exceeds ${MAX_MARKER_SCAN_NODES} nodes`;
72
+ }
73
+ for (let i = hostScratch.length - 1; i >= 0; i--) {
74
+ const error = registerSpan(idScratch[i], hostScratch[i], i + 1 < idScratch.length ? idScratch[i + 1] : undefined);
75
+ if (error) {
76
+ clearScratch();
77
+ return error;
78
+ }
79
+ }
80
+ clearScratch();
81
+ return null;
82
+ }
83
+ function registerSpan(id, host, parentId) {
84
+ const existing = openSpans.get(id);
85
+ if (existing) {
86
+ return existing.host === host && existing.parentId === parentId
87
+ ? null
88
+ : `span instance ${id} is already open for another ancestry`;
89
+ }
90
+ if (id !== nextExpectedSpanInstanceId) {
91
+ return `expected span instance ${nextExpectedSpanInstanceId}, received ${id}`;
92
+ }
93
+ const marker = host.previousSibling;
94
+ if (marker?.nodeType !== 8 ||
95
+ marker.data !== `${SPAN_START_PREFIX}${id}`) {
96
+ return `missing root-local start marker for span ${id}`;
97
+ }
98
+ const parent = parentId === undefined ? undefined : openSpans.get(parentId);
99
+ if (parentId !== undefined && !parent) {
100
+ return `parent span ${parentId} is not open`;
101
+ }
102
+ openSpans.set(id, {
103
+ host,
104
+ start: marker,
105
+ parentId,
106
+ openChildren: 0,
107
+ });
108
+ if (parent)
109
+ parent.openChildren++;
110
+ nextExpectedSpanInstanceId++;
111
+ return null;
112
+ }
113
+ export function prepareSpanCompletion(id, range) {
114
+ const start = range.start;
115
+ const end = range.end;
116
+ if (!start || !end)
117
+ return `span ${id} completion is markerless`;
118
+ let host;
119
+ let node = start.nextSibling;
120
+ let hops = 0;
121
+ while (node && node !== end) {
122
+ if (hops >= MAX_MARKER_SCAN_NODES) {
123
+ return `span ${id} host lookup exceeds ${MAX_MARKER_SCAN_NODES} nodes`;
124
+ }
125
+ hops++;
126
+ if (node.nodeType === 1) {
127
+ const hostId = spanIdOf(node);
128
+ if (hostId === INVALID_SPAN_ID)
129
+ return invalidSpanAttrReason();
130
+ if (hostId !== NOT_A_SPAN_HOST) {
131
+ if (hostId !== id) {
132
+ return `span completion targets span ${id}, but its host declares span ${hostId}`;
133
+ }
134
+ host = node;
135
+ break;
136
+ }
137
+ }
138
+ node = node.nextSibling;
139
+ }
140
+ if (!host) {
141
+ return `span completion targets span ${id}, but no spanning host was found inside its markers`;
142
+ }
143
+ if (!openSpans.has(id)) {
144
+ const error = registerEnclosingSpans(host, id);
145
+ if (error)
146
+ return error;
147
+ }
148
+ const span = openSpans.get(id);
149
+ if (!span)
150
+ return `span completion targets span ${id}, which is not open`;
151
+ if (span.openChildren !== 0) {
152
+ return `span ${id} completed before its nested component spans`;
153
+ }
154
+ return span.host === host && span.start === start &&
155
+ host.parentNode === start.parentNode
156
+ ? null
157
+ : `span completion markers do not match the open span ${id}`;
158
+ }
159
+ export function completeSpan(id) {
160
+ const span = openSpans.get(id);
161
+ if (!span)
162
+ return;
163
+ openSpans.delete(id);
164
+ safeRemoveAttribute(span.host, STREAMING_SPAN_HOST_ATTR);
165
+ if (span.parentId === undefined)
166
+ return;
167
+ const parent = openSpans.get(span.parentId);
168
+ if (parent && parent.openChildren > 0)
169
+ parent.openChildren--;
170
+ }
171
+ export function abandonOpenSpans() {
172
+ for (const span of openSpans.values()) {
173
+ safeRemoveAttribute(span.host, STREAMING_SPAN_HOST_ATTR);
174
+ safeRemove(span.start);
175
+ }
176
+ openSpans.clear();
177
+ clearScratch();
178
+ nextExpectedSpanInstanceId = 0;
179
+ }
180
+ function ascendRenderRoots(node) {
181
+ if (node.nodeType === 11) {
182
+ return node.host ?? null;
183
+ }
184
+ if (node.nodeType === 1) {
185
+ const slot = node.assignedSlot;
186
+ if (slot)
187
+ return slot;
188
+ }
189
+ return node.parentNode;
190
+ }
191
+ function clearScratch() {
192
+ hostScratch.length = 0;
193
+ idScratch.length = 0;
194
+ }
195
+ export function openSpanCountForTests() {
196
+ return openSpans.size;
197
+ }
198
+ export function hasOpenSpans() {
199
+ return openSpans.size !== 0;
200
+ }
@@ -0,0 +1,17 @@
1
+ import type { TemplateBlockMeta } from './template-types.js';
2
+ export interface TemplateStylesheetDescriptor {
3
+ readonly crossOrigin: string | null;
4
+ readonly disabled: boolean;
5
+ readonly elementIndex: number;
6
+ readonly href: string;
7
+ readonly hasInlineStyles: boolean;
8
+ readonly hasUnsupportedAttributes: boolean;
9
+ readonly integrity: string;
10
+ readonly media: string;
11
+ readonly referrerPolicy: string;
12
+ readonly title: string;
13
+ readonly type: string;
14
+ }
15
+ export declare function cloneTemplateContent(meta: TemplateBlockMeta): DocumentFragment;
16
+ export declare function getTemplateStylesheets(meta: TemplateBlockMeta): readonly TemplateStylesheetDescriptor[] | undefined;
17
+ export declare function templateHtmlMayContainLink(html: string): boolean;
@@ -0,0 +1,106 @@
1
+ const EMPTY_STYLESHEETS = Object.freeze([]);
2
+ const NO_LINK_STYLESHEETS = Object.freeze([]);
3
+ const templateContentCache = new WeakMap();
4
+ export function cloneTemplateContent(meta) {
5
+ return getTemplateContent(meta).fragment.cloneNode(true);
6
+ }
7
+ export function getTemplateStylesheets(meta) {
8
+ const stylesheets = getTemplateContent(meta, true).stylesheets;
9
+ return stylesheets === NO_LINK_STYLESHEETS ? undefined : stylesheets;
10
+ }
11
+ export function templateHtmlMayContainLink(html) {
12
+ for (let i = 0; i <= html.length - 5; i++) {
13
+ if (html.charCodeAt(i) !== 60)
14
+ continue;
15
+ if (asciiLower(html.charCodeAt(i + 1)) !== 108 ||
16
+ asciiLower(html.charCodeAt(i + 2)) !== 105 ||
17
+ asciiLower(html.charCodeAt(i + 3)) !== 110 ||
18
+ asciiLower(html.charCodeAt(i + 4)) !== 107) {
19
+ continue;
20
+ }
21
+ const next = html.charCodeAt(i + 5);
22
+ if (i + 5 === html.length ||
23
+ next === 9 ||
24
+ next === 10 ||
25
+ next === 12 ||
26
+ next === 13 ||
27
+ next === 32 ||
28
+ next === 47 ||
29
+ next === 62) {
30
+ return true;
31
+ }
32
+ }
33
+ return false;
34
+ }
35
+ function getTemplateContent(meta, mayContainLink) {
36
+ let cached = templateContentCache.get(meta);
37
+ if (cached)
38
+ return cached;
39
+ const template = document.createElement('template');
40
+ template.innerHTML = meta.h;
41
+ const fragment = template.content;
42
+ const stylesheets = (mayContainLink ?? templateHtmlMayContainLink(meta.h))
43
+ ? collectStylesheetDescriptors(fragment)
44
+ : NO_LINK_STYLESHEETS;
45
+ cached = { fragment, stylesheets };
46
+ templateContentCache.set(meta, cached);
47
+ return cached;
48
+ }
49
+ function collectStylesheetDescriptors(fragment) {
50
+ const elements = fragment.querySelectorAll('*');
51
+ let hasLink = false;
52
+ let hasInlineStyles;
53
+ let stylesheets;
54
+ for (let i = 0; i < elements.length; i++) {
55
+ const element = elements[i];
56
+ if (element.localName !== 'link')
57
+ continue;
58
+ hasLink = true;
59
+ const link = element;
60
+ if (!link.relList.contains('stylesheet') ||
61
+ link.relList.contains('alternate')) {
62
+ continue;
63
+ }
64
+ const href = link.getAttribute('href');
65
+ if (!href)
66
+ continue;
67
+ hasInlineStyles ??= fragment.querySelector('style') !== null;
68
+ (stylesheets ??= []).push({
69
+ crossOrigin: link.getAttribute('crossorigin'),
70
+ disabled: link.disabled,
71
+ elementIndex: i + 1,
72
+ href,
73
+ hasInlineStyles,
74
+ hasUnsupportedAttributes: containsUnsupportedAttributes(link),
75
+ integrity: link.integrity,
76
+ media: link.media,
77
+ referrerPolicy: link.referrerPolicy,
78
+ title: link.title,
79
+ type: link.type,
80
+ });
81
+ }
82
+ return stylesheets ?? (hasLink ? EMPTY_STYLESHEETS : NO_LINK_STYLESHEETS);
83
+ }
84
+ function asciiLower(code) {
85
+ return code >= 65 && code <= 90 ? code + 32 : code;
86
+ }
87
+ function containsUnsupportedAttributes(link) {
88
+ const attributes = link.attributes;
89
+ for (let i = 0; i < attributes.length; i++) {
90
+ switch (attributes[i].name) {
91
+ case 'crossorigin':
92
+ case 'disabled':
93
+ case 'href':
94
+ case 'integrity':
95
+ case 'media':
96
+ case 'referrerpolicy':
97
+ case 'rel':
98
+ case 'title':
99
+ case 'type':
100
+ break;
101
+ default:
102
+ return true;
103
+ }
104
+ }
105
+ return false;
106
+ }
@@ -1,8 +1,14 @@
1
1
  import type { TemplateMeta, TemplateBlockMeta, TemplateNodeIndex } from './template.js';
2
2
  import { STREAMING_BOUNDARY_ACTIVATE } from './streaming-mode.js';
3
+ import type { ActivationOutcome } from './streaming-mode.js';
3
4
  import type { ScopeFrame, TemplateInstance } from './element/types.js';
4
5
  declare const WEBUI_SET_STATE_KEY: unique symbol;
5
6
  declare const STREAMING_BOUNDARY_ABANDON: unique symbol;
7
+ interface PendingParentState {
8
+ readonly values: Record<string, unknown>;
9
+ replay?: Set<string>;
10
+ }
11
+ export declare function consumePendingParentState(element: Element): PendingParentState | undefined;
6
12
  export declare class TemplateElement extends HTMLElement {
7
13
  private static readonly $ancestorReleaseQueue;
8
14
  private static $ancestorReleaseIndex;
@@ -11,6 +17,8 @@ export declare class TemplateElement extends HTMLElement {
11
17
  private $meta?;
12
18
  private $ready;
13
19
  private $hydrated;
20
+ private $deferredClientMount;
21
+ private $resetClientShadow;
14
22
  private $hasMounted;
15
23
  private $deferredSSR;
16
24
  private $activatingDeferredSSR;
@@ -31,12 +39,15 @@ export declare class TemplateElement extends HTMLElement {
31
39
  private $pathIndex?;
32
40
  private $wildcardBindings?;
33
41
  [WEBUI_SET_STATE_KEY](key: string, value: unknown): boolean;
34
- [STREAMING_BOUNDARY_ACTIVATE](state?: Record<string, unknown>): number;
42
+ [STREAMING_BOUNDARY_ACTIVATE](state?: Record<string, unknown>, bypassAncestor?: Element): ActivationOutcome;
35
43
  [STREAMING_BOUNDARY_ABANDON](): void;
44
+ private $clearAncestorDeferral;
45
+ private $resumeRetainedRoot;
36
46
  static define(tagName: string): void;
37
47
  connectedCallback(): void;
38
48
  private $remount;
39
49
  private $mount;
50
+ private $installStyles;
40
51
  disconnectedCallback(): void;
41
52
  $destroy(): void;
42
53
  private $clearRepeatBinding;
@@ -94,7 +105,6 @@ export declare class TemplateElement extends HTMLElement {
94
105
  private $collectConditionalRange;
95
106
  private $hasRootStructuralSlot;
96
107
  private $hydrateCondContent;
97
- private $findSSRText;
98
108
  private $findSSRSlotRef;
99
109
  private $rootTag;
100
110
  private $wireAttrs;
@@ -105,6 +115,7 @@ export declare class TemplateElement extends HTMLElement {
105
115
  private $buildPathIndex;
106
116
  private $updateBindings;
107
117
  private $textStateIsKnown;
118
+ private $invalidateRawValues;
108
119
  private $attrStateIsKnown;
109
120
  private $partsAreKnown;
110
121
  private $pathsAreKnown;
@@ -112,15 +123,19 @@ export declare class TemplateElement extends HTMLElement {
112
123
  $updateInstance(instance: TemplateInstance): void;
113
124
  private $patchText;
114
125
  private $patchAttr;
126
+ private $swapOwnedRange;
115
127
  private $toggleCond;
116
128
  $resolveValue(path: string, scope?: ScopeFrame): unknown;
129
+ private $conditionResolver;
117
130
  $hasStateRoot(path: string, scope?: ScopeFrame): boolean;
118
131
  private $resolveComponentRoot;
119
132
  private $resolveParts;
133
+ private $setInstanceParent;
120
134
  $block(blockIndex: number): TemplateBlockMeta | undefined;
121
135
  $createBlockInstance(blockIndex: number, scope?: ScopeFrame, parent?: TemplateInstance, container?: ParentNode & Node): TemplateInstance | null;
122
136
  $removeInstance(instance: TemplateInstance): void;
123
137
  private $disposeInstance;
138
+ private $removeInstanceNodes;
124
139
  private $compactNodeArray;
125
140
  $changeStructure(removedFrom?: TemplateInstance): void;
126
141
  $insertInstanceAfter(cursor: Node | null, container: ParentNode & Node, instance: TemplateInstance): Node | null;