x4js 2.0.35 → 2.1.0-manual
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -21
- package/package.json +39 -26
- package/src/components/base.scss +25 -89
- package/src/components/boxes/boxes.module.scss +54 -54
- package/src/components/boxes/boxes.ts +513 -513
- package/src/components/breadcrumb/breadcrumb.scss +56 -56
- package/src/components/breadcrumb/breadcrumb.ts +93 -93
- package/src/components/btngroup/btngroup.module.scss +40 -40
- package/src/components/btngroup/btngroup.ts +152 -152
- package/src/components/button/button.module.scss +172 -172
- package/src/components/button/button.ts +232 -232
- package/src/components/calendar/calendar.module.scss +162 -162
- package/src/components/calendar/calendar.ts +326 -326
- package/src/components/canvas/canvas.module.scss +24 -24
- package/src/components/canvas/canvas.ts +195 -195
- package/src/components/canvas/canvas_ex.ts +275 -275
- package/src/components/checkbox/check.svg +3 -3
- package/src/components/checkbox/checkbox.module.scss +141 -141
- package/src/components/checkbox/checkbox.ts +139 -139
- package/src/components/colorinput/colorinput.module.scss +64 -64
- package/src/components/colorinput/colorinput.ts +90 -90
- package/src/components/colorpicker/colorpicker.module.scss +132 -132
- package/src/components/colorpicker/colorpicker.ts +481 -481
- package/src/components/combobox/combobox.module.scss +145 -145
- package/src/components/combobox/combobox.ts +282 -282
- package/src/components/combobox/updown.svg +3 -3
- package/src/components/components.ts +45 -44
- package/src/components/dialog/dialog.module.scss +103 -105
- package/src/components/dialog/dialog.ts +233 -233
- package/src/components/filedrop/filedrop.module.scss +69 -69
- package/src/components/filedrop/filedrop.ts +130 -130
- package/src/components/form/form.module.scss +38 -38
- package/src/components/form/form.ts +172 -172
- package/src/components/gridview/gridview.module.scss +323 -337
- package/src/components/gridview/gridview.ts +1276 -1316
- package/src/components/header/header.module.scss +40 -40
- package/src/components/header/header.ts +141 -141
- package/src/components/icon/icon.module.scss +32 -32
- package/src/components/icon/icon.ts +165 -165
- package/src/components/image/image.module.scss +27 -27
- package/src/components/image/image.ts +168 -168
- package/src/components/input/input.module.scss +74 -74
- package/src/components/input/input.ts +537 -537
- package/src/components/keyboard/keyboard.module.scss +136 -136
- package/src/components/keyboard/keyboard.ts +549 -549
- package/src/components/label/label.module.scss +90 -91
- package/src/components/label/label.ts +101 -101
- package/src/components/link/link.module.scss +44 -44
- package/src/components/link/link.ts +87 -87
- package/src/components/listbox/listbox.module.scss +179 -179
- package/src/components/listbox/listbox.ts +596 -596
- package/src/components/menu/menu.module.scss +128 -128
- package/src/components/menu/menu.ts +174 -174
- package/src/components/messages/messages.module.scss +92 -146
- package/src/components/messages/messages.ts +237 -303
- package/src/components/normalize.scss +391 -391
- package/src/components/notification/notification.module.scss +83 -83
- package/src/components/notification/notification.ts +107 -107
- package/src/components/panel/panel.module.scss +66 -71
- package/src/components/panel/panel.ts +57 -57
- package/src/components/popup/popup.module.scss +51 -51
- package/src/components/popup/popup.ts +457 -457
- package/src/components/progress/progress.module.scss +56 -56
- package/src/components/progress/progress.ts +43 -43
- package/src/components/propgrid/progrid.module.scss +111 -111
- package/src/components/propgrid/propgrid.ts +300 -300
- package/src/components/propgrid/updown.svg +3 -3
- package/src/components/radio/radio.module.scss +163 -163
- package/src/components/radio/radio.svg +3 -3
- package/src/components/radio/radio.ts +141 -141
- package/src/components/rating/rating.module.scss +22 -22
- package/src/components/rating/rating.ts +131 -131
- package/src/components/select/select.module.scss +8 -8
- package/src/components/select/select.ts +134 -134
- package/src/components/shared.scss +141 -71
- package/src/components/sizers/sizer.module.scss +90 -112
- package/src/components/sizers/sizer.ts +131 -155
- package/src/components/slider/slider.module.scss +117 -117
- package/src/components/slider/slider.ts +197 -197
- package/src/components/spreadsheet/spreadsheet.module.scss +307 -307
- package/src/components/spreadsheet/spreadsheet.ts +1223 -1223
- package/src/components/switch/switch.module.scss +126 -126
- package/src/components/switch/switch.ts +61 -61
- package/src/components/tabs/tabs.module.scss +46 -67
- package/src/components/tabs/tabs.ts +229 -234
- package/src/components/textarea/textarea.module.scss +63 -63
- package/src/components/textarea/textarea.ts +131 -131
- package/src/components/textedit/textedit.module.scss +115 -115
- package/src/components/textedit/textedit.ts +122 -122
- package/src/components/themes.scss +90 -90
- package/src/components/tickline/tickline.module.scss +25 -25
- package/src/components/tickline/tickline.ts +81 -81
- package/src/components/tooltips/tooltips.scss +71 -71
- package/src/components/tooltips/tooltips.ts +120 -120
- package/src/components/treeview/treeview.module.scss +192 -192
- package/src/components/treeview/treeview.ts +484 -484
- package/src/components/viewport/viewport.module.scss +31 -31
- package/src/components/viewport/viewport.ts +41 -41
- package/src/core/component.ts +1299 -1299
- package/src/core/core_application.ts +361 -361
- package/src/core/core_colors.ts +512 -512
- package/src/core/core_data.ts +1297 -1310
- package/src/core/core_dom.ts +481 -481
- package/src/core/core_dragdrop.ts +225 -225
- package/src/core/core_element.ts +221 -221
- package/src/core/core_events.ts +214 -214
- package/src/core/core_i18n.ts +395 -395
- package/src/core/core_pdf.ts +454 -454
- package/src/core/core_react.ts +78 -78
- package/src/core/core_router.ts +296 -296
- package/src/core/core_state.ts +62 -62
- package/src/core/core_styles.ts +213 -213
- package/src/core/core_svg.ts +1042 -1042
- package/src/core/core_tools.ts +996 -996
- package/src/types/scss.d.ts +4 -4
- package/src/types/x4react.d.ts +8 -8
- package/src/x4.scss +19 -19
- package/src/x4.ts +36 -36
- package/src/x4tsx.d.ts +26 -26
- package/.vscode/launch.json +0 -14
- package/.vscode/settings.json +0 -2
- package/demo/assets/house-light.svg +0 -1
- package/demo/assets/radio.svg +0 -4
- package/demo/index.html +0 -12
- package/demo/main.scss +0 -23
- package/demo/main.ts +0 -324
- package/demo/package.json +0 -26
- package/demo/scss.d.ts +0 -4
- package/demo/svg.d.ts +0 -1
- package/demo/tsconfig.json +0 -14
- package/src/components/gridview/folder-open.svg +0 -1
- package/src/components/messages/spinner.svg +0 -1
- package/src/x4.d.ts +0 -10
- package/tsconfig.json +0 -11
|
@@ -1,513 +1,513 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ___ ___ __
|
|
3
|
-
* \ \/ / / _
|
|
4
|
-
* \ / /_| |_
|
|
5
|
-
* / \____ _|
|
|
6
|
-
* /__/\__\ |_|
|
|
7
|
-
*
|
|
8
|
-
* @file boxes.ts
|
|
9
|
-
* @author Etienne Cochard
|
|
10
|
-
*
|
|
11
|
-
* @copyright (c) 2024 R-libre ingenierie
|
|
12
|
-
*
|
|
13
|
-
* Use of this source code is governed by an MIT-style license
|
|
14
|
-
* that can be found in the LICENSE file or at https://opensource.org/licenses/MIT.
|
|
15
|
-
**/
|
|
16
|
-
|
|
17
|
-
import { asap, class_ns, isArray, isNumber } from '../../core/core_tools';
|
|
18
|
-
import { Component, ComponentEvents, ComponentProps, EvSelectionChange } from "../../core/component"
|
|
19
|
-
import { EventCallback } from '../../core/core_events';
|
|
20
|
-
|
|
21
|
-
import "./boxes.module.scss";
|
|
22
|
-
|
|
23
|
-
export interface BoxProps extends ComponentProps {
|
|
24
|
-
/** Optional HTML tag to use for the box. */
|
|
25
|
-
tag?: string;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* A generic container component for grouping and laying out child components.
|
|
30
|
-
* The CSS class for this component is automatically generated as `x4box`.
|
|
31
|
-
*/
|
|
32
|
-
|
|
33
|
-
@class_ns( "x4" )
|
|
34
|
-
export class Box<P extends BoxProps=BoxProps,E extends ComponentEvents=ComponentEvents> extends Component<P,E> {
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* A horizontal box layout component.
|
|
40
|
-
* Arranges child components in a horizontal line.
|
|
41
|
-
* The CSS class for this component is automatically generated as `x4hbox`.
|
|
42
|
-
*/
|
|
43
|
-
|
|
44
|
-
@class_ns( "x4" )
|
|
45
|
-
export class HBox<P extends BoxProps=BoxProps,E extends ComponentEvents=ComponentEvents> extends Box<P,E> {
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* A vertical box layout component.
|
|
50
|
-
* Arranges child components in a vertical stack.
|
|
51
|
-
* The CSS class for this component is automatically generated as `x4vbox`.
|
|
52
|
-
*/
|
|
53
|
-
|
|
54
|
-
@class_ns( "x4" )
|
|
55
|
-
export class VBox<P extends BoxProps=BoxProps,E extends ComponentEvents=ComponentEvents> extends Box<P,E> {
|
|
56
|
-
constructor( p: P ) {
|
|
57
|
-
super( p );
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
type ContentBuilder = ( ) => Component;
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Represents an item in a {@link StackBox}.
|
|
67
|
-
*/
|
|
68
|
-
|
|
69
|
-
interface StackItem {
|
|
70
|
-
/** Unique name for the stack item. */
|
|
71
|
-
name: string;
|
|
72
|
-
/** Content of the stack item, either a component or a builder function. */
|
|
73
|
-
content: Component | ContentBuilder;
|
|
74
|
-
title?: string;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Events specific to the {@link StackBox} component.
|
|
79
|
-
*/
|
|
80
|
-
|
|
81
|
-
interface StackeBoxEvents extends ComponentEvents {
|
|
82
|
-
/** Fired when the current page changes. */
|
|
83
|
-
pageChange?: EvSelectionChange;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Properties for the {@link StackBox} component.
|
|
88
|
-
*/
|
|
89
|
-
|
|
90
|
-
export interface StackBoxProps extends Omit<ComponentProps,"content"> {
|
|
91
|
-
/** Name of the default page to display. */
|
|
92
|
-
default: string;
|
|
93
|
-
|
|
94
|
-
/** List of stack items. */
|
|
95
|
-
items: StackItem[];
|
|
96
|
-
|
|
97
|
-
/** Callback for page change events. */
|
|
98
|
-
pageChange?: EventCallback<EvSelectionChange>;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
interface StackItemEx extends StackItem {
|
|
103
|
-
page: Component;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* A stack of widgets where only one widget is visible at a time.
|
|
108
|
-
* The CSS class for this component is automatically generated as `x4stackbox`.
|
|
109
|
-
*/
|
|
110
|
-
|
|
111
|
-
@class_ns( "x4" )
|
|
112
|
-
export class StackBox<P extends StackBoxProps = StackBoxProps, E extends StackeBoxEvents = StackeBoxEvents> extends Box<StackBoxProps,StackeBoxEvents> {
|
|
113
|
-
|
|
114
|
-
protected _items: StackItemEx[];
|
|
115
|
-
protected _cur: number;
|
|
116
|
-
|
|
117
|
-
constructor( props: StackBoxProps ) {
|
|
118
|
-
super( props );
|
|
119
|
-
|
|
120
|
-
this.mapPropEvents( props, "pageChange" );
|
|
121
|
-
|
|
122
|
-
this._items = props.items?.map( itm => {
|
|
123
|
-
return { ...itm, page: null as any};
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
if( props.default ) {
|
|
127
|
-
this.select( props.default );
|
|
128
|
-
}
|
|
129
|
-
else if( this._items.length ) {
|
|
130
|
-
this.select( this._items[0].name );
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Adds a new item to the stack.
|
|
136
|
-
* @param item - The item to add.
|
|
137
|
-
*/
|
|
138
|
-
|
|
139
|
-
addItem( item: StackItem ) {
|
|
140
|
-
this._items.push( {
|
|
141
|
-
name: item.name,
|
|
142
|
-
content: item.content,
|
|
143
|
-
page: null
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Removes an item from the stack by its name.
|
|
149
|
-
* @param name - The name of the item to remove.
|
|
150
|
-
*/
|
|
151
|
-
|
|
152
|
-
removeItem( name: string ) {
|
|
153
|
-
const index = this._items.findIndex( x => x.name==name );
|
|
154
|
-
if( index>=0 ) {
|
|
155
|
-
const pg = this._items[index];
|
|
156
|
-
if( pg?.page ) {
|
|
157
|
-
this.removeChild( pg.page );
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
this._items.splice( index, 1 );
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Selects a page by its name.
|
|
166
|
-
* @param name - The name of the page to select.
|
|
167
|
-
* @returns The selected page component, if any.
|
|
168
|
-
*/
|
|
169
|
-
|
|
170
|
-
select( name: string ) {
|
|
171
|
-
let sel = this.query( `:scope > .selected` );
|
|
172
|
-
if( sel ) {
|
|
173
|
-
sel.setClass( "selected", false );
|
|
174
|
-
(sel as any).deactivate?.( );
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
this._cur = this._items.findIndex( x => x.name==name );
|
|
178
|
-
const pg = this._items[this._cur];
|
|
179
|
-
|
|
180
|
-
if( pg ) {
|
|
181
|
-
if( !pg.page ) {
|
|
182
|
-
pg.page = this._createPage( pg );
|
|
183
|
-
this.appendContent( pg.page );
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
sel = pg.page;
|
|
187
|
-
if( sel ) {
|
|
188
|
-
(sel as any).activate?.( );
|
|
189
|
-
sel.setClass( "selected", true );
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
asap( ( ) => this.fire( "pageChange", { selection: [pg.name], empty: !sel } ) );
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
return pg?.page;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
*
|
|
200
|
-
*/
|
|
201
|
-
|
|
202
|
-
private _createPage( page: StackItemEx ) {
|
|
203
|
-
|
|
204
|
-
let content: Component;
|
|
205
|
-
if( page.content instanceof Function ) {
|
|
206
|
-
content = page.content( );
|
|
207
|
-
page.content = content; // keep it
|
|
208
|
-
}
|
|
209
|
-
else {
|
|
210
|
-
content = page.content;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
content?.setData( "stackname", page.name );
|
|
214
|
-
return content;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Retrieves a page by its name.
|
|
219
|
-
* @param name - The name of the page to retrieve.
|
|
220
|
-
* @returns The page content, if found.
|
|
221
|
-
*/
|
|
222
|
-
|
|
223
|
-
getPage( name: string ) {
|
|
224
|
-
const pg = this._items.find( x => x.name==name );
|
|
225
|
-
return pg ? pg.content : null;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Gets the total number of pages in the stack.
|
|
230
|
-
* @returns The number of pages.
|
|
231
|
-
*/
|
|
232
|
-
|
|
233
|
-
getPageCount( ) {
|
|
234
|
-
return this._items.length;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Enumerates the names of all pages in the stack.
|
|
239
|
-
* @returns An array of page names.
|
|
240
|
-
*/
|
|
241
|
-
|
|
242
|
-
enumPageNames( ) {
|
|
243
|
-
return this._items.map( x => x.name );
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Retrieves a stack item by its name.
|
|
248
|
-
* @param name - The name of the item to retrieve.
|
|
249
|
-
* @returns The stack item, if found.
|
|
250
|
-
*/
|
|
251
|
-
|
|
252
|
-
getItem( name: string ) {
|
|
253
|
-
const pg = this._items.find( x => x.name==name );
|
|
254
|
-
return pg;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Gets the name of the currently selected page.
|
|
259
|
-
* @returns The name of the current page, if any.
|
|
260
|
-
*/
|
|
261
|
-
|
|
262
|
-
getCurPage( ) {
|
|
263
|
-
const c = this._items[this._cur];
|
|
264
|
-
return c?.name;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// :: ASSIST BOX ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* A specialized stack box for assisted navigation, such as wizards or carousels.
|
|
273
|
-
* The CSS class for this component is automatically generated as `x4assistbox`.
|
|
274
|
-
*/
|
|
275
|
-
|
|
276
|
-
@class_ns( "x4" )
|
|
277
|
-
export class AssistBox extends StackBox {
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Selects the next or previous page in the stack.
|
|
281
|
-
* @param nxt - If `true`, selects the next page; otherwise, selects the previous page.
|
|
282
|
-
*/
|
|
283
|
-
|
|
284
|
-
selectNextPage( nxt = true ) {
|
|
285
|
-
let p;
|
|
286
|
-
if( nxt && this._cur<this._items.length-1 ) {
|
|
287
|
-
p = this._items[this._cur+1];
|
|
288
|
-
}
|
|
289
|
-
else if( !nxt && this._cur>0 ) {
|
|
290
|
-
p = this._items[this._cur-1];
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
if( p ) {
|
|
294
|
-
this.select( p.name );
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* Checks if the current page is the first page.
|
|
300
|
-
* @returns `true` if the current page is the first page.
|
|
301
|
-
*/
|
|
302
|
-
|
|
303
|
-
isFirstPage( ) {
|
|
304
|
-
return this._cur==0;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
/**
|
|
308
|
-
* Checks if the current page is the last page.
|
|
309
|
-
* @returns `true` if the current page is the last page.
|
|
310
|
-
*/
|
|
311
|
-
|
|
312
|
-
isLastPage( ) {
|
|
313
|
-
return this._cur==this._items.length-1;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
// :: GRIDBOX ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
320
|
-
|
|
321
|
-
interface GridBoxItem {
|
|
322
|
-
row: number; // starts at 0
|
|
323
|
-
col: number; // starts at 0
|
|
324
|
-
item: Component;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
export interface GridBoxProps extends Omit<BoxProps,"content"> {
|
|
328
|
-
rows?: number | string | string[];
|
|
329
|
-
columns?: number | string | string[];
|
|
330
|
-
items?: GridBoxItem[];
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* Grid-based layout container.
|
|
335
|
-
* Auto-generates CSS class: `x4gridbox`.
|
|
336
|
-
*/
|
|
337
|
-
|
|
338
|
-
@class_ns("x4")
|
|
339
|
-
export class GridBox<P extends GridBoxProps=GridBoxProps,E extends ComponentEvents=ComponentEvents> extends Box<P,E> {
|
|
340
|
-
|
|
341
|
-
constructor( props: P ) {
|
|
342
|
-
super( props );
|
|
343
|
-
|
|
344
|
-
if( props.rows!==undefined ) {
|
|
345
|
-
this.setRows( props.rows );
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
if( props.columns!==undefined ) {
|
|
349
|
-
this.setCols( props.columns );
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
if( props.items ) {
|
|
353
|
-
this.setItems( props.items );
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
/**
|
|
358
|
-
* Sets grid rows (e.g., `2`, `"1fr 2fr"`, `["1fr", "2fr"]`).
|
|
359
|
-
* @param r - Rows definition.
|
|
360
|
-
*/
|
|
361
|
-
|
|
362
|
-
setRows( r: number | string | string[] ) {
|
|
363
|
-
if( isArray(r) ) {
|
|
364
|
-
r = r.join( " " );
|
|
365
|
-
}
|
|
366
|
-
else if( isNumber(r) ) {
|
|
367
|
-
r = `repeat( ${r}, 1fr )`;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
this.setStyleValue( "gridTemplateRows", r );
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
/**
|
|
374
|
-
* Sets grid columns (e.g., `3`, `"1fr 1fr"`, `["auto", "1fr"]`).
|
|
375
|
-
* @param r - Columns definition.
|
|
376
|
-
*/
|
|
377
|
-
|
|
378
|
-
setCols( r: number | string | string[] ) {
|
|
379
|
-
if( isArray(r) ) {
|
|
380
|
-
r = r.join( " " );
|
|
381
|
-
}
|
|
382
|
-
else if( isNumber(r) ) {
|
|
383
|
-
r = `repeat( ${r}, 1fr )`;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
this.setStyleValue( "gridTemplateColumns", r );
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
/**
|
|
390
|
-
* Sets the number of rows.
|
|
391
|
-
* @param n - Row count.
|
|
392
|
-
*/
|
|
393
|
-
|
|
394
|
-
setRowCount( n: number ) {
|
|
395
|
-
this.setStyleValue( "gridTemplateRows", `repeat(${n})` );
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
/**
|
|
399
|
-
* Sets the number of columns.
|
|
400
|
-
* @param n - Column count.
|
|
401
|
-
*/
|
|
402
|
-
|
|
403
|
-
setColCount( n: number ) {
|
|
404
|
-
this.setStyleValue( "gridTemplateColumns", `repeat(${n})` );
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
/**
|
|
408
|
-
* Sets grid template areas (e.g., `["a a", "b c"]`).
|
|
409
|
-
* @param t - Template strings.
|
|
410
|
-
*/
|
|
411
|
-
|
|
412
|
-
setTemplate( t: string[] ) {
|
|
413
|
-
this.setAttribute( "grid-template-area", t.map( x => '"' + x + '"' ).join(" ") );
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
/**
|
|
417
|
-
* Places items at specific grid positions.
|
|
418
|
-
* @param items - Array of `{row, col, item}`.
|
|
419
|
-
*/
|
|
420
|
-
|
|
421
|
-
setItems( items: GridBoxItem[] ) {
|
|
422
|
-
items.forEach( x => {
|
|
423
|
-
x.item.setStyle( {
|
|
424
|
-
gridColumn: (x.col+1)+"",
|
|
425
|
-
gridRow: (x.row+1)+"",
|
|
426
|
-
} );
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
this.setContent( items.map( x => x.item ) );
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
// :: MASONRY ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
435
|
-
|
|
436
|
-
// from a nice article of Andy Barefoot
|
|
437
|
-
// https://medium.com/@andybarefoot/a-masonry-style-layout-using-css-grid-8c663d355ebb
|
|
438
|
-
|
|
439
|
-
interface MasonryProps extends Omit<BoxProps,"content"> {
|
|
440
|
-
items: Component[];
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
/**
|
|
444
|
-
* Masonry-style layout (Pinterest-like).
|
|
445
|
-
* Auto-generates CSS class: `x4masonrybox`.
|
|
446
|
-
*/
|
|
447
|
-
|
|
448
|
-
@class_ns("x4")
|
|
449
|
-
export class MasonryBox extends Box<MasonryProps> {
|
|
450
|
-
|
|
451
|
-
constructor(props: MasonryProps ) {
|
|
452
|
-
super(props);
|
|
453
|
-
|
|
454
|
-
this.addDOMEvent( 'resized', () => {
|
|
455
|
-
this.resizeAllItems( );
|
|
456
|
-
});
|
|
457
|
-
|
|
458
|
-
if( props.items ) {
|
|
459
|
-
this.setItems( props.items );
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
/**
|
|
464
|
-
* Resizes a single masonry item.
|
|
465
|
-
* @param item - Item to resize.
|
|
466
|
-
*/
|
|
467
|
-
|
|
468
|
-
resizeItem(item: Component) {
|
|
469
|
-
const style = this.getComputedStyle();
|
|
470
|
-
|
|
471
|
-
const rowHeight = parseInt(style['gridAutoRows']);
|
|
472
|
-
const rowGap = parseInt(style['rowGap']);
|
|
473
|
-
|
|
474
|
-
let content = item.query('.content');
|
|
475
|
-
if( !content ) {
|
|
476
|
-
content = item;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
if (content && (rowHeight + rowGap)) {
|
|
480
|
-
const rc = content.getBoundingRect();
|
|
481
|
-
const rowSpan = Math.ceil( (rc.height + rowGap) / (rowHeight + rowGap) );
|
|
482
|
-
item.setStyleValue('gridRowEnd', "span " + rowSpan);
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
/**
|
|
487
|
-
* Resizes all items to fit the grid.
|
|
488
|
-
*/
|
|
489
|
-
|
|
490
|
-
resizeAllItems( ) {
|
|
491
|
-
const els = this.queryAll( ".item" );
|
|
492
|
-
els.forEach( itm => {
|
|
493
|
-
this.resizeItem( itm );
|
|
494
|
-
} );
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
/**
|
|
498
|
-
* Sets masonry items.
|
|
499
|
-
* @param items - Array of components.
|
|
500
|
-
*/
|
|
501
|
-
|
|
502
|
-
setItems( items: Component[] ) {
|
|
503
|
-
const els = items.map( x => {
|
|
504
|
-
return new Box( {
|
|
505
|
-
cls: 'item',
|
|
506
|
-
content: x
|
|
507
|
-
} );
|
|
508
|
-
});
|
|
509
|
-
|
|
510
|
-
this.setContent( els );
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
|
|
1
|
+
/**
|
|
2
|
+
* ___ ___ __
|
|
3
|
+
* \ \/ / / _
|
|
4
|
+
* \ / /_| |_
|
|
5
|
+
* / \____ _|
|
|
6
|
+
* /__/\__\ |_|
|
|
7
|
+
*
|
|
8
|
+
* @file boxes.ts
|
|
9
|
+
* @author Etienne Cochard
|
|
10
|
+
*
|
|
11
|
+
* @copyright (c) 2024 R-libre ingenierie
|
|
12
|
+
*
|
|
13
|
+
* Use of this source code is governed by an MIT-style license
|
|
14
|
+
* that can be found in the LICENSE file or at https://opensource.org/licenses/MIT.
|
|
15
|
+
**/
|
|
16
|
+
|
|
17
|
+
import { asap, class_ns, isArray, isNumber } from '../../core/core_tools';
|
|
18
|
+
import { Component, ComponentEvents, ComponentProps, EvSelectionChange } from "../../core/component"
|
|
19
|
+
import { EventCallback } from '../../core/core_events';
|
|
20
|
+
|
|
21
|
+
import "./boxes.module.scss";
|
|
22
|
+
|
|
23
|
+
export interface BoxProps extends ComponentProps {
|
|
24
|
+
/** Optional HTML tag to use for the box. */
|
|
25
|
+
tag?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A generic container component for grouping and laying out child components.
|
|
30
|
+
* The CSS class for this component is automatically generated as `x4box`.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
@class_ns( "x4" )
|
|
34
|
+
export class Box<P extends BoxProps=BoxProps,E extends ComponentEvents=ComponentEvents> extends Component<P,E> {
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* A horizontal box layout component.
|
|
40
|
+
* Arranges child components in a horizontal line.
|
|
41
|
+
* The CSS class for this component is automatically generated as `x4hbox`.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
@class_ns( "x4" )
|
|
45
|
+
export class HBox<P extends BoxProps=BoxProps,E extends ComponentEvents=ComponentEvents> extends Box<P,E> {
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* A vertical box layout component.
|
|
50
|
+
* Arranges child components in a vertical stack.
|
|
51
|
+
* The CSS class for this component is automatically generated as `x4vbox`.
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
@class_ns( "x4" )
|
|
55
|
+
export class VBox<P extends BoxProps=BoxProps,E extends ComponentEvents=ComponentEvents> extends Box<P,E> {
|
|
56
|
+
constructor( p: P ) {
|
|
57
|
+
super( p );
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
type ContentBuilder = ( ) => Component;
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Represents an item in a {@link StackBox}.
|
|
67
|
+
*/
|
|
68
|
+
|
|
69
|
+
interface StackItem {
|
|
70
|
+
/** Unique name for the stack item. */
|
|
71
|
+
name: string;
|
|
72
|
+
/** Content of the stack item, either a component or a builder function. */
|
|
73
|
+
content: Component | ContentBuilder;
|
|
74
|
+
title?: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Events specific to the {@link StackBox} component.
|
|
79
|
+
*/
|
|
80
|
+
|
|
81
|
+
interface StackeBoxEvents extends ComponentEvents {
|
|
82
|
+
/** Fired when the current page changes. */
|
|
83
|
+
pageChange?: EvSelectionChange;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Properties for the {@link StackBox} component.
|
|
88
|
+
*/
|
|
89
|
+
|
|
90
|
+
export interface StackBoxProps extends Omit<ComponentProps,"content"> {
|
|
91
|
+
/** Name of the default page to display. */
|
|
92
|
+
default: string;
|
|
93
|
+
|
|
94
|
+
/** List of stack items. */
|
|
95
|
+
items: StackItem[];
|
|
96
|
+
|
|
97
|
+
/** Callback for page change events. */
|
|
98
|
+
pageChange?: EventCallback<EvSelectionChange>;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
interface StackItemEx extends StackItem {
|
|
103
|
+
page: Component;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* A stack of widgets where only one widget is visible at a time.
|
|
108
|
+
* The CSS class for this component is automatically generated as `x4stackbox`.
|
|
109
|
+
*/
|
|
110
|
+
|
|
111
|
+
@class_ns( "x4" )
|
|
112
|
+
export class StackBox<P extends StackBoxProps = StackBoxProps, E extends StackeBoxEvents = StackeBoxEvents> extends Box<StackBoxProps,StackeBoxEvents> {
|
|
113
|
+
|
|
114
|
+
protected _items: StackItemEx[];
|
|
115
|
+
protected _cur: number;
|
|
116
|
+
|
|
117
|
+
constructor( props: StackBoxProps ) {
|
|
118
|
+
super( props );
|
|
119
|
+
|
|
120
|
+
this.mapPropEvents( props, "pageChange" );
|
|
121
|
+
|
|
122
|
+
this._items = props.items?.map( itm => {
|
|
123
|
+
return { ...itm, page: null as any};
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
if( props.default ) {
|
|
127
|
+
this.select( props.default );
|
|
128
|
+
}
|
|
129
|
+
else if( this._items.length ) {
|
|
130
|
+
this.select( this._items[0].name );
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Adds a new item to the stack.
|
|
136
|
+
* @param item - The item to add.
|
|
137
|
+
*/
|
|
138
|
+
|
|
139
|
+
addItem( item: StackItem ) {
|
|
140
|
+
this._items.push( {
|
|
141
|
+
name: item.name,
|
|
142
|
+
content: item.content,
|
|
143
|
+
page: null
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Removes an item from the stack by its name.
|
|
149
|
+
* @param name - The name of the item to remove.
|
|
150
|
+
*/
|
|
151
|
+
|
|
152
|
+
removeItem( name: string ) {
|
|
153
|
+
const index = this._items.findIndex( x => x.name==name );
|
|
154
|
+
if( index>=0 ) {
|
|
155
|
+
const pg = this._items[index];
|
|
156
|
+
if( pg?.page ) {
|
|
157
|
+
this.removeChild( pg.page );
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
this._items.splice( index, 1 );
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Selects a page by its name.
|
|
166
|
+
* @param name - The name of the page to select.
|
|
167
|
+
* @returns The selected page component, if any.
|
|
168
|
+
*/
|
|
169
|
+
|
|
170
|
+
select( name: string ) {
|
|
171
|
+
let sel = this.query( `:scope > .selected` );
|
|
172
|
+
if( sel ) {
|
|
173
|
+
sel.setClass( "selected", false );
|
|
174
|
+
(sel as any).deactivate?.( );
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
this._cur = this._items.findIndex( x => x.name==name );
|
|
178
|
+
const pg = this._items[this._cur];
|
|
179
|
+
|
|
180
|
+
if( pg ) {
|
|
181
|
+
if( !pg.page ) {
|
|
182
|
+
pg.page = this._createPage( pg );
|
|
183
|
+
this.appendContent( pg.page );
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
sel = pg.page;
|
|
187
|
+
if( sel ) {
|
|
188
|
+
(sel as any).activate?.( );
|
|
189
|
+
sel.setClass( "selected", true );
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
asap( ( ) => this.fire( "pageChange", { selection: [pg.name], empty: !sel } ) );
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return pg?.page;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
*
|
|
200
|
+
*/
|
|
201
|
+
|
|
202
|
+
private _createPage( page: StackItemEx ) {
|
|
203
|
+
|
|
204
|
+
let content: Component;
|
|
205
|
+
if( page.content instanceof Function ) {
|
|
206
|
+
content = page.content( );
|
|
207
|
+
page.content = content; // keep it
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
content = page.content;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
content?.setData( "stackname", page.name );
|
|
214
|
+
return content;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Retrieves a page by its name.
|
|
219
|
+
* @param name - The name of the page to retrieve.
|
|
220
|
+
* @returns The page content, if found.
|
|
221
|
+
*/
|
|
222
|
+
|
|
223
|
+
getPage( name: string ) {
|
|
224
|
+
const pg = this._items.find( x => x.name==name );
|
|
225
|
+
return pg ? pg.content : null;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Gets the total number of pages in the stack.
|
|
230
|
+
* @returns The number of pages.
|
|
231
|
+
*/
|
|
232
|
+
|
|
233
|
+
getPageCount( ) {
|
|
234
|
+
return this._items.length;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Enumerates the names of all pages in the stack.
|
|
239
|
+
* @returns An array of page names.
|
|
240
|
+
*/
|
|
241
|
+
|
|
242
|
+
enumPageNames( ) {
|
|
243
|
+
return this._items.map( x => x.name );
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Retrieves a stack item by its name.
|
|
248
|
+
* @param name - The name of the item to retrieve.
|
|
249
|
+
* @returns The stack item, if found.
|
|
250
|
+
*/
|
|
251
|
+
|
|
252
|
+
getItem( name: string ) {
|
|
253
|
+
const pg = this._items.find( x => x.name==name );
|
|
254
|
+
return pg;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Gets the name of the currently selected page.
|
|
259
|
+
* @returns The name of the current page, if any.
|
|
260
|
+
*/
|
|
261
|
+
|
|
262
|
+
getCurPage( ) {
|
|
263
|
+
const c = this._items[this._cur];
|
|
264
|
+
return c?.name;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// :: ASSIST BOX ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* A specialized stack box for assisted navigation, such as wizards or carousels.
|
|
273
|
+
* The CSS class for this component is automatically generated as `x4assistbox`.
|
|
274
|
+
*/
|
|
275
|
+
|
|
276
|
+
@class_ns( "x4" )
|
|
277
|
+
export class AssistBox extends StackBox {
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Selects the next or previous page in the stack.
|
|
281
|
+
* @param nxt - If `true`, selects the next page; otherwise, selects the previous page.
|
|
282
|
+
*/
|
|
283
|
+
|
|
284
|
+
selectNextPage( nxt = true ) {
|
|
285
|
+
let p;
|
|
286
|
+
if( nxt && this._cur<this._items.length-1 ) {
|
|
287
|
+
p = this._items[this._cur+1];
|
|
288
|
+
}
|
|
289
|
+
else if( !nxt && this._cur>0 ) {
|
|
290
|
+
p = this._items[this._cur-1];
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if( p ) {
|
|
294
|
+
this.select( p.name );
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Checks if the current page is the first page.
|
|
300
|
+
* @returns `true` if the current page is the first page.
|
|
301
|
+
*/
|
|
302
|
+
|
|
303
|
+
isFirstPage( ) {
|
|
304
|
+
return this._cur==0;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Checks if the current page is the last page.
|
|
309
|
+
* @returns `true` if the current page is the last page.
|
|
310
|
+
*/
|
|
311
|
+
|
|
312
|
+
isLastPage( ) {
|
|
313
|
+
return this._cur==this._items.length-1;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
// :: GRIDBOX ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
320
|
+
|
|
321
|
+
interface GridBoxItem {
|
|
322
|
+
row: number; // starts at 0
|
|
323
|
+
col: number; // starts at 0
|
|
324
|
+
item: Component;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export interface GridBoxProps extends Omit<BoxProps,"content"> {
|
|
328
|
+
rows?: number | string | string[];
|
|
329
|
+
columns?: number | string | string[];
|
|
330
|
+
items?: GridBoxItem[];
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Grid-based layout container.
|
|
335
|
+
* Auto-generates CSS class: `x4gridbox`.
|
|
336
|
+
*/
|
|
337
|
+
|
|
338
|
+
@class_ns("x4")
|
|
339
|
+
export class GridBox<P extends GridBoxProps=GridBoxProps,E extends ComponentEvents=ComponentEvents> extends Box<P,E> {
|
|
340
|
+
|
|
341
|
+
constructor( props: P ) {
|
|
342
|
+
super( props );
|
|
343
|
+
|
|
344
|
+
if( props.rows!==undefined ) {
|
|
345
|
+
this.setRows( props.rows );
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if( props.columns!==undefined ) {
|
|
349
|
+
this.setCols( props.columns );
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if( props.items ) {
|
|
353
|
+
this.setItems( props.items );
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Sets grid rows (e.g., `2`, `"1fr 2fr"`, `["1fr", "2fr"]`).
|
|
359
|
+
* @param r - Rows definition.
|
|
360
|
+
*/
|
|
361
|
+
|
|
362
|
+
setRows( r: number | string | string[] ) {
|
|
363
|
+
if( isArray(r) ) {
|
|
364
|
+
r = r.join( " " );
|
|
365
|
+
}
|
|
366
|
+
else if( isNumber(r) ) {
|
|
367
|
+
r = `repeat( ${r}, 1fr )`;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
this.setStyleValue( "gridTemplateRows", r );
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Sets grid columns (e.g., `3`, `"1fr 1fr"`, `["auto", "1fr"]`).
|
|
375
|
+
* @param r - Columns definition.
|
|
376
|
+
*/
|
|
377
|
+
|
|
378
|
+
setCols( r: number | string | string[] ) {
|
|
379
|
+
if( isArray(r) ) {
|
|
380
|
+
r = r.join( " " );
|
|
381
|
+
}
|
|
382
|
+
else if( isNumber(r) ) {
|
|
383
|
+
r = `repeat( ${r}, 1fr )`;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
this.setStyleValue( "gridTemplateColumns", r );
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Sets the number of rows.
|
|
391
|
+
* @param n - Row count.
|
|
392
|
+
*/
|
|
393
|
+
|
|
394
|
+
setRowCount( n: number ) {
|
|
395
|
+
this.setStyleValue( "gridTemplateRows", `repeat(${n})` );
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Sets the number of columns.
|
|
400
|
+
* @param n - Column count.
|
|
401
|
+
*/
|
|
402
|
+
|
|
403
|
+
setColCount( n: number ) {
|
|
404
|
+
this.setStyleValue( "gridTemplateColumns", `repeat(${n})` );
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Sets grid template areas (e.g., `["a a", "b c"]`).
|
|
409
|
+
* @param t - Template strings.
|
|
410
|
+
*/
|
|
411
|
+
|
|
412
|
+
setTemplate( t: string[] ) {
|
|
413
|
+
this.setAttribute( "grid-template-area", t.map( x => '"' + x + '"' ).join(" ") );
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Places items at specific grid positions.
|
|
418
|
+
* @param items - Array of `{row, col, item}`.
|
|
419
|
+
*/
|
|
420
|
+
|
|
421
|
+
setItems( items: GridBoxItem[] ) {
|
|
422
|
+
items.forEach( x => {
|
|
423
|
+
x.item.setStyle( {
|
|
424
|
+
gridColumn: (x.col+1)+"",
|
|
425
|
+
gridRow: (x.row+1)+"",
|
|
426
|
+
} );
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
this.setContent( items.map( x => x.item ) );
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
// :: MASONRY ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
435
|
+
|
|
436
|
+
// from a nice article of Andy Barefoot
|
|
437
|
+
// https://medium.com/@andybarefoot/a-masonry-style-layout-using-css-grid-8c663d355ebb
|
|
438
|
+
|
|
439
|
+
interface MasonryProps extends Omit<BoxProps,"content"> {
|
|
440
|
+
items: Component[];
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Masonry-style layout (Pinterest-like).
|
|
445
|
+
* Auto-generates CSS class: `x4masonrybox`.
|
|
446
|
+
*/
|
|
447
|
+
|
|
448
|
+
@class_ns("x4")
|
|
449
|
+
export class MasonryBox extends Box<MasonryProps> {
|
|
450
|
+
|
|
451
|
+
constructor(props: MasonryProps ) {
|
|
452
|
+
super(props);
|
|
453
|
+
|
|
454
|
+
this.addDOMEvent( 'resized', () => {
|
|
455
|
+
this.resizeAllItems( );
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
if( props.items ) {
|
|
459
|
+
this.setItems( props.items );
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Resizes a single masonry item.
|
|
465
|
+
* @param item - Item to resize.
|
|
466
|
+
*/
|
|
467
|
+
|
|
468
|
+
resizeItem(item: Component) {
|
|
469
|
+
const style = this.getComputedStyle();
|
|
470
|
+
|
|
471
|
+
const rowHeight = parseInt(style['gridAutoRows']);
|
|
472
|
+
const rowGap = parseInt(style['rowGap']);
|
|
473
|
+
|
|
474
|
+
let content = item.query('.content');
|
|
475
|
+
if( !content ) {
|
|
476
|
+
content = item;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (content && (rowHeight + rowGap)) {
|
|
480
|
+
const rc = content.getBoundingRect();
|
|
481
|
+
const rowSpan = Math.ceil( (rc.height + rowGap) / (rowHeight + rowGap) );
|
|
482
|
+
item.setStyleValue('gridRowEnd', "span " + rowSpan);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Resizes all items to fit the grid.
|
|
488
|
+
*/
|
|
489
|
+
|
|
490
|
+
resizeAllItems( ) {
|
|
491
|
+
const els = this.queryAll( ".item" );
|
|
492
|
+
els.forEach( itm => {
|
|
493
|
+
this.resizeItem( itm );
|
|
494
|
+
} );
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Sets masonry items.
|
|
499
|
+
* @param items - Array of components.
|
|
500
|
+
*/
|
|
501
|
+
|
|
502
|
+
setItems( items: Component[] ) {
|
|
503
|
+
const els = items.map( x => {
|
|
504
|
+
return new Box( {
|
|
505
|
+
cls: 'item',
|
|
506
|
+
content: x
|
|
507
|
+
} );
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
this.setContent( els );
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|