@xh/hoist 78.0.0-SNAPSHOT.1762979452848 → 78.0.0-SNAPSHOT.1763398473242

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 CHANGED
@@ -2,17 +2,24 @@
2
2
 
3
3
  ## 78.0.0-SNAPSHOT - unreleased
4
4
 
5
+ ### ⚙️ Technical
6
+
7
+ * `FetchService` will recognize variants on the `application/json` content-type when processing
8
+ failed responses and decoding exceptions - e.g. `application/problem+json`.
9
+
5
10
  ## 77.1.1 - 2025-11-12
6
11
 
7
12
  ### 🎁 New Features
8
- * New method `StoreRecord.getModifiedValues()` to gather edited data from a store record.
13
+
14
+ * New method `StoreRecord.getModifiedValues()` to gather edited data from a store record.
9
15
 
10
16
  ### 🐞 Bug Fixes
11
- * StoreRecord will no longer report `isModified` as `true` if a field has been edited and
12
- then returned to its original value in a subsequent edit.
13
- * Restore support for `TabModel.content` being nullable to support dynamic tab content.
14
- * Remove stray context menu from appearing when clicking on column group headers and other grid
15
- empty space.
17
+
18
+ * StoreRecord will no longer report `isModified` as `true` if a field has been edited and
19
+ then returned to its original value in a subsequent edit.
20
+ * Restore support for `TabModel.content` being nullable to support dynamic tab content.
21
+ * Remove stray context menu from appearing when clicking on column group headers and other grid
22
+ empty space.
16
23
 
17
24
  ## 77.0.1 - 2025-10-29
18
25
 
@@ -42,7 +49,6 @@
42
49
 
43
50
  * Support Grails 7 service name conventions in admin client (backward compatible)
44
51
 
45
-
46
52
  ## 76.2.0 - 2025-10-22
47
53
 
48
54
  ### ⚙️ Technical
@@ -20,6 +20,11 @@ export interface DashCanvasConfig extends DashConfig<DashCanvasViewSpec, DashCan
20
20
  maxRows?: number;
21
21
  /** Padding inside the container [x, y] in pixels. Defaults to same as `margin`. */
22
22
  containerPadding?: [number, number];
23
+ /**
24
+ * Whether an overlay with an Add View button should be rendered
25
+ * when the canvas is empty. Default true.
26
+ */
27
+ showAddViewButtonWhenEmpty?: boolean;
23
28
  }
24
29
  export interface DashCanvasItemState {
25
30
  layout: DashCanvasItemLayout;
@@ -45,7 +50,9 @@ export declare class DashCanvasModel extends DashModel<DashCanvasViewSpec, DashC
45
50
  compact: boolean;
46
51
  margin: [number, number];
47
52
  containerPadding: [number, number];
53
+ DROPPING_ELEM_ID: string;
48
54
  maxRows: number;
55
+ showAddViewButtonWhenEmpty: boolean;
49
56
  /** Current number of rows in canvas */
50
57
  get rows(): number;
51
58
  get isEmpty(): boolean;
@@ -54,7 +61,7 @@ export declare class DashCanvasModel extends DashModel<DashCanvasViewSpec, DashC
54
61
  isResizing: boolean;
55
62
  private isLoadingState;
56
63
  get rglLayout(): any[];
57
- constructor({ viewSpecs, viewSpecDefaults, initialState, layoutLocked, contentLocked, renameLocked, persistWith, emptyText, addViewButtonText, columns, rowHeight, compact, margin, maxRows, containerPadding, extraMenuItems }: DashCanvasConfig);
64
+ constructor({ viewSpecs, viewSpecDefaults, initialState, layoutLocked, contentLocked, renameLocked, persistWith, emptyText, addViewButtonText, columns, rowHeight, compact, margin, maxRows, containerPadding, extraMenuItems, showAddViewButtonWhenEmpty }: DashCanvasConfig);
58
65
  /** Removes all views from the canvas */
59
66
  clear(): void;
60
67
  /**
@@ -77,6 +84,16 @@ export declare class DashCanvasModel extends DashModel<DashCanvasViewSpec, DashC
77
84
  width?: number;
78
85
  height?: number;
79
86
  }): DashCanvasViewModel;
87
+ dropViewIntoCanvas(specId: string, opts: {
88
+ title: string;
89
+ state: any;
90
+ layout: {
91
+ x: number;
92
+ y: number;
93
+ w: number;
94
+ h: number;
95
+ };
96
+ }, rglLayout: any[]): DashCanvasViewModel;
80
97
  /**
81
98
  * Remove a view from the DashCanvas
82
99
  * @param id - DashCanvasViewModel id to remove from the container
@@ -25,6 +25,11 @@ import { IStringifyOptions } from 'qs';
25
25
  export declare class FetchService extends HoistService {
26
26
  static instance: FetchService;
27
27
  NO_JSON_RESPONSES: StatusCodes[];
28
+ /**
29
+ * Regex applied during failed response handling to determine if contentType indicates JSON.
30
+ * Matches `application/json` as well as variants such as `application/problem+json`
31
+ */
32
+ JSON_CONTENT_TYPE_RE: RegExp;
28
33
  private idGenerator;
29
34
  private autoAborters;
30
35
  private _defaultHeaders;
@@ -98,7 +98,7 @@ export const [DashCanvas, dashCanvas] = hoistCmp.withFactory<DashCanvasProps>({
98
98
  ),
99
99
  ...rglOptions
100
100
  }),
101
- emptyContainerOverlay()
101
+ emptyContainerOverlay({omit: !model.showAddViewButtonWhenEmpty})
102
102
  ],
103
103
  [TEST_ID]: testId
104
104
  })
@@ -50,6 +50,12 @@ export interface DashCanvasConfig extends DashConfig<DashCanvasViewSpec, DashCan
50
50
 
51
51
  /** Padding inside the container [x, y] in pixels. Defaults to same as `margin`. */
52
52
  containerPadding?: [number, number];
53
+
54
+ /**
55
+ * Whether an overlay with an Add View button should be rendered
56
+ * when the canvas is empty. Default true.
57
+ */
58
+ showAddViewButtonWhenEmpty?: boolean;
53
59
  }
54
60
 
55
61
  export interface DashCanvasItemState {
@@ -86,7 +92,9 @@ export class DashCanvasModel
86
92
  //-----------------------------
87
93
  // Public properties
88
94
  //-----------------------------
95
+ DROPPING_ELEM_ID = '__dropping-elem__';
89
96
  maxRows: number;
97
+ showAddViewButtonWhenEmpty: boolean;
90
98
 
91
99
  /** Current number of rows in canvas */
92
100
  get rows(): number {
@@ -106,21 +114,25 @@ export class DashCanvasModel
106
114
  private isLoadingState: boolean;
107
115
 
108
116
  get rglLayout() {
109
- return this.layout.map(it => {
110
- const dashCanvasView = this.getView(it.i),
111
- {autoHeight, viewSpec} = dashCanvasView;
112
-
113
- return {
114
- ...it,
115
- resizeHandles: autoHeight
116
- ? ['w', 'e']
117
- : ['s', 'w', 'e', 'n', 'sw', 'nw', 'se', 'ne'],
118
- maxH: viewSpec.maxHeight,
119
- minH: viewSpec.minHeight,
120
- maxW: viewSpec.maxWidth,
121
- minW: viewSpec.minWidth
122
- };
123
- });
117
+ return this.layout
118
+ .map(it => {
119
+ const dashCanvasView = this.getView(it.i);
120
+ if (!dashCanvasView) return null;
121
+
122
+ const {autoHeight, viewSpec} = dashCanvasView;
123
+
124
+ return {
125
+ ...it,
126
+ resizeHandles: autoHeight
127
+ ? ['w', 'e']
128
+ : ['s', 'w', 'e', 'n', 'sw', 'nw', 'se', 'ne'],
129
+ maxH: viewSpec.maxHeight,
130
+ minH: viewSpec.minHeight,
131
+ maxW: viewSpec.maxWidth,
132
+ minW: viewSpec.minWidth
133
+ };
134
+ })
135
+ .filter(Boolean);
124
136
  }
125
137
 
126
138
  constructor({
@@ -139,7 +151,8 @@ export class DashCanvasModel
139
151
  margin = [10, 10],
140
152
  maxRows = Infinity,
141
153
  containerPadding = margin,
142
- extraMenuItems
154
+ extraMenuItems,
155
+ showAddViewButtonWhenEmpty = true
143
156
  }: DashCanvasConfig) {
144
157
  super();
145
158
  makeObservable(this);
@@ -187,6 +200,7 @@ export class DashCanvasModel
187
200
  this.emptyText = emptyText;
188
201
  this.addViewButtonText = addViewButtonText;
189
202
  this.extraMenuItems = extraMenuItems;
203
+ this.showAddViewButtonWhenEmpty = showAddViewButtonWhenEmpty;
190
204
 
191
205
  this.loadState(initialState);
192
206
  this.state = this.buildState();
@@ -261,6 +275,27 @@ export class DashCanvasModel
261
275
  return this.addViewInternal(specId, {title, layout, state});
262
276
  }
263
277
 
278
+ @action dropViewIntoCanvas(
279
+ specId: string,
280
+ opts: {
281
+ title: string;
282
+ state: any;
283
+ layout: {
284
+ x: number;
285
+ y: number;
286
+ w: number;
287
+ h: number;
288
+ };
289
+ },
290
+ rglLayout: any[]
291
+ ): DashCanvasViewModel {
292
+ const newViewModel: DashCanvasViewModel = this.addViewInternal(specId, opts),
293
+ droppingItem: any = rglLayout.find(it => it.i === this.DROPPING_ELEM_ID);
294
+ droppingItem.i = newViewModel.id;
295
+ this.onRglLayoutChange(rglLayout);
296
+ return newViewModel;
297
+ }
298
+
264
299
  /**
265
300
  * Remove a view from the DashCanvas
266
301
  * @param id - DashCanvasViewModel id to remove from the container
@@ -386,6 +421,8 @@ export class DashCanvasModel
386
421
 
387
422
  onRglLayoutChange(rglLayout) {
388
423
  rglLayout = rglLayout.map(it => pick(it, ['i', 'x', 'y', 'w', 'h']));
424
+ if (rglLayout.some(it => it.i === this.DROPPING_ELEM_ID)) return;
425
+
389
426
  this.setLayout(rglLayout);
390
427
  }
391
428
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xh/hoist",
3
- "version": "78.0.0-SNAPSHOT.1762979452848",
3
+ "version": "78.0.0-SNAPSHOT.1763398473242",
4
4
  "description": "Hoist add-on for building and deploying React Applications.",
5
5
  "repository": "github:xh/hoist-react",
6
6
  "homepage": "https://xh.io",
@@ -38,6 +38,12 @@ export class FetchService extends HoistService {
38
38
 
39
39
  NO_JSON_RESPONSES = [StatusCodes.NO_CONTENT, StatusCodes.RESET_CONTENT];
40
40
 
41
+ /**
42
+ * Regex applied during failed response handling to determine if contentType indicates JSON.
43
+ * Matches `application/json` as well as variants such as `application/problem+json`
44
+ */
45
+ JSON_CONTENT_TYPE_RE = /application\/[^+]*[+]?(json);?.*/i;
46
+
41
47
  private idGenerator = new ShortUniqueId({length: 16});
42
48
  private autoAborters = {};
43
49
  private _defaultHeaders: DefaultHeaders[] = [];
@@ -410,10 +416,9 @@ export class FetchService extends HoistService {
410
416
  });
411
417
  }
412
418
 
413
- // Try to "smart" decode as server provided JSON Exception (with a name)
419
+ // Attempt to decode server-provided exception if returned as JSON.
414
420
  try {
415
- const cType = headers.get('Content-Type');
416
- if (cType?.includes('application/json')) {
421
+ if (headers.get('Content-Type')?.match(this.JSON_CONTENT_TYPE_RE)) {
417
422
  const parsedResp = this.safeParseJson(responseText);
418
423
  return this.createException({
419
424
  ...defaults,