@microsoft/webui-framework 0.0.12 → 0.0.13
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/dist/decorators.d.ts +2 -0
- package/dist/decorators.js +150 -33
- package/dist/element.d.ts +8 -0
- package/dist/element.js +96 -34
- package/package.json +2 -2
package/dist/decorators.d.ts
CHANGED
|
@@ -36,6 +36,7 @@ export declare function getObservableNames(ctor: Function): Set<string>;
|
|
|
36
36
|
* only bindings that reference this property.
|
|
37
37
|
*/
|
|
38
38
|
export declare function observable(target: object, name: string): void;
|
|
39
|
+
export declare function isAttributeProperty(ctor: Function, property: string): boolean;
|
|
39
40
|
/**
|
|
40
41
|
* Like {@link observable} but also reflects to/from an HTML attribute
|
|
41
42
|
* (kebab-case). The decorator patches `observedAttributes` and
|
|
@@ -56,5 +57,6 @@ export interface AttrOptions {
|
|
|
56
57
|
* Default is string mode (property receives the attribute string value). */
|
|
57
58
|
mode?: 'boolean';
|
|
58
59
|
}
|
|
60
|
+
export declare function syncAttrProperties(instance: object, ctor: Function): void;
|
|
59
61
|
export declare function attr(target: object, name: string): void;
|
|
60
62
|
export declare function attr(options: AttrOptions): (target: object, name: string) => void;
|
package/dist/decorators.js
CHANGED
|
@@ -84,11 +84,12 @@ export function toKebabCase(str) {
|
|
|
84
84
|
}
|
|
85
85
|
return out;
|
|
86
86
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
87
|
+
const reflectingAttribute = Symbol('webui.reflectingAttribute');
|
|
88
|
+
function parentConstructor(ctor) {
|
|
89
|
+
const parent = Object.getPrototypeOf(ctor);
|
|
90
|
+
return typeof parent === 'function' && parent !== Function.prototype ? parent : null;
|
|
91
|
+
}
|
|
92
|
+
function createReactiveProperty(proto, name, attrDefinition) {
|
|
92
93
|
const backingKey = `_${name}`;
|
|
93
94
|
const changedKey = `${name}Changed`;
|
|
94
95
|
Object.defineProperty(proto, name, {
|
|
@@ -97,9 +98,12 @@ function createReactiveProperty(proto, name) {
|
|
|
97
98
|
},
|
|
98
99
|
set(newValue) {
|
|
99
100
|
const oldValue = this[backingKey];
|
|
100
|
-
if (oldValue
|
|
101
|
+
if (Object.is(oldValue, newValue))
|
|
101
102
|
return;
|
|
102
103
|
this[backingKey] = newValue;
|
|
104
|
+
if (attrDefinition && this['$ready'] === true) {
|
|
105
|
+
reflectPropertyToAttribute(this, attrDefinition, newValue);
|
|
106
|
+
}
|
|
103
107
|
const cb = this[changedKey];
|
|
104
108
|
if (typeof cb === 'function') {
|
|
105
109
|
cb.call(this, oldValue, newValue);
|
|
@@ -114,6 +118,54 @@ function createReactiveProperty(proto, name) {
|
|
|
114
118
|
configurable: true,
|
|
115
119
|
});
|
|
116
120
|
}
|
|
121
|
+
function reflectPropertyToAttribute(instance, definition, value) {
|
|
122
|
+
const element = instance;
|
|
123
|
+
const attrName = definition.attribute;
|
|
124
|
+
if (definition.boolean) {
|
|
125
|
+
const shouldHaveAttribute = Boolean(value);
|
|
126
|
+
if (element.hasAttribute(attrName) === shouldHaveAttribute)
|
|
127
|
+
return;
|
|
128
|
+
setReflectingAttribute(instance, attrName);
|
|
129
|
+
try {
|
|
130
|
+
if (shouldHaveAttribute)
|
|
131
|
+
element.setAttribute(attrName, '');
|
|
132
|
+
else
|
|
133
|
+
element.removeAttribute(attrName);
|
|
134
|
+
}
|
|
135
|
+
finally {
|
|
136
|
+
restoreReflectingAttribute(instance);
|
|
137
|
+
}
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (value == null) {
|
|
141
|
+
if (!element.hasAttribute(attrName))
|
|
142
|
+
return;
|
|
143
|
+
setReflectingAttribute(instance, attrName);
|
|
144
|
+
try {
|
|
145
|
+
element.removeAttribute(attrName);
|
|
146
|
+
}
|
|
147
|
+
finally {
|
|
148
|
+
restoreReflectingAttribute(instance);
|
|
149
|
+
}
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const attrValue = typeof value === 'string' ? value : String(value);
|
|
153
|
+
if (element.getAttribute(attrName) === attrValue)
|
|
154
|
+
return;
|
|
155
|
+
setReflectingAttribute(instance, attrName);
|
|
156
|
+
try {
|
|
157
|
+
element.setAttribute(attrName, attrValue);
|
|
158
|
+
}
|
|
159
|
+
finally {
|
|
160
|
+
restoreReflectingAttribute(instance);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function setReflectingAttribute(instance, attrName) {
|
|
164
|
+
instance[reflectingAttribute] = attrName;
|
|
165
|
+
}
|
|
166
|
+
function restoreReflectingAttribute(instance) {
|
|
167
|
+
instance[reflectingAttribute] = undefined;
|
|
168
|
+
}
|
|
117
169
|
// ---------------------------------------------------------------------------
|
|
118
170
|
// @observable
|
|
119
171
|
// ---------------------------------------------------------------------------
|
|
@@ -124,7 +176,21 @@ const observableRegistry = new WeakMap();
|
|
|
124
176
|
*/
|
|
125
177
|
const EMPTY_SET = Object.freeze(new Set());
|
|
126
178
|
export function getObservableNames(ctor) {
|
|
127
|
-
|
|
179
|
+
const names = observableRegistry.get(ctor);
|
|
180
|
+
if (names)
|
|
181
|
+
return names;
|
|
182
|
+
const parent = parentConstructor(ctor);
|
|
183
|
+
return parent ? getObservableNames(parent) : EMPTY_SET;
|
|
184
|
+
}
|
|
185
|
+
function registerObservableProperty(ctor, name) {
|
|
186
|
+
let names = observableRegistry.get(ctor);
|
|
187
|
+
if (!names) {
|
|
188
|
+
const parent = parentConstructor(ctor);
|
|
189
|
+
const inherited = parent ? getObservableNames(parent) : EMPTY_SET;
|
|
190
|
+
names = inherited.size > 0 ? new Set(inherited) : new Set();
|
|
191
|
+
observableRegistry.set(ctor, names);
|
|
192
|
+
}
|
|
193
|
+
names.add(name);
|
|
128
194
|
}
|
|
129
195
|
/**
|
|
130
196
|
* Marks a property as observable. When the value changes the decorator will:
|
|
@@ -134,10 +200,7 @@ export function getObservableNames(ctor) {
|
|
|
134
200
|
*/
|
|
135
201
|
export function observable(target, name) {
|
|
136
202
|
const ctor = target.constructor;
|
|
137
|
-
|
|
138
|
-
observableRegistry.set(ctor, new Set());
|
|
139
|
-
}
|
|
140
|
-
observableRegistry.get(ctor).add(name);
|
|
203
|
+
registerObservableProperty(ctor, name);
|
|
141
204
|
createReactiveProperty(target, name);
|
|
142
205
|
}
|
|
143
206
|
// ---------------------------------------------------------------------------
|
|
@@ -147,30 +210,74 @@ export function observable(target, name) {
|
|
|
147
210
|
* Registry of attribute-name → property-name mappings per constructor.
|
|
148
211
|
* Used by `attributeChangedCallback` to route attribute changes to properties.
|
|
149
212
|
*/
|
|
150
|
-
const
|
|
151
|
-
/** Registry of
|
|
152
|
-
const
|
|
213
|
+
const attrByAttribute = new WeakMap();
|
|
214
|
+
/** Registry of property-name → attribute metadata, used for mount-time sync. */
|
|
215
|
+
const attrByProperty = new WeakMap();
|
|
216
|
+
function inheritedAttrMap(registry, ctor) {
|
|
217
|
+
let current = parentConstructor(ctor);
|
|
218
|
+
while (current) {
|
|
219
|
+
const map = registry.get(current);
|
|
220
|
+
if (map)
|
|
221
|
+
return map;
|
|
222
|
+
current = parentConstructor(current);
|
|
223
|
+
}
|
|
224
|
+
return undefined;
|
|
225
|
+
}
|
|
226
|
+
function attrDefinitionFor(ctor, attribute) {
|
|
227
|
+
let current = ctor;
|
|
228
|
+
while (current) {
|
|
229
|
+
const definition = attrByAttribute.get(current)?.get(attribute);
|
|
230
|
+
if (definition)
|
|
231
|
+
return definition;
|
|
232
|
+
current = parentConstructor(current);
|
|
233
|
+
}
|
|
234
|
+
return undefined;
|
|
235
|
+
}
|
|
236
|
+
function attrPropertyMapFor(ctor) {
|
|
237
|
+
let current = ctor;
|
|
238
|
+
while (current) {
|
|
239
|
+
const map = attrByProperty.get(current);
|
|
240
|
+
if (map)
|
|
241
|
+
return map;
|
|
242
|
+
current = parentConstructor(current);
|
|
243
|
+
}
|
|
244
|
+
return undefined;
|
|
245
|
+
}
|
|
246
|
+
export function isAttributeProperty(ctor, property) {
|
|
247
|
+
return attrPropertyMapFor(ctor)?.has(property) === true;
|
|
248
|
+
}
|
|
153
249
|
function applyAttr(target, name, options) {
|
|
154
250
|
const proto = target;
|
|
155
251
|
const ctor = proto.constructor;
|
|
156
|
-
// 1. Install the reactive getter/setter (same as @observable).
|
|
157
|
-
createReactiveProperty(proto, name);
|
|
158
|
-
// 2. Register the attribute mapping.
|
|
159
252
|
const attrName = options?.attribute ?? toKebabCase(name);
|
|
160
|
-
|
|
161
|
-
|
|
253
|
+
const definition = {
|
|
254
|
+
attribute: attrName,
|
|
255
|
+
property: name,
|
|
256
|
+
boolean: options?.mode === 'boolean',
|
|
257
|
+
};
|
|
258
|
+
// 1. Install the reactive getter/setter (same as @observable), with
|
|
259
|
+
// attribute reflection enabled after the element finishes hydration.
|
|
260
|
+
registerObservableProperty(ctor, name);
|
|
261
|
+
createReactiveProperty(proto, name, definition);
|
|
262
|
+
// 2. Register the attribute mapping.
|
|
263
|
+
let byAttribute = attrByAttribute.get(ctor);
|
|
264
|
+
if (!byAttribute) {
|
|
265
|
+
const inherited = inheritedAttrMap(attrByAttribute, ctor);
|
|
266
|
+
byAttribute = inherited ? new Map(inherited) : new Map();
|
|
267
|
+
attrByAttribute.set(ctor, byAttribute);
|
|
162
268
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if (
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
boolAttrs.get(ctor).add(attrName);
|
|
269
|
+
byAttribute.set(attrName, definition);
|
|
270
|
+
let byProperty = attrByProperty.get(ctor);
|
|
271
|
+
if (!byProperty) {
|
|
272
|
+
const inherited = inheritedAttrMap(attrByProperty, ctor);
|
|
273
|
+
byProperty = inherited ? new Map(inherited) : new Map();
|
|
274
|
+
attrByProperty.set(ctor, byProperty);
|
|
170
275
|
}
|
|
276
|
+
byProperty.set(name, definition);
|
|
171
277
|
// 3. Accumulate observed attributes on the constructor.
|
|
172
|
-
if (!ctor
|
|
173
|
-
ctor._observedAttrs
|
|
278
|
+
if (!Object.prototype.hasOwnProperty.call(ctor, '_observedAttrs')) {
|
|
279
|
+
const inheritedAttrs = ctor._observedAttrs;
|
|
280
|
+
ctor._observedAttrs = inheritedAttrs ? inheritedAttrs.slice() : [];
|
|
174
281
|
// Define the static getter that `customElements.define` inspects.
|
|
175
282
|
Object.defineProperty(ctor, 'observedAttributes', {
|
|
176
283
|
get() {
|
|
@@ -182,11 +289,12 @@ function applyAttr(target, name, options) {
|
|
|
182
289
|
const origACB = proto['attributeChangedCallback'];
|
|
183
290
|
proto['attributeChangedCallback'] = function (attribute, oldVal, newVal) {
|
|
184
291
|
// Route the attribute change to the corresponding property.
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
292
|
+
const definition = attrDefinitionFor(this.constructor, attribute);
|
|
293
|
+
if (definition !== undefined &&
|
|
294
|
+
this[reflectingAttribute] !== attribute) {
|
|
295
|
+
this[definition.property] = definition.boolean
|
|
296
|
+
? newVal !== null
|
|
297
|
+
: newVal;
|
|
190
298
|
}
|
|
191
299
|
// Preserve any pre-existing attributeChangedCallback.
|
|
192
300
|
if (origACB) {
|
|
@@ -196,6 +304,15 @@ function applyAttr(target, name, options) {
|
|
|
196
304
|
}
|
|
197
305
|
ctor._observedAttrs.push(attrName);
|
|
198
306
|
}
|
|
307
|
+
export function syncAttrProperties(instance, ctor) {
|
|
308
|
+
const attrs = attrPropertyMapFor(ctor);
|
|
309
|
+
if (!attrs)
|
|
310
|
+
return;
|
|
311
|
+
const reactiveInstance = instance;
|
|
312
|
+
for (const definition of attrs.values()) {
|
|
313
|
+
reflectPropertyToAttribute(reactiveInstance, definition, reactiveInstance[definition.property]);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
199
316
|
export function attr(targetOrOptions, name) {
|
|
200
317
|
if (typeof name === 'string') {
|
|
201
318
|
applyAttr(targetOrOptions, name);
|
package/dist/element.d.ts
CHANGED
|
@@ -81,6 +81,14 @@ export declare class WebUIElement extends HTMLElement {
|
|
|
81
81
|
* Starts searching from `after` (exclusive) if provided, or from firstChild.
|
|
82
82
|
*/
|
|
83
83
|
private $findMarker;
|
|
84
|
+
/**
|
|
85
|
+
* Check whether there is non-marker content between a conditional
|
|
86
|
+
* start anchor and its closing marker. Used during SSR hydration to
|
|
87
|
+
* detect server-rendered conditional content even when the runtime
|
|
88
|
+
* condition value has not been set yet (e.g. complex property from a
|
|
89
|
+
* parent repeat binding that hydrates after its children).
|
|
90
|
+
*/
|
|
91
|
+
private $hasContentAfterMarker;
|
|
84
92
|
/**
|
|
85
93
|
* Find existing SSR text node by mapping template text-node ordinal.
|
|
86
94
|
*
|
package/dist/element.js
CHANGED
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
*/
|
|
41
41
|
import { getTemplate } from './template.js';
|
|
42
42
|
import { hydrationStart, hydrationEnd } from './lifecycle.js';
|
|
43
|
-
import { getObservableNames } from './decorators.js';
|
|
43
|
+
import { getObservableNames, isAttributeProperty, syncAttrProperties } from './decorators.js';
|
|
44
44
|
import { syncRepeat, dotWalk } from './element/diff.js';
|
|
45
45
|
import { collectItemMarkers, nextElement, findByOrdinal, MARKER_COND_START, MARKER_COND_END, MARKER_REPEAT_START, } from './element/markers.js';
|
|
46
46
|
import { injectModuleStyle, } from './element/styles.js';
|
|
@@ -201,6 +201,7 @@ export class WebUIElement extends HTMLElement {
|
|
|
201
201
|
this.$meta = meta;
|
|
202
202
|
this.$hydrated = true;
|
|
203
203
|
this.$ready = true;
|
|
204
|
+
syncAttrProperties(this, this.constructor);
|
|
204
205
|
// Client-created components: flush current attr/observable values
|
|
205
206
|
// into the freshly-wired template DOM. Call $updateInstance directly
|
|
206
207
|
// to avoid the $update() path-index build — it will be lazy-built
|
|
@@ -299,9 +300,10 @@ export class WebUIElement extends HTMLElement {
|
|
|
299
300
|
const state = window.__webui?.state;
|
|
300
301
|
if (!state || typeof state !== 'object')
|
|
301
302
|
return;
|
|
302
|
-
const
|
|
303
|
+
const ctor = this.constructor;
|
|
304
|
+
const names = getObservableNames(ctor);
|
|
303
305
|
for (const key of Object.keys(state)) {
|
|
304
|
-
if (names.has(key)) {
|
|
306
|
+
if (names.has(key) && !isAttributeProperty(ctor, key)) {
|
|
305
307
|
// Write to backing field directly — no reactive update yet
|
|
306
308
|
this[`_${key}`] = state[key];
|
|
307
309
|
}
|
|
@@ -639,8 +641,18 @@ export class WebUIElement extends HTMLElement {
|
|
|
639
641
|
}
|
|
640
642
|
if (marker)
|
|
641
643
|
lastCondMarker = marker;
|
|
642
|
-
|
|
643
|
-
|
|
644
|
+
// SSR hydration: if the marker exists, the server rendered this
|
|
645
|
+
// conditional — hydrate its content regardless of the current
|
|
646
|
+
// condition value. Complex properties from parent bindings may
|
|
647
|
+
// not have arrived yet (the parent hydrates after children), so
|
|
648
|
+
// the condition could evaluate to false even though the server
|
|
649
|
+
// rendered it as true. Trust the SSR DOM and wire it up; the
|
|
650
|
+
// condition will re-evaluate correctly once all data is set.
|
|
651
|
+
const ssrContentPresent = marker && blockMeta && this.$hasContentAfterMarker(condAnchor, MARKER_COND_END);
|
|
652
|
+
if (blockMeta && (shown || ssrContentPresent)) {
|
|
653
|
+
if (marker) {
|
|
654
|
+
condInstance = this.$hydrateCondContent(condAnchor, blockMeta, scope);
|
|
655
|
+
}
|
|
644
656
|
}
|
|
645
657
|
// Collect <!--/wc--> end marker for deferred removal.
|
|
646
658
|
// Do NOT remove here — later phases (repeats, events) still need
|
|
@@ -830,9 +842,12 @@ export class WebUIElement extends HTMLElement {
|
|
|
830
842
|
// Single-root optimisation: hydrate the element in-place (pathStart=1).
|
|
831
843
|
const el = nextElement(condAnchor);
|
|
832
844
|
if (el) {
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
845
|
+
// Wire bindings only — do NOT call $updateInstance. SSR text
|
|
846
|
+
// nodes already contain correct values; evaluating bindings now
|
|
847
|
+
// would overwrite them with stale data (e.g. a complex property
|
|
848
|
+
// from a parent that hasn't hydrated yet). This is consistent
|
|
849
|
+
// with $mount which also skips $updateInstance for SSR roots.
|
|
850
|
+
return this.$hydrate(el, blockMeta, tplDom, scope, 1);
|
|
836
851
|
}
|
|
837
852
|
return null;
|
|
838
853
|
}
|
|
@@ -850,7 +865,7 @@ export class WebUIElement extends HTMLElement {
|
|
|
850
865
|
condAnchor.parentNode?.insertBefore(inst.nodes[cn], afterNode.nextSibling);
|
|
851
866
|
afterNode = inst.nodes[cn];
|
|
852
867
|
}
|
|
853
|
-
|
|
868
|
+
// Same as above — trust SSR DOM, skip binding evaluation.
|
|
854
869
|
return inst;
|
|
855
870
|
}
|
|
856
871
|
/**
|
|
@@ -867,6 +882,23 @@ export class WebUIElement extends HTMLElement {
|
|
|
867
882
|
}
|
|
868
883
|
return null;
|
|
869
884
|
}
|
|
885
|
+
/**
|
|
886
|
+
* Check whether there is non-marker content between a conditional
|
|
887
|
+
* start anchor and its closing marker. Used during SSR hydration to
|
|
888
|
+
* detect server-rendered conditional content even when the runtime
|
|
889
|
+
* condition value has not been set yet (e.g. complex property from a
|
|
890
|
+
* parent repeat binding that hydrates after its children).
|
|
891
|
+
*/
|
|
892
|
+
$hasContentAfterMarker(anchor, endData) {
|
|
893
|
+
let sibling = anchor.nextSibling;
|
|
894
|
+
while (sibling) {
|
|
895
|
+
if (sibling.nodeType === 8 && sibling.data === endData) {
|
|
896
|
+
return false; // reached end marker with no content in between
|
|
897
|
+
}
|
|
898
|
+
return true; // any non-end-marker node = content present
|
|
899
|
+
}
|
|
900
|
+
return false;
|
|
901
|
+
}
|
|
870
902
|
/**
|
|
871
903
|
* Find existing SSR text node by mapping template text-node ordinal.
|
|
872
904
|
*
|
|
@@ -1050,36 +1082,66 @@ export class WebUIElement extends HTMLElement {
|
|
|
1050
1082
|
const root = dot > -1 ? path.slice(0, dot) : path;
|
|
1051
1083
|
return observableNames.has(root) ? root : '*';
|
|
1052
1084
|
};
|
|
1053
|
-
const
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1085
|
+
const isLocalPath = (path, scope) => {
|
|
1086
|
+
const dot = path.indexOf('.');
|
|
1087
|
+
const root = dot > -1 ? path.slice(0, dot) : path;
|
|
1088
|
+
let current = scope;
|
|
1089
|
+
while (current) {
|
|
1090
|
+
if (current.name === root)
|
|
1091
|
+
return true;
|
|
1092
|
+
current = current.parent;
|
|
1093
|
+
}
|
|
1094
|
+
return false;
|
|
1095
|
+
};
|
|
1096
|
+
const visit = (instance) => {
|
|
1097
|
+
for (const t of instance.texts) {
|
|
1098
|
+
if (t.parts) {
|
|
1099
|
+
for (const p of t.parts) {
|
|
1100
|
+
if (typeof p !== 'string' && !isLocalPath(p[0], t.scope)) {
|
|
1101
|
+
ensure(keyFor(p[0])).texts.push(t);
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
else if (t.path && !isLocalPath(t.path, t.scope)) {
|
|
1106
|
+
ensure(keyFor(t.path)).texts.push(t);
|
|
1059
1107
|
}
|
|
1060
1108
|
}
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1109
|
+
for (const a of instance.attrs) {
|
|
1110
|
+
if (a.path && !isLocalPath(a.path, a.scope)) {
|
|
1111
|
+
ensure(keyFor(a.path)).attrs.push(a);
|
|
1112
|
+
}
|
|
1113
|
+
if (a.parts) {
|
|
1114
|
+
for (const p of a.parts) {
|
|
1115
|
+
if (typeof p !== 'string' && !isLocalPath(p[0], a.scope)) {
|
|
1116
|
+
ensure(keyFor(p[0])).attrs.push(a);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
if (a.condition) {
|
|
1121
|
+
for (const p of a.condition[1]) {
|
|
1122
|
+
if (!isLocalPath(p, a.scope))
|
|
1123
|
+
ensure(keyFor(p)).attrs.push(a);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
for (const c of instance.conds) {
|
|
1128
|
+
for (const p of c.condition[1]) {
|
|
1129
|
+
if (!isLocalPath(p, c.scope))
|
|
1130
|
+
ensure(keyFor(p)).conds.push(c);
|
|
1069
1131
|
}
|
|
1132
|
+
if (c.instance)
|
|
1133
|
+
visit(c.instance);
|
|
1070
1134
|
}
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
ensure(keyFor(
|
|
1135
|
+
for (const rep of instance.repeats) {
|
|
1136
|
+
if (!isLocalPath(rep.collection, rep.scope)) {
|
|
1137
|
+
ensure(keyFor(rep.collection)).repeats.push(rep);
|
|
1138
|
+
}
|
|
1139
|
+
for (let i = 0; i < rep.instances.length; i++) {
|
|
1140
|
+
visit(rep.instances[i].instance);
|
|
1141
|
+
}
|
|
1074
1142
|
}
|
|
1075
|
-
}
|
|
1076
|
-
|
|
1077
|
-
for (const p of c.condition[1])
|
|
1078
|
-
ensure(keyFor(p)).conds.push(c);
|
|
1079
|
-
}
|
|
1080
|
-
for (const rep of r.repeats) {
|
|
1081
|
-
ensure(keyFor(rep.collection)).repeats.push(rep);
|
|
1082
|
-
}
|
|
1143
|
+
};
|
|
1144
|
+
visit(this.$root);
|
|
1083
1145
|
// Store wildcard bindings separately — avoids duplicating them into every path
|
|
1084
1146
|
const wc = index.get('*');
|
|
1085
1147
|
if (wc) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@microsoft/webui-framework",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "WebUI Framework Next — Preact-inspired lightweight Web Component runtime with SSR hydration. 15KB minified, compiled-template path mapping, no hydration markers.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"@playwright/test": "^1.58.2",
|
|
20
20
|
"@types/node": "^25.3.5",
|
|
21
21
|
"typescript": "^5.9.3",
|
|
22
|
-
"@microsoft/webui-test-support": "0.0.
|
|
22
|
+
"@microsoft/webui-test-support": "0.0.13"
|
|
23
23
|
},
|
|
24
24
|
"scripts": {
|
|
25
25
|
"build": "tsc",
|