p-elements-core 1.2.32-rc8 → 1.2.32
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/.editorconfig +17 -17
- package/.gitlab-ci.yml +18 -18
- package/CHANGELOG.md +201 -201
- package/demo/sample.js +1 -1
- package/demo/screen.css +16 -16
- package/dist/p-elements-core-modern.js +1 -1
- package/dist/p-elements-core.js +1 -1
- package/docs/package-lock.json +6897 -6897
- package/docs/package.json +27 -27
- package/docs/src/404.md +8 -8
- package/docs/src/_data/demos/hello-world/hello-world.tsx +35 -35
- package/docs/src/_data/demos/hello-world/index.html +10 -10
- package/docs/src/_data/demos/hello-world/project.json +7 -7
- package/docs/src/_data/demos/timer/demo-timer.tsx +120 -120
- package/docs/src/_data/demos/timer/icons.tsx +62 -62
- package/docs/src/_data/demos/timer/index.html +12 -12
- package/docs/src/_data/demos/timer/project.json +8 -8
- package/docs/src/_data/global.js +13 -13
- package/docs/src/_data/helpers.js +19 -19
- package/docs/src/_includes/layouts/base.njk +30 -30
- package/docs/src/_includes/layouts/playground.njk +40 -40
- package/docs/src/_includes/partials/app-header.njk +8 -8
- package/docs/src/_includes/partials/head.njk +14 -14
- package/docs/src/_includes/partials/nav.njk +19 -19
- package/docs/src/_includes/partials/top-nav.njk +51 -51
- package/docs/src/documentation/custom-element.md +221 -221
- package/docs/src/documentation/decorators/bind.md +71 -71
- package/docs/src/documentation/decorators/custom-element-config.md +63 -63
- package/docs/src/documentation/decorators/property.md +83 -83
- package/docs/src/documentation/decorators/query.md +66 -66
- package/docs/src/documentation/decorators/render-property-on-set.md +60 -60
- package/docs/src/documentation/decorators.md +9 -9
- package/docs/src/documentation/reactive-properties.md +53 -53
- package/docs/src/index.d.ts +25 -25
- package/docs/src/index.md +3 -3
- package/docs/src/scripts/components/app-mode-switch/app-mode-switch.css +78 -78
- package/docs/src/scripts/components/app-mode-switch/app-mode-switch.tsx +166 -166
- package/docs/src/scripts/components/app-playground/app-playground.tsx +189 -189
- package/docs/tsconfig.json +22 -22
- package/index.html +10 -2
- package/package.json +1 -1
- package/readme.md +206 -206
- package/src/custom-element-controller.ts +31 -31
- package/src/custom-element.test.ts +906 -906
- package/src/custom-element.ts +3 -8
- package/src/decorators/bind.test.ts +163 -163
- package/src/decorators/bind.ts +46 -46
- package/src/decorators/custom-element-config.ts +17 -17
- package/src/decorators/property.test.ts +279 -279
- package/src/decorators/query.test.ts +146 -146
- package/src/decorators/query.ts +12 -12
- package/src/decorators/render-property-on-set.ts +3 -3
- package/src/helpers/css.ts +71 -71
- package/src/maquette/cache.ts +35 -35
- package/src/maquette/dom.ts +115 -115
- package/src/maquette/h.ts +100 -100
- package/src/maquette/index.ts +12 -12
- package/src/maquette/interfaces.ts +536 -536
- package/src/maquette/jsx.ts +61 -61
- package/src/maquette/mapping.ts +56 -56
- package/src/maquette/projection.ts +666 -666
- package/src/maquette/projector.ts +205 -205
- package/src/sample/mixin/highlight.tsx +33 -33
- package/src/sample/sample.tsx +98 -0
|
@@ -1,221 +1,221 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: "CustomElement class"
|
|
3
|
-
layout: "base.njk"
|
|
4
|
-
description: "Getting started with CustomElement Base Class"
|
|
5
|
-
permalink: "/documentation/custom-element-base-class.html"
|
|
6
|
-
eleventyNavigation:
|
|
7
|
-
key: CustomElementClass
|
|
8
|
-
title: CustomElement class
|
|
9
|
-
order: 3
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
# CustomElement class (work in progress)
|
|
13
|
-
|
|
14
|
-
The `CustomElement` base class provides a foundation for creating web components within the p-elements-core framework. It simplifies common tasks like rendering, managing properties, handling lifecycle events, and applying styles.
|
|
15
|
-
|
|
16
|
-
## Getting Started
|
|
17
|
-
|
|
18
|
-
To create a new custom element, extend the `CustomElement` class and use the `@customElementConfig` decorator to define its tag name.
|
|
19
|
-
|
|
20
|
-
```typescript
|
|
21
|
-
@CustomElementConfig({ tagName: 'my-element' })
|
|
22
|
-
export class MyElement extends CustomElement {
|
|
23
|
-
|
|
24
|
-
static readonly style = `
|
|
25
|
-
:host{
|
|
26
|
-
color: green;
|
|
27
|
-
}
|
|
28
|
-
`;
|
|
29
|
-
|
|
30
|
-
// https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow#delegatesfocus
|
|
31
|
-
static readonly delegatesFocus = true; // optional if true, the shadowroot is created with param delegatesFocus
|
|
32
|
-
|
|
33
|
-
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/attachInternals
|
|
34
|
-
static readonly formAssociated = true; // optional Allows a custom element to participate in HTML forms.
|
|
35
|
-
|
|
36
|
-
static readonly projectorMode = "replace"; // Optional could be "append" | "merge" | "replace"
|
|
37
|
-
|
|
38
|
-
@property({ type: 'string', reflect: true, attribute: 'message' })
|
|
39
|
-
message: string = 'Hello';
|
|
40
|
-
|
|
41
|
-
// Initialize component state or perform setup (optional)
|
|
42
|
-
init() {
|
|
43
|
-
console.log('MyElement initialized');
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Define the component's template
|
|
47
|
-
render() {
|
|
48
|
-
return <div>{this.message}</div>
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Called after the component is connected to the DOM (optional)
|
|
52
|
-
connectedCallback() {
|
|
53
|
-
super.connectedCallback(); // Call super if overriding
|
|
54
|
-
console.log('MyElement connected');
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Called after the component is disconnected from the DOM (optional)
|
|
58
|
-
disconnectedCallback() {
|
|
59
|
-
super.disconnectedCallback(); // Call super if overriding
|
|
60
|
-
console.log('MyElement disconnected');
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Called when an observed attribute changes (optional)
|
|
64
|
-
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null) {
|
|
65
|
-
super.attributeChangedCallback(name, oldValue, newValue); // Call super if overriding
|
|
66
|
-
console.log(`Attribute ${name} changed from ${oldValue} to ${newValue}`);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Called before rendering starts (optional)
|
|
70
|
-
renderStart(isFirstRender: boolean) {
|
|
71
|
-
console.log('Render starting...', isFirstRender ? '(first time)' : '');
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Called after rendering finishes (optional)
|
|
75
|
-
renderDone(isFirstRender: boolean) {
|
|
76
|
-
console.log('Render done.', isFirstRender ? '(first time)' : '');
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
shouldUpdate(property: string, oldValue: any, newValue: any) {
|
|
80
|
-
// Called before a property update, property is not updated if returns false (optional)
|
|
81
|
-
console.info("shouldUpdate", { property, oldValue, newValue });
|
|
82
|
-
return true;
|
|
83
|
-
}
|
|
84
|
-
// Called after a property update triggers a re-render (optional)
|
|
85
|
-
updated(propertyName: string, oldValue: any, newValue: any) {
|
|
86
|
-
console.log(`Property ${propertyName} updated from ${oldValue} to ${newValue}`);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
## Core Concepts
|
|
92
|
-
|
|
93
|
-
### 1. Defining the Element (`@CustomElementConfig`)
|
|
94
|
-
|
|
95
|
-
Use the `@CustomElementConfig` decorator on your class to register it with the browser's `CustomElementRegistry`.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
### 2. Rendering (`render()` method)
|
|
99
|
-
|
|
100
|
-
The `render()` method is **required**. It should return a virtual DOM representation of your element's content,
|
|
101
|
-
using TSX. `CustomElement` uses Maquette's projector internally to efficiently update the DOM.
|
|
102
|
-
|
|
103
|
-
```typescript
|
|
104
|
-
|
|
105
|
-
// ... inside your class
|
|
106
|
-
render() {
|
|
107
|
-
return <div>Hello world</div>;
|
|
108
|
-
}
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
The rendering engine automatically schedules updates when reactive properties change. You can manually trigger a render using `this.scheduleRender()` or force an immediate synchronous render with `this.renderNow()`.
|
|
112
|
-
|
|
113
|
-
### 3. Reactive Properties (`@Property` decorator)
|
|
114
|
-
|
|
115
|
-
Declare properties that should trigger re-renders when changed using the `@property` decorator.
|
|
116
|
-
|
|
117
|
-
```typescript
|
|
118
|
-
// ... inside your class
|
|
119
|
-
@property({
|
|
120
|
-
type: 'string', // Data type ('string', 'number', 'boolean', 'object')
|
|
121
|
-
reflect: true, // Reflect property value to an attribute?
|
|
122
|
-
attribute: 'my-attr', // Custom attribute name (defaults to property name)
|
|
123
|
-
readonly: false, // Make the property read-only after first set?
|
|
124
|
-
converter: { // Optional: Custom attribute-to-property conversion
|
|
125
|
-
fromAttribute: (value) => value ? value.toUpperCase() : undefined,
|
|
126
|
-
toAttribute: (value) => value ? value.toLowerCase() : undefined
|
|
127
|
-
}
|
|
128
|
-
})
|
|
129
|
-
myProp: string = 'initial value';
|
|
130
|
-
|
|
131
|
-
@property({ type: 'boolean', reflect: true })
|
|
132
|
-
disabled: boolean = false;
|
|
133
|
-
|
|
134
|
-
@property({ type: 'number' })
|
|
135
|
-
count: number = 0;
|
|
136
|
-
|
|
137
|
-
@property({ type: 'object' })
|
|
138
|
-
data: { id: number, name: string };
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
- **`type`**: Helps with automatic attribute-to-property conversion (especially for `boolean` and `number`).
|
|
142
|
-
- **`reflect`**: If `true`, the property's value is automatically reflected to the corresponding HTML attribute.
|
|
143
|
-
- **`attribute`**: Specifies the HTML attribute name to link with the property. If omitted, it defaults to the property name (e.g., `myProp` becomes `myprop`). For - **`readonly`**: If `true`, the property can only be set once. Subsequent attempts will throw an error.
|
|
144
|
-
- **`converter`**: Provides custom logic for converting between attribute strings and property values.
|
|
145
|
-
|
|
146
|
-
When a property decorated with `@Property` changes, `scheduleRender()` is called automatically. If the property has `reflect: true` and an associated `attribute`, the attribute value is updated accordingly.
|
|
147
|
-
|
|
148
|
-
### 4. Lifecycle Methods
|
|
149
|
-
|
|
150
|
-
`CustomElement` provides hooks into the standard custom element lifecycle and its own rendering cycle:
|
|
151
|
-
|
|
152
|
-
- **`constructor()`**: Called when the element instance is created. Basic initialization happens here.
|
|
153
|
-
- **`init()`**: An optional custom initialization method called after the base class constructor logic finishes but before the first render. Good place for setting up initial state or listeners that don't depend on the DOM.
|
|
154
|
-
- **`connectedCallback()`**: Called when the element is inserted into the DOM. Use this for DOM-dependent setup. Remember to call `super.connectedCallback()` if you override it.
|
|
155
|
-
- **`disconnectedCallback()`**: Called when the element is removed from the DOM. Use for cleanup. Remember to call `super.disconnectedCallback()`.
|
|
156
|
-
- **`attributeChangedCallback(name, oldValue, newValue)`**: Called when an attribute listed in the static `observedAttributes` getter changes. `CustomElement` automatically populates `observedAttributes` based on `@property` decorators with an `attribute` defined and handles updating the corresponding property. You usually only need to override this for attributes *not* managed by `@property`. Call `super.attributeChangedCallback()` to ensure property updates still occur.
|
|
157
|
-
- **`renderStart(isFirstRender: boolean)`**: Called just before the Maquette projector starts a render cycle. `isFirstRender` is true only for the very first render.
|
|
158
|
-
- **`renderDone(isFirstRender: boolean)`**: Called just after the Maquette projector finishes a render cycle and updates the DOM. `isFirstRender` is true only for the very first render.
|
|
159
|
-
- **`shouldUpdate(propertyName, oldValue, newValue)`** Called before update a reactive property, if the method returns true the property will be updated.
|
|
160
|
-
- **`updated(propertyName, oldValue, newValue)`**: Called *after* a reactive property has been updated and the subsequent render cycle (if any) has completed.
|
|
161
|
-
|
|
162
|
-
### 5. Styling
|
|
163
|
-
|
|
164
|
-
There are several ways to style your `CustomElement`:
|
|
165
|
-
|
|
166
|
-
- **Shadow DOM with Static `style` Property**: The recommended approach for encapsulated styles. Define a static `style` property containing your CSS string. `CustomElement` will automatically create a shadow root (if not present) and attach the styles.
|
|
167
|
-
|
|
168
|
-
```typescript
|
|
169
|
-
import { css } from 'p-elements-core/helpers/css'; // Optional helper
|
|
170
|
-
|
|
171
|
-
@customElementConfig({ tagName: 'styled-element' })
|
|
172
|
-
export class StyledElement extends CustomElement {
|
|
173
|
-
static readonly style = css`
|
|
174
|
-
:host {
|
|
175
|
-
display: block;
|
|
176
|
-
border: 1px solid blue;
|
|
177
|
-
padding: 1rem;
|
|
178
|
-
}
|
|
179
|
-
p {
|
|
180
|
-
color: blue;
|
|
181
|
-
}
|
|
182
|
-
`;
|
|
183
|
-
|
|
184
|
-
render() {
|
|
185
|
-
return <div><p>Hello world</p></div>;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
```
|
|
189
|
-
This method uses adopted stylesheets if supported by the browser, falling back to `<link>` elements otherwise. It also includes a polyfill for the abandoned `@apply` CSS proposal using CSS variables. This polyfill is just for backwards compatibility.
|
|
190
|
-
|
|
191
|
-
- **Shadow DOM with `<template>`**: Use `this.templateFromString(htmlString)` in your constructor or `init` method. The method parses the string, extracts and applies the `<style>` tag content to the shadow root, and returns the template content `DocumentFragment`.
|
|
192
|
-
|
|
193
|
-
### 6. Controllers
|
|
194
|
-
|
|
195
|
-
Controllers (`ICustomElementController`) are separate classes that can hook into the element's lifecycle to manage specific behaviors (like handling focus, state management, etc.).
|
|
196
|
-
|
|
197
|
-
```typescript
|
|
198
|
-
// In your element class
|
|
199
|
-
import { MyController } from './my-controller';
|
|
200
|
-
|
|
201
|
-
// ... inside your class
|
|
202
|
-
#myController = new MyController(this); // Pass host element
|
|
203
|
-
|
|
204
|
-
constructor() {
|
|
205
|
-
super();
|
|
206
|
-
this.addController(this.#myController);
|
|
207
|
-
// ...
|
|
208
|
-
}
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
The `CustomElement` will automatically call the controller's corresponding lifecycle methods (`init`, `connected`, `disconnected`, `hostRenderStart`, `hostRenderDone`).
|
|
212
|
-
|
|
213
|
-
### 7. Form Association
|
|
214
|
-
|
|
215
|
-
To make your custom element participate in forms (like a native `<input>`), add a static `formAssociated = true` property and use `this.internals` (ElementInternals API).
|
|
216
|
-
|
|
217
|
-
### 8. Advanced Topics
|
|
218
|
-
|
|
219
|
-
- **Projector Mode**: Control how Maquette renders content relative to the host element (or shadow root container) using the static `projectorMode` property (`'append'`, `'merge'`, or `'replace'`). Defaults to `'append'` unless a static `style` is provided (then defaults to `'replace'` within the shadow root).
|
|
220
|
-
- **Focus Delegation**: Set static `delegatesFocus = true` to forward focus into the shadow root. Requires a shadow root.
|
|
221
|
-
- **`ElementInternals`**: Access via `this.internals` when `static formAssociated = true`. Used for form participation, accessibility roles/states, etc.
|
|
1
|
+
---
|
|
2
|
+
title: "CustomElement class"
|
|
3
|
+
layout: "base.njk"
|
|
4
|
+
description: "Getting started with CustomElement Base Class"
|
|
5
|
+
permalink: "/documentation/custom-element-base-class.html"
|
|
6
|
+
eleventyNavigation:
|
|
7
|
+
key: CustomElementClass
|
|
8
|
+
title: CustomElement class
|
|
9
|
+
order: 3
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# CustomElement class (work in progress)
|
|
13
|
+
|
|
14
|
+
The `CustomElement` base class provides a foundation for creating web components within the p-elements-core framework. It simplifies common tasks like rendering, managing properties, handling lifecycle events, and applying styles.
|
|
15
|
+
|
|
16
|
+
## Getting Started
|
|
17
|
+
|
|
18
|
+
To create a new custom element, extend the `CustomElement` class and use the `@customElementConfig` decorator to define its tag name.
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
@CustomElementConfig({ tagName: 'my-element' })
|
|
22
|
+
export class MyElement extends CustomElement {
|
|
23
|
+
|
|
24
|
+
static readonly style = `
|
|
25
|
+
:host{
|
|
26
|
+
color: green;
|
|
27
|
+
}
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow#delegatesfocus
|
|
31
|
+
static readonly delegatesFocus = true; // optional if true, the shadowroot is created with param delegatesFocus
|
|
32
|
+
|
|
33
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/attachInternals
|
|
34
|
+
static readonly formAssociated = true; // optional Allows a custom element to participate in HTML forms.
|
|
35
|
+
|
|
36
|
+
static readonly projectorMode = "replace"; // Optional could be "append" | "merge" | "replace"
|
|
37
|
+
|
|
38
|
+
@property({ type: 'string', reflect: true, attribute: 'message' })
|
|
39
|
+
message: string = 'Hello';
|
|
40
|
+
|
|
41
|
+
// Initialize component state or perform setup (optional)
|
|
42
|
+
init() {
|
|
43
|
+
console.log('MyElement initialized');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Define the component's template
|
|
47
|
+
render() {
|
|
48
|
+
return <div>{this.message}</div>
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Called after the component is connected to the DOM (optional)
|
|
52
|
+
connectedCallback() {
|
|
53
|
+
super.connectedCallback(); // Call super if overriding
|
|
54
|
+
console.log('MyElement connected');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Called after the component is disconnected from the DOM (optional)
|
|
58
|
+
disconnectedCallback() {
|
|
59
|
+
super.disconnectedCallback(); // Call super if overriding
|
|
60
|
+
console.log('MyElement disconnected');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Called when an observed attribute changes (optional)
|
|
64
|
+
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null) {
|
|
65
|
+
super.attributeChangedCallback(name, oldValue, newValue); // Call super if overriding
|
|
66
|
+
console.log(`Attribute ${name} changed from ${oldValue} to ${newValue}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Called before rendering starts (optional)
|
|
70
|
+
renderStart(isFirstRender: boolean) {
|
|
71
|
+
console.log('Render starting...', isFirstRender ? '(first time)' : '');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Called after rendering finishes (optional)
|
|
75
|
+
renderDone(isFirstRender: boolean) {
|
|
76
|
+
console.log('Render done.', isFirstRender ? '(first time)' : '');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
shouldUpdate(property: string, oldValue: any, newValue: any) {
|
|
80
|
+
// Called before a property update, property is not updated if returns false (optional)
|
|
81
|
+
console.info("shouldUpdate", { property, oldValue, newValue });
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
// Called after a property update triggers a re-render (optional)
|
|
85
|
+
updated(propertyName: string, oldValue: any, newValue: any) {
|
|
86
|
+
console.log(`Property ${propertyName} updated from ${oldValue} to ${newValue}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Core Concepts
|
|
92
|
+
|
|
93
|
+
### 1. Defining the Element (`@CustomElementConfig`)
|
|
94
|
+
|
|
95
|
+
Use the `@CustomElementConfig` decorator on your class to register it with the browser's `CustomElementRegistry`.
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
### 2. Rendering (`render()` method)
|
|
99
|
+
|
|
100
|
+
The `render()` method is **required**. It should return a virtual DOM representation of your element's content,
|
|
101
|
+
using TSX. `CustomElement` uses Maquette's projector internally to efficiently update the DOM.
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
|
|
105
|
+
// ... inside your class
|
|
106
|
+
render() {
|
|
107
|
+
return <div>Hello world</div>;
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The rendering engine automatically schedules updates when reactive properties change. You can manually trigger a render using `this.scheduleRender()` or force an immediate synchronous render with `this.renderNow()`.
|
|
112
|
+
|
|
113
|
+
### 3. Reactive Properties (`@Property` decorator)
|
|
114
|
+
|
|
115
|
+
Declare properties that should trigger re-renders when changed using the `@property` decorator.
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
// ... inside your class
|
|
119
|
+
@property({
|
|
120
|
+
type: 'string', // Data type ('string', 'number', 'boolean', 'object')
|
|
121
|
+
reflect: true, // Reflect property value to an attribute?
|
|
122
|
+
attribute: 'my-attr', // Custom attribute name (defaults to property name)
|
|
123
|
+
readonly: false, // Make the property read-only after first set?
|
|
124
|
+
converter: { // Optional: Custom attribute-to-property conversion
|
|
125
|
+
fromAttribute: (value) => value ? value.toUpperCase() : undefined,
|
|
126
|
+
toAttribute: (value) => value ? value.toLowerCase() : undefined
|
|
127
|
+
}
|
|
128
|
+
})
|
|
129
|
+
myProp: string = 'initial value';
|
|
130
|
+
|
|
131
|
+
@property({ type: 'boolean', reflect: true })
|
|
132
|
+
disabled: boolean = false;
|
|
133
|
+
|
|
134
|
+
@property({ type: 'number' })
|
|
135
|
+
count: number = 0;
|
|
136
|
+
|
|
137
|
+
@property({ type: 'object' })
|
|
138
|
+
data: { id: number, name: string };
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
- **`type`**: Helps with automatic attribute-to-property conversion (especially for `boolean` and `number`).
|
|
142
|
+
- **`reflect`**: If `true`, the property's value is automatically reflected to the corresponding HTML attribute.
|
|
143
|
+
- **`attribute`**: Specifies the HTML attribute name to link with the property. If omitted, it defaults to the property name (e.g., `myProp` becomes `myprop`). For - **`readonly`**: If `true`, the property can only be set once. Subsequent attempts will throw an error.
|
|
144
|
+
- **`converter`**: Provides custom logic for converting between attribute strings and property values.
|
|
145
|
+
|
|
146
|
+
When a property decorated with `@Property` changes, `scheduleRender()` is called automatically. If the property has `reflect: true` and an associated `attribute`, the attribute value is updated accordingly.
|
|
147
|
+
|
|
148
|
+
### 4. Lifecycle Methods
|
|
149
|
+
|
|
150
|
+
`CustomElement` provides hooks into the standard custom element lifecycle and its own rendering cycle:
|
|
151
|
+
|
|
152
|
+
- **`constructor()`**: Called when the element instance is created. Basic initialization happens here.
|
|
153
|
+
- **`init()`**: An optional custom initialization method called after the base class constructor logic finishes but before the first render. Good place for setting up initial state or listeners that don't depend on the DOM.
|
|
154
|
+
- **`connectedCallback()`**: Called when the element is inserted into the DOM. Use this for DOM-dependent setup. Remember to call `super.connectedCallback()` if you override it.
|
|
155
|
+
- **`disconnectedCallback()`**: Called when the element is removed from the DOM. Use for cleanup. Remember to call `super.disconnectedCallback()`.
|
|
156
|
+
- **`attributeChangedCallback(name, oldValue, newValue)`**: Called when an attribute listed in the static `observedAttributes` getter changes. `CustomElement` automatically populates `observedAttributes` based on `@property` decorators with an `attribute` defined and handles updating the corresponding property. You usually only need to override this for attributes *not* managed by `@property`. Call `super.attributeChangedCallback()` to ensure property updates still occur.
|
|
157
|
+
- **`renderStart(isFirstRender: boolean)`**: Called just before the Maquette projector starts a render cycle. `isFirstRender` is true only for the very first render.
|
|
158
|
+
- **`renderDone(isFirstRender: boolean)`**: Called just after the Maquette projector finishes a render cycle and updates the DOM. `isFirstRender` is true only for the very first render.
|
|
159
|
+
- **`shouldUpdate(propertyName, oldValue, newValue)`** Called before update a reactive property, if the method returns true the property will be updated.
|
|
160
|
+
- **`updated(propertyName, oldValue, newValue)`**: Called *after* a reactive property has been updated and the subsequent render cycle (if any) has completed.
|
|
161
|
+
|
|
162
|
+
### 5. Styling
|
|
163
|
+
|
|
164
|
+
There are several ways to style your `CustomElement`:
|
|
165
|
+
|
|
166
|
+
- **Shadow DOM with Static `style` Property**: The recommended approach for encapsulated styles. Define a static `style` property containing your CSS string. `CustomElement` will automatically create a shadow root (if not present) and attach the styles.
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
import { css } from 'p-elements-core/helpers/css'; // Optional helper
|
|
170
|
+
|
|
171
|
+
@customElementConfig({ tagName: 'styled-element' })
|
|
172
|
+
export class StyledElement extends CustomElement {
|
|
173
|
+
static readonly style = css`
|
|
174
|
+
:host {
|
|
175
|
+
display: block;
|
|
176
|
+
border: 1px solid blue;
|
|
177
|
+
padding: 1rem;
|
|
178
|
+
}
|
|
179
|
+
p {
|
|
180
|
+
color: blue;
|
|
181
|
+
}
|
|
182
|
+
`;
|
|
183
|
+
|
|
184
|
+
render() {
|
|
185
|
+
return <div><p>Hello world</p></div>;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
This method uses adopted stylesheets if supported by the browser, falling back to `<link>` elements otherwise. It also includes a polyfill for the abandoned `@apply` CSS proposal using CSS variables. This polyfill is just for backwards compatibility.
|
|
190
|
+
|
|
191
|
+
- **Shadow DOM with `<template>`**: Use `this.templateFromString(htmlString)` in your constructor or `init` method. The method parses the string, extracts and applies the `<style>` tag content to the shadow root, and returns the template content `DocumentFragment`.
|
|
192
|
+
|
|
193
|
+
### 6. Controllers
|
|
194
|
+
|
|
195
|
+
Controllers (`ICustomElementController`) are separate classes that can hook into the element's lifecycle to manage specific behaviors (like handling focus, state management, etc.).
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
// In your element class
|
|
199
|
+
import { MyController } from './my-controller';
|
|
200
|
+
|
|
201
|
+
// ... inside your class
|
|
202
|
+
#myController = new MyController(this); // Pass host element
|
|
203
|
+
|
|
204
|
+
constructor() {
|
|
205
|
+
super();
|
|
206
|
+
this.addController(this.#myController);
|
|
207
|
+
// ...
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
The `CustomElement` will automatically call the controller's corresponding lifecycle methods (`init`, `connected`, `disconnected`, `hostRenderStart`, `hostRenderDone`).
|
|
212
|
+
|
|
213
|
+
### 7. Form Association
|
|
214
|
+
|
|
215
|
+
To make your custom element participate in forms (like a native `<input>`), add a static `formAssociated = true` property and use `this.internals` (ElementInternals API).
|
|
216
|
+
|
|
217
|
+
### 8. Advanced Topics
|
|
218
|
+
|
|
219
|
+
- **Projector Mode**: Control how Maquette renders content relative to the host element (or shadow root container) using the static `projectorMode` property (`'append'`, `'merge'`, or `'replace'`). Defaults to `'append'` unless a static `style` is provided (then defaults to `'replace'` within the shadow root).
|
|
220
|
+
- **Focus Delegation**: Set static `delegatesFocus = true` to forward focus into the shadow root. Requires a shadow root.
|
|
221
|
+
- **`ElementInternals`**: Access via `this.internals` when `static formAssociated = true`. Used for form participation, accessibility roles/states, etc.
|
|
@@ -1,71 +1,71 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: "Bind decorator"
|
|
3
|
-
layout: "base.njk"
|
|
4
|
-
description: "Documentation for @Bind decorator"
|
|
5
|
-
permalink: "/documentation/decorators/bind.html"
|
|
6
|
-
eleventyNavigation:
|
|
7
|
-
parent: Decorators
|
|
8
|
-
key: DecoratorsBuery
|
|
9
|
-
title: Bind
|
|
10
|
-
order: 5
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
# Bind decorator
|
|
15
|
-
|
|
16
|
-
**Note:** This decorator is deprecated. It's recommended to use arrow function expressions instead for binding `this`.
|
|
17
|
-
|
|
18
|
-
The `@Bind` decorator automatically binds a class method to the instance of the class. This ensures that `this` inside the method always refers to the class instance, even when the method is passed as a callback or event handler.
|
|
19
|
-
|
|
20
|
-
## Usage
|
|
21
|
-
|
|
22
|
-
Apply the `@Bind` decorator directly above the method definition.
|
|
23
|
-
|
|
24
|
-
```typescript
|
|
25
|
-
class MyComponent extends HTMLElement {
|
|
26
|
-
message = "Hello!";
|
|
27
|
-
|
|
28
|
-
@Bind
|
|
29
|
-
handleClick() {
|
|
30
|
-
console.log(this.message); // 'this' correctly refers to the MyComponent instance
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
connectedCallback() {
|
|
34
|
-
this.addEventListener('click', this.handleClick);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
disconnectedCallback() {
|
|
38
|
-
this.removeEventListener('click', this.handleClick);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
customElements.define('my-component', MyComponent);
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
## Why use it? (Deprecated)
|
|
46
|
-
|
|
47
|
-
In JavaScript, the value of `this` can change depending on how a function is called. When passing a method as a callback (like in `addEventListener`), `this` might not refer to the class instance, leading to errors. `@Bind` solves this by ensuring the method is always bound to the instance.
|
|
48
|
-
|
|
49
|
-
## Alternative (Recommended)
|
|
50
|
-
|
|
51
|
-
Use arrow function expressions for class methods that need to be bound:
|
|
52
|
-
|
|
53
|
-
```typescript
|
|
54
|
-
class MyComponent extends HTMLElement {
|
|
55
|
-
message = "Hello!";
|
|
56
|
-
|
|
57
|
-
handleClick = () => {
|
|
58
|
-
console.log(this.message); // 'this' correctly refers to the MyComponent instance
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
connectedCallback() {
|
|
62
|
-
this.addEventListener('click', this.handleClick);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
disconnectedCallback() {
|
|
66
|
-
this.removeEventListener('click', this.handleClick);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
customElements.define('my-component', MyComponent);
|
|
71
|
-
```
|
|
1
|
+
---
|
|
2
|
+
title: "Bind decorator"
|
|
3
|
+
layout: "base.njk"
|
|
4
|
+
description: "Documentation for @Bind decorator"
|
|
5
|
+
permalink: "/documentation/decorators/bind.html"
|
|
6
|
+
eleventyNavigation:
|
|
7
|
+
parent: Decorators
|
|
8
|
+
key: DecoratorsBuery
|
|
9
|
+
title: Bind
|
|
10
|
+
order: 5
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Bind decorator
|
|
15
|
+
|
|
16
|
+
**Note:** This decorator is deprecated. It's recommended to use arrow function expressions instead for binding `this`.
|
|
17
|
+
|
|
18
|
+
The `@Bind` decorator automatically binds a class method to the instance of the class. This ensures that `this` inside the method always refers to the class instance, even when the method is passed as a callback or event handler.
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
Apply the `@Bind` decorator directly above the method definition.
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
class MyComponent extends HTMLElement {
|
|
26
|
+
message = "Hello!";
|
|
27
|
+
|
|
28
|
+
@Bind
|
|
29
|
+
handleClick() {
|
|
30
|
+
console.log(this.message); // 'this' correctly refers to the MyComponent instance
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
connectedCallback() {
|
|
34
|
+
this.addEventListener('click', this.handleClick);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
disconnectedCallback() {
|
|
38
|
+
this.removeEventListener('click', this.handleClick);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
customElements.define('my-component', MyComponent);
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Why use it? (Deprecated)
|
|
46
|
+
|
|
47
|
+
In JavaScript, the value of `this` can change depending on how a function is called. When passing a method as a callback (like in `addEventListener`), `this` might not refer to the class instance, leading to errors. `@Bind` solves this by ensuring the method is always bound to the instance.
|
|
48
|
+
|
|
49
|
+
## Alternative (Recommended)
|
|
50
|
+
|
|
51
|
+
Use arrow function expressions for class methods that need to be bound:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
class MyComponent extends HTMLElement {
|
|
55
|
+
message = "Hello!";
|
|
56
|
+
|
|
57
|
+
handleClick = () => {
|
|
58
|
+
console.log(this.message); // 'this' correctly refers to the MyComponent instance
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
connectedCallback() {
|
|
62
|
+
this.addEventListener('click', this.handleClick);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
disconnectedCallback() {
|
|
66
|
+
this.removeEventListener('click', this.handleClick);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
customElements.define('my-component', MyComponent);
|
|
71
|
+
```
|