Skip to content

Commit 3f73d7a

Browse files
authored
Merge pull request #330 from github/copilot/add-controller-name-override
Add optional element name parameter to @controller decorator
2 parents 466f80b + 1a068b4 commit 3f73d7a

File tree

11 files changed

+84
-18
lines changed

11 files changed

+84
-18
lines changed

.devcontainer/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.222.0/containers/javascript-node/.devcontainer/base.Dockerfile
22

33
# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 16, 14, 12, 16-bullseye, 14-bullseye, 12-bullseye, 16-buster, 14-buster, 12-buster
4-
ARG VARIANT="16"
4+
ARG VARIANT="24"
55
FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT}
66

77
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \

.devcontainer/devcontainer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
// Update 'VARIANT' to pick a Node version: 16, 14, 12.
88
// Append -bullseye or -buster to pin to an OS version.
99
// Use -bullseye variants on local arm64/Apple Silicon.
10-
"args": { "VARIANT": "16" }
10+
"args": { "VARIANT": "24" }
1111
},
1212

1313
// Set *default* container specific settings.json values on container create.

.github/workflows/lighthouse.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ jobs:
1010
- name: Checkout the project
1111
uses: actions/checkout@v3
1212

13-
- name: Use Node.js 16.x (LTS)
13+
- name: Use Node.js 24.x (LTS)
1414
uses: actions/setup-node@v3
1515
with:
16-
node-version: 16.x
16+
node-version: 24.x
1717
cache: 'npm'
1818
- run: npm ci
1919

.github/workflows/nodejs.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ jobs:
1111
steps:
1212
- name: Checkout the project
1313
uses: actions/checkout@v3
14-
- name: Use Node.js 16.x (LTS)
14+
- name: Use Node.js 24.x (LTS)
1515
uses: actions/setup-node@v3
1616
with:
17-
node-version: 16.x
17+
node-version: 24.x
1818
cache: 'npm'
1919
- run: npm ci
2020
- name: Lint Codebase

.github/workflows/publish.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ jobs:
1010
steps:
1111
- name: Checkout the project
1212
uses: actions/checkout@v3
13-
- name: Use Node.js 16.x (LTS)
13+
- name: Use Node.js 24.x (LTS)
1414
uses: actions/setup-node@v3
1515
with:
16-
node-version: 16.x
16+
node-version: 24.x
1717
registry-url: https://registry.npmjs.org/
1818
cache: npm
1919
- run: npm ci

docs/_guide/your-first-component.md

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,27 @@ Catalyst will automatically convert the classes name; removing the trailing `Ele
3131

3232
By convention Catalyst controllers end in `Element`; Catalyst will omit this when generating a tag name. The `Element` suffix is _not_ required - just convention. All examples in this guide use `Element` suffixed names.
3333

34+
### Custom Element Names
35+
36+
If you need to use a specific element name that doesn't match your class name (for example, to support minification), you can pass the element name directly to the `@controller` decorator:
37+
38+
```js
39+
import {controller} from '@github/catalyst'
40+
41+
@controller('hello-widget')
42+
class SomeClass extends HTMLElement {
43+
connectedCallback() {
44+
this.innerHTML = 'Hello from hello-widget!'
45+
}
46+
}
47+
```
48+
<br>
49+
50+
This will register the element as `<hello-widget>` regardless of the class name. This is particularly useful when:
51+
- Your production build minifies class names
52+
- You want explicit control over the element name
53+
- The class name doesn't follow the naming pattern required for automatic naming
54+
3455
{% capture callout %}
3556
Remember! A class name _must_ include at least two CamelCased words (not including the `Element` suffix). One-word elements will raise exceptions. Example of good names: `UserListElement`, `SubTaskElement`, `PagerContainerElement`
3657
{% endcapture %}{% include callout.md %}
@@ -40,8 +61,8 @@ Remember! A class name _must_ include at least two CamelCased words (not includi
4061

4162
The `@controller` decorator ties together the various other decorators within Catalyst, plus a few extra conveniences such as automatically registering the element, which saves you writing some boilerplate that you'd otherwise have to write by hand. Specifically the `@controller` decorator:
4263

43-
- Derives a tag name based on your class name, removing the trailing `Element` suffix and lowercasing all capital letters, separating them with a dash.
44-
- Calls `window.customElements.define` with the newly derived tag name and your class.
64+
- Derives a tag name based on your class name, removing the trailing `Element` suffix and lowercasing all capital letters, separating them with a dash. You can optionally provide a custom element name as a parameter (e.g., `@controller('my-element')`).
65+
- Calls `window.customElements.define` with the newly derived (or provided) tag name and your class.
4566
- Calls `defineObservedAttributes` with the class to add map any `@attr` decorators. See [attrs]({{ site.baseurl }}/guide/attrs) for more on this.
4667
- Injects the following code inside of the `connectedCallback()` function of your class:
4768
- `bind(this)`; ensures that as your element connects it picks up any `data-action` handlers. See [actions]({{ site.baseurl }}/guide/actions) for more on this.
@@ -79,4 +100,16 @@ controller(
79100
}
80101
)
81102
```
103+
104+
Or with a custom element name:
105+
106+
```js
107+
import {controller} from '@github/catalyst'
108+
109+
controller('my-custom-name')(
110+
class HelloWorldElement extends HTMLElement {
111+
//...
112+
}
113+
)
114+
```
82115
<br>

src/controller.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@ import type {CustomElementClass} from './custom-element.js'
66
* registry, as well as ensuring `bind(this)` is called on `connectedCallback`,
77
* wrapping the classes `connectedCallback` method if needed.
88
*/
9-
export function controller(classObject: CustomElementClass): void {
10-
new CatalystDelegate(classObject)
9+
export function controller(classObject: CustomElementClass): void
10+
export function controller(name: string): (classObject: CustomElementClass) => void
11+
export function controller(
12+
classObjectOrName: CustomElementClass | string
13+
): void | ((classObject: CustomElementClass) => void) {
14+
if (typeof classObjectOrName === 'string') {
15+
return (classObject: CustomElementClass) => {
16+
new CatalystDelegate(classObject, classObjectOrName)
17+
}
18+
}
19+
new CatalystDelegate(classObjectOrName)
1120
}

src/core.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {observe} from './lazy-define.js'
88
const symbol = Symbol.for('catalyst')
99

1010
export class CatalystDelegate {
11-
constructor(classObject: CustomElementClass) {
11+
constructor(classObject: CustomElementClass, elementName?: string) {
1212
// eslint-disable-next-line @typescript-eslint/no-this-alias
1313
const delegate = this
1414

@@ -44,7 +44,7 @@ export class CatalystDelegate {
4444
})
4545

4646
defineObservedAttributes(classObject)
47-
register(classObject)
47+
register(classObject, elementName)
4848
}
4949

5050
observedAttributes(instance: HTMLElement, observedAttributes: string[]) {

src/register.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ import {dasherize} from './dasherize.js'
88
*
99
* Example: HelloController => hello-controller
1010
*/
11-
export function register(classObject: CustomElementClass): CustomElementClass {
12-
const name = dasherize(classObject.name).replace(/-element$/, '')
11+
export function register(classObject: CustomElementClass, name?: string): CustomElementClass {
12+
const tagName = name || dasherize(classObject.name).replace(/-element$/, '')
1313

1414
try {
15-
window.customElements.define(name, classObject)
15+
window.customElements.define(tagName, classObject)
1616
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
1717
// @ts-ignore
18-
window[classObject.name] = customElements.get(name)
18+
window[classObject.name] = customElements.get(tagName)
1919
} catch (e: unknown) {
2020
// The only reason for window.customElements.define to throw a `NotSupportedError`
2121
// is if the element has already been defined.

test/controller.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,29 @@ describe('controller', () => {
1414
expect(instance).to.be.instanceof(ControllerRegisterElement)
1515
})
1616

17+
it('registers element with custom name when provided', async () => {
18+
@controller('happy-widget')
19+
class SomeClass extends HTMLElement {}
20+
instance = await fixture(html`<happy-widget />`)
21+
expect(instance).to.be.instanceof(SomeClass)
22+
})
23+
24+
it('registers element with custom name using function syntax', async () => {
25+
controller('custom-element-name')(class AnotherClass extends HTMLElement {})
26+
instance = await fixture(html`<custom-element-name />`)
27+
expect(instance).to.exist
28+
})
29+
30+
it('adds data-catalyst to elements with custom names', async () => {
31+
@controller('custom-named-element')
32+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
33+
class CustomNamedElement extends HTMLElement {}
34+
35+
instance = await fixture(html`<custom-named-element />`)
36+
expect(instance.hasAttribute('data-catalyst')).to.equal(true)
37+
expect(instance.getAttribute('data-catalyst')).to.equal('')
38+
})
39+
1740
it('adds data-catalyst to elements', async () => {
1841
@controller
1942
// eslint-disable-next-line @typescript-eslint/no-unused-vars

0 commit comments

Comments
 (0)