1 | import { ArrayExt } from '@lumino/algorithm';
|
2 | import { SplitLayout } from './splitlayout';
|
3 | import { Title } from './title';
|
4 | import Utils from './utils';
|
5 | import { Widget } from './widget';
|
6 |
|
7 |
|
8 |
|
9 |
|
10 | export class AccordionLayout extends SplitLayout {
|
11 | |
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 | constructor(options: AccordionLayout.IOptions) {
|
22 | super({ ...options, orientation: options.orientation || 'vertical' });
|
23 | this.titleSpace = options.titleSpace || 22;
|
24 | }
|
25 |
|
26 | |
27 |
|
28 |
|
29 | get titleSpace(): number {
|
30 | return this.widgetOffset;
|
31 | }
|
32 | set titleSpace(value: number) {
|
33 | value = Utils.clampDimension(value);
|
34 | if (this.widgetOffset === value) {
|
35 | return;
|
36 | }
|
37 | this.widgetOffset = value;
|
38 | if (!this.parent) {
|
39 | return;
|
40 | }
|
41 | this.parent.fit();
|
42 | }
|
43 |
|
44 | |
45 |
|
46 |
|
47 | get titles(): ReadonlyArray<HTMLElement> {
|
48 | return this._titles;
|
49 | }
|
50 |
|
51 | |
52 |
|
53 |
|
54 | dispose(): void {
|
55 | if (this.isDisposed) {
|
56 | return;
|
57 | }
|
58 |
|
59 |
|
60 | this._titles.length = 0;
|
61 |
|
62 |
|
63 | super.dispose();
|
64 | }
|
65 |
|
66 | |
67 |
|
68 |
|
69 | readonly renderer: AccordionLayout.IRenderer;
|
70 |
|
71 | public updateTitle(index: number, widget: Widget): void {
|
72 | const oldTitle = this._titles[index];
|
73 | const expanded = oldTitle.classList.contains('lm-mod-expanded');
|
74 | const newTitle = Private.createTitle(this.renderer, widget.title, expanded);
|
75 | this._titles[index] = newTitle;
|
76 |
|
77 |
|
78 | this.parent!.node.replaceChild(newTitle, oldTitle);
|
79 | }
|
80 |
|
81 | |
82 |
|
83 |
|
84 |
|
85 |
|
86 |
|
87 |
|
88 | protected attachWidget(index: number, widget: Widget): void {
|
89 | const title = Private.createTitle(this.renderer, widget.title);
|
90 |
|
91 | ArrayExt.insert(this._titles, index, title);
|
92 |
|
93 |
|
94 | this.parent!.node.appendChild(title);
|
95 |
|
96 | widget.node.setAttribute('role', 'region');
|
97 | widget.node.setAttribute('aria-labelledby', title.id);
|
98 |
|
99 | super.attachWidget(index, widget);
|
100 | }
|
101 |
|
102 | |
103 |
|
104 |
|
105 |
|
106 |
|
107 |
|
108 |
|
109 |
|
110 |
|
111 | protected moveWidget(
|
112 | fromIndex: number,
|
113 | toIndex: number,
|
114 | widget: Widget
|
115 | ): void {
|
116 | ArrayExt.move(this._titles, fromIndex, toIndex);
|
117 | super.moveWidget(fromIndex, toIndex, widget);
|
118 | }
|
119 |
|
120 | |
121 |
|
122 |
|
123 |
|
124 |
|
125 |
|
126 |
|
127 |
|
128 |
|
129 |
|
130 | protected detachWidget(index: number, widget: Widget): void {
|
131 | const title = ArrayExt.removeAt(this._titles, index);
|
132 |
|
133 | this.parent!.node.removeChild(title!);
|
134 |
|
135 | super.detachWidget(index, widget);
|
136 | }
|
137 |
|
138 | |
139 |
|
140 |
|
141 |
|
142 |
|
143 |
|
144 |
|
145 |
|
146 |
|
147 |
|
148 |
|
149 | protected updateItemPosition(
|
150 | i: number,
|
151 | isHorizontal: boolean,
|
152 | left: number,
|
153 | top: number,
|
154 | height: number,
|
155 | width: number,
|
156 | size: number
|
157 | ): void {
|
158 | const titleStyle = this._titles[i].style;
|
159 |
|
160 |
|
161 | titleStyle.top = `${top}px`;
|
162 | titleStyle.left = `${left}px`;
|
163 | titleStyle.height = `${this.widgetOffset}px`;
|
164 | if (isHorizontal) {
|
165 | titleStyle.width = `${height}px`;
|
166 | } else {
|
167 | titleStyle.width = `${width}px`;
|
168 | }
|
169 |
|
170 | super.updateItemPosition(i, isHorizontal, left, top, height, width, size);
|
171 | }
|
172 |
|
173 | private _titles: HTMLElement[] = [];
|
174 | }
|
175 |
|
176 | export namespace AccordionLayout {
|
177 | |
178 |
|
179 |
|
180 | export type Orientation = SplitLayout.Orientation;
|
181 |
|
182 | |
183 |
|
184 |
|
185 | export type Alignment = SplitLayout.Alignment;
|
186 |
|
187 | |
188 |
|
189 |
|
190 | export interface IOptions extends SplitLayout.IOptions {
|
191 | |
192 |
|
193 |
|
194 | renderer: IRenderer;
|
195 |
|
196 | |
197 |
|
198 |
|
199 |
|
200 |
|
201 | titleSpace?: number;
|
202 | }
|
203 |
|
204 | |
205 |
|
206 |
|
207 | export interface IRenderer extends SplitLayout.IRenderer {
|
208 | |
209 |
|
210 |
|
211 | readonly titleClassName: string;
|
212 |
|
213 | |
214 |
|
215 |
|
216 |
|
217 |
|
218 |
|
219 |
|
220 | createSectionTitle(title: Title<Widget>): HTMLElement;
|
221 | }
|
222 | }
|
223 |
|
224 | namespace Private {
|
225 | |
226 |
|
227 |
|
228 |
|
229 |
|
230 |
|
231 |
|
232 | export function createTitle(
|
233 | renderer: AccordionLayout.IRenderer,
|
234 | data: Title<Widget>,
|
235 | expanded: boolean = true
|
236 | ): HTMLElement {
|
237 | const title = renderer.createSectionTitle(data);
|
238 | title.style.position = 'absolute';
|
239 | title.setAttribute('aria-label', `${data.label} Section`);
|
240 | title.setAttribute('aria-expanded', expanded ? 'true' : 'false');
|
241 | title.setAttribute('aria-controls', data.owner.id);
|
242 | if (expanded) {
|
243 | title.classList.add('lm-mod-expanded');
|
244 | }
|
245 | return title;
|
246 | }
|
247 | }
|