Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "major",
"comment": "remove deprecated declarative event e support",
"packageName": "@microsoft/fast-element",
"email": "7559015+janechu@users.noreply.github.com",
"dependentChangeType": "none"
}
2 changes: 0 additions & 2 deletions packages/fast-element/DECLARATIVE_DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,6 @@ All delimiters used by the parser are defined in a single `Syntax` interface and
| `whenDirectiveOpen` / `whenDirectiveClose` | `<f-when` / `</f-when>` | When directive |
| `attributeDirectivePrefix` | `f-` | Attribute directive prefix |
| `eventArgAccessor` | `$e` | DOM event argument |
| `deprecatedEventArgAccessor` | `e` | Deprecated DOM event argument (emits a deduplicated warning once per component) |
| `executionContextAccessor` | `$c` | Execution context argument |

---
Expand Down Expand Up @@ -351,7 +350,6 @@ sequenceDiagram
|---|---|---|
| `parse()` | public | Entry point: parses declarative HTML into `{ strings, values }`. |
| `createTemplate()` | public | Creates a `ViewTemplate` from resolved strings and values. |
| `hasDeprecatedEventSyntax` | public | Getter indicating whether the last parse encountered deprecated `e` event syntax. |
| `resolveStringsAndValues()` | private | Creates `strings`/`values` arrays and delegates to `resolveInnerHTML()`. |
| `resolveInnerHTML()` | private | Recursive HTML parser that dispatches to data binding or template directive handlers. |
| `resolveDataBinding()` | private | Thin dispatcher that routes to `resolveContentBinding()`, `resolveAttributeBinding()`, or `resolveAttributeDirectiveBinding()`. |
Expand Down
7 changes: 2 additions & 5 deletions packages/fast-element/DECLARATIVE_HTML.md
Original file line number Diff line number Diff line change
Expand Up @@ -358,15 +358,12 @@ You can pass the DOM event object, the execution context, or both as arguments.
<button @click="{handleClick($e, $c)}"></button>
```

**Arbitrary binding expressions** — any token that is not `$e`, `$c`, or `e` is resolved as a binding path on the data source:
**Arbitrary binding expressions** — any token that is not `$e` or `$c` is resolved as a binding path on the data source:
```html
<button @click="{handleClick(user.id)}"></button>
```

> **Deprecated:** The bare `e` token still works but will emit a console warning once per component. The warning includes the component name to help locate usage. Migrate to `$e`.
> ```html
> <button @click="{handleClick(e)}"></button>
> ```
Use `$e` for the DOM event object.

### Directives

Expand Down
2 changes: 1 addition & 1 deletion packages/fast-element/DECLARATIVE_RENDERING.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ Event bindings such as `@keydown` and `@click` can be represented with hydration

Example event binding:
```html
<button @click="{handleClick(e)}">Button</button>
<button @click="{handleClick($e)}">Button</button>
```

Should result in:
Expand Down
26 changes: 23 additions & 3 deletions packages/fast-element/MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,30 @@ removed `@microsoft/fast-html` package.
2. Update declarative utility imports such as `deepMerge` to
`@microsoft/fast-element/declarative/utilities.js`.
3. Keep importing core FAST Element APIs (for example `FASTElement`, `attr`,
`observable`) from `@microsoft/fast-element`.
`observable`) from `@microsoft/fast-element`.
4. Do not switch to the root `@microsoft/fast-element` barrel for declarative
APIs; the declarative entrypoint owns the debug-message and hydratable-view
side effects.
APIs; the declarative entrypoint owns the debug-message and hydratable-view
side effects.

## Declarative event handler `e` removal (v3)

### Removed behavior

| Removed | Replacement |
|---|---|
| Bare `e` event arguments in declarative event handlers | `$e` |
| `TemplateParser.hasDeprecatedEventSyntax` | No replacement |

Only `$e` and `$c` are reserved event handler arguments in declarative
templates. Bare `e` now resolves like any other binding path on the current
data source.

### Migration steps

1. Replace declarative event bindings such as
`@click="{handleClick(e)}"` with `@click="{handleClick($e)}"`.
2. Remove any `TemplateParser.hasDeprecatedEventSyntax` checks or warnings from
custom tooling.

## Prerendered Content Optimization (v2 → v3)

Expand Down
2 changes: 2 additions & 0 deletions packages/fast-element/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ TemplateElement.define({ name: "f-template" });
Declarative utilities such as `deepMerge` are available from
`@microsoft/fast-element/declarative/utilities.js`. See
[`DECLARATIVE_HTML.md`](./DECLARATIVE_HTML.md) for declarative usage details.
Declarative event bindings use `$e` for the DOM event object and `$c` for the
execution context.

## Prerendered Content Optimization

Expand Down
1 change: 0 additions & 1 deletion packages/fast-element/docs/declarative/api-report.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,6 @@ export class TemplateElement extends FASTElement {
export class TemplateParser {
// Warning: (ae-forgotten-export) The symbol "ViewTemplate" needs to be exported by the entry point declarative.d.ts
createTemplate(strings: Array<string>, values: Array<any>): ViewTemplate<any, any>;
get hasDeprecatedEventSyntax(): boolean;
parse(innerHTML: string, schema: Schema): ResolvedStringsAndValues;
}

Expand Down
3 changes: 0 additions & 3 deletions packages/fast-element/src/declarative/syntax.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ interface Syntax {
clientSideCloseExpression: string;
executionContextAccessor: string;
eventArgAccessor: string;
deprecatedEventArgAccessor: string;
openExpression: string;
closeExpression: string;
unescapedOpenExpression: string;
Expand All @@ -26,7 +25,6 @@ export const {
clientSideCloseExpression,
clientSideOpenExpression,
closeExpression,
deprecatedEventArgAccessor,
eventArgAccessor,
executionContextAccessor,
openExpression,
Expand All @@ -41,7 +39,6 @@ export const {
clientSideCloseExpression: "}",
clientSideOpenExpression: "{",
closeExpression: "}}",
deprecatedEventArgAccessor: "e",
eventArgAccessor: "$e",
executionContextAccessor: "$c",
openExpression: "{{",
Expand Down
24 changes: 0 additions & 24 deletions packages/fast-element/src/declarative/template-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,37 +72,18 @@ class StringsAccumulator {
*
* This class is intentionally stateless across invocations — all mutable
* parsing state lives on the call stack or in the `TemplateResolutionContext`.
* The only per-parse state is `_hasDeprecatedEventSyntax`, which is reset at
* the start of each `parse()` call.
*
* The parsing pipeline is fully synchronous — no promises are allocated
* during template resolution.
*/
export class TemplateParser {
/**
* Whether the template contains deprecated "e" event argument usage.
* Set during template processing; checked after parsing to emit a
* single warning per template.
*/
// TODO: remove per https://github.com/microsoft/fast/issues/7314
private _hasDeprecatedEventSyntax = false;

/**
* Whether the last parsed template contained deprecated "e" event syntax.
*/
public get hasDeprecatedEventSyntax(): boolean {
return this._hasDeprecatedEventSyntax;
}

/**
* Parse declarative HTML into strings and values for ViewTemplate creation.
* @param innerHTML - The transformed innerHTML to parse.
* @param schema - The Schema instance for property tracking.
* @returns The resolved strings and values.
*/
public parse(innerHTML: string, schema: Schema): ResolvedStringsAndValues {
this._hasDeprecatedEventSyntax = false;

return this.resolveStringsAndValues(null, innerHTML, {
parentContext: null,
level: 0,
Expand Down Expand Up @@ -370,14 +351,9 @@ export class TemplateParser {

const parsedArgs = parseEventArgs(argsString);

if (parsedArgs.some(a => a.type === "deprecated-event")) {
this._hasDeprecatedEventSyntax = true;
}

const argResolvers = parsedArgs.map((parsedArg): ((x: any, c: any) => any) => {
switch (parsedArg.type) {
case "event":
case "deprecated-event":
return (_x, c) => c.event;
case "context":
return (_x, c) => c;
Expand Down
10 changes: 1 addition & 9 deletions packages/fast-element/src/declarative/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { Message } from "./interfaces.js";
import { ObserverMap, type ObserverMapOption } from "./observer-map.js";
import { Schema } from "./schema.js";
import { TemplateParser } from "./template-parser.js";
import { eventArgAccessor, transformInnerHTML } from "./utilities.js";
import { transformInnerHTML } from "./utilities.js";

/**
* Checks whether a map option (observerMap or attributeMap) is enabled.
Expand Down Expand Up @@ -253,14 +253,6 @@ class TemplateElement extends FASTElement {

const { strings, values } = parser.parse(innerHTML, schema);

if (parser.hasDeprecatedEventSyntax) {
console.warn(
`[fast-element/declarative] Using "e" as an event argument is deprecated` +
` in component "${name}".` +
` Use "${eventArgAccessor}" instead.`,
);
}

// Define the root properties cached in the observer map as observable (only if observerMap exists)
this.observerMap?.defineProperties();

Expand Down
9 changes: 0 additions & 9 deletions packages/fast-element/src/declarative/utilities.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -941,9 +941,6 @@ test.describe("utilities", async () => {
test("should parse $e as an event argument", async () => {
expect(parseEventArgs(eventArgAccessor)).toEqual([{ type: "event" }]);
});
test("should parse e (no $) as a deprecated-event argument", async () => {
expect(parseEventArgs("e")).toEqual([{ type: "deprecated-event" }]);
});
test("should parse $c as a context argument", async () => {
expect(parseEventArgs("$c")).toEqual([{ type: "context" }]);
});
Expand Down Expand Up @@ -972,12 +969,6 @@ test.describe("utilities", async () => {
{ type: "context" },
]);
});
test("should parse e (deprecated) mixed with $c", async () => {
expect(parseEventArgs("e, $c")).toEqual([
{ type: "deprecated-event" },
{ type: "context" },
]);
});
test("should return a binding type for unrecognised tokens in a mixed list", async () => {
expect(parseEventArgs("$e, foo")).toEqual([
{ type: "event" },
Expand Down
11 changes: 5 additions & 6 deletions packages/fast-element/src/declarative/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
clientSideCloseExpression,
clientSideOpenExpression,
closeExpression,
deprecatedEventArgAccessor,
eventArgAccessor,
executionContextAccessor,
openExpression,
Expand Down Expand Up @@ -91,12 +90,12 @@ interface ObservedTargetsAndProperties {

export const contextPrefixDot: string = `${executionContextAccessor}.`;

export { deprecatedEventArgAccessor, eventArgAccessor, executionContextAccessor };
export { eventArgAccessor, executionContextAccessor };

/**
* The type of a parsed event handler argument.
*/
export type EventArgType = "event" | "deprecated-event" | "context" | "binding";
export type EventArgType = "event" | "context" | "binding";

/**
* A parsed event handler argument descriptor.
Expand All @@ -114,9 +113,11 @@ export interface ParsedEventArg {
*
* Special arguments:
* - `$e` — resolves to the DOM event object
* - `e` — resolves to the DOM event object (deprecated, use `$e`)
* - `$c` — resolves to the full execution context object
*
* Any other token is treated as a binding path and resolved against the current
* data source.
*
* @param argsString - The raw arguments string from between the parentheses,
* e.g. `""`, `"$e"`, `"$c"`, or `"$e, $c"`.
* @returns An array of {@link ParsedEventArg} descriptors.
Expand All @@ -132,8 +133,6 @@ export function parseEventArgs(argsString: string): ParsedEventArg[] {
switch (arg) {
case eventArgAccessor:
return { type: "event" };
case deprecatedEventArgAccessor:
return { type: "deprecated-event" };
case executionContextAccessor:
return { type: "context" };
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,36 +17,6 @@ test.describe("f-template", async () => {

expect(message).toEqual("no args");
});
test("create an event attribute with an event argument (deprecated e)", async ({
page,
}) => {
const warnings: string[] = [];
page.on("console", msg => {
if (msg.type() === "warning") {
warnings.push(msg.text());
}
});

const hydrationCompleted = page.waitForFunction(
() => (window as any).hydrationCompleted === true,
);
await page.goto("/fixtures/bindings/event/");
await hydrationCompleted;

const customElement = page.locator("test-element");

let message;
page.on("console", msg => (message = msg.text()));

await customElement.locator("button").nth(1).click();

expect(message).toEqual("click");

// The deprecation warning should include the component name
const deprecationWarnings = warnings.filter(w => w.includes("deprecated"));
expect(deprecationWarnings).toHaveLength(1);
expect(deprecationWarnings[0]).toContain('"test-element"');
});
test("should properly bind events with `this`", async ({ page }) => {
const hydrationCompleted = page.waitForFunction(
() => (window as any).hydrationCompleted === true,
Expand All @@ -58,7 +28,7 @@ test.describe("f-template", async () => {

await expect(customElement).toHaveJSProperty("foo", "bar");

await customElement.locator("button").nth(2).click();
await customElement.locator("button").nth(1).click();

await expect(customElement).toHaveJSProperty("foo", "modified-by-click");
});
Expand All @@ -74,7 +44,7 @@ test.describe("f-template", async () => {
let message;
page.on("console", msg => (message = msg.text()));

await customElement.locator("button").nth(3).click();
await customElement.locator("button").nth(2).click();

expect(message).toEqual("click");
});
Expand All @@ -90,7 +60,7 @@ test.describe("f-template", async () => {
let message;
page.on("console", msg => (message = msg.text()));

await customElement.locator("button").nth(4).click();
await customElement.locator("button").nth(3).click();

expect(message).toEqual("click");
});
Expand All @@ -108,7 +78,7 @@ test.describe("f-template", async () => {
let message;
page.on("console", msg => (message = msg.text()));

await customElement.locator("button").nth(5).click();
await customElement.locator("button").nth(4).click();

expect(message).toEqual("click,click");
});
Expand All @@ -124,7 +94,7 @@ test.describe("f-template", async () => {
let message;
page.on("console", msg => (message = msg.text()));

await customElement.locator("button").nth(6).click();
await customElement.locator("button").nth(5).click();

expect(message).toEqual("click");
});
Expand Down
Loading
Loading