-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCOMPONENT-NAME.js
More file actions
120 lines (107 loc) · 3.03 KB
/
COMPONENT-NAME.js
File metadata and controls
120 lines (107 loc) · 3.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
/**
* ComponentNameElement - COMPONENT_DESCRIPTION
*
* @element COMPONENT-NAME
*
* @attr {string} example-attribute - Description of the attribute
*
* @fires COMPONENT-NAME:event-name - Description of the event
*
* @slot - Default slot for content
*
* @cssprop --component-name-color - Description of CSS custom property
*/
export class ComponentNameElement extends HTMLElement {
static get observedAttributes() {
return ['example-attribute'];
}
constructor() {
super();
this.attachShadow({ mode: 'open' });
this._internals = {
isRendered: false,
};
}
connectedCallback() {
requestAnimationFrame(() => {
// Upgrade properties that may have been set before the element was defined
this._upgradeProperty('exampleAttribute');
// Check for global attributes before setting defaults
// Don't override author-set attributes
if (!this.hasAttribute('role')) {
// this.setAttribute('role', 'group'); // Example - set appropriate role
}
if (!this.hasAttribute('tabindex')) {
// this.setAttribute('tabindex', 0); // Example - set if focusable
}
this.render();
});
}
attributeChangedCallback(name, oldValue, newValue) {
// Only handle side effects, avoid full re-render
// The property getter will read from the attribute
if (oldValue === newValue) {
return;
}
switch (name) {
case 'example-attribute':
// Handle side effects (e.g., update ARIA attributes, dispatch events)
// Don't re-render the entire component unless necessary
// Example: this.setAttribute('aria-label', newValue);
// Dispatch events for internal state changes, not for host-set properties
// Only dispatch if the change came from internal component activity
if (this._internals.isRendered) {
// Example pattern (commented out by default):
// this.dispatchEvent(new CustomEvent('COMPONENT-NAME:change', {
// detail: { exampleAttribute: newValue },
// bubbles: true,
// composed: true
// }));
}
break;
}
}
/**
* Upgrade a property to handle cases where it was set before the element upgraded.
* This is especially important for framework compatibility.
* @param {string} prop - Property name to upgrade
* @private
*/
_upgradeProperty(prop) {
if (Object.prototype.hasOwnProperty.call(this, prop)) {
const value = this[prop];
delete this[prop];
this[prop] = value;
}
}
/**
* Example attribute as a property.
* Reflects between property and attribute to keep them in sync.
*/
get exampleAttribute() {
return this.getAttribute('example-attribute');
}
set exampleAttribute(value) {
// Reflect property to attribute
if (value === null || value === undefined) {
this.removeAttribute('example-attribute');
} else {
this.setAttribute('example-attribute', value);
}
}
render() {
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
}
/* Support the hidden attribute properly */
:host([hidden]) {
display: none;
}
</style>
<slot></slot>
`;
this._internals.isRendered = true;
}
}