-
-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathct-collapse.ts
More file actions
130 lines (118 loc) · 2.93 KB
/
Copy pathct-collapse.ts
File metadata and controls
130 lines (118 loc) · 2.93 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
121
122
123
124
125
126
127
128
129
130
import { sleep } from "./ct-helpers.js";
import { CtLit, css, customElement, html, property, query } from "./ct-lit.js";
/**
* ## `ct-collapse`
* A collapsible content component that can smoothly expand and collapse with animation.
*
* ### Usage
* ```html
* <ct-collapse opened>
* <div>This content can be expanded or collapsed</div>
* </ct-collapse>
* ```
*
* ### Events
* - None specific to this component
*
* ### Notes
* - Only one child element is supported. If multiple elements are needed, wrap them in a container.
* - The component automatically calculates required heights for smooth animations.
*
* @group lit-ct-components
* @element ct-collapse
*/
@customElement("ct-collapse")
export class CtCollapse extends CtLit {
/**
* Controls whether the content is expanded (true) or collapsed (false)
*/
@property({ type: Boolean }) opened = false;
/**
* Reference to the content slot element
*/
@query("#content") $content!: HTMLSlotElement;
/**
* Stores the main content element
* @private
*/
content: any;
/**
* Stores assigned elements
* @private
*/
elems: any[] = [];
static styles: any = [
css`
:host {
display: block;
transition: all 250ms;
overflow: hidden;
}
:host(:not(.open)) {
max-height: 0 !important;
}
`
];
/**
* Renders the collapse component with a slot for content
* @returns {TemplateResult} The rendered template
*/
render(): any {
return html` <slot id="content"></slot> `;
}
/**
* Lifecycle callback when the component is first updated
* Gets the assigned nodes and performs initial setup
*/
firstUpdated() {
let elems = (this.$content.assignedNodes() as HTMLElement[]).filter(elem => elem.nodeType == Node.ELEMENT_NODE);
this.content = elems[0];
if (elems.length > 1) {
console.warn("`ct-collapse` can have a ONE child, you can wrap him in a <div>");
}
}
/**
* Handles property updates and triggers height calculation when opened state changes
* @param {Map<PropertyKey, any>} map - Map of changed properties
*/
update(map: Map<PropertyKey, any>) {
super.update(map);
if (map.has("opened")) {
this.calcMaxHeight(this.opened);
}
}
/**
* Toggles the opened state of the collapse component
*/
toggle() {
this.opened = !this.opened;
}
/**
* Calculates and sets the max height for smooth animation
* @param {boolean} opened - Whether the collapse should be opened
* @private
*/
async calcMaxHeight(opened: boolean) {
if (this.content) {
this.style.maxHeight = `${this.content.offsetHeight}px`;
if (!opened) {
this.style.overflow = "hidden!important";
}
await sleep(50);
this.classList.toggle("open", opened);
await sleep(250);
this.style.maxHeight = ``;
if (opened) {
this.style.overflow = "visible!important";
}
} else if (opened) {
await sleep(50);
this.calcMaxHeight(opened);
}
}
}
declare global {
interface HTMLElementTagNameMap {
"ct-collapse": CtCollapse;
}
}