fluentui-extended 2026.9.6 → 2026.9.16-beta.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/CHANGELOG.md CHANGED
@@ -2,6 +2,51 @@
2
2
 
3
3
  > Version format: `YYYY.M.DD` (e.g., `2026.8.30` = August 30, 2026)
4
4
 
5
+ ## Unreleased
6
+
7
+ - ✨ **[DataGrid](docs/DataGrid.md)** — a virtualised, editable grid on TanStack Table and TanStack
8
+ Virtual. Two production apps had each grown their own copy of the same machinery — row
9
+ virtualisation, column drag and resize, grouping with subtotals, a filter row, density, inline
10
+ editors and layout persistence — and the copies had begun to drift. This is that machinery,
11
+ extracted: fetching, saving and the toolbar stay with the consumer.
12
+
13
+ Notable behaviour: `getCellValue` layers unsaved edits over the stored record, and sorting,
14
+ filtering, grouping and the footer totals all read through it — so the grid orders by what is on
15
+ screen rather than by what the server last returned. Column layout restores during the first
16
+ render rather than in an effect, so the grid paints its saved layout instead of reflowing a frame
17
+ later, and the storage key includes the origin so dev, test and production orgs in one browser do
18
+ not share one layout. Sorting ascends first whatever the data type, unlike TanStack's default.
19
+
20
+ Also exports `useDataGrid`, the headless state machine, plus the chrome components and style hook,
21
+ so a bespoke render does not have to fork the component.
22
+
23
+ Hooks added so the grid can replace the two hand-built project grids (tracked in
24
+ `enhancements/DataGrid-replace-project-grids.md`):
25
+ - **States:** custom and restyled cell and row states (`cellStates`, `rowStates`), layered row
26
+ states from `getRowState`, and built-in `deleted` and `placeholder` states.
27
+ - **Cells:** `getCellTooltip`.
28
+ - **Groups:** banner hooks (`renderGroupHeader`, `groupContextMenu`, `onGroupDoubleClick`,
29
+ `groupSelection`), and row drops that report `toGroup`.
30
+ - **Keyboard:** spreadsheet keys — Tab, F2, type-over, and caret-aware arrows.
31
+ - **Totals:** function summaries, `includeInSummary` and `groupSubtotalsMinGroups`.
32
+ - **Visibility and views:** controlled visibility, `forceHiddenColumns`, `columnGroup`, and
33
+ `getLayout` / `applyLayout`.
34
+ - **Row chrome and toolbar:** `renderRowIndicator`, `showEditableHints`, `highlightRow`, and
35
+ `renderToolbarSearch`.
36
+
37
+ Fixes in the same pass:
38
+ - Changing `persist.scope` while mounted now loads that scope's layout. It used to overwrite it
39
+ with the current one.
40
+ - Nested group subtotals and counts now come from leaf rows rather than inner banners.
41
+ - Hiding a column while grouped no longer saves the grouped column as hidden.
42
+ - Keys pressed inside a cell's popup no longer move the active cell.
43
+
44
+ `cellStateClass` is replaced by `cellStateProps`.
45
+
46
+ - 📦 `@tanstack/react-table`, `@tanstack/react-virtual` and the three `@dnd-kit` packages are
47
+ **optional** peer dependencies. Nothing outside `DataGrid` imports them, so consumers using only
48
+ the field components ship none of it.
49
+
5
50
  ## 2026.9.6
6
51
 
7
52
  - ✨ **`size` on [OptionSetField](README.md#optionsetfield)**, forwarded to the underlying Combobox.
package/README.md CHANGED
@@ -103,6 +103,36 @@ sorting, and lookups rendered as names rather than GUIDs.
103
103
 
104
104
  ![EntityGrid](assets/screenshot-entitygrid.png)
105
105
 
106
+ ### DataGrid
107
+
108
+ A virtualised, editable grid on TanStack Table and TanStack Virtual, for the spreadsheet-style
109
+ screens a subgrid cannot carry: thousands of rows, inline editors, column drag and resize,
110
+ grouping with subtotals, and a column layout that survives a reload.
111
+
112
+ Bring your own rows — fetching and saving stay yours. The grid libraries are **optional** peer
113
+ dependencies, so nothing ships them unless you use this component.
114
+
115
+ ```bash
116
+ npm install @tanstack/react-table @tanstack/react-virtual \
117
+ @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities
118
+ ```
119
+
120
+ ```tsx
121
+ <DataGrid
122
+ data={rows}
123
+ columns={columns}
124
+ getRowId={(row) => row.id}
125
+ getCellValue={(row, key, rowId) => pending[rowId]?.[key] ?? row[key]}
126
+ onCellChange={stageEdit}
127
+ getRowState={(_, rowId) => (pending[rowId] ? 'dirty' : null)}
128
+ selectable
129
+ groupBy="category"
130
+ persist={{ scope: 'quote-lines' }}
131
+ />
132
+ ```
133
+
134
+ **[Read more &rarr;](https://github.com/garethcheyne/npm-fluentui-extended/blob/main/docs/DataGrid.md)** - columns, unsaved-edit layering, persistence, and the headless `useDataGrid` hook.
135
+
106
136
  ### DateTimeField
107
137
 
108
138
  A date/time field that respects the attribute's Dynamics `DateTimeBehavior`, so `DateOnly` values
@@ -143,6 +173,76 @@ selections render as the usual Lookup badges and multi-select comes for free.
143
173
 
144
174
  ![OwnerLookup users and teams](assets/screenshot-ownerlookup-open.png)
145
175
 
176
+ ### AddressLookup
177
+
178
+ A preconfigured `Lookup` for Google Places. Type an address, hover a suggestion to see it on a
179
+ map, and the selection arrives flattened into the columns an address is stored in.
180
+
181
+ Google bills per request, so the work is split the way the API prices it: typing buys
182
+ Autocomplete predictions only, and a place is resolved once - when its card opens, or when the
183
+ row is chosen, whichever happens first. The result is cached, so hovering then clicking the same
184
+ suggestion costs a single Place Details call.
185
+
186
+ Each suggestion is iconed by what it actually is - a street address, a building, a suburb, a
187
+ region, an airport, a shop - rather than every row carrying the same pin, so a list of mixed
188
+ results can be read at a glance.
189
+
190
+ ```tsx
191
+ import { AddressLookup } from 'fluentui-extended';
192
+ import type { PlaceAddress } from 'fluentui-extended';
193
+
194
+ const [address, setAddress] = useState<PlaceAddress | null>(null);
195
+
196
+ <AddressLookup
197
+ apiKey={googleMapsKey}
198
+ label="Address"
199
+ value={address}
200
+ onSelect={(picked) => setAddress(picked)}
201
+ countries={['nz', 'au']}
202
+ stateShortName
203
+ countryShortName
204
+ />
205
+ ```
206
+
207
+ `onSelect` receives the flattened address and the place it came from:
208
+
209
+ ```ts
210
+ {
211
+ placeId: 'ChIJ...',
212
+ formattedAddress: '1 Queen Street, Auckland CBD, Auckland 1010, New Zealand',
213
+ building: '', street: '1 Queen Street', suburb: 'Auckland CBD',
214
+ city: 'Auckland', state: 'AUK', postcode: '1010', country: 'NZ',
215
+ latitude: -36.8442, longitude: 174.7676,
216
+ }
217
+ ```
218
+
219
+ The key needs the **Maps JavaScript API** and the **Places API** enabled, and the host's origin
220
+ allowed under its HTTP referrer restrictions. The Maps script is loaded once per page and shared,
221
+ so several AddressLookups on one form cost one script tag between them.
222
+
223
+ #### AddressLookup Props
224
+
225
+ | Prop | Type | Default | Description |
226
+ |------|------|---------|-------------|
227
+ | `apiKey` | `string` | - | Google Maps API key (required) |
228
+ | `value` / `onSelect` | `PlaceAddress \| null` | - | Selection (controlled). `onSelect` also receives the full `PlaceDetail` |
229
+ | `countries` | `string \| string[]` | - | Restrict to ISO country codes, e.g. `['nz','au']`. Google allows five |
230
+ | `searchTypes` | `string[]` | `['address']` | Use `['establishment']` for businesses, `['geocode']` for anything mappable |
231
+ | `stateShortName` | `boolean` | `false` | Return `NSW` rather than `New South Wales` |
232
+ | `countryShortName` | `boolean` | `false` | Return `AU` rather than `Australia` |
233
+ | `showMapCard` | `boolean` | `true` | Map card on hover |
234
+ | `mapCardTarget` | `'list' \| 'rest' \| 'both'` | `'both'` | Where the card is offered |
235
+ | `mapCardDelayMs` | `number` | `400` | Pointer settle delay before the place is resolved |
236
+ | `mapZoom` | `number` | `15` | Map zoom on the card |
237
+ | `showCoordinates` / `showRatings` | `boolean` | `true` | Card detail toggles |
238
+ | `cardActions` | `ReactNode` | - | Content at the bottom of the card |
239
+ | `minSearchLength` | `number` | `3` | Short input never leaves the browser |
240
+ | `onError` | `(error) => void` | - | Search or place lookup failures |
241
+
242
+ The Places client is exported on its own for geocoding outside a form:
243
+ `loadGoogleMaps`, `searchPlacePredictions`, `getPlaceDetails`, `parsePlaceAddress`,
244
+ `placeMapUrl`, `isGoogleMapsReady`, `clearPlacesCache`.
245
+
146
246
  ### ParentPortal
147
247
 
148
248
  Renders Fluent UI content in the **parent document**, escaping an iframe boundary with full styling. Designed for Dynamics 365 web resources where dialogs must float above the entire D365 page rather than being trapped inside the iframe.