x4js 2.3.15 → 2.3.17
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 +170 -13
- package/package.json +1 -1
- package/src/components/colorinput/colorinput.module.scss +2 -2
- package/src/components/colorpicker/colorpicker.module.scss +2 -2
- package/src/components/combobox/combobox.ts +5 -1
- package/src/components/components.ts +1 -0
- package/src/components/dockingview/dockingview.module.scss +297 -0
- package/src/components/dockingview/dockingview.ts +1036 -0
- package/src/components/dockingview/docklayout.ts +748 -0
- package/src/components/gauge/gauge.ts +4 -0
- package/src/components/input/input.ts +6 -1
- package/src/components/notification/notification.module.scss +8 -3
- package/src/components/notification/notification.ts +3 -0
- package/src/components/progress/progress.ts +7 -3
- package/src/components/switch/switch.ts +2 -1
- 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
|
|
@@ -264,7 +265,7 @@ interface EvDblClick extends ComponentEvent {}
|
|
|
264
265
|
interface EvBtnClick extends CoreEvent { button: string }
|
|
265
266
|
|
|
266
267
|
// Spacers
|
|
267
|
-
class Flex extends Component // CSS "x4flex"
|
|
268
|
+
class Flex extends Component // new Flex(), no argument; CSS "x4flex"
|
|
268
269
|
class Space extends Component // new Space(width?: number|string, cls?: string)
|
|
269
270
|
```
|
|
270
271
|
|
|
@@ -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,84 @@ 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
|
+
sides?: DockSide[] // a tool: the bands it may be docked in, instead of the view's
|
|
407
|
+
}
|
|
408
|
+
interface DockingViewProps extends ComponentProps {
|
|
409
|
+
panels: DockPanel[]
|
|
410
|
+
layout: DockLayout // where the panels are at first
|
|
411
|
+
floating?: boolean // tools can float above the rest
|
|
412
|
+
closable?: boolean // tools have a button that hides them
|
|
413
|
+
sides?: DockSide[] // the bands tools may be docked in; left and right when missing
|
|
414
|
+
persist?: string // a name: the layout is remembered under it (application storage)
|
|
415
|
+
layoutChange?: EventCallback<ComponentEvent>
|
|
416
|
+
}
|
|
417
|
+
class DockingView extends Component {
|
|
418
|
+
getState(): DockLayout // plain data, to save
|
|
419
|
+
setState(layout: unknown): void // a saved layout; what it lacks comes from props.layout
|
|
420
|
+
resetLayout(): void // back to props.layout
|
|
421
|
+
showPanel(id: string, show?: boolean): void
|
|
422
|
+
isPanelVisible(id: string): boolean
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
interface DockLayout {
|
|
426
|
+
left: DockBand; right: DockBand // the tools, on the sides
|
|
427
|
+
top?: DockBand; bottom?: DockBand // the tools above and under the contents, between left and right
|
|
428
|
+
content: DockNode // the contents; null when there is none
|
|
429
|
+
floats: DockFloat[] // floating tools: { panel, x, y, w, h }
|
|
430
|
+
hidden: string[] // ids of the closed panels
|
|
431
|
+
}
|
|
432
|
+
interface DockBand { size: number; root: DockNode } // size: width in px (height for top/bottom); root: null when empty
|
|
433
|
+
type DockSide = 'left' | 'right' | 'top' | 'bottom'
|
|
434
|
+
type DockNode = DockStack | DockSplit
|
|
435
|
+
interface DockStack { type: 'stack'; size: number; panels: string[]; active: string } // one tab per panel
|
|
436
|
+
interface DockSplit { type: 'split'; dir: 'row' | 'column'; size: number; children: DockNode[] }
|
|
437
|
+
// size of a node: its share of its split (0 = an equal share)
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
```ts
|
|
441
|
+
const view = new DockingView({
|
|
442
|
+
flex: true,
|
|
443
|
+
floating: true,
|
|
444
|
+
closable: true,
|
|
445
|
+
persist: 'myapp.layout',
|
|
446
|
+
panels: [
|
|
447
|
+
{ id: 'tools', title: 'Tools', content: tools },
|
|
448
|
+
{ id: 'props', title: 'Properties', content: props },
|
|
449
|
+
{ id: 'page', kind: 'content', content: editor },
|
|
450
|
+
],
|
|
451
|
+
layout: {
|
|
452
|
+
left: { size: 180, root: { type: 'stack', size: 0, panels: ['tools'], active: 'tools' } },
|
|
453
|
+
right: { size: 260, root: { type: 'stack', size: 0, panels: ['props'], active: 'props' } },
|
|
454
|
+
content: { type: 'stack', size: 0, panels: ['page'], active: 'page' },
|
|
455
|
+
floats: [],
|
|
456
|
+
hidden: [],
|
|
457
|
+
},
|
|
458
|
+
})
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
- A closed panel is hidden, not removed: `showPanel(id)` brings it back where it was. Offer it in a menu.
|
|
462
|
+
- Tools only go in the bands or in `floats`; contents only in `content`. A panel missing from the layout is added where its kind goes.
|
|
463
|
+
- A tool goes in `left` or `right` only, unless `sides` says otherwise (on the view, or on one panel): `sides: ['bottom']` for what is wide and low (a log, a list of results). It is never offered a place in a band it may not be in.
|
|
464
|
+
- A content alone has no title bar; with several contents, each stack has tabs.
|
|
465
|
+
- What a panel shows is built once (`content`) and moved as the layout changes: never rebuild it.
|
|
466
|
+
- 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`.
|
|
467
|
+
|
|
468
|
+
---
|
|
469
|
+
|
|
390
470
|
## Display components
|
|
391
471
|
|
|
392
472
|
### `Label`
|
|
@@ -460,14 +540,15 @@ class Image extends Component {
|
|
|
460
540
|
### `Progress`
|
|
461
541
|
|
|
462
542
|
```ts
|
|
463
|
-
interface ProgressProps extends ComponentProps { /*
|
|
543
|
+
interface ProgressProps extends ComponentProps { value: number; min?: number /* 0 */; max?: number /* 100 */ }
|
|
544
|
+
// the bar is where value is between min (0%) and max (100%)
|
|
464
545
|
class Progress extends Component { setValue(value: number): void }
|
|
465
546
|
```
|
|
466
547
|
|
|
467
548
|
### `Gauge`
|
|
468
549
|
|
|
469
550
|
```ts
|
|
470
|
-
interface GaugeProps extends ComponentProps { min?: number
|
|
551
|
+
interface GaugeProps extends ComponentProps { min?: number /* 0 */; max?: number /* 100 */; colors?: string[] }
|
|
471
552
|
class Gauge extends Component {
|
|
472
553
|
setRange(min: number, max: number): void
|
|
473
554
|
setPos(pos: number): void
|
|
@@ -541,6 +622,11 @@ class TextEdit extends HBox {
|
|
|
541
622
|
### `TextArea`
|
|
542
623
|
|
|
543
624
|
```ts
|
|
625
|
+
interface TextAreaProps extends BaseProps { // BaseProps: see Input (name, readonly, placeholder...)
|
|
626
|
+
label?: string; value?: string
|
|
627
|
+
resize?: boolean | "none" | "vertical" | "horizontal" | "both"
|
|
628
|
+
trim?: boolean // getText() trims, unless false
|
|
629
|
+
}
|
|
544
630
|
class TextArea extends VBox { getText(): string; setText(text: string): void }
|
|
545
631
|
```
|
|
546
632
|
|
|
@@ -561,7 +647,13 @@ class Checkbox extends Component {
|
|
|
561
647
|
### `Radio`
|
|
562
648
|
|
|
563
649
|
```ts
|
|
564
|
-
interface RadioProps extends ComponentProps {
|
|
650
|
+
interface RadioProps extends ComponentProps {
|
|
651
|
+
label: string; icon?: string
|
|
652
|
+
name: string // required: the radios that have the same name are one group
|
|
653
|
+
value: number | string // required
|
|
654
|
+
checked?: boolean
|
|
655
|
+
change?: EventCallback<EvChange>
|
|
656
|
+
}
|
|
565
657
|
class Radio extends Component {
|
|
566
658
|
getValue(): string; setCheck(ck: boolean): void
|
|
567
659
|
setLabel(text: string): void
|
|
@@ -572,14 +664,19 @@ class Radio extends Component {
|
|
|
572
664
|
### `Switch`
|
|
573
665
|
|
|
574
666
|
```ts
|
|
575
|
-
interface SwitchProps extends ComponentProps {
|
|
667
|
+
interface SwitchProps extends ComponentProps { label: string; checked?: boolean; name?: string }
|
|
668
|
+
// with a name, a Form reads and writes it as a boolean
|
|
576
669
|
class Switch extends HBox {}
|
|
577
670
|
```
|
|
578
671
|
|
|
579
672
|
### `Slider`
|
|
580
673
|
|
|
581
674
|
```ts
|
|
582
|
-
interface SliderProps extends ComponentProps {
|
|
675
|
+
interface SliderProps extends ComponentProps {
|
|
676
|
+
value: number; min: number; max: number // required
|
|
677
|
+
step?: number; vertical?: boolean
|
|
678
|
+
change?: EventCallback<EvChange>
|
|
679
|
+
}
|
|
583
680
|
class Slider extends Component {
|
|
584
681
|
setMin(min: number): void; setMax(max: number): void; setValue(v: number): void
|
|
585
682
|
}
|
|
@@ -590,17 +687,29 @@ class Slider extends Component {
|
|
|
590
687
|
```ts
|
|
591
688
|
interface SelectProps extends ComponentProps {
|
|
592
689
|
items: ListItem[] // required
|
|
593
|
-
value
|
|
594
|
-
multiple?: boolean; name?: string
|
|
690
|
+
value?: ListboxID // the id of the item that is selected
|
|
691
|
+
multiple?: boolean; required?: boolean; name?: string
|
|
595
692
|
change?: EventCallback<EvChange>; focus?: EventCallback<EvFocus>
|
|
596
693
|
}
|
|
597
|
-
class Select extends Component {
|
|
694
|
+
class Select extends Component {
|
|
695
|
+
getValue(): ListboxID; setValue(value: ListboxID): void
|
|
696
|
+
getSelValues(): ListboxID[]; setSelValues(values: ListboxID[]): void // multiple
|
|
697
|
+
setItems(items: ListItem[]): void
|
|
698
|
+
}
|
|
598
699
|
```
|
|
599
700
|
|
|
600
701
|
### `Combobox` — input + dropdown list
|
|
601
702
|
|
|
602
703
|
```ts
|
|
603
|
-
interface ComboboxProps extends ComponentProps {
|
|
704
|
+
interface ComboboxProps extends ComponentProps {
|
|
705
|
+
items: ListItem[] // required
|
|
706
|
+
value?: ListboxID // the id of the item that is selected
|
|
707
|
+
label?: string; labelWidth?: number | string
|
|
708
|
+
readonly?: boolean // true by default: the user picks, and cannot type. false to let him type
|
|
709
|
+
renderer?: (item: ListItem) => Component // draws an item of the list (the field still shows item.text)
|
|
710
|
+
required?: boolean; name?: string
|
|
711
|
+
selectionChange?: EventCallback<EvSelectionChange>
|
|
712
|
+
}
|
|
604
713
|
class Combobox extends Component {
|
|
605
714
|
getValue(): string; setValue(value: string): void
|
|
606
715
|
getInput(): Input
|
|
@@ -950,7 +1059,14 @@ class ProgressionBox extends Dialog {
|
|
|
950
1059
|
### `Notification`
|
|
951
1060
|
|
|
952
1061
|
```ts
|
|
953
|
-
|
|
1062
|
+
interface NotificationProps extends ComponentProps {
|
|
1063
|
+
title: string; text: string | UnsafeHtml
|
|
1064
|
+
mode?: "success" | "danger"
|
|
1065
|
+
loading?: boolean // a turning icon
|
|
1066
|
+
iconId?: string
|
|
1067
|
+
closable?: boolean // a button that closes it
|
|
1068
|
+
}
|
|
1069
|
+
class Notification extends Popup { display(time_in_s?: number): void } // 0: until it is closed
|
|
954
1070
|
```
|
|
955
1071
|
|
|
956
1072
|
---
|
|
@@ -1100,6 +1216,46 @@ parseRoute(str: string|RegExp, loose?: boolean): Segment
|
|
|
1100
1216
|
|
|
1101
1217
|
---
|
|
1102
1218
|
|
|
1219
|
+
## Shortcuts (`core_shortcuts`)
|
|
1220
|
+
|
|
1221
|
+
Keyboard shortcuts. A sequence is written as it is read: `"Mod+C"`, `"Shift+Mod+Z"`, `"Delete"`, `"Alt+ArrowLeft"`.
|
|
1222
|
+
Modifiers: `Shift`, `Ctrl`, `Cmd`, `Alt`, and `Mod` = the key of the commands (Ctrl on Windows and Linux, Cmd on a Mac). Prefer `Mod`.
|
|
1223
|
+
|
|
1224
|
+
```ts
|
|
1225
|
+
interface ShortcutOptions { editable?: boolean } // true: also while the user types in a field
|
|
1226
|
+
|
|
1227
|
+
component.addShortcut(keys, callback, options?) // a view: while the focus is in the component
|
|
1228
|
+
Application.instance().addShortcut(keys, callback, options?) // the whole application, wherever the focus is
|
|
1229
|
+
|
|
1230
|
+
shortcutText(keys: string): string // for a tooltip or a menu: "Ctrl+Z", "⌘Z" on a Mac
|
|
1231
|
+
|
|
1232
|
+
class Shortcuts { // the list itself; rarely used directly
|
|
1233
|
+
add(keys: string | string[], callback: (ev: KeyboardEvent) => void, options?: ShortcutOptions): void
|
|
1234
|
+
clear(): void
|
|
1235
|
+
handle(ev: KeyboardEvent): boolean // true when the key was a shortcut
|
|
1236
|
+
}
|
|
1237
|
+
```
|
|
1238
|
+
|
|
1239
|
+
```ts
|
|
1240
|
+
class MyView extends VBox {
|
|
1241
|
+
constructor(props: BoxProps) {
|
|
1242
|
+
super(props)
|
|
1243
|
+
this.addShortcut('Mod+C', () => this.copy())
|
|
1244
|
+
this.addShortcut(['Mod+Y', 'Shift+Mod+Z'], () => this.redo())
|
|
1245
|
+
this.setContent(new Button({ label: 'Undo', tooltip: shortcutText('Mod+Z'), click: () => this.undo() }))
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
```
|
|
1249
|
+
|
|
1250
|
+
- 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.
|
|
1251
|
+
- A key typed in an `input`, a `textarea` or a `select` is not a shortcut, unless `editable` is set.
|
|
1252
|
+
- A key that what has the focus already used (it called `preventDefault` or `stopPropagation`) is not a shortcut.
|
|
1253
|
+
- A shortcut always takes its key (`preventDefault`). For a key that must sometimes be left alone, use a `keydown` handler instead.
|
|
1254
|
+
- An application shortcut also works while a dialog is open.
|
|
1255
|
+
- Do not write a `keydown` handler with a `switch` on `ev.ctrlKey` for commands: use `addShortcut`.
|
|
1256
|
+
|
|
1257
|
+
---
|
|
1258
|
+
|
|
1103
1259
|
## i18n (`core_i18n`)
|
|
1104
1260
|
|
|
1105
1261
|
Two built-in languages (`fr` default, and `en`).
|
|
@@ -1499,6 +1655,7 @@ Optional keys: `external`, `define`, `esbuild`, `dev` (`host`, `port`, `https`,
|
|
|
1499
1655
|
- `Component` stores its instance on its DOM node. Use `componentFromDOM(el)` to retrieve a component from an `HTMLElement`.
|
|
1500
1656
|
- Numbers passed to `setStyle`/`setStyleValue` are auto-suffixed with `"px"` except for unitless properties (see the `unitless` set: `zIndex`, `opacity`, `flexGrow`...).
|
|
1501
1657
|
- `show(false)` / `hide()` toggle the `"x4hidden"` CSS class, not `display:none`.
|
|
1658
|
+
- x4 sets `user-select: none` on every element (`*`): text that must be selectable needs `user-select: text` on it and on its descendants.
|
|
1502
1659
|
- `enable(false)` / `disable()` propagate the `disabled` state to child `<input>` and `<button>` elements.
|
|
1503
1660
|
- `Application` is a strict singleton — one instance per page (an assertion fails otherwise).
|
|
1504
1661
|
- `Popup.show()` routes to `displayCenter()`. To position, call `displayAt()` or `displayNear()` instead.
|
package/package.json
CHANGED
|
@@ -50,10 +50,10 @@
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
// props.nullable, no color:
|
|
53
|
+
// props.nullable, no color: the checkerboard that stands for what is transparent
|
|
54
54
|
&.none {
|
|
55
55
|
border: 1px solid var( --border );
|
|
56
|
-
background:
|
|
56
|
+
background: repeating-conic-gradient( #c8c8c8 0% 25%, #ffffff 0% 50% ) 0 0 / 6px 6px;
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|
|
@@ -147,9 +147,9 @@
|
|
|
147
147
|
border-color: var( --border-focus );
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
// no color:
|
|
150
|
+
// no color: the checkerboard that stands for what is transparent
|
|
151
151
|
&.none {
|
|
152
|
-
background:
|
|
152
|
+
background: repeating-conic-gradient( #c8c8c8 0% 25%, #ffffff 0% 50% ) 0 0 / 6px 6px;
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
155
|
}
|
|
@@ -87,6 +87,10 @@ interface ComboboxProps extends Omit<ComponentProps,"content"> {
|
|
|
87
87
|
readonly?: boolean;
|
|
88
88
|
required?: boolean;
|
|
89
89
|
items: ListItem[];
|
|
90
|
+
|
|
91
|
+
/** draws an item of the list, instead of its text. The field still shows the text of the item that is chosen. */
|
|
92
|
+
renderer?: ( item: ListItem ) => Component;
|
|
93
|
+
|
|
90
94
|
selectionChange?: EventCallback<EvSelectionChange>,
|
|
91
95
|
}
|
|
92
96
|
|
|
@@ -139,7 +143,7 @@ export class Combobox extends Component<ComboboxProps,ComboboxEvents> {
|
|
|
139
143
|
this.setAttribute( "required", true );
|
|
140
144
|
}
|
|
141
145
|
|
|
142
|
-
this._popup = new DropdownList( { items: props.items } );
|
|
146
|
+
this._popup = new DropdownList( { items: props.items, renderer: props.renderer } );
|
|
143
147
|
const list = this._popup.getList( );
|
|
144
148
|
|
|
145
149
|
const _select = ( sel: ListboxID ) => {
|
|
@@ -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,297 @@
|
|
|
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, what is between the
|
|
38
|
+
// two, the band of the right. The floating tools are placed from its
|
|
39
|
+
// 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
|
+
// Between the bands of the left and of the right, one above the other:
|
|
49
|
+
// the band of the top, the room of the contents, the band of the bottom.
|
|
50
|
+
.dock-middle {
|
|
51
|
+
display: flex;
|
|
52
|
+
flex-direction: column;
|
|
53
|
+
flex: 1 1 0;
|
|
54
|
+
min-width: 0;
|
|
55
|
+
min-height: 0;
|
|
56
|
+
overflow: hidden;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// The tools of one side. Its width is given by the layout; it only
|
|
60
|
+
// gets narrower when the box has no room left.
|
|
61
|
+
.dock-band {
|
|
62
|
+
display: flex;
|
|
63
|
+
flex: 0 1 auto;
|
|
64
|
+
min-width: 48px;
|
|
65
|
+
min-height: 0;
|
|
66
|
+
overflow: hidden;
|
|
67
|
+
|
|
68
|
+
> .dock-split,
|
|
69
|
+
> .dock-stack {
|
|
70
|
+
flex: 1 1 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// the bands that lie flat: it is their height that the layout gives
|
|
74
|
+
&.top,
|
|
75
|
+
&.bottom {
|
|
76
|
+
min-width: 0;
|
|
77
|
+
min-height: 48px;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// the room of the contents: what the bands leave
|
|
82
|
+
.dock-area {
|
|
83
|
+
display: flex;
|
|
84
|
+
flex: 1 1 0;
|
|
85
|
+
min-width: 0;
|
|
86
|
+
min-height: 0;
|
|
87
|
+
overflow: hidden;
|
|
88
|
+
|
|
89
|
+
> .dock-split,
|
|
90
|
+
> .dock-stack {
|
|
91
|
+
flex: 1 1 0;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.dock-split {
|
|
96
|
+
display: flex;
|
|
97
|
+
min-width: 0;
|
|
98
|
+
min-height: 0;
|
|
99
|
+
overflow: hidden;
|
|
100
|
+
|
|
101
|
+
&.column {
|
|
102
|
+
flex-direction: column;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// the smallest size of a child (MIN_SIZE in dockingview.ts)
|
|
106
|
+
&.row > .dock-split,
|
|
107
|
+
&.row > .dock-stack {
|
|
108
|
+
min-width: 48px;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
&.column > .dock-split,
|
|
112
|
+
&.column > .dock-stack {
|
|
113
|
+
min-height: 48px;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// One line between two children. It is caught from a little further
|
|
118
|
+
// than what is drawn.
|
|
119
|
+
.dock-sizer {
|
|
120
|
+
position: relative;
|
|
121
|
+
z-index: 1;
|
|
122
|
+
flex: none;
|
|
123
|
+
align-self: stretch;
|
|
124
|
+
min-width: 1px;
|
|
125
|
+
min-height: 1px;
|
|
126
|
+
background: var( --dock-line );
|
|
127
|
+
touch-action: none;
|
|
128
|
+
|
|
129
|
+
&::after {
|
|
130
|
+
content: "";
|
|
131
|
+
position: absolute;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
&:hover,
|
|
135
|
+
&:active {
|
|
136
|
+
background: var( --dock-accent );
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// between two boxes that are side by side
|
|
141
|
+
.dock-sizer.row {
|
|
142
|
+
cursor: ew-resize;
|
|
143
|
+
|
|
144
|
+
&::after {
|
|
145
|
+
inset: 0 -3px;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// between two boxes that are one above the other
|
|
150
|
+
.dock-sizer.column {
|
|
151
|
+
cursor: ns-resize;
|
|
152
|
+
|
|
153
|
+
&::after {
|
|
154
|
+
inset: -3px 0;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.dock-stack {
|
|
159
|
+
display: flex;
|
|
160
|
+
flex-direction: column;
|
|
161
|
+
min-width: 0;
|
|
162
|
+
min-height: 0;
|
|
163
|
+
overflow: hidden;
|
|
164
|
+
|
|
165
|
+
// what a panel shows keeps its z-index for itself: it stays under
|
|
166
|
+
// the floating panels
|
|
167
|
+
isolation: isolate;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// the titles of a stack, which are also what it is dragged by
|
|
171
|
+
.dock-titles {
|
|
172
|
+
display: flex;
|
|
173
|
+
flex: none;
|
|
174
|
+
overflow: hidden;
|
|
175
|
+
background: var( --dock-title-background );
|
|
176
|
+
border-bottom: 1px solid var( --dock-line );
|
|
177
|
+
cursor: grab;
|
|
178
|
+
user-select: none;
|
|
179
|
+
touch-action: none;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.dock-title {
|
|
183
|
+
display: flex;
|
|
184
|
+
flex: none;
|
|
185
|
+
align-items: center;
|
|
186
|
+
gap: 6px;
|
|
187
|
+
padding: 6px 8px;
|
|
188
|
+
font-weight: 600;
|
|
189
|
+
white-space: nowrap;
|
|
190
|
+
color: var( --dock-title-color );
|
|
191
|
+
|
|
192
|
+
// under the pointer: it can be taken
|
|
193
|
+
&:hover {
|
|
194
|
+
color: var( --dock-title-color-active );
|
|
195
|
+
background: var( --dock-title-background-hover );
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// The only title of a stack takes the whole bar: its button is at
|
|
199
|
+
// the end of it. Among several tabs, it follows the text of its tab.
|
|
200
|
+
&:only-child {
|
|
201
|
+
flex: 1 1 0;
|
|
202
|
+
min-width: 0;
|
|
203
|
+
|
|
204
|
+
> span {
|
|
205
|
+
flex: 1 1 0;
|
|
206
|
+
min-width: 0;
|
|
207
|
+
overflow: hidden;
|
|
208
|
+
text-overflow: ellipsis;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// several tabs: the one in front stands out
|
|
213
|
+
&.active:not( :only-child ) {
|
|
214
|
+
color: var( --dock-title-color-active );
|
|
215
|
+
box-shadow: inset 0 -2px 0 var( --dock-accent );
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.dock-close {
|
|
220
|
+
width: 16px;
|
|
221
|
+
height: 16px;
|
|
222
|
+
line-height: 16px;
|
|
223
|
+
border-radius: 3px;
|
|
224
|
+
text-align: center;
|
|
225
|
+
font-weight: 400;
|
|
226
|
+
cursor: pointer;
|
|
227
|
+
opacity: 0.6;
|
|
228
|
+
|
|
229
|
+
&:hover {
|
|
230
|
+
opacity: 1;
|
|
231
|
+
background: var( --dock-line );
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.dock-body {
|
|
236
|
+
display: flex;
|
|
237
|
+
flex: 1 1 0;
|
|
238
|
+
min-width: 0;
|
|
239
|
+
min-height: 0;
|
|
240
|
+
overflow: hidden;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// what a panel shows fills its page
|
|
244
|
+
.dock-page {
|
|
245
|
+
display: flex;
|
|
246
|
+
flex-direction: column;
|
|
247
|
+
flex: 1 1 0;
|
|
248
|
+
min-width: 0;
|
|
249
|
+
min-height: 0;
|
|
250
|
+
overflow: hidden;
|
|
251
|
+
|
|
252
|
+
> * {
|
|
253
|
+
flex: 1 1 0;
|
|
254
|
+
min-width: 0;
|
|
255
|
+
min-height: 0;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.dock-float {
|
|
260
|
+
position: absolute;
|
|
261
|
+
z-index: 20;
|
|
262
|
+
display: flex;
|
|
263
|
+
flex-direction: column;
|
|
264
|
+
min-width: 120px;
|
|
265
|
+
min-height: 80px;
|
|
266
|
+
overflow: hidden;
|
|
267
|
+
isolation: isolate;
|
|
268
|
+
|
|
269
|
+
background: var( --dock-float-background );
|
|
270
|
+
border: 1px solid var( --dock-line );
|
|
271
|
+
border-radius: 4px;
|
|
272
|
+
box-shadow: var( --dock-float-shadow );
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// where the panel that is dragged will go
|
|
276
|
+
.dock-preview {
|
|
277
|
+
position: absolute;
|
|
278
|
+
z-index: 30;
|
|
279
|
+
box-sizing: border-box;
|
|
280
|
+
pointer-events: none;
|
|
281
|
+
|
|
282
|
+
background: color-mix( in srgb, var( --dock-accent ) 30%, transparent );
|
|
283
|
+
border: 1px solid var( --dock-accent );
|
|
284
|
+
border-radius: 3px;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// It holds the pointer during a drag: its cursor is the one that shows.
|
|
288
|
+
// It must be in the page to do so, but takes no room.
|
|
289
|
+
.dock-grip {
|
|
290
|
+
position: absolute;
|
|
291
|
+
left: 0;
|
|
292
|
+
top: 0;
|
|
293
|
+
width: 0;
|
|
294
|
+
height: 0;
|
|
295
|
+
cursor: grabbing;
|
|
296
|
+
}
|
|
297
|
+
}
|