x4js 2.3.15 → 2.3.16
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/aicontext.md +117 -2
- package/package.json +1 -1
- package/src/components/components.ts +1 -0
- package/src/components/dockingview/dockingview.module.scss +279 -0
- package/src/components/dockingview/dockingview.ts +899 -0
- package/src/components/dockingview/docklayout.ts +659 -0
- package/src/core/component.ts +1 -1
package/aicontext.md
CHANGED
|
@@ -39,7 +39,7 @@ CoreElement<E> named timers + event surface (no DOM)
|
|
|
39
39
|
│ ├── Progress, TickLine, Canvas, Image, Spreadsheet, MonacoEditor
|
|
40
40
|
│ ├── SvgComponent → Chart
|
|
41
41
|
│ ├── CSizer → HSizer / VSizer
|
|
42
|
-
│ ├── FileDialog, ScrollView, Viewport, Flex, Space
|
|
42
|
+
│ ├── FileDialog, ScrollView, Viewport, DockingView, Flex, Space
|
|
43
43
|
│ ├── HBox-based: ColorInput, Rating, Switch, Keyboard, TextEdit → AutoComplete, Breadcrumbs
|
|
44
44
|
│ └── VBox-based: Calendar, ColorPicker, FileDrop, Panel, PropertyGrid, TextArea
|
|
45
45
|
└── Application<E> singleton, no DOM of its own
|
|
@@ -50,7 +50,7 @@ EventSource<E> standalone event registry
|
|
|
50
50
|
└── StateManager<T>
|
|
51
51
|
|
|
52
52
|
CoreElement-based (non-DOM): DataProxy, DataView, Store
|
|
53
|
-
Standalone: Rect, Timer, Color, DataModel, Stylesheet, ComputedStyle,
|
|
53
|
+
Standalone: Rect, Timer, Color, DataModel, Shortcuts, Stylesheet, ComputedStyle,
|
|
54
54
|
SvgBuilder, X4PDFBuilder, x4_react, CMover, UnsafeHtml (extends String)
|
|
55
55
|
```
|
|
56
56
|
|
|
@@ -240,6 +240,7 @@ setCapture(pointerId: number): void; releaseCapture(pointerId: number): void
|
|
|
240
240
|
animate(keyframes: Keyframe[], duration: number): void
|
|
241
241
|
onGlobalEvent(cb: (ev: EvMessage) => void): void // auto-removed on DOM removal
|
|
242
242
|
queryInterface<T>(name: string): T // "form-element" → IFormElement; "tab-handler" → ITabHandler
|
|
243
|
+
addShortcut(keys: string | string[], callback: (ev: KeyboardEvent) => void, options?: ShortcutOptions): this // see Shortcuts
|
|
243
244
|
```
|
|
244
245
|
|
|
245
246
|
### Exported helpers
|
|
@@ -285,6 +286,7 @@ setEnv(name: string, value: any): void
|
|
|
285
286
|
getEnv(name: string, def_value?: any): any
|
|
286
287
|
static fireGlobal(msg: string, params?: any): void
|
|
287
288
|
focusNext(next: boolean): boolean
|
|
289
|
+
addShortcut(keys: string | string[], callback: (ev: KeyboardEvent) => void, options?: ShortcutOptions): void // see Shortcuts
|
|
288
290
|
setupSocketMessaging(path?: string, looseCallback?: () => void): void
|
|
289
291
|
getStorage(name: string): string
|
|
290
292
|
setStorage(name: string, value: string|number): void
|
|
@@ -387,6 +389,79 @@ class MasonryBox extends Box {
|
|
|
387
389
|
|
|
388
390
|
---
|
|
389
391
|
|
|
392
|
+
## `DockingView` — panels the user arranges by dragging their title
|
|
393
|
+
|
|
394
|
+
Not a `Box`. Two kinds of panels, which never mix: **tools**, docked in a band on the left or on the right
|
|
395
|
+
(one above the other, side by side, or as tabs) and which can float; **contents**, what is worked on
|
|
396
|
+
(as the editors of an IDE), which share the room between the bands, split or as tabs.
|
|
397
|
+
|
|
398
|
+
```ts
|
|
399
|
+
interface DockPanel {
|
|
400
|
+
id: string
|
|
401
|
+
title?: string // shown in its tab; the id when missing
|
|
402
|
+
content: Component
|
|
403
|
+
kind?: 'tool' | 'content' // default 'tool'
|
|
404
|
+
closable?: boolean // for this panel, instead of the view's
|
|
405
|
+
floating?: boolean // idem; a content never floats
|
|
406
|
+
}
|
|
407
|
+
interface DockingViewProps extends ComponentProps {
|
|
408
|
+
panels: DockPanel[]
|
|
409
|
+
layout: DockLayout // where the panels are at first
|
|
410
|
+
floating?: boolean // tools can float above the rest
|
|
411
|
+
closable?: boolean // tools have a button that hides them
|
|
412
|
+
persist?: string // a name: the layout is remembered under it (application storage)
|
|
413
|
+
layoutChange?: EventCallback<ComponentEvent>
|
|
414
|
+
}
|
|
415
|
+
class DockingView extends Component {
|
|
416
|
+
getState(): DockLayout // plain data, to save
|
|
417
|
+
setState(layout: unknown): void // a saved layout; what it lacks comes from props.layout
|
|
418
|
+
resetLayout(): void // back to props.layout
|
|
419
|
+
showPanel(id: string, show?: boolean): void
|
|
420
|
+
isPanelVisible(id: string): boolean
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
interface DockLayout {
|
|
424
|
+
left: DockBand; right: DockBand // the tools
|
|
425
|
+
content: DockNode // the contents; null when there is none
|
|
426
|
+
floats: DockFloat[] // floating tools: { panel, x, y, w, h }
|
|
427
|
+
hidden: string[] // ids of the closed panels
|
|
428
|
+
}
|
|
429
|
+
interface DockBand { size: number; root: DockNode } // size: width in px; root: null when empty
|
|
430
|
+
type DockNode = DockStack | DockSplit
|
|
431
|
+
interface DockStack { type: 'stack'; size: number; panels: string[]; active: string } // one tab per panel
|
|
432
|
+
interface DockSplit { type: 'split'; dir: 'row' | 'column'; size: number; children: DockNode[] }
|
|
433
|
+
// size of a node: its share of its split (0 = an equal share)
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
```ts
|
|
437
|
+
const view = new DockingView({
|
|
438
|
+
flex: true,
|
|
439
|
+
floating: true,
|
|
440
|
+
closable: true,
|
|
441
|
+
persist: 'myapp.layout',
|
|
442
|
+
panels: [
|
|
443
|
+
{ id: 'tools', title: 'Tools', content: tools },
|
|
444
|
+
{ id: 'props', title: 'Properties', content: props },
|
|
445
|
+
{ id: 'page', kind: 'content', content: editor },
|
|
446
|
+
],
|
|
447
|
+
layout: {
|
|
448
|
+
left: { size: 180, root: { type: 'stack', size: 0, panels: ['tools'], active: 'tools' } },
|
|
449
|
+
right: { size: 260, root: { type: 'stack', size: 0, panels: ['props'], active: 'props' } },
|
|
450
|
+
content: { type: 'stack', size: 0, panels: ['page'], active: 'page' },
|
|
451
|
+
floats: [],
|
|
452
|
+
hidden: [],
|
|
453
|
+
},
|
|
454
|
+
})
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
- A closed panel is hidden, not removed: `showPanel(id)` brings it back where it was. Offer it in a menu.
|
|
458
|
+
- Tools only go in `left`, `right` or `floats`; contents only in `content`. A panel missing from the layout is added where its kind goes.
|
|
459
|
+
- A content alone has no title bar; with several contents, each stack has tabs.
|
|
460
|
+
- What a panel shows is built once (`content`) and moved as the layout changes: never rebuild it.
|
|
461
|
+
- CSS variables: `--dock-line`, `--dock-accent`, `--dock-title-background`, `--dock-title-background-hover`, `--dock-title-color`, `--dock-title-color-active`, `--dock-float-background`, `--dock-float-shadow`.
|
|
462
|
+
|
|
463
|
+
---
|
|
464
|
+
|
|
390
465
|
## Display components
|
|
391
466
|
|
|
392
467
|
### `Label`
|
|
@@ -1100,6 +1175,46 @@ parseRoute(str: string|RegExp, loose?: boolean): Segment
|
|
|
1100
1175
|
|
|
1101
1176
|
---
|
|
1102
1177
|
|
|
1178
|
+
## Shortcuts (`core_shortcuts`)
|
|
1179
|
+
|
|
1180
|
+
Keyboard shortcuts. A sequence is written as it is read: `"Mod+C"`, `"Shift+Mod+Z"`, `"Delete"`, `"Alt+ArrowLeft"`.
|
|
1181
|
+
Modifiers: `Shift`, `Ctrl`, `Cmd`, `Alt`, and `Mod` = the key of the commands (Ctrl on Windows and Linux, Cmd on a Mac). Prefer `Mod`.
|
|
1182
|
+
|
|
1183
|
+
```ts
|
|
1184
|
+
interface ShortcutOptions { editable?: boolean } // true: also while the user types in a field
|
|
1185
|
+
|
|
1186
|
+
component.addShortcut(keys, callback, options?) // a view: while the focus is in the component
|
|
1187
|
+
Application.instance().addShortcut(keys, callback, options?) // the whole application, wherever the focus is
|
|
1188
|
+
|
|
1189
|
+
shortcutText(keys: string): string // for a tooltip or a menu: "Ctrl+Z", "⌘Z" on a Mac
|
|
1190
|
+
|
|
1191
|
+
class Shortcuts { // the list itself; rarely used directly
|
|
1192
|
+
add(keys: string | string[], callback: (ev: KeyboardEvent) => void, options?: ShortcutOptions): void
|
|
1193
|
+
clear(): void
|
|
1194
|
+
handle(ev: KeyboardEvent): boolean // true when the key was a shortcut
|
|
1195
|
+
}
|
|
1196
|
+
```
|
|
1197
|
+
|
|
1198
|
+
```ts
|
|
1199
|
+
class MyView extends VBox {
|
|
1200
|
+
constructor(props: BoxProps) {
|
|
1201
|
+
super(props)
|
|
1202
|
+
this.addShortcut('Mod+C', () => this.copy())
|
|
1203
|
+
this.addShortcut(['Mod+Y', 'Shift+Mod+Z'], () => this.redo())
|
|
1204
|
+
this.setContent(new Button({ label: 'Undo', tooltip: shortcutText('Mod+Z'), click: () => this.undo() }))
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
```
|
|
1208
|
+
|
|
1209
|
+
- A component shortcut also works while the focus is nowhere, if the component is the last one with shortcuts that had the focus: of two views shown together, the one the user works in answers.
|
|
1210
|
+
- A key typed in an `input`, a `textarea` or a `select` is not a shortcut, unless `editable` is set.
|
|
1211
|
+
- A key that what has the focus already used (it called `preventDefault` or `stopPropagation`) is not a shortcut.
|
|
1212
|
+
- A shortcut always takes its key (`preventDefault`). For a key that must sometimes be left alone, use a `keydown` handler instead.
|
|
1213
|
+
- An application shortcut also works while a dialog is open.
|
|
1214
|
+
- Do not write a `keydown` handler with a `switch` on `ev.ctrlKey` for commands: use `addShortcut`.
|
|
1215
|
+
|
|
1216
|
+
---
|
|
1217
|
+
|
|
1103
1218
|
## i18n (`core_i18n`)
|
|
1104
1219
|
|
|
1105
1220
|
Two built-in languages (`fr` default, and `en`).
|
package/package.json
CHANGED
|
@@ -12,6 +12,7 @@ export * from "./colorinput/colorinput"
|
|
|
12
12
|
export * from "./colorpicker/colorpicker"
|
|
13
13
|
export * from "./combobox/combobox"
|
|
14
14
|
export * from "./dialog/dialog"
|
|
15
|
+
export * from "./dockingview/dockingview"
|
|
15
16
|
export * from "./filedrop/filedrop"
|
|
16
17
|
export * from "./form/form"
|
|
17
18
|
export * from "./gauge/gauge"
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ___ ___ __
|
|
3
|
+
* \ \/ / / _
|
|
4
|
+
* \ / /_| |_
|
|
5
|
+
* / \____ _|
|
|
6
|
+
* /__/\__\ |_|
|
|
7
|
+
*
|
|
8
|
+
* @file dockingview.module.scss
|
|
9
|
+
* @author Etienne Cochard
|
|
10
|
+
*
|
|
11
|
+
* @copyright (c) 2026 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
|
+
.x4dockingview {
|
|
18
|
+
--dock-line: var( --border, #cfdae0 ); // between two panels, under a title
|
|
19
|
+
--dock-title-background: var( --background-secondary, #f3f7f9 );
|
|
20
|
+
--dock-title-background-hover: color-mix( in srgb, var( --dock-accent ) 25%, transparent );
|
|
21
|
+
--dock-title-color: var( --text-ternary, #607784 );
|
|
22
|
+
--dock-title-color-active: var( --text-primary, #002a40 );
|
|
23
|
+
--dock-accent: var( --accent-background, #126e9e ); // the tab in front, the sizer under the pointer, the preview
|
|
24
|
+
--dock-float-background: var( --background-primary, white );
|
|
25
|
+
--dock-float-shadow: 0 6px 24px rgba( 0, 0, 0, 0.35 );
|
|
26
|
+
|
|
27
|
+
position: relative;
|
|
28
|
+
display: flex;
|
|
29
|
+
min-width: 0;
|
|
30
|
+
min-height: 0;
|
|
31
|
+
overflow: hidden;
|
|
32
|
+
|
|
33
|
+
// it has no size of its own: it takes what its container gives it,
|
|
34
|
+
// even in an HBox, which centers its children
|
|
35
|
+
align-self: stretch;
|
|
36
|
+
|
|
37
|
+
// The boxes of the layout: the band of the left, the room of the
|
|
38
|
+
// contents, the band of the right. The floating tools are placed from
|
|
39
|
+
// its top-left corner, which is the one of the box.
|
|
40
|
+
> .dock-frame {
|
|
41
|
+
position: relative;
|
|
42
|
+
display: flex;
|
|
43
|
+
flex: 1 1 0;
|
|
44
|
+
min-width: 0;
|
|
45
|
+
min-height: 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// The tools of one side. Its width is given by the layout; it only
|
|
49
|
+
// gets narrower when the box has no room left.
|
|
50
|
+
.dock-band {
|
|
51
|
+
display: flex;
|
|
52
|
+
flex: 0 1 auto;
|
|
53
|
+
min-width: 48px;
|
|
54
|
+
min-height: 0;
|
|
55
|
+
overflow: hidden;
|
|
56
|
+
|
|
57
|
+
> .dock-split,
|
|
58
|
+
> .dock-stack {
|
|
59
|
+
flex: 1 1 0;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// the room of the contents: what the bands leave
|
|
64
|
+
.dock-area {
|
|
65
|
+
display: flex;
|
|
66
|
+
flex: 1 1 0;
|
|
67
|
+
min-width: 0;
|
|
68
|
+
min-height: 0;
|
|
69
|
+
overflow: hidden;
|
|
70
|
+
|
|
71
|
+
> .dock-split,
|
|
72
|
+
> .dock-stack {
|
|
73
|
+
flex: 1 1 0;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.dock-split {
|
|
78
|
+
display: flex;
|
|
79
|
+
min-width: 0;
|
|
80
|
+
min-height: 0;
|
|
81
|
+
overflow: hidden;
|
|
82
|
+
|
|
83
|
+
&.column {
|
|
84
|
+
flex-direction: column;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// the smallest size of a child (MIN_SIZE in dockingview.ts)
|
|
88
|
+
&.row > .dock-split,
|
|
89
|
+
&.row > .dock-stack {
|
|
90
|
+
min-width: 48px;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
&.column > .dock-split,
|
|
94
|
+
&.column > .dock-stack {
|
|
95
|
+
min-height: 48px;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// One line between two children. It is caught from a little further
|
|
100
|
+
// than what is drawn.
|
|
101
|
+
.dock-sizer {
|
|
102
|
+
position: relative;
|
|
103
|
+
z-index: 1;
|
|
104
|
+
flex: none;
|
|
105
|
+
align-self: stretch;
|
|
106
|
+
min-width: 1px;
|
|
107
|
+
min-height: 1px;
|
|
108
|
+
background: var( --dock-line );
|
|
109
|
+
touch-action: none;
|
|
110
|
+
|
|
111
|
+
&::after {
|
|
112
|
+
content: "";
|
|
113
|
+
position: absolute;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
&:hover,
|
|
117
|
+
&:active {
|
|
118
|
+
background: var( --dock-accent );
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// between two boxes that are side by side
|
|
123
|
+
.dock-sizer.row {
|
|
124
|
+
cursor: ew-resize;
|
|
125
|
+
|
|
126
|
+
&::after {
|
|
127
|
+
inset: 0 -3px;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// between two boxes that are one above the other
|
|
132
|
+
.dock-sizer.column {
|
|
133
|
+
cursor: ns-resize;
|
|
134
|
+
|
|
135
|
+
&::after {
|
|
136
|
+
inset: -3px 0;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.dock-stack {
|
|
141
|
+
display: flex;
|
|
142
|
+
flex-direction: column;
|
|
143
|
+
min-width: 0;
|
|
144
|
+
min-height: 0;
|
|
145
|
+
overflow: hidden;
|
|
146
|
+
|
|
147
|
+
// what a panel shows keeps its z-index for itself: it stays under
|
|
148
|
+
// the floating panels
|
|
149
|
+
isolation: isolate;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// the titles of a stack, which are also what it is dragged by
|
|
153
|
+
.dock-titles {
|
|
154
|
+
display: flex;
|
|
155
|
+
flex: none;
|
|
156
|
+
overflow: hidden;
|
|
157
|
+
background: var( --dock-title-background );
|
|
158
|
+
border-bottom: 1px solid var( --dock-line );
|
|
159
|
+
cursor: grab;
|
|
160
|
+
user-select: none;
|
|
161
|
+
touch-action: none;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.dock-title {
|
|
165
|
+
display: flex;
|
|
166
|
+
flex: none;
|
|
167
|
+
align-items: center;
|
|
168
|
+
gap: 6px;
|
|
169
|
+
padding: 6px 8px;
|
|
170
|
+
font-weight: 600;
|
|
171
|
+
white-space: nowrap;
|
|
172
|
+
color: var( --dock-title-color );
|
|
173
|
+
|
|
174
|
+
// under the pointer: it can be taken
|
|
175
|
+
&:hover {
|
|
176
|
+
color: var( --dock-title-color-active );
|
|
177
|
+
background: var( --dock-title-background-hover );
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// The only title of a stack takes the whole bar: its button is at
|
|
181
|
+
// the end of it. Among several tabs, it follows the text of its tab.
|
|
182
|
+
&:only-child {
|
|
183
|
+
flex: 1 1 0;
|
|
184
|
+
min-width: 0;
|
|
185
|
+
|
|
186
|
+
> span {
|
|
187
|
+
flex: 1 1 0;
|
|
188
|
+
min-width: 0;
|
|
189
|
+
overflow: hidden;
|
|
190
|
+
text-overflow: ellipsis;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// several tabs: the one in front stands out
|
|
195
|
+
&.active:not( :only-child ) {
|
|
196
|
+
color: var( --dock-title-color-active );
|
|
197
|
+
box-shadow: inset 0 -2px 0 var( --dock-accent );
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.dock-close {
|
|
202
|
+
width: 16px;
|
|
203
|
+
height: 16px;
|
|
204
|
+
line-height: 16px;
|
|
205
|
+
border-radius: 3px;
|
|
206
|
+
text-align: center;
|
|
207
|
+
font-weight: 400;
|
|
208
|
+
cursor: pointer;
|
|
209
|
+
opacity: 0.6;
|
|
210
|
+
|
|
211
|
+
&:hover {
|
|
212
|
+
opacity: 1;
|
|
213
|
+
background: var( --dock-line );
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.dock-body {
|
|
218
|
+
display: flex;
|
|
219
|
+
flex: 1 1 0;
|
|
220
|
+
min-width: 0;
|
|
221
|
+
min-height: 0;
|
|
222
|
+
overflow: hidden;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// what a panel shows fills its page
|
|
226
|
+
.dock-page {
|
|
227
|
+
display: flex;
|
|
228
|
+
flex-direction: column;
|
|
229
|
+
flex: 1 1 0;
|
|
230
|
+
min-width: 0;
|
|
231
|
+
min-height: 0;
|
|
232
|
+
overflow: hidden;
|
|
233
|
+
|
|
234
|
+
> * {
|
|
235
|
+
flex: 1 1 0;
|
|
236
|
+
min-width: 0;
|
|
237
|
+
min-height: 0;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.dock-float {
|
|
242
|
+
position: absolute;
|
|
243
|
+
z-index: 20;
|
|
244
|
+
display: flex;
|
|
245
|
+
flex-direction: column;
|
|
246
|
+
min-width: 120px;
|
|
247
|
+
min-height: 80px;
|
|
248
|
+
overflow: hidden;
|
|
249
|
+
isolation: isolate;
|
|
250
|
+
|
|
251
|
+
background: var( --dock-float-background );
|
|
252
|
+
border: 1px solid var( --dock-line );
|
|
253
|
+
border-radius: 4px;
|
|
254
|
+
box-shadow: var( --dock-float-shadow );
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// where the panel that is dragged will go
|
|
258
|
+
.dock-preview {
|
|
259
|
+
position: absolute;
|
|
260
|
+
z-index: 30;
|
|
261
|
+
box-sizing: border-box;
|
|
262
|
+
pointer-events: none;
|
|
263
|
+
|
|
264
|
+
background: color-mix( in srgb, var( --dock-accent ) 30%, transparent );
|
|
265
|
+
border: 1px solid var( --dock-accent );
|
|
266
|
+
border-radius: 3px;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// It holds the pointer during a drag: its cursor is the one that shows.
|
|
270
|
+
// It must be in the page to do so, but takes no room.
|
|
271
|
+
.dock-grip {
|
|
272
|
+
position: absolute;
|
|
273
|
+
left: 0;
|
|
274
|
+
top: 0;
|
|
275
|
+
width: 0;
|
|
276
|
+
height: 0;
|
|
277
|
+
cursor: grabbing;
|
|
278
|
+
}
|
|
279
|
+
}
|