@deephaven/iris-grid 1.22.1 → 1.22.2-alpha-pivot-builder.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/README.md +284 -1
- package/dist/AdvancedFilterCreator.js +65 -102
- package/dist/AdvancedFilterCreator.js.map +1 -1
- package/dist/AdvancedFilterCreatorFilterItem.js +22 -37
- package/dist/AdvancedFilterCreatorFilterItem.js.map +1 -1
- package/dist/AdvancedFilterCreatorSelectValue.js +37 -63
- package/dist/AdvancedFilterCreatorSelectValue.js.map +1 -1
- package/dist/AdvancedFilterCreatorSelectValueList.js +29 -59
- package/dist/AdvancedFilterCreatorSelectValueList.js.map +1 -1
- package/dist/ColumnHeaderGroup.js +6 -8
- package/dist/ColumnHeaderGroup.js.map +1 -1
- package/dist/ColumnStatistics.js +22 -28
- package/dist/ColumnStatistics.js.map +1 -1
- package/dist/CommonTypes.d.ts +62 -2
- package/dist/CommonTypes.d.ts.map +1 -1
- package/dist/CommonTypes.js.map +1 -1
- package/dist/CrossColumnSearch.js +20 -29
- package/dist/CrossColumnSearch.js.map +1 -1
- package/dist/FilterInputField.js +19 -46
- package/dist/FilterInputField.js.map +1 -1
- package/dist/GotoRow.js +32 -29
- package/dist/GotoRow.js.map +1 -1
- package/dist/IrisGrid.d.ts +85 -2
- package/dist/IrisGrid.d.ts.map +1 -1
- package/dist/IrisGrid.js +709 -861
- package/dist/IrisGrid.js.map +1 -1
- package/dist/IrisGridBottomBar.js +10 -11
- package/dist/IrisGridBottomBar.js.map +1 -1
- package/dist/IrisGridCacheUtils.js +28 -8
- package/dist/IrisGridCacheUtils.js.map +1 -1
- package/dist/IrisGridCellOverflowModal.js +30 -13
- package/dist/IrisGridCellOverflowModal.js.map +1 -1
- package/dist/IrisGridCellRendererUtils.js +6 -12
- package/dist/IrisGridCellRendererUtils.js.map +1 -1
- package/dist/IrisGridCopyHandler.js +28 -47
- package/dist/IrisGridCopyHandler.js.map +1 -1
- package/dist/IrisGridMetricCalculator.js +28 -51
- package/dist/IrisGridMetricCalculator.js.map +1 -1
- package/dist/IrisGridModel.d.ts +30 -1
- package/dist/IrisGridModel.d.ts.map +1 -1
- package/dist/IrisGridModel.js +36 -1
- package/dist/IrisGridModel.js.map +1 -1
- package/dist/IrisGridModelUpdater.js +32 -30
- package/dist/IrisGridModelUpdater.js.map +1 -1
- package/dist/IrisGridModelWidgetProps.d.ts +26 -0
- package/dist/IrisGridModelWidgetProps.d.ts.map +1 -0
- package/dist/IrisGridModelWidgetProps.js +2 -0
- package/dist/IrisGridModelWidgetProps.js.map +1 -0
- package/dist/IrisGridPartitionSelector.js +28 -62
- package/dist/IrisGridPartitionSelector.js.map +1 -1
- package/dist/IrisGridProxyModel.d.ts.map +1 -1
- package/dist/IrisGridProxyModel.js +41 -14
- package/dist/IrisGridProxyModel.js.map +1 -1
- package/dist/IrisGridRenderer.js +162 -264
- package/dist/IrisGridRenderer.js.map +1 -1
- package/dist/IrisGridTableModel.js +23 -16
- package/dist/IrisGridTableModel.js.map +1 -1
- package/dist/IrisGridTableModelTemplate.js +49 -73
- package/dist/IrisGridTableModelTemplate.js.map +1 -1
- package/dist/IrisGridTestUtils.js +19 -19
- package/dist/IrisGridTestUtils.js.map +1 -1
- package/dist/IrisGridTextCellRenderer.d.ts.map +1 -1
- package/dist/IrisGridTextCellRenderer.js +36 -53
- package/dist/IrisGridTextCellRenderer.js.map +1 -1
- package/dist/IrisGridTheme.js +9 -1
- package/dist/IrisGridTheme.js.map +1 -1
- package/dist/IrisGridThemeProvider.js +5 -7
- package/dist/IrisGridThemeProvider.js.map +1 -1
- package/dist/IrisGridTreeTableModel.js +5 -9
- package/dist/IrisGridTreeTableModel.js.map +1 -1
- package/dist/IrisGridUtils.d.ts +25 -2
- package/dist/IrisGridUtils.d.ts.map +1 -1
- package/dist/IrisGridUtils.js +297 -294
- package/dist/IrisGridUtils.js.map +1 -1
- package/dist/LazyIrisGrid.d.ts +1 -1
- package/dist/NoPastePermissionModal.js +3 -5
- package/dist/NoPastePermissionModal.js.map +1 -1
- package/dist/PendingDataBottomBar.js +27 -16
- package/dist/PendingDataBottomBar.js.map +1 -1
- package/dist/ToastBottomBar.js +16 -8
- package/dist/ToastBottomBar.js.map +1 -1
- package/dist/format-context-menus/CustomFormatAction.js +11 -24
- package/dist/format-context-menus/CustomFormatAction.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/key-handlers/CopyCellKeyHandler.js +3 -4
- package/dist/key-handlers/CopyCellKeyHandler.js.map +1 -1
- package/dist/key-handlers/CopyKeyHandler.js +1 -3
- package/dist/key-handlers/CopyKeyHandler.js.map +1 -1
- package/dist/key-handlers/ReverseKeyHandler.js +1 -3
- package/dist/key-handlers/ReverseKeyHandler.js.map +1 -1
- package/dist/mousehandlers/IrisGridCellOverflowMouseHandler.js +35 -59
- package/dist/mousehandlers/IrisGridCellOverflowMouseHandler.js.map +1 -1
- package/dist/mousehandlers/IrisGridColumnSelectMouseHandler.js +13 -29
- package/dist/mousehandlers/IrisGridColumnSelectMouseHandler.js.map +1 -1
- package/dist/mousehandlers/IrisGridColumnTooltipMouseHandler.js +8 -17
- package/dist/mousehandlers/IrisGridColumnTooltipMouseHandler.js.map +1 -1
- package/dist/mousehandlers/IrisGridContextMenuHandler.js +96 -199
- package/dist/mousehandlers/IrisGridContextMenuHandler.js.map +1 -1
- package/dist/mousehandlers/IrisGridDataSelectMouseHandler.js +2 -4
- package/dist/mousehandlers/IrisGridDataSelectMouseHandler.js.map +1 -1
- package/dist/mousehandlers/IrisGridFilterMouseHandler.js +16 -30
- package/dist/mousehandlers/IrisGridFilterMouseHandler.js.map +1 -1
- package/dist/mousehandlers/IrisGridPartitionedTableMouseHandler.js +5 -13
- package/dist/mousehandlers/IrisGridPartitionedTableMouseHandler.js.map +1 -1
- package/dist/mousehandlers/IrisGridRowTreeMouseHandler.js +20 -36
- package/dist/mousehandlers/IrisGridRowTreeMouseHandler.js.map +1 -1
- package/dist/mousehandlers/IrisGridSortMouseHandler.js +3 -5
- package/dist/mousehandlers/IrisGridSortMouseHandler.js.map +1 -1
- package/dist/mousehandlers/IrisGridTokenMouseHandler.js +9 -18
- package/dist/mousehandlers/IrisGridTokenMouseHandler.js.map +1 -1
- package/dist/mousehandlers/PendingMouseHandler.js +10 -18
- package/dist/mousehandlers/PendingMouseHandler.js.map +1 -1
- package/dist/sidebar/AdvancedSettingsMenu.js +12 -5
- package/dist/sidebar/AdvancedSettingsMenu.js.map +1 -1
- package/dist/sidebar/ChartBuilder.js +45 -102
- package/dist/sidebar/ChartBuilder.js.map +1 -1
- package/dist/sidebar/CustomColumnBuilder.js +35 -77
- package/dist/sidebar/CustomColumnBuilder.js.map +1 -1
- package/dist/sidebar/CustomColumnInput.js +9 -11
- package/dist/sidebar/CustomColumnInput.js.map +1 -1
- package/dist/sidebar/InputEditor.js +17 -24
- package/dist/sidebar/InputEditor.js.map +1 -1
- package/dist/sidebar/IrisGridTableOptionsWidgetProps.d.ts +22 -0
- package/dist/sidebar/IrisGridTableOptionsWidgetProps.d.ts.map +1 -0
- package/dist/sidebar/IrisGridTableOptionsWidgetProps.js +2 -0
- package/dist/sidebar/IrisGridTableOptionsWidgetProps.js.map +1 -0
- package/dist/sidebar/OptionType.d.ts +8 -0
- package/dist/sidebar/OptionType.d.ts.map +1 -1
- package/dist/sidebar/OptionType.js +7 -0
- package/dist/sidebar/OptionType.js.map +1 -1
- package/dist/sidebar/PluginTableOptionsErrorBoundary.d.ts +30 -0
- package/dist/sidebar/PluginTableOptionsErrorBoundary.d.ts.map +1 -0
- package/dist/sidebar/PluginTableOptionsErrorBoundary.js +55 -0
- package/dist/sidebar/PluginTableOptionsErrorBoundary.js.map +1 -0
- package/dist/sidebar/RollupRows.js +72 -113
- package/dist/sidebar/RollupRows.js.map +1 -1
- package/dist/sidebar/SelectDistinctBuilder.js +16 -33
- package/dist/sidebar/SelectDistinctBuilder.js.map +1 -1
- package/dist/sidebar/TableCsvExporter.js +50 -74
- package/dist/sidebar/TableCsvExporter.js.map +1 -1
- package/dist/sidebar/TableSaver.js +18 -42
- package/dist/sidebar/TableSaver.js.map +1 -1
- package/dist/sidebar/aggregations/AggregationEdit.js +25 -18
- package/dist/sidebar/aggregations/AggregationEdit.js.map +1 -1
- package/dist/sidebar/aggregations/AggregationUtils.js +1 -3
- package/dist/sidebar/aggregations/AggregationUtils.js.map +1 -1
- package/dist/sidebar/aggregations/Aggregations.js +45 -34
- package/dist/sidebar/aggregations/Aggregations.js.map +1 -1
- package/dist/sidebar/conditional-formatting/ColumnFormatEditor.js +35 -24
- package/dist/sidebar/conditional-formatting/ColumnFormatEditor.js.map +1 -1
- package/dist/sidebar/conditional-formatting/ConditionEditor.js +38 -21
- package/dist/sidebar/conditional-formatting/ConditionEditor.js.map +1 -1
- package/dist/sidebar/conditional-formatting/ConditionalFormatEditor.js +27 -11
- package/dist/sidebar/conditional-formatting/ConditionalFormatEditor.js.map +1 -1
- package/dist/sidebar/conditional-formatting/ConditionalFormattingMenu.js +9 -11
- package/dist/sidebar/conditional-formatting/ConditionalFormattingMenu.js.map +1 -1
- package/dist/sidebar/conditional-formatting/ConditionalFormattingUtils.js +48 -57
- package/dist/sidebar/conditional-formatting/ConditionalFormattingUtils.js.map +1 -1
- package/dist/sidebar/conditional-formatting/RowFormatEditor.js +35 -24
- package/dist/sidebar/conditional-formatting/RowFormatEditor.js.map +1 -1
- package/dist/sidebar/conditional-formatting/StyleEditor.js +26 -11
- package/dist/sidebar/conditional-formatting/StyleEditor.js.map +1 -1
- package/dist/sidebar/index.d.ts +3 -2
- package/dist/sidebar/index.d.ts.map +1 -1
- package/dist/sidebar/index.js.map +1 -1
- package/dist/sidebar/visibility-ordering-builder/SearchItem.js +5 -7
- package/dist/sidebar/visibility-ordering-builder/SearchItem.js.map +1 -1
- package/dist/sidebar/visibility-ordering-builder/SearchWithModal.js +24 -18
- package/dist/sidebar/visibility-ordering-builder/SearchWithModal.js.map +1 -1
- package/dist/sidebar/visibility-ordering-builder/VisibilityOrderingBuilder.js +166 -256
- package/dist/sidebar/visibility-ordering-builder/VisibilityOrderingBuilder.js.map +1 -1
- package/dist/sidebar/visibility-ordering-builder/VisibilityOrderingBuilderUtils.js +4 -11
- package/dist/sidebar/visibility-ordering-builder/VisibilityOrderingBuilderUtils.js.map +1 -1
- package/dist/sidebar/visibility-ordering-builder/VisibilityOrderingGroup.js +28 -14
- package/dist/sidebar/visibility-ordering-builder/VisibilityOrderingGroup.js.map +1 -1
- package/dist/sidebar/visibility-ordering-builder/VisibilityOrderingItem.js +16 -19
- package/dist/sidebar/visibility-ordering-builder/VisibilityOrderingItem.js.map +1 -1
- package/dist/sidebar/visibility-ordering-builder/sortable-tree/PointerSensorWithInteraction.js +1 -3
- package/dist/sidebar/visibility-ordering-builder/sortable-tree/PointerSensorWithInteraction.js.map +1 -1
- package/dist/sidebar/visibility-ordering-builder/sortable-tree/SortableTree.js +19 -29
- package/dist/sidebar/visibility-ordering-builder/sortable-tree/SortableTree.js.map +1 -1
- package/dist/sidebar/visibility-ordering-builder/sortable-tree/SortableTreeDndContext.js +39 -52
- package/dist/sidebar/visibility-ordering-builder/sortable-tree/SortableTreeDndContext.js.map +1 -1
- package/dist/sidebar/visibility-ordering-builder/sortable-tree/SortableTreeItem.js +18 -23
- package/dist/sidebar/visibility-ordering-builder/sortable-tree/SortableTreeItem.js.map +1 -1
- package/dist/sidebar/visibility-ordering-builder/sortable-tree/TreeItem.js +19 -15
- package/dist/sidebar/visibility-ordering-builder/sortable-tree/TreeItem.js.map +1 -1
- package/dist/sidebar/visibility-ordering-builder/sortable-tree/keyboardCoordinates.js +16 -30
- package/dist/sidebar/visibility-ordering-builder/sortable-tree/keyboardCoordinates.js.map +1 -1
- package/dist/sidebar/visibility-ordering-builder/sortable-tree/utilities.js +30 -39
- package/dist/sidebar/visibility-ordering-builder/sortable-tree/utilities.js.map +1 -1
- package/package.json +16 -16
package/README.md
CHANGED
|
@@ -20,4 +20,287 @@ const model = await IrisGridModelFactory.makeModel(dh, table);
|
|
|
20
20
|
|
|
21
21
|
// In your render function
|
|
22
22
|
<IrisGrid dh={dh} model={model} />
|
|
23
|
-
```
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Customizing the Table Options menu
|
|
26
|
+
|
|
27
|
+
The Table Options sidebar (the gear menu on the right edge of the grid) is
|
|
28
|
+
extensible. Plugin authors can hide built-in items, relabel or reorder
|
|
29
|
+
them, and add their own items that open a custom configuration page —
|
|
30
|
+
without forking `IrisGrid`.
|
|
31
|
+
|
|
32
|
+
There is a single entry point: the `transformTableOptions` prop on
|
|
33
|
+
`<IrisGrid>`. It is **opt-in** — it lives on the iris-grid-specific
|
|
34
|
+
`IrisGridTableOptionsWidgetProps`, not on the generic
|
|
35
|
+
`WidgetComponentProps` / `WidgetPanelProps`, so widgets that don't care
|
|
36
|
+
about the Table Options menu never see it.
|
|
37
|
+
|
|
38
|
+
- **Own the render site?** Pass `transformTableOptions` straight to
|
|
39
|
+
`<IrisGrid>`.
|
|
40
|
+
- **A `WidgetMiddlewarePlugin` that doesn't render `<IrisGrid>`
|
|
41
|
+
yourself?** Thread the prop down the middleware chain via the
|
|
42
|
+
`Component` you wrap, composing your own transform on top of the one
|
|
43
|
+
you received (see [Publishing from middleware](#publishing-from-middleware)).
|
|
44
|
+
The panel hosts that ship with Deephaven (`IrisGridPanel`,
|
|
45
|
+
`GridWidgetPlugin`) accept `transformTableOptions` as a prop and
|
|
46
|
+
forward it to `<IrisGrid>`.
|
|
47
|
+
|
|
48
|
+
### Writing a transform
|
|
49
|
+
|
|
50
|
+
`transformTableOptions(defaults)` is a pure function that receives the
|
|
51
|
+
built-in items (already filtered by what the current model supports) and
|
|
52
|
+
returns the items to actually render. Use it to add, hide, relabel,
|
|
53
|
+
reorder, or replace entries.
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
import { OptionType, type OptionItem } from '@deephaven/iris-grid';
|
|
57
|
+
|
|
58
|
+
const transformTableOptions = (defaults: readonly OptionItem[]) => [
|
|
59
|
+
// hide a built-in
|
|
60
|
+
...defaults.filter(o => o.type !== OptionType.SELECT_DISTINCT),
|
|
61
|
+
// add a plugin item with its own page
|
|
62
|
+
{
|
|
63
|
+
type: 'plugin:my-plugin:column-inspector',
|
|
64
|
+
title: 'Column Inspector',
|
|
65
|
+
configPage: ColumnInspectorPage,
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Rules:
|
|
71
|
+
|
|
72
|
+
- The transform should be referentially stable and side-effect-free
|
|
73
|
+
(it's called inside memoization). Memoize it with `useMemo` /
|
|
74
|
+
`useCallback` rather than rebuilding per render.
|
|
75
|
+
- A throwing transform is logged once and treated as identity for that
|
|
76
|
+
render, so the menu degrades gracefully.
|
|
77
|
+
- Items **without** a `configPage` MUST have a `type` matching an
|
|
78
|
+
existing `OptionType` enum value — those are rendered by the built-in
|
|
79
|
+
page switch.
|
|
80
|
+
- Items **with** a `configPage` SHOULD use a namespaced key,
|
|
81
|
+
conventionally `plugin:<name>:<id>`, to avoid colliding with built-ins
|
|
82
|
+
or other plugins.
|
|
83
|
+
|
|
84
|
+
### Implementing a `configPage`
|
|
85
|
+
|
|
86
|
+
A `configPage` is a regular React component that receives
|
|
87
|
+
`IrisGridTableOptionsPageProps`:
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
import { type IrisGridTableOptionsPageProps } from '@deephaven/iris-grid';
|
|
91
|
+
|
|
92
|
+
export function ColumnInspectorPage({
|
|
93
|
+
model,
|
|
94
|
+
onBack,
|
|
95
|
+
}: IrisGridTableOptionsPageProps): JSX.Element {
|
|
96
|
+
return (
|
|
97
|
+
<div>
|
|
98
|
+
<button type="button" onClick={onBack}>Back</button>
|
|
99
|
+
<pre>{model.columns.map(c => c.name).join('\n')}</pre>
|
|
100
|
+
</div>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`IrisGrid` wraps each `configPage` render in `PluginTableOptionsErrorBoundary`,
|
|
106
|
+
so a throw inside your page shows a small inline fallback instead of
|
|
107
|
+
unmounting the whole grid.
|
|
108
|
+
|
|
109
|
+
### Publishing from middleware
|
|
110
|
+
|
|
111
|
+
When you're a `WidgetMiddlewarePlugin` and don't render `<IrisGrid>`
|
|
112
|
+
yourself, you receive `transformTableOptions` as a prop and pass a
|
|
113
|
+
composed transform down to the `Component` (or panel) you wrap. Run the
|
|
114
|
+
upstream transform first so contributions compose, then layer your own
|
|
115
|
+
changes on top.
|
|
116
|
+
|
|
117
|
+
A `panelComponent` middleware should be built with `createPanelMiddleware`
|
|
118
|
+
from `@deephaven/plugin`: you supply a body hook that may `inject` props onto
|
|
119
|
+
the wrapped component and/or `wrap` the child in a wrapper element (both
|
|
120
|
+
optional), and the factory owns the `React.forwardRef` ceremony and ref
|
|
121
|
+
forwarding for you. That ref matters —
|
|
122
|
+
golden-layout binds a ref to the registered panel to persist class-panel
|
|
123
|
+
state (sorts, filters, column moves, etc.) into its `componentState`, and a
|
|
124
|
+
middleware that swallowed it would silently break that persistence for every
|
|
125
|
+
panel below it; the factory guarantees it can't be dropped. For the non-panel
|
|
126
|
+
`component` path use `createWidgetMiddleware`, which is otherwise identical but
|
|
127
|
+
takes no ref.
|
|
128
|
+
|
|
129
|
+
```tsx
|
|
130
|
+
import { useMemo } from 'react';
|
|
131
|
+
import { createPanelMiddleware, type WidgetPanelProps } from '@deephaven/plugin';
|
|
132
|
+
import {
|
|
133
|
+
type IrisGridTableOptionsWidgetProps,
|
|
134
|
+
type TableOptionsTransform,
|
|
135
|
+
} from '@deephaven/iris-grid';
|
|
136
|
+
|
|
137
|
+
function makeMyTransform(
|
|
138
|
+
upstream: TableOptionsTransform | undefined
|
|
139
|
+
): TableOptionsTransform {
|
|
140
|
+
return defaults => {
|
|
141
|
+
const base = upstream != null ? upstream(defaults) : defaults;
|
|
142
|
+
return [...base, myPluginItem];
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const MyMiddleware = createPanelMiddleware<
|
|
147
|
+
unknown,
|
|
148
|
+
WidgetPanelProps & IrisGridTableOptionsWidgetProps
|
|
149
|
+
>(({ transformTableOptions }) => {
|
|
150
|
+
const composedTransform = useMemo(
|
|
151
|
+
() => makeMyTransform(transformTableOptions),
|
|
152
|
+
[transformTableOptions]
|
|
153
|
+
);
|
|
154
|
+
return { inject: { transformTableOptions: composedTransform } };
|
|
155
|
+
}, 'MyMiddleware');
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The body hook receives the incoming props (minus `Component`) and returns an
|
|
159
|
+
optional `{ inject?, wrap? }`. Every incoming prop is forwarded to the wrapped
|
|
160
|
+
component automatically; `inject` only adds or overrides the few props you
|
|
161
|
+
actually change (here `transformTableOptions`), and `wrap` optionally nests the
|
|
162
|
+
child (e.g. in a context provider). Both fields are optional — a pass-through
|
|
163
|
+
middleware can return `{}`. The factory adds the `ref` plumbing on top.
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
Composition rule: each middleware layer reads the `transformTableOptions`
|
|
167
|
+
it was handed, runs that transform first, then layers its own changes on
|
|
168
|
+
top — last writer wins for any given `OptionItem.type`.
|
|
169
|
+
|
|
170
|
+
#### Model-aware menus
|
|
171
|
+
|
|
172
|
+
The transform must stay **pure** — it only sees `defaults`, never the
|
|
173
|
+
`IrisGridModel`. To make a menu react to model state (e.g. relabel an
|
|
174
|
+
item once a pivot is active), take a **snapshot of the value you care
|
|
175
|
+
about** from model events and fold it into the transform's identity:
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
const [isPivot, setIsPivot] = useState(model.isPivot);
|
|
179
|
+
useEffect(() => {
|
|
180
|
+
const handler = () => setIsPivot(model.isPivot);
|
|
181
|
+
model.addEventListener(SOME_MODEL_EVENT, handler);
|
|
182
|
+
return () => model.removeEventListener(SOME_MODEL_EVENT, handler);
|
|
183
|
+
}, [model]);
|
|
184
|
+
|
|
185
|
+
const composedTransform = useMemo(
|
|
186
|
+
() => makeMyTransform(transformTableOptions, isPivot),
|
|
187
|
+
[transformTableOptions, isPivot]
|
|
188
|
+
);
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Because `composedTransform`'s identity changes when the snapshot
|
|
192
|
+
changes, `IrisGrid` re-runs it (its menu cache is keyed on
|
|
193
|
+
`[defaults, transform]`). Keeping the snapshot in the dependency array —
|
|
194
|
+
rather than reading `model.isPivot` inside the transform — is what keeps
|
|
195
|
+
that memoization honest.
|
|
196
|
+
|
|
197
|
+
To obtain the model when the host builds it for you, pass an
|
|
198
|
+
`onModelChanged` callback to `IrisGridPanel` (called once the panel's
|
|
199
|
+
model is ready).
|
|
200
|
+
|
|
201
|
+
### Full example
|
|
202
|
+
|
|
203
|
+
See the [`@deephaven/js-plugin-pivot-builder`](https://github.com/deephaven/deephaven-plugins/tree/main/plugins/pivot-builder)
|
|
204
|
+
plugin for a working `WidgetMiddlewarePlugin` that replaces the default
|
|
205
|
+
widget renderer and adds a `configPage`-backed "Rollup, Aggregate and Pivot"
|
|
206
|
+
item to the Table Options sidebar.
|
|
207
|
+
|
|
208
|
+
### Why the transform doesn't take the model
|
|
209
|
+
|
|
210
|
+
The transform signature is `(defaults) => items` — it deliberately does
|
|
211
|
+
**not** receive the `IrisGridModel` or grid state. State-aware menus
|
|
212
|
+
(e.g. "relabel an item once a pivot is active", "show *Reset filters*
|
|
213
|
+
only when filters exist") are implemented in the middleware: subscribe
|
|
214
|
+
to model events, snapshot the value you need, and fold that snapshot
|
|
215
|
+
into the transform's identity (see
|
|
216
|
+
[Model-aware menus](#model-aware-menus)). The transform itself stays
|
|
217
|
+
pure.
|
|
218
|
+
|
|
219
|
+
This isn't just about keeping the public surface small — it's also
|
|
220
|
+
what keeps menu memoization honest. `IrisGrid` caches the computed
|
|
221
|
+
item list on `[defaults, transform]` (see `getCachedTransformedOptionItems`),
|
|
222
|
+
both of which are stable values/refs. Adding a live `model` argument
|
|
223
|
+
would break that: `IrisGridModel` is a long-lived mutable handle whose
|
|
224
|
+
identity does not change when `isExpandable`, `filter`, `sorts`, or
|
|
225
|
+
`isRollup` flip, so any plugin that read those fields would silently
|
|
226
|
+
return stale items until something unrelated invalidated the cache.
|
|
227
|
+
|
|
228
|
+
By passing a **curated snapshot of values** through the transform's
|
|
229
|
+
closure instead, the memo key changes exactly when those values change
|
|
230
|
+
and re-runs are driven by actual dependencies. Passing the model itself,
|
|
231
|
+
or the full `IrisGrid` instance, is intentionally off the table: the
|
|
232
|
+
surface is too large, too volatile, and (in the model's case)
|
|
233
|
+
memoization-hostile.
|
|
234
|
+
|
|
235
|
+
## Transforming the model
|
|
236
|
+
|
|
237
|
+
Some plugins need more than a custom menu — they need to change the
|
|
238
|
+
**model** the grid renders (e.g. wrap it in a proxy that can swap its
|
|
239
|
+
inner model in response to a config page). For that there is a second,
|
|
240
|
+
symmetric opt-in seam: the `transformModel` prop.
|
|
241
|
+
|
|
242
|
+
Like `transformTableOptions`, it lives on an iris-grid-specific
|
|
243
|
+
interface (`IrisGridModelWidgetProps`), not on the generic
|
|
244
|
+
`WidgetComponentProps` / `WidgetPanelProps`, and is threaded down the
|
|
245
|
+
middleware chain by the hosts that build the model for you
|
|
246
|
+
(`IrisGridPanel`, `GridWidgetPlugin` / `useIrisGridModel`).
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
import { type IrisGridModelTransform } from '@deephaven/iris-grid';
|
|
250
|
+
|
|
251
|
+
// (model: IrisGridModel) => IrisGridModel | Promise<IrisGridModel>
|
|
252
|
+
const transformModel: IrisGridModelTransform = model =>
|
|
253
|
+
wrapInMyProxy(model);
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
The host builds the model from `fetch` as usual, then applies
|
|
257
|
+
`transformModel` to whatever it built **before** handing it to
|
|
258
|
+
`<IrisGrid>`. The returned model must be a drop-in for the input — the
|
|
259
|
+
host owns its lifecycle and will `close()` whatever you return, so wrap
|
|
260
|
+
rather than discard the model you were given. The transform may be async
|
|
261
|
+
if it needs to await dependencies first.
|
|
262
|
+
|
|
263
|
+
Rules:
|
|
264
|
+
|
|
265
|
+
- `transformModel` must be **referentially stable**. It is applied when
|
|
266
|
+
the model is (re)built; an unstable transform would rebuild the model.
|
|
267
|
+
Memoize it with `useMemo` / `useCallback`.
|
|
268
|
+
- It runs once per model build, not per render, so it is the right place
|
|
269
|
+
for one-time wrapping — not for per-render state.
|
|
270
|
+
- This is why model construction stays in the host: the host keeps
|
|
271
|
+
ownership of `fetch`, error/loading state, and `close()`, while the
|
|
272
|
+
plugin only augments the result. A middleware using `transformModel`
|
|
273
|
+
can therefore render the wrapped `Component` and stay a **chained**
|
|
274
|
+
layer instead of taking over model construction and becoming terminal.
|
|
275
|
+
|
|
276
|
+
### Publishing `transformModel` from middleware
|
|
277
|
+
|
|
278
|
+
A middleware that needs both seams composes them the same way — run any
|
|
279
|
+
upstream transform first, then layer your own — and returns both from its
|
|
280
|
+
body hook's `inject`. Props you don't touch (here `transformTableOptions`)
|
|
281
|
+
are forwarded automatically, so you only inject what you change:
|
|
282
|
+
|
|
283
|
+
```tsx
|
|
284
|
+
import { useMemo } from 'react';
|
|
285
|
+
import { createPanelMiddleware, type WidgetPanelProps } from '@deephaven/plugin';
|
|
286
|
+
import {
|
|
287
|
+
type IrisGridModelTransform,
|
|
288
|
+
type IrisGridModelWidgetProps,
|
|
289
|
+
type IrisGridTableOptionsWidgetProps,
|
|
290
|
+
} from '@deephaven/iris-grid';
|
|
291
|
+
|
|
292
|
+
const MyMiddleware = createPanelMiddleware<
|
|
293
|
+
unknown,
|
|
294
|
+
WidgetPanelProps & IrisGridModelWidgetProps & IrisGridTableOptionsWidgetProps
|
|
295
|
+
>(({ transformModel }) => {
|
|
296
|
+
const composedModel = useMemo<IrisGridModelTransform>(
|
|
297
|
+
() => async model => {
|
|
298
|
+
const base = transformModel != null ? await transformModel(model) : model;
|
|
299
|
+
return wrapInMyProxy(base);
|
|
300
|
+
},
|
|
301
|
+
[transformModel]
|
|
302
|
+
);
|
|
303
|
+
return { inject: { transformModel: composedModel } };
|
|
304
|
+
}, 'MyMiddleware');
|
|
305
|
+
```
|
|
306
|
+
|
|
@@ -48,23 +48,17 @@ class AdvancedFilterCreator extends PureComponent {
|
|
|
48
48
|
this.handleFocusTrapEnd = this.handleFocusTrapEnd.bind(this);
|
|
49
49
|
this.handleUpdateTimeout = this.handleUpdateTimeout.bind(this);
|
|
50
50
|
this.focusTrapContainer = /*#__PURE__*/React.createRef();
|
|
51
|
-
var
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
filterOperators,
|
|
58
|
-
invertSelection,
|
|
59
|
-
selectedValues
|
|
60
|
-
} = options;
|
|
51
|
+
var model = props.model,
|
|
52
|
+
column = props.column,
|
|
53
|
+
options = props.options;
|
|
54
|
+
var filterOperators = options.filterOperators,
|
|
55
|
+
invertSelection = options.invertSelection,
|
|
56
|
+
selectedValues = options.selectedValues;
|
|
61
57
|
|
|
62
58
|
// can be null or an empty array
|
|
63
59
|
var filterItems = (_options$filterItems$ = (_options$filterItems = options.filterItems) === null || _options$filterItems === void 0 ? void 0 : _options$filterItems.map(_ref => {
|
|
64
|
-
var
|
|
65
|
-
|
|
66
|
-
value
|
|
67
|
-
} = _ref;
|
|
60
|
+
var selectedType = _ref.selectedType,
|
|
61
|
+
value = _ref.value;
|
|
68
62
|
return {
|
|
69
63
|
selectedType,
|
|
70
64
|
value,
|
|
@@ -117,10 +111,9 @@ class AdvancedFilterCreator extends PureComponent {
|
|
|
117
111
|
return this.handleFilterDelete.bind(this, index);
|
|
118
112
|
}
|
|
119
113
|
initValuesTable() {
|
|
120
|
-
var
|
|
121
|
-
model,
|
|
122
|
-
column
|
|
123
|
-
} = this.props;
|
|
114
|
+
var _this$props = this.props,
|
|
115
|
+
model = _this$props.model,
|
|
116
|
+
column = _this$props.column;
|
|
124
117
|
if (!model.isValuesTableAvailable) {
|
|
125
118
|
log.debug('No values table for this model, just ignore');
|
|
126
119
|
return;
|
|
@@ -158,10 +151,9 @@ class AdvancedFilterCreator extends PureComponent {
|
|
|
158
151
|
}
|
|
159
152
|
}
|
|
160
153
|
handleAddAnd() {
|
|
161
|
-
var
|
|
162
|
-
filterItems,
|
|
163
|
-
filterOperators
|
|
164
|
-
} = this.state;
|
|
154
|
+
var _this$state = this.state,
|
|
155
|
+
filterItems = _this$state.filterItems,
|
|
156
|
+
filterOperators = _this$state.filterOperators;
|
|
165
157
|
filterItems = filterItems.concat(AdvancedFilterCreator.makeFilterItem());
|
|
166
158
|
filterOperators = filterOperators.concat(FilterOperator.and);
|
|
167
159
|
this.setState({
|
|
@@ -170,10 +162,9 @@ class AdvancedFilterCreator extends PureComponent {
|
|
|
170
162
|
});
|
|
171
163
|
}
|
|
172
164
|
handleAddOr() {
|
|
173
|
-
var
|
|
174
|
-
filterItems,
|
|
175
|
-
filterOperators
|
|
176
|
-
} = this.state;
|
|
165
|
+
var _this$state2 = this.state,
|
|
166
|
+
filterItems = _this$state2.filterItems,
|
|
167
|
+
filterOperators = _this$state2.filterOperators;
|
|
177
168
|
filterItems = filterItems.concat(AdvancedFilterCreator.makeFilterItem());
|
|
178
169
|
filterOperators = filterOperators.concat(FilterOperator.or);
|
|
179
170
|
this.setState({
|
|
@@ -182,9 +173,7 @@ class AdvancedFilterCreator extends PureComponent {
|
|
|
182
173
|
});
|
|
183
174
|
}
|
|
184
175
|
handleChangeFilterOperator(index, operator) {
|
|
185
|
-
var
|
|
186
|
-
filterOperators
|
|
187
|
-
} = this.state;
|
|
176
|
+
var filterOperators = this.state.filterOperators;
|
|
188
177
|
filterOperators = [...filterOperators];
|
|
189
178
|
assertFilterOperatorValue(operator);
|
|
190
179
|
filterOperators[index] = operator;
|
|
@@ -194,13 +183,9 @@ class AdvancedFilterCreator extends PureComponent {
|
|
|
194
183
|
this.startUpdateTimer();
|
|
195
184
|
}
|
|
196
185
|
handleFilterChange(filterIndex, selectedType, value) {
|
|
197
|
-
var
|
|
198
|
-
filterItems
|
|
199
|
-
} = this.state;
|
|
186
|
+
var filterItems = this.state.filterItems;
|
|
200
187
|
filterItems = [...filterItems];
|
|
201
|
-
var
|
|
202
|
-
key
|
|
203
|
-
} = filterItems[filterIndex];
|
|
188
|
+
var key = filterItems[filterIndex].key;
|
|
204
189
|
filterItems[filterIndex] = {
|
|
205
190
|
key,
|
|
206
191
|
selectedType,
|
|
@@ -212,10 +197,9 @@ class AdvancedFilterCreator extends PureComponent {
|
|
|
212
197
|
this.startUpdateTimer();
|
|
213
198
|
}
|
|
214
199
|
handleFilterDelete(filterIndex) {
|
|
215
|
-
var
|
|
216
|
-
filterItems,
|
|
217
|
-
filterOperators
|
|
218
|
-
} = this.state;
|
|
200
|
+
var _this$state3 = this.state,
|
|
201
|
+
filterItems = _this$state3.filterItems,
|
|
202
|
+
filterOperators = _this$state3.filterOperators;
|
|
219
203
|
filterItems = [...filterItems];
|
|
220
204
|
filterOperators = [...filterOperators];
|
|
221
205
|
if (filterIndex < filterItems.length) {
|
|
@@ -271,9 +255,7 @@ class AdvancedFilterCreator extends PureComponent {
|
|
|
271
255
|
log.debug('Submitting and Closing Advanced Filter');
|
|
272
256
|
this.stopUpdateTimer();
|
|
273
257
|
this.sendUpdate();
|
|
274
|
-
var
|
|
275
|
-
onDone
|
|
276
|
-
} = this.props;
|
|
258
|
+
var onDone = this.props.onDone;
|
|
277
259
|
onDone();
|
|
278
260
|
event.preventDefault();
|
|
279
261
|
}
|
|
@@ -288,17 +270,13 @@ class AdvancedFilterCreator extends PureComponent {
|
|
|
288
270
|
* @returns true If the add filter buttons should be shown, false otherwise
|
|
289
271
|
*/
|
|
290
272
|
shouldShowAddFilter() {
|
|
291
|
-
var
|
|
292
|
-
filterItems
|
|
293
|
-
} = this.state;
|
|
273
|
+
var filterItems = this.state.filterItems;
|
|
294
274
|
if (filterItems.length === 0) {
|
|
295
275
|
return false;
|
|
296
276
|
}
|
|
297
277
|
var filterItem = filterItems[filterItems.length - 1];
|
|
298
|
-
var
|
|
299
|
-
|
|
300
|
-
value
|
|
301
|
-
} = filterItem;
|
|
278
|
+
var selectedType = filterItem.selectedType,
|
|
279
|
+
value = filterItem.value;
|
|
302
280
|
return selectedType != null && selectedType.length > 0 && value != null && value.length > 0;
|
|
303
281
|
}
|
|
304
282
|
|
|
@@ -309,13 +287,10 @@ class AdvancedFilterCreator extends PureComponent {
|
|
|
309
287
|
*/
|
|
310
288
|
sortTable(direction) {
|
|
311
289
|
var addToExisting = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
|
312
|
-
var
|
|
313
|
-
column,
|
|
314
|
-
onSortChange
|
|
315
|
-
|
|
316
|
-
var {
|
|
317
|
-
isSortable
|
|
318
|
-
} = this.state;
|
|
290
|
+
var _this$props2 = this.props,
|
|
291
|
+
column = _this$props2.column,
|
|
292
|
+
onSortChange = _this$props2.onSortChange;
|
|
293
|
+
var isSortable = this.state.isSortable;
|
|
319
294
|
if (isSortable) {
|
|
320
295
|
onSortChange(column, direction, addToExisting);
|
|
321
296
|
}
|
|
@@ -331,26 +306,20 @@ class AdvancedFilterCreator extends PureComponent {
|
|
|
331
306
|
}
|
|
332
307
|
}
|
|
333
308
|
sendUpdate() {
|
|
334
|
-
var
|
|
335
|
-
filterItems,
|
|
336
|
-
filterOperators,
|
|
337
|
-
invertSelection,
|
|
338
|
-
selectedValues
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
} = this.props;
|
|
346
|
-
var {
|
|
347
|
-
formatter
|
|
348
|
-
} = model;
|
|
309
|
+
var _this$state4 = this.state,
|
|
310
|
+
filterItems = _this$state4.filterItems,
|
|
311
|
+
filterOperators = _this$state4.filterOperators,
|
|
312
|
+
invertSelection = _this$state4.invertSelection,
|
|
313
|
+
selectedValues = _this$state4.selectedValues;
|
|
314
|
+
var _this$props3 = this.props,
|
|
315
|
+
column = _this$props3.column,
|
|
316
|
+
onFilterChange = _this$props3.onFilterChange,
|
|
317
|
+
model = _this$props3.model,
|
|
318
|
+
tableUtils = _this$props3.tableUtils;
|
|
319
|
+
var formatter = model.formatter;
|
|
349
320
|
var items = filterItems.filter(_ref2 => {
|
|
350
|
-
var
|
|
351
|
-
|
|
352
|
-
value
|
|
353
|
-
} = _ref2;
|
|
321
|
+
var selectedType = _ref2.selectedType,
|
|
322
|
+
value = _ref2.value;
|
|
354
323
|
return selectedType != null && value != null && value !== '';
|
|
355
324
|
});
|
|
356
325
|
var operators = filterOperators.filter((operator, i) => operator != null && filterItems[i].selectedType != null && filterItems[i].value != null && filterItems[i].value !== '').slice(0, items.length - 1);
|
|
@@ -367,28 +336,24 @@ class AdvancedFilterCreator extends PureComponent {
|
|
|
367
336
|
}
|
|
368
337
|
render() {
|
|
369
338
|
var _this = this;
|
|
370
|
-
var
|
|
371
|
-
column,
|
|
372
|
-
model,
|
|
373
|
-
sortDirection,
|
|
374
|
-
formatter,
|
|
375
|
-
tableUtils,
|
|
376
|
-
isMaximized,
|
|
377
|
-
onToggleMaximize
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
var {
|
|
389
|
-
dh,
|
|
390
|
-
isValuesTableAvailable
|
|
391
|
-
} = model;
|
|
339
|
+
var _this$props4 = this.props,
|
|
340
|
+
column = _this$props4.column,
|
|
341
|
+
model = _this$props4.model,
|
|
342
|
+
sortDirection = _this$props4.sortDirection,
|
|
343
|
+
formatter = _this$props4.formatter,
|
|
344
|
+
tableUtils = _this$props4.tableUtils,
|
|
345
|
+
isMaximized = _this$props4.isMaximized,
|
|
346
|
+
onToggleMaximize = _this$props4.onToggleMaximize;
|
|
347
|
+
var _this$state5 = this.state,
|
|
348
|
+
filterItems = _this$state5.filterItems,
|
|
349
|
+
filterOperators = _this$state5.filterOperators,
|
|
350
|
+
invertSelection = _this$state5.invertSelection,
|
|
351
|
+
selectedValues = _this$state5.selectedValues,
|
|
352
|
+
valuesTable = _this$state5.valuesTable,
|
|
353
|
+
valuesTableError = _this$state5.valuesTableError,
|
|
354
|
+
isSortable = _this$state5.isSortable;
|
|
355
|
+
var dh = model.dh,
|
|
356
|
+
isValuesTableAvailable = model.isValuesTableAvailable;
|
|
392
357
|
var isBoolean = TableUtils.isBooleanType(column.type);
|
|
393
358
|
var isDateType = TableUtils.isDateType(column.type);
|
|
394
359
|
var filterTypes = this.getFilterTypes(column.type);
|
|
@@ -397,11 +362,9 @@ class AdvancedFilterCreator extends PureComponent {
|
|
|
397
362
|
if (!isBoolean && filterTypes.length) {
|
|
398
363
|
var _loop = function _loop(i) {
|
|
399
364
|
var filterItem = filterItems[i];
|
|
400
|
-
var
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
value
|
|
404
|
-
} = filterItem;
|
|
365
|
+
var key = filterItem.key,
|
|
366
|
+
selectedType = filterItem.selectedType,
|
|
367
|
+
value = filterItem.value;
|
|
405
368
|
var element = /*#__PURE__*/_jsx(AdvancedFilterCreatorFilterItem, {
|
|
406
369
|
column: column,
|
|
407
370
|
filterTypes: filterTypes,
|