RFC: Customizing application of native element props and ref#18983
Conversation
|
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. Latest deployment of this branch, based on commit 511c32a:
|
Asset size changesSize Auditor did not detect a change in bundle size for any component! Baseline commit: 1e5b888607626cae4ff69b5b9055bf8b9537c45b (build) |
📊 Bundle size reportUnchanged fixtures
|
Perf Analysis (
|
| Scenario | Render type | Master Ticks | PR Ticks | Iterations | Status |
|---|---|---|---|---|---|
| Avatar | mount | 1034 | 1029 | 5000 | |
| BaseButton | mount | 1038 | 1043 | 5000 | |
| Breadcrumb | mount | 2917 | 2856 | 1000 | |
| ButtonNext | mount | 512 | 516 | 5000 | |
| Checkbox | mount | 1741 | 1697 | 5000 | |
| CheckboxBase | mount | 1520 | 1504 | 5000 | |
| ChoiceGroup | mount | 5544 | 5520 | 5000 | |
| ComboBox | mount | 1063 | 1115 | 1000 | |
| CommandBar | mount | 11382 | 11289 | 1000 | |
| ContextualMenu | mount | 7263 | 7249 | 1000 | |
| DefaultButton | mount | 1328 | 1330 | 5000 | |
| DetailsRow | mount | 4311 | 4313 | 5000 | |
| DetailsRowFast | mount | 4329 | 4324 | 5000 | |
| DetailsRowNoStyles | mount | 4141 | 4078 | 5000 | |
| Dialog | mount | 2699 | 2739 | 1000 | |
| DocumentCardTitle | mount | 174 | 147 | 1000 | |
| Dropdown | mount | 3717 | 3686 | 5000 | |
| FluentProviderNext | mount | 7478 | 7670 | 5000 | |
| FluentProviderWithTheme | mount | 383 | 392 | 10 | |
| FluentProviderWithTheme | virtual-rerender | 95 | 109 | 10 | |
| FluentProviderWithTheme | virtual-rerender-with-unmount | 510 | 542 | 10 | |
| FocusTrapZone | mount | 2149 | 2088 | 5000 | |
| FocusZone | mount | 2037 | 2083 | 5000 | |
| IconButton | mount | 2079 | 2065 | 5000 | |
| Label | mount | 378 | 398 | 5000 | |
| Layer | mount | 3457 | 3504 | 5000 | |
| Link | mount | 530 | 583 | 5000 | |
| MakeStyles | mount | 2093 | 2096 | 50000 | |
| MenuButton | mount | 1725 | 1764 | 5000 | |
| MessageBar | mount | 2296 | 2326 | 5000 | |
| Nav | mount | 3778 | 3785 | 1000 | |
| OverflowSet | mount | 1235 | 1196 | 5000 | |
| Panel | mount | 2578 | 2557 | 1000 | |
| Persona | mount | 937 | 957 | 1000 | |
| Pivot | mount | 1602 | 1577 | 1000 | |
| PrimaryButton | mount | 1430 | 1485 | 5000 | |
| Rating | mount | 8845 | 9048 | 5000 | |
| SearchBox | mount | 1494 | 1550 | 5000 | |
| Shimmer | mount | 2938 | 2933 | 5000 | |
| Slider | mount | 2278 | 2209 | 5000 | |
| SpinButton | mount | 5464 | 5572 | 5000 | |
| Spinner | mount | 455 | 468 | 5000 | |
| SplitButton | mount | 3914 | 3530 | 5000 | |
| Stack | mount | 574 | 604 | 5000 | |
| StackWithIntrinsicChildren | mount | 1859 | 1837 | 5000 | |
| StackWithTextChildren | mount | 5300 | 5324 | 5000 | |
| SwatchColorPicker | mount | 11253 | 11309 | 5000 | |
| Tabs | mount | 1555 | 1565 | 1000 | |
| TagPicker | mount | 2960 | 2985 | 5000 | |
| TeachingBubble | mount | 14346 | 14423 | 5000 | |
| Text | mount | 470 | 499 | 5000 | |
| TextField | mount | 1521 | 1591 | 5000 | |
| ThemeProvider | mount | 1301 | 1285 | 5000 | |
| ThemeProvider | virtual-rerender | 641 | 619 | 5000 | |
| ThemeProvider | virtual-rerender-with-unmount | 2087 | 2068 | 5000 | |
| Toggle | mount | 928 | 889 | 5000 | |
| buttonNative | mount | 120 | 124 | 5000 |
Perf Analysis (@fluentui/react-northstar)
Perf tests with no regressions
| Scenario | Current PR Ticks | Baseline Ticks | Ratio |
|---|---|---|---|
| ChatDuplicateMessagesPerf.default | 360 | 325 | 1.11:1 |
| AttachmentMinimalPerf.default | 190 | 172 | 1.1:1 |
| BoxMinimalPerf.default | 412 | 374 | 1.1:1 |
| ButtonMinimalPerf.default | 199 | 181 | 1.1:1 |
| ListWith60ListItems.default | 790 | 716 | 1.1:1 |
| CarouselMinimalPerf.default | 547 | 503 | 1.09:1 |
| ChatWithPopoverPerf.default | 463 | 425 | 1.09:1 |
| FormMinimalPerf.default | 516 | 480 | 1.08:1 |
| AnimationMinimalPerf.default | 489 | 456 | 1.07:1 |
| ImageMinimalPerf.default | 458 | 427 | 1.07:1 |
| ListMinimalPerf.default | 631 | 587 | 1.07:1 |
| HeaderMinimalPerf.default | 427 | 402 | 1.06:1 |
| TableMinimalPerf.default | 471 | 443 | 1.06:1 |
| TreeWith60ListItems.default | 210 | 198 | 1.06:1 |
| InputMinimalPerf.default | 1448 | 1381 | 1.05:1 |
| ListNestedPerf.default | 663 | 632 | 1.05:1 |
| MenuButtonMinimalPerf.default | 1947 | 1852 | 1.05:1 |
| TextMinimalPerf.default | 412 | 392 | 1.05:1 |
| LayoutMinimalPerf.default | 422 | 407 | 1.04:1 |
| ListCommonPerf.default | 761 | 734 | 1.04:1 |
| LoaderMinimalPerf.default | 825 | 797 | 1.04:1 |
| RefMinimalPerf.default | 264 | 254 | 1.04:1 |
| TableManyItemsPerf.default | 2273 | 2180 | 1.04:1 |
| ChatMinimalPerf.default | 770 | 751 | 1.03:1 |
| CheckboxMinimalPerf.default | 3121 | 3034 | 1.03:1 |
| DatepickerMinimalPerf.default | 6216 | 6028 | 1.03:1 |
| DividerMinimalPerf.default | 431 | 420 | 1.03:1 |
| PortalMinimalPerf.default | 193 | 188 | 1.03:1 |
| ReactionMinimalPerf.default | 446 | 431 | 1.03:1 |
| SliderMinimalPerf.default | 1852 | 1791 | 1.03:1 |
| DialogMinimalPerf.default | 856 | 838 | 1.02:1 |
| MenuMinimalPerf.default | 988 | 968 | 1.02:1 |
| PopupMinimalPerf.default | 674 | 658 | 1.02:1 |
| StatusMinimalPerf.default | 773 | 760 | 1.02:1 |
| TextAreaMinimalPerf.default | 601 | 588 | 1.02:1 |
| AlertMinimalPerf.default | 308 | 304 | 1.01:1 |
| DropdownManyItemsPerf.default | 785 | 775 | 1.01:1 |
| CustomToolbarPrototype.default | 4304 | 4248 | 1.01:1 |
| TooltipMinimalPerf.default | 1183 | 1172 | 1.01:1 |
| TreeMinimalPerf.default | 947 | 934 | 1.01:1 |
| AvatarMinimalPerf.default | 227 | 227 | 1:1 |
| ButtonOverridesMissPerf.default | 1889 | 1896 | 1:1 |
| LabelMinimalPerf.default | 458 | 459 | 1:1 |
| ProviderMergeThemesPerf.default | 1818 | 1812 | 1:1 |
| RadioGroupMinimalPerf.default | 537 | 538 | 1:1 |
| SplitButtonMinimalPerf.default | 4746 | 4757 | 1:1 |
| DropdownMinimalPerf.default | 3332 | 3364 | 0.99:1 |
| FlexMinimalPerf.default | 316 | 320 | 0.99:1 |
| GridMinimalPerf.default | 390 | 393 | 0.99:1 |
| SkeletonMinimalPerf.default | 414 | 418 | 0.99:1 |
| IconMinimalPerf.default | 716 | 720 | 0.99:1 |
| ToolbarMinimalPerf.default | 1093 | 1104 | 0.99:1 |
| EmbedMinimalPerf.default | 4619 | 4704 | 0.98:1 |
| ProviderMinimalPerf.default | 1126 | 1146 | 0.98:1 |
| ButtonSlotsPerf.default | 623 | 645 | 0.97:1 |
| HeaderSlotsPerf.default | 870 | 901 | 0.97:1 |
| ItemLayoutMinimalPerf.default | 1423 | 1460 | 0.97:1 |
| RosterPerf.default | 1350 | 1392 | 0.97:1 |
| AttachmentSlotsPerf.default | 1196 | 1244 | 0.96:1 |
| SegmentMinimalPerf.default | 387 | 405 | 0.96:1 |
| AccordionMinimalPerf.default | 174 | 183 | 0.95:1 |
| CardMinimalPerf.default | 640 | 671 | 0.95:1 |
| VideoMinimalPerf.default | 742 | 790 | 0.94:1 |
There was a problem hiding this comment.
IMO this is not ideal and also not intuitive, and I don't see which big impact would it make in layout if this wasn't adopted.
There was a problem hiding this comment.
I think the scenario for this was if someone wanted to apply styling (for example flex properties) to fields within a form. Then they would probably expect the top-level className to go to the top-level element.
There was a problem hiding this comment.
makeMergeProps will be deprecated after as prop simplification RFC
There was a problem hiding this comment.
Ah okay, I was wondering about that after trying to copy some of the Accordion code in Input and noticing makeMergeProps was gone. Where would this go instead?
There was a problem hiding this comment.
Right now this is done manually on state construction as you can verify on Accordion code. Biggest change from as prop simplification RFC is the usage of a more explicit approach for state declaration, it becomes dev responsibility to pass ref and other props to the proper slot.
|
IMO this RFC makes
I believe that this changes are valid and makes slots implementation even more generic and explicit. |
|
@ecraig12345 is this ready for review or do you plan to update it? |
|
@layershifter It's not ready for review. I need to update it but haven't gotten to do it yet. |
There was a problem hiding this comment.
Do I correctly understand that this RFC is proposing to do following?
<>
<Button root={{ type: "submit" }} />
<Link root={{ href: "#" }} />
<Accordion root={{ onClick: () => {} }} />
</>If so, I don't think it's a good idea as it will create confusion for customers. For example:
<>
{/* 😕 will this render <a href="#bar" />? */}
<Link href="#bar" root={{ href: "#foo" }} />
{/* 😕 what this will render at all? */}
<Link href="#bar" root={{ children: () => <Foo /> }}>
Hello world
</Link>
</>It's one of reasons why I would prefer to use wrapper than root as a slot:
- due a reason above IMO it's not good idea to expose
rootslot- then it cannot be consistent across components: some components will have
rootinprops, some will not, but all haverootslot
- then it cannot be consistent across components: some components will have
- it should not be (???) consistent (as components like
Inputare exceptions)
There was a problem hiding this comment.
To me the issue of which prop takes precedence isn't too big of a concern; we just define and document it (probably that the top-level prop wins), and if someone is confused, point them to the documentation.
There was a problem hiding this comment.
It's not fully clear what Checkbox does, can you please modify this example to be clearer?
For example (just to show where props are spreaded):
<Checkbox aria-label="foo" type="input" /><div {...slotProps.root}> // "aria-label" goes there
<div {...slotProps.checkmark } />
<input type="input" /> // "type" goes to "input"
<label {...slotProps.label} />
</div>There was a problem hiding this comment.
It's a bit beside the point in this example to show where the slot props go (I'm not sure why it's shown for the label); it's just intended to show the overall structure of these components and why the input can't be the actual DOM root.
There was a problem hiding this comment.
Hmm actually thinking about it more, using the slot props might be an easier way to understand the slot labeling rather than using comments
There was a problem hiding this comment.
IMO we should not do it as it will confuse customers:
// ❓ what "name" will have "input" element, "bar" or "foo"?
<Checkbox name="foo" input={{ name: 'foo' }} root={{ id: 'bar' }} />There should be clear rules for all components, for example:
<Checkbox {...inputProps} root={...rootProps} />inputPropsare going to a slotinputrootPropsare going to a slotroot- as there is way to pass props to
input- component does not haveinputslot
|
Updated to promote "discarded option 3" and have two proposed solutions: Option A: allow setting a different "primary" slot; explicitly expose
|
|
To make it clearer my main open question that would decide between A and C is: Should we special-case
The reason we might want to do this is based on the assumption that users would expect styling to be applied to the entire control, rather than an inner element. For example, it could be confusing why Both FluentUI v8 and Northstar v0 put I took a look at a few libraries and made a table of their behaviors here: #18804 (comment) |
No, as this will produce strange behavior. For example, both .foo#bar {
color: red;
}From JSX it's not clear if they will go to different elements: <Input id="bar" className="foo" /><--! 💥 CSS selector will not work -->
<div className="foo">
<input id="bar" />
</div>
Honestly, there is not enough data to make a decision based on it: once we will remove Fluent UI flavors from the table, it's 50/50. |
No.
Unfortunately, Northstar is not that consistent,
There are different use cases - for positioning the control inside a flex, you need to style the outermost element.
This is the right approach how we should make decisions, thank you very much for that 👍 My main concern is not about where For <Button className="only-here" /> |
I agree that table wasn't really enough to get a full picture. So I did a larger survey of components in a number of react-based libraries. I found that a large majority follow the rule " Also for what it's worth, we may want to weight FluentUI flavors more heavily, since that's what many of our users will be upgrading from. Option C does make the upgrade scenario more difficult, as it changes the behavior in a way that can't be caught by build breaks or warnings. We could likely write a codemod to help with that though. |
Option C works and gets my vote 👍, and gives instant compatibility to form validation libraries. I just see no point in calling it Let's just call this |
|
Thanks everyone for the input! I'll give a more detailed response later but quick FYI about @behowell's comparison table: note that similar to what @miroslavstastny said about |
|
I'm proposing Option D based on a meeting including @ecraig12345, @miroslavstastny, @levithomason and others: Start with Option C, with the following changes:
Some background on the reasoning:
|
I think we are kind of bound by our Let's look at evergreen's checkbox: <label @class @style @data>
<input type="checkbox" @id @name />
<div />
<span />
</label>It is just a mix and match of attributes, and styling the I think we need consistency becuase we are actively pushing slots as a part of our general architecture. Therefore my vote is still on option C |
|
My vote is still on C. IMO we should be consistent and // "id" & "className" should go to the same slot
<Input id="bar" className="foo" />
|
|
My 2c; Agree with @behowell above; Beyond those 2 props, ideally 99% of the other top level props "do the right thing" and you only need to break into slot props in a very very rare occasion. I think Option C (with the exceptions) should lead to this. You can style the |
There was a problem hiding this comment.
One way that I've used Fluent components in several applications is along with StyledComponents and Emotion. I extended or modified the style of a Fluent component using the style extension mechanism.
const StyledLink = styled(Link)`
color: palevioletred;
font-weight: bold;
`;My expectation is that the styles apply to the root element of the component. In the checkbox example, If I applied a margin of 4px and this was applied to the input - moving the bookends away from the input part - I would be very surprised.
When using StyledComponents with Fluent, I often examine the output DOM of a component and then apply selectors to update the style of child parts. Sometimes this doesn't work because the default part styles override. I then either need to create a styles object to style that part directly, or apply important to my style.
Whichever solution is chosen, I think it should support the expected behavior of developers working with other styling libraries.
miroslavstastny
left a comment
There was a problem hiding this comment.
@dzearing's argument re styling is definitely valid.
But also
Predictability is key.
Once we will do prop splitting we need to add slots for both root and input to be able to override the split - and this "three names for the two places" and "which one wins?" is what I see as breaking the predictability.
So styles+className go always to the outermost element versus never do automatic prop splitting?
I still prefer the latter but if quorum prefers the styles, I am ok with D:
#18983 (comment)
|
@miroslavstastny @ling1726 @bsunderhus Thanks for the approvals! Just to be 100% clear, which option are you approving? |
|
I've updated the RFC to reflect the Option D proposal from above. |
|
As @miroslavstastny exposed, we're still more inclined to the never do prop splitting approach (Option C). We have the feeling that this discussion is kind of stuck and none of the the parties are coming to an agreement, so if majority prefers Option D, let's go with Option D and simply get it done 💪🏼, we can always come back to this discussion latter |
miroslavstastny
left a comment
There was a problem hiding this comment.
Approving the current proposed design (aka Option D)
Add support for the primary slot described in RFC [RFC: Customizing application of native element props and ref](https://github.com/microsoft/fluentui/blob/master/rfcs/convergence/native-element-props.md) (#18983) * Add `getPartitionedNativeProps` helper function to make sure the right props go to the right slots * Update conformance tests to support primarySlot * Update utility types * Change Checkbox's primary slot to `input`, and other misc cleanup for Checkbox
) Add support for the primary slot described in RFC [RFC: Customizing application of native element props and ref](https://github.com/microsoft/fluentui/blob/master/rfcs/convergence/native-element-props.md) (microsoft#18983) * Add `getPartitionedNativeProps` helper function to make sure the right props go to the right slots * Update conformance tests to support primarySlot * Update utility types * Change Checkbox's primary slot to `input`, and other misc cleanup for Checkbox
Alternate version of #18804 with a slightly different approach, and broadening the focus beyond input components in some aspects. This came after a discussion in the Redmond components crew sync and incorporates various people's feedback from the previous PR.
EDIT: removed description because it was very outdated--see the final text and the discussion for details