@fluentui/react-popover 9.3.1 → 9.3.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. package/CHANGELOG.json +105 -1
  2. package/CHANGELOG.md +31 -2
  3. package/lib/components/Popover/usePopover.js +3 -10
  4. package/lib/components/Popover/usePopover.js.map +1 -1
  5. package/lib-amd/Popover.js +6 -0
  6. package/lib-amd/Popover.js.map +1 -0
  7. package/lib-amd/PopoverSurface.js +6 -0
  8. package/lib-amd/PopoverSurface.js.map +1 -0
  9. package/lib-amd/PopoverTrigger.js +6 -0
  10. package/lib-amd/PopoverTrigger.js.map +1 -0
  11. package/lib-amd/components/Popover/Popover.js +15 -0
  12. package/lib-amd/components/Popover/Popover.js.map +1 -0
  13. package/lib-amd/components/Popover/Popover.types.js +5 -0
  14. package/lib-amd/components/Popover/Popover.types.js.map +1 -0
  15. package/lib-amd/components/Popover/constants.js +15 -0
  16. package/lib-amd/components/Popover/constants.js.map +1 -0
  17. package/lib-amd/components/Popover/index.js +9 -0
  18. package/lib-amd/components/Popover/index.js.map +1 -0
  19. package/lib-amd/components/Popover/renderPopover.js +32 -0
  20. package/lib-amd/components/Popover/renderPopover.js.map +1 -0
  21. package/lib-amd/components/Popover/usePopover.js +144 -0
  22. package/lib-amd/components/Popover/usePopover.js.map +1 -0
  23. package/lib-amd/components/PopoverSurface/PopoverSurface.js +15 -0
  24. package/lib-amd/components/PopoverSurface/PopoverSurface.js.map +1 -0
  25. package/lib-amd/components/PopoverSurface/PopoverSurface.types.js +5 -0
  26. package/lib-amd/components/PopoverSurface/PopoverSurface.types.js.map +1 -0
  27. package/lib-amd/components/PopoverSurface/index.js +10 -0
  28. package/lib-amd/components/PopoverSurface/index.js.map +1 -0
  29. package/lib-amd/components/PopoverSurface/renderPopoverSurface.js +20 -0
  30. package/lib-amd/components/PopoverSurface/renderPopoverSurface.js.map +1 -0
  31. package/lib-amd/components/PopoverSurface/usePopoverSurface.js +65 -0
  32. package/lib-amd/components/PopoverSurface/usePopoverSurface.js.map +1 -0
  33. package/lib-amd/components/PopoverSurface/usePopoverSurfaceStyles.js +44 -0
  34. package/lib-amd/components/PopoverSurface/usePopoverSurfaceStyles.js.map +1 -0
  35. package/lib-amd/components/PopoverTrigger/PopoverTrigger.js +17 -0
  36. package/lib-amd/components/PopoverTrigger/PopoverTrigger.js.map +1 -0
  37. package/lib-amd/components/PopoverTrigger/PopoverTrigger.types.js +5 -0
  38. package/lib-amd/components/PopoverTrigger/PopoverTrigger.types.js.map +1 -0
  39. package/lib-amd/components/PopoverTrigger/index.js +9 -0
  40. package/lib-amd/components/PopoverTrigger/index.js.map +1 -0
  41. package/lib-amd/components/PopoverTrigger/renderPopoverTrigger.js +13 -0
  42. package/lib-amd/components/PopoverTrigger/renderPopoverTrigger.js.map +1 -0
  43. package/lib-amd/components/PopoverTrigger/usePopoverTrigger.js +61 -0
  44. package/lib-amd/components/PopoverTrigger/usePopoverTrigger.js.map +1 -0
  45. package/lib-amd/index.js +20 -0
  46. package/lib-amd/index.js.map +1 -0
  47. package/lib-amd/popoverContext.js +28 -0
  48. package/lib-amd/popoverContext.js.map +1 -0
  49. package/lib-commonjs/components/Popover/usePopover.js +3 -10
  50. package/lib-commonjs/components/Popover/usePopover.js.map +1 -1
  51. package/package.json +9 -9
  52. package/Spec.md +0 -516
package/Spec.md DELETED
@@ -1,516 +0,0 @@
1
- # @fluentui/react-popover Spec
2
-
3
- ## Background
4
-
5
- `Popovers` contain content that is opened after interacting with visible content. The content does not belong to the flow of visible information and is rendered out of DOM order. The content can display complementary information to existing content, or serve as a lightweight Dialog with interactable content.
6
-
7
- ## Prior Art
8
-
9
- - [Open UI research](https://github.com/openui/open-ui/pull/205)
10
- - [Github epic issue #17920](https://github.com/microsoft/fluentui/issues/17920)
11
-
12
- ### v0/v8 components
13
-
14
- - [v8 Callout](https://developer.microsoft.com/en-us/fluentui#/controls/web/callout)
15
- - [v0 Popup](https://fluentsite.z22.web.core.windows.net/0.54.0/components/popup/definition)
16
-
17
- ### Callout in v8
18
-
19
- The v8 `Callout` component only covers the positioned content functionality. The default usage involves conditional rendering of the `Callout`. The `onDismiss` prop requests the `Callout` to close on events such as clicking outside or pressing the escape key.
20
-
21
- ```tsx
22
- {isCalloutVisible && (
23
- <Callout
24
- target={`#${targetId}`}
25
- onDismiss={toggleIsCalloutVisible}
26
- >
27
- {children}
28
- </Callout>
29
- }
30
- ```
31
-
32
- Focus traps are supported for this scenario using a different component.
33
-
34
- ```tsx
35
- {isCalloutVisible && (
36
- <FocusTrapCallout
37
- setInitialFocus
38
- target={`#${targetId}`}
39
- onDismiss={toggleIsCalloutVisible}
40
- >
41
- {children}
42
- </FocusTrapCallout>
43
- }
44
- ```
45
-
46
- ### Popup in v0
47
-
48
- The v0 `Popup` comes in both the controlled an uncontrolled variant that includes a `trigger` element which will open the `Popup`. Four interactions to open the `Popup` are supported:
49
-
50
- - Click
51
- - Hover
52
- - Context (i.e. right click)
53
- - Focus
54
-
55
- In the controlled variant, an `onOpenChange` callback requests open/close of the popup to the user. This callback handles all the interactions for the trigger (above) and events such as clicking outside and the escape key.
56
-
57
- ```tsx
58
- // Uncontrolled
59
- <Popup
60
- trigger={<Button icon={<MoreIcon />} title="Show popup" />}
61
- content={popupContent}
62
- on=['click', 'hover', 'context', 'focus']
63
- />
64
-
65
- // Controlled
66
- <Popup
67
- open={open}
68
- onOpenChange={(e, { open }) => setOpen(open)}
69
- trigger={<Button icon={<MoreIcon />} title="Show popup" />}
70
- content={popupContent}
71
- on=['click', 'hover', 'context', 'focus']
72
- />
73
- ```
74
-
75
- Focus trap is enabled using the `trapFocus` prop.
76
-
77
- A `target` prop also exists so that the `Popup` does not have to be anchored to the `trigger` element.
78
-
79
- ### Position/Alignment hints
80
-
81
- Both libraries provide an API that achieves the same end result for positioning and alignment. Below is a table that maps the v8 `DirectionalHint` with the v0 props of `position` and `alignment`
82
-
83
- | DirectionalHint (v7) | position (v0) | align (v0) |
84
- | -------------------- | ------------- | ---------- |
85
- | topLeftEdge | above | start |
86
- | topCenter | above | center |
87
- | topRightEdge | above | bottom |
88
- | topAutoEdge | above | |
89
- | bottomLeftEdge | below | start |
90
- | bottomCenter | below | center |
91
- | bottomRightEdge | below | bottom |
92
- | bottomAutoEdge | below | |
93
- | leftTopEdge | before | top |
94
- | leftCenter | before | center |
95
- | leftBottomEdge | before | bottom |
96
- | rightTopEdge | after | before |
97
- | rightCenter | after | center |
98
- | rightBottomEdge | after | bottom |
99
-
100
- v8 uses `left` and `right`. v0 uses `before` and `after`. v0 vocabulary tries to be consistent regardless of RTL state. It's also possible to supply an explicit RTL hint to v8 which is a flip by default. v0 will flip by default but requires the consumer to detect RTL scenarios and modify props in these situations.
101
-
102
- In general the separation of both the position and alignment in v0 results in an API that is easier to use if a consumer only needs to modify one of the two props. However both try to achieve the same result in the end.
103
-
104
- It's important to note that if an incorrect pair of `position` and `align` are provided in v0, then `position` takes priority and `align` is set to `center`
105
-
106
- ### Offset
107
-
108
- ```tsx
109
- <Callout
110
- // single number value
111
- gap={100}
112
- />
113
-
114
- <Popup
115
- offset={[-100, 100]}
116
- />
117
-
118
- // offset can also be a function of raw Popper properties
119
- const offsetFunction = ({
120
- popper: PopperJs.Rect;
121
- reference: PopperJs.Rect;
122
- placement: PopperJs.Placement;
123
- }) => ([popper.width, -popper.height])
124
- ```
125
-
126
- v8 positioning can only apply a numerical value to the first part position attribute of DirectionalHint. v0 supports a function to defer calculation at runtime. v0 also supports offset of the Popup in both axes while supporting RTL flips for offset values.
127
-
128
- ### Bounds and overflow
129
-
130
- ```tsx
131
- <Popup
132
- ...
133
- flipBoundary={htmlElement}
134
- overflowBoundary={htmlElement}
135
- mountNode={htmlElement}
136
- />
137
- ```
138
-
139
- v0 `Popup` provides 3 different properties to handle bounds and overflow:
140
-
141
- - flipBoundary - the bounds to calculate when to flip positioning of the popup
142
- - overflowBoundary - the bounds to shift the popup without overflowing
143
- - mountNode - where the popup is actually rendered in the DOM, by default this is a portal to a div in body
144
-
145
- ```tsx
146
- <Callout
147
- // pixel values for bounding rectangle
148
- // defaults to target window as default bounding rectangle
149
- bounds={{height: 0, width: 0, top: 0, left:0 , right: 0, bottom: 0}}
150
- // callback for bounds
151
- bounds{(target, targetWindow) => ({/*Same object as above*/})}
152
- target={htmlElement}
153
-
154
- // renders to a portal node on body
155
- layerProps={/*ILayerProps*/}
156
-
157
- // every single one of the above can all be declared here too
158
- calloutProps={{bounds, target}}
159
- />
160
- ```
161
-
162
- v8 `Callout` has no notion of separate boundaries for flip or overflow, and auto behaviour is used for flip and overflow 'pushing'.
163
-
164
- ### Events
165
-
166
- v8 provides the following positioning event callbacks
167
-
168
- - onLayerMounted -> proposed to be removed in converged Portal spec [#17824](https://github.com/microsoft/fluentui/pull/17824)
169
- - onPositioned -> `Callout` calls this when it finishes positioning the element
170
- - onScroll -> `Callout` calls this when the contents are scrolled
171
-
172
- ### Hidden mount
173
-
174
- v8 `Callout` provides two props which will allow mounting a hidden popup and disabling renders when the component is hidden. According to the PRs that introduced the features, it should be a performance optimization.
175
-
176
- - `hidden` [#4419](https://github.com/microsoft/fluentui/pull/4419)
177
- - `shouldUpdateWhenHidden` [#10465](https://github.com/microsoft/fluentui/pull/10465)
178
-
179
- ## Sample Code
180
-
181
- ```tsx
182
- <Popover>
183
- <PopoverTrigger>
184
- <button>Opens popover</button>
185
- </PopoverTrigger>
186
-
187
- <PopoverSurface>
188
- <h1>Popover</h2>
189
- <div>Some section</div>
190
- <div>Some section</div>
191
- </PopoverSurface>
192
- </Popover>
193
- ```
194
-
195
- ## Variants
196
-
197
- - Anchor content to a different target than the trigger
198
- - Inline popover rendered in DOM order
199
- - Open on:
200
- - Click
201
- - Hover
202
- - Context menu (right click)
203
- - Focus
204
-
205
- ## API
206
-
207
- The `Popover` component will use React context to manage both a trigger and content component.
208
-
209
- ### Popover
210
-
211
- Outer component that sets up context and does not render DOM.
212
-
213
- > TODO Discuss: dismiss on scroll ?
214
-
215
- > TODO Discuss: v8 `hidden` `shouldUpdateHidden` prop [#4419](https://github.com/microsoft/fluentui/pull/4419) [#10465](https://github.com/microsoft/fluentui/pull/10465)
216
-
217
- > TODO Discuss: v8 `onPositioned`
218
-
219
- > TODO Discuss: A11y -> Should only one popup be open at a time or is aria-hidden enough ?
220
-
221
- > TODO Discuss: merge position and align props -> no real reason they were separated in v0 in the first place
222
-
223
- > TODO Discuss: v0 `unstable`props supported out of the box ? (pinned, disableTether)
224
-
225
- > TODO Discuss v0 `shouldDismissOnWindowFocus` ?
226
-
227
- The `@fluentui/react-positioning` library that exports the `usePopper` hook which will power the `Popover` contains more than the declared props here. These extra positioning props should be exposed as required.
228
-
229
- ```typescript
230
- export type PopoverProps = {
231
- /**
232
- * Controls the popover open state
233
- */
234
- open?: boolean;
235
-
236
- /**
237
- * Call back when the component requests to change value
238
- */
239
- onOpenChange?: (e: OpenPopoverEvents, data: OpenEventData) => void;
240
-
241
- /**
242
- * Flag to open the Popover by hovering the trigger
243
- */
244
- openOnHover?: boolean;
245
-
246
- /**
247
- * Anchor the popover to an element other than the trigger
248
- */
249
- target?: HTMLElement;
250
-
251
- /**
252
- * Popover position relative to target
253
- */
254
- position?: 'above' | 'below' | 'before' | 'after';
255
-
256
- /**
257
- * Popover alignment relative to target
258
- */
259
- align?: 'top' | 'bottom' | 'start' | 'end' | 'center';
260
-
261
- /**
262
- * Popover offset value or callback with positioning props
263
- */
264
- offset?: OffsetFunction | [number, number];
265
-
266
- /**
267
- * Renders `PopoverSurface` to a portal out of DOM order
268
- *
269
- * @default document.body
270
- */
271
- mountNode?: string;
272
-
273
- /**
274
- * Explicitly render the popover in DOM order
275
- */
276
- inline?: boolean;
277
-
278
- /**
279
- * Traps focus inside the popup and applies modal dialog behaviour
280
- */
281
- trapFocus?: boolean;
282
-
283
- /**
284
- * Covers the target that the popover is anchored to. This is the `PopoverTrigger` unless `target` prop is used
285
- */
286
- coverTarget?: boolean;
287
-
288
- /**
289
- * Do not render an arrow pointing to the target element. This is the `PopoverTrigger` unless `target` prop is used
290
- */
291
- noArrow?: boolean;
292
-
293
- /**
294
- * Sets the delay for closing popover on mouse leave
295
- */
296
- mouseLeaveDelay?: number;
297
-
298
- /**
299
- * Close when scrolling outside of it
300
- */
301
- closeOnScroll?: boolean;
302
- };
303
- ```
304
-
305
- ### PopoverTrigger
306
-
307
- This component does not render DOM. Utility component that clones a single child and applies HTML event callbacks to control the open/dismiss of the popover.
308
-
309
- ```typescript
310
- export type PopoverTriggerProps = {
311
- /**
312
- * Should only be a single child
313
- */
314
- children?: React.ReactElement;
315
- };
316
- ```
317
-
318
- ### PopoverSurface
319
-
320
- This component renders the positioned HTML element and renders user provided children. Renders as `<div>` by default.
321
-
322
- ```typescript
323
- export type PopoverSurfaceProps = {
324
- children?: React.ReactNode;
325
- };
326
- ```
327
-
328
- ## Structure
329
-
330
- Default popover
331
-
332
- ```tsx
333
- <div id="container">
334
- <Popover>
335
- <PopoverTrigger>
336
- <button>Trigger</button>
337
- </PopoverTrigger>
338
-
339
- <PopoverSurface>
340
- {children}
341
- </PopoverSurface>
342
- </Popover>
343
- <div>
344
-
345
- // Expected Markup
346
- <div id="container">
347
- <button aria-expanded="false">Trigger</button>
348
- </div>
349
-
350
- // on document.body
351
- <div role="group">
352
- {/** content */}
353
- </div>
354
- ```
355
-
356
- Popover that traps focus
357
-
358
- ```tsx
359
- <div id="container">
360
- <Popover trapFocus>
361
- <PopoverTrigger>
362
- <button>Trigger</button>
363
- </PopoverTrigger>
364
-
365
- <PopoverSurface>
366
- {children}
367
- </PopoverSurface>
368
- </Popover>
369
- <div>
370
-
371
- // Expected Markup
372
- <div id="container">
373
- <button aria-expanded="false">Trigger</button>
374
- </div>
375
-
376
- // on document.body
377
- <div role="dialog" aria-modal="true">
378
- {/** content */}
379
- </div>
380
- ```
381
-
382
- Inline popover
383
-
384
- ```tsx
385
- <div id="container">
386
- <Popover inline>
387
- <PopoverTrigger>
388
- <button>Trigger</button>
389
- </PopoverTrigger>
390
-
391
- <PopoverSurface>
392
- {children}
393
- </PopoverSurface>
394
- </Popover>
395
- <div>
396
-
397
- // Expected Markup
398
- <div id="container">
399
- <button aria-expanded="false">Trigger</button>
400
- <div>
401
- {/** content */}
402
- </div>
403
- </div>
404
-
405
-
406
- ```
407
-
408
- ## Migration
409
-
410
- ### v8
411
-
412
- - `onDismiss` should listen to `onOpenChange`.
413
- - `preventDismissOnEvent` no longer exists, `onOpenChange` will return the associated event, so this functionality is still possible.
414
- - Removed `onRestoreFocus`. `Popover` will focus the trigger when closed by default. Any other behaviour can be done through an effect by users.
415
- - Removed `onPositioned`.
416
- - Removed `doNotLayer` with `inline`.
417
- - Removed all styling props supported by `Callout`. `makeStyles` should be used instead.
418
- - Removed `coverTarget`, use `offset` callback instead.
419
- - Removed `setInitialFocus`. If `trapFocus` is used, first focusable element is used. Users must handle other specific cases.
420
-
421
- ### v0
422
-
423
- - No more `autoFocus` for scenarios without `trapFocus`. Users should handle this scenario manually.
424
- - No more `tabbableTrigger`. Users can do this with their own trigger element.
425
- - No `PopoverSurface` props, v0 duplicated props from `Popover` to `PopoverSurface`, all props should be declared on converged `Popover`.
426
-
427
- ## Behaviors
428
-
429
- ### Trigger interactions
430
-
431
- A popover should support click, hover, context menu and focus interactions for the `PopoverTrigger`. These interactions should also be composable.
432
-
433
- #### Click
434
-
435
- Clicking the trigger when the popover is closed opens the popover.
436
-
437
- Clicking the trigger when the popover is open closes the popover.
438
-
439
- Click also includes `Spacebar` and `Enter` keys.
440
-
441
- #### Hover
442
-
443
- When the mouse hovers over the trigger the popover opens.
444
-
445
- When the mouse leaves the trigger and popover content, the popover closes.
446
-
447
- #### Context menu
448
-
449
- Right click on the trigger opens the popover. The popover anchors to the mouse position.
450
-
451
- Shift + F10 on the trigger opens the popover
452
-
453
- ### Additional interactions
454
-
455
- #### Escape key
456
-
457
- The popover closes with the escape key when the trigger or popover content has focus.
458
-
459
- #### Click outside
460
-
461
- The popover closes when a click happens outside the popover trigger or content.
462
-
463
- #### Scroll outside
464
-
465
- The context menu popover closes when scroll happens outside the popover trigger or content.
466
- When popover is configured with `closeOnScroll`, popover closes when scroll happens outside the popover trigger or content.
467
-
468
- ### Focus trap
469
-
470
- When the popover is configured to be a focus trap, focus the first focusable element inside the popover on open.
471
-
472
- ### Nesting
473
-
474
- Popovers should allow nesting
475
-
476
- ## Accessibility
477
-
478
- ### Existing patterns
479
-
480
- The [WAI Dialog pattern](https://www.w3.org/TR/wai-aria-practices-1.2/#dialog_modal) and its variants are the inspirations for `Popover` accessibility.
481
-
482
- - [Datepicker](https://www.w3.org/TR/wai-aria-practices-1.2/examples/dialog-modal/datepicker-dialog.html)
483
- - [Modal Dialog](https://www.w3.org/TR/wai-aria-practices-1.2/examples/dialog-modal/dialog.html)
484
-
485
- ### DOM element usage
486
-
487
- Only the `PopoverSurface` component will render DOM markup. By default the components renders an HTML `div` element.
488
-
489
- ### aria-hidden
490
-
491
- Using a Popover with a focus trap is no different from a modal dialog in terms of a11y. Therefore, aria-hidden must be applied to all non-interactive elements of the page when the Popover is open.
492
-
493
- This also means that a Popover should be closed when another Popover is opened if they are not nested. In a nested case, the parent Popovers need to be hidden.
494
-
495
- ### Accessible markup
496
-
497
- Accessible markup is divided into two scenarios:
498
-
499
- ```tsx
500
- // Popover that does not trap focus
501
- <button aria-expanded="false">Trigger</button>
502
- <div role="group">
503
- No focus trap
504
- </div>
505
-
506
- // Popover that does trap focus
507
- <div aria-hidden="true" /> // other content
508
- <div aria-hidden="true" /> // other content
509
- <div aria-hidden="true" className='fui-provider'>
510
- <button aria-expanded="false">Trigger</button>
511
- </div>
512
-
513
- <div role="dialog" aria-modal="true">
514
- Focus trapped
515
- </div>
516
- ```