@gluon/gluon 2.5.2 → 3.0.0

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/CHANGELOG.md ADDED
@@ -0,0 +1,41 @@
1
+ # Changelog
2
+
3
+ All notable changes to GluonJS are documented in this file. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project adheres to [Semantic Versioning](https://semver.org/).
4
+
5
+ ## [Unreleased]
6
+
7
+ ## [3.0.0] - 2026-10-09
8
+
9
+ GluonJS 3 is a rewrite in TypeScript on lite-html. The lists below cover what changed for an element written against 2.x.
10
+
11
+ ### Added
12
+
13
+ - Reactive properties, declared in the static `properties` map of an element. A declared property requests an update when it changes. With `attribute: true` it is set from the attribute with its kebab-cased name, and with `reflect: true` it is written back to that attribute. The `type` and `converter` options control the conversion, and `hasChanged` the change detection.
14
+ - A batched update cycle. Changes are rendered once per microtask. `requestUpdate()`, `performUpdate()`, `updateComplete`, `isUpdatePending` and `hasUpdated` control and observe it, and the `shouldUpdate()`, `willUpdate()`, `update()`, `firstUpdated()` and `updated()` methods are called during it with the changed properties.
15
+ - Static `styles`: CSS text, a constructed `CSSStyleSheet`, or a list of them, adopted by the shadow root of every instance. The `css` tag marks CSS in template literals.
16
+ - `define(ElementClass, name, registry?)` registers an element class, and tolerates being called again with the same class.
17
+ - `disconnectSignal`, an `AbortSignal` that is aborted when the element is disconnected, for listeners and observers set up in `connectedCallback()`.
18
+ - Static `shadowRootOptions` for the shadow root that `createRenderRoot()` attaches.
19
+ - Type declarations: the project is written in TypeScript and the package ships `.d.ts` files for the public API.
20
+
21
+ ### Changed
22
+
23
+ - Templates are rendered with [lite-html](https://github.com/ruphin/lite-html) instead of lit-html. The `html`, `svg` and `render` functions and the directives of lite-html are exported from `@gluon/gluon`.
24
+ - `render()` returns what the element renders, and is called by the update cycle. It replaces the `template` getter, and is no longer the method that schedules a render; use `requestUpdate()` and `performUpdate()` for that.
25
+ - An element renders after it is connected and its first update completes, instead of synchronously in `connectedCallback()`.
26
+ - The package entry point is the built ES module `dist/index.js`, which imports lite-html. The minified bundle `dist/gluon.min.js` includes lite-html and is what unpkg serves.
27
+
28
+ ### Removed
29
+
30
+ - The `$` cache of nodes with an `id`. Query `renderRoot` instead.
31
+ - `static get is()`. Pass the tag name to `define()` or `customElements.define()`.
32
+ - `render({ sync: true })`. Call `performUpdate()` to update synchronously.
33
+ - Rendering through lit-html's `shady-render`, for the ShadyDOM and ShadyCSS polyfills.
34
+
35
+ ## [2.5.3] - 2019-02-20
36
+
37
+ Last release of the 2.x line, built on lit-html.
38
+
39
+ [Unreleased]: https://github.com/ruphin/gluonjs/compare/v3.0.0...HEAD
40
+ [3.0.0]: https://github.com/ruphin/gluonjs/compare/v2.5.3...v3.0.0
41
+ [2.5.3]: https://github.com/ruphin/gluonjs/releases/tag/v2.5.3
package/README.md CHANGED
@@ -1,220 +1,366 @@
1
- # Gluonjs
1
+ # GluonJS
2
2
 
3
- [![Build Status](https://api.travis-ci.org/ruphin/gluonjs.svg?branch=master)](https://travis-ci.org/ruphin/gluonjs)
4
3
  [![NPM Latest version](https://img.shields.io/npm/v/@gluon/gluon.svg)](https://www.npmjs.com/package/@gluon/gluon)
5
- [![Code Style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier)
6
4
 
7
- _A lightweight library for building web components and applications_
5
+ _A tiny base class for Web Components_
8
6
 
9
7
  ---
10
8
 
11
- - **Platform Based:** GluonJS is designed to leverage the latest web platform capabilities, making it extremely small in size, and very performant on modern browsers. Additionally, it means that **build/compile steps are optional**; GluonJS components work on modern browsers without any pre-processing.
12
- - **Component Model:** Build components with encapsulated logic and style, then compose them to make complex interfaces. Uses the Web Component standards, with all related APIs available directly to developers.
13
- - **Highly Reusable:** Because GluonJS creates standards-compliant Web Components, you can use components created with GluonJS in almost any existing application. Check [Custom Elements Everywhere](https://custom-elements-everywhere.com/) for up-to-date compatibility tables with existing frameworks.
14
- - **Powerful Templating:** GluonJS uses [lit-html](https://github.com/PolymerLabs/lit-html) for templating, making it highly expressive and flexible.
9
+ - **Reactive properties:** Declare the properties of an element once. Each one re-renders the element when it changes, and can be set from an attribute with type conversion and optional reflection.
10
+ - **Batched rendering:** Templates are written with [lite-html](https://github.com/ruphin/lite-html) and rendered into a shadow root. Changes are batched into one render per microtask, and only the changed parts of the template are updated.
11
+ - **Single file components:** Styles, properties, logic, and template live in one class, in one file. No decorators, no compiler settings, and no build step for plain JavaScript.
12
+ - **Standards based:** Elements are plain custom elements, with the Web Component APIs available directly. They work in any framework, see [Custom Elements Everywhere](https://custom-elements-everywhere.com/).
15
13
 
16
- ## Concepts
14
+ ## Example
17
15
 
18
- ###
16
+ ```typescript
17
+ import { GluonElement, define, html, css } from "@gluon/gluon";
19
18
 
20
- ```javascript
21
- import { GluonElement } from '/node_modules/@gluon/gluon/gluon.js';
19
+ class MyCounter extends GluonElement {
20
+ static override styles = css`
21
+ :host { display: inline-flex; gap: 8px; }
22
+ button { font: inherit; }
23
+ `;
22
24
 
23
- class MyElement extends GluonElement {
24
- // ...
25
+ static override properties = {
26
+ count: { type: Number, attribute: true, reflect: true },
27
+ step: { type: Number, attribute: true },
28
+ };
29
+
30
+ count = 0;
31
+ step = 1;
32
+
33
+ override render() {
34
+ return html`
35
+ <button @click=${this.decrement}>-</button>
36
+ <span>${this.count}</span>
37
+ <button @click=${this.increment}>+</button>
38
+ `;
39
+ }
40
+
41
+ increment() {
42
+ this.count += this.step;
43
+ this.emit();
44
+ }
45
+
46
+ decrement() {
47
+ this.count -= this.step;
48
+ this.emit();
49
+ }
50
+
51
+ private emit() {
52
+ this.dispatchEvent(new CustomEvent("count-changed", { detail: { count: this.count }, bubbles: true, composed: true }));
53
+ }
25
54
  }
26
55
 
27
- customElements.define(MyElement.is, MyElement);
56
+ define(MyCounter, "my-counter");
57
+
58
+ declare global {
59
+ interface HTMLElementTagNameMap {
60
+ "my-counter": MyCounter;
61
+ }
62
+ interface GlobalEventHandlersEventMap {
63
+ "count-changed": CustomEvent<{ count: number }>;
64
+ }
65
+ }
28
66
  ```
29
67
 
30
- ### Rendering
68
+ ```html
69
+ <script type="module" src="/my-counter.js"></script>
31
70
 
32
- Gluon uses [lit-html](https://github.com/PolymerLabs/lit-html) to efficiently render DOM into elements. The template to render is defined in the `template()` getter of an element, using JavaScript tagged template literals.
71
+ <my-counter count="5" step="5"></my-counter>
72
+ ```
33
73
 
34
- If a `template` is defined, Gluon will render the template during the initialization of the element (when `super.connectedCallback()` is called).
74
+ ## Installing
35
75
 
36
- ## API
76
+ ```
77
+ npm install @gluon/gluon
78
+ ```
37
79
 
38
- ### $
80
+ GluonJS is published as an ES module with type declarations, and depends on `lite-html`. Import it from your bundler or import map:
39
81
 
40
- All nodes in the template with an `id` attribute are automatically mappped in the `$` property of an element. This provides an easy way to access named nodes without `querySelector` or `getElementById`.
82
+ ```javascript
83
+ import { GluonElement, define, html, css } from "@gluon/gluon";
84
+ ```
41
85
 
42
- The map is created during the initial render of the template. Use `this.shadowRoot.getElementById()` to access nodes that are added after the initial render.
86
+ Or load the bundle that includes lite-html:
43
87
 
44
- ### static get is()
88
+ ```javascript
89
+ import { GluonElement, define, html } from "https://unpkg.com/@gluon/gluon";
90
+ ```
45
91
 
46
- Returns a kebab-cased version of the element ClassName, as an easy default tagname for the customElementRegistry. This allows easy Custom Element registration using `customElements.define(MyElement.is, MyElement)`.
92
+ The `html`, `svg`, and `render` functions and the directives of lite-html are exported from `@gluon/gluon` as well, so one import is enough.
47
93
 
48
- \*\* NOTE: JavaScript minifiers like es-uglify may break this feature. Use the `{ keep_fnames: true, mangle: {keep_fnames: true} }` options in es-uglify to avoid breaking this feature. Alternatively, override the method to return a string to define a fixed tagname: `
94
+ ## Elements
49
95
 
50
- ‡ Pending IE support in [lit-html](https://github.com/PolymerLabs/lit-html)static get is() { return 'my-element' }`.
96
+ An element is a class that extends `GluonElement`, registered with `define`:
51
97
 
52
- ### get template()
98
+ ```typescript
99
+ class MyElement extends GluonElement {
100
+ override render() {
101
+ return html`<p>Hello</p>`;
102
+ }
103
+ }
53
104
 
54
- ### render()
105
+ define(MyElement, "my-element");
106
+ ```
55
107
 
56
- Calling `render()` on an element will queue a render at the next [microtask timing](https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/). Multiple calls are automatically batched into a single render.
108
+ ### define(ElementClass, name, registry?)
57
109
 
58
- Returns a promise object that is fulfilled after the render is complete.
110
+ Registers the class with a custom element registry under the tag name `name`, and returns the class.
59
111
 
60
- To render synchronously, `render({sync: true})`
112
+ A name that is already registered is not registered again, so a module can be loaded more than once, for example by a development server that reloads modules. When the name is registered with a different class, the existing registration is kept and a warning is logged.
61
113
 
62
- ## Common Patterns
114
+ The `registry` defaults to the global `customElements`. Pass a scoped `CustomElementRegistry` to register the element in that scope.
63
115
 
64
- ### Defining properties with getters and setters
116
+ ### render()
65
117
 
66
- This basic pattern works by defining a property getter and setter that wrap some other storage location for the property value.
118
+ Returns what the element renders: a lite-html template, or any other [renderable value](https://github.com/ruphin/lite-html#renderable-values). The result is rendered into `renderRoot`, which is an open shadow root by default. Only the parts of the template that changed since the last render are updated.
67
119
 
68
- ```javascript
69
- get someProp() {
70
- return this._someProp;
71
- }
120
+ Event handlers in the template are called with the element as `this`, so methods of the element can be used directly: `@click=${this.increment}`.
121
+
122
+ `render()` runs in a microtask after the element is connected, and again after any of its properties change. It should be a pure function of the element's properties and state. Return `noChange` to leave the rendered content as it is.
123
+
124
+ ### createRenderRoot()
125
+
126
+ Returns the node to render into. By default it attaches a shadow root with the static `shadowRootOptions`, or reuses the shadow root that already exists. Override it to render somewhere else:
72
127
 
73
- set someProp(value) {
74
- this._someProp = value;
128
+ ```typescript
129
+ class LightElement extends GluonElement {
130
+ protected override createRenderRoot() {
131
+ return this; // Render as the children of the element
132
+ }
75
133
  }
76
134
  ```
77
135
 
78
- Defining properties with getters and setters has no benefits in itself, but it makes for a more flexible system when adding more features such as property defaults, synchronising between properties and attributes, typed properties, or observing property changes, examples of which are listed below.
136
+ The static `shadowRootOptions` default to `{ mode: "open" }`. Override them to make the shadow root closed, or to delegate focus:
79
137
 
80
- ### Computed properties
138
+ ```typescript
139
+ static override shadowRootOptions: ShadowRootInit = { mode: "open", delegatesFocus: true };
140
+ ```
81
141
 
82
- Computed properties can be created by defining a property getter that computes the value for the property.
142
+ ### styles
83
143
 
84
- ```javascript
85
- get computedProp() {
86
- return this.someProp + this.otherProp;
144
+ The static `styles` are applied to the shadow root of every instance: a string of CSS, a constructed `CSSStyleSheet`, or an array of either. Each string is turned into one `CSSStyleSheet` that is shared by all instances. Where constructed stylesheets cannot be adopted, a `<style>` element is added to the shadow root instead.
145
+
146
+ The `css` tag returns its template literal as a string. It marks the CSS for editors, formatters, and linters, and composes interpolated styles and numbers: `` css`${base} p { gap: ${8}px; }` ``.
147
+
148
+ ```typescript
149
+ class StyledElement extends GluonElement {
150
+ static override styles = css`
151
+ :host { display: block; }
152
+ p { color: firebrick; }
153
+ `;
87
154
  }
88
155
  ```
89
156
 
90
- \*\* NOTE: Computed properties are re-computed for every reference to the property. When the computation is expensive, it may be worthwhile to implement a cache:
157
+ Styles are not applied when the element renders into its light DOM. A class that is extended by another class with its own styles needs the `Styles` type annotation on the field, so TypeScript accepts a different value in the subclass:
91
158
 
92
- ```javascript
93
- get computedProp() {
94
- if (this.__previousSomeProp == this.someProp && this.__previousOtherProp === this.otherProp) {
95
- return this.__cachedComputedProp;
96
- }
159
+ ```typescript
160
+ class BaseElement extends GluonElement {
161
+ static override styles: Styles = css`p { color: firebrick; }`;
162
+ }
163
+
164
+ class LoudElement extends BaseElement {
165
+ static override styles = [BaseElement.styles!, css`p { font-weight: bold; }`].flat();
97
166
 
98
- this.__previousSomeProp = this.someProp;
99
- this.__previousOtherProp = this.otherProp;
100
- this.__cachedComputedProp = this.someProp + this.otherProp;
101
- return this.__cachedComputedProp;
102
167
  }
103
168
  ```
104
169
 
105
- ### Typed properties
170
+ ## Properties
106
171
 
107
- Define typed properties by adding type coercion in the property getter or setter.
108
-
109
- ```javascript
110
- get numberProperty() {
111
- return this._numberProperty;
112
- }
172
+ Reactive properties are declared in the static `properties` map, and defined as class fields with their default values:
113
173
 
114
- set numberProperty(value) {
115
- this._numberProperty = Number(value);
174
+ ```typescript
175
+ class MyElement extends GluonElement {
176
+ static override properties = {
177
+ name: { attribute: true },
178
+ count: { type: Number, attribute: true, reflect: true },
179
+ active: { type: Boolean, attribute: true },
180
+ items: { type: Array },
181
+ };
182
+
183
+ name = "";
184
+ count = 0;
185
+ active = false;
186
+ items: string[] = [];
116
187
  }
117
188
  ```
118
189
 
119
- ### Synchronising properties and attributes
190
+ Every declared property becomes an accessor that requests an update when its value changes. The class field is the default value. A property has no attribute unless it is declared with `attribute: true`, which observes the kebab-cased name of the property: `maxValue` is set from `max-value`. The options of a property are:
120
191
 
121
- ### Observing attribute changes
192
+ | Option | Default | Meaning |
193
+ | ------------ | ------------ | ------------------------------------------------------------------------------------------------------------- |
194
+ | `type` | `String` | `String`, `Number`, `Boolean`, `Array`, or `Object`. A hint for the default converter |
195
+ | `attribute` | `false` | Set the property from the attribute with its kebab-cased name |
196
+ | `reflect` | `false` | Write the property to its attribute when it changes, including its initial value. Only with `attribute: true` |
197
+ | `converter` | built-in | An object with `fromAttribute(value, type)` and `toAttribute(value, type)` functions |
198
+ | `hasChanged` | `!Object.is` | A function `(value, oldValue) => boolean` that decides whether a new value counts as a change |
122
199
 
123
- Observing attribute changes is done using the Web Component attribute observer standards.
200
+ The default converter copies `String` attributes, parses `Number` attributes with `Number()`, treats `Boolean` attributes as `true` when present, and parses `Array` and `Object` attributes as JSON. A property that is `null` or `undefined` removes its attribute when reflected. Rich data belongs in properties, not attributes: pass it from a template with `.items=${items}`, and leave `attribute` off.
124
201
 
125
- To observe changes on attributes, define a `static get observedAttributes()` on the class that returns an array of all attributes to observe:
202
+ Changing a property requests an update. Mutating an array or object inside a property does not, because the property itself did not change. Either assign a new value, or call `requestUpdate()`:
126
203
 
127
- ```javascript
128
- static get observedAttributes() {
129
- return ['some-attr', 'other-attr']
204
+ ```typescript
205
+ addItem(item: string) {
206
+ this.items = [...this.items, item]; // Requests an update
207
+ }
208
+
209
+ addItemInPlace(item: string) {
210
+ this.items.push(item);
211
+ this.requestUpdate(); // The element cannot see the push
130
212
  }
131
213
  ```
132
214
 
133
- With this defined, `attributeChangedCallback(attr, oldValue, newValue)` will be called for all attributes listed in the array.
215
+ ### Class fields and accessors
134
216
 
135
- ```javascript
136
- attributeChangedCallback(attr, oldValue, newValue) {
137
- if (attr === 'some-attr') {
138
- // some-attr changed
139
- } else if (attr === 'other-attr') {
140
- // other-attr changed
141
- }
142
- }
217
+ A class field is defined on the instance when the element is constructed, and shadows the accessor on the prototype until the element is connected. At the first connect, the field is removed and its value is applied through the accessor. Properties and attributes that are set before that moment are kept, with one exception: a property that is set on an element before its class is defined is overwritten by the class field when the element is upgraded.
218
+
219
+ To keep such values, declare the property with a getter and setter pair instead of a field. With TypeScript 5 or a bundler that lowers the keyword, the `accessor` keyword does the same in one line:
220
+
221
+ ```typescript
222
+ accessor count = 0;
143
223
  ```
144
224
 
145
- \*\* NOTE: attributeChangedCallback is also called for the initial attributes set on an element. It is in fact called before the `connectedCallback()`, which means the template has not yet been rendered. If you need to interact with child nodes, use the promise returned by `render()` to guarantee the template has been rendered and the child nodes exist:
225
+ Vite lowers `accessor` only with `experimentalDecorators: true` in `tsconfig.json`. Without that setting, the keyword reaches the browser unchanged, and no browser supports it yet.
146
226
 
147
- ```javascript
148
- attributeChangedCallback(attr, oldValue, newValue) {
149
- if (attr === 'some-attr') {
150
- this.render().then( () => this.$.child.someFunction() );
151
- }
227
+ ### A property with custom behaviour
228
+
229
+ A declared property can have its own getter and setter. The setter is wrapped so it still requests an update:
230
+
231
+ ```typescript
232
+ static override properties = { value: { type: Number } };
233
+
234
+ #value = 0;
235
+
236
+ get value() {
237
+ return this.#value;
238
+ }
239
+
240
+ set value(value: number) {
241
+ this.#value = Math.max(0, value);
152
242
  }
153
243
  ```
154
244
 
155
- ### Observing property changes
245
+ ### Inheritance
156
246
 
157
- Observing property changes is done by calling the observer at the end of the property setter function.
247
+ A class inherits the properties of its superclass, and can declare more. A class that is extended by another class with its own properties needs the `PropertyDeclarations` type annotation on the map, so TypeScript accepts a different map in the subclass:
158
248
 
159
- ```javascript
160
- get someProp() {
161
- return this._someProp;
249
+ ```typescript
250
+ class BaseElement extends GluonElement {
251
+ static override properties: PropertyDeclarations = { name: { attribute: true } };
252
+ name = "";
162
253
  }
163
254
 
164
- set someProp(value) {
165
- this._someProp = value;
166
- this.somePropChanged();
255
+
256
+ class ExtendedElement extends BaseElement {
257
+ static override properties = { count: { type: Number } };
258
+ count = 0;
167
259
  }
168
260
  ```
169
261
 
170
- ## Examples
262
+ ## Updates
171
263
 
172
- Here is an example of a GluonJS component:
264
+ Changing a property schedules an update in a microtask. All changes before the microtask are batched into one update. An update runs these methods in order, each with a `PropertyValues` map of the changed properties to their previous values:
173
265
 
174
- ```javascript
175
- // helloMessage.js
176
- import { GluonElement, html } from '/node_modules/@gluon/gluon/gluon.js';
266
+ 1. `shouldUpdate(changed)`: return `false` to skip the update. Defaults to `true`.
267
+ 2. `willUpdate(changed)`: compute values that depend on the changed properties, before rendering.
268
+ 3. `update(changed)`: reflects properties to attributes, and renders `render()` into `renderRoot`. Call `super.update(changed)` when overriding it.
269
+ 4. `firstUpdated(changed)`: called once, after the first update. The rendered DOM exists from here on.
270
+ 5. `updated(changed)`: called after every update.
177
271
 
178
- class HelloMessage extends GluonElement {
179
- get template() {
180
- return html`<div>Hello ${this.getAttribute('name')}</div>`;
272
+ ```typescript
273
+ class FullName extends GluonElement {
274
+ static override properties = { first: {}, last: {} };
275
+ first = "";
276
+ last = "";
277
+ full = "";
278
+
279
+ override willUpdate(changed: PropertyValues<this>) {
280
+ if (changed.has("first") || changed.has("last")) {
281
+ this.full = `${this.first} ${this.last}`;
282
+ }
283
+ }
284
+
285
+ override render() {
286
+ return html`<p>${this.full}</p>`;
181
287
  }
182
288
  }
289
+ ```
290
+
291
+ The first update waits for the element to be connected. Elements render after they are connected, whether or not they are still connected.
183
292
 
184
- customElements.define(HelloMessage.is, HelloMessage);
293
+ ### updateComplete
294
+
295
+ A promise that resolves when the pending update completes. It resolves with `true` when no further update was requested during the update, and `false` otherwise. Await it before reading the rendered DOM:
296
+
297
+ ```typescript
298
+ element.count = 5;
299
+ await element.updateComplete;
300
+ element.renderRoot.querySelector("span"); // Shows 5
185
301
  ```
186
302
 
187
- We can import and use this component from anywhere:
303
+ ### requestUpdate(name?, oldValue?)
188
304
 
189
- ```html
190
- <!-- index.html -->
191
- <script type="module" src="/helloMessage.js"></script>
305
+ Requests an update. Call it without arguments after a change the element cannot see, such as a mutation of an array in a property. The setters of declared properties call it with the property name and its previous value.
306
+
307
+ ### performUpdate()
308
+
309
+ Performs the pending update now, instead of in a microtask.
310
+
311
+ ### isUpdatePending and hasUpdated
312
+
313
+ `isUpdatePending` is `true` while an update is scheduled. `hasUpdated` is `true` after the first update.
314
+
315
+ ## Lifecycle
192
316
 
193
- <hello-message name="World"></hello-message>
317
+ The standard custom element callbacks are available. Call the `super` method when overriding `connectedCallback()`, `disconnectedCallback()`, or `attributeChangedCallback()`.
318
+
319
+ `connectedCallback()` runs every time the element is connected, including when it is moved. Listeners on the document or window belong here, with the `disconnectSignal` as their `signal`, so they are removed when the element is disconnected:
320
+
321
+ ```typescript
322
+ override connectedCallback() {
323
+ super.connectedCallback();
324
+ window.addEventListener("resize", this.measure, { signal: this.disconnectSignal });
325
+ }
194
326
  ```
195
327
 
196
- This example will render "Hello World".
328
+ `disconnectSignal` is an `AbortSignal` that is aborted when the element is disconnected. A new signal is created on every connect.
197
329
 
198
- ## Installation
330
+ ## Events
199
331
 
200
- GluonJS is available through [npm](https://www.npmjs.com) as `@gluon/gluon`.
332
+ Dispatch events with `dispatchEvent`. An event that should be heard outside the shadow root needs `composed: true`, and `bubbles: true` to reach ancestors:
201
333
 
202
- ## Compatibility
334
+ ```typescript
335
+ this.dispatchEvent(new CustomEvent("count-changed", { detail: { count: this.count }, bubbles: true, composed: true }));
336
+ ```
203
337
 
204
- | Chrome | Safari | Firefox | Edge | IE |
205
- | ------ | ------ | ------- | ---- | ---- |
206
- | ✔ | ✔ | \* | \* | \* † |
338
+ Declare the event in `GlobalEventHandlersEventMap`, so `addEventListener("count-changed", ...)` is typed on elements, documents, and windows:
207
339
 
208
- \* Requires [Web Component polyfill](https://www.webcomponents.org/polyfills/)
340
+ ```typescript
341
+ declare global {
342
+ interface GlobalEventHandlersEventMap {
343
+ "count-changed": CustomEvent<{ count: number }>;
344
+ }
345
+ }
346
+ ```
347
+
348
+ ## TypeScript
209
349
 
210
- † Requires transpiling to ES5
350
+ GluonJS ships type declarations and needs no compiler settings. Declare every element in `HTMLElementTagNameMap`, so `document.createElement("my-counter")` and `querySelector("my-counter")` return the element class:
211
351
 
212
- ## Contributing
352
+ ```typescript
353
+ declare global {
354
+ interface HTMLElementTagNameMap {
355
+ "my-counter": MyCounter;
356
+ }
357
+ }
358
+ ```
213
359
 
214
- All work on GluonJS happens in the open on [Github](https://github.com/ruphin/gluonjs). A development environment is available at `localhost:5000` with `npm install && npm run dev`, or `make dev` if you use [Docker](https://www.docker.com/). All issue reports and pull requests are welcome.
360
+ With `noImplicitOverride`, the overridden members of the base class carry the `override` keyword, as in the examples above. Without it, the keyword can be left out.
215
361
 
216
362
  ## License
217
363
 
218
- [MIT](http://opensource.org/licenses/MIT)
364
+ [MIT](https://opensource.org/licenses/MIT)
219
365
 
220
- Copyright © 2017-present, Goffert van Gool
366
+ Copyright © 2026-present, Goffert van Gool