x4js 2.3.14 → 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 +144 -10
- package/package.json +1 -1
- package/src/components/colorinput/colorinput.ts +5 -2
- 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/components/popup/popup.ts +7 -6
- package/src/core/component.ts +38 -1
- package/src/core/core_application.ts +33 -0
- package/src/core/core_shortcuts.ts +181 -0
- package/src/core/core_state.ts +7 -3
- package/src/x4.d.ts +1 -0
- package/src/x4.ts +1 -0
package/aicontext.md
CHANGED
|
@@ -39,17 +39,18 @@ 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
|
|
46
46
|
|
|
47
47
|
EventSource<E> standalone event registry
|
|
48
48
|
├── DataStore<T>
|
|
49
|
-
|
|
49
|
+
├── Router
|
|
50
|
+
└── StateManager<T>
|
|
50
51
|
|
|
51
52
|
CoreElement-based (non-DOM): DataProxy, DataView, Store
|
|
52
|
-
Standalone: Rect, Timer, Color, DataModel,
|
|
53
|
+
Standalone: Rect, Timer, Color, DataModel, Shortcuts, Stylesheet, ComputedStyle,
|
|
53
54
|
SvgBuilder, X4PDFBuilder, x4_react, CMover, UnsafeHtml (extends String)
|
|
54
55
|
```
|
|
55
56
|
|
|
@@ -239,6 +240,7 @@ setCapture(pointerId: number): void; releaseCapture(pointerId: number): void
|
|
|
239
240
|
animate(keyframes: Keyframe[], duration: number): void
|
|
240
241
|
onGlobalEvent(cb: (ev: EvMessage) => void): void // auto-removed on DOM removal
|
|
241
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
|
|
242
244
|
```
|
|
243
245
|
|
|
244
246
|
### Exported helpers
|
|
@@ -284,6 +286,7 @@ setEnv(name: string, value: any): void
|
|
|
284
286
|
getEnv(name: string, def_value?: any): any
|
|
285
287
|
static fireGlobal(msg: string, params?: any): void
|
|
286
288
|
focusNext(next: boolean): boolean
|
|
289
|
+
addShortcut(keys: string | string[], callback: (ev: KeyboardEvent) => void, options?: ShortcutOptions): void // see Shortcuts
|
|
287
290
|
setupSocketMessaging(path?: string, looseCallback?: () => void): void
|
|
288
291
|
getStorage(name: string): string
|
|
289
292
|
setStorage(name: string, value: string|number): void
|
|
@@ -386,6 +389,79 @@ class MasonryBox extends Box {
|
|
|
386
389
|
|
|
387
390
|
---
|
|
388
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
|
+
|
|
389
465
|
## Display components
|
|
390
466
|
|
|
391
467
|
### `Label`
|
|
@@ -1099,6 +1175,46 @@ parseRoute(str: string|RegExp, loose?: boolean): Segment
|
|
|
1099
1175
|
|
|
1100
1176
|
---
|
|
1101
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
|
+
|
|
1102
1218
|
## i18n (`core_i18n`)
|
|
1103
1219
|
|
|
1104
1220
|
Two built-in languages (`fr` default, and `en`).
|
|
@@ -1277,17 +1393,36 @@ initTooltips(): void // initialize the global
|
|
|
1277
1393
|
|
|
1278
1394
|
---
|
|
1279
1395
|
|
|
1280
|
-
## State (`core_state`)
|
|
1396
|
+
## State (`core_state`)
|
|
1281
1397
|
|
|
1282
1398
|
```ts
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1399
|
+
makeState<T>(initialState: T): StateProxy<T> // the data itself + on / off / once / watch
|
|
1400
|
+
|
|
1401
|
+
const state = makeState({ count: 0, user: { name: '' }, items: [1, 2, 3] })
|
|
1402
|
+
state.on('change', e => ...) // EvStateChange { path, value }
|
|
1403
|
+
state.watch('user', e => ...) // 'user', anything below it, or a parent replaced; returns { off() }
|
|
1404
|
+
state.count++ // path "count"
|
|
1405
|
+
state.user.name = 'x' // path "user.name"
|
|
1406
|
+
state.items.push(4) // path "items[3]"
|
|
1407
|
+
|
|
1408
|
+
class StateManager<T> extends EventSource<StateEvents> {
|
|
1409
|
+
constructor(initialState: T)
|
|
1410
|
+
proxify(): StateProxy<T>
|
|
1411
|
+
on(name, listener): { off(): void }
|
|
1412
|
+
off(name, listener): void
|
|
1413
|
+
once(name, listener): { off(): void }
|
|
1414
|
+
watch(path: string, cb: (ev: EvStateChange) => void): { off(): void }
|
|
1287
1415
|
}
|
|
1288
|
-
|
|
1416
|
+
|
|
1417
|
+
// Component (protected): a state kept in localStorage under "x4@persist:<name>",
|
|
1418
|
+
// saved 500 ms after a change
|
|
1419
|
+
loadPState(name: string, defaults: Record<string, any>): any
|
|
1289
1420
|
```
|
|
1290
1421
|
|
|
1422
|
+
- Plain objects and arrays are proxied when read; Date, Map, Set and class instances are not.
|
|
1423
|
+
- Setting a property to the value it already has fires nothing.
|
|
1424
|
+
- Reading a property that does not exist logs an error and returns `undefined`.
|
|
1425
|
+
|
|
1291
1426
|
---
|
|
1292
1427
|
|
|
1293
1428
|
## Common patterns
|
|
@@ -1487,7 +1622,6 @@ Optional keys: `external`, `define`, `esbuild`, `dev` (`host`, `port`, `https`,
|
|
|
1487
1622
|
- The default language is `fr`. Call `selectLanguage('en')` to switch to English.
|
|
1488
1623
|
- `Gridview` is backed by a `DataStore`; `Spreadsheet` is backed by a `Store` (different, cell-based).
|
|
1489
1624
|
- `DataRecordID` is currently typed `any`; `strictNullChecks` is intentionally off in the recommended `tsconfig.json`.
|
|
1490
|
-
- `StateManager` (`core_state`) is marked **Experimental** — verify before relying on it.
|
|
1491
1625
|
- `MonacoEditor` requires `MonacoEditor.start()` (async) before use; register extra typings with `MonacoEditor.addTypelib(name, code)`.
|
|
1492
1626
|
- `x4_react.create_element` is the JSX pragma;
|
|
1493
1627
|
- `unbubbleEvents` is the set of DOM events that do not bubble through the x4 component tree.
|
package/package.json
CHANGED
|
@@ -86,6 +86,7 @@ export class ColorInput extends HBox<ColorInputProps,ColorInputEvents> {
|
|
|
86
86
|
private _edit: Input;
|
|
87
87
|
private _swatches: Swatches | ( ( ) => Swatches );
|
|
88
88
|
private _popup: Popup; // the color picker, once it was opened
|
|
89
|
+
private _closedAt = -1; // when it closed for the last time (performance.now)
|
|
89
90
|
|
|
90
91
|
constructor( props: ColorInputProps ) {
|
|
91
92
|
super( props );
|
|
@@ -128,11 +129,12 @@ export class ColorInput extends HBox<ColorInputProps,ColorInputEvents> {
|
|
|
128
129
|
if( props.picker ) {
|
|
129
130
|
// A click on the swatch opens the picker, a second one closes it.
|
|
130
131
|
// The picker closes by itself as soon as the button goes down
|
|
131
|
-
// outside of it
|
|
132
|
+
// outside of it, before the swatch hears of it: it was open if
|
|
133
|
+
// it closed since this very press began.
|
|
132
134
|
let wasOpen = false;
|
|
133
135
|
|
|
134
136
|
this._swatch.addClass( "picker" );
|
|
135
|
-
this._swatch.addDOMEvent( "pointerdown",
|
|
137
|
+
this._swatch.addDOMEvent( "pointerdown", ev => { wasOpen = this._closedAt >= ev.timeStamp; } );
|
|
136
138
|
this._swatch.addDOMEvent( "click", ( ) => {
|
|
137
139
|
if( !wasOpen ) {
|
|
138
140
|
this._openPicker( );
|
|
@@ -202,6 +204,7 @@ export class ColorInput extends HBox<ColorInputProps,ColorInputEvents> {
|
|
|
202
204
|
} );
|
|
203
205
|
|
|
204
206
|
const popup = this._popup = new Popup( { cls: "x4colorinput-popup", autoClose: true, content: picker } );
|
|
207
|
+
popup.on( "closed", ( ) => { this._closedAt = performance.now( ); } );
|
|
205
208
|
|
|
206
209
|
// after the construction: the picker fires a first change when it is built
|
|
207
210
|
picker.on( "change", ev => {
|
|
@@ -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
|
+
}
|