@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 +41 -0
- package/README.md +268 -122
- package/dist/gluon.d.ts +124 -0
- package/dist/gluon.min.js +4 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +254 -0
- package/dist/properties.d.ts +60 -0
- package/dist/styles.d.ts +26 -0
- package/package.json +44 -44
- package/gluon.es5.js +0 -2061
- package/gluon.es5.js.map +0 -1
- package/gluon.js +0 -126
- package/gluon.umd.js +0 -1591
- package/gluon.umd.js.map +0 -1
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
|
-
#
|
|
1
|
+
# GluonJS
|
|
2
2
|
|
|
3
|
-
[](https://travis-ci.org/ruphin/gluonjs)
|
|
4
3
|
[](https://www.npmjs.com/package/@gluon/gluon)
|
|
5
|
-
[](https://github.com/prettier/prettier)
|
|
6
4
|
|
|
7
|
-
_A
|
|
5
|
+
_A tiny base class for Web Components_
|
|
8
6
|
|
|
9
7
|
---
|
|
10
8
|
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
13
|
-
- **
|
|
14
|
-
- **
|
|
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
|
-
##
|
|
14
|
+
## Example
|
|
17
15
|
|
|
18
|
-
|
|
16
|
+
```typescript
|
|
17
|
+
import { GluonElement, define, html, css } from "@gluon/gluon";
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
68
|
+
```html
|
|
69
|
+
<script type="module" src="/my-counter.js"></script>
|
|
31
70
|
|
|
32
|
-
|
|
71
|
+
<my-counter count="5" step="5"></my-counter>
|
|
72
|
+
```
|
|
33
73
|
|
|
34
|
-
|
|
74
|
+
## Installing
|
|
35
75
|
|
|
36
|
-
|
|
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
|
-
|
|
82
|
+
```javascript
|
|
83
|
+
import { GluonElement, define, html, css } from "@gluon/gluon";
|
|
84
|
+
```
|
|
41
85
|
|
|
42
|
-
|
|
86
|
+
Or load the bundle that includes lite-html:
|
|
43
87
|
|
|
44
|
-
|
|
88
|
+
```javascript
|
|
89
|
+
import { GluonElement, define, html } from "https://unpkg.com/@gluon/gluon";
|
|
90
|
+
```
|
|
45
91
|
|
|
46
|
-
|
|
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
|
-
|
|
94
|
+
## Elements
|
|
49
95
|
|
|
50
|
-
|
|
96
|
+
An element is a class that extends `GluonElement`, registered with `define`:
|
|
51
97
|
|
|
52
|
-
|
|
98
|
+
```typescript
|
|
99
|
+
class MyElement extends GluonElement {
|
|
100
|
+
override render() {
|
|
101
|
+
return html`<p>Hello</p>`;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
53
104
|
|
|
54
|
-
|
|
105
|
+
define(MyElement, "my-element");
|
|
106
|
+
```
|
|
55
107
|
|
|
56
|
-
|
|
108
|
+
### define(ElementClass, name, registry?)
|
|
57
109
|
|
|
58
|
-
|
|
110
|
+
Registers the class with a custom element registry under the tag name `name`, and returns the class.
|
|
59
111
|
|
|
60
|
-
|
|
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
|
-
|
|
114
|
+
The `registry` defaults to the global `customElements`. Pass a scoped `CustomElementRegistry` to register the element in that scope.
|
|
63
115
|
|
|
64
|
-
###
|
|
116
|
+
### render()
|
|
65
117
|
|
|
66
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
74
|
-
|
|
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
|
-
|
|
136
|
+
The static `shadowRootOptions` default to `{ mode: "open" }`. Override them to make the shadow root closed, or to delegate focus:
|
|
79
137
|
|
|
80
|
-
|
|
138
|
+
```typescript
|
|
139
|
+
static override shadowRootOptions: ShadowRootInit = { mode: "open", delegatesFocus: true };
|
|
140
|
+
```
|
|
81
141
|
|
|
82
|
-
|
|
142
|
+
### styles
|
|
83
143
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
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
|
-
```
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
170
|
+
## Properties
|
|
106
171
|
|
|
107
|
-
|
|
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
|
-
|
|
115
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
```
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
215
|
+
### Class fields and accessors
|
|
134
216
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
###
|
|
245
|
+
### Inheritance
|
|
156
246
|
|
|
157
|
-
|
|
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
|
-
```
|
|
160
|
-
|
|
161
|
-
|
|
249
|
+
```typescript
|
|
250
|
+
class BaseElement extends GluonElement {
|
|
251
|
+
static override properties: PropertyDeclarations = { name: { attribute: true } };
|
|
252
|
+
name = "";
|
|
162
253
|
}
|
|
163
254
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
255
|
+
|
|
256
|
+
class ExtendedElement extends BaseElement {
|
|
257
|
+
static override properties = { count: { type: Number } };
|
|
258
|
+
count = 0;
|
|
167
259
|
}
|
|
168
260
|
```
|
|
169
261
|
|
|
170
|
-
##
|
|
262
|
+
## Updates
|
|
171
263
|
|
|
172
|
-
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
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
|
-
|
|
303
|
+
### requestUpdate(name?, oldValue?)
|
|
188
304
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
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
|
-
|
|
328
|
+
`disconnectSignal` is an `AbortSignal` that is aborted when the element is disconnected. A new signal is created on every connect.
|
|
197
329
|
|
|
198
|
-
##
|
|
330
|
+
## Events
|
|
199
331
|
|
|
200
|
-
|
|
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
|
-
|
|
334
|
+
```typescript
|
|
335
|
+
this.dispatchEvent(new CustomEvent("count-changed", { detail: { count: this.count }, bubbles: true, composed: true }));
|
|
336
|
+
```
|
|
203
337
|
|
|
204
|
-
|
|
205
|
-
| ------ | ------ | ------- | ---- | ---- |
|
|
206
|
-
| ✔ | ✔ | \* | \* | \* † |
|
|
338
|
+
Declare the event in `GlobalEventHandlersEventMap`, so `addEventListener("count-changed", ...)` is typed on elements, documents, and windows:
|
|
207
339
|
|
|
208
|
-
|
|
340
|
+
```typescript
|
|
341
|
+
declare global {
|
|
342
|
+
interface GlobalEventHandlersEventMap {
|
|
343
|
+
"count-changed": CustomEvent<{ count: number }>;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
## TypeScript
|
|
209
349
|
|
|
210
|
-
|
|
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
|
-
|
|
352
|
+
```typescript
|
|
353
|
+
declare global {
|
|
354
|
+
interface HTMLElementTagNameMap {
|
|
355
|
+
"my-counter": MyCounter;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
```
|
|
213
359
|
|
|
214
|
-
|
|
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](
|
|
364
|
+
[MIT](https://opensource.org/licenses/MIT)
|
|
219
365
|
|
|
220
|
-
Copyright ©
|
|
366
|
+
Copyright © 2026-present, Goffert van Gool
|