UNPKG

6.52 kBPlain TextView Raw
1import { ArrayExt } from '@lumino/algorithm';
2import { SplitLayout } from './splitlayout';
3import { Title } from './title';
4import Utils from './utils';
5import { Widget } from './widget';
6
7/**
8 * A layout which arranges its widgets into collapsible resizable sections.
9 */
10export class AccordionLayout extends SplitLayout {
11 /**
12 * Construct a new accordion layout.
13 *
14 * @param options - The options for initializing the layout.
15 *
16 * #### Notes
17 * The default orientation will be vertical.
18 *
19 * Titles must be rotated for horizontal accordion panel using CSS: see accordionpanel.css
20 */
21 constructor(options: AccordionLayout.IOptions) {
22 super({ ...options, orientation: options.orientation || 'vertical' });
23 this.titleSpace = options.titleSpace || 22;
24 }
25
26 /**
27 * The section title height or width depending on the orientation.
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 * A read-only array of the section titles in the panel.
46 */
47 get titles(): ReadonlyArray<HTMLElement> {
48 return this._titles;
49 }
50
51 /**
52 * Dispose of the resources held by the layout.
53 */
54 dispose(): void {
55 if (this.isDisposed) {
56 return;
57 }
58
59 // Clear the layout state.
60 this._titles.length = 0;
61
62 // Dispose of the rest of the layout.
63 super.dispose();
64 }
65
66 /**
67 * The renderer used by the accordion layout.
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 // Add the title node to the parent before the widget.
78 this.parent!.node.replaceChild(newTitle, oldTitle);
79 }
80
81 /**
82 * Attach a widget to the parent's DOM node.
83 *
84 * @param index - The current index of the widget in the layout.
85 *
86 * @param widget - The widget to attach to the parent.
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 // Add the title node to the parent before the widget.
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 * Move a widget in the parent's DOM node.
104 *
105 * @param fromIndex - The previous index of the widget in the layout.
106 *
107 * @param toIndex - The current index of the widget in the layout.
108 *
109 * @param widget - The widget to move in the parent.
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 * Detach a widget from the parent's DOM node.
122 *
123 * @param index - The previous index of the widget in the layout.
124 *
125 * @param widget - The widget to detach from the parent.
126 *
127 * #### Notes
128 * This is a reimplementation of the superclass method.
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 * Update the item position.
140 *
141 * @param i Item index
142 * @param isHorizontal Whether the layout is horizontal or not
143 * @param left Left position in pixels
144 * @param top Top position in pixels
145 * @param height Item height
146 * @param width Item width
147 * @param size Item size
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 // Titles must be rotated for horizontal accordion panel using CSS: see accordionpanel.css
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
176export namespace AccordionLayout {
177 /**
178 * A type alias for a accordion layout orientation.
179 */
180 export type Orientation = SplitLayout.Orientation;
181
182 /**
183 * A type alias for a accordion layout alignment.
184 */
185 export type Alignment = SplitLayout.Alignment;
186
187 /**
188 * An options object for initializing a accordion layout.
189 */
190 export interface IOptions extends SplitLayout.IOptions {
191 /**
192 * The renderer to use for the accordion layout.
193 */
194 renderer: IRenderer;
195
196 /**
197 * The section title height or width depending on the orientation.
198 *
199 * The default is `22`.
200 */
201 titleSpace?: number;
202 }
203
204 /**
205 * A renderer for use with an accordion layout.
206 */
207 export interface IRenderer extends SplitLayout.IRenderer {
208 /**
209 * Common class name for all accordion titles.
210 */
211 readonly titleClassName: string;
212
213 /**
214 * Render the element for a section title.
215 *
216 * @param data - The data to use for rendering the section title.
217 *
218 * @returns A element representing the section title.
219 */
220 createSectionTitle(title: Title<Widget>): HTMLElement;
221 }
222}
223
224namespace Private {
225 /**
226 * Create the title HTML element.
227 *
228 * @param renderer Accordion renderer
229 * @param data Widget title
230 * @returns Title HTML element
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}