@microsoft/webui-framework 0.0.29 → 3.0.0
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 +48 -46
- package/dist/component-asset/asset.d.ts +8 -14
- package/dist/component-asset/asset.js +46 -143
- package/dist/component-asset/loader.d.ts +2 -0
- package/dist/component-asset/loader.js +7 -149
- package/dist/component-asset/registration.d.ts +11 -0
- package/dist/component-asset/registration.js +63 -0
- package/dist/component-asset/validate-graph.d.ts +2 -0
- package/dist/component-asset/validate-graph.js +42 -0
- package/dist/component-asset-runtime.d.ts +7 -0
- package/dist/component-asset-runtime.js +38 -0
- package/dist/component-asset.d.ts +1 -1
- package/dist/decorators.js +42 -8
- package/dist/element.d.ts +12 -0
- package/dist/element.js +122 -1
- package/dist/template-content.d.ts +1 -1
- package/dist/template-content.js +2 -2
- package/dist/template-element.d.ts +4 -2
- package/dist/template-element.js +39 -20
- package/package.json +8 -2
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ This package is the browser-side runtime used by `webui build --plugin=webui`. I
|
|
|
12
12
|
|
|
13
13
|
If you are building WebUI apps in this repo, this is the component model used by examples like `examples/app/todo-webui`, `examples/app/commerce`, and `examples/app/contact-book-manager`.
|
|
14
14
|
|
|
15
|
-
> 📖 **Full documentation at [microsoft.github.io/webui](https://microsoft.github.io/webui)**, see the [Interactivity Guide](https://microsoft.github.io/webui/guide/concepts/interactivity) for component authoring patterns. For framework internals (hydration, path resolution, reactive update model), see [
|
|
15
|
+
> 📖 **Full documentation at [microsoft.github.io/webui](https://microsoft.github.io/webui)**, see the [Interactivity Guide](https://microsoft.github.io/webui/guide/concepts/interactivity) for component authoring patterns. For framework internals (hydration, path resolution, reactive update model), see [the hydration contract](https://github.com/microsoft/webui/blob/main/specs/hydration.md).
|
|
16
16
|
|
|
17
17
|
## Install
|
|
18
18
|
|
|
@@ -328,7 +328,7 @@ update that DOM, so the write is dropped and the runtime logs a
|
|
|
328
328
|
`[WebUI] Hydration mismatch` warning naming the properties. Seed such values in
|
|
329
329
|
the SSR state, or assign them from `hydratedCallback()`. The warning is
|
|
330
330
|
development-only and is dead-code-eliminated from production bundles via the
|
|
331
|
-
`__WEBUI_DEV__` compile-time flag (on by default; `webui
|
|
331
|
+
`__WEBUI_DEV__` compile-time flag (on by default; `webui press build` sets it to
|
|
332
332
|
`false`). See the
|
|
333
333
|
[Interactivity Guide](https://microsoft.github.io/webui/guide/concepts/interactivity#setting-observable-state-during-setup).
|
|
334
334
|
|
|
@@ -396,36 +396,40 @@ In most components you do not call `$update()` directly. Property changes throug
|
|
|
396
396
|
|
|
397
397
|
### Static component assets
|
|
398
398
|
|
|
399
|
-
|
|
400
|
-
`settings-dialog.webui.js` next to `protocol.bin`. Load the ESM asset before
|
|
401
|
-
creating the component when you are not using `@microsoft/webui-router`:
|
|
399
|
+
Generate stable ESM inputs separately from deployable output:
|
|
402
400
|
|
|
403
|
-
```
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
panelSlot.replaceChildren(await settingsAssets.create('settings-dialog'));
|
|
401
|
+
```bash
|
|
402
|
+
webui build ./src --out ./dist --plugin=webui \
|
|
403
|
+
--emit-component-assets settings-dialog \
|
|
404
|
+
--component-assets-out ./.webui
|
|
408
405
|
```
|
|
409
406
|
|
|
410
|
-
```
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
}
|
|
407
|
+
```typescript
|
|
408
|
+
import {
|
|
409
|
+
defineComponentAsset,
|
|
410
|
+
preloadComponentAssetStyles,
|
|
411
|
+
} from '@microsoft/webui-framework/component-asset-runtime.js';
|
|
412
|
+
|
|
413
|
+
const loadSettings = () => {
|
|
414
|
+
preloadComponentAssetStyles('settings-dialog');
|
|
415
|
+
return import('../../.webui/settings-dialog.webui.js')
|
|
416
|
+
.then(module => defineComponentAsset(module.default));
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
void loadSettings().then(asset => asset.preload()).catch(() => {});
|
|
420
|
+
panelSlot.replaceChildren(await (await loadSettings()).create());
|
|
421
421
|
```
|
|
422
422
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
423
|
+
WebUI emits one module per asset-owned component and a thin root entry with
|
|
424
|
+
static imports for its closure. The application bundler owns final splitting,
|
|
425
|
+
sharing, hashes, public paths, and caching. Entry-owned templates remain
|
|
426
|
+
external prerequisites, so load the normal application entry first. Component
|
|
427
|
+
assets cannot be combined with `<route>`. Current generated assets use version
|
|
428
|
+
4 on the root. Imported component payloads contain template and style data,
|
|
429
|
+
not repeated root manifests. Compiler-proven coverage is checked at build time;
|
|
430
|
+
registration checks external entry templates and live style conflicts before
|
|
431
|
+
mutating the registries. The manifest-driven loader additionally validates
|
|
432
|
+
untrusted graphs.
|
|
429
433
|
|
|
430
434
|
The compiler records final Link stylesheet filenames in the protocol. For
|
|
431
435
|
Shadow builds, the handler emits that finite manifest as inert JSON in the
|
|
@@ -437,25 +441,23 @@ WebUI handler or `Protocol`, which emits `#webui-component-assets`. A shell that
|
|
|
437
441
|
uses build artifacts without rendering the protocol still mounts safely through
|
|
438
442
|
the native stylesheet guard, but it does not receive the earlier
|
|
439
443
|
compiler-owned style preload.
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
module is evicted from the registry so a later `preload(tag)` or `create(tag)`
|
|
458
|
-
retries it.
|
|
444
|
+
Only stable generated root input paths belong in authored imports. Final
|
|
445
|
+
JavaScript filenames are application-bundler outputs, while content-hashed
|
|
446
|
+
stylesheet filenames remain compiler-owned. `--metafile` exposes the static
|
|
447
|
+
input graph for analysis and build tooling.
|
|
448
|
+
|
|
449
|
+
Generated roots export semantic payloads only. Application-owned code loads
|
|
450
|
+
component behavior and data directly, so it also owns caching and recovery policy.
|
|
451
|
+
The runtime returns one facade per imported payload. Native ESM failures may stay
|
|
452
|
+
cached for the document lifetime: resetting an application promise does not
|
|
453
|
+
guarantee a new network request. Handle real open failures with visible errors
|
|
454
|
+
and bundler-owned recovery or an explicit page reload. Consume speculative
|
|
455
|
+
preload rejections without automatically reloading.
|
|
456
|
+
In Shadow builds, applications can import the small runtime eagerly, call
|
|
457
|
+
`preloadComponentAssetStyles(root)` before the dynamic import, and wrap the
|
|
458
|
+
default generated payload with `defineComponentAsset()`. This keeps templates
|
|
459
|
+
lazy while starting CSS and JavaScript in parallel. Concurrent roots share
|
|
460
|
+
in-flight registration and stylesheet work.
|
|
459
461
|
|
|
460
462
|
### `@observable`
|
|
461
463
|
|
|
@@ -1,24 +1,18 @@
|
|
|
1
1
|
import type { CompiledConditionFn, TemplateMeta } from '../template.js';
|
|
2
2
|
import { type ComponentStyles } from '../element/styles.js';
|
|
3
|
-
export interface
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
export interface ComponentAssetPayload {
|
|
4
|
+
componentStyles: ComponentStyles;
|
|
5
|
+
templates: Record<string, TemplateMeta>;
|
|
6
|
+
templateFunctions?: Record<string, CompiledConditionFn[]>;
|
|
7
7
|
}
|
|
8
8
|
export interface ComponentAsset {
|
|
9
9
|
type: 'webui-component-asset';
|
|
10
|
-
version:
|
|
11
|
-
|
|
12
|
-
root?: string;
|
|
13
|
-
components: string[];
|
|
14
|
-
requiredComponents: string[];
|
|
10
|
+
version: 4;
|
|
11
|
+
root: string;
|
|
15
12
|
externalComponents: string[];
|
|
16
|
-
imports:
|
|
13
|
+
imports: ComponentAssetPayload[];
|
|
17
14
|
componentStyles: ComponentStyles;
|
|
18
|
-
templates: Record<string, TemplateMeta>;
|
|
19
|
-
templateFunctions?: Record<string, CompiledConditionFn[]>;
|
|
20
15
|
}
|
|
21
16
|
export declare function prepareAssetComponentStyles(value: unknown): ComponentStyles;
|
|
22
17
|
export declare function readComponentAssetModule(module: unknown): unknown;
|
|
23
|
-
export declare function validateAsset(value: unknown
|
|
24
|
-
export declare function sameComponents(left: readonly string[], right: readonly string[]): boolean;
|
|
18
|
+
export declare function validateAsset(value: unknown): asserts value is ComponentAsset;
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { requireComponentStyles, } from '../element/styles.js';
|
|
2
|
-
const ASSET_TYPE = 'webui-component-asset';
|
|
3
|
-
const COMPONENT_STYLES_ASSET_VERSION = 3;
|
|
4
2
|
const preparedAssetStyles = new WeakMap();
|
|
5
3
|
export function prepareAssetComponentStyles(value) {
|
|
6
|
-
if (
|
|
4
|
+
if (isObject(value)) {
|
|
7
5
|
const cached = preparedAssetStyles.get(value);
|
|
8
6
|
if (cached)
|
|
9
7
|
return cached;
|
|
@@ -19,169 +17,74 @@ export function readComponentAssetModule(module) {
|
|
|
19
17
|
}
|
|
20
18
|
return module.default;
|
|
21
19
|
}
|
|
22
|
-
export function validateAsset(value
|
|
20
|
+
export function validateAsset(value) {
|
|
23
21
|
if (!isObject(value)) {
|
|
24
22
|
throw new Error('[WebUI] Component asset default export must be an object.');
|
|
25
23
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
throw new Error(`[WebUI] Invalid component asset type: ${String(asset.type)}`);
|
|
24
|
+
if (value.type !== 'webui-component-asset') {
|
|
25
|
+
throw new Error(`[WebUI] Invalid component asset type: ${String(value.type)}`);
|
|
29
26
|
}
|
|
30
|
-
if (
|
|
31
|
-
throw new Error(`[WebUI] Unsupported component asset version: ${String(
|
|
27
|
+
if (value.version !== 4) {
|
|
28
|
+
throw new Error(`[WebUI] Unsupported component asset version: ${String(value.version)}`);
|
|
32
29
|
}
|
|
33
|
-
if (
|
|
34
|
-
throw new Error('[WebUI]
|
|
30
|
+
if (typeof value.root !== 'string' || value.root.length === 0) {
|
|
31
|
+
throw new Error('[WebUI] Component asset root must name its root component.');
|
|
35
32
|
}
|
|
36
|
-
if (
|
|
37
|
-
throw new Error(
|
|
33
|
+
if (value.componentStyles === undefined) {
|
|
34
|
+
throw new Error('[WebUI] Version 4 component assets require componentStyles.');
|
|
38
35
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (!Array.isArray(asset.imports)) {
|
|
36
|
+
prepareAssetComponentStyles(value.componentStyles);
|
|
37
|
+
validateExternalComponents(value.externalComponents);
|
|
38
|
+
if (!Array.isArray(value.imports)) {
|
|
43
39
|
throw new Error('[WebUI] Component asset imports must be an array.');
|
|
44
40
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (expectedKind === 'root') {
|
|
51
|
-
if (typeof asset.root !== 'string' || asset.root.length === 0) {
|
|
52
|
-
throw new Error('[WebUI] Component asset root must name its root component.');
|
|
41
|
+
const providers = new Set(value.externalComponents);
|
|
42
|
+
for (let i = 0; i < value.imports.length; i++) {
|
|
43
|
+
const tag = validatePayload(value.imports[i]);
|
|
44
|
+
if (providers.has(tag)) {
|
|
45
|
+
throw new Error(`[WebUI] Component asset assigns template <${tag}> to more than one import or external prerequisite.`);
|
|
53
46
|
}
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
else {
|
|
57
|
-
validateChunkShape(asset.root, asset.imports, asset.externalComponents);
|
|
47
|
+
providers.add(tag);
|
|
58
48
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
validateAssetPayload(asset.components, asset.templates, asset.templateFunctions);
|
|
63
|
-
validateAssetCoverage(root, asset.components, asset.requiredComponents, asset.externalComponents, asset.imports);
|
|
64
|
-
}
|
|
65
|
-
export function sameComponents(left, right) {
|
|
66
|
-
if (left.length !== right.length)
|
|
67
|
-
return false;
|
|
68
|
-
for (let i = 0; i < left.length; i++) {
|
|
69
|
-
if (left[i] !== right[i])
|
|
70
|
-
return false;
|
|
49
|
+
if (!providers.has(value.root)) {
|
|
50
|
+
throw new Error(`[WebUI] Component asset root <${value.root}> has no imported payload or external prerequisite.`);
|
|
71
51
|
}
|
|
72
|
-
return true;
|
|
73
52
|
}
|
|
74
53
|
function isObject(value) {
|
|
75
54
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
76
55
|
}
|
|
77
|
-
function
|
|
78
|
-
if (root !== undefined) {
|
|
79
|
-
throw new Error('[WebUI] Shared component asset chunks cannot declare a root.');
|
|
80
|
-
}
|
|
81
|
-
if (imports.length !== 0) {
|
|
82
|
-
throw new Error('[WebUI] Shared component asset chunks cannot import other chunks.');
|
|
83
|
-
}
|
|
84
|
-
if (externalComponents.length !== 0) {
|
|
85
|
-
throw new Error('[WebUI] Shared component asset chunks cannot declare external components.');
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
function validateAssetImport(value) {
|
|
89
|
-
if (!isObject(value)) {
|
|
90
|
-
throw new Error('[WebUI] Component asset import must be an object.');
|
|
91
|
-
}
|
|
92
|
-
const assetImport = value;
|
|
93
|
-
validateUniqueStringArray(assetImport.components, 'import components');
|
|
94
|
-
if (assetImport.components.length === 0) {
|
|
95
|
-
throw new Error('[WebUI] Component asset imports must provide at least one component.');
|
|
96
|
-
}
|
|
97
|
-
if (typeof assetImport.href !== 'string' || assetImport.href.length === 0) {
|
|
98
|
-
throw new Error('[WebUI] Component asset import href must be a non-empty string.');
|
|
99
|
-
}
|
|
100
|
-
if (typeof assetImport.load !== 'function') {
|
|
101
|
-
throw new Error('[WebUI] Component asset import load must be a function.');
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
function validateUniqueStringArray(value, field) {
|
|
105
|
-
validateStringArray(value, field);
|
|
106
|
-
const seen = new Set();
|
|
107
|
-
for (let i = 0; i < value.length; i++) {
|
|
108
|
-
if (seen.has(value[i])) {
|
|
109
|
-
throw new Error(`[WebUI] Component asset ${field} cannot contain duplicate <${value[i]}> entries.`);
|
|
110
|
-
}
|
|
111
|
-
seen.add(value[i]);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
function validateStringArray(value, field) {
|
|
56
|
+
function validateExternalComponents(value) {
|
|
115
57
|
if (!Array.isArray(value)) {
|
|
116
|
-
throw new Error(
|
|
58
|
+
throw new Error('[WebUI] Component asset externalComponents must be an array.');
|
|
117
59
|
}
|
|
60
|
+
const seen = new Set();
|
|
118
61
|
for (let i = 0; i < value.length; i++) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
function validateAssetCoverage(root, components, requiredComponents, externalComponents, imports) {
|
|
125
|
-
const required = new Set(requiredComponents);
|
|
126
|
-
if (root && !required.has(root)) {
|
|
127
|
-
throw new Error(`[WebUI] Component asset root <${root}> must include itself in requiredComponents.`);
|
|
128
|
-
}
|
|
129
|
-
const providers = new Set();
|
|
130
|
-
const addProvider = (component) => {
|
|
131
|
-
if (!required.has(component)) {
|
|
132
|
-
throw new Error(`[WebUI] Component asset provides undeclared template <${component}>. Add it to requiredComponents.`);
|
|
133
|
-
}
|
|
134
|
-
if (providers.has(component)) {
|
|
135
|
-
throw new Error(`[WebUI] Component asset assigns required template <${component}> to more than one payload, import, or external prerequisite.`);
|
|
136
|
-
}
|
|
137
|
-
providers.add(component);
|
|
138
|
-
};
|
|
139
|
-
for (let i = 0; i < components.length; i++)
|
|
140
|
-
addProvider(components[i]);
|
|
141
|
-
for (let i = 0; i < externalComponents.length; i++)
|
|
142
|
-
addProvider(externalComponents[i]);
|
|
143
|
-
for (let i = 0; i < imports.length; i++) {
|
|
144
|
-
const imported = imports[i].components;
|
|
145
|
-
for (let j = 0; j < imported.length; j++)
|
|
146
|
-
addProvider(imported[j]);
|
|
147
|
-
}
|
|
148
|
-
for (let i = 0; i < requiredComponents.length; i++) {
|
|
149
|
-
const component = requiredComponents[i];
|
|
150
|
-
if (!providers.has(component)) {
|
|
151
|
-
throw new Error(`[WebUI] Component asset required template <${component}> has no payload, import, or external prerequisite.`);
|
|
62
|
+
const tag = value[i];
|
|
63
|
+
if (typeof tag !== 'string' || tag.length === 0 || seen.has(tag)) {
|
|
64
|
+
throw new Error('[WebUI] Component asset externalComponents must contain unique non-empty strings.');
|
|
152
65
|
}
|
|
66
|
+
seen.add(tag);
|
|
153
67
|
}
|
|
154
68
|
}
|
|
155
|
-
function
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
170
|
-
if (templateFunctions === undefined)
|
|
171
|
-
return;
|
|
172
|
-
if (!isObject(templateFunctions)) {
|
|
173
|
-
throw new Error('[WebUI] Component asset templateFunctions must be an object.');
|
|
174
|
-
}
|
|
175
|
-
const functionNames = Object.keys(templateFunctions);
|
|
176
|
-
for (let i = 0; i < functionNames.length; i++) {
|
|
177
|
-
const component = functionNames[i];
|
|
178
|
-
if (!declared.has(component)) {
|
|
179
|
-
throw new Error(`[WebUI] Component asset templateFunctions contains undeclared payload <${component}>.`);
|
|
69
|
+
function validatePayload(value) {
|
|
70
|
+
if (!isObject(value) || !isObject(value.templates)) {
|
|
71
|
+
throw new Error('[WebUI] Component asset payload must contain its template metadata.');
|
|
72
|
+
}
|
|
73
|
+
const tags = Object.keys(value.templates);
|
|
74
|
+
if (tags.length !== 1 || tags[0].length === 0) {
|
|
75
|
+
throw new Error('[WebUI] Component asset payload must contain exactly one template.');
|
|
76
|
+
}
|
|
77
|
+
const tag = tags[0];
|
|
78
|
+
prepareAssetComponentStyles(value.componentStyles);
|
|
79
|
+
const functions = value.templateFunctions;
|
|
80
|
+
if (functions !== undefined) {
|
|
81
|
+
if (!isObject(functions) || Object.keys(functions).some(name => name !== tag)) {
|
|
82
|
+
throw new Error(`[WebUI] Component asset templateFunctions contains undeclared payload for <${tag}>.`);
|
|
180
83
|
}
|
|
181
|
-
const
|
|
182
|
-
if (!Array.isArray(
|
|
183
|
-
|
|
184
|
-
throw new Error(`[WebUI] Component asset templateFunctions for <${component}> must contain only functions.`);
|
|
84
|
+
const closures = functions[tag];
|
|
85
|
+
if (!Array.isArray(closures) || closures.some(candidate => typeof candidate !== 'function')) {
|
|
86
|
+
throw new Error(`[WebUI] Component asset templateFunctions for <${tag}> must contain only functions.`);
|
|
185
87
|
}
|
|
186
88
|
}
|
|
89
|
+
return tag;
|
|
187
90
|
}
|
|
@@ -1,2 +1,4 @@
|
|
|
1
|
+
import { type ComponentAsset } from './asset.js';
|
|
1
2
|
import type { ComponentAssetSource } from './manifest.js';
|
|
2
3
|
export declare function loadComponentAsset(tag: string, source: ComponentAssetSource): Promise<void>;
|
|
4
|
+
export declare function registerComponentAsset(asset: ComponentAsset): Promise<void>;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { prepareComponentStyleLinks, prepareRegisteredLinkStyles, } from '../element/link-styles.js';
|
|
1
|
+
import { prepareAssetComponentStyles, readComponentAssetModule, validateAsset, } from './asset.js';
|
|
2
|
+
import { registerComponentAssetGraph } from './registration.js';
|
|
3
|
+
import { validateComponentAssetGraph } from './validate-graph.js';
|
|
5
4
|
const assetModulePromises = new Map();
|
|
6
5
|
export function loadComponentAsset(tag, source) {
|
|
7
6
|
if (typeof source === 'function') {
|
|
@@ -16,34 +15,11 @@ export function loadComponentAsset(tag, source) {
|
|
|
16
15
|
}
|
|
17
16
|
async function registerRootAsset(expectedRoot, href, imported) {
|
|
18
17
|
const asset = readComponentAssetModule(imported);
|
|
19
|
-
validateAsset(asset
|
|
18
|
+
validateAsset(asset);
|
|
20
19
|
if (asset.root !== expectedRoot) {
|
|
21
20
|
throw new Error(`[WebUI] Component asset manifest expected <${expectedRoot}> but ${href} exports <${String(asset.root)}>.`);
|
|
22
21
|
}
|
|
23
|
-
|
|
24
|
-
const root = prepareComponentPayload(asset);
|
|
25
|
-
const chunks = await prepareAssetImports(asset.imports);
|
|
26
|
-
const graph = [...chunks, root];
|
|
27
|
-
validatePreparedGraph(graph);
|
|
28
|
-
for (let i = 0; i < chunks.length; i++) {
|
|
29
|
-
registerComponentResources(chunks[i]);
|
|
30
|
-
}
|
|
31
|
-
registerComponentResources(root);
|
|
32
|
-
let pendingStyles;
|
|
33
|
-
for (let i = 0; i < chunks.length; i++) {
|
|
34
|
-
const ready = chunks[i].linkStyles;
|
|
35
|
-
if (!ready)
|
|
36
|
-
continue;
|
|
37
|
-
(pendingStyles ??= []).push(ready);
|
|
38
|
-
}
|
|
39
|
-
if (root.linkStyles)
|
|
40
|
-
(pendingStyles ??= []).push(root.linkStyles);
|
|
41
|
-
if (pendingStyles)
|
|
42
|
-
await Promise.all(pendingStyles);
|
|
43
|
-
for (let i = 0; i < chunks.length; i++) {
|
|
44
|
-
registerComponentTemplates(chunks[i]);
|
|
45
|
-
}
|
|
46
|
-
registerComponentTemplates(root);
|
|
22
|
+
await registerComponentAssetGraph(asset, prepareAssetComponentStyles, validateComponentAssetGraph);
|
|
47
23
|
}
|
|
48
24
|
function loadAssetModule(href, load) {
|
|
49
25
|
let promise = assetModulePromises.get(href);
|
|
@@ -57,124 +33,6 @@ function loadAssetModule(href, load) {
|
|
|
57
33
|
assetModulePromises.set(href, promise);
|
|
58
34
|
return promise;
|
|
59
35
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
for (let i = 0; i < imports.length; i++) {
|
|
63
|
-
const assetImport = imports[i];
|
|
64
|
-
if (componentsAlreadyRegistered(assetImport.components))
|
|
65
|
-
continue;
|
|
66
|
-
pending.push(importAndPrepareChunk(assetImport));
|
|
67
|
-
}
|
|
68
|
-
return Promise.all(pending);
|
|
69
|
-
}
|
|
70
|
-
function importAndPrepareChunk(assetImport) {
|
|
71
|
-
const href = new URL(assetImport.href, document.baseURI).href;
|
|
72
|
-
return loadAssetModule(href, assetImport.load)
|
|
73
|
-
.then(imported => {
|
|
74
|
-
const chunk = readComponentAssetModule(imported);
|
|
75
|
-
validateAsset(chunk, 'chunk');
|
|
76
|
-
if (!sameComponents(chunk.components, assetImport.components)) {
|
|
77
|
-
throw new Error(`[WebUI] Shared component asset ${href} does not provide the components declared by its root import.`);
|
|
78
|
-
}
|
|
79
|
-
return prepareComponentPayload(chunk);
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
function prepareComponentPayload(asset) {
|
|
83
|
-
const componentStyles = prepareAssetComponentStyles(asset.componentStyles);
|
|
84
|
-
prepareAssetTemplateData(asset.templates, asset.templateFunctions);
|
|
85
|
-
const templateLinkStyles = prepareRegisteredLinkStyles(asset.templates);
|
|
86
|
-
const componentStyleLinks = prepareComponentStyleLinks(componentStyles);
|
|
87
|
-
let linkStyles = templateLinkStyles;
|
|
88
|
-
if (templateLinkStyles && componentStyleLinks) {
|
|
89
|
-
linkStyles = Promise.all([templateLinkStyles, componentStyleLinks]).then(() => { });
|
|
90
|
-
}
|
|
91
|
-
else {
|
|
92
|
-
linkStyles = templateLinkStyles ?? componentStyleLinks;
|
|
93
|
-
}
|
|
94
|
-
return {
|
|
95
|
-
asset,
|
|
96
|
-
componentStyles,
|
|
97
|
-
linkStyles,
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
function registerComponentResources(prepared) {
|
|
101
|
-
registerPreparedComponentStyles(prepared.componentStyles);
|
|
102
|
-
}
|
|
103
|
-
function registerComponentTemplates(prepared) {
|
|
104
|
-
const { asset } = prepared;
|
|
105
|
-
if (componentsAlreadyRegistered(asset.components))
|
|
106
|
-
return;
|
|
107
|
-
registerTemplateData(asset.templates, asset.templateFunctions);
|
|
108
|
-
}
|
|
109
|
-
function validatePreparedGraph(graph) {
|
|
110
|
-
const resources = new Map();
|
|
111
|
-
const closures = new Map();
|
|
112
|
-
const provided = new Set();
|
|
113
|
-
for (let i = 0; i < graph.length; i++) {
|
|
114
|
-
const prepared = graph[i];
|
|
115
|
-
validateComponentStylesRegistration(prepared.componentStyles);
|
|
116
|
-
for (const component of prepared.asset.components)
|
|
117
|
-
provided.add(component);
|
|
118
|
-
const styles = prepared.componentStyles;
|
|
119
|
-
for (const id of Object.keys(styles.resources)) {
|
|
120
|
-
const resource = styles.resources[id];
|
|
121
|
-
const current = resources.get(id);
|
|
122
|
-
if (current && !sameComponentStyleResource(current, resource)) {
|
|
123
|
-
throw new Error(`[WebUI] Conflicting component style resource "${id}".`);
|
|
124
|
-
}
|
|
125
|
-
resources.set(id, resource);
|
|
126
|
-
}
|
|
127
|
-
for (const root of Object.keys(styles.closures)) {
|
|
128
|
-
const closure = styles.closures[root];
|
|
129
|
-
const current = closures.get(root);
|
|
130
|
-
if (current && !sameComponentStyleClosure(current, closure)) {
|
|
131
|
-
throw new Error(`[WebUI] Conflicting component style closure "${root}".`);
|
|
132
|
-
}
|
|
133
|
-
closures.set(root, closure);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
for (let i = 0; i < graph.length; i++) {
|
|
137
|
-
const styles = graph[i].componentStyles;
|
|
138
|
-
for (const root of Object.keys(styles.closures)) {
|
|
139
|
-
for (const id of styles.closures[root]) {
|
|
140
|
-
if (!resources.has(id) &&
|
|
141
|
-
!hasRegisteredComponentStyleResource(id)) {
|
|
142
|
-
throw new Error(`[WebUI] Component style closure "${root}" references missing resource "${id}".`);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
const missing = [];
|
|
148
|
-
for (let i = 0; i < graph.length; i++) {
|
|
149
|
-
const asset = graph[i].asset;
|
|
150
|
-
for (const required of asset.requiredComponents) {
|
|
151
|
-
if (provided.has(required) || getTemplate(required))
|
|
152
|
-
continue;
|
|
153
|
-
if (missing.indexOf(required) < 0)
|
|
154
|
-
missing.push(required);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
if (missing.length === 0)
|
|
158
|
-
return;
|
|
159
|
-
throw new Error(`[WebUI] Component asset is missing required templ${missing.length === 1 ? 'ate' : 'ates'} ${missing.map(tag => `<${tag}>`).join(', ')}. Load the application entry bundle and protocol before deferred component assets.`);
|
|
160
|
-
}
|
|
161
|
-
function componentsAlreadyRegistered(components) {
|
|
162
|
-
if (components.length === 0)
|
|
163
|
-
return false;
|
|
164
|
-
for (let i = 0; i < components.length; i++) {
|
|
165
|
-
if (!getTemplate(components[i]))
|
|
166
|
-
return false;
|
|
167
|
-
}
|
|
168
|
-
return true;
|
|
169
|
-
}
|
|
170
|
-
function validateExternalComponents(asset) {
|
|
171
|
-
const missing = [];
|
|
172
|
-
for (let i = 0; i < asset.externalComponents.length; i++) {
|
|
173
|
-
const component = asset.externalComponents[i];
|
|
174
|
-
if (!getTemplate(component))
|
|
175
|
-
missing.push(component);
|
|
176
|
-
}
|
|
177
|
-
if (missing.length === 0)
|
|
178
|
-
return;
|
|
179
|
-
throw new Error(`[WebUI] Component asset requires entr${missing.length === 1 ? 'y template' : 'y templates'} ${missing.map(tag => `<${tag}>`).join(', ')}. Load the application entry bundle and protocol before deferred component assets.`);
|
|
36
|
+
export function registerComponentAsset(asset) {
|
|
37
|
+
return registerRootAsset(typeof asset.root === 'string' ? asset.root : '', 'generated component asset', { default: asset });
|
|
180
38
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type ComponentStyles } from '../element/styles.js';
|
|
2
|
+
import type { ComponentAsset, ComponentAssetPayload } from './asset.js';
|
|
3
|
+
export interface PreparedComponentAsset {
|
|
4
|
+
tag: string;
|
|
5
|
+
payload?: ComponentAssetPayload;
|
|
6
|
+
componentStyles: ComponentStyles;
|
|
7
|
+
}
|
|
8
|
+
type PrepareStyles = (value: unknown) => ComponentStyles;
|
|
9
|
+
export type ValidateComponentAssetGraph = (graph: readonly PreparedComponentAsset[]) => void;
|
|
10
|
+
export declare function registerComponentAssetGraph(asset: ComponentAsset, prepareStyles?: PrepareStyles, validateGraph?: ValidateComponentAssetGraph): Promise<void>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { getTemplate, prepareAssetTemplateData, registerTemplateData, } from '../template.js';
|
|
2
|
+
import { registerPreparedComponentStyles, validateComponentStylesRegistration, } from '../element/styles.js';
|
|
3
|
+
import { prepareComponentStyleLinks, prepareRegisteredLinkStyles, } from '../element/link-styles.js';
|
|
4
|
+
export async function registerComponentAssetGraph(asset, prepareStyles = trustedComponentStyles, validateGraph) {
|
|
5
|
+
const graph = [];
|
|
6
|
+
for (let i = 0; i < asset.imports.length; i++) {
|
|
7
|
+
const payload = asset.imports[i];
|
|
8
|
+
const styles = prepareStyles(payload.componentStyles);
|
|
9
|
+
validateComponentStylesRegistration(styles);
|
|
10
|
+
graph.push({
|
|
11
|
+
tag: Object.keys(payload.templates)[0],
|
|
12
|
+
payload,
|
|
13
|
+
componentStyles: styles,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
const rootStyles = prepareStyles(asset.componentStyles);
|
|
17
|
+
validateComponentStylesRegistration(rootStyles);
|
|
18
|
+
graph.push({ tag: asset.root, componentStyles: rootStyles });
|
|
19
|
+
validateGraph?.(graph);
|
|
20
|
+
validateExternalTemplates(asset.externalComponents);
|
|
21
|
+
for (let i = 0; i < graph.length; i++) {
|
|
22
|
+
const prepared = graph[i];
|
|
23
|
+
const payload = prepared.payload;
|
|
24
|
+
if (payload && !getTemplate(prepared.tag)) {
|
|
25
|
+
prepareAssetTemplateData(payload.templates, payload.templateFunctions);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const pendingStyles = [];
|
|
29
|
+
for (let i = 0; i < graph.length; i++) {
|
|
30
|
+
const prepared = graph[i];
|
|
31
|
+
const payload = prepared.payload;
|
|
32
|
+
const templateLinks = payload && prepareRegisteredLinkStyles(payload.templates);
|
|
33
|
+
const componentLinks = prepareComponentStyleLinks(prepared.componentStyles);
|
|
34
|
+
if (templateLinks)
|
|
35
|
+
pendingStyles.push(templateLinks);
|
|
36
|
+
if (componentLinks)
|
|
37
|
+
pendingStyles.push(componentLinks);
|
|
38
|
+
}
|
|
39
|
+
for (let i = 0; i < graph.length; i++) {
|
|
40
|
+
registerPreparedComponentStyles(graph[i].componentStyles);
|
|
41
|
+
}
|
|
42
|
+
if (pendingStyles.length > 0)
|
|
43
|
+
await Promise.all(pendingStyles);
|
|
44
|
+
for (let i = 0; i < graph.length; i++) {
|
|
45
|
+
const payload = graph[i].payload;
|
|
46
|
+
if (payload && !getTemplate(graph[i].tag)) {
|
|
47
|
+
registerTemplateData(payload.templates, payload.templateFunctions);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function validateExternalTemplates(external) {
|
|
52
|
+
const missing = [];
|
|
53
|
+
for (let i = 0; i < external.length; i++) {
|
|
54
|
+
if (!getTemplate(external[i]))
|
|
55
|
+
missing.push(external[i]);
|
|
56
|
+
}
|
|
57
|
+
if (missing.length > 0) {
|
|
58
|
+
throw new Error(`[WebUI] Component asset requires entr${missing.length === 1 ? 'y template' : 'y templates'} ${missing.map(tag => `<${tag}>`).join(', ')}. Load the application entry bundle and protocol before deferred component assets.`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function trustedComponentStyles(value) {
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { hasRegisteredComponentStyleResource, sameComponentStyleClosure, sameComponentStyleResource, } from '../element/styles.js';
|
|
2
|
+
export function validateComponentAssetGraph(graph) {
|
|
3
|
+
const resources = new Map();
|
|
4
|
+
const closures = new Map();
|
|
5
|
+
for (let i = 0; i < graph.length; i++) {
|
|
6
|
+
const styles = graph[i].componentStyles;
|
|
7
|
+
const resourceIds = Object.keys(styles.resources);
|
|
8
|
+
for (let j = 0; j < resourceIds.length; j++) {
|
|
9
|
+
const id = resourceIds[j];
|
|
10
|
+
const resource = styles.resources[id];
|
|
11
|
+
const current = resources.get(id);
|
|
12
|
+
if (current && !sameComponentStyleResource(current, resource)) {
|
|
13
|
+
throw new Error(`[WebUI] Conflicting component style resource "${id}".`);
|
|
14
|
+
}
|
|
15
|
+
resources.set(id, resource);
|
|
16
|
+
}
|
|
17
|
+
const roots = Object.keys(styles.closures);
|
|
18
|
+
for (let j = 0; j < roots.length; j++) {
|
|
19
|
+
const root = roots[j];
|
|
20
|
+
const closure = styles.closures[root];
|
|
21
|
+
const current = closures.get(root);
|
|
22
|
+
if (current && !sameComponentStyleClosure(current, closure)) {
|
|
23
|
+
throw new Error(`[WebUI] Conflicting component style closure "${root}".`);
|
|
24
|
+
}
|
|
25
|
+
closures.set(root, closure);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
for (let i = 0; i < graph.length; i++) {
|
|
29
|
+
const styles = graph[i].componentStyles;
|
|
30
|
+
const roots = Object.keys(styles.closures);
|
|
31
|
+
for (let j = 0; j < roots.length; j++) {
|
|
32
|
+
const root = roots[j];
|
|
33
|
+
const closure = styles.closures[root];
|
|
34
|
+
for (let k = 0; k < closure.length; k++) {
|
|
35
|
+
const id = closure[k];
|
|
36
|
+
if (!resources.has(id) && !hasRegisteredComponentStyleResource(id)) {
|
|
37
|
+
throw new Error(`[WebUI] Component style closure "${root}" references missing resource "${id}".`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ComponentAsset } from './component-asset/asset.js';
|
|
2
|
+
export interface GeneratedComponentAsset {
|
|
3
|
+
preload(): Promise<void>;
|
|
4
|
+
create(): Promise<HTMLElement>;
|
|
5
|
+
}
|
|
6
|
+
export declare function preloadComponentAssetStyles(root: string): void;
|
|
7
|
+
export declare function defineComponentAsset(asset: ComponentAsset): GeneratedComponentAsset;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { registerComponentAssetGraph } from './component-asset/registration.js';
|
|
2
|
+
import { takeGeneratedComponentAssetStyles } from './component-asset/generated-manifest.js';
|
|
3
|
+
import { preloadComponentAssetStyles as preloadComponentAssetStylesInternal } from './element/link-styles.js';
|
|
4
|
+
const facades = new WeakMap();
|
|
5
|
+
export function preloadComponentAssetStyles(root) {
|
|
6
|
+
const styles = takeGeneratedComponentAssetStyles(root);
|
|
7
|
+
if (styles)
|
|
8
|
+
preloadComponentAssetStylesInternal(styles);
|
|
9
|
+
}
|
|
10
|
+
export function defineComponentAsset(asset) {
|
|
11
|
+
if (asset.type !== 'webui-component-asset' || asset.version !== 4) {
|
|
12
|
+
throw new Error('[WebUI] Expected a version 4 component asset root. Rebuild generated inputs with a compatible compiler.');
|
|
13
|
+
}
|
|
14
|
+
const existing = facades.get(asset);
|
|
15
|
+
if (existing)
|
|
16
|
+
return existing;
|
|
17
|
+
const root = asset.root;
|
|
18
|
+
let pending;
|
|
19
|
+
const preload = () => {
|
|
20
|
+
if (pending)
|
|
21
|
+
return pending;
|
|
22
|
+
preloadComponentAssetStyles(root);
|
|
23
|
+
const next = registerComponentAssetGraph(asset).catch((error) => {
|
|
24
|
+
if (pending === next)
|
|
25
|
+
pending = undefined;
|
|
26
|
+
throw error;
|
|
27
|
+
});
|
|
28
|
+
pending = next;
|
|
29
|
+
return next;
|
|
30
|
+
};
|
|
31
|
+
const create = async () => {
|
|
32
|
+
await preload();
|
|
33
|
+
return document.createElement(root);
|
|
34
|
+
};
|
|
35
|
+
const facade = { preload, create };
|
|
36
|
+
facades.set(asset, facade);
|
|
37
|
+
return facade;
|
|
38
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { ComponentAssetManifest, ComponentAssetRegistry } from './component-asset/manifest.js';
|
|
2
|
-
export type { ComponentAsset,
|
|
2
|
+
export type { ComponentAsset, ComponentAssetPayload, } from './component-asset/asset.js';
|
|
3
3
|
export type { ComponentAssetCreateOptions, ComponentAssetManifest, ComponentAssetManifestEntry, ComponentAssetPreload, ComponentAssetRegistry, ComponentAssetSource, ComponentAssetState, } from './component-asset/manifest.js';
|
|
4
4
|
export declare function defineComponentAssets(manifest: ComponentAssetManifest): ComponentAssetRegistry;
|
package/dist/decorators.js
CHANGED
|
@@ -162,6 +162,12 @@ function createReactiveProperty(proto, name, attrDefinition) {
|
|
|
162
162
|
return this[backingKey];
|
|
163
163
|
},
|
|
164
164
|
set(newValue) {
|
|
165
|
+
const captured = this['$preUpgradeProperties'];
|
|
166
|
+
if (captured?.has(name) && this.matches(':defined')) {
|
|
167
|
+
captured.delete(name);
|
|
168
|
+
if (captured.size === 0)
|
|
169
|
+
this['$preUpgradeProperties'] = undefined;
|
|
170
|
+
}
|
|
165
171
|
const oldValue = this[backingKey];
|
|
166
172
|
if (Object.is(oldValue, newValue)) {
|
|
167
173
|
if (this['$deferredSSR'] === true) {
|
|
@@ -172,12 +178,33 @@ function createReactiveProperty(proto, name, attrDefinition) {
|
|
|
172
178
|
return;
|
|
173
179
|
}
|
|
174
180
|
this[backingKey] = newValue;
|
|
175
|
-
|
|
176
|
-
|
|
181
|
+
let canReflect = false;
|
|
182
|
+
if (attrDefinition) {
|
|
183
|
+
const checkReflection = this['$canRunAuthoredEffects'];
|
|
184
|
+
if (typeof checkReflection === 'function' &&
|
|
185
|
+
checkReflection.call(this)) {
|
|
186
|
+
reflectPropertyToAttribute(this, attrDefinition, newValue);
|
|
187
|
+
canReflect = true;
|
|
188
|
+
}
|
|
177
189
|
}
|
|
178
|
-
const
|
|
179
|
-
if (typeof
|
|
180
|
-
|
|
190
|
+
const callback = this[changedKey];
|
|
191
|
+
if (typeof callback === 'function') {
|
|
192
|
+
if (this['$authoredPhase'] === 1 &&
|
|
193
|
+
(attrDefinition ? canReflect : this.isConnected)) {
|
|
194
|
+
callback
|
|
195
|
+
.call(this, oldValue, newValue);
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
const record = this['$recordPropertyChange'];
|
|
199
|
+
if (typeof record === 'function') {
|
|
200
|
+
record
|
|
201
|
+
.call(this, name, oldValue);
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
callback
|
|
205
|
+
.call(this, oldValue, newValue);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
181
208
|
}
|
|
182
209
|
if (this.isConnected ||
|
|
183
210
|
this['$deferredSSR'] === true) {
|
|
@@ -234,9 +261,16 @@ function applyAttr(target, name, options) {
|
|
|
234
261
|
const definition = attrDefinitionFor(this.constructor, attribute);
|
|
235
262
|
if (definition !== undefined &&
|
|
236
263
|
this[reflectingAttribute] !== attribute) {
|
|
237
|
-
this[
|
|
238
|
-
|
|
239
|
-
|
|
264
|
+
const captured = this['$preUpgradeProperties'];
|
|
265
|
+
if (captured)
|
|
266
|
+
this['$preUpgradeProperties'] = undefined;
|
|
267
|
+
try {
|
|
268
|
+
this[definition.property] = definition.boolean ? newVal !== null : newVal;
|
|
269
|
+
}
|
|
270
|
+
finally {
|
|
271
|
+
if (captured)
|
|
272
|
+
this['$preUpgradeProperties'] = captured;
|
|
273
|
+
}
|
|
240
274
|
}
|
|
241
275
|
if (origACB) {
|
|
242
276
|
origACB.call(this, attribute, oldVal, newVal);
|
package/dist/element.d.ts
CHANGED
|
@@ -4,6 +4,18 @@ import type { TemplateBlockMeta, TemplateMeta, TemplateNodeIndex } from './templ
|
|
|
4
4
|
import type { ScopeFrame, TemplateInstance } from './element/types.js';
|
|
5
5
|
export declare class WebUIElement extends TemplateElement {
|
|
6
6
|
private $lazyHydrationMode;
|
|
7
|
+
private $authoredPhase;
|
|
8
|
+
private $pendingPropertyChanges;
|
|
9
|
+
private $preUpgradeProperties;
|
|
10
|
+
constructor();
|
|
11
|
+
connectedCallback(): void;
|
|
12
|
+
$destroy(): void;
|
|
13
|
+
$recordPropertyChange(name: string, oldValue: unknown): void;
|
|
14
|
+
protected $reconcileAuthoredState(): boolean;
|
|
15
|
+
$flushUpdates(): void;
|
|
16
|
+
private $flushAuthoredChanges;
|
|
17
|
+
private $invokePropertyChange;
|
|
18
|
+
private $releasePropertyChanges;
|
|
7
19
|
protected $shouldDeferSSRHydration(meta?: TemplateMeta): boolean;
|
|
8
20
|
protected $didDeferSSRHydration(): void;
|
|
9
21
|
protected $didDeferStreamedSSRHydration(state: Record<string, unknown> | undefined): void;
|
package/dist/element.js
CHANGED
|
@@ -7,6 +7,9 @@ const RENDER_ATTR = 'w-render';
|
|
|
7
7
|
const HYDRATE_EAGER = 'eager';
|
|
8
8
|
const INTERACTION_HYDRATION = 3;
|
|
9
9
|
const LAZY_RENDER_INTERACTION = 4;
|
|
10
|
+
const AUTHORED_LIVE = 1;
|
|
11
|
+
const AUTHORED_PENDING = 2;
|
|
12
|
+
const AUTHORED_FLUSHING = 4;
|
|
10
13
|
const DEV = typeof __WEBUI_DEV__ === 'undefined' || __WEBUI_DEV__;
|
|
11
14
|
let warnedMissingLazyHydrationEntry = false;
|
|
12
15
|
function warnMissingLazyHydrationEntry(tag, policy) {
|
|
@@ -22,6 +25,121 @@ function warnMissingLazyHydrationEntry(tag, policy) {
|
|
|
22
25
|
'your component definitions to enable visibility-deferred hydration.');
|
|
23
26
|
}
|
|
24
27
|
export class WebUIElement extends TemplateElement {
|
|
28
|
+
constructor() {
|
|
29
|
+
super();
|
|
30
|
+
const names = getObservableNames(this.constructor);
|
|
31
|
+
for (const name of names) {
|
|
32
|
+
if (!Object.prototype.hasOwnProperty.call(this, name))
|
|
33
|
+
continue;
|
|
34
|
+
(this.$preUpgradeProperties ??= new Map()).set(name, this[name]);
|
|
35
|
+
delete this[name];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
connectedCallback() {
|
|
39
|
+
const properties = this.$preUpgradeProperties;
|
|
40
|
+
if (properties) {
|
|
41
|
+
this.$preUpgradeProperties = undefined;
|
|
42
|
+
for (const [name, value] of properties) {
|
|
43
|
+
this[name] = value;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
super.connectedCallback();
|
|
47
|
+
}
|
|
48
|
+
$destroy() {
|
|
49
|
+
if (this.$authoredPhase !== undefined)
|
|
50
|
+
this.$authoredPhase &= ~AUTHORED_LIVE;
|
|
51
|
+
super.$destroy();
|
|
52
|
+
}
|
|
53
|
+
$recordPropertyChange(name, oldValue) {
|
|
54
|
+
if (this.$authoredPhase === undefined)
|
|
55
|
+
return;
|
|
56
|
+
const changes = this.$pendingPropertyChanges;
|
|
57
|
+
if ((this.$authoredPhase & AUTHORED_LIVE) && this.$canRunAuthoredEffects()) {
|
|
58
|
+
if (changes?.has(name)) {
|
|
59
|
+
oldValue = changes.get(name);
|
|
60
|
+
changes.delete(name);
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
this.$invokePropertyChange(name, oldValue);
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
this.$releasePropertyChanges();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
const queued = changes ?? (this.$pendingPropertyChanges = new Map());
|
|
71
|
+
if (!queued.has(name))
|
|
72
|
+
queued.set(name, oldValue);
|
|
73
|
+
this.$authoredPhase |= AUTHORED_PENDING;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
$reconcileAuthoredState() {
|
|
77
|
+
if (!this.$canRunAuthoredEffects())
|
|
78
|
+
return !this.$pendingPropertyChanges;
|
|
79
|
+
if (this.$authoredPhase === undefined) {
|
|
80
|
+
const names = getObservableNames(this.constructor);
|
|
81
|
+
for (const name of names) {
|
|
82
|
+
if (typeof this[`${name}Changed`] !== 'function')
|
|
83
|
+
continue;
|
|
84
|
+
this.$authoredPhase = AUTHORED_LIVE;
|
|
85
|
+
if (Object.prototype.hasOwnProperty.call(this, `_${name}`)) {
|
|
86
|
+
(this.$pendingPropertyChanges ??= new Map()).set(name, undefined);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (this.$authoredPhase === undefined)
|
|
91
|
+
return true;
|
|
92
|
+
this.$authoredPhase |= AUTHORED_LIVE;
|
|
93
|
+
if (this.$authoredPhase & AUTHORED_FLUSHING)
|
|
94
|
+
return false;
|
|
95
|
+
if (this.$pendingPropertyChanges) {
|
|
96
|
+
this.$authoredPhase |= AUTHORED_PENDING;
|
|
97
|
+
this.$flushAuthoredChanges();
|
|
98
|
+
}
|
|
99
|
+
return !this.$pendingPropertyChanges;
|
|
100
|
+
}
|
|
101
|
+
$flushUpdates() {
|
|
102
|
+
if (this.$pendingPropertyChanges)
|
|
103
|
+
this.$notifyHydrated();
|
|
104
|
+
super.$flushUpdates();
|
|
105
|
+
}
|
|
106
|
+
$flushAuthoredChanges() {
|
|
107
|
+
const changes = this.$pendingPropertyChanges;
|
|
108
|
+
if (!changes || !this.$canRunAuthoredEffects())
|
|
109
|
+
return;
|
|
110
|
+
this.$authoredPhase = AUTHORED_LIVE | AUTHORED_PENDING | AUTHORED_FLUSHING;
|
|
111
|
+
try {
|
|
112
|
+
for (const [name, oldValue] of changes) {
|
|
113
|
+
if (!this.$canRunAuthoredEffects()) {
|
|
114
|
+
this.$authoredPhase &= ~AUTHORED_LIVE;
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
changes.delete(name);
|
|
118
|
+
this.$invokePropertyChange(name, oldValue);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
finally {
|
|
122
|
+
this.$authoredPhase &= ~AUTHORED_FLUSHING;
|
|
123
|
+
this.$releasePropertyChanges();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
$invokePropertyChange(name, oldValue) {
|
|
127
|
+
const value = this[name];
|
|
128
|
+
if (Object.is(oldValue, value))
|
|
129
|
+
return;
|
|
130
|
+
const callback = this[`${name}Changed`];
|
|
131
|
+
if (typeof callback === 'function') {
|
|
132
|
+
callback.call(this, oldValue, value);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
$releasePropertyChanges() {
|
|
136
|
+
const phase = this.$authoredPhase;
|
|
137
|
+
if (phase !== undefined && this.$pendingPropertyChanges?.size === 0 &&
|
|
138
|
+
!(phase & AUTHORED_FLUSHING)) {
|
|
139
|
+
this.$pendingPropertyChanges = undefined;
|
|
140
|
+
this.$authoredPhase = phase & ~AUTHORED_PENDING;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
25
143
|
$shouldDeferSSRHydration(meta) {
|
|
26
144
|
this.$lazyHydrationMode = this.$resolveLazyHydrationMode(meta);
|
|
27
145
|
return this.$lazyHydrationMode !== undefined;
|
|
@@ -90,6 +208,8 @@ export class WebUIElement extends TemplateElement {
|
|
|
90
208
|
this.$activateDeferredSSR(state);
|
|
91
209
|
}
|
|
92
210
|
disconnectedCallback() {
|
|
211
|
+
if (this.$authoredPhase !== undefined)
|
|
212
|
+
this.$authoredPhase &= ~AUTHORED_LIVE;
|
|
93
213
|
if (this.$lazyHydrationMode !== undefined) {
|
|
94
214
|
disconnectLazyHydration(this);
|
|
95
215
|
}
|
|
@@ -103,7 +223,8 @@ export class WebUIElement extends TemplateElement {
|
|
|
103
223
|
return attribute === undefined || !this.hasAttribute(attribute);
|
|
104
224
|
}
|
|
105
225
|
$syncAuthoredAttributes() {
|
|
106
|
-
|
|
226
|
+
if (this.isConnected)
|
|
227
|
+
syncAttrProperties(this, this.constructor);
|
|
107
228
|
}
|
|
108
229
|
$emit(name, detail) {
|
|
109
230
|
return this.dispatchEvent(new CustomEvent(name, {
|
|
@@ -12,7 +12,7 @@ export interface TemplateStylesheetDescriptor {
|
|
|
12
12
|
readonly title: string;
|
|
13
13
|
readonly type: string;
|
|
14
14
|
}
|
|
15
|
-
export declare function cloneTemplateContent(meta: TemplateBlockMeta): DocumentFragment;
|
|
15
|
+
export declare function cloneTemplateContent(meta: TemplateBlockMeta, ownerDocument: Document): DocumentFragment;
|
|
16
16
|
export declare function getTemplateFragment(meta: TemplateBlockMeta): DocumentFragment;
|
|
17
17
|
export declare function getTemplateStylesheets(meta: TemplateBlockMeta): readonly TemplateStylesheetDescriptor[] | undefined;
|
|
18
18
|
export declare function templateHtmlMayContainLink(html: string): boolean;
|
package/dist/template-content.js
CHANGED
|
@@ -2,8 +2,8 @@ import { setTemplateContent } from './trusted-types-policy.js';
|
|
|
2
2
|
const EMPTY_STYLESHEETS = Object.freeze([]);
|
|
3
3
|
const NO_LINK_STYLESHEETS = Object.freeze([]);
|
|
4
4
|
const templateContentCache = new WeakMap();
|
|
5
|
-
export function cloneTemplateContent(meta) {
|
|
6
|
-
return getTemplateFragment(meta)
|
|
5
|
+
export function cloneTemplateContent(meta, ownerDocument) {
|
|
6
|
+
return ownerDocument.importNode(getTemplateFragment(meta), true);
|
|
7
7
|
}
|
|
8
8
|
export function getTemplateFragment(meta) {
|
|
9
9
|
return getTemplateContent(meta).fragment;
|
|
@@ -19,7 +19,7 @@ export declare class TemplateElement extends HTMLElement {
|
|
|
19
19
|
private $hydrated;
|
|
20
20
|
private $deferredClientMount;
|
|
21
21
|
private $resetClientShadow;
|
|
22
|
-
private $
|
|
22
|
+
private $mountPhase;
|
|
23
23
|
private $deferredSSR;
|
|
24
24
|
private $activatingDeferredSSR;
|
|
25
25
|
private $deferredAncestor;
|
|
@@ -78,7 +78,7 @@ export declare class TemplateElement extends HTMLElement {
|
|
|
78
78
|
protected $shouldApplySSRState(_key: string): boolean;
|
|
79
79
|
protected $syncAuthoredAttributes(): void;
|
|
80
80
|
protected hydratedCallback(): void;
|
|
81
|
-
|
|
81
|
+
protected $notifyHydrated(): void;
|
|
82
82
|
private $finishHydration;
|
|
83
83
|
protected $shouldApplyTemplateStateFromSSR(_key: string): boolean;
|
|
84
84
|
protected $setTemplateState(key: string, value: unknown): boolean;
|
|
@@ -93,6 +93,8 @@ export declare class TemplateElement extends HTMLElement {
|
|
|
93
93
|
private $recordDeferredWrite;
|
|
94
94
|
private $replayDeferredWrites;
|
|
95
95
|
$flushUpdates(): void;
|
|
96
|
+
protected $reconcileAuthoredState(): boolean;
|
|
97
|
+
protected $canRunAuthoredEffects(): boolean;
|
|
96
98
|
private $flush;
|
|
97
99
|
private $recordPreReadyWrite;
|
|
98
100
|
private $checkHydrationMismatch;
|
package/dist/template-element.js
CHANGED
|
@@ -204,6 +204,8 @@ function hasAuthoredMember(instance, key) {
|
|
|
204
204
|
}
|
|
205
205
|
return false;
|
|
206
206
|
}
|
|
207
|
+
const MOUNTED = 1;
|
|
208
|
+
const HYDRATION_NOTIFIED = 2;
|
|
207
209
|
export class TemplateElement extends HTMLElement {
|
|
208
210
|
constructor() {
|
|
209
211
|
super(...arguments);
|
|
@@ -212,7 +214,7 @@ export class TemplateElement extends HTMLElement {
|
|
|
212
214
|
this.$hydrated = false;
|
|
213
215
|
this.$deferredClientMount = false;
|
|
214
216
|
this.$resetClientShadow = false;
|
|
215
|
-
this.$
|
|
217
|
+
this.$mountPhase = 0;
|
|
216
218
|
this.$deferredSSR = false;
|
|
217
219
|
this.$activatingDeferredSSR = false;
|
|
218
220
|
this.$hasUnknownScopes = false;
|
|
@@ -322,6 +324,8 @@ export class TemplateElement extends HTMLElement {
|
|
|
322
324
|
hydrationStart();
|
|
323
325
|
try {
|
|
324
326
|
this.$ready = true;
|
|
327
|
+
this.$syncAuthoredAttributes();
|
|
328
|
+
this.$notifyHydrated();
|
|
325
329
|
this.$update();
|
|
326
330
|
}
|
|
327
331
|
finally {
|
|
@@ -329,7 +333,7 @@ export class TemplateElement extends HTMLElement {
|
|
|
329
333
|
}
|
|
330
334
|
return;
|
|
331
335
|
}
|
|
332
|
-
if (!this.$
|
|
336
|
+
if (!this.$mountPhase &&
|
|
333
337
|
isStreamingHydrationMode() &&
|
|
334
338
|
this.hasAttribute(STREAMED_HOST_ATTR)) {
|
|
335
339
|
this.$deferredSSR = true;
|
|
@@ -346,7 +350,7 @@ export class TemplateElement extends HTMLElement {
|
|
|
346
350
|
return;
|
|
347
351
|
}
|
|
348
352
|
this.$meta = meta;
|
|
349
|
-
if (this.$
|
|
353
|
+
if (this.$mountPhase) {
|
|
350
354
|
this.$remount(meta);
|
|
351
355
|
}
|
|
352
356
|
else {
|
|
@@ -856,10 +860,14 @@ export class TemplateElement extends HTMLElement {
|
|
|
856
860
|
hydratedCallback() {
|
|
857
861
|
}
|
|
858
862
|
$notifyHydrated() {
|
|
859
|
-
if (this.$
|
|
863
|
+
if (!this.$mountPhase)
|
|
864
|
+
this.$mountPhase = MOUNTED;
|
|
865
|
+
if (!this.$reconcileAuthoredState())
|
|
860
866
|
return;
|
|
861
|
-
this.$
|
|
862
|
-
|
|
867
|
+
if (this.$mountPhase === MOUNTED) {
|
|
868
|
+
this.$mountPhase = HYDRATION_NOTIFIED;
|
|
869
|
+
this.hydratedCallback();
|
|
870
|
+
}
|
|
863
871
|
}
|
|
864
872
|
$finishHydration() {
|
|
865
873
|
let callbackFailed = false;
|
|
@@ -1031,9 +1039,18 @@ export class TemplateElement extends HTMLElement {
|
|
|
1031
1039
|
this.$flush(true);
|
|
1032
1040
|
}
|
|
1033
1041
|
$flushUpdates() {
|
|
1042
|
+
if (this.$mountPhase === MOUNTED)
|
|
1043
|
+
this.$notifyHydrated();
|
|
1034
1044
|
if (this.$pendingFlush)
|
|
1035
1045
|
this.$flush();
|
|
1036
1046
|
}
|
|
1047
|
+
$reconcileAuthoredState() {
|
|
1048
|
+
return true;
|
|
1049
|
+
}
|
|
1050
|
+
$canRunAuthoredEffects() {
|
|
1051
|
+
return this.$hydrated && this.$root !== null && this.$ready &&
|
|
1052
|
+
!this.$deferredClientMount && this.isConnected;
|
|
1053
|
+
}
|
|
1037
1054
|
$flush(requireKnownState = this.$guardUnknownState === true) {
|
|
1038
1055
|
if (!this.$ready || !this.$root) {
|
|
1039
1056
|
this.$dirtyPaths = null;
|
|
@@ -1108,10 +1125,10 @@ export class TemplateElement extends HTMLElement {
|
|
|
1108
1125
|
void import('./hydration-mismatch.js').then((m) => m.reportHydrationMismatch(tag, writes, index, ctx));
|
|
1109
1126
|
}
|
|
1110
1127
|
$parseTemplate(meta) {
|
|
1111
|
-
return cloneTemplateContent(meta);
|
|
1128
|
+
return cloneTemplateContent(meta, this.ownerDocument);
|
|
1112
1129
|
}
|
|
1113
1130
|
$createStagingRoot(meta) {
|
|
1114
|
-
const wrapper =
|
|
1131
|
+
const wrapper = this.ownerDocument.createElement('div');
|
|
1115
1132
|
const fragment = this.$parseTemplate(meta);
|
|
1116
1133
|
wrapper.appendChild(fragment);
|
|
1117
1134
|
customElements.upgrade(wrapper);
|
|
@@ -1125,7 +1142,7 @@ export class TemplateElement extends HTMLElement {
|
|
|
1125
1142
|
root.appendChild(first);
|
|
1126
1143
|
return;
|
|
1127
1144
|
}
|
|
1128
|
-
const fragment =
|
|
1145
|
+
const fragment = this.ownerDocument.createDocumentFragment();
|
|
1129
1146
|
while (stagingRoot.firstChild) {
|
|
1130
1147
|
fragment.appendChild(stagingRoot.firstChild);
|
|
1131
1148
|
}
|
|
@@ -1157,6 +1174,7 @@ export class TemplateElement extends HTMLElement {
|
|
|
1157
1174
|
}
|
|
1158
1175
|
}
|
|
1159
1176
|
$wire(root, meta, scope) {
|
|
1177
|
+
const ownerDocument = this.ownerDocument;
|
|
1160
1178
|
const instance = {
|
|
1161
1179
|
scope, container: root, nodes: childNodesArray(root),
|
|
1162
1180
|
texts: bindingArray(meta.tx?.length ?? 0),
|
|
@@ -1181,8 +1199,8 @@ export class TemplateElement extends HTMLElement {
|
|
|
1181
1199
|
let node;
|
|
1182
1200
|
let end;
|
|
1183
1201
|
if (raw) {
|
|
1184
|
-
const start =
|
|
1185
|
-
end =
|
|
1202
|
+
const start = ownerDocument.createComment(rawMarker(rawIndex));
|
|
1203
|
+
end = ownerDocument.createComment(rawMarker(rawIndex, true));
|
|
1186
1204
|
rawIndex++;
|
|
1187
1205
|
instance.texts.push({
|
|
1188
1206
|
node: start,
|
|
@@ -1195,7 +1213,7 @@ export class TemplateElement extends HTMLElement {
|
|
|
1195
1213
|
node = start;
|
|
1196
1214
|
}
|
|
1197
1215
|
else {
|
|
1198
|
-
const textNode =
|
|
1216
|
+
const textNode = ownerDocument.createTextNode('');
|
|
1199
1217
|
instance.texts.push({ node: textNode, parts, scope });
|
|
1200
1218
|
node = textNode;
|
|
1201
1219
|
}
|
|
@@ -1217,7 +1235,7 @@ export class TemplateElement extends HTMLElement {
|
|
|
1217
1235
|
const parent = elements[parentIndex];
|
|
1218
1236
|
if (!parent || (parent.nodeType !== 1 && parent.nodeType !== 11))
|
|
1219
1237
|
continue;
|
|
1220
|
-
const anchor =
|
|
1238
|
+
const anchor = ownerDocument.createComment('');
|
|
1221
1239
|
instance.conds.push({
|
|
1222
1240
|
condition: condition,
|
|
1223
1241
|
blockIndex,
|
|
@@ -1243,7 +1261,7 @@ export class TemplateElement extends HTMLElement {
|
|
|
1243
1261
|
const parent = elements[parentIndex];
|
|
1244
1262
|
if (!parent || (parent.nodeType !== 1 && parent.nodeType !== 11))
|
|
1245
1263
|
continue;
|
|
1246
|
-
const anchor =
|
|
1264
|
+
const anchor = ownerDocument.createComment('');
|
|
1247
1265
|
const binding = {
|
|
1248
1266
|
markerId: i, collection, itemVar, blockIndex,
|
|
1249
1267
|
container: parent, start: anchor, end: null,
|
|
@@ -1271,6 +1289,7 @@ export class TemplateElement extends HTMLElement {
|
|
|
1271
1289
|
return instance;
|
|
1272
1290
|
}
|
|
1273
1291
|
$hydrate(ssrRoot, meta, tplDom, scope, pathStart = 0) {
|
|
1292
|
+
const ownerDocument = this.ownerDocument;
|
|
1274
1293
|
const instance = {
|
|
1275
1294
|
scope,
|
|
1276
1295
|
container: (pathStart > 0 ? ssrRoot.parentNode : ssrRoot),
|
|
@@ -1334,7 +1353,7 @@ export class TemplateElement extends HTMLElement {
|
|
|
1334
1353
|
}
|
|
1335
1354
|
let textNode = previous?.nodeType === 3 ? previous : null;
|
|
1336
1355
|
if (textNode === null) {
|
|
1337
|
-
textNode =
|
|
1356
|
+
textNode = ownerDocument.createTextNode('');
|
|
1338
1357
|
ssrParent.insertBefore(textNode, insertRef);
|
|
1339
1358
|
}
|
|
1340
1359
|
instance.texts.push({ node: textNode, parts, scope });
|
|
@@ -1356,7 +1375,7 @@ export class TemplateElement extends HTMLElement {
|
|
|
1356
1375
|
condAnchor = marker;
|
|
1357
1376
|
}
|
|
1358
1377
|
else {
|
|
1359
|
-
condAnchor =
|
|
1378
|
+
condAnchor = ownerDocument.createComment('');
|
|
1360
1379
|
const [, beforeIndex] = slotMeta;
|
|
1361
1380
|
const insertRef = ssrParent.childNodes[beforeIndex ?? ssrParent.childNodes.length] ?? null;
|
|
1362
1381
|
ssrParent.insertBefore(condAnchor, insertRef);
|
|
@@ -1405,7 +1424,7 @@ export class TemplateElement extends HTMLElement {
|
|
|
1405
1424
|
anchor = marker;
|
|
1406
1425
|
}
|
|
1407
1426
|
else {
|
|
1408
|
-
anchor =
|
|
1427
|
+
anchor = ownerDocument.createComment('');
|
|
1409
1428
|
const [, beforeIndex] = slotMeta;
|
|
1410
1429
|
const tplParent = tplElements[parentIndex];
|
|
1411
1430
|
const staticCount = tplParent ? tplParent.childNodes.length : 0;
|
|
@@ -1447,7 +1466,7 @@ export class TemplateElement extends HTMLElement {
|
|
|
1447
1466
|
else {
|
|
1448
1467
|
const itemParent = itemMarkers[j].parentNode;
|
|
1449
1468
|
const nextBound = j + 1 < itemMarkers.length ? itemMarkers[j + 1] : endMarker;
|
|
1450
|
-
const wrapper =
|
|
1469
|
+
const wrapper = ownerDocument.createElement('div');
|
|
1451
1470
|
let cursor = itemMarkers[j].nextSibling;
|
|
1452
1471
|
while (cursor && cursor !== nextBound) {
|
|
1453
1472
|
const next = cursor.nextSibling;
|
|
@@ -1553,7 +1572,7 @@ export class TemplateElement extends HTMLElement {
|
|
|
1553
1572
|
const condNodes = this.$collectConditionalRange(condAnchor);
|
|
1554
1573
|
if (condNodes.length === 0)
|
|
1555
1574
|
return null;
|
|
1556
|
-
const wrapper =
|
|
1575
|
+
const wrapper = this.ownerDocument.createElement('div');
|
|
1557
1576
|
for (let cn = 0; cn < condNodes.length; cn++)
|
|
1558
1577
|
wrapper.appendChild(condNodes[cn]);
|
|
1559
1578
|
const inst = this.$hydrate(wrapper, blockMeta, tplDom, scope);
|
|
@@ -2006,7 +2025,7 @@ export class TemplateElement extends HTMLElement {
|
|
|
2006
2025
|
const first = instance.nodes[0] ?? null;
|
|
2007
2026
|
const container = first?.parentNode;
|
|
2008
2027
|
if (first && container) {
|
|
2009
|
-
const anchor =
|
|
2028
|
+
const anchor = this.ownerDocument.createComment('');
|
|
2010
2029
|
container.insertBefore(anchor, first);
|
|
2011
2030
|
this.$swapOwnedRange(c.owner, instance.nodes, anchor);
|
|
2012
2031
|
c.anchor = anchor;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@microsoft/webui-framework",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"tag": "latest"
|
|
6
6
|
},
|
|
@@ -20,6 +20,12 @@
|
|
|
20
20
|
"default": "./dist/component-asset.js"
|
|
21
21
|
}
|
|
22
22
|
},
|
|
23
|
+
"./component-asset-runtime.js": {
|
|
24
|
+
"import": {
|
|
25
|
+
"types": "./dist/component-asset-runtime.d.ts",
|
|
26
|
+
"default": "./dist/component-asset-runtime.js"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
23
29
|
"./streaming.js": {
|
|
24
30
|
"import": {
|
|
25
31
|
"types": "./dist/streaming-entry.d.ts",
|
|
@@ -43,7 +49,7 @@
|
|
|
43
49
|
"dist/"
|
|
44
50
|
],
|
|
45
51
|
"devDependencies": {
|
|
46
|
-
"@microsoft/webui-test-support": "0.0
|
|
52
|
+
"@microsoft/webui-test-support": "3.0.0",
|
|
47
53
|
"@playwright/test": "^1.63.0",
|
|
48
54
|
"@types/node": "^26.5.1",
|
|
49
55
|
"typescript": "7.0.2"
|