@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
- Should you need to refer to the parent element (not the individual item in the list), you can use the context of a previous repeat, or no context which will resolve to the custom element.
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.
@@ -40,6 +40,8 @@ export interface ChildrenMap {
40
40
  customElementName: string;
41
41
  attributeName: string;
42
42
  }
43
+ export declare const contextPrefix: string;
44
+ export declare const contextPrefixDot: string;
43
45
  declare const LogicalOperator: {
44
46
  AND: string;
45
47
  OR: string;
@@ -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, _c, _d, _e, _f, _g;
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 (!((_c = TemplateElement.elementOptions) === null || _c === void 0 ? void 0 : _c[name])) {
74
+ if (!((_d = TemplateElement.elementOptions) === null || _d === void 0 ? void 0 : _d[name])) {
75
75
  TemplateElement.setOptions(name);
76
76
  }
77
- if (((_d = TemplateElement.elementOptions[name]) === null || _d === void 0 ? void 0 : _d.observerMap) === "all") {
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
- (_f = (_e = TemplateElement.lifecycleCallbacks).templateWillUpdate) === null || _f === void 0 ? void 0 : _f.call(_e, name);
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
- (_g = this.observerMap) === null || _g === void 0 ? void 0 : _g.defineProperties();
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 attributeBinding = (x, c) => binding(x, c).bind(x)(...(arg === "e" ? [c.event] : []), ...(arg !== "e" && arg !== ""
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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@microsoft/fast-html",
3
- "version": "1.0.0-alpha.40",
3
+ "version": "1.0.0-alpha.41",
4
4
  "type": "module",
5
5
  "author": {
6
6
  "name": "Microsoft",