@xh/hoist 87.1.0 → 87.2.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 (264) hide show
  1. package/.npmignore +39 -0
  2. package/CHANGELOG.md +99 -20
  3. package/admin/AdminUtils.ts +64 -0
  4. package/admin/App.scss +88 -0
  5. package/admin/columns/UserData.ts +30 -3
  6. package/admin/detail/RestDetailModel.ts +64 -0
  7. package/admin/detail/RestDetailPanel.ts +106 -0
  8. package/admin/tabs/activity/tracking/ActivityTrackingModel.ts +27 -94
  9. package/admin/tabs/activity/tracking/ActivityTrackingPanel.ts +23 -60
  10. package/admin/tabs/activity/tracking/detail/ActivityDetailView.ts +1 -2
  11. package/admin/tabs/general/alertBanner/AlertBannerPanel.ts +2 -13
  12. package/admin/tabs/general/config/ConfigDetailPanel.ts +72 -0
  13. package/admin/tabs/general/config/ConfigPanel.ts +41 -53
  14. package/admin/tabs/general/config/ConfigPanelModel.ts +3 -0
  15. package/admin/tabs/general/config/ConfigValue.scss +14 -0
  16. package/admin/tabs/general/config/ConfigValue.ts +104 -65
  17. package/admin/tabs/userData/jsonblob/JsonBlobDetailPanel.ts +93 -0
  18. package/admin/tabs/userData/jsonblob/JsonBlobModel.ts +4 -0
  19. package/admin/tabs/userData/jsonblob/JsonBlobPanel.ts +2 -0
  20. package/admin/tabs/userData/prefs/UserPreferenceDetailPanel.ts +62 -0
  21. package/admin/tabs/userData/prefs/UserPreferenceModel.ts +10 -3
  22. package/admin/tabs/userData/prefs/UserPreferencePanel.ts +3 -1
  23. package/admin/tabs/userData/roles/details/RoleDetails.scss +2 -19
  24. package/admin/tabs/userData/roles/details/RoleDetails.ts +13 -13
  25. package/appcontainer/AboutDialogModel.ts +1 -0
  26. package/appcontainer/AppContainerModel.ts +3 -0
  27. package/appcontainer/AppStateModel.ts +1 -0
  28. package/appcontainer/BannerSourceModel.ts +1 -0
  29. package/appcontainer/ChangelogDialogModel.ts +1 -0
  30. package/appcontainer/ColChooserOptionsModel.ts +1 -0
  31. package/appcontainer/ExceptionDialogModel.ts +1 -0
  32. package/appcontainer/FeedbackDialogModel.ts +1 -0
  33. package/appcontainer/ImpersonationBarModel.ts +1 -0
  34. package/appcontainer/MessageSourceModel.ts +1 -0
  35. package/appcontainer/OptionsDialogModel.ts +1 -0
  36. package/appcontainer/PageStateModel.ts +1 -0
  37. package/appcontainer/RouterModel.ts +2 -0
  38. package/appcontainer/SizingModeModel.ts +1 -0
  39. package/appcontainer/ThemeModel.ts +1 -0
  40. package/appcontainer/ToastSourceModel.ts +1 -0
  41. package/appcontainer/UserAgentModel.ts +1 -0
  42. package/appcontainer/ViewportSizeModel.ts +1 -0
  43. package/build/types/admin/AdminUtils.d.ts +29 -0
  44. package/build/types/admin/columns/UserData.d.ts +3 -0
  45. package/build/types/admin/detail/RestDetailModel.d.ts +20 -0
  46. package/build/types/admin/detail/RestDetailPanel.d.ts +34 -0
  47. package/build/types/admin/tabs/activity/tracking/ActivityTrackingModel.d.ts +5 -17
  48. package/build/types/admin/tabs/general/config/ConfigDetailPanel.d.ts +7 -0
  49. package/build/types/admin/tabs/general/config/ConfigValue.d.ts +10 -4
  50. package/build/types/admin/tabs/userData/jsonblob/JsonBlobDetailPanel.d.ts +3 -0
  51. package/build/types/admin/tabs/userData/prefs/UserPreferenceDetailPanel.d.ts +3 -0
  52. package/build/types/admin/tabs/userData/prefs/UserPreferenceModel.d.ts +3 -0
  53. package/build/types/appcontainer/AboutDialogModel.d.ts +1 -0
  54. package/build/types/appcontainer/AppContainerModel.d.ts +1 -0
  55. package/build/types/appcontainer/AppStateModel.d.ts +1 -0
  56. package/build/types/appcontainer/BannerSourceModel.d.ts +1 -0
  57. package/build/types/appcontainer/ChangelogDialogModel.d.ts +1 -0
  58. package/build/types/appcontainer/ColChooserOptionsModel.d.ts +1 -0
  59. package/build/types/appcontainer/ExceptionDialogModel.d.ts +1 -0
  60. package/build/types/appcontainer/FeedbackDialogModel.d.ts +1 -0
  61. package/build/types/appcontainer/ImpersonationBarModel.d.ts +1 -0
  62. package/build/types/appcontainer/MessageSourceModel.d.ts +1 -0
  63. package/build/types/appcontainer/OptionsDialogModel.d.ts +1 -0
  64. package/build/types/appcontainer/PageStateModel.d.ts +1 -0
  65. package/build/types/appcontainer/RouterModel.d.ts +1 -0
  66. package/build/types/appcontainer/SizingModeModel.d.ts +1 -0
  67. package/build/types/appcontainer/ThemeModel.d.ts +1 -0
  68. package/build/types/appcontainer/ToastSourceModel.d.ts +1 -0
  69. package/build/types/appcontainer/UserAgentModel.d.ts +1 -0
  70. package/build/types/appcontainer/ViewportSizeModel.d.ts +1 -0
  71. package/build/types/cmp/ag-grid/AgGridModel.d.ts +3 -1
  72. package/build/types/cmp/card/Card.d.ts +2 -2
  73. package/build/types/cmp/chart/ChartModel.d.ts +2 -0
  74. package/build/types/cmp/dataview/DataViewModel.d.ts +2 -0
  75. package/build/types/cmp/daterange/DateRangePickerModel.d.ts +266 -0
  76. package/build/types/cmp/daterange/DateRangePresets.d.ts +26 -0
  77. package/build/types/cmp/daterange/DateRangeUtils.d.ts +96 -0
  78. package/build/types/cmp/daterange/Types.d.ts +152 -0
  79. package/build/types/cmp/daterange/index.d.ts +4 -0
  80. package/build/types/cmp/filter/FilterChooserModel.d.ts +3 -1
  81. package/build/types/cmp/form/FormModel.d.ts +3 -1
  82. package/build/types/cmp/grid/GridModel.d.ts +2 -0
  83. package/build/types/cmp/grid/Types.d.ts +2 -0
  84. package/build/types/cmp/grid/filter/GridFilterModel.d.ts +1 -1
  85. package/build/types/cmp/grouping/GroupingChooserModel.d.ts +3 -1
  86. package/build/types/cmp/input/HoistInputProps.d.ts +49 -2
  87. package/build/types/cmp/tab/TabContainerModel.d.ts +3 -1
  88. package/build/types/cmp/tab/TabModel.d.ts +3 -1
  89. package/build/types/cmp/treemap/TreeMapModel.d.ts +2 -0
  90. package/build/types/cmp/viewmanager/ViewManagerModel.d.ts +2 -0
  91. package/build/types/cmp/zoneGrid/ZoneGridModel.d.ts +2 -0
  92. package/build/types/core/HoistBase.d.ts +7 -0
  93. package/build/types/core/HoistProps.d.ts +26 -1
  94. package/build/types/core/model/RootRefreshContextModel.d.ts +1 -0
  95. package/build/types/core/types/Telemetry.d.ts +1 -1
  96. package/build/types/data/Store.d.ts +3 -1
  97. package/build/types/data/StoreSelectionModel.d.ts +3 -1
  98. package/build/types/data/cube/Cube.d.ts +5 -2
  99. package/build/types/data/cube/View.d.ts +2 -0
  100. package/build/types/desktop/cmp/appOption/AutoRefreshAppOption.d.ts +3 -0
  101. package/build/types/desktop/cmp/appOption/ThemeAppOption.d.ts +3 -0
  102. package/build/types/desktop/cmp/button/Button.d.ts +2 -2
  103. package/build/types/desktop/cmp/button/ButtonGroup.d.ts +2 -2
  104. package/build/types/desktop/cmp/dash/DashConfig.d.ts +2 -0
  105. package/build/types/desktop/cmp/dash/DashViewModel.d.ts +3 -1
  106. package/build/types/desktop/cmp/dash/canvas/DashCanvasModel.d.ts +1 -1
  107. package/build/types/desktop/cmp/dash/container/DashContainerModel.d.ts +1 -1
  108. package/build/types/desktop/cmp/daterange/DateRangePicker.d.ts +66 -0
  109. package/build/types/desktop/cmp/daterange/impl/CustomTab.d.ts +3 -0
  110. package/build/types/desktop/cmp/daterange/impl/DateRangePickerLocalModel.d.ts +121 -0
  111. package/build/types/desktop/cmp/daterange/impl/PeriodTab.d.ts +2 -0
  112. package/build/types/desktop/cmp/daterange/impl/PresetsTab.d.ts +3 -0
  113. package/build/types/desktop/cmp/daterange/impl/RelativeTab.d.ts +3 -0
  114. package/build/types/desktop/cmp/daterange/impl/TabUtils.d.ts +21 -0
  115. package/build/types/desktop/cmp/daterange/index.d.ts +2 -0
  116. package/build/types/desktop/cmp/dock/DockContainerModel.d.ts +3 -1
  117. package/build/types/desktop/cmp/dock/DockViewModel.d.ts +3 -1
  118. package/build/types/desktop/cmp/grouping/GroupingChooser.d.ts +4 -1
  119. package/build/types/desktop/cmp/input/IntentInput.d.ts +38 -0
  120. package/build/types/desktop/cmp/input/NumberInput.d.ts +2 -2
  121. package/build/types/desktop/cmp/input/SegmentedControl.d.ts +25 -4
  122. package/build/types/desktop/cmp/input/TextArea.d.ts +2 -2
  123. package/build/types/desktop/cmp/input/TextInput.d.ts +2 -2
  124. package/build/types/desktop/cmp/input/index.d.ts +1 -0
  125. package/build/types/desktop/cmp/leftrightchooser/LeftRightChooserModel.d.ts +3 -1
  126. package/build/types/desktop/cmp/panel/PanelModel.d.ts +3 -1
  127. package/build/types/desktop/cmp/rest/RestGridModel.d.ts +1 -1
  128. package/build/types/desktop/cmp/tab/impl/TabContainer.d.ts +1 -1
  129. package/build/types/icon/Icon.d.ts +3 -0
  130. package/build/types/inspector/impl/InspectorUtils.d.ts +3 -0
  131. package/build/types/inspector/instances/InstancesModel.d.ts +4 -0
  132. package/build/types/kit/react-day-picker/index.d.ts +3 -3
  133. package/build/types/mobile/cmp/button/Button.d.ts +2 -2
  134. package/build/types/mobile/cmp/input/NumberInput.d.ts +2 -2
  135. package/build/types/mobile/cmp/input/SegmentedControl.d.ts +18 -4
  136. package/build/types/mobile/cmp/input/TextArea.d.ts +2 -2
  137. package/build/types/mobile/cmp/input/TextInput.d.ts +2 -2
  138. package/build/types/mobile/cmp/navigator/NavigatorModel.d.ts +3 -1
  139. package/build/types/mobile/cmp/navigator/PageModel.d.ts +3 -1
  140. package/build/types/mobile/cmp/tab/impl/TabContainer.d.ts +1 -1
  141. package/build/types/svc/InspectorService.d.ts +1 -0
  142. package/build/types/utils/js/LangUtils.d.ts +24 -5
  143. package/cmp/README.md +1 -0
  144. package/cmp/ag-grid/AgGridModel.ts +5 -0
  145. package/cmp/badge/Badge.ts +5 -3
  146. package/cmp/card/Card.ts +4 -3
  147. package/cmp/chart/ChartModel.ts +11 -1
  148. package/cmp/dataview/DataViewModel.ts +7 -1
  149. package/cmp/daterange/DateRangePickerModel.ts +628 -0
  150. package/cmp/daterange/DateRangePresets.ts +274 -0
  151. package/cmp/daterange/DateRangeUtils.ts +557 -0
  152. package/cmp/daterange/README.md +434 -0
  153. package/cmp/daterange/Types.ts +203 -0
  154. package/cmp/daterange/index.ts +10 -0
  155. package/cmp/filter/FilterChooserModel.ts +7 -4
  156. package/cmp/form/FormModel.ts +6 -0
  157. package/cmp/form/README.md +1 -0
  158. package/cmp/grid/GridModel.ts +24 -4
  159. package/cmp/grid/Types.ts +3 -0
  160. package/cmp/grid/filter/GridFilterModel.ts +3 -1
  161. package/cmp/grid/impl/RecordSortUtils.ts +10 -2
  162. package/cmp/grouping/GroupingChooserModel.ts +6 -1
  163. package/cmp/input/HoistInputProps.ts +50 -2
  164. package/cmp/input/README.md +42 -0
  165. package/cmp/layout/Box.ts +3 -2
  166. package/cmp/tab/TabContainerModel.ts +9 -1
  167. package/cmp/tab/TabModel.ts +5 -0
  168. package/cmp/treemap/SplitTreeMapModel.ts +12 -2
  169. package/cmp/treemap/TreeMapModel.ts +6 -1
  170. package/cmp/viewmanager/ViewManagerModel.ts +6 -1
  171. package/cmp/zoneGrid/ZoneGridModel.ts +7 -0
  172. package/core/HoistBase.ts +11 -0
  173. package/core/HoistProps.ts +27 -0
  174. package/core/README.md +46 -0
  175. package/core/impl/InstallServices.ts +5 -1
  176. package/core/model/RootRefreshContextModel.ts +2 -0
  177. package/core/types/Telemetry.ts +1 -1
  178. package/data/README.md +3 -1
  179. package/data/Store.ts +7 -0
  180. package/data/StoreSelectionModel.ts +5 -1
  181. package/data/cube/Cube.ts +12 -3
  182. package/data/cube/View.ts +5 -1
  183. package/desktop/README.md +2 -0
  184. package/desktop/appcontainer/LoginPanel.ts +2 -0
  185. package/desktop/cmp/button/Button.scss +71 -42
  186. package/desktop/cmp/button/Button.ts +10 -1
  187. package/desktop/cmp/button/ButtonGroup.ts +4 -1
  188. package/desktop/cmp/button/grid/ColChooserButton.ts +9 -1
  189. package/desktop/cmp/button/zoneGrid/ZoneMapperButton.ts +1 -0
  190. package/desktop/cmp/dash/DashConfig.ts +3 -0
  191. package/desktop/cmp/dash/DashViewModel.ts +14 -1
  192. package/desktop/cmp/dash/canvas/DashCanvasModel.ts +3 -1
  193. package/desktop/cmp/dash/container/DashContainerModel.ts +3 -1
  194. package/desktop/cmp/daterange/DateRangePicker.scss +577 -0
  195. package/desktop/cmp/daterange/DateRangePicker.ts +357 -0
  196. package/desktop/cmp/daterange/impl/CustomTab.ts +165 -0
  197. package/desktop/cmp/daterange/impl/DateRangePickerLocalModel.ts +482 -0
  198. package/desktop/cmp/daterange/impl/PeriodTab.ts +138 -0
  199. package/desktop/cmp/daterange/impl/PresetsTab.ts +58 -0
  200. package/desktop/cmp/daterange/impl/RelativeTab.ts +108 -0
  201. package/desktop/cmp/daterange/impl/TabUtils.ts +34 -0
  202. package/desktop/cmp/daterange/index.ts +8 -0
  203. package/desktop/cmp/dock/DockContainerModel.ts +6 -1
  204. package/desktop/cmp/dock/DockViewModel.ts +6 -1
  205. package/desktop/cmp/filechooser/FileChooser.ts +4 -0
  206. package/desktop/cmp/form/FormField.ts +1 -0
  207. package/desktop/cmp/grouping/GroupingChooser.ts +8 -2
  208. package/desktop/cmp/input/Checkbox.ts +1 -0
  209. package/desktop/cmp/input/CodeInput.ts +1 -0
  210. package/desktop/cmp/input/DateInput.scss +1 -1
  211. package/desktop/cmp/input/DateInput.ts +1 -0
  212. package/desktop/cmp/input/IntentInput.scss +202 -0
  213. package/desktop/cmp/input/IntentInput.ts +213 -0
  214. package/desktop/cmp/input/NumberInput.ts +11 -2
  215. package/desktop/cmp/input/Picker.ts +1 -0
  216. package/desktop/cmp/input/RadioInput.ts +2 -1
  217. package/desktop/cmp/input/SegmentedControl.scss +45 -2
  218. package/desktop/cmp/input/SegmentedControl.ts +66 -11
  219. package/desktop/cmp/input/Select.ts +9 -7
  220. package/desktop/cmp/input/Slider.ts +2 -0
  221. package/desktop/cmp/input/SwitchInput.ts +1 -0
  222. package/desktop/cmp/input/TextArea.ts +11 -2
  223. package/desktop/cmp/input/TextInput.ts +11 -2
  224. package/desktop/cmp/input/index.ts +1 -0
  225. package/desktop/cmp/leftrightchooser/LeftRightChooserModel.ts +5 -0
  226. package/desktop/cmp/panel/Panel.ts +8 -5
  227. package/desktop/cmp/panel/PanelModel.ts +8 -0
  228. package/desktop/cmp/panel/impl/ResizeContainer.ts +2 -1
  229. package/desktop/cmp/rest/RestGridModel.ts +4 -0
  230. package/desktop/cmp/tab/impl/TabContainer.ts +2 -0
  231. package/docs/README.md +2 -0
  232. package/docs/build-and-publish.md +23 -7
  233. package/docs/doc-registry.json +8 -0
  234. package/docs/version-compatibility.md +2 -0
  235. package/icon/Icon.ts +9 -0
  236. package/icon/index.ts +24 -0
  237. package/inspector/README.md +8 -2
  238. package/inspector/impl/InspectorUtils.ts +12 -0
  239. package/inspector/instances/DiagnosticsModel.ts +3 -2
  240. package/inspector/instances/InstancesModel.ts +81 -14
  241. package/inspector/instances/InstancesPanel.ts +6 -0
  242. package/kit/react-day-picker/index.ts +3 -3
  243. package/mobile/appcontainer/LoginPanel.ts +2 -0
  244. package/mobile/cmp/button/Button.ts +16 -3
  245. package/mobile/cmp/form/FormField.ts +1 -0
  246. package/mobile/cmp/input/Checkbox.ts +1 -0
  247. package/mobile/cmp/input/DateInput.ts +1 -0
  248. package/mobile/cmp/input/Label.ts +13 -8
  249. package/mobile/cmp/input/NumberInput.ts +11 -2
  250. package/mobile/cmp/input/SearchInput.ts +1 -0
  251. package/mobile/cmp/input/SegmentedControl.scss +36 -6
  252. package/mobile/cmp/input/SegmentedControl.ts +37 -11
  253. package/mobile/cmp/input/Select.ts +2 -0
  254. package/mobile/cmp/input/SwitchInput.ts +1 -0
  255. package/mobile/cmp/input/TextArea.ts +11 -2
  256. package/mobile/cmp/input/TextInput.ts +11 -2
  257. package/mobile/cmp/navigator/NavigatorModel.ts +6 -1
  258. package/mobile/cmp/navigator/PageModel.ts +6 -1
  259. package/mobile/cmp/tab/impl/TabContainer.ts +9 -1
  260. package/package.json +19 -19
  261. package/styles/vars.scss +32 -2
  262. package/svc/InspectorService.ts +2 -0
  263. package/svc/TraceService.ts +5 -2
  264. package/utils/js/LangUtils.ts +37 -5
@@ -59,6 +59,9 @@ export interface FormConfig {
59
59
  disabled?: boolean;
60
60
  readonly?: boolean;
61
61
 
62
+ /** See {@link HoistBase.xhName}. */
63
+ xhName?: string;
64
+
62
65
  /** @internal */
63
66
  xhImpl?: boolean;
64
67
  }
@@ -140,11 +143,13 @@ export class FormModel extends HoistModel {
140
143
  disabled = false,
141
144
  persistWith = null,
142
145
  readonly = false,
146
+ xhName = null,
143
147
  xhImpl = false
144
148
  }: FormConfig = {}) {
145
149
  super();
146
150
  makeObservable(this);
147
151
  this.xhImpl = xhImpl;
152
+ this.xhName = xhName;
148
153
 
149
154
  this.disabled = disabled;
150
155
  this.readonly = readonly;
@@ -171,6 +176,7 @@ export class FormModel extends HoistModel {
171
176
  forOwn(this.fields, f => {
172
177
  f.formModel = this;
173
178
  f.xhImpl = xhImpl;
179
+ f.xhName ??= this.childXhName(f.name);
174
180
  });
175
181
  }
176
182
 
@@ -108,6 +108,7 @@ const formModel = new FormModel({
108
108
  | `disabled` | `boolean` | Disable all fields |
109
109
  | `readonly` | `boolean` | Make all fields read-only |
110
110
  | `persistWith` | `FormPersistOptions` | Options for persisting form state |
111
+ | `xhName` | `string` | Developer-facing name for logs, telemetry, and Inspector |
111
112
 
112
113
  ### Working with Data
113
114
 
@@ -392,6 +392,9 @@ export interface GridConfig {
392
392
  /** Extra app-specific data for the GridModel. */
393
393
  appData?: PlainObject;
394
394
 
395
+ /** See {@link HoistBase.xhName}. */
396
+ xhName?: string;
397
+
395
398
  /** @internal */
396
399
  xhImpl?: boolean;
397
400
  }
@@ -707,11 +710,13 @@ export class GridModel extends HoistModel {
707
710
  enableFullWidthScroll = GridModel.defaults.enableFullWidthScroll,
708
711
  experimental,
709
712
  appData,
713
+ xhName = null,
710
714
  xhImpl,
711
715
  ...rest
712
716
  }: GridConfig = config;
713
717
 
714
718
  this.xhImpl = xhImpl;
719
+ this.xhName = xhName;
715
720
 
716
721
  this._defaultState = {columns, sortBy, groupBy, expandLevel};
717
722
 
@@ -776,6 +781,7 @@ export class GridModel extends HoistModel {
776
781
  sizingMode = this.parseSizingMode(sizingMode);
777
782
 
778
783
  this.agGridModel = new AgGridModel({
784
+ xhName: this.childXhName('agGridModel'),
779
785
  sizingMode,
780
786
  showHover,
781
787
  rowBorders,
@@ -1894,7 +1900,11 @@ export class GridModel extends HoistModel {
1894
1900
  store = storeOrConfig;
1895
1901
  } else {
1896
1902
  storeOrConfig = this.enhanceStoreConfigFromColumns(storeOrConfig);
1897
- store = new Store({loadTreeData: this.treeMode, ...storeOrConfig});
1903
+ store = new Store({
1904
+ xhName: this.childXhName('store'),
1905
+ loadTreeData: this.treeMode,
1906
+ ...storeOrConfig
1907
+ });
1898
1908
  store.xhImpl = this.xhImpl;
1899
1909
  this.markManaged(store);
1900
1910
  }
@@ -2106,7 +2116,12 @@ export class GridModel extends HoistModel {
2106
2116
  }
2107
2117
 
2108
2118
  return this.markManaged(
2109
- new StoreSelectionModel({...selModel, store: this.store, xhImpl: true})
2119
+ new StoreSelectionModel({
2120
+ xhName: this.childXhName('selModel'),
2121
+ ...selModel,
2122
+ store: this.store,
2123
+ xhImpl: true
2124
+ })
2110
2125
  );
2111
2126
  }
2112
2127
 
@@ -2114,7 +2129,10 @@ export class GridModel extends HoistModel {
2114
2129
  if (XH.isMobileApp || !filterModel) return null;
2115
2130
 
2116
2131
  filterModel = filterModel === true ? {} : filterModel;
2117
- return new GridFilterModel({bind: this.store, ...filterModel}, this);
2132
+ return new GridFilterModel(
2133
+ {xhName: this.childXhName('filterModel'), bind: this.store, ...filterModel},
2134
+ this
2135
+ );
2118
2136
  }
2119
2137
 
2120
2138
  private parseExperimental(experimental: GridExperimentalFlags) {
@@ -2146,7 +2164,9 @@ export class GridModel extends HoistModel {
2146
2164
  ? DesktopDockedColChooserModel
2147
2165
  : DesktopModalColChooserModel;
2148
2166
 
2149
- return this.markManaged(new modelClass({...config, gridModel: this}));
2167
+ const ret = this.markManaged(new modelClass({...config, gridModel: this}));
2168
+ ret.xhName = this.childXhName('colChooserModel');
2169
+ return ret;
2150
2170
  }
2151
2171
 
2152
2172
  private isGroupSpec(col: ColumnOrGroupSpec): col is ColumnGroupSpec {
package/cmp/grid/Types.ts CHANGED
@@ -161,6 +161,9 @@ export interface GridFilterModelConfig {
161
161
  * Defaults to `Icon.filter()` (the standard funnel icon in regular/outline style).
162
162
  */
163
163
  activeFilterIcon?: ReactElement;
164
+
165
+ /** See {@link HoistBase.xhName}. */
166
+ xhName?: string;
164
167
  }
165
168
 
166
169
  /**
@@ -65,12 +65,14 @@ export class GridFilterModel extends HoistModel {
65
65
  commitOnChange = false,
66
66
  fieldSpecs,
67
67
  fieldSpecDefaults,
68
- activeFilterIcon
68
+ activeFilterIcon,
69
+ xhName = null
69
70
  }: GridFilterModelConfig,
70
71
  gridModel: GridModel
71
72
  ) {
72
73
  super();
73
74
  makeObservable(this);
75
+ this.xhName = xhName;
74
76
  this.gridModel = gridModel;
75
77
  this.bind = bind;
76
78
  this.commitOnChange = commitOnChange;
@@ -105,15 +105,23 @@ function getGroupSorters(gridModel: GridModel): RecordSorter[] {
105
105
  const column = gridModel.getColumn(colId);
106
106
  if (!column) return null;
107
107
 
108
- const {field} = column;
108
+ const {field} = column,
109
+ {getValueFn, ctx} = sorterFor(gridModel, column);
109
110
  return {
110
- ...sorterFor(gridModel, column),
111
+ ctx,
112
+ // groupSortFn expects ag-Grid group keys - always string or null, never raw values.
113
+ getValueFn: params => toGroupKey(getValueFn(params)),
111
114
  compare: (a, b, nodeA, nodeB) => groupSortFn(a, b, field, {gridModel, nodeA, nodeB})
112
115
  };
113
116
  })
114
117
  );
115
118
  }
116
119
 
120
+ /** Mirror ag-Grid's ValueService.getKeyForNode - string/null pass through, else String(). */
121
+ function toGroupKey(value: any): string {
122
+ return value == null || typeof value === 'string' ? value : String(value);
123
+ }
124
+
117
125
  /** Value-resolution half of a sorter - the caller supplies the comparator. */
118
126
  function sorterFor(gridModel: GridModel, column: Column) {
119
127
  const {field, getValueFn} = column;
@@ -76,6 +76,9 @@ export interface GroupingChooserConfig {
76
76
  * provided in the `dimensions` config.
77
77
  */
78
78
  sortDimensions?: boolean;
79
+
80
+ /** See {@link HoistBase.xhName}. */
81
+ xhName?: string;
79
82
  }
80
83
 
81
84
  export interface GroupingChooserModelDefaults {
@@ -159,10 +162,12 @@ export class GroupingChooserModel extends HoistModel {
159
162
  initialValue = [],
160
163
  maxDepth = null,
161
164
  persistWith = null,
162
- sortDimensions = true
165
+ sortDimensions = true,
166
+ xhName = null
163
167
  }: GroupingChooserConfig) {
164
168
  super();
165
169
  makeObservable(this);
170
+ this.xhName = xhName;
166
171
 
167
172
  this.allowEmpty = allowEmpty;
168
173
  this.bind = bind;
@@ -5,9 +5,9 @@
5
5
  * Copyright © 2026 Extremely Heavy Industries Inc.
6
6
  */
7
7
 
8
- import {TestSupportProps} from '@xh/hoist/core';
8
+ import {DomAttrsProps, TestSupportProps} from '@xh/hoist/core';
9
9
 
10
- export interface HoistInputProps extends TestSupportProps {
10
+ export interface HoistInputProps extends TestSupportProps, DomAttrsProps {
11
11
  /**
12
12
  * Field or model property name from which this component should read and write its value
13
13
  * in controlled mode. Can be set by parent FormField.
@@ -32,3 +32,51 @@ export interface HoistInputProps extends TestSupportProps {
32
32
  /** Value of the control, if provided directly. */
33
33
  value?: any;
34
34
  }
35
+
36
+ /**
37
+ * Props for text-like inputs that browser password managers may treat as credential fields.
38
+ *
39
+ * Extended by the inputs that render a free-text `<input>` or `<textarea>` - the elements password
40
+ * manager extensions scan. Controls that cannot present as a credential field (checkboxes, sliders,
41
+ * segmented controls, and similar) do not accept this prop.
42
+ */
43
+ export interface PasswordManagerSupportProps {
44
+ /**
45
+ * True to allow browser password managers to offer autofill on this input. Defaults to false,
46
+ * suppressing them.
47
+ *
48
+ * Password managers flag any field that *looks* like a username, email, or password and offer
49
+ * a saved-login prompt, whether or not the field has anything to do with signing in. That is
50
+ * unwanted on the ordinary data-entry forms most Hoist applications are built from, so Hoist
51
+ * opts out by default - the same posture as {@link TextInputProps.autoComplete}, which defaults
52
+ * to 'off'. Note `autocomplete="off"` alone does not stop password managers, which have ignored
53
+ * it on login-like fields for years.
54
+ *
55
+ * Set true on genuine credential fields so users get real password manager support.
56
+ */
57
+ enablePasswordManagers?: boolean;
58
+ }
59
+
60
+ /**
61
+ * Vendor-specific attributes that tell a password manager to skip a field. Each manager checks its
62
+ * own attribute - there is no cross-vendor standard. Covers 1Password, LastPass, and Bitwarden.
63
+ *
64
+ * Frozen and module-level so it adds no per-render allocation.
65
+ */
66
+ export const PW_MANAGER_IGNORE_ATTRS = Object.freeze({
67
+ 'data-1p-ignore': 'true',
68
+ 'data-lpignore': 'true',
69
+ 'data-bwignore': 'true'
70
+ });
71
+
72
+ /**
73
+ * Resolve the password manager attributes for an input, given its `enablePasswordManagers` prop.
74
+ * Spread onto the element rendering the native `<input>` / `<textarea>`, ahead of any `domAttrs`
75
+ * so an explicit caller attribute wins.
76
+ *
77
+ * Note these must be present in the DOM before a field is first focused - 1Password caches its
78
+ * assessment of a field on first interaction - so apply them during render, never in an effect.
79
+ */
80
+ export function getPasswordManagerAttrs(enablePasswordManagers: boolean) {
81
+ return enablePasswordManagers ? null : PW_MANAGER_IGNORE_ATTRS;
82
+ }
@@ -139,6 +139,48 @@ The common props interface extended by all input components.
139
139
  | `onCommit` | `(value, oldValue) => void` | Called when value is committed |
140
140
  | `tabIndex` | `number` | Tab order for focus (-1 to skip) |
141
141
  | `id` | `string` | DOM ID for the input element |
142
+ | `testId` | `string` | Emits `data-testid` on the input's primary DOM element |
143
+ | `domAttrs` | `data-*` / `aria-*` / `role` keys | Extra HTML attributes for that same element |
144
+
145
+ ### Extra DOM Attributes with `domAttrs`
146
+
147
+ Input components pass an explicit, named set of props to the element they render, so any other
148
+ attribute is dropped. Use `domAttrs` to apply `data-*` or `aria-*` attributes that Hoist does not
149
+ model with a dedicated prop.
150
+
151
+ ```typescript
152
+ textInput({
153
+ bind: 'email',
154
+ domAttrs: {'data-analytics-id': 'signup-email', 'aria-describedby': 'email-help'}
155
+ });
156
+ ```
157
+
158
+ Keys are constrained by type to `data-*` and `aria-*` attributes, plus `role`, so the prop cannot
159
+ reach attributes the component manages itself.
160
+
161
+ > **Mobile caveat:** `TextInput` and `NumberInput` on mobile render Onsen's `<ons-input>` custom
162
+ > element, and `SearchInput` renders `<ons-search-input>`. Both mirror only a fixed allowlist of
163
+ > attributes onto the real `<input>` they create. Attributes supplied via `domAttrs` are applied to the `<ons-input>` wrapper,
164
+ > not to the inner `<input>`. This matches where `testId` lands today. Mobile `TextArea` is
165
+ > unaffected - it renders a native `<textarea>`.
166
+
167
+ ### Password Managers
168
+
169
+ Password manager extensions flag any field that *looks* like a username, email, or password and
170
+ offer a saved-login prompt, whether or not the field has anything to do with signing in. Note that
171
+ `autocomplete="off"` does not stop them - they have ignored it on login-like fields for years.
172
+
173
+ `TextInput`, `TextArea`, and `NumberInput` therefore opt out by default, applying the vendor
174
+ attributes for 1Password, LastPass, and Bitwarden. Set `enablePasswordManagers: true` on genuine
175
+ credential fields, as Hoist's own `LoginPanel` does:
176
+
177
+ ```typescript
178
+ textInput({bind: 'password', type: 'password', autoComplete: 'current-password',
179
+ enablePasswordManagers: true});
180
+ ```
181
+
182
+ The attributes are applied during render, never in an effect - 1Password caches its assessment of
183
+ a field on first focus, so they must already be present. The mobile caveat above applies here too.
142
184
 
143
185
  ## Integration with Forms
144
186
 
package/cmp/layout/Box.ts CHANGED
@@ -35,12 +35,13 @@ export const [Box, box] = hoistCmp.withFactory<BoxComponentProps>({
35
35
  // Note `model` destructured off of non-layout props to avoid setting
36
36
  // model as a bogus DOM attribute. This low-level component may easily be passed one from
37
37
  // a parent that has not properly managed its own props.
38
- let [layoutProps, {children, model, testId, ...restProps}] = splitLayoutProps(props);
38
+ let [layoutProps, {children, model, testId, domAttrs, ...restProps}] =
39
+ splitLayoutProps(props);
39
40
 
40
41
  restProps = mergeDeep(
41
42
  {style: {display: 'flex', overflow: 'hidden', position: 'relative'}},
42
43
  {style: layoutProps},
43
- {[TEST_ID]: testId},
44
+ {[TEST_ID]: testId, ...domAttrs},
44
45
  restProps
45
46
  );
46
47
 
@@ -93,6 +93,9 @@ export interface TabContainerConfig {
93
93
 
94
94
  /** @internal */
95
95
  xhImpl?: boolean;
96
+
97
+ /** See {@link HoistBase.xhName}. */
98
+ xhName?: string;
96
99
  }
97
100
 
98
101
  /**
@@ -149,6 +152,7 @@ export class TabContainerModel extends HoistModel {
149
152
  refreshMode = 'onShowLazy',
150
153
  persistWith,
151
154
  emptyText = 'No tabs to display.',
155
+ xhName = null,
152
156
  xhImpl = false,
153
157
  switcher = {mode: 'static'}
154
158
  }: TabContainerConfig,
@@ -157,6 +161,7 @@ export class TabContainerModel extends HoistModel {
157
161
  super();
158
162
  makeObservable(this);
159
163
  this.xhImpl = xhImpl;
164
+ this.xhName = xhName;
160
165
 
161
166
  this.depth = depth;
162
167
  this.renderMode = renderMode;
@@ -168,6 +173,7 @@ export class TabContainerModel extends HoistModel {
168
173
  this.setTabs(tabs);
169
174
  this.refreshContextModel = new RefreshContextModel();
170
175
  this.refreshContextModel.xhImpl = xhImpl;
176
+ this.refreshContextModel.xhName = this.childXhName('refreshContextModel');
171
177
  this.switcherConfig = switcher;
172
178
  this.dynamicTabSwitcherModel = this.parseSwitcher(switcher);
173
179
 
@@ -419,7 +425,9 @@ export class TabContainerModel extends HoistModel {
419
425
  if (!switcher || switcher.mode === 'static') return null;
420
426
  throwIf(XH.isMobileApp, 'DynamicTabSwitcherModel not supported for mobile TabContainer.');
421
427
 
422
- return this.markManaged(new DynamicTabSwitcherModel(switcher, this));
428
+ const ret = this.markManaged(new DynamicTabSwitcherModel(switcher, this));
429
+ ret.xhName = this.childXhName('dynamicTabSwitcherModel');
430
+ return ret;
423
431
  }
424
432
 
425
433
  private initPersist({
@@ -75,6 +75,9 @@ export interface TabConfig {
75
75
 
76
76
  /** @internal */
77
77
  xhImpl?: boolean;
78
+
79
+ /** See {@link HoistBase.xhName}. */
80
+ xhName?: string;
78
81
  }
79
82
 
80
83
  /**
@@ -119,6 +122,7 @@ export class TabModel extends HoistModel {
119
122
  content,
120
123
  refreshMode,
121
124
  renderMode,
125
+ xhName = null,
122
126
  xhImpl = false
123
127
  }: TabConfig,
124
128
  containerModel: TabContainerModel
@@ -133,6 +137,7 @@ export class TabModel extends HoistModel {
133
137
  );
134
138
 
135
139
  this.id = id.toString();
140
+ this.xhName = xhName ?? containerModel.childXhName(this.id);
136
141
  this.title = title;
137
142
  this.icon = icon;
138
143
  this.tooltip = tooltip;
@@ -64,8 +64,10 @@ export class SplitTreeMapModel extends HoistModel {
64
64
  mapTitleFn,
65
65
  showSplitter = false,
66
66
  orientation = 'vertical',
67
+ xhName = null,
67
68
  ...rest
68
69
  } = config ?? {};
70
+ this.xhName = xhName;
69
71
 
70
72
  this.mapFilter = withDefault(mapFilter, this.defaultMapFilter);
71
73
  this.mapTitleFn = mapTitleFn;
@@ -77,8 +79,16 @@ export class SplitTreeMapModel extends HoistModel {
77
79
  );
78
80
  this.orientation = orientation;
79
81
 
80
- this.primaryMapModel = new TreeMapModel({...rest, filter: r => this.mapFilter(r)});
81
- this.secondaryMapModel = new TreeMapModel({...rest, filter: r => !this.mapFilter(r)});
82
+ this.primaryMapModel = new TreeMapModel({
83
+ xhName: this.childXhName('primaryMapModel'),
84
+ ...rest,
85
+ filter: r => this.mapFilter(r)
86
+ });
87
+ this.secondaryMapModel = new TreeMapModel({
88
+ xhName: this.childXhName('secondaryMapModel'),
89
+ ...rest,
90
+ filter: r => !this.mapFilter(r)
91
+ });
82
92
  }
83
93
 
84
94
  // Getters derived from both underlying TreeMapModels.
@@ -90,6 +90,9 @@ export interface TreeMapConfig {
90
90
 
91
91
  /** Data filter to apply to records. */
92
92
  filter?: FilterLike;
93
+
94
+ /** See {@link HoistBase.xhName}. */
95
+ xhName?: string;
93
96
  }
94
97
 
95
98
  export interface TreeMapModelDefaults {
@@ -178,8 +181,10 @@ export class TreeMapModel extends HoistModel {
178
181
  onDoubleClick,
179
182
  tooltip = true,
180
183
  emptyText,
181
- filter
184
+ filter,
185
+ xhName = null
182
186
  }: TreeMapConfig = config ?? {};
187
+ this.xhName = xhName;
183
188
 
184
189
  this.gridModel = gridModel;
185
190
  this.store = store ? store : gridModel ? gridModel.store : null;
@@ -154,6 +154,9 @@ export interface ViewManagerConfig {
154
154
  * ViewManager menu.
155
155
  */
156
156
  viewMenuItemFn?: (view: ViewInfo, model: ViewManagerModel) => ReactNode;
157
+
158
+ /** See {@link HoistBase.xhName}. */
159
+ xhName?: string;
157
160
  }
158
161
 
159
162
  /**
@@ -328,10 +331,12 @@ export class ViewManagerModel<T = PlainObject> extends HoistModel {
328
331
  enableGlobal = true,
329
332
  enableSharing = true,
330
333
  preserveUnsavedChanges = true,
331
- initialViewSpec = null
334
+ initialViewSpec = null,
335
+ xhName = null
332
336
  }: ViewManagerConfig) {
333
337
  super();
334
338
  makeObservable(this);
339
+ this.xhName = xhName ?? type;
335
340
 
336
341
  throwIf(
337
342
  !enableDefault && !initialViewSpec,
@@ -301,6 +301,9 @@ export interface ZoneGridConfig {
301
301
 
302
302
  /** @internal */
303
303
  xhImpl?: boolean;
304
+
305
+ /** See {@link HoistBase.xhName}. */
306
+ xhName?: string;
304
307
  }
305
308
 
306
309
  /**
@@ -364,8 +367,10 @@ export class ZoneGridModel extends HoistModel {
364
367
  restoreDefaultsFn,
365
368
  restoreDefaultsWarning,
366
369
  persistWith,
370
+ xhName = null,
367
371
  ...rest
368
372
  } = config;
373
+ this.xhName = xhName;
369
374
 
370
375
  this.availableColumns = columns
371
376
  .filter(it => !executeIfFunction(it.omit))
@@ -392,6 +397,7 @@ export class ZoneGridModel extends HoistModel {
392
397
  this.setGroupBy(groupBy);
393
398
 
394
399
  this.mapperModel = this.parseMapperModel(zoneMapperModel);
400
+ if (this.mapperModel) this.mapperModel.xhName = this.childXhName('mapperModel');
395
401
  if (persistWith) initPersist(this, persistWith);
396
402
 
397
403
  this.addReaction({
@@ -569,6 +575,7 @@ export class ZoneGridModel extends HoistModel {
569
575
  //-----------------------
570
576
  private createGridModel(config: GridConfig): GridModel {
571
577
  return new GridModel({
578
+ xhName: this.childXhName('gridModel'),
572
579
  ...config,
573
580
  xhImpl: true,
574
581
  contextMenu: withDefault(config.contextMenu, this.getDefaultContextMenu),
package/core/HoistBase.ts CHANGED
@@ -92,6 +92,12 @@ export abstract class HoistBase {
92
92
  */
93
93
  xhImpl: boolean = undefined;
94
94
 
95
+ /**
96
+ * Optional developer-facing name for this instance, shown in place of its class name in log
97
+ * output, telemetry, and the Inspector. Uniqueness is not enforced.
98
+ */
99
+ xhName: string = null;
100
+
95
101
  // Internal State
96
102
  private managedInstances = [];
97
103
  private disposers = [];
@@ -272,6 +278,11 @@ export abstract class HoistBase {
272
278
  return getOrCreate(this, '_xhId', XH.genId);
273
279
  }
274
280
 
281
+ /** Name for a child of this object - `{xhName}.{key}`, or null if this object is unnamed. */
282
+ childXhName(key: string): string {
283
+ return this.xhName ? `${this.xhName}.${key}` : null;
284
+ }
285
+
275
286
  /**
276
287
  * Mark an object as managed by this object.
277
288
  *
@@ -76,6 +76,7 @@ export interface BoxProps
76
76
  extends
77
77
  LayoutProps,
78
78
  TestSupportProps,
79
+ DomAttrsProps,
79
80
  Omit<HTMLAttributes<HTMLDivElement>, 'onChange' | 'contextMenu'> {}
80
81
 
81
82
  /**
@@ -99,6 +100,32 @@ export interface TestSupportProps {
99
100
  testId?: string;
100
101
  }
101
102
 
103
+ /**
104
+ * Props to support applying arbitrary HTML attributes to the primary DOM element rendered by a
105
+ * component.
106
+ *
107
+ * Intended as an escape hatch for `data-*` and `aria-*` attributes that Hoist does not model with
108
+ * a dedicated prop - e.g. attributes read by analytics libraries, browser extensions, or
109
+ * third-party test tooling. Hoist components pick the props they pass to the elements they render,
110
+ * so attributes specified any other way are typically dropped and never reach the DOM.
111
+ */
112
+ export interface DomAttrsProps {
113
+ /**
114
+ * Additional HTML attributes to apply to the primary DOM element rendered by this component -
115
+ * the same element that receives `data-testid` when {@link TestSupportProps.testId} is set.
116
+ *
117
+ * Keys are constrained to `data-*` and `aria-*` attributes, plus `role`, so this cannot be
118
+ * used to override attributes the component manages itself (`className`, `style`, `value`).
119
+ * Note the constraint applies to object literals only - a pre-built `Record<string, string>`
120
+ * will still assign, for the rare case that calls for an attribute outside this set. Take care
121
+ * there: precedence against component-managed attributes is not guaranteed and varies by
122
+ * component.
123
+ */
124
+ domAttrs?: Record<`data-${string}` | `aria-${string}`, string | number | boolean> & {
125
+ role?: string;
126
+ };
127
+ }
128
+
102
129
  export interface LayoutProps {
103
130
  margin?: string | number | boolean;
104
131
  marginTop?: string | number | boolean;
package/core/README.md CHANGED
@@ -117,6 +117,19 @@ class MyModel extends HoistModel {
117
117
  }
118
118
  ```
119
119
 
120
+ **Instance Naming**
121
+
122
+ Set `xhName` to distinguish instances of the same class in logs, telemetry, and the Inspector.
123
+ Config-driven models accept it as a config; any `HoistBase` can set it directly.
124
+
125
+ ```typescript
126
+ new GridModel({xhName: 'positionsGrid', store: {...}});
127
+
128
+ export class PositionsModel extends HoistModel {
129
+ override xhName = 'positions';
130
+ }
131
+ ```
132
+
120
133
  ### Core API
121
134
 
122
135
  | Method | Purpose |
@@ -128,6 +141,8 @@ class MyModel extends HoistModel {
128
141
  | `setBindable(prop, val)` | Set value via conventional setter |
129
142
  | `destroy()` | Clean up all managed resources |
130
143
  | `xhId` | Unique identifier for this instance |
144
+ | `xhName` | Optional developer-facing name for logs, telemetry, and Inspector |
145
+ | `childXhName(key)` | `{xhName}.{key}`, for naming child objects |
131
146
 
132
147
  ## HoistModel
133
148
 
@@ -538,6 +553,37 @@ Button.defaults.minimal = false;
538
553
  This pattern is analogous to `static defaults` on Model and Service classes (e.g.
539
554
  `GridModel.defaults`), adapted for functional components created via `hoistCmp`.
540
555
 
556
+ ### Extra DOM Attributes with `domAttrs`
557
+
558
+ Hoist components choose which props they pass along to the elements they render. An attribute that
559
+ a component does not model with a dedicated prop is therefore dropped and never reaches the DOM.
560
+
561
+ `DomAttrsProps` provides the escape hatch. Components that support it accept a `domAttrs` object
562
+ and apply its entries to their primary DOM element - the same element that receives `data-testid`
563
+ when `testId` is set.
564
+
565
+ ```typescript
566
+ textInput({
567
+ bind: 'email',
568
+ testId: 'signup-email',
569
+ domAttrs: {'data-analytics-id': 'signup-email', 'aria-describedby': 'email-help'}
570
+ });
571
+ ```
572
+
573
+ Keys are constrained by type to `data-*` and `aria-*` attributes, plus `role`, so the prop cannot
574
+ be used to reach for attributes the component manages itself (`className`, `style`, `value`). The
575
+ constraint applies to object literals only; a pre-built `Record<string, string>` still assigns, for
576
+ the rare attribute outside that set. Take care there - precedence against component-managed
577
+ attributes is not guaranteed and varies by component.
578
+
579
+ As with `style`, an inline `domAttrs` literal is a new object on each render, so hoist a constant
580
+ out of the render function when passing one to a component that relies on `memo`.
581
+
582
+ Support is provided by `Box` and the layout components built on it (and therefore by `Panel`,
583
+ `Toolbar`, and similar containers), by `Button` and `ButtonGroup`, by `Card` and `Badge`, and by
584
+ the input components in `/cmp/input/`. See [`/cmp/input/README.md`](../cmp/input/README.md) for
585
+ notes specific to inputs.
586
+
541
587
  ## Element Factories
542
588
 
543
589
  **File**: `elem.ts`
@@ -34,7 +34,11 @@ export async function installServicesAsync(
34
34
  const notSvc = serviceClasses.find((it: any) => !it.isHoistService);
35
35
  throwIf(notSvc, `Cannot initialize ${notSvc?.name} - does not extend HoistService`);
36
36
 
37
- const svcs = serviceClasses.map(c => createSingleton(c));
37
+ const svcs = serviceClasses.map(c => {
38
+ const svc = createSingleton(c);
39
+ svc.xhName ??= camelCase(c.name);
40
+ return svc;
41
+ });
38
42
  await initServicesInternalAsync(svcs, ctx);
39
43
 
40
44
  svcs.forEach(svc => {
@@ -18,6 +18,8 @@ import {RefreshContextModel} from './';
18
18
  * The `XH.refreshAppAsync()` convenience method is the recommended entry-point for apps to call.
19
19
  */
20
20
  export class RootRefreshContextModel extends RefreshContextModel {
21
+ override xhName = 'refreshContextModel';
22
+
21
23
  override async doLoadAsync(loadSpec: LoadSpec) {
22
24
  const {appModel} = XH;
23
25
  if (appModel.loadSupport) {