@xh/hoist 87.1.1 → 87.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.
- package/.npmignore +39 -0
- package/CHANGELOG.md +108 -20
- package/admin/AdminUtils.ts +64 -0
- package/admin/App.scss +88 -0
- package/admin/columns/UserData.ts +30 -3
- package/admin/detail/RestDetailModel.ts +64 -0
- package/admin/detail/RestDetailPanel.ts +106 -0
- package/admin/tabs/activity/tracking/ActivityTrackingModel.ts +27 -94
- package/admin/tabs/activity/tracking/ActivityTrackingPanel.ts +23 -60
- package/admin/tabs/activity/tracking/detail/ActivityDetailView.ts +1 -2
- package/admin/tabs/general/alertBanner/AlertBannerPanel.ts +2 -13
- package/admin/tabs/general/config/ConfigDetailPanel.ts +72 -0
- package/admin/tabs/general/config/ConfigPanel.ts +41 -53
- package/admin/tabs/general/config/ConfigPanelModel.ts +3 -0
- package/admin/tabs/general/config/ConfigValue.scss +14 -0
- package/admin/tabs/general/config/ConfigValue.ts +104 -65
- package/admin/tabs/userData/jsonblob/JsonBlobDetailPanel.ts +93 -0
- package/admin/tabs/userData/jsonblob/JsonBlobModel.ts +4 -0
- package/admin/tabs/userData/jsonblob/JsonBlobPanel.ts +2 -0
- package/admin/tabs/userData/prefs/UserPreferenceDetailPanel.ts +62 -0
- package/admin/tabs/userData/prefs/UserPreferenceModel.ts +10 -3
- package/admin/tabs/userData/prefs/UserPreferencePanel.ts +3 -1
- package/admin/tabs/userData/roles/details/RoleDetails.scss +2 -19
- package/admin/tabs/userData/roles/details/RoleDetails.ts +13 -13
- package/appcontainer/AboutDialogModel.ts +1 -0
- package/appcontainer/AppContainerModel.ts +3 -0
- package/appcontainer/AppStateModel.ts +1 -0
- package/appcontainer/BannerSourceModel.ts +1 -0
- package/appcontainer/ChangelogDialogModel.ts +1 -0
- package/appcontainer/ColChooserOptionsModel.ts +1 -0
- package/appcontainer/ExceptionDialogModel.ts +1 -0
- package/appcontainer/FeedbackDialogModel.ts +1 -0
- package/appcontainer/ImpersonationBarModel.ts +1 -0
- package/appcontainer/MessageSourceModel.ts +1 -0
- package/appcontainer/OptionsDialogModel.ts +1 -0
- package/appcontainer/PageStateModel.ts +1 -0
- package/appcontainer/RouterModel.ts +2 -0
- package/appcontainer/SizingModeModel.ts +1 -0
- package/appcontainer/ThemeModel.ts +1 -0
- package/appcontainer/ToastSourceModel.ts +1 -0
- package/appcontainer/UserAgentModel.ts +1 -0
- package/appcontainer/ViewportSizeModel.ts +1 -0
- package/build/types/admin/AdminUtils.d.ts +29 -0
- package/build/types/admin/columns/UserData.d.ts +3 -0
- package/build/types/admin/detail/RestDetailModel.d.ts +20 -0
- package/build/types/admin/detail/RestDetailPanel.d.ts +34 -0
- package/build/types/admin/tabs/activity/tracking/ActivityTrackingModel.d.ts +5 -17
- package/build/types/admin/tabs/general/config/ConfigDetailPanel.d.ts +7 -0
- package/build/types/admin/tabs/general/config/ConfigValue.d.ts +10 -4
- package/build/types/admin/tabs/userData/jsonblob/JsonBlobDetailPanel.d.ts +3 -0
- package/build/types/admin/tabs/userData/prefs/UserPreferenceDetailPanel.d.ts +3 -0
- package/build/types/admin/tabs/userData/prefs/UserPreferenceModel.d.ts +3 -0
- package/build/types/appcontainer/AboutDialogModel.d.ts +1 -0
- package/build/types/appcontainer/AppContainerModel.d.ts +1 -0
- package/build/types/appcontainer/AppStateModel.d.ts +1 -0
- package/build/types/appcontainer/BannerSourceModel.d.ts +1 -0
- package/build/types/appcontainer/ChangelogDialogModel.d.ts +1 -0
- package/build/types/appcontainer/ColChooserOptionsModel.d.ts +1 -0
- package/build/types/appcontainer/ExceptionDialogModel.d.ts +1 -0
- package/build/types/appcontainer/FeedbackDialogModel.d.ts +1 -0
- package/build/types/appcontainer/ImpersonationBarModel.d.ts +1 -0
- package/build/types/appcontainer/MessageSourceModel.d.ts +1 -0
- package/build/types/appcontainer/OptionsDialogModel.d.ts +1 -0
- package/build/types/appcontainer/PageStateModel.d.ts +1 -0
- package/build/types/appcontainer/RouterModel.d.ts +1 -0
- package/build/types/appcontainer/SizingModeModel.d.ts +1 -0
- package/build/types/appcontainer/ThemeModel.d.ts +1 -0
- package/build/types/appcontainer/ToastSourceModel.d.ts +1 -0
- package/build/types/appcontainer/UserAgentModel.d.ts +1 -0
- package/build/types/appcontainer/ViewportSizeModel.d.ts +1 -0
- package/build/types/cmp/ag-grid/AgGridModel.d.ts +3 -1
- package/build/types/cmp/card/Card.d.ts +2 -2
- package/build/types/cmp/chart/ChartModel.d.ts +2 -0
- package/build/types/cmp/dataview/DataViewModel.d.ts +2 -0
- package/build/types/cmp/daterange/DateRangePickerModel.d.ts +266 -0
- package/build/types/cmp/daterange/DateRangePresets.d.ts +26 -0
- package/build/types/cmp/daterange/DateRangeUtils.d.ts +96 -0
- package/build/types/cmp/daterange/Types.d.ts +152 -0
- package/build/types/cmp/daterange/index.d.ts +4 -0
- package/build/types/cmp/filter/FilterChooserModel.d.ts +3 -1
- package/build/types/cmp/form/FormModel.d.ts +3 -1
- package/build/types/cmp/grid/GridModel.d.ts +2 -0
- package/build/types/cmp/grid/Types.d.ts +2 -0
- package/build/types/cmp/grid/filter/GridFilterModel.d.ts +1 -1
- package/build/types/cmp/grid/impl/ColumnWidthCalculator.d.ts +1 -1
- package/build/types/cmp/grouping/GroupingChooserModel.d.ts +3 -1
- package/build/types/cmp/input/HoistInputProps.d.ts +49 -2
- package/build/types/cmp/tab/TabContainerModel.d.ts +3 -1
- package/build/types/cmp/tab/TabModel.d.ts +3 -1
- package/build/types/cmp/treemap/TreeMapModel.d.ts +2 -0
- package/build/types/cmp/viewmanager/ViewManagerModel.d.ts +2 -0
- package/build/types/cmp/zoneGrid/ZoneGridModel.d.ts +2 -0
- package/build/types/core/HoistBase.d.ts +7 -0
- package/build/types/core/HoistProps.d.ts +26 -1
- package/build/types/core/model/RootRefreshContextModel.d.ts +1 -0
- package/build/types/core/types/Telemetry.d.ts +1 -1
- package/build/types/data/Store.d.ts +3 -1
- package/build/types/data/StoreSelectionModel.d.ts +3 -1
- package/build/types/data/cube/Cube.d.ts +5 -2
- package/build/types/data/cube/View.d.ts +2 -0
- package/build/types/desktop/cmp/appOption/AutoRefreshAppOption.d.ts +3 -0
- package/build/types/desktop/cmp/appOption/ThemeAppOption.d.ts +3 -0
- package/build/types/desktop/cmp/button/Button.d.ts +2 -2
- package/build/types/desktop/cmp/button/ButtonGroup.d.ts +2 -2
- package/build/types/desktop/cmp/dash/DashConfig.d.ts +2 -0
- package/build/types/desktop/cmp/dash/DashViewModel.d.ts +3 -1
- package/build/types/desktop/cmp/dash/canvas/DashCanvasModel.d.ts +1 -1
- package/build/types/desktop/cmp/dash/container/DashContainerModel.d.ts +1 -1
- package/build/types/desktop/cmp/daterange/DateRangePicker.d.ts +66 -0
- package/build/types/desktop/cmp/daterange/impl/CustomTab.d.ts +3 -0
- package/build/types/desktop/cmp/daterange/impl/DateRangePickerLocalModel.d.ts +121 -0
- package/build/types/desktop/cmp/daterange/impl/PeriodTab.d.ts +2 -0
- package/build/types/desktop/cmp/daterange/impl/PresetsTab.d.ts +3 -0
- package/build/types/desktop/cmp/daterange/impl/RelativeTab.d.ts +3 -0
- package/build/types/desktop/cmp/daterange/impl/TabUtils.d.ts +21 -0
- package/build/types/desktop/cmp/daterange/index.d.ts +2 -0
- package/build/types/desktop/cmp/dock/DockContainerModel.d.ts +3 -1
- package/build/types/desktop/cmp/dock/DockViewModel.d.ts +3 -1
- package/build/types/desktop/cmp/grouping/GroupingChooser.d.ts +4 -1
- package/build/types/desktop/cmp/input/IntentInput.d.ts +38 -0
- package/build/types/desktop/cmp/input/NumberInput.d.ts +2 -2
- package/build/types/desktop/cmp/input/SegmentedControl.d.ts +25 -4
- package/build/types/desktop/cmp/input/TextArea.d.ts +2 -2
- package/build/types/desktop/cmp/input/TextInput.d.ts +2 -2
- package/build/types/desktop/cmp/input/index.d.ts +1 -0
- package/build/types/desktop/cmp/leftrightchooser/LeftRightChooserModel.d.ts +3 -1
- package/build/types/desktop/cmp/panel/PanelModel.d.ts +3 -1
- package/build/types/desktop/cmp/rest/RestGridModel.d.ts +1 -1
- package/build/types/desktop/cmp/tab/impl/TabContainer.d.ts +1 -1
- package/build/types/desktop/hooks/UseHotkeys.d.ts +4 -3
- package/build/types/icon/Icon.d.ts +3 -0
- package/build/types/inspector/impl/InspectorUtils.d.ts +3 -0
- package/build/types/inspector/instances/InstancesModel.d.ts +4 -0
- package/build/types/kit/react-day-picker/index.d.ts +3 -3
- package/build/types/mobile/cmp/button/Button.d.ts +2 -2
- package/build/types/mobile/cmp/input/NumberInput.d.ts +2 -2
- package/build/types/mobile/cmp/input/SegmentedControl.d.ts +18 -4
- package/build/types/mobile/cmp/input/TextArea.d.ts +2 -2
- package/build/types/mobile/cmp/input/TextInput.d.ts +2 -2
- package/build/types/mobile/cmp/navigator/NavigatorModel.d.ts +8 -2
- package/build/types/mobile/cmp/navigator/PageModel.d.ts +3 -1
- package/build/types/mobile/cmp/tab/impl/TabContainer.d.ts +1 -1
- package/build/types/svc/InspectorService.d.ts +1 -0
- package/build/types/utils/js/LangUtils.d.ts +24 -5
- package/cmp/README.md +1 -0
- package/cmp/ag-grid/AgGridModel.ts +5 -0
- package/cmp/badge/Badge.ts +5 -3
- package/cmp/card/Card.ts +4 -3
- package/cmp/chart/ChartModel.ts +11 -1
- package/cmp/clock/Clock.ts +1 -1
- package/cmp/dataview/DataViewModel.ts +7 -1
- package/cmp/daterange/DateRangePickerModel.ts +628 -0
- package/cmp/daterange/DateRangePresets.ts +274 -0
- package/cmp/daterange/DateRangeUtils.ts +557 -0
- package/cmp/daterange/README.md +434 -0
- package/cmp/daterange/Types.ts +203 -0
- package/cmp/daterange/index.ts +10 -0
- package/cmp/filter/FilterChooserModel.ts +7 -4
- package/cmp/form/FormModel.ts +6 -0
- package/cmp/form/README.md +1 -0
- package/cmp/grid/GridModel.ts +24 -4
- package/cmp/grid/Types.ts +3 -0
- package/cmp/grid/filter/GridFilterModel.ts +3 -1
- package/cmp/grid/impl/ColumnWidthCalculator.ts +5 -10
- package/cmp/grouping/GroupingChooserModel.ts +6 -1
- package/cmp/input/HoistInputProps.ts +50 -2
- package/cmp/input/README.md +42 -0
- package/cmp/layout/Box.ts +3 -2
- package/cmp/tab/TabContainerModel.ts +9 -1
- package/cmp/tab/TabModel.ts +5 -0
- package/cmp/treemap/SplitTreeMapModel.ts +12 -2
- package/cmp/treemap/TreeMapModel.ts +6 -1
- package/cmp/viewmanager/ViewManagerModel.ts +6 -1
- package/cmp/zoneGrid/ZoneGridModel.ts +7 -0
- package/core/HoistBase.ts +11 -0
- package/core/HoistProps.ts +27 -0
- package/core/README.md +46 -0
- package/core/impl/InstallServices.ts +5 -1
- package/core/model/RootRefreshContextModel.ts +2 -0
- package/core/types/Telemetry.ts +1 -1
- package/data/README.md +3 -1
- package/data/Store.ts +7 -0
- package/data/StoreSelectionModel.ts +5 -1
- package/data/cube/Cube.ts +12 -3
- package/data/cube/View.ts +5 -1
- package/desktop/README.md +2 -0
- package/desktop/appcontainer/LoginPanel.ts +2 -0
- package/desktop/cmp/button/Button.scss +71 -42
- package/desktop/cmp/button/Button.ts +10 -1
- package/desktop/cmp/button/ButtonGroup.ts +4 -1
- package/desktop/cmp/button/grid/ColChooserButton.ts +9 -1
- package/desktop/cmp/button/zoneGrid/ZoneMapperButton.ts +1 -0
- package/desktop/cmp/dash/DashConfig.ts +3 -0
- package/desktop/cmp/dash/DashViewModel.ts +14 -1
- package/desktop/cmp/dash/canvas/DashCanvasModel.ts +3 -1
- package/desktop/cmp/dash/container/DashContainerModel.ts +3 -1
- package/desktop/cmp/daterange/DateRangePicker.scss +577 -0
- package/desktop/cmp/daterange/DateRangePicker.ts +357 -0
- package/desktop/cmp/daterange/impl/CustomTab.ts +165 -0
- package/desktop/cmp/daterange/impl/DateRangePickerLocalModel.ts +482 -0
- package/desktop/cmp/daterange/impl/PeriodTab.ts +138 -0
- package/desktop/cmp/daterange/impl/PresetsTab.ts +58 -0
- package/desktop/cmp/daterange/impl/RelativeTab.ts +108 -0
- package/desktop/cmp/daterange/impl/TabUtils.ts +34 -0
- package/desktop/cmp/daterange/index.ts +8 -0
- package/desktop/cmp/dock/DockContainerModel.ts +6 -1
- package/desktop/cmp/dock/DockViewModel.ts +6 -1
- package/desktop/cmp/filechooser/FileChooser.ts +4 -0
- package/desktop/cmp/form/FormField.ts +1 -0
- package/desktop/cmp/grid/editors/DateEditor.ts +11 -2
- package/desktop/cmp/grouping/GroupingChooser.ts +8 -2
- package/desktop/cmp/input/Checkbox.ts +1 -0
- package/desktop/cmp/input/CodeInput.ts +1 -0
- package/desktop/cmp/input/DateInput.scss +1 -1
- package/desktop/cmp/input/DateInput.ts +1 -0
- package/desktop/cmp/input/IntentInput.scss +202 -0
- package/desktop/cmp/input/IntentInput.ts +213 -0
- package/desktop/cmp/input/NumberInput.ts +11 -2
- package/desktop/cmp/input/Picker.ts +1 -0
- package/desktop/cmp/input/RadioInput.ts +2 -1
- package/desktop/cmp/input/SegmentedControl.scss +57 -5
- package/desktop/cmp/input/SegmentedControl.ts +66 -11
- package/desktop/cmp/input/Select.ts +9 -7
- package/desktop/cmp/input/Slider.ts +2 -0
- package/desktop/cmp/input/SwitchInput.ts +1 -0
- package/desktop/cmp/input/TextArea.ts +11 -2
- package/desktop/cmp/input/TextInput.ts +11 -2
- package/desktop/cmp/input/index.ts +1 -0
- package/desktop/cmp/leftrightchooser/LeftRightChooserModel.ts +5 -0
- package/desktop/cmp/panel/Panel.ts +8 -5
- package/desktop/cmp/panel/PanelModel.ts +8 -0
- package/desktop/cmp/panel/impl/ResizeContainer.ts +2 -1
- package/desktop/cmp/rest/RestGridModel.ts +4 -0
- package/desktop/cmp/tab/impl/TabContainer.ts +2 -0
- package/desktop/cmp/toolbar/Toolbar.scss +20 -6
- package/desktop/hooks/UseHotkeys.ts +41 -12
- package/docs/README.md +2 -0
- package/docs/build-and-publish.md +23 -7
- package/docs/doc-registry.json +8 -0
- package/docs/version-compatibility.md +2 -0
- package/icon/Icon.ts +9 -0
- package/icon/index.ts +24 -0
- package/inspector/README.md +8 -2
- package/inspector/impl/InspectorUtils.ts +12 -0
- package/inspector/instances/DiagnosticsModel.ts +3 -2
- package/inspector/instances/InstancesModel.ts +81 -14
- package/inspector/instances/InstancesPanel.ts +6 -0
- package/kit/react-day-picker/index.ts +3 -3
- package/mobile/appcontainer/LoginPanel.ts +2 -0
- package/mobile/cmp/button/Button.ts +16 -3
- package/mobile/cmp/form/FormField.ts +1 -0
- package/mobile/cmp/input/Checkbox.ts +1 -0
- package/mobile/cmp/input/DateInput.ts +1 -0
- package/mobile/cmp/input/Label.ts +13 -8
- package/mobile/cmp/input/NumberInput.ts +11 -2
- package/mobile/cmp/input/SearchInput.ts +1 -0
- package/mobile/cmp/input/SegmentedControl.scss +36 -6
- package/mobile/cmp/input/SegmentedControl.ts +37 -11
- package/mobile/cmp/input/Select.ts +2 -0
- package/mobile/cmp/input/SwitchInput.ts +1 -0
- package/mobile/cmp/input/TextArea.ts +11 -2
- package/mobile/cmp/input/TextInput.ts +11 -2
- package/mobile/cmp/navigator/NavigatorModel.ts +20 -8
- package/mobile/cmp/navigator/PageModel.ts +6 -1
- package/mobile/cmp/tab/impl/TabContainer.ts +9 -1
- package/package.json +19 -19
- package/styles/vars.scss +32 -2
- package/svc/InspectorService.ts +2 -0
- package/svc/TraceService.ts +5 -2
- package/utils/js/LangUtils.ts +37 -5
package/cmp/form/FormModel.ts
CHANGED
|
@@ -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
|
|
package/cmp/form/README.md
CHANGED
|
@@ -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
|
|
package/cmp/grid/GridModel.ts
CHANGED
|
@@ -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({
|
|
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({
|
|
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(
|
|
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
|
-
|
|
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;
|
|
@@ -111,8 +111,7 @@ export class ColumnWidthCalculator {
|
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
async calcLevelWidthAsync(gridModel, records, column, options, indentationPx = 0) {
|
|
114
|
-
const {field, getValueFn, renderer, rendererIsComplex,
|
|
115
|
-
column,
|
|
114
|
+
const {field, getValueFn, renderer, rendererIsComplex, cellClassRules} = column,
|
|
116
115
|
{store, sizingMode, rowClassFn, rowClassRules} = gridModel,
|
|
117
116
|
bufferPx = column.autosizeBufferPx ?? options.bufferPx;
|
|
118
117
|
|
|
@@ -163,11 +162,11 @@ export class ColumnWidthCalculator {
|
|
|
163
162
|
await forEachAsync(sample, ({value, records}) => {
|
|
164
163
|
// Get unique combinations of row and cell classes applied
|
|
165
164
|
const classNames = new Set<string>();
|
|
166
|
-
if (rowClassFn ||
|
|
165
|
+
if (rowClassFn || !isEmpty(rowClassRules) || !isEmpty(cellClassRules)) {
|
|
167
166
|
records.forEach(record => {
|
|
168
167
|
const rawValue = getValueFn({record, field, column, gridModel, store}),
|
|
169
168
|
rowClass = this.getRowClass(gridModel, record),
|
|
170
|
-
cellClass = this.getCellClass(
|
|
169
|
+
cellClass = this.getCellClass(column, record, rawValue);
|
|
171
170
|
classNames.add(rowClass + '|' + cellClass);
|
|
172
171
|
});
|
|
173
172
|
} else {
|
|
@@ -342,14 +341,10 @@ export class ColumnWidthCalculator {
|
|
|
342
341
|
return this._cellEl;
|
|
343
342
|
}
|
|
344
343
|
|
|
345
|
-
getCellClass(
|
|
346
|
-
const {
|
|
344
|
+
getCellClass(column, record, value) {
|
|
345
|
+
const {cellClassRules} = column,
|
|
347
346
|
ret = [];
|
|
348
347
|
|
|
349
|
-
if (cellClassFn) {
|
|
350
|
-
ret.push(cellClassFn({record, column, gridModel}));
|
|
351
|
-
}
|
|
352
|
-
|
|
353
348
|
if (cellClassRules) {
|
|
354
349
|
forOwn(cellClassRules, (fn, className) => {
|
|
355
350
|
if (fn({value, data: record})) {
|
|
@@ -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
|
+
}
|
package/cmp/input/README.md
CHANGED
|
@@ -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}] =
|
|
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
|
-
|
|
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({
|
package/cmp/tab/TabModel.ts
CHANGED
|
@@ -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({
|
|
81
|
-
|
|
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
|
*
|
package/core/HoistProps.ts
CHANGED
|
@@ -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 =>
|
|
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 => {
|