@microsoft/fast-html 1.0.0-alpha.40 → 1.0.0-alpha.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -313,13 +313,36 @@ Where the right operand can be either a reference to a value (string e.g. `{{foo
|
|
|
313
313
|
<ul><f-repeat value="{{item in list}}"><li>{{item}}</li></f-repeat></ul>
|
|
314
314
|
```
|
|
315
315
|
|
|
316
|
-
|
|
316
|
+
Bindings inside `<f-repeat>` without a context prefix resolve to the custom element. For example, `{{title}}` below resolves to the host element's `title` property:
|
|
317
317
|
|
|
318
|
-
Example:
|
|
319
318
|
```html
|
|
320
319
|
<ul><f-repeat value="{{item in list}}"><li>{{item}} - {{title}}</li></f-repeat></ul>
|
|
321
320
|
```
|
|
322
321
|
|
|
322
|
+
#### Execution Context Access
|
|
323
|
+
|
|
324
|
+
In imperative fast-element templates, every binding expression receives both the data source and the execution context: `${(x, c) => c.parent.handleClick(c.event)}`. Declarative `<f-template>` expressions can access the same execution context using the `$c` prefix.
|
|
325
|
+
|
|
326
|
+
This is particularly useful inside `<f-repeat>`, where `$c.parent` refers to the parent view-model (typically the host element) and `$c.event` provides the DOM event.
|
|
327
|
+
|
|
328
|
+
Event handler with context access:
|
|
329
|
+
|
|
330
|
+
```html
|
|
331
|
+
<f-repeat value="{{item in items}}">
|
|
332
|
+
<button @click="{$c.parent.handleItemClick($c.event)}">{{item.name}}</button>
|
|
333
|
+
</f-repeat>
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Conditional rendering using a host property inside a repeat:
|
|
337
|
+
|
|
338
|
+
```html
|
|
339
|
+
<f-repeat value="{{item in items}}">
|
|
340
|
+
<f-when value="{{$c.parent.showNames}}">
|
|
341
|
+
<span>{{item.name}}</span>
|
|
342
|
+
</f-when>
|
|
343
|
+
</f-repeat>
|
|
344
|
+
```
|
|
345
|
+
|
|
323
346
|
#### Unescaped HTML
|
|
324
347
|
|
|
325
348
|
You can add unescaped HTML using triple braces, this will create an additional `div` element as the HTML needs an element to bind to. Where possible it is advisable to not use unescaped HTML and instead use other binding techniques.
|
|
@@ -3,7 +3,7 @@ import { attr, children, elements, FAST, FASTElement, FASTElementDefinition, fas
|
|
|
3
3
|
import "@microsoft/fast-element/install-hydratable-view-templates.js";
|
|
4
4
|
import { ObserverMap } from "./observer-map.js";
|
|
5
5
|
import { Schema } from "./schema.js";
|
|
6
|
-
import { bindingResolver, getExpressionChain, getNextBehavior, getRootPropertyName, resolveWhen, transformInnerHTML, } from "./utilities.js";
|
|
6
|
+
import { bindingResolver, contextPrefixDot, getExpressionChain, getNextBehavior, getRootPropertyName, resolveWhen, transformInnerHTML, } from "./utilities.js";
|
|
7
7
|
/**
|
|
8
8
|
* Values for the observerMap element option.
|
|
9
9
|
*/
|
|
@@ -69,24 +69,24 @@ class TemplateElement extends FASTElement {
|
|
|
69
69
|
}
|
|
70
70
|
this.schema = new Schema(name);
|
|
71
71
|
FASTElementDefinition.registerAsync(name).then(async (value) => {
|
|
72
|
-
var _a, _b,
|
|
72
|
+
var _a, _b, _d, _e, _f, _g, _h;
|
|
73
73
|
(_b = (_a = TemplateElement.lifecycleCallbacks).elementDidRegister) === null || _b === void 0 ? void 0 : _b.call(_a, name);
|
|
74
|
-
if (!((
|
|
74
|
+
if (!((_d = TemplateElement.elementOptions) === null || _d === void 0 ? void 0 : _d[name])) {
|
|
75
75
|
TemplateElement.setOptions(name);
|
|
76
76
|
}
|
|
77
|
-
if (((
|
|
77
|
+
if (((_e = TemplateElement.elementOptions[name]) === null || _e === void 0 ? void 0 : _e.observerMap) === "all") {
|
|
78
78
|
this.observerMap = new ObserverMap(value.prototype, this.schema);
|
|
79
79
|
}
|
|
80
80
|
const registeredFastElement = fastElementRegistry.getByType(value);
|
|
81
81
|
const template = this.getElementsByTagName("template").item(0);
|
|
82
82
|
if (template) {
|
|
83
83
|
// Callback: Before template has been evaluated and assigned
|
|
84
|
-
(
|
|
84
|
+
(_g = (_f = TemplateElement.lifecycleCallbacks).templateWillUpdate) === null || _g === void 0 ? void 0 : _g.call(_f, name);
|
|
85
85
|
const innerHTML = transformInnerHTML(this.innerHTML);
|
|
86
86
|
// Cache paths during template processing (pass undefined if observerMap is not available)
|
|
87
87
|
const { strings, values } = await this.resolveStringsAndValues(null, innerHTML, false, null, 0, this.schema, this.observerMap);
|
|
88
88
|
// Define the root properties cached in the observer map as observable (only if observerMap exists)
|
|
89
|
-
(
|
|
89
|
+
(_h = this.observerMap) === null || _h === void 0 ? void 0 : _h.defineProperties();
|
|
90
90
|
if (registeredFastElement) {
|
|
91
91
|
// Attach lifecycle callbacks to the definition before assigning template
|
|
92
92
|
// This allows the Observable notification to trigger the callbacks
|
|
@@ -226,7 +226,14 @@ class TemplateElement extends FASTElement {
|
|
|
226
226
|
rootPropertyName = getRootPropertyName(rootPropertyName, propName, parentContext, type);
|
|
227
227
|
const arg = bindingHTML.slice(openingParenthesis + 1, closingParenthesis);
|
|
228
228
|
const binding = bindingResolver(strings.join(""), rootPropertyName, propName, parentContext, type, schema, parentContext, level);
|
|
229
|
-
const
|
|
229
|
+
const isContextPath = propName.startsWith(contextPrefixDot);
|
|
230
|
+
const getOwner = isContextPath
|
|
231
|
+
? (_x, c) => {
|
|
232
|
+
const ownerPath = propName.split(".").slice(1, -1);
|
|
233
|
+
return ownerPath.reduce((prev, item) => prev === null || prev === void 0 ? void 0 : prev[item], c);
|
|
234
|
+
}
|
|
235
|
+
: (x, _c) => x;
|
|
236
|
+
const attributeBinding = (x, c) => binding(x, c).bind(getOwner(x, c))(...(arg === "e" ? [c.event] : []), ...(arg !== "e" && arg !== ""
|
|
230
237
|
? [
|
|
231
238
|
bindingResolver(strings.join(""), rootPropertyName, arg, parentContext, type, schema, parentContext, level)(x, c),
|
|
232
239
|
]
|
|
@@ -10,6 +10,8 @@ const openTagStart = "<f-";
|
|
|
10
10
|
const tagEnd = ">";
|
|
11
11
|
const closeTagStart = "</f-";
|
|
12
12
|
const attributeDirectivePrefix = "f-";
|
|
13
|
+
export const contextPrefix = "$c";
|
|
14
|
+
export const contextPrefixDot = `${contextPrefix}.`;
|
|
13
15
|
const startInnerHTMLDiv = `<div :innerHTML="{{`;
|
|
14
16
|
const startInnerHTMLDivLength = startInnerHTMLDiv.length;
|
|
15
17
|
const endInnerHTMLDiv = `}}"></div>`;
|
|
@@ -318,6 +320,13 @@ function isLegitimateClientSideBinding(result) {
|
|
|
318
320
|
export function pathResolver(path, contextPath, level, rootSchema) {
|
|
319
321
|
var _a, _b;
|
|
320
322
|
let splitPath = path.split(".");
|
|
323
|
+
// Explicit context access via contextPrefix — resolve directly from ExecutionContext
|
|
324
|
+
if (splitPath[0] === contextPrefix) {
|
|
325
|
+
const contextAccessPath = splitPath.slice(1);
|
|
326
|
+
return (_accessibleObject, context) => {
|
|
327
|
+
return contextAccessPath.reduce((prev, item) => prev === null || prev === void 0 ? void 0 : prev[item], context);
|
|
328
|
+
};
|
|
329
|
+
}
|
|
321
330
|
let levelCount = level;
|
|
322
331
|
let self = splitPath[0] === contextPath;
|
|
323
332
|
const parentContexts = [];
|
|
@@ -370,6 +379,13 @@ function pathWithContextResolver(splitPath, self) {
|
|
|
370
379
|
};
|
|
371
380
|
}
|
|
372
381
|
export function bindingResolver(previousString, rootPropertyName, path, parentContext, type, schema, currentContext, level) {
|
|
382
|
+
// Explicit context access — resolve from ExecutionContext, skip schema tracking
|
|
383
|
+
if (path.startsWith(contextPrefixDot)) {
|
|
384
|
+
const segments = path.split(".").slice(1);
|
|
385
|
+
return (_x, context) => {
|
|
386
|
+
return segments.reduce((prev, item) => prev === null || prev === void 0 ? void 0 : prev[item], context);
|
|
387
|
+
};
|
|
388
|
+
}
|
|
373
389
|
rootPropertyName = getRootPropertyName(rootPropertyName, path, currentContext, type);
|
|
374
390
|
if (type !== "event" && rootPropertyName !== null) {
|
|
375
391
|
const childrenMap = getChildrenMap(previousString);
|
|
@@ -391,6 +407,8 @@ export function expressionResolver(rootPropertyName, expression, parentContext,
|
|
|
391
407
|
if (rootPropertyName !== null) {
|
|
392
408
|
const paths = extractPathsFromChainedExpression(expression);
|
|
393
409
|
paths.forEach(path => {
|
|
410
|
+
if (path.startsWith(contextPrefixDot))
|
|
411
|
+
return;
|
|
394
412
|
schema.addPath({
|
|
395
413
|
pathConfig: {
|
|
396
414
|
type: "access",
|