@xh/hoist 86.1.0 → 86.3.0

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.
Files changed (104) hide show
  1. package/.gitattributes +4 -0
  2. package/CHANGELOG.md +104 -0
  3. package/admin/AdminJsonDisplay.ts +31 -0
  4. package/admin/App.scss +3 -11
  5. package/admin/AppModel.ts +3 -4
  6. package/admin/jsonsearch/impl/JsonSearchImplModel.ts +4 -4
  7. package/admin/tabs/activity/tracking/ActivityTrackingModel.ts +56 -6
  8. package/admin/tabs/activity/tracking/ActivityTrackingPanel.ts +9 -26
  9. package/admin/tabs/clients/ClientsModel.ts +4 -1
  10. package/admin/tabs/clients/activity/ClientDetailModel.ts +5 -1
  11. package/admin/tabs/cluster/instances/connpool/ConnPoolMonitorPanel.ts +3 -11
  12. package/admin/tabs/cluster/instances/services/DetailsPanel.ts +2 -13
  13. package/admin/tabs/cluster/objects/DetailPanel.ts +3 -12
  14. package/admin/tabs/userData/roles/RolePanel.ts +2 -2
  15. package/appcontainer/RouterModel.ts +6 -6
  16. package/build/types/admin/AdminJsonDisplay.d.ts +7 -0
  17. package/build/types/admin/AppModel.d.ts +3 -4
  18. package/build/types/admin/jsonsearch/impl/JsonSearchImplModel.d.ts +2 -2
  19. package/build/types/admin/tabs/activity/tracking/ActivityTrackingModel.d.ts +14 -3
  20. package/build/types/admin/tabs/clients/ClientsModel.d.ts +3 -0
  21. package/build/types/appcontainer/RouterModel.d.ts +5 -5
  22. package/build/types/cmp/grid/Grid.d.ts +8 -4
  23. package/build/types/cmp/grid/GridModel.d.ts +13 -2
  24. package/build/types/cmp/grid/Types.d.ts +8 -1
  25. package/build/types/cmp/grid/filter/GridFilterFieldSpec.d.ts +24 -6
  26. package/build/types/cmp/input/SegmentedControlOption.d.ts +12 -0
  27. package/build/types/core/HoistAppModel.d.ts +2 -3
  28. package/build/types/core/types/Types.d.ts +12 -0
  29. package/build/types/desktop/cmp/button/grid/ExpandToLevelButton.d.ts +2 -1
  30. package/build/types/desktop/cmp/filter/FilterChooser.d.ts +5 -0
  31. package/build/types/desktop/cmp/filter/PopoverFilterChooser.d.ts +3 -1
  32. package/build/types/desktop/cmp/grid/impl/colchooser/ColChooserModel.d.ts +2 -2
  33. package/build/types/desktop/cmp/grid/impl/filter/headerfilter/custom/CustomRowModel.d.ts +9 -1
  34. package/build/types/desktop/cmp/grid/impl/filter/headerfilter/custom/CustomTabModel.d.ts +8 -2
  35. package/build/types/desktop/cmp/input/CodeInput.d.ts +7 -0
  36. package/build/types/desktop/cmp/input/NumberInput.d.ts +1 -1
  37. package/build/types/desktop/cmp/input/Select.d.ts +8 -0
  38. package/build/types/desktop/cmp/input/impl/CalcWindowedMenuWidth.d.ts +20 -0
  39. package/build/types/desktop/cmp/rest/impl/RestFormModel.d.ts +1 -1
  40. package/build/types/format/FormatNumber.d.ts +11 -3
  41. package/build/types/icon/Icon.d.ts +4 -3
  42. package/build/types/kit/react-dropzone/index.d.ts +1 -0
  43. package/build/types/kit/swiper/index.d.ts +4 -3
  44. package/build/types/mobile/cmp/button/grid/ExpandToLevelButton.d.ts +2 -1
  45. package/build/types/mobile/cmp/grid/impl/ColChooserModel.d.ts +2 -2
  46. package/build/types/mobile/cmp/input/NumberInput.d.ts +1 -1
  47. package/build/types/mobile/cmp/input/Select.d.ts +8 -0
  48. package/cmp/grid/Grid.ts +27 -2
  49. package/cmp/grid/GridModel.ts +21 -8
  50. package/cmp/grid/Types.ts +9 -1
  51. package/cmp/grid/filter/GridFilterFieldSpec.ts +28 -5
  52. package/cmp/grid/impl/MenuSupport.ts +3 -5
  53. package/cmp/input/SegmentedControlOption.ts +14 -0
  54. package/cmp/store/impl/StoreFilterFieldImplModel.ts +5 -6
  55. package/core/HoistAppModel.ts +2 -3
  56. package/core/types/Types.ts +14 -5
  57. package/desktop/cmp/button/grid/ExpandToLevelButton.ts +4 -5
  58. package/desktop/cmp/filechooser/FileChooser.ts +6 -3
  59. package/desktop/cmp/filechooser/FileChooserModel.ts +12 -5
  60. package/desktop/cmp/filter/FilterChooser.scss +41 -0
  61. package/desktop/cmp/filter/FilterChooser.ts +147 -7
  62. package/desktop/cmp/filter/PopoverFilterChooser.ts +7 -84
  63. package/desktop/cmp/grid/editors/NumberEditor.ts +16 -17
  64. package/desktop/cmp/grid/editors/SelectEditor.ts +15 -10
  65. package/desktop/cmp/grid/editors/impl/InlineEditorModel.ts +6 -1
  66. package/desktop/cmp/grid/find/impl/GridFindFieldImplModel.ts +3 -5
  67. package/desktop/cmp/grid/impl/colchooser/ColChooserModel.ts +2 -2
  68. package/desktop/cmp/grid/impl/filter/GridFilterDialog.ts +2 -2
  69. package/desktop/cmp/grid/impl/filter/headerfilter/custom/CustomRow.ts +12 -12
  70. package/desktop/cmp/grid/impl/filter/headerfilter/custom/CustomRowModel.ts +18 -0
  71. package/desktop/cmp/grid/impl/filter/headerfilter/custom/CustomTab.scss +5 -0
  72. package/desktop/cmp/grid/impl/filter/headerfilter/custom/CustomTab.ts +13 -2
  73. package/desktop/cmp/grid/impl/filter/headerfilter/custom/CustomTabModel.ts +97 -14
  74. package/desktop/cmp/grid/impl/filter/headerfilter/values/ValuesTabModel.ts +46 -7
  75. package/desktop/cmp/input/CheckboxButton.ts +21 -1
  76. package/desktop/cmp/input/CodeInput.ts +20 -3
  77. package/desktop/cmp/input/NumberInput.ts +1 -1
  78. package/desktop/cmp/input/SegmentedControl.ts +7 -3
  79. package/desktop/cmp/input/Select.ts +37 -18
  80. package/desktop/cmp/input/impl/CalcWindowedMenuWidth.ts +106 -0
  81. package/desktop/cmp/rest/impl/RestFormField.ts +1 -1
  82. package/desktop/cmp/tab/Tabs.scss +72 -25
  83. package/docs/routing.md +4 -2
  84. package/docs/upgrade-notes/v86-upgrade-notes.md +73 -8
  85. package/format/FormatNumber.ts +69 -32
  86. package/icon/Icon.scss +13 -0
  87. package/icon/Icon.ts +4 -3
  88. package/icon/impl/IconHtml.ts +1 -1
  89. package/kit/react-dropzone/index.ts +1 -0
  90. package/mcp/README.md +12 -0
  91. package/mcp/data/ts-registry.spec.ts +156 -0
  92. package/mcp/data/ts-registry.ts +14 -7
  93. package/mcp/formatters/typescript.ts +7 -3
  94. package/mcp/util/paths.ts +21 -0
  95. package/mobile/cmp/button/grid/ExpandToLevelButton.ts +4 -5
  96. package/mobile/cmp/grid/impl/ColChooserModel.ts +2 -2
  97. package/mobile/cmp/input/CheckboxButton.ts +18 -1
  98. package/mobile/cmp/input/NumberInput.ts +1 -1
  99. package/mobile/cmp/input/SegmentedControl.ts +7 -3
  100. package/mobile/cmp/input/Select.ts +21 -15
  101. package/mobx/README.md +2 -1
  102. package/package.json +10 -10
  103. package/styles/vars.scss +15 -0
  104. package/desktop/cmp/filter/PopoverFilterChooser.scss +0 -42
@@ -24,7 +24,7 @@ import type {
24
24
  import {resolve} from 'node:path';
25
25
 
26
26
  import {log} from '../util/logger.js';
27
- import {resolveRepoRoot} from '../util/paths.js';
27
+ import {resolveRepoRoot, resolveRepoRootPosix, toPosixPath} from '../util/paths.js';
28
28
  import {computeFingerprint, loadCache, writeCache} from './index-cache.js';
29
29
 
30
30
  //------------------------------------------------------------------
@@ -295,7 +295,9 @@ function buildSymbolIndex(proj: Project): {
295
295
  } {
296
296
  const index = new Map<string, SymbolEntry[]>();
297
297
  const mIndex = new Map<string, MemberIndexEntry[]>();
298
- const repoRoot = resolveRepoRoot();
298
+ // POSIX form: compared against ts-morph `getFilePath()`, which always uses
299
+ // forward slashes even on Windows.
300
+ const repoRoot = resolveRepoRootPosix();
299
301
 
300
302
  const counts = {total: 0, exported: 0, byKind: {} as Record<string, number>};
301
303
  let memberCount = 0;
@@ -549,7 +551,7 @@ function buildSymbolIndex(proj: Project): {
549
551
  // Index Promise prototype extensions from promise/Promise.ts
550
552
  const promiseFile = proj.getSourceFile(sf => sf.getFilePath().endsWith('/promise/Promise.ts'));
551
553
  if (promiseFile) {
552
- indexPromiseExtensions(promiseFile, index, mIndex, resolveRepoRoot());
554
+ indexPromiseExtensions(promiseFile, index, mIndex, resolveRepoRootPosix());
553
555
  }
554
556
 
555
557
  // Populate `memberNames` on symbol entries for every member-indexed owner
@@ -799,7 +801,8 @@ function ensureProject(): Project {
799
801
  * JSDoc).
800
802
  */
801
803
  function enrichMemberIndexFromImplements(proj: Project): void {
802
- const repoRoot = resolveRepoRoot();
804
+ // POSIX form: compared against ts-morph `getFilePath()` (see buildSymbolIndex).
805
+ const repoRoot = resolveRepoRootPosix();
803
806
 
804
807
  for (const sourceFile of proj.getSourceFiles()) {
805
808
  const filePath = sourceFile.getFilePath();
@@ -1374,8 +1377,12 @@ function findIndexEntry(name: string, filePath?: string): SymbolEntry | null {
1374
1377
 
1375
1378
  if (filePath) {
1376
1379
  // Resolve relative paths against repo root -- search results display repo-relative
1377
- // paths, so callers will typically pass those rather than absolute paths.
1378
- const resolved = filePath.startsWith('/') ? filePath : resolve(resolveRepoRoot(), filePath);
1380
+ // paths, so callers will typically pass those rather than absolute paths. Normalize
1381
+ // to POSIX so the comparison matches entry.filePath (ts-morph forward slashes) on
1382
+ // Windows, and recognize Windows drive-letter absolutes (e.g. `D:/...`) as absolute.
1383
+ const posix = toPosixPath(filePath);
1384
+ const isAbsolute = posix.startsWith('/') || /^[a-zA-Z]:\//.test(posix);
1385
+ const resolved = isAbsolute ? posix : toPosixPath(resolve(resolveRepoRoot(), posix));
1379
1386
  return exact.find(e => e.filePath === resolved) ?? null;
1380
1387
  }
1381
1388
 
@@ -1432,7 +1439,7 @@ function extractSymbolDetail(entry: SymbolEntry): SymbolDetail | null {
1432
1439
  *
1433
1440
  * `JSDoc.getDescription()` returns the text before the first JSDoc block tag,
1434
1441
  * but the TS parser treats ANY line whose first non-whitespace char is `@` as a
1435
- * tag boundary - even inside a fenced ``` code block. So a canonical-usage
1442
+ * tag boundary - even inside a fenced code block. So a canonical-usage
1436
1443
  * example containing e.g. `@observable.ref` silently truncates the description
1437
1444
  * mid-example, dropping everything after it (further examples, "SEE ALSO"
1438
1445
  * lists, trailing prose). See #4352.
@@ -11,7 +11,7 @@
11
11
  */
12
12
  import {z} from 'zod';
13
13
  import type {MemberInfo, MemberIndexEntry, SymbolEntry, SymbolDetail} from '../data/ts-registry.js';
14
- import {resolveRepoRoot} from '../util/paths.js';
14
+ import {resolveRepoRootPosix, toPosixPath} from '../util/paths.js';
15
15
 
16
16
  /** Remove blank lines from a JSDoc string to produce more compact output. */
17
17
  function collapseJsDoc(jsDoc: string): string {
@@ -31,8 +31,12 @@ export function truncateType(typeStr: string): string {
31
31
 
32
32
  /** Convert an absolute file path to a repo-relative path. */
33
33
  export function toRelativePath(filePath: string): string {
34
- const root = resolveRepoRoot();
35
- return filePath.startsWith(root) ? filePath.slice(root.length + 1) : filePath;
34
+ // Symbol filePaths originate from ts-morph (forward slashes on all platforms);
35
+ // compare in POSIX form so the repo-root prefix strips correctly on Windows,
36
+ // where `resolveRepoRoot()` would otherwise yield a backslash path.
37
+ const root = resolveRepoRootPosix();
38
+ const posix = toPosixPath(filePath);
39
+ return posix.startsWith(root) ? posix.slice(root.length + 1) : posix;
36
40
  }
37
41
 
38
42
  /**
package/mcp/util/paths.ts CHANGED
@@ -39,6 +39,27 @@ export function resolveRepoRoot(): string {
39
39
  return repoRoot;
40
40
  }
41
41
 
42
+ /** Convert a filesystem path to POSIX (forward-slash) form. */
43
+ export function toPosixPath(p: string): string {
44
+ return p.replace(/\\/g, '/');
45
+ }
46
+
47
+ /**
48
+ * Repo root in POSIX (forward-slash) form, matching ts-morph's `getFilePath()`
49
+ * convention, which always reports forward slashes on every platform.
50
+ *
51
+ * On Windows, {@link resolveRepoRoot} returns a backslash path (e.g.
52
+ * `D:\hoist-react`) while ts-morph reports `D:/hoist-react/...`; comparing or
53
+ * slicing the two directly fails, silently filtering out every source file and
54
+ * yielding an empty symbol index. Use this whenever comparing against or
55
+ * slicing a ts-morph source-file path (see `data/ts-registry.ts` and
56
+ * `formatters/typescript.ts`). Filesystem access that stays within Node's
57
+ * `path` APIs should keep using {@link resolveRepoRoot}.
58
+ */
59
+ export function resolveRepoRootPosix(): string {
60
+ return toPosixPath(resolveRepoRoot());
61
+ }
62
+
42
63
  /**
43
64
  * Resolve the `@xh/hoist` library version from the repo root `package.json`.
44
65
  *
@@ -21,7 +21,8 @@ export interface ExpandToLevelButtonProps extends MenuButtonProps {
21
21
 
22
22
  /**
23
23
  * A menu button to expand a multi-level grouped or tree grid out to a desired level.
24
- * Requires {@link GridConfig.levelLabels} to be configured on the bound GridModel.
24
+ * Requires {@link GridConfig.levelLabels} to be configured on the bound GridModel - the menu offers
25
+ * one entry per labelled level, so a partial array will limit the available expand-to targets.
25
26
  */
26
27
  export const [ExpandToLevelButton, expandToLevelButton] =
27
28
  hoistCmp.withFactory<ExpandToLevelButtonProps>({
@@ -51,13 +52,11 @@ export const [ExpandToLevelButton, expandToLevelButton] =
51
52
  }
52
53
 
53
54
  // Render a disabled button if requested or if we have a flat grid, or no level labels
54
- const {maxDepth, expandLevel, resolvedLevelLabels} = gridModel;
55
+ const {maxDepth, resolvedLevelLabels} = gridModel;
55
56
  if (disabled || !maxDepth || !resolvedLevelLabels) return disabledButton();
56
57
 
57
58
  const menuItems: MenuItemLike[] = resolvedLevelLabels.map((label, idx) => {
58
- const isCurrLevel =
59
- expandLevel === idx ||
60
- (expandLevel > maxDepth && idx === resolvedLevelLabels.length - 1);
59
+ const isCurrLevel = gridModel.isCurrentExpandLevel(idx);
61
60
 
62
61
  return {
63
62
  icon: isCurrLevel ? Icon.check() : Icon.placeholder(),
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Copyright © 2026 Extremely Heavy Industries Inc.
6
6
  */
7
- import {GridModel} from '@xh/hoist/cmp/grid';
7
+ import {GridModel, IColChooserModel} from '@xh/hoist/cmp/grid';
8
8
  import {HoistModel, HSide, XH} from '@xh/hoist/core';
9
9
  import '@xh/hoist/mobile/register';
10
10
  import {bindable, action, makeObservable, observable} from '@xh/hoist/mobx';
@@ -17,7 +17,7 @@ import {clone, find, sortBy} from 'lodash';
17
17
  * It is not necessary to manually create instances of this class within an application.
18
18
  * @internal
19
19
  */
20
- export class ColChooserModel extends HoistModel {
20
+ export class ColChooserModel extends HoistModel implements IColChooserModel {
21
21
  override xhImpl = true;
22
22
 
23
23
  gridModel: GridModel;
@@ -44,7 +44,24 @@ class CheckboxButtonInputModel extends HoistInputModel {
44
44
  // Implementation
45
45
  //----------------------------------
46
46
  const cmp = hoistCmp.factory<CheckboxButtonInputModel>(
47
- ({model, text, checkedIcon, uncheckedIcon, ...props}, ref) => {
47
+ (
48
+ {
49
+ // HoistInput props - exclude from passthrough to Onsen
50
+ bind,
51
+ value,
52
+ commitOnChange,
53
+ onChange,
54
+ onCommit,
55
+ // Consumed by this component
56
+ model,
57
+ text,
58
+ checkedIcon,
59
+ uncheckedIcon,
60
+ // Remainder passed to hoist button & Onsen button
61
+ ...props
62
+ },
63
+ ref
64
+ ) => {
48
65
  const checked = !!model.renderValue;
49
66
  return button({
50
67
  text: withDefault(text, model.getField()?.displayName),
@@ -46,7 +46,7 @@ export interface NumberInputProps extends HoistProps, HoistInputProps, StyleProp
46
46
  /** Text to display when control is empty. */
47
47
  placeholder?: string;
48
48
 
49
- /** Max decimal precision of the value, defaults to 4. */
49
+ /** Max decimal precision of the value, defaults to 4. Set to null for full, unrestricted precision. */
50
50
  precision?: NumericPrecision;
51
51
 
52
52
  /**
@@ -85,6 +85,7 @@ export const [SegmentedControl, segmentedControl] = hoistCmp.withFactory<Segment
85
85
  interface NormalizedOption extends SegmentedControlOption {
86
86
  label: string;
87
87
  intent?: Intent;
88
+ testId?: string;
88
89
  _key: string;
89
90
  }
90
91
 
@@ -97,13 +98,14 @@ class SegmentedControlModel extends HoistInputModel {
97
98
  return options.map((o: any, idx: number) => {
98
99
  const key = String(idx);
99
100
  if (isObject(o)) {
100
- const {label, value, icon, disabled, intent} = o as SegmentedControlOption;
101
+ const {label, value, icon, disabled, intent, testId} = o as SegmentedControlOption;
101
102
  return {
102
103
  value: this.toInternal(value),
103
104
  label: label ?? (icon ? '' : String(value)),
104
105
  icon,
105
106
  disabled,
106
107
  intent,
108
+ testId,
107
109
  _key: key
108
110
  };
109
111
  } else {
@@ -168,7 +170,8 @@ const cmp = hoistCmp.factory<SegmentedControlModel>(({model, className, ...props
168
170
 
169
171
  const buttons = model.normalizedOptions.map(opt => {
170
172
  const optIntent = opt.intent ?? defaultIntent,
171
- selected = opt._key === selectedKey;
173
+ selected = opt._key === selectedKey,
174
+ optTestId = opt.testId ?? (testId ? `${testId}-${String(opt.value)}` : null);
172
175
  // Wrap the label so it can truncate with an ellipsis when the segment is too narrow,
173
176
  // rather than hard-clipping mid-character. Pass null for icon-only options so the Button
174
177
  // renders the icon alone (an empty span would suppress that).
@@ -187,7 +190,8 @@ const cmp = hoistCmp.factory<SegmentedControlModel>(({model, className, ...props
187
190
  selected && 'xh-segmented-control-option--selected',
188
191
  optIntent && `xh-segmented-control-option--${optIntent}`
189
192
  ),
190
- onClick: () => model.onValueChange(opt._key)
193
+ onClick: () => model.onValueChange(opt._key),
194
+ testId: optTestId
191
195
  });
192
196
  });
193
197
 
@@ -21,7 +21,7 @@ import {action, bindable, makeObservable, observable, override} from '@xh/hoist/
21
21
  import {debouncePromise, wait} from '@xh/hoist/promise';
22
22
  import {throwIf, withDefault, mergeDeep} from '@xh/hoist/utils/js';
23
23
  import {createObservableRef, getLayoutProps} from '@xh/hoist/utils/react';
24
- import {escapeRegExp, isEqual, isNil, isPlainObject, keyBy} from 'lodash';
24
+ import {escapeRegExp, isEqual, isNil, isPlainObject, unionWith} from 'lodash';
25
25
  import {Children, ReactNode, ReactPortal} from 'react';
26
26
  import ReactDom from 'react-dom';
27
27
  import './Select.scss';
@@ -157,6 +157,15 @@ export interface SelectProps extends HoistProps, HoistInputProps, LayoutProps {
157
157
 
158
158
  /** Field on provided options for sourcing each option's value (default `value`). */
159
159
  valueField?: string;
160
+
161
+ /**
162
+ * Function to generate a `SelectOption` for a (non-null) selected value not present in the
163
+ * current options list. Return null to fall back to the default value-as-label behavior.
164
+ *
165
+ * Useful with queryFn-based selects, readonly forms, or any case where options may not be
166
+ * loaded when a value is set, ensuring the value renders with its proper label.
167
+ */
168
+ generateOptionFn?: (value: any) => SelectOption;
160
169
  }
161
170
 
162
171
  /**
@@ -377,7 +386,8 @@ class SelectInputModel extends HoistInputModel {
377
386
 
378
387
  // Convert external value into option object(s). Options created if missing - this takes the
379
388
  // external value from the model, and we will respect that even if we don't know about it.
380
- // (Exception for a null value, which we will only accept if explicitly present in options.)
389
+ // (Exception for a null value, which is never synthesized - accepted only if provided via
390
+ // options.)
381
391
  override toInternal(external) {
382
392
  return this.findOption(external, !isNil(external));
383
393
  }
@@ -393,7 +403,10 @@ class SelectInputModel extends HoistInputModel {
393
403
  }
394
404
  }
395
405
 
396
- return createIfNotFound ? this.valueToOption(value) : null;
406
+ if (!createIfNotFound) return null;
407
+
408
+ // Value not among options - let the app generate an option for it, else synthesize one.
409
+ return this.componentProps.generateOptionFn?.(value) ?? this.valueToOption(value);
397
410
  }
398
411
 
399
412
  override toExternal(internal) {
@@ -453,15 +466,9 @@ class SelectInputModel extends HoistInputModel {
453
466
 
454
467
  // Carry forward and add to any existing internalOpts to allow our value
455
468
  // converters to continue all selected values in multiMode.
456
- const matchesByVal = keyBy(matchOpts, 'value'),
457
- newOpts = [...matchOpts];
458
-
459
- this.internalOptions.forEach(currOpt => {
460
- const matchOpt = matchesByVal[currOpt.value];
461
- if (!matchOpt) newOpts.push(currOpt); // avoiding dupes
462
- });
463
-
464
- this.internalOptions = newOpts;
469
+ this.internalOptions = unionWith(matchOpts, this.internalOptions, (a, b) =>
470
+ isEqual(a.value, b.value)
471
+ );
465
472
 
466
473
  // But only return the matching options back to the combo.
467
474
  return matchOpts;
@@ -484,8 +491,7 @@ class SelectInputModel extends HoistInputModel {
484
491
  // Option Rendering
485
492
  //----------------------
486
493
  formatOptionLabel = (opt, params) => {
487
- // Always display the standard label string in the value container (context == 'value').
488
- // If we need to expose customization here, we could consider a dedicated prop.
494
+ // Display the standard label string in the value container (context == 'value').
489
495
  if (params.context !== 'menu') {
490
496
  return opt.label;
491
497
  }
@@ -501,7 +507,7 @@ class SelectInputModel extends HoistInputModel {
501
507
  return div(opt.label);
502
508
  }
503
509
 
504
- return this.externalValue === opt.value
510
+ return isEqual(this.externalValue, opt.value)
505
511
  ? hbox({
506
512
  items: [
507
513
  div({
package/mobx/README.md CHANGED
@@ -45,7 +45,8 @@ The following are re-exported from MobX and mobx-react-lite:
45
45
 
46
46
  The package configures MobX with `enforceActions: 'observed'`, meaning any modification to
47
47
  an observable property that is currently being observed must occur inside an `@action` method,
48
- `runInAction()` block, or `@bindable` setter. MobX logs a warning if this rule is violated.
48
+ `runInAction()` block, `@bindable` setter, or a `reaction`/`when` run callback (MobX
49
+ auto-wraps these in an action). MobX logs a warning if this rule is violated.
49
50
 
50
51
  This enforcement prevents accidental state mutations and makes data flow predictable.
51
52
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xh/hoist",
3
- "version": "86.1.0",
3
+ "version": "86.3.0",
4
4
  "description": "Hoist add-on for building and deploying React Applications.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -38,15 +38,15 @@
38
38
  ]
39
39
  },
40
40
  "dependencies": {
41
- "@auth0/auth0-spa-js": "~2.21.1",
42
- "@azure/msal-browser": "~5.14.0",
41
+ "@auth0/auth0-spa-js": "~2.23.0",
42
+ "@azure/msal-browser": "~5.16.0",
43
43
  "@blueprintjs/core": "^6.3.2",
44
44
  "@blueprintjs/datetime": "^6.0.6",
45
- "@codemirror/commands": "~6.10.3",
46
- "@codemirror/language": "~6.12.3",
47
- "@codemirror/language-data": "~6.5.2",
48
- "@codemirror/lint": "~6.9.6",
49
- "@codemirror/state": "~6.6.0",
45
+ "@codemirror/commands": "^6.10.3",
46
+ "@codemirror/language": "^6.12.3",
47
+ "@codemirror/language-data": "^6.5.2",
48
+ "@codemirror/lint": "^6.9.6",
49
+ "@codemirror/state": "^6.7.0",
50
50
  "@codemirror/view": "^6.43.0",
51
51
  "@fortawesome/fontawesome-pro": "^7.2.0",
52
52
  "@fortawesome/fontawesome-svg-core": "^7.2.0",
@@ -90,7 +90,7 @@
90
90
  "semver": "~7.8.0",
91
91
  "short-unique-id": "~5.3.2",
92
92
  "store2": "~2.14.3",
93
- "swiper": "^12.1.2",
93
+ "swiper": "^14.0.0",
94
94
  "ts-morph": "^27.0.2",
95
95
  "tsx": "^4.21.0",
96
96
  "ua-parser-js": "~2.0.4",
@@ -118,7 +118,7 @@
118
118
  "react-dom": "~18.2.0",
119
119
  "stylelint": "17.x",
120
120
  "stylelint-config-standard-scss": "17.x",
121
- "type-fest": "4.x",
121
+ "type-fest": "5.x",
122
122
  "typescript": "~5.9.3"
123
123
  },
124
124
  "resolutions": {
package/styles/vars.scss CHANGED
@@ -844,6 +844,21 @@ body {
844
844
  --xh-tab-font-size: var(--tab-font-size, var(--xh-font-size));
845
845
  --xh-tab-font-size-px: calc(var(--xh-tab-font-size) * 1px);
846
846
 
847
+ // Vertical (left/right) TabSwitcher - the styled "rail" of rounded, hoverable items with a
848
+ // filled active "pill". All overridable via the unprefixed hooks.
849
+ --xh-tab-switcher-vertical-min-width: var(--tab-switcher-vertical-min-width, auto);
850
+ --xh-tab-switcher-vertical-padding: var(--tab-switcher-vertical-padding, var(--xh-pad-half-px));
851
+ --xh-tab-switcher-vertical-border: var(--tab-switcher-vertical-border, var(--xh-border-solid));
852
+ --xh-tab-switcher-vertical-item-gap: var(--tab-switcher-vertical-item-gap, 2px);
853
+ --xh-tab-switcher-vertical-item-padding: var(--tab-switcher-vertical-item-padding, var(--xh-pad-half-px) var(--xh-pad-px));
854
+ --xh-tab-switcher-vertical-item-border-radius: var(--tab-switcher-vertical-item-border-radius, var(--xh-border-radius-px));
855
+ --xh-tab-switcher-vertical-item-text-color: var(--tab-switcher-vertical-item-text-color, var(--xh-text-color-muted));
856
+ --xh-tab-switcher-vertical-item-active-text-color: var(--tab-switcher-vertical-item-active-text-color, var(--xh-tab-active-text-color));
857
+ --xh-tab-switcher-vertical-item-hover-bg: var(--tab-switcher-vertical-item-hover-bg, var(--xh-bg-alt));
858
+ --xh-tab-switcher-vertical-item-active-bg: var(--tab-switcher-vertical-item-active-bg, var(--xh-bg-highlight));
859
+ --xh-tab-switcher-vertical-item-font-weight: var(--tab-switcher-vertical-item-font-weight, 500);
860
+ --xh-tab-switcher-vertical-item-active-font-weight: var(--tab-switcher-vertical-item-active-font-weight, 600);
861
+
847
862
  &.xh-dark {
848
863
  --xh-tab-active-text-color: var(--tab-active-text-color, var(--xh-intent-primary-lighter));
849
864
  }
@@ -1,42 +0,0 @@
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
- .xh-popover-filter-chooser {
8
- & > .bp6-popover-target {
9
- display: flex;
10
- flex: 1;
11
- }
12
-
13
- // Extra class names required to override the default styles of the popover
14
- &__popover.bp6-popover.bp6-minimal {
15
- margin-top: -15px !important;
16
- box-shadow: none;
17
-
18
- .bp6-popover-content {
19
- background: transparent;
20
- }
21
-
22
- .xh-select__value-container--is-multi {
23
- height: unset;
24
- line-height: unset;
25
- }
26
- }
27
-
28
- &__filter-chooser {
29
- .xh-select__value-container--is-multi {
30
- overflow-y: hidden !important;
31
- }
32
-
33
- .xh-select {
34
- .xh-select__control--is-disabled {
35
- background: var(--xh-input-bg);
36
- }
37
- .xh-select__multi-value--is-disabled .xh-select__multi-value__label {
38
- color: unset;
39
- }
40
- }
41
- }
42
- }