1 |
|
2 |
|
3 |
|
4 | import { Message } from '@lumino/messaging';
|
5 | import { ISignal, Signal } from '@lumino/signaling';
|
6 | import { Panel, PanelLayout, Title, Widget } from '@lumino/widgets';
|
7 |
|
8 | import { caretDownIcon } from '../icon';
|
9 |
|
10 | const COLLAPSE_CLASS = 'jp-Collapse';
|
11 |
|
12 | const CONTENTS_CLASS = 'jp-Collapse-contents';
|
13 |
|
14 | const HEADER_CLASS = 'jp-Collapse-header';
|
15 |
|
16 | const HEADER_COLLAPSED_CLASS = 'jp-Collapse-header-collapsed';
|
17 |
|
18 | const ICON_CLASS = 'jp-Collapser-icon';
|
19 |
|
20 | const TITLE_CLASS = 'jp-Collapser-title';
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 | export class Collapser<T extends Widget = Widget> extends Widget {
|
27 | constructor(options: Collapser.IOptions<T>) {
|
28 | super(options);
|
29 | const { widget, collapsed = true } = options;
|
30 |
|
31 | this.addClass(COLLAPSE_CLASS);
|
32 | this._header = new Widget();
|
33 | this._header.addClass(HEADER_CLASS);
|
34 | if (collapsed) {
|
35 | this._header.addClass(HEADER_COLLAPSED_CLASS);
|
36 | }
|
37 | this._header.node.appendChild(
|
38 | caretDownIcon.element({
|
39 | className: ICON_CLASS
|
40 | })
|
41 | );
|
42 | const titleSpan = document.createElement('span');
|
43 | titleSpan.classList.add(TITLE_CLASS);
|
44 | titleSpan.textContent = widget.title.label;
|
45 | this._header.node.appendChild(titleSpan);
|
46 |
|
47 | this._content = new Panel();
|
48 | this._content.addClass(CONTENTS_CLASS);
|
49 |
|
50 | const layout = new PanelLayout();
|
51 | this.layout = layout;
|
52 | layout.addWidget(this._header);
|
53 | layout.addWidget(this._content);
|
54 |
|
55 | this.widget = widget;
|
56 | this.collapsed = collapsed;
|
57 | }
|
58 |
|
59 | |
60 |
|
61 |
|
62 | get widget(): T {
|
63 | return this._widget;
|
64 | }
|
65 | set widget(widget: T) {
|
66 | const oldWidget = this._widget;
|
67 | if (oldWidget) {
|
68 | oldWidget.title.changed.disconnect(this._onTitleChanged, this);
|
69 | oldWidget.parent = null;
|
70 | }
|
71 | this._widget = widget;
|
72 | widget.title.changed.connect(this._onTitleChanged, this);
|
73 | this._onTitleChanged(widget.title);
|
74 | this._content.addWidget(widget);
|
75 | }
|
76 |
|
77 | |
78 |
|
79 |
|
80 | get collapsed(): boolean {
|
81 | return this._collapsed;
|
82 | }
|
83 | set collapsed(value: boolean) {
|
84 | if (value === this._collapsed) {
|
85 | return;
|
86 | }
|
87 | if (value) {
|
88 | this._collapse();
|
89 | } else {
|
90 | this._uncollapse();
|
91 | }
|
92 | }
|
93 |
|
94 | |
95 |
|
96 |
|
97 | get collapseChanged(): ISignal<Collapser, void> {
|
98 | return this._collapseChanged;
|
99 | }
|
100 |
|
101 | |
102 |
|
103 |
|
104 | toggle(): void {
|
105 | this.collapsed = !this.collapsed;
|
106 | }
|
107 |
|
108 | |
109 |
|
110 |
|
111 | dispose(): void {
|
112 | if (this.isDisposed) {
|
113 | return;
|
114 | }
|
115 |
|
116 |
|
117 | this._header = null!;
|
118 | this._widget = null!;
|
119 | this._content = null!;
|
120 |
|
121 | super.dispose();
|
122 | }
|
123 |
|
124 | |
125 |
|
126 |
|
127 |
|
128 |
|
129 |
|
130 |
|
131 |
|
132 |
|
133 |
|
134 | handleEvent(event: Event): void {
|
135 | switch (event.type) {
|
136 | case 'click':
|
137 | this._evtClick(event as MouseEvent);
|
138 | break;
|
139 | default:
|
140 | break;
|
141 | }
|
142 | }
|
143 |
|
144 | protected onAfterAttach(msg: Message): void {
|
145 | this._header.node.addEventListener('click', this);
|
146 | }
|
147 |
|
148 | protected onBeforeDetach(msg: Message): void {
|
149 | this._header.node.removeEventListener('click', this);
|
150 | }
|
151 |
|
152 | private _collapse() {
|
153 | this._collapsed = true;
|
154 | if (this._content) {
|
155 | this._content.hide();
|
156 | }
|
157 | this._setHeader();
|
158 | this._collapseChanged.emit(void 0);
|
159 | }
|
160 |
|
161 | private _uncollapse() {
|
162 | this._collapsed = false;
|
163 | if (this._content) {
|
164 | this._content.show();
|
165 | }
|
166 | this._setHeader();
|
167 | this._collapseChanged.emit(void 0);
|
168 | }
|
169 |
|
170 | private _evtClick(event: MouseEvent) {
|
171 | this.toggle();
|
172 | }
|
173 |
|
174 | |
175 |
|
176 |
|
177 | private _onTitleChanged(sender: Title<Widget>): void {
|
178 | this._setHeader();
|
179 | }
|
180 |
|
181 | private _setHeader(): void {
|
182 | if (this._collapsed) {
|
183 | this._header.addClass(HEADER_COLLAPSED_CLASS);
|
184 | } else {
|
185 | this._header.removeClass(HEADER_COLLAPSED_CLASS);
|
186 | }
|
187 | }
|
188 |
|
189 | private _collapseChanged = new Signal<this, void>(this);
|
190 | private _collapsed: boolean;
|
191 | private _content: Panel;
|
192 | private _header: Widget;
|
193 | private _widget: T;
|
194 | }
|
195 |
|
196 | export namespace Collapser {
|
197 | export interface IOptions<T extends Widget = Widget> extends Widget.IOptions {
|
198 | widget: T;
|
199 | collapsed?: boolean;
|
200 | }
|
201 | }
|