@xh/hoist 80.0.0-SNAPSHOT.1768003263151 → 80.0.0-SNAPSHOT.1768251400948
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/CHANGELOG.md +11 -0
- package/build/types/cmp/grid/GridModel.d.ts +2 -2
- package/build/types/cmp/layout/CollapsibleSet.d.ts +14 -0
- package/build/types/cmp/layout/Tags.d.ts +2 -0
- package/build/types/cmp/tab/TabContainerModel.d.ts +1 -1
- package/build/types/core/enums/RenderMode.d.ts +1 -1
- package/build/types/desktop/cmp/button/CollapsibleSetButton.d.ts +12 -0
- package/build/types/desktop/cmp/dash/canvas/DashCanvasModel.d.ts +46 -6
- package/build/types/desktop/cmp/dash/canvas/widgetwell/DashCanvasWidgetWell.d.ts +19 -0
- package/build/types/desktop/cmp/dash/canvas/widgetwell/DashCanvasWidgetWellModel.d.ts +11 -0
- package/build/types/dynamics/desktop.d.ts +1 -0
- package/build/types/dynamics/mobile.d.ts +1 -0
- package/build/types/mobile/cmp/button/CollapsibleSetButton.d.ts +12 -0
- package/cmp/grid/GridModel.ts +2 -2
- package/cmp/layout/CollapsibleSet.scss +49 -0
- package/cmp/layout/CollapsibleSet.ts +135 -0
- package/cmp/layout/Tags.ts +2 -0
- package/cmp/tab/TabContainerModel.ts +1 -1
- package/core/enums/RenderMode.ts +1 -1
- package/desktop/appcontainer/AppContainer.ts +2 -0
- package/desktop/cmp/button/CollapsibleSetButton.ts +57 -0
- package/desktop/cmp/dash/canvas/DashCanvas.ts +21 -4
- package/desktop/cmp/dash/canvas/DashCanvasModel.ts +140 -24
- package/desktop/cmp/dash/canvas/widgetwell/DashCanvasWidgetWell.scss +34 -0
- package/desktop/cmp/dash/canvas/widgetwell/DashCanvasWidgetWell.ts +135 -0
- package/desktop/cmp/dash/canvas/widgetwell/DashCanvasWidgetWellModel.ts +65 -0
- package/dynamics/desktop.ts +2 -0
- package/dynamics/mobile.ts +2 -0
- package/mobile/appcontainer/AppContainer.ts +2 -0
- package/mobile/cmp/button/CollapsibleSetButton.ts +57 -0
- package/package.json +3 -3
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -23,6 +23,17 @@
|
|
|
23
23
|
possible to use these models with other, alternate implementations if needed.
|
|
24
24
|
* Added new static typeguard methods on `Store`, `View`, and `Filter` + subclasses.
|
|
25
25
|
|
|
26
|
+
### 🎁 New Features
|
|
27
|
+
|
|
28
|
+
* DashCanvas:
|
|
29
|
+
* supports dragging and dropping widgets in from an external container.
|
|
30
|
+
* supports new compacting strategy: 'wrap'
|
|
31
|
+
* new elementFactory tags: `fieldset`, `legend`
|
|
32
|
+
|
|
33
|
+
### 📚 Libraries
|
|
34
|
+
|
|
35
|
+
* react-grid-layout `2.1 → 2.2.2`
|
|
36
|
+
|
|
26
37
|
## 79.0.0 - 2026-01-05
|
|
27
38
|
|
|
28
39
|
### 💥 Breaking Changes
|
|
@@ -553,9 +553,9 @@ export declare class GridModel extends HoistModel {
|
|
|
553
553
|
autosizeAsync(overrideOpts?: Omit<GridAutosizeOptions, 'mode'>): Promise<void>;
|
|
554
554
|
/**
|
|
555
555
|
* Begin an inline editing session.
|
|
556
|
-
* @param record - StoreRecord/ID to edit. If unspecified, the first selected StoreRecord
|
|
556
|
+
* @param opts.record - StoreRecord/ID to edit. If unspecified, the first selected StoreRecord
|
|
557
557
|
* will be used, if any, or the first overall StoreRecord in the grid.
|
|
558
|
-
* @param colId - ID of column on which to start editing. If unspecified, the first
|
|
558
|
+
* @param opts.colId - ID of column on which to start editing. If unspecified, the first
|
|
559
559
|
* editable column will be used.
|
|
560
560
|
*/
|
|
561
561
|
beginEditAsync(opts?: {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type FieldsetHTMLAttributes, type ReactElement, type ReactNode } from 'react';
|
|
2
|
+
import type { HoistProps, Intent, LayoutProps, RenderMode, TestSupportProps } from '@xh/hoist/core';
|
|
3
|
+
import './CollapsibleSet.scss';
|
|
4
|
+
export interface CollapsibleSetProps extends FieldsetHTMLAttributes<HTMLFieldSetElement>, HoistProps, TestSupportProps, LayoutProps {
|
|
5
|
+
icon?: ReactElement;
|
|
6
|
+
label: ReactNode;
|
|
7
|
+
tooltip?: JSX.Element | string;
|
|
8
|
+
intent?: Intent;
|
|
9
|
+
clickHandler?: () => void;
|
|
10
|
+
collapsed?: boolean;
|
|
11
|
+
hideItemCount?: boolean;
|
|
12
|
+
renderMode?: RenderMode;
|
|
13
|
+
}
|
|
14
|
+
export declare const CollapsibleSet: import("react").FC<CollapsibleSetProps>, collapsibleSet: import("@xh/hoist/core").ElementFactory<CollapsibleSetProps>;
|
|
@@ -10,6 +10,7 @@ export declare const a: import("@xh/hoist/core").ElementFactory<import("react").
|
|
|
10
10
|
export declare const br: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLBRElement>, HTMLBRElement>>;
|
|
11
11
|
export declare const code: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLElement>, HTMLElement>>;
|
|
12
12
|
export declare const div: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>>;
|
|
13
|
+
export declare const fieldset: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").FieldsetHTMLAttributes<HTMLFieldSetElement>, HTMLFieldSetElement>>;
|
|
13
14
|
export declare const form: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").FormHTMLAttributes<HTMLFormElement>, HTMLFormElement>>;
|
|
14
15
|
export declare const hr: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLHRElement>, HTMLHRElement>>;
|
|
15
16
|
export declare const h1: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>>;
|
|
@@ -17,6 +18,7 @@ export declare const h2: import("@xh/hoist/core").ElementFactory<import("react")
|
|
|
17
18
|
export declare const h3: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>>;
|
|
18
19
|
export declare const h4: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>>;
|
|
19
20
|
export declare const label: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").LabelHTMLAttributes<HTMLLabelElement>, HTMLLabelElement>>;
|
|
21
|
+
export declare const legend: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLLegendElement>, HTMLLegendElement>>;
|
|
20
22
|
export declare const li: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>>;
|
|
21
23
|
export declare const nav: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLElement>, HTMLElement>>;
|
|
22
24
|
export declare const ol: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").OlHTMLAttributes<HTMLOListElement>, HTMLOListElement>>;
|
|
@@ -75,7 +75,7 @@ export declare class TabContainerModel extends HoistModel {
|
|
|
75
75
|
protected lastActiveTabId: string;
|
|
76
76
|
/**
|
|
77
77
|
* @param config - TabContainer configuration.
|
|
78
|
-
* @param
|
|
78
|
+
* @param depth - Depth in hierarchy of nested TabContainerModels. Not for application use.
|
|
79
79
|
*/
|
|
80
80
|
constructor({ tabs, defaultTabId, route, track, renderMode, refreshMode, persistWith, emptyText, xhImpl, switcher }: TabContainerConfig, depth?: number);
|
|
81
81
|
/** Set/replace all tabs within the container. */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Options for how contents should be rendered by their parent container.
|
|
3
|
-
* Used by {@link TabContainerModel}, {@link DashContainerModel}, and {@link
|
|
3
|
+
* Used by {@link TabContainerModel}, {@link DashContainerModel}, {@link PanelModel}, and {@link CollapsibleSet}.
|
|
4
4
|
*/
|
|
5
5
|
export declare const RenderMode: Readonly<{
|
|
6
6
|
/** Always render contents when the parent container is rendered, even if inactive. */
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type ReactElement, type ReactNode, type JSX } from 'react';
|
|
2
|
+
import type { Intent, HoistProps } from '@xh/hoist/core';
|
|
3
|
+
export interface CollapsibleSetButtonProps extends HoistProps {
|
|
4
|
+
icon?: ReactElement;
|
|
5
|
+
text: ReactNode;
|
|
6
|
+
tooltip?: JSX.Element | string;
|
|
7
|
+
clickHandler?: (boolean: any) => void;
|
|
8
|
+
intent?: Intent;
|
|
9
|
+
collapsed?: boolean;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare const CollapsibleSetButton: import("react").FC<CollapsibleSetButtonProps>, collapsibleSetButton: import("@xh/hoist/core").ElementFactory<CollapsibleSetButtonProps>;
|
|
@@ -14,11 +14,12 @@ export interface DashCanvasConfig extends DashConfig<DashCanvasViewSpec, DashCan
|
|
|
14
14
|
*/
|
|
15
15
|
rowHeight?: number;
|
|
16
16
|
/**
|
|
17
|
-
* Whether views should "compact" vertically or
|
|
17
|
+
* Whether views should "compact" vertically, horizontally or wrap
|
|
18
18
|
* to condense space. Default `true` defaults to vertical compaction.
|
|
19
|
+
* Use `wrap` with caution. It only works well if all items are 1 row high.
|
|
19
20
|
* See react-grid-layout docs for more information.
|
|
20
|
-
|
|
21
|
-
compact?: boolean | 'vertical' | 'horizontal';
|
|
21
|
+
*/
|
|
22
|
+
compact?: boolean | 'vertical' | 'horizontal' | 'wrap';
|
|
22
23
|
/** Between items [x,y] in pixels. Default `[10, 10]`. */
|
|
23
24
|
margin?: [number, number];
|
|
24
25
|
/** Padding inside the container [x, y] in pixels. Defaults to same as `margin`. */
|
|
@@ -29,6 +30,31 @@ export interface DashCanvasConfig extends DashConfig<DashCanvasViewSpec, DashCan
|
|
|
29
30
|
* Whether a grid background should be shown. Default false.
|
|
30
31
|
*/
|
|
31
32
|
showGridBackground?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Whether the canvas should accept drag-and-drop of views from outside
|
|
35
|
+
* the canvas. Default false.
|
|
36
|
+
*/
|
|
37
|
+
allowsDrop?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Optional callback to invoke after a view is successfully dropped onto the canvas.
|
|
40
|
+
*/
|
|
41
|
+
onDropDone?: (viewModel: DashCanvasViewModel) => void;
|
|
42
|
+
/**
|
|
43
|
+
* Optional callback to invoke when an item is dragged over the canvas. This may be used to
|
|
44
|
+
* customize how the size of the dropping placeholder is calculated. The callback should
|
|
45
|
+
* return an object with optional properties indicating the desired width, height (in grid units),
|
|
46
|
+
* and offset (in pixels) of the dropping placeholder. The method's signature is the same as
|
|
47
|
+
* the `onDropDragOver` prop of ReactGridLayout.
|
|
48
|
+
* Returning `false` will prevent the dropping placeholder from being shown, and prevents a drop.
|
|
49
|
+
* Returning `void` will use the default behavior, which is to size the placeholder as per the
|
|
50
|
+
* `dropConfig.defaultItem` specification.
|
|
51
|
+
*/
|
|
52
|
+
onDropDragOver?: (e: DragEvent) => OnDropDragOverResult;
|
|
53
|
+
/**
|
|
54
|
+
* Whether an overlay with an Add View button should be rendered
|
|
55
|
+
* when the canvas is empty. Default true.
|
|
56
|
+
*/
|
|
57
|
+
showAddViewButtonWhenEmpty?: boolean;
|
|
32
58
|
}
|
|
33
59
|
export interface DashCanvasItemState {
|
|
34
60
|
layout: DashCanvasItemLayout;
|
|
@@ -42,6 +68,12 @@ export interface DashCanvasItemLayout {
|
|
|
42
68
|
w: number;
|
|
43
69
|
h: number;
|
|
44
70
|
}
|
|
71
|
+
export type OnDropDragOverResult = {
|
|
72
|
+
w?: number;
|
|
73
|
+
h?: number;
|
|
74
|
+
dragOffsetX?: number;
|
|
75
|
+
dragOffsetY?: number;
|
|
76
|
+
} | false | void;
|
|
45
77
|
/**
|
|
46
78
|
* Model for {@link DashCanvas}, managing all configurable options for the component and publishing
|
|
47
79
|
* the observable state of its current widgets and their layout.
|
|
@@ -51,12 +83,17 @@ export declare class DashCanvasModel extends DashModel<DashCanvasViewSpec, DashC
|
|
|
51
83
|
}> {
|
|
52
84
|
columns: number;
|
|
53
85
|
rowHeight: number;
|
|
54
|
-
compact: 'vertical' | 'horizontal';
|
|
86
|
+
compact: 'vertical' | 'horizontal' | 'wrap';
|
|
55
87
|
margin: [number, number];
|
|
56
88
|
containerPadding: [number, number];
|
|
57
89
|
showGridBackground: boolean;
|
|
58
90
|
rglHeight: number;
|
|
91
|
+
showAddViewButtonWhenEmpty: boolean;
|
|
92
|
+
DROPPING_ELEM_ID: string;
|
|
59
93
|
maxRows: number;
|
|
94
|
+
allowsDrop: boolean;
|
|
95
|
+
onDropDone: (viewModel: DashCanvasViewModel) => void;
|
|
96
|
+
draggedInView: DashCanvasItemState;
|
|
60
97
|
/** Current number of rows in canvas */
|
|
61
98
|
get rows(): number;
|
|
62
99
|
get isEmpty(): boolean;
|
|
@@ -65,7 +102,7 @@ export declare class DashCanvasModel extends DashModel<DashCanvasViewSpec, DashC
|
|
|
65
102
|
isResizing: boolean;
|
|
66
103
|
private isLoadingState;
|
|
67
104
|
get rglLayout(): any[];
|
|
68
|
-
constructor({ viewSpecs, viewSpecDefaults, initialState, layoutLocked, contentLocked, renameLocked, persistWith, emptyText, addViewButtonText, columns, rowHeight, compact, margin, maxRows, containerPadding, extraMenuItems, showGridBackground }: DashCanvasConfig);
|
|
105
|
+
constructor({ viewSpecs, viewSpecDefaults, initialState, layoutLocked, contentLocked, renameLocked, persistWith, emptyText, addViewButtonText, columns, rowHeight, compact, margin, maxRows, containerPadding, extraMenuItems, showGridBackground, showAddViewButtonWhenEmpty, allowsDrop, onDropDone, onDropDragOver }: DashCanvasConfig);
|
|
69
106
|
/** Removes all views from the canvas */
|
|
70
107
|
clear(): void;
|
|
71
108
|
/**
|
|
@@ -103,6 +140,10 @@ export declare class DashCanvasModel extends DashModel<DashCanvasViewSpec, DashC
|
|
|
103
140
|
renameView(id: string): void;
|
|
104
141
|
/** Scrolls a DashCanvasView into view. */
|
|
105
142
|
ensureViewVisible(id: string): void;
|
|
143
|
+
onDrop(rglLayout: LayoutItem[], layoutItem: LayoutItem, evt: Event): void;
|
|
144
|
+
setDraggedInView(view?: DashCanvasItemState): void;
|
|
145
|
+
onDropDragOver(evt: DragEvent): OnDropDragOverResult;
|
|
146
|
+
getViewsBySpecId(id: any): DashCanvasViewModel[];
|
|
106
147
|
getPersistableState(): PersistableState<{
|
|
107
148
|
state: DashCanvasItemState[];
|
|
108
149
|
}>;
|
|
@@ -123,6 +164,5 @@ export declare class DashCanvasModel extends DashModel<DashCanvasViewSpec, DashC
|
|
|
123
164
|
private setViewLayout;
|
|
124
165
|
private getSpec;
|
|
125
166
|
private hasSpec;
|
|
126
|
-
private getViewsBySpecId;
|
|
127
167
|
private getNextAvailablePosition;
|
|
128
168
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { HoistProps, TestSupportProps } from '@xh/hoist/core';
|
|
2
|
+
import { DashCanvasModel } from '@xh/hoist/desktop/cmp/dash';
|
|
3
|
+
import './DashCanvasWidgetWell.scss';
|
|
4
|
+
export interface DashCanvasWidgetWellProps extends HoistProps, TestSupportProps {
|
|
5
|
+
/** DashCanvasModel for which this widget well should allow the user to add views from. */
|
|
6
|
+
dashCanvasModel?: DashCanvasModel;
|
|
7
|
+
/** Defaults to `column` */
|
|
8
|
+
flexDirection?: 'row' | 'column';
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Widget Well from which to add items to a DashCanvas by drag-and-drop.
|
|
12
|
+
*
|
|
13
|
+
* Available view specs are listed in their defined order,
|
|
14
|
+
* grouped by their 'groupName' property if present.
|
|
15
|
+
*
|
|
16
|
+
* Typically, an app developer would place this inside a collapsible panel to the side of
|
|
17
|
+
* a DashCanvas.
|
|
18
|
+
*/
|
|
19
|
+
export declare const DashCanvasWidgetWell: import("react").FC<DashCanvasWidgetWellProps>, dashCanvasWidgetWell: import("@xh/hoist/core").ElementFactory<DashCanvasWidgetWellProps>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { DragEvent } from 'react';
|
|
2
|
+
import { DashCanvasModel } from '@xh/hoist/desktop/cmp/dash';
|
|
3
|
+
import { HoistModel } from '@xh/hoist/core';
|
|
4
|
+
import '@xh/hoist/desktop/register';
|
|
5
|
+
export declare class DashCanvasWidgetWellModel extends HoistModel {
|
|
6
|
+
dashCanvasModel: DashCanvasModel;
|
|
7
|
+
constructor();
|
|
8
|
+
onLinked(): void;
|
|
9
|
+
onDragStart(evt: DragEvent<HTMLDivElement>): void;
|
|
10
|
+
onDragEnd(evt: DragEvent<HTMLDivElement>): void;
|
|
11
|
+
}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* See the platform specific AppContainer where these implementations are actually provided.
|
|
10
10
|
*/
|
|
11
|
+
export declare let collapsibleSetButton: any;
|
|
11
12
|
export declare let ColChooserModel: any;
|
|
12
13
|
export declare let ColumnHeaderFilterModel: any;
|
|
13
14
|
export declare let ModalSupportModel: any;
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* See the platform specific AppContainer where these implementations are actually provided.
|
|
10
10
|
*/
|
|
11
|
+
export declare let collapsibleSetButton: any;
|
|
11
12
|
export declare let ColChooserModel: any;
|
|
12
13
|
export declare let colChooser: any;
|
|
13
14
|
export declare let zoneMapper: any;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type ReactElement, type ReactNode, type JSX } from 'react';
|
|
2
|
+
import type { Intent, HoistProps } from '@xh/hoist/core';
|
|
3
|
+
export interface CollapsibleSetButtonProps extends HoistProps {
|
|
4
|
+
icon?: ReactElement;
|
|
5
|
+
text: ReactNode;
|
|
6
|
+
tooltip?: JSX.Element | string;
|
|
7
|
+
clickHandler?: (boolean: any) => void;
|
|
8
|
+
intent?: Intent;
|
|
9
|
+
collapsed?: boolean;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare const CollapsibleSetButton: import("react").FC<CollapsibleSetButtonProps>, collapsibleSetButton: import("@xh/hoist/core").ElementFactory<CollapsibleSetButtonProps>;
|
package/cmp/grid/GridModel.ts
CHANGED
|
@@ -1442,9 +1442,9 @@ export class GridModel extends HoistModel {
|
|
|
1442
1442
|
|
|
1443
1443
|
/**
|
|
1444
1444
|
* Begin an inline editing session.
|
|
1445
|
-
* @param record - StoreRecord/ID to edit. If unspecified, the first selected StoreRecord
|
|
1445
|
+
* @param opts.record - StoreRecord/ID to edit. If unspecified, the first selected StoreRecord
|
|
1446
1446
|
* will be used, if any, or the first overall StoreRecord in the grid.
|
|
1447
|
-
* @param colId - ID of column on which to start editing. If unspecified, the first
|
|
1447
|
+
* @param opts.colId - ID of column on which to start editing. If unspecified, the first
|
|
1448
1448
|
* editable column will be used.
|
|
1449
1449
|
*/
|
|
1450
1450
|
async beginEditAsync(opts: {record?: StoreRecordOrId; colId?: string} = {}) {
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file belongs to Hoist, an application development toolkit
|
|
3
|
+
* developed by Extremely Heavy Industries (www.xh.io | info@xh.io)
|
|
4
|
+
*
|
|
5
|
+
* Copyright © 2026 Extremely Heavy Industries Inc.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
.xh-collapsible-set {
|
|
9
|
+
&--collapsed {
|
|
10
|
+
border-bottom: none;
|
|
11
|
+
border-left: none;
|
|
12
|
+
border-right: none;
|
|
13
|
+
|
|
14
|
+
&--render-mode--always,
|
|
15
|
+
&--render-mode--lazy {
|
|
16
|
+
> *:not(:first-child) {
|
|
17
|
+
display: none;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
&.xh-collapsible-set--intent-primary {
|
|
23
|
+
border-color: var(--xh-intent-primary-trans2);
|
|
24
|
+
&.xh-collapsible-set--enabled {
|
|
25
|
+
border-color: var(--xh-intent-primary);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
&.xh-collapsible-set--intent-success {
|
|
30
|
+
border-color: var(--xh-intent-success-trans2);
|
|
31
|
+
&.xh-collapsible-set--enabled {
|
|
32
|
+
border-color: var(--xh-intent-success);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
&.xh-collapsible-set--intent-warning {
|
|
37
|
+
border-color: var(--xh-intent-warning-trans2);
|
|
38
|
+
&.xh-collapsible-set--enabled {
|
|
39
|
+
border-color: var(--xh-intent-warning);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
&.xh-collapsible-set--intent-danger {
|
|
44
|
+
border-color: var(--xh-intent-danger-trans2);
|
|
45
|
+
&.xh-collapsible-set--enabled {
|
|
46
|
+
border-color: var(--xh-intent-danger);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file belongs to Hoist, an application development toolkit
|
|
3
|
+
* developed by Extremely Heavy Industries (www.xh.io | info@xh.io)
|
|
4
|
+
*
|
|
5
|
+
* Copyright © 2026 Extremely Heavy Industries Inc.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import classNames from 'classnames';
|
|
9
|
+
import {castArray} from 'lodash';
|
|
10
|
+
import {type FieldsetHTMLAttributes, type ReactElement, type ReactNode, useState} from 'react';
|
|
11
|
+
import {XH, hoistCmp} from '@xh/hoist/core';
|
|
12
|
+
import type {HoistProps, Intent, LayoutProps, RenderMode, TestSupportProps} from '@xh/hoist/core';
|
|
13
|
+
import {fieldset} from '@xh/hoist/cmp/layout';
|
|
14
|
+
import {TEST_ID, mergeDeep} from '@xh/hoist/utils/js';
|
|
15
|
+
import {splitLayoutProps} from '@xh/hoist/utils/react';
|
|
16
|
+
import {collapsibleSetButton as desktopCollapsibleSetButtonImpl} from '@xh/hoist/dynamics/desktop';
|
|
17
|
+
import {collapsibleSetButton as mobileCollapsibleSetButtonImpl} from '@xh/hoist/dynamics/mobile';
|
|
18
|
+
|
|
19
|
+
import './CollapsibleSet.scss';
|
|
20
|
+
|
|
21
|
+
export interface CollapsibleSetProps
|
|
22
|
+
extends FieldsetHTMLAttributes<HTMLFieldSetElement>, HoistProps, TestSupportProps, LayoutProps {
|
|
23
|
+
icon?: ReactElement;
|
|
24
|
+
label: ReactNode;
|
|
25
|
+
tooltip?: JSX.Element | string;
|
|
26
|
+
intent?: Intent;
|
|
27
|
+
clickHandler?: () => void;
|
|
28
|
+
collapsed?: boolean;
|
|
29
|
+
hideItemCount?: boolean;
|
|
30
|
+
renderMode?: RenderMode;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const [CollapsibleSet, collapsibleSet] = hoistCmp.withFactory<CollapsibleSetProps>({
|
|
34
|
+
displayName: 'CollapsibleSet',
|
|
35
|
+
model: false,
|
|
36
|
+
className: 'xh-collapsible-set',
|
|
37
|
+
render({
|
|
38
|
+
icon,
|
|
39
|
+
label,
|
|
40
|
+
tooltip,
|
|
41
|
+
intent,
|
|
42
|
+
collapsed,
|
|
43
|
+
children,
|
|
44
|
+
hideItemCount,
|
|
45
|
+
className,
|
|
46
|
+
disabled,
|
|
47
|
+
display = 'flex',
|
|
48
|
+
flexDirection = 'column',
|
|
49
|
+
flexWrap = 'wrap',
|
|
50
|
+
renderMode = 'unmountOnHide',
|
|
51
|
+
...rest
|
|
52
|
+
}) {
|
|
53
|
+
// Note `model` destructured off of non-layout props to avoid setting
|
|
54
|
+
// model as a bogus DOM attribute. This low-level component may easily be passed one from
|
|
55
|
+
// a parent that has not properly managed its own props.
|
|
56
|
+
let [layoutProps, {model, testId, ...restProps}] = splitLayoutProps(rest);
|
|
57
|
+
|
|
58
|
+
restProps = mergeDeep(
|
|
59
|
+
{style: {display, flexDirection, flexWrap, ...layoutProps}},
|
|
60
|
+
{[TEST_ID]: testId},
|
|
61
|
+
restProps
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const [isCollapsed, setIsCollapsed] = useState<boolean>(collapsed === true),
|
|
65
|
+
[expandCount, setExpandCount] = useState<number>(!collapsed ? 1 : 0),
|
|
66
|
+
items = castArray(children),
|
|
67
|
+
itemCount = hideItemCount === true ? '' : ` (${items.length})`,
|
|
68
|
+
classes = [];
|
|
69
|
+
|
|
70
|
+
if (isCollapsed) {
|
|
71
|
+
classes.push('xh-collapsible-set--collapsed');
|
|
72
|
+
} else {
|
|
73
|
+
classes.push('xh-collapsible-set--expanded');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (disabled) {
|
|
77
|
+
classes.push('xh-collapsible-set--disabled');
|
|
78
|
+
} else {
|
|
79
|
+
classes.push('xh-collapsible-set--enabled');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (intent) {
|
|
83
|
+
classes.push(`xh-collapsible-set--intent-${intent}`);
|
|
84
|
+
} else {
|
|
85
|
+
classes.push(`xh-collapsible-set--intent-none`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const btn = XH.isMobileApp
|
|
89
|
+
? mobileCollapsibleSetButtonImpl
|
|
90
|
+
: desktopCollapsibleSetButtonImpl;
|
|
91
|
+
|
|
92
|
+
let content;
|
|
93
|
+
switch (renderMode) {
|
|
94
|
+
case 'always':
|
|
95
|
+
content = items;
|
|
96
|
+
if (isCollapsed) {
|
|
97
|
+
classes.push('xh-collapsible-set--collapsed--render-mode--always');
|
|
98
|
+
}
|
|
99
|
+
break;
|
|
100
|
+
|
|
101
|
+
case 'lazy':
|
|
102
|
+
content = isCollapsed && !expandCount ? [] : items;
|
|
103
|
+
if (isCollapsed) {
|
|
104
|
+
classes.push('xh-collapsible-set--collapsed--render-mode--lazy');
|
|
105
|
+
}
|
|
106
|
+
break;
|
|
107
|
+
|
|
108
|
+
// unmountOnHide
|
|
109
|
+
default:
|
|
110
|
+
content = isCollapsed ? [] : items;
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return fieldset({
|
|
115
|
+
className: classNames(className, classes),
|
|
116
|
+
items: [
|
|
117
|
+
btn({
|
|
118
|
+
icon,
|
|
119
|
+
text: `${label}${itemCount}`,
|
|
120
|
+
tooltip,
|
|
121
|
+
intent,
|
|
122
|
+
clickHandler: val => {
|
|
123
|
+
setIsCollapsed(val);
|
|
124
|
+
setExpandCount(expandCount + 1);
|
|
125
|
+
},
|
|
126
|
+
collapsed: isCollapsed,
|
|
127
|
+
disabled
|
|
128
|
+
}),
|
|
129
|
+
...content
|
|
130
|
+
],
|
|
131
|
+
disabled,
|
|
132
|
+
...restProps
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
});
|
package/cmp/layout/Tags.ts
CHANGED
|
@@ -29,6 +29,7 @@ export const a = elementFactory('a');
|
|
|
29
29
|
export const br = elementFactory('br');
|
|
30
30
|
export const code = elementFactory('code');
|
|
31
31
|
export const div = elementFactory('div');
|
|
32
|
+
export const fieldset = elementFactory('fieldset');
|
|
32
33
|
export const form = elementFactory('form');
|
|
33
34
|
export const hr = elementFactory('hr');
|
|
34
35
|
export const h1 = elementFactory('h1');
|
|
@@ -36,6 +37,7 @@ export const h2 = elementFactory('h2');
|
|
|
36
37
|
export const h3 = elementFactory('h3');
|
|
37
38
|
export const h4 = elementFactory('h4');
|
|
38
39
|
export const label = elementFactory('label');
|
|
40
|
+
export const legend = elementFactory('legend');
|
|
39
41
|
export const li = elementFactory('li');
|
|
40
42
|
export const nav = elementFactory('nav');
|
|
41
43
|
export const ol = elementFactory('ol');
|
|
@@ -123,7 +123,7 @@ export class TabContainerModel extends HoistModel {
|
|
|
123
123
|
|
|
124
124
|
/**
|
|
125
125
|
* @param config - TabContainer configuration.
|
|
126
|
-
* @param
|
|
126
|
+
* @param depth - Depth in hierarchy of nested TabContainerModels. Not for application use.
|
|
127
127
|
*/
|
|
128
128
|
constructor(
|
|
129
129
|
{
|
package/core/enums/RenderMode.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Options for how contents should be rendered by their parent container.
|
|
10
|
-
* Used by {@link TabContainerModel}, {@link DashContainerModel}, and {@link
|
|
10
|
+
* Used by {@link TabContainerModel}, {@link DashContainerModel}, {@link PanelModel}, and {@link CollapsibleSet}.
|
|
11
11
|
*/
|
|
12
12
|
export const RenderMode = Object.freeze({
|
|
13
13
|
/** Always render contents when the parent container is rendered, even if inactive. */
|
|
@@ -27,6 +27,7 @@ import {zoneMapperDialog as zoneMapper} from '@xh/hoist/desktop/cmp/zoneGrid/imp
|
|
|
27
27
|
import {useContextMenu, useHotkeys} from '@xh/hoist/desktop/hooks';
|
|
28
28
|
import {DynamicTabSwitcherModel, installDesktopImpls} from '@xh/hoist/dynamics/desktop';
|
|
29
29
|
import {inspectorPanel} from '@xh/hoist/inspector/InspectorPanel';
|
|
30
|
+
import {collapsibleSetButton} from '@xh/hoist/desktop/cmp/button/CollapsibleSetButton';
|
|
30
31
|
import {blueprintProvider} from '@xh/hoist/kit/blueprint';
|
|
31
32
|
import {consumeEvent} from '@xh/hoist/utils/js';
|
|
32
33
|
import {elementFromContent, useOnMount} from '@xh/hoist/utils/react';
|
|
@@ -46,6 +47,7 @@ import {toastSource} from './ToastSource';
|
|
|
46
47
|
import {versionBar} from './VersionBar';
|
|
47
48
|
|
|
48
49
|
installDesktopImpls({
|
|
50
|
+
collapsibleSetButton,
|
|
49
51
|
tabContainerImpl,
|
|
50
52
|
dockContainerImpl,
|
|
51
53
|
storeFilterFieldImpl,
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file belongs to Hoist, an application development toolkit
|
|
3
|
+
* developed by Extremely Heavy Industries (www.xh.io | info@xh.io)
|
|
4
|
+
*
|
|
5
|
+
* Copyright © 2026 Extremely Heavy Industries Inc.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {type ReactElement, type ReactNode, type JSX, useState} from 'react';
|
|
9
|
+
import {tooltip as bpTooltip} from '@xh/hoist/kit/blueprint';
|
|
10
|
+
import {hoistCmp} from '@xh/hoist/core';
|
|
11
|
+
import type {Intent, HoistProps} from '@xh/hoist/core';
|
|
12
|
+
import {button} from '@xh/hoist/desktop/cmp/button';
|
|
13
|
+
import {legend} from '@xh/hoist/cmp/layout';
|
|
14
|
+
import {Icon} from '@xh/hoist/icon/Icon';
|
|
15
|
+
|
|
16
|
+
export interface CollapsibleSetButtonProps extends HoistProps {
|
|
17
|
+
icon?: ReactElement;
|
|
18
|
+
text: ReactNode;
|
|
19
|
+
tooltip?: JSX.Element | string;
|
|
20
|
+
clickHandler?: (boolean) => void;
|
|
21
|
+
intent?: Intent;
|
|
22
|
+
collapsed?: boolean;
|
|
23
|
+
disabled?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const [CollapsibleSetButton, collapsibleSetButton] =
|
|
27
|
+
hoistCmp.withFactory<CollapsibleSetButtonProps>({
|
|
28
|
+
displayName: 'CollapsibleSetButton',
|
|
29
|
+
model: false,
|
|
30
|
+
render({icon, text, tooltip, intent, clickHandler, collapsed, disabled}) {
|
|
31
|
+
const [isCollapsed, setIsCollapsed] = useState<boolean>(collapsed === true),
|
|
32
|
+
btn = button({
|
|
33
|
+
text,
|
|
34
|
+
icon,
|
|
35
|
+
rightIcon: isCollapsed ? Icon.angleDown() : Icon.angleUp(),
|
|
36
|
+
outlined: isCollapsed && !intent,
|
|
37
|
+
minimal: !intent || (intent && !isCollapsed),
|
|
38
|
+
intent,
|
|
39
|
+
disabled,
|
|
40
|
+
onClick: () => {
|
|
41
|
+
const val = !isCollapsed;
|
|
42
|
+
setIsCollapsed(val);
|
|
43
|
+
clickHandler?.(val);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
return legend(
|
|
48
|
+
tooltip
|
|
49
|
+
? bpTooltip({
|
|
50
|
+
item: btn,
|
|
51
|
+
content: tooltip,
|
|
52
|
+
intent
|
|
53
|
+
})
|
|
54
|
+
: btn
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
@@ -10,7 +10,7 @@ import ReactGridLayout, {
|
|
|
10
10
|
useContainerWidth,
|
|
11
11
|
getCompactor
|
|
12
12
|
} from 'react-grid-layout';
|
|
13
|
-
import {GridBackground, type GridBackgroundProps} from 'react-grid-layout/extras';
|
|
13
|
+
import {GridBackground, type GridBackgroundProps, wrapCompactor} from 'react-grid-layout/extras';
|
|
14
14
|
import composeRefs from '@seznam/compose-react-refs';
|
|
15
15
|
import {div, vbox, vspacer} from '@xh/hoist/cmp/layout';
|
|
16
16
|
import {
|
|
@@ -62,7 +62,11 @@ export const [DashCanvas, dashCanvas] = hoistCmp.withFactory<DashCanvasProps>({
|
|
|
62
62
|
render({className, model, rglOptions, testId}, ref) {
|
|
63
63
|
const isDraggable = !model.layoutLocked,
|
|
64
64
|
isResizable = !model.layoutLocked,
|
|
65
|
-
{width, containerRef, mounted} = useContainerWidth()
|
|
65
|
+
{width, containerRef, mounted} = useContainerWidth(),
|
|
66
|
+
defaultDroppedItemDims = {
|
|
67
|
+
w: Math.floor(model.columns / 3),
|
|
68
|
+
h: Math.floor(model.columns / 3)
|
|
69
|
+
};
|
|
66
70
|
|
|
67
71
|
return refreshContextView({
|
|
68
72
|
model: model.refreshContextModel,
|
|
@@ -98,7 +102,20 @@ export const [DashCanvas, dashCanvas] = hoistCmp.withFactory<DashCanvasProps>({
|
|
|
98
102
|
resizeConfig: {
|
|
99
103
|
enabled: isResizable
|
|
100
104
|
},
|
|
101
|
-
|
|
105
|
+
dropConfig: {
|
|
106
|
+
enabled: model.contentLocked ? false : model.allowsDrop,
|
|
107
|
+
defaultItem: defaultDroppedItemDims,
|
|
108
|
+
onDragOver: (evt: DragEvent) => model.onDropDragOver(evt)
|
|
109
|
+
},
|
|
110
|
+
onDrop: (
|
|
111
|
+
layout: LayoutItem[],
|
|
112
|
+
layoutItem: LayoutItem,
|
|
113
|
+
evt: Event
|
|
114
|
+
) => model.onDrop(layout, layoutItem, evt),
|
|
115
|
+
compactor:
|
|
116
|
+
model.compact === 'wrap'
|
|
117
|
+
? wrapCompactor
|
|
118
|
+
: getCompactor(model.compact, false, false),
|
|
102
119
|
onLayoutChange: (layout: LayoutItem[]) =>
|
|
103
120
|
model.onRglLayoutChange(layout),
|
|
104
121
|
onResizeStart: () => (model.isResizing = true),
|
|
@@ -116,7 +133,7 @@ export const [DashCanvas, dashCanvas] = hoistCmp.withFactory<DashCanvasProps>({
|
|
|
116
133
|
),
|
|
117
134
|
width
|
|
118
135
|
}),
|
|
119
|
-
emptyContainerOverlay({omit: !mounted})
|
|
136
|
+
emptyContainerOverlay({omit: !mounted || !model.showAddViewButtonWhenEmpty})
|
|
120
137
|
],
|
|
121
138
|
[TEST_ID]: testId
|
|
122
139
|
})
|